1. 程式人生 > >Redis Cluster的FailOver失敗案例分析

Redis Cluster的FailOver失敗案例分析

場景:
      使用redis clusterRC1部署叢集,6臺機器,每臺部署16個例項,每個master使用一個slave,node_timeout為預設值(15s)。kill掉其中一個master發現failover完成不了。通過cluster nodes觀察,該節點一直處於pfail狀態。問題出在失敗判定上,一直處於PFail,說明完成不了PFail->Fail的轉換。然而同樣的配置在32節點的叢集中,Failover一點問題沒有。
FailOver設計實現:
      Failover,通俗地說,一個master有N(N>=1)個slave,當master掛掉以後,能選出一個slave晉升成Master繼續提供服務。Failover由失敗判定和Leader選舉兩部分組成,Redis Cluster採用去中心化(Gossip)的設計,每個節點通過傳送Ping(包括Gossip資訊)/Pong心跳的方式來探測對方節點的存活,如果心跳超時則標記對方節點的狀態為PFail,這個意思是說該節點認為對方節點可能失敗了,有可能是網路閃斷或者分割槽等其他原因導致通訊失敗。例如節點A給節點B發Ping/Pong心跳超時,則A將B標記為PFAIL,強調一點,僅是在A看來B節點失敗。要完全判定B失敗,則需要一種協商的方式,需要叢集中一半以上的Master節點認為B處於PFail狀態,才會正式將節點B標記為Fail。那麼問題來了,Master之間如何交換意見呢,或者說節點A如何知道其他Master也將B標記為PFfail了,並且能統計出是否有一半以上的Master認為B為PFail呢?前面提到Gossip,Gossip的主要作用就是資訊交換,在A給C發Ping的時候,A將已知節點隨機挑選三個節點新增到Ping包中發給C。既然是隨機,經過多次Gossip以後,A會將處於PFail的B告訴給C。在節點C上,B節點有一個失敗報告的連結串列,A告訴C,B可能失敗,將A節點新增到B節點的失敗報告連結串列中。經過叢集中所有節點之間多次Gossip,一旦B的失敗報告數量超過Master數量的一半以上,就立即標記B為Fail並廣播給整個叢集。那這樣還會有一個問題,假設一天之內失敗報告的數量超過Master的一半以上,同時報告的時間間隔又比較大,那麼就會產生誤判。所以得給失敗報告加上一個有效期,在一定的時間視窗內,失敗報告的數量超過Master的一半以上以後標記為Fail,這樣才能避免誤判。至此就把失敗判定說完了,剩下還有Leader選舉,Redis Cluster採用類似Raft的演算法,有一點不同的是並不是slave之間進行投票,而是在所有Master中間進行投票。這樣做的好處就是即使一主一從也能完成選舉,Redis Cluster這樣做也是有道理。slave不提供任務服務,如果允許掛N個節點,就得部署(2N+1)個slave,這是資源的極大浪費。Leader選舉和主題不太相關就不細講了,我寫了一個PPT講Redis Cluster的Failover設計(

http://vdisk.weibo.com/s/u78RGlrhC7FF/1422958608)。
問題定位:
      看完上面的FailOver設計實現,問題就不難定位了。在時間視窗內,失敗報告的數量沒有達到Master的一半以上,所以完成不了Pfail到Fail的轉換。Redis Cluster的這個時間視窗是cluster-node-timeout *  REDIS_CLUSTER_FAIL_REPORT_VALIDITY_MULT,其中REDIS_CLUSTER_FAIL_REPORT_VALIDITY_MULT=2,是一個常量,不能修改。那麼預設的失敗報告有效期是30s,在30s內不能收集到Master的一半以上的失敗報告,新加入的失敗報告趕不上失效的速度,所以一直完不成PFail到Fail的轉換,這就是問題所在。我首先想到調大REDIS_CLUSTER_FAIL_REPORT_VALIDITY_MULT到10,Failover能成功了,但耗時過長至少要在60s以上,並且不知要調大了有沒有副作用,就提了一個issue(
https://github.com/antirez/redis/issues/2285
)給作者 。作者看了以後,不確定副作用,但他覺得需要一個更加通用的解決方案。其實調大這個引數只是治標不治本,問題的根源是Gossip太慢了,隨機挑選3個節點,並且選中PFail的節點還需要有一定的概率。是不是可以優先考慮從PFail的節點集合中隨機選出一部分,再從正常節點中選出一部分,這就兼顧了PFail和新加入的節點,就又提了個issue(https://github.com/antirez/redis/issues/2336)。作者先做了一個調整,Gossip攜帶節點的數量不再是3而是叢集規模的1/10,他認為1/10是一個魔數,具體原因在cluster.c的clusterSendPing函式中有描述。然後在這個版本上進行測試,修改cluster-node-timeout為5s,發現Master的失敗判定只需要12s,這個時間包括PFail的標記和PFai->Fail的轉換。然而slave的失敗判定就不是很穩定了在20~70s之間。我覺得還不是很理想,作者說他測試的結果比我的要理想很多,並且說他又做了一些優化,還是先發布RC3吧。我比對了RC3和我測試版本的區別,發現作者在Gossip的時候新增對PFail或Fail節點的偏好,我重新在RC3上測試,結果很理想,Master失敗判定需要9s,Slave需要11s並且很穩定。

結論:

      如果在較大規模的RedisCluster叢集上遇到這個問題,果然地升級RC3吧。