31 事務機制
Redis 提供了 MULTI、EXEC 兩個命令來完成事務。
原子性
原子性的要求就是一個事務中的多個操作必須都完成,或者都不完成。
情況1:在執行EXEC命令前,客戶端傳送的命令有錯誤,且Redis例項檢出了(如語法錯誤)。
結果:執行EXEC時拒絕執行所有命令,返回事務失敗。
情況2:在執行EXEC命令前,命令有錯誤但Redis例項沒有檢出(如運算元據型別不匹配)。
結果:Redis會對錯誤命令報錯,但還是會把正確的命令執行完。
影響:事務的原子性無法保證。
注意:Redis沒有提供回滾機制。
DISCARD命令:主動放棄事務執行,把暫存的命令佇列清空。
情況3:執行EXEC時例項故障,導致事務執行失敗。
結果1:如果開啟了AOF,會有部分日誌記錄到AOF中,需要使用redis-check-aof命令,將未完成的事務操作剔除,保證原子性。
結果2:如果沒有開啟AOF,例項重啟後資料無法恢復。
由於 RDB 不會在事務執行時執行,所以 RDB 檔案中不會記錄只執行了一部分的結果資料。之後用 RDB 恢復例項資料,恢復的還是事務之前的資料。
一致性
資料庫中的資料在事務執行前後是一致的。
情況1:命令入隊時就報錯
結果:事務放棄執行,可以保證一致性。
情況2:命令入隊時沒報錯,執行時報錯
結果:錯誤命令不會執行,可以保證一致性
解析:一致的概念是資料符合資料庫本身的定義和要求,沒有包含非法或者無效的錯誤資料,只要沒有執行錯誤的命令,那麼就保證了一致性。
情況3:EXEC命令執行時例項故障
結果1:沒有開啟RDB或AOF,資料丟失,可以保證一致性。
結果2:使用RDB快照,RDB不會在事務中執行,可以保證一致性。
結果3:使用AOF日誌,需要使用redis-check-aof清理事務中已完成的操作,清理後資料一致性。
總結:Redis可以保證事務一致性。
隔離性
資料庫在執行一個事務時,其它操作無法存取到正在執行事務訪問的資料。
事務的隔離性受併發操作的影響,分為EXEC執行前和執行後兩個階段。
- EXEC執行前併發:需要使用WATCH機制保證隔離性;
- EXEC執行後併發:可以保證隔離性。
WATCH機制
在事務執行前,監控一個或多個鍵的值變化情況,當事務呼叫 EXEC 命令執行時,WATCH 機制會先檢查監控的鍵是否被其它客戶端修改了。
- 如果修改了,就放棄事務執行,避免事務的隔離性被破壞。
- 客戶端可以再次執行事務,如果沒有併發修改事務資料的操作了,事務就能正常執行,保證了隔離性。
Redis 是用單執行緒執行命令,EXEC 命令執行後,Redis 會保證先把命令佇列中的所有命令執行完。所以,在這種情況下,併發操作不會破壞事務的隔離性。
永續性
資料庫執行事務後,資料的修改要被持久化儲存下來。當資料庫重啟後,資料的值需要是被修改後的值。
情況1:未開啟AOF和RDB,重啟資料丟失,沒有永續性。
情況2:RDB模式,如果在事務執行後,RDB快照執行前當機,資料會丟失。
情況3:AOF三種配置都存在資料丟失情況(always會丟失一個事件迴圈的資料)。
解析:
每次執行客戶端命令的時候操作並沒有寫到aof檔案中,只是寫到了aof_buf記憶體當中,當進行下一個事件迴圈(event_loop)的時候執行beforeSleep之時,才會去fsync到disk中。
結論:Redis無法保證事務的永續性。
32 主從同步與故障切換
Redis 的主從同步機制不僅可以讓從庫服務更多的讀請求,分擔主庫的壓力,而且還能在主庫發生故障時,進行主從庫切換,提供高可靠服務。
主從資料不一致
主從資料不一致,指的是客戶端從庫中讀取到的值和主庫中的最新值不一致。
原因:主從庫的命令複製是非同步進行的。
- 主庫收到新的寫命令,傳送給從庫;
- 主庫在本地執行命令後,向客戶端返回結果
- 如果從庫沒有執行命令,主從資料就不一致了
從庫滯後執行命令原因:
- 網路傳輸延遲
- 從庫阻塞(執行集合操作等複雜命令)
解決方案:外部程式監控
- Redis的INFO replication命令,可以檢視主庫接收寫命令的進度資訊(master_repl_offset)和從庫複製寫命令的進度資訊(slave_repl_offset)
- 開發監控程式監控master_repl_offset和slave_repl_offset的差值,超過閾值客戶端不再讀該從庫
讀取過期資料
使用Redis主從叢集時,有時會讀取到過期資料,這是由Redis的過期資料刪除策略引起的。
Redis過期資料刪除策略:
惰性刪除:資料過期後不會立即刪除,而是等到有請求讀寫該資料時進行檢查,發現過期時再刪除。
- 優點:減少CPU資源使用。
- 缺點:佔用記憶體。
- 定期刪除:每隔一段時間(預設100ms)隨機選出一些資料判斷是否過期,刪除過期資料。
情況1:
如果客戶端從主庫讀取過期資料,主庫會觸發刪除操作;
如果客戶端從庫讀取過期資料,從庫不會觸發刪除操作,會返回空值(3.2以上版本)。
情況2:
Redis設定過期時間命令在從庫上可能被延後,此時可能讀取到過期資料。
過期時間命令:
- EXPIRE:設定存活時間x秒
- PEXPIRE:設定存活時間x毫秒
- EXPIREAT:設定過期時間戳(秒)
- PEXPIREAT:設定過期時間戳(毫秒)
建議:
- 在業務應用中使用 EXPIREAT/PEXPIREAT 命令,把資料的過期時間設定為具體的時間點,避免讀到過期資料。
- 注意主從節點時鐘要一致,讓主從節點和相同的NTP(時間)伺服器進行時鐘同步。
不合理配置項
protected-mode 配置項:限定哨兵能否被其它伺服器訪問,設定yes時哨兵間無法通訊,無法判斷主庫下線,造成redis不可用。
- 注意將protected-mode設定為no
- bing配置項設定為其它哨兵例項的IP地址
cluster-node-timeout配置項:設定叢集例項響應心跳訊息超時時間。如果主從且切換時間較長,會導致例項心跳超時;如切換例項超過半數,會被Redis Cluster判斷為異常,導致叢集掛掉。
- 建議設定為10~20秒
- slave-server-stale-data配置項:設定從庫能否處理資料讀寫命令,yes可能處理到過期資料,建議設定為no,在主從失去連結或資訊同步時,slave會對除了INFO和SLAVEOF外的所有命令回覆"SYNC with master in progress"
- slave-read-only配置項:從庫能否處理寫命令(只讀),yes時只能處理讀請求,無法處理寫請求。
33 腦裂
腦裂指主從叢集中,同時有兩個主節點,都能接收讀寫請求,導致不同客戶端向不同主節點寫入資料,導致資料丟失。
資料丟失原因:
從庫升級為主庫後,原主庫和新主庫重新進行全量同步,需要清空本地資料,載入新主庫的RDB檔案,切換期間原主庫寫入的資料就丟失了。
查詢原因
1.確認資料同步:
主從叢集資料丟失最常見原因是主庫的資料還沒有同步到從庫時,主庫發生故障,從庫升級為主庫後,未同步資料丟失。
判斷方法:計算master_repl_offset 和 slave_repl_offset 的差值,如果slave小於master,則資料丟失是由於同步未完成導致的。
2.排查客戶端操作日誌:
如果主從切換後,有客戶端仍在和原主庫通訊,則認為發生了腦裂。
3.腦裂原因:原主庫假故障
主庫下線原因:超過(quorum配置)的哨兵例項和主庫心跳都超時了,判斷主庫客觀下線,哨兵開始執行切換。切換完成後,客戶端和新主庫進行通訊。
主庫假故障:主庫沒有響應哨兵心跳,被判斷為客觀下線,在沒有完成主從切換時,又重新處理請求了,此時客戶端仍可以和原主庫通訊,寫入資料。
解決方案
1.限制主庫請求處理:
- min-salves-to-write:設定主庫能進行資料同步的最少從庫數量;
- min-slaves-max-lag:設定了主從庫間進行資料複製時,從庫給主庫傳送 ACK 訊息的最大延遲(以秒為單位)。
這兩個配置項搭配起來使用,分別給它們設定一定的閾值,假設為 N 和 T。
組合後主庫連線的從庫中至少有 N 個從庫,和主庫進行資料複製時的 ACK 訊息延遲不能超過 T 秒,否則,主庫就不會再接收客戶端的請求了。
建議:從庫有 K 個,可以將 min-slaves-to-write 設定為 K/2+1(如果 K 等於 1,就設為 1),將 min-slaves-max-lag 設定為十幾秒(例如 10~20s),在這個配置下,如果有一半以上的從庫和主庫進行的 ACK 訊息延遲超過十幾秒,我們就禁止主庫接收客戶端寫請求。
CAP
使用 CAP 理論來分析一下課程的內容:
- redis 叢集允許腦裂存在,其實是一種可用性高的特徵,但不保證資料一直。
- redis 透過設定兩個引數,一定程度上其實是在降低可用性,以提供資料一致性。
- 為什麼願意降低可用性?因為那部分的資料會因為主從切換而丟失,所以寧願不可用。
Redis本身不支援強一致性,因為保證強一致性的代價太大,從CAP角度看,就是放棄C,選擇A和P。
min-slaves-to-write 和 min-slaves-max-lag的設定,是為了避免主從資料不一致程度加重。兩個引數起作用時,相當於對主庫做降級,放棄了A,選擇C和P。
35 Codis叢集方案
Codis 叢集中包含了 4 類關鍵元件。
- codis server:這是進行了二次開發的 Redis 例項,其中增加了額外的資料結構,支援資料遷移操作,主要負責處理具體的資料讀寫請求。
- codis proxy:接收客戶端請求,並把請求轉發給 codis server。
- Zookeeper 叢集:儲存叢集後設資料,例如資料位置資訊和 codis proxy 資訊。
- codis dashboard 和 codis fe:共同組成了叢集管理工具。其中,codis dashboard 負責執行叢集管理工作,包括增刪 codis server、codis proxy 和進行資料遷移。而 codis fe 負責提供 dashboard 的 Web 操作介面,便於我們直接在 Web 介面上進行叢集管理。
工作流程
- 先使用 codis dashboard 設定 codis server 和 codis proxy 的訪問地址,然後它們開始接收連線。
- 當客戶端要讀寫資料時,客戶端直接和 codis proxy 建立連線(RESP 互動協議)。
- codis proxy 接收到請求,根據對映關係把請求轉發給相應的 codis server 進行處理,處理完成後由proxy再返回資料給客戶端。
資料分佈
- Codis 叢集一共有 1024 個 Slot(邏輯槽),手動或自動分配給codis server。
- 客戶端讀取資料時,使用CRC32演算法計算key的雜湊值,對1024取模,對應slot編號。
- Slot 和 codis server 的對映關係稱為資料路由表(簡稱路由表),儲存在proxy和Zookeeper。
- 例項增減時,路由表被修改,dashbaord會把新表發給proxy。
注:Redis Cluster中,路由表在每個例項上儲存一份,變化時需要在所有例項間傳遞。
叢集擴容
增加server:
Codis 叢集按照 Slot 的粒度進行資料遷移,我們來看下遷移的基本流程。
- 在源 server 上,Codis 從要遷移的 Slot 中隨機選擇一個資料,傳送給目的 server。
- 目的 server 確認收到資料後,會給源 server 返回確認訊息。這時,源 server 會在本地將剛才遷移的資料刪除。
- 第一步和第二步就是單個資料的遷移過程。Codis 會不斷重複這個遷移過程,直到要遷移的 Slot 中的資料全部遷移完成。
同步遷移:資料傳送到目的server並執行完的過程中,源server阻塞,無法處理新請求。
非同步遷移:
- 目的server收到資料後返回ACK,表示遷移完成,源server刪除資料,不需要等命令執行完。
- 拆分指令:對bigkey中每個元素用一條指令遷移,並設定臨時過期時間,遷移失敗後會過期刪除。
增加proxy:
codis proxy 的訪問連線資訊都會儲存在 Zookeeper 上。
當新增了 proxy 後,Zookeeper 上會有最新的訪問列表,客戶端也就可以從 Zookeeper 上讀取 proxy 訪問列表,把請求傳送給新增的 proxy。
可靠性
Codis server 其實就是 Redis 例項,只不過增加了和叢集操作相關的命令。
Codis 給每個 server 配置從庫,並使用哨兵機制進行監控,當發生故障時,主從庫可以進行切換,從而保證了 server 的可靠性。
codis proxy 使用 Zookeeper 叢集儲存路由表,可以充分利用 Zookeeper 的高可靠性保證來確保 codis proxy 的可靠性。
當 codis proxy 發生故障後,直接重啟 proxy 就行。重啟後的 proxy,可以透過 codis dashboard 從 Zookeeper 叢集上獲取路由表,然後,就可以接收客戶端請求進行轉發了。
方案選擇
- Codis更成熟穩定
- 單例項客戶端改叢集,選Codis可以避免修改業務應用中的客戶端
- Codis不支援Redis新版本(>3.2.8)命令和資料型別
- Codis支援非同步遷移,叢集遷移比較頻繁時優先選擇
建議:
當你有多條業務線要使用 Codis 時,可以啟動多個 codis dashboard,每個 dashboard 管理一部分 codis server,同時,再用一個 dashboard 對應負責一個業務線的叢集管理,這樣,就可以做到用一個 Codis 叢集實現多條業務線的隔離管理了。