成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

超大規模系統下,MySQL到Redis的數據同步也不難吧?

數據庫 新聞
本文將重點討論在超大規模系統中緩存會面臨什么樣的問題,以及應該使用什么樣的策略來更新緩存。

一、緩存穿透

超大規模系統的不能承受之痛

如何構建Redis集群?由于集群可以水平擴容,因此只要集群足夠大,理論上支持海量并發就不是問題。但是,如果并發請求數量的基數過大,那么即使只有很小比率的請求穿透緩存,直接訪問數據庫的請求其絕對數量也仍然不小。再加上大促期間的流量峰值,還是會存在因為緩存穿透而引發系統雪崩的風險。

那么,這個問題該如何解決呢?其實方法并不難想到,不讓請求穿透緩存就行了。如今內存存儲的價格一路走低,只要能買得起足夠多的服務器,Redis集群的容量就是無限的。 我們可以把全量數據都放在Redis集群中,處理讀請求的時候,只需要讀取Redis,而不用訪問數據庫,這樣就完全沒有“緩存穿透”的風險了。 實際上,很多大型互聯網公司都在使用這種方法。

不過,在Redis中緩存全量數據,又會引發一個新的問題。那就是,緩存中的數據應該如何更新呢?因為我們取消了緩存穿透的機制,在這種情況下,如果能從緩存中直接讀到數據,則可以直接返回,如果沒能讀到數據,那就只能返回錯誤了! 所以,當系統更新數據庫的數據之后,必須及時更新緩存。

至此,我們又要面對一個老問題:如何保證Redis中的數據與數據庫中的數據同步更新?可以用分布式事務來解決數據一致性的問題,但是這些方法都不太適合用來更新緩存。原因是,分布式事務對數據更新服務有很強的侵入性。這里仍以下單服務為例來說明,如果為了更新緩存,增加一個分布式事務,那么無論我們使用哪種分布式事務,下單服務的性能或多或少都會受到影響。還有一個問題是,如果Redis本身出現了故障,寫入數據失敗,則還會導致下單失敗的問題,相當于是降低了下單服務的性能和可用性,這樣肯定是不行的。

對于像訂單服務之類的核心業務,一個可行的方法是,啟動一個更新訂單緩存的服務,接收訂單變更的消息隊列(Message Queue,MQ)中的消息,然后更新Redis中緩存的訂單數據。使用訂單變更消息更新緩存的結構如圖1所示。因為對于這類核心的業務數據,使用方通常會非常多,服務本來就需要向外發送消息,增加一個消費訂閱,基本上不會增加額外的開發成本,也不需要對訂單服務本身做出任何更改。

圖1使用訂單變更消息更新緩存

對于上述方法,我們唯一需要擔心的問題是,如果消息丟失了,應該怎么辦?因為現在消息是緩存數據的唯一來源,一旦出現消息丟失的問題,緩存里缺失的那條數據就會永遠也無法補上,所以,必須保證整個消息鏈條的可靠性。不過,好在現在的MQ集群(比如Kafka或RocketMQ),都擁有高可用性和高可靠性的保證機制,只要能事先正確配置好,就可以滿足數據的可靠性要求。

像訂單服務這樣,由于本來就有現成的數據變更消息可以訂閱,因此像這樣更新緩存也是一個不錯的選擇,因為這種方式實現起來很簡單,對系統的其他模塊也完全沒有侵入。

二、使用Binlog實時更新Redis緩存

如果我們要緩存的數據,原本就沒有一份數據更新的消息隊列可以訂閱,又該怎么辦呢?下面就來介紹很多大型互聯網企業所采用的,也是更通用的解決方案。

數據更新服務只負責處理業務邏輯,更新MySQL,完全不用考慮如何更新緩存。 負責更新緩存的服務,把自己偽裝成一個MySQL的從節點,從MySQL接收并解析Binlog之后,就可以得到實時的數據變更信息,然后該服務就會根據這個變更信息去更新Redis緩存。訂閱Binlog更新緩存的結構如圖2所示。

圖2訂閱Binlog更新緩存的結構

訂閱Binlog更新緩存的方案,相較于上文中接收消息更新Redis緩存的方案,兩者的實現思路其實是一樣的,都是 異步實時訂閱數據變更信息以更新Redis緩存。 只不過,直接讀取Binlog這種方式,通用性更強。該方式不會要求訂單服務再發送訂單消息,訂單更新服務也不用額外考慮如何解決“消息發送失敗了該怎么辦?”這種數據一致性問題。

除此之外,由于在整個緩存更新鏈路上,減少了一個收發消息隊列的環節,從MySQL更新到Redis更新的時延變得更短,出現故障的可能性也更低,因此很多大型互聯網企業更青睞于采用這種方案。

訂閱Binlog更新緩存的方案唯一的缺點是,實現訂單緩存更新服務比較復雜,該方案畢竟不像接收消息那樣,收到的直接就是訂單數據,解析Binlog還是挺麻煩的。

很多開源的項目都提供了訂閱和解析MySQL Binlog的功能,下面就以比較常用的開源項目Canal為例來演示,如何實時接收Binlog更新Redis緩存。

Canal通過模擬MySQL主從復制的交互協議,把自己偽裝成一個MySQL的從節點,向MySQL主節點發送dump請求。MySQL收到請求后,就會向Canal開始推送Binlog,Canal解析Binlog字節流之后,將其轉換為便于讀取的結構化數據,供下游程序訂閱使用。圖3展示了如何使用Canal訂閱Binlog更新Redis中的訂單緩存。

圖3使用Canal訂閱Binlog更新緩存

在這個示例中,MySQL和Redis都在本地的默認端口上運行,MySQL的端口為3306,Redis的端口為6379。為了便于大家操作,下面還是以第5章中提到的賬戶余額表account_balance作為演示數據。

首先,下載并在本地解壓Canal當前最新的1.1.4版本,操作命令如下:

wget https://github.com/alibaba/canal/releases/download/canal-1.1.4/canal.deployer-1.1.4.tar.gz
tar zvfx canal.deployer-1.1.4.tar.gz

然后,配置MySQL,我們需要在MySQL的配置文件中開啟Binlog,并將Binlog的格式設置為ROW,配置項如下:

[mysqld]
log-bin=mysql-bin # 開啟Binlog。
binlog-format=ROW # 將Binlog格式設置為ROW。
server_id=1 # 配置一個ServerID。

接下來,為Canal新建一個專門的MySQL用戶并授權,以確保這個用戶有復制Binlog的權限,具體操作的SQL命令如下:

CREATE USER canal IDENTIFIED BY 'canal';
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';
FLUSH PRIVILEGES;

然后,重啟MySQL,以確保所有的配置都能生效。重啟后再檢查一下當前的Binlog文件和位置,SQL命令和輸出結果具體如下:

mysql> show master status;
+-------------+--------+------------+----------------+-----------------+
| File |Position|Binlog_Do_DB|Binlog_Ignore_DB|Executed_Gtid_Set|
+-------------+--------+------------+----------------+-----------------+
|binlog.000009| 155| | | |
+-------------+--------+------------+----------------+-----------------+

記錄下File和Position兩列的值,然后再來配置Canal。編輯Canal的實例配置文件canal/conf/example/instance.properties,以便讓Canal連接到我們的MySQL上,具體配置如下:

canal.instance.gtidon=false
# position info
canal.instance.master.address=127.0.0.1:3306
canal.instance.master.journal.name=binlog.000009
canal.instance.master.position=155
canal.instance.master.timestamp=
canal.instance.master.gtid=
# username/password
canal.instance.dbUsername=canal
canal.instance.dbPassword=canal
canal.instance.connectionCharset = UTF-8
canal.instance.defaultDatabaseName=test
# table regex
canal.instance.filter.regex=.*\\..*

這個配置文件需要配置MySQL的連接地址、庫名、用戶名和密碼,除此之外,還要配置canal.instance.master.journal.name和canal.instance.master.position這兩個屬性,取值就是剛剛記錄的File和Position兩列。然后就可以啟動Canal服務了,命令如下:

canal/bin/startup.sh

啟動之后再查看一下日志文件canal/logs/example/example.log,如果日志中沒有報錯信息,就說明Canal服務已啟動成功并連接到我們的MySQL上了。

Canal服務啟動之后,會開啟一個端口(11111)等待客戶端連接,客戶端連接上Canal服務之后,就可以從Canal服務拉取(PULL)數據了,每拉取一批數據,正確寫入Redis之后,需要向Canal服務返回處理成功的響應。如果發生客戶端程序宕機,或者處理失敗等異常情況,Canal服務沒有收到處理成功的響應,那么下次客戶端來拉取的就還是同一批數據,這樣就可以保證讀到的Binlog順序不會亂,并且不會丟失數據。

接下來,我們來開發一個賬戶余額緩存的更新程序,以下代碼都是用Java語言編寫的:

while (true) {
Message message = connector.getWithoutAck(batchSize); // 獲取指定數量的數據。
long batchId = message.getId();
try {
int size = message.getEntries().size();
if (batchId == -1 || size == 0) {
Thread.sleep(1000);
} else {
processEntries(message.getEntries(), jedis);
}


connector.ack(batchId); // 提交確認。
} catch (Throwable t) {
connector.rollback(batchId); // 處理失敗,回滾數據。
}
}

這個程序的邏輯并不復雜,程序啟動并連接到Canal服務后,就不停地拉取數據,如果沒有數據就休眠一會兒,如果有數據就調用processEntries方法處理并更新緩存。每批數據更新成功之后,都會調用ack方法向Canal服務返回成功響應,如果失敗則拋出異常之后再回滾。下面是processEntries方法的主要代碼:

for (CanalEntry.RowData rowData : rowChage.getRowDatasList()) {
if (eventType == CanalEntry.EventType.DELETE) { // 刪除。
jedis.del(row2Key("user_id", rowData.getBeforeColumnsList()));
} else if (eventType == CanalEntry.EventType.INSERT) { // 插入。
jedis.set(row2Key("user_id", rowData.getAfterColumnsList()), row2Value(rowData.getAfterColumnsList()));
} else { // 更新。
jedis.set(row2Key("user_id", rowData.getAfterColumnsList()), row2Value(rowData.getAfterColumnsList()));
}
}

上述代碼會根據事件類型分別進行處理,如果MySQL中的數據刪除了,就刪除Redis中對應的數據。如果是更新和插入操作,就調用Redis的SET命令來寫入數據。

下面就來啟動這個賬戶緩存更新服務以進行驗證。在賬戶余額表中插入一條記錄,SQL命令如下:

mysql> insert into account_balance values (888, 100, NOW(), 999);

然后,我們再來看一下Redis緩存,操作命令和輸出結果如下:

127.0.0.1:6379> get 888
"{\"log_id\":\"999\",\"balance\":\"100\",\"user_id\":\"888\",\
"timestamp\":\"2020-03-08 16:18:10\"}"

從上述輸出結果中我們可以看到,數據已經自動同步到Redis中了。GitHub上可以下載該示例的完整代碼,鏈接地址是:https://github.com/liyue2008/canal-to-redis-example。

三、總結

在處理超大規模并發的場景時,由于并發請求的數量非常大,即使只有少量的緩存穿透,也有可能卡死數據庫引發雪崩效應。對于這種情況,我們可以通過Redis緩存全量數據來徹底避免緩存穿透的問題。對于緩存數據更新的方法,我們可以通過訂閱數據更新的消息隊列來異步更新緩存,更通用的方法是,把緩存更新服務偽裝成一個MySQL從節點,訂閱MySQL的Binlog,通過Binlog來更新Redis緩存。

需要特別注意的是,無論是通過消息隊列還是Canal來異步更新緩存,系統對整個更新服務的數據可靠性和實時性要求都比較高,數據丟失或者更新慢了,都會造成Redis中的數據與MySQL中的數據不同步的問題。在把這套方案應用到生產環境之前,我們需要考慮一旦出現不同步的問題,應該采取什么樣的降級或補償方案。

作者介紹

李玥, 美團基礎技術部高級技術專家,極客時間《后端存儲實戰課》《消息隊列高手課》等專欄作者。曾在當當網、京東零售等公司任職。從事互聯網電商行業基礎架構領域的架構設計和研發工作多年,曾多次參與雙十一和618電商大促。專注于分布式存儲、云原生架構下的服務治理、分布式消息和實時計算等技術領域,致力于推進基礎架構技術的創新與開源。

本文摘編自《電商存儲系統實戰:架構設計與海量數據處理》,經出版方授權發布。

責任編輯:張燕妮 來源: 數倉寶貝庫
相關推薦

2024-04-30 07:00:00

公共云云策略云計算

2020-07-23 14:03:09

數據中心數據網絡

2016-12-14 11:44:25

阿里Docker大數據

2021-03-16 10:28:41

數據中心IT云計算

2022-12-30 14:14:51

數據中心服務器

2020-12-11 19:52:06

數據中心超大規模數據中心

2023-02-14 11:24:36

2024-05-13 10:42:05

云計算

2021-03-24 11:13:12

數據中心云計算物聯網

2020-10-30 11:09:30

Pandas數據代碼

2024-10-21 17:40:22

2025-02-26 08:30:00

2011-12-16 09:54:17

網絡架構網絡架構系統架構系統

2017-09-22 10:31:17

超大規模微型數據中心

2017-09-25 16:48:12

數據中心超大規模微型

2023-08-22 16:14:36

2023-08-02 15:46:29

2020-09-25 09:52:48

機器學習人工智能計算機

2023-01-11 21:11:37

RabbitMQRocketMQ消息中間件

2020-02-10 08:00:38

AI 數據人工智能
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产精品www | 国产精品夜间视频香蕉 | 日韩成人免费视频 | 福利视频大全 | 少妇性l交大片免费一 | 国产午夜精品久久 | 一区二区视频在线 | 国产一二三区电影 | 亚洲精品久久视频 | 国产在线精品一区二区 | 亚洲性在线| 日本 欧美 国产 | 色婷婷av一区二区三区软件 | 日屁网站 | 午夜噜噜噜 | 亚洲欧美在线视频 | 一级毛片免费完整视频 | 精品国产乱码久久久久久丨区2区 | 欧洲亚洲视频 | 国产高清av免费观看 | 亚洲免费人成在线视频观看 | 成人在线一区二区三区 | 91国内精品久久 | 亚洲一区二区三区在线视频 | 天天操天天天 | 在线一区视频 | 中文字幕在线观看一区 | 欧美黄 片免费观看 | 国产成人精品一区二区三区在线 | 日韩中文一区二区 | 久久之精品 | 日本午夜免费福利视频 | 久久久久中文字幕 | 欧美在线视频一区 | 国产精品视频在线播放 | 天天艹逼网 | 精品国产欧美一区二区 | 在线视频国产一区 | 亚洲一区二区在线视频 | 免费视频一区 | 亚洲成人久久久 |