在進入正文以前,順便在此給你們推薦一個Java架構方面的交流學習羣:698581634,裏面會分享一些資深架構師錄製的視頻錄像:有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化這些成爲架構師必備的知識體系,主要針對Java開發人員提高本身,突破瓶頸,相信你來學習,會有提高和收穫。在這個羣裏會有你須要的內容 朋友們請抓緊時間加入進來吧。redis
本文將要介紹的哨兵,它基於 Redis 主從複製,主要做用即是解決主節點故障恢復的自動化問題,進一步提升系統的高可用性。算法
文章將首先介紹哨兵的做用和架構;而後講述哨兵系統的部署方法,以及經過客戶端訪問哨兵系統的方法;而後簡要說明哨兵實現的基本原理;最後給出關於哨兵實踐的一些建議。(注:文章內容基於 Redis 3.0 版本)安全
哨兵的做用和架構性能優化
哨兵的做用服務器
在介紹哨兵以前,首先從宏觀角度回顧一下 Redis 實現高可用相關的技術。網絡
它們包括:持久化、複製、哨兵和集羣,其主要做用和解決的問題是:架構
複製主要實現了數據的多機備份,以及對於讀操做的負載均衡和簡單的故障恢復。缺陷:故障恢復沒法自動化;寫操做沒法負載均衡;存儲能力受到單機的限制。併發
下面說回哨兵,Redis Sentinel,即 Redis 哨兵,在 Redis 2.8 版本開始引入。哨兵的核心功能是主節點的自動故障轉移。負載均衡
下面是 Redis 官方文檔對於哨兵功能的描述:運維
其中,監控和自動故障轉移功能,使得哨兵能夠及時發現主節點故障並完成轉移;而配置提供者和通知功能,則須要在與客戶端的交互中才能體現。
這裏對「客戶端」一詞在本文的用法作一個說明:在前面的文章中,只要經過 API 訪問 Redis 服務器,都會稱做客戶端,包括 redis-cli、Java 客戶端 Jedis 等。
爲了便於區分說明,本文中的客戶端並不包括 redis-cli,而是比 redis-cli 更加複雜。
redis-cli 使用的是 Redis 提供的底層接口,而客戶端則對這些接口、功能進行了封裝,以便充分利用哨兵的配置提供者和通知功能。
哨兵的架構
典型的哨兵架構圖以下所示:
它由兩部分組成,哨兵節點和數據節點:
哨兵系統的部署方法
這一部分將部署一個簡單的哨兵系統,包含 1 個主節點、2 個從節點和 3 個哨兵節點。
方便起見:全部這些節點都部署在一臺機器上(局域網 IP:192.168.92.128),使用端口號區分;節點的配置儘量簡化。
部署主從節點
哨兵系統中的主從節點,與普通的主從節點配置是同樣的,並不須要作任何額外配置。
下面分別是主節點(port=6379)和 2 個從節點(port=6380/6381)的配置文件,配置都比較簡單,再也不詳述。
#redis-6379.conf port 6379 daemonize yes logfile "6379.log" dbfilename "dump-6379.rdb" #redis-6380.conf port 6380 daemonize yes logfile "6380.log" dbfilename "dump-6380.rdb" slaveof 192.168.92.128 6379 #redis-6381.conf port 6381 daemonize yes logfile "6381.log" dbfilename "dump-6381.rdb" slaveof 192.168.92.128 6379
配置完成後,依次啓動主節點和從節點:
redis-server redis-6379.conf redis-server redis-6380.conf redis-server redis-6381.conf
節點啓動後,鏈接主節點查看主從狀態是否正常,以下圖所示:
部署哨兵節點
哨兵節點本質上是特殊的 Redis 節點。3 個哨兵節點的配置幾乎是徹底同樣的,主要區別在於端口號的不一樣(26379/26380/26381)。
下面以 26379 節點爲例,介紹節點的配置和啓動方式,配置部分儘可能簡化,更多配置會在後面介紹。
#sentinel-26379.conf port 26379 daemonize yes logfile "26379.log" sentinel monitor mymaster 192.168.92.128 6379 2
其中,sentinel monitor mymaster 192.168.92.128 6379 2 配置的含義是:該哨兵節點監控 192.168.92.128:6379 這個主節點。
該主節點的名稱是 mymaster,最後的 2 的含義與主節點的故障斷定有關:至少須要 2 個哨兵節點贊成,才能斷定主節點故障並進行故障轉移。
哨兵節點的啓動有兩種方式,兩者做用是徹底相同的:
redis-sentinel sentinel-26379.conf redis-server sentinel-26379.conf --sentinel
按照上述方式配置和啓動以後,整個哨兵系統就啓動完畢了,能夠經過 redis-cli 鏈接哨兵節點進行驗證。
以下圖所示:能夠看出 26379 哨兵節點已經在監控 mymaster 主節點(即192.168.92.128:6379),並發現了其 2 個從節點和另外 2 個哨兵節點。
此時若是查看哨兵節點的配置文件,會發現一些變化,以 26379 爲例:
其中,dir 只是顯式聲明瞭數據和日誌所在的目錄(在哨兵語境下只有日誌);known-slave 和 known-sentinel 顯示哨兵已經發現了從節點和其餘哨兵。
帶有 epoch 的參數與配置紀元有關(配置紀元是一個從 0 開始的計數器,每進行一次領導者哨兵選舉,都會 +1;領導者哨兵選舉是故障轉移階段的一個操做,在後文原理部分會介紹)。
演示故障轉移
哨兵的四個做用中,配置提供者和通知須要客戶端的配合,本文將在下一章介紹客戶端訪問哨兵系統的方法時再詳細介紹。
這一小節將演示當主節點發生故障時,哨兵的監控和自動故障轉移功能。
(1)首先,使用 Kill 命令殺掉主節點:
(2)若是此時當即在哨兵節點中使用 info Sentinel 命令查看,會發現主節點尚未切換過來,由於哨兵發現主節點故障並轉移,須要一段時間。
(3)一段時間之後,再次在哨兵節點中執行 info Sentinel 查看,發現主節點已經切換成 6380 節點。
可是同時能夠發現,哨兵節點認爲新的主節點仍然有 2 個從節點,這是由於哨兵在將 6380 切換成主節點的同時,將 6379 節點置爲其從節點。
雖然 6379 從節點已經掛掉,可是因爲哨兵並不會對從節點進行客觀下線(其含義將在原理部分介紹),所以認爲該從節點一直存在。
當 6379 節點從新啓動後,會自動變成 6380 節點的從節點,下面驗證一下。
(4)重啓 6379 節點:能夠看到 6379 節點成爲了 6380 節點的從節點。
(5)在故障轉移階段,哨兵和主從節點的配置文件都會被改寫。
對於主從節點,主要是 slaveof 配置的變化:新的主節點沒有了 slaveof 配置,其從節點則 slaveof 新的主節點。
對於哨兵節點,除了主從節點信息的變化,紀元(epoch)也會變化,下圖中能夠看到紀元相關的參數都 +1 了。
小結
哨兵系統的搭建過程,有幾點須要注意:
客戶端訪問哨兵系統
上一小節演示了哨兵的兩大做用:監控和自動故障轉移。本小節則結合客戶端演示哨兵的另外兩個做用:配置提供者和通知。
代碼示例
在介紹客戶端的原理以前,先以 Java 客戶端 Jedis 爲例,演示一下使用方法:下面代碼能夠鏈接咱們剛剛搭建的哨兵系統,並進行各類讀寫操做(代碼中只演示如何鏈接哨兵,異常處理、資源關閉等未考慮)。
public static void testSentinel() throws Exception { String masterName = "mymaster"; Set<String> sentinels = new HashSet<>(); sentinels.add("192.168.92.128:26379"); sentinels.add("192.168.92.128:26380"); sentinels.add("192.168.92.128:26381"); JedisSentinelPool pool = new JedisSentinelPool(masterName, sentinels); //初始化過程作了不少工做 Jedis jedis = pool.getResource(); jedis.set("key1", "value1"); pool.close(); }
客戶端原理
Jedis 客戶端對哨兵提供了很好的支持。如上述代碼所示,咱們只須要向 Jedis 提供哨兵節點集合和 masterName,構造 JedisSentinelPool 對象。
而後即可以像使用普通 Redis 鏈接池同樣來使用了:經過 pool.getResource() 獲取鏈接,執行具體的命令。
在整個過程當中,咱們的代碼不須要顯式的指定主節點的地址,就能夠鏈接到主節點;代碼中對故障轉移沒有任何體現,就能夠在哨兵完成故障轉移後自動的切換主節點。
之因此能夠作到這一點,是由於在 JedisSentinelPool 的構造器中,進行了相關的工做;主要包括如下兩點:
遍歷哨兵節點,獲取主節點信息:遍歷哨兵節點,經過其中一個哨兵節點 + masterName 得到主節點的信息。
該功能是經過調用哨兵節點的 sentinelget-master-addr-by-name 命令實現,該命令示例以下:
一旦得到主節點信息,中止遍歷(所以通常來講遍歷到第一個哨兵節點,循環就中止了)。
增長對哨兵的監聽:這樣當發生故障轉移時,客戶端即可以收到哨兵的通知,從而完成主節點的切換。
具體作法是:利用 Redis 提供的發佈訂閱功能,爲每個哨兵節點開啓一個單獨的線程,訂閱哨兵節點的 + switch-master 頻道,當收到消息時,從新初始化鏈接池。
小結
經過客戶端原理的介紹,咱們能夠加深對哨兵功能的理解。
配置提供者:客戶端能夠經過哨兵節點 + masterName 獲取主節點信息,在這裏哨兵起到的做用就是配置提供者。
須要注意的是,哨兵只是配置提供者,而不是代理。兩者的區別在於:
舉一個例子能夠很好的理解哨兵的做用是配置提供者,而不是代理。在前面部署的哨兵系統中,將哨兵節點的配置文件進行以下修改:
sentinel monitor mymaster 192.168.92.128 6379 2 改成 sentinel monitor mymaster 127.0.0.1 6379 2
而後,將前述客戶端代碼在局域網的另一臺機器上運行,會發現客戶端沒法鏈接主節點。
這是由於哨兵做爲配置提供者,客戶端經過它查詢到主節點的地址爲 127.0.0.1:6379,客戶端會向 127.0.0.1:6379 創建 Redis 鏈接,天然沒法鏈接。若是哨兵是代理,這個問題就不會出現了。
通知:哨兵節點在故障轉移完成後,會將新的主節點信息發送給客戶端,以便客戶端及時切換主節點。
哨兵實現的基本原理
前面介紹了哨兵部署、使用的基本方法,本部分介紹哨兵實現的基本原理。
哨兵節點支持的命令
哨兵節點做爲運行在特殊模式下的 Redis 節點,其支持的命令與普通的 Redis 節點不一樣。
在運維中,咱們能夠經過這些命令查詢或修改哨兵系統;不過更重要的是,哨兵系統要實現故障發現、故障轉移等各類功能,離不開哨兵節點之間的通訊。
而通訊的很大一部分是經過哨兵節點支持的命令來實現的。下面介紹哨兵節點支持的主要命令。
基礎查詢
經過這些命令,能夠查詢哨兵系統的拓撲結構、節點信息、配置信息等:
增長/移除對主節點的監控
sentinel monitor mymaster2 192.168.92.128 16379 2:與部署哨兵節點時配置文件中的 sentinel monitor 功能徹底同樣,再也不詳述。
sentinel remove mymaster2:取消當前哨兵節點對主節點 mymaster2 的監控。
強制故障轉移
sentinel failover mymaster:該命令能夠強制對 mymaster 執行故障轉移,即使當前的主節點運行無缺。
例如,若是當前主節點所在機器即將報廢,即可以提早經過failover命令進行故障轉移。
基本原理
關於哨兵的原理,關鍵是瞭解如下幾個概念。
定時任務
每一個哨兵節點維護了 3 個定時任務,定時任務的功能分別以下:
主觀下線
在心跳檢測的定時任務中,若是其餘節點超過必定時間沒有回覆,哨兵節點就會將其進行主觀下線。
顧名思義,主觀下線的意思是一個哨兵節點「主觀地」判斷下線;與主觀下線相對應的是客觀下線。
客觀下線
哨兵節點在對主節點進行主觀下線後,會經過 sentinelis-master-down-by-addr 命令詢問其餘哨兵節點該主節點的狀態。
若是判斷主節點下線的哨兵數量達到必定數值,則對該主節點進行客觀下線。
須要特別注意的是,客觀下線是主節點纔有的概念;若是從節點和哨兵節點發生故障,被哨兵主觀下線後,不會再有後續的客觀下線和故障轉移操做。
選舉領導者哨兵節點
當主節點被判斷客觀下線之後,各個哨兵節點會進行協商,選舉出一個領導者哨兵節點,並由該領導者節點對其進行故障轉移操做。
監視該主節點的全部哨兵都有可能被選爲領導者,選舉使用的算法是 Raft 算法。
Raft 算法的基本思路是先到先得:即在一輪選舉中,哨兵 A 向 B 發送成爲領導者的申請,若是 B 沒有贊成過其餘哨兵,則會贊成 A 成爲領導者。
選舉的具體過程這裏不作詳細描述,通常來講,哨兵選擇的過程很快,誰先完成客觀下線,通常就能成爲領導者。
故障轉移
選舉出的領導者哨兵,開始進行故障轉移操做,該操做大致能夠分爲 3 個步驟:
若是優先級沒法區分,則選擇複製偏移量最大的從節點;若是仍沒法區分,則選擇 runid 最小的從節點。
經過上述幾個關鍵概念,能夠基本瞭解哨兵的工做原理。爲了更形象的說明,下圖展現了領導者哨兵節點的日誌,包括從節點啓動到完成故障轉移。
哨兵配置與實踐建議
哨兵配置
下面介紹與哨兵相關的幾個配置。
sentinel monitor {masterName} {masterIp} {masterPort}{quorum}
sentinel monitor 是哨兵最核心的配置,在前文講述部署哨兵節點時已說明,其中:masterName 指定了主節點名稱,masterIp 和 masterPort 指定了主節點地址,quorum 是判斷主節點客觀下線的哨兵數量閾值。
當斷定主節點下線的哨兵數量達到 quorum 時,對主節點進行客觀下線。建議取值爲哨兵數量的一半加 1。
sentinel down-after-milliseconds {masterName} {time}
sentinel down-after-milliseconds 與主觀下線的判斷有關:哨兵使用 ping 命令對其餘節點進行心跳檢測。
若是其餘節點超過 down-after-milliseconds 配置的時間沒有回覆,哨兵就會將其進行主觀下線,該配置對主節點、從節點和哨兵節點的主觀下線斷定都有效。
down-after-milliseconds 的默認值是 30000,即 30s;能夠根據不一樣的網絡環境和應用要求來調整。
值越大,對主觀下線的斷定會越寬鬆,好處是誤判的可能性小,壞處是故障發現和故障轉移的時間變長,客戶端等待的時間也會變長。
例如,若是應用對可用性要求較高,則能夠將值適當調小,當故障發生時儘快完成轉移;若是網絡環境相對較差,能夠適當提升該閾值,避免頻繁誤判。
sentinel parallel-syncs {masterName} {number}
sentinel parallel-syncs 與故障轉移以後從節點的複製有關:它規定了每次向新的主節點發起復制操做的從節點個數。
例如,假設主節點切換完成以後,有 3 個從節點要向新的主節點發起復制;若是 parallel-syncs=1,則從節點會一個一個開始複製;若是 parallel-syncs=3,則 3 個從節點會一塊兒開始複製。
parallel-syncs 取值越大,從節點完成複製的時間越快,可是對主節點的網絡負載、硬盤負載形成的壓力也越大;應根據實際狀況設置。
例如,若是主節點的負載較低,而從節點對服務可用的要求較高,能夠適量增長 parallel-syncs 取值。parallel-syncs 的默認值是 1。
sentinel failover-timeout {masterName} {time}
sentinel failover-timeout 與故障轉移超時的判斷有關,可是該參數不是用來判斷整個故障轉移階段的超時,而是其幾個子階段的超時。
例如若是主節點晉升從節點時間超過 timeout,或從節點向新的主節點發起復制操做的時間(不包括複製數據的時間)超過 timeout,都會致使故障轉移超時失敗。
failover-timeout 的默認值是 180000,即 180s;若是超時,則下一次該值會變爲原來的 2 倍。
除上述幾個參數外,還有一些其餘參數,如安全驗證相關的參數,這裏不作介紹。
實踐建議
哨兵節點的數量應不止一個,一方面增長哨兵節點的冗餘,避免哨兵自己成爲高可用的瓶頸;另外一方面減小對下線的誤判。此外,這些不一樣的哨兵節點應部署在不一樣的物理機上。
哨兵節點的數量應該是奇數,便於哨兵經過投票作出「決策」:領導者選舉的決策、客觀下線的決策等。
各個哨兵節點的配置應一致,包括硬件、參數等;此外,全部節點都應該使用 ntp 或相似服務,保證時間準確、一致。
哨兵的配置提供者和通知客戶端功能,須要客戶端的支持才能實現,如前文所說的 Jedis;若是開發者使用的庫未提供相應支持,則可能須要開發者本身實現。
當哨兵系統中的節點在 Docker(或其餘可能進行端口映射的軟件)中部署時,應特別注意端口映射可能會致使哨兵系統沒法正常工做。
由於哨兵的工做基於與其餘節點的通訊,而 Docker 的端口映射可能致使哨兵沒法鏈接到其餘節點。
例如,哨兵之間互相發現,依賴於它們對外宣稱的 IP 和 port,若是某個哨兵 A 部署在作了端口映射的 Docker 中,那麼其餘哨兵使用 A 宣稱的 port 沒法鏈接到 A。
總結
本文首先介紹了哨兵的做用:監控、故障轉移、配置提供者和通知;而後講述了哨兵系統的部署方法,以及經過客戶端訪問哨兵系統的方法;再而後簡要說明了哨兵實現的基本原理;最後給出了關於哨兵實踐的一些建議。
在主從複製的基礎上,哨兵引入了主節點的自動故障轉移,進一步提升了 Redis 的高可用性。
可是哨兵的缺陷一樣很明顯:哨兵沒法對從節點進行自動故障轉移,在讀寫分離場景下,從節點故障會致使讀服務不可用,須要咱們對從節點作額外的監控、切換操做。
此外,哨兵仍然沒有解決寫操做沒法負載均衡、及存儲能力受到單機限制的問題;這些問題的解決須要使用集羣,我將在後面的文章中介紹,歡迎關注。