基於內存的Redis應該是目前各類web開發業務中最爲經常使用的key-value數據庫了,咱們常常在業務中用其存儲用戶登錄態(Session存儲),加速一些熱數據的查詢(相比較mysql而言,速度有數量級的提高),作簡單的消息隊列(LPUSH和BRPOP)、訂閱發佈(PUB/SUB)系統等等。mysql
規模比較大的互聯網公司,通常都會有專門的團隊,將Redis存儲以基礎服務的形式提供給各個業務調用。web
不過任何一個基礎服務的提供方,都會被調用方問起的一個問題是:你的服務是否具備高可用性?最好不要由於你的服務常常出問題,致使我這邊的業務跟着遭殃。最近我所在的項目中也本身搭了一套小型的「高可用」Redis服務,在此作一下本身的總結和思考。redis
首先咱們要定義一下對於Redis服務來講怎樣纔算是高可用,即在各類出現異常的狀況下,依然能夠正常提供服務。或者寬鬆一些,出現異常的狀況下,只通過很短暫的時間便可恢復正常服務。所謂異常,應該至少包含了如下幾種可能性:sql
【異常1】某個節點服務器的某個進程忽然down掉(例如某開發手殘,把一臺服務器的redis-server進程kill了)數據庫
【異常2】某臺節點服務器down掉,至關於這個節點上全部進程都停了(例如某運維手殘,把一個服務器的電源拔了;例如一些老舊機器出現硬件故障)服務器
【異常3】任意兩個節點服務器之間的通訊中斷了(例如某臨時工手殘,把用於兩個機房通訊的光纜挖斷了)網絡
其實以上任意一種異常都是小几率事件,而作到高可用性的基本指導思想就是:多個小几率事件同時發生的機率能夠忽略不計。只要咱們設計的系統能夠容忍短期內的單點故障,便可實現高可用性。架構
對於搭建高可用Redis服務,網上已有了不少方案,例如Keepalived,Codis,Twemproxy,Redis Sentinel。其中Codis和Twemproxy主要是用於大規模的Redis集羣中,也是在Redis官方發佈Redis Sentinel以前twitter和豌豆莢提供的開源解決方案。併發
個人業務中數據量並不大,因此搞集羣服務反而是浪費機器了。最終在Keepalived和Redis Sentinel之間作了個選擇,選擇了官方的解決方案Redis Sentinel。運維
Redis Sentinel能夠理解爲一個監控Redis Server服務是否正常的進程,而且一旦檢測到不正常,能夠自動地將備份(slave)Redis Server啓用,使得外部用戶對Redis服務內部出現的異常無感知。咱們按照由簡至繁的步驟,搭建一個最小型的高可用的Redis服務。
方案1:單機版Redis Server,無Sentinel
通常狀況下,咱們搭的我的網站,或者平時作開發時,會起一個單實例的Redis Server。調用方直接鏈接Redis服務便可,甚至Client和Redis自己就處於同一臺服務器上。
這種搭配僅適合我的學習娛樂,畢竟這種配置總會有單點故障的問題沒法解決。一旦Redis服務進程掛了,或者服務器1停機了,那麼服務就不可用了。而且若是沒有配置Redis數據持久化的話,Redis內部已經存儲的數據也會丟失。
方案2:主從同步Redis Server,單實例Sentinel
爲了實現高可用,解決方案1中所述的單點故障問題,咱們必須增長一個備份服務,即在兩臺服務器上分別各啓動一個Redis Server進程,通常狀況下由master提供服務,slave只負責同步和備份。
與此同時,在額外啓動一個Sentinel進程,監控兩個Redis Server實例的可用性,以便在master掛掉的時候,及時把slave提高到master的角色繼續提供服務,這樣就實現了Redis Server的高可用。
這基於一個高可用服務設計的依據,即單點故障自己就是個小几率事件,而多個單點同時故障(即master和slave同時掛掉),能夠認爲是(基本)不可能發生的事件。
對於Redis服務的調用方來講,如今要鏈接的是Redis Sentinel服務,而不是Redis Server了。常見的調用過程是,client先鏈接Redis Sentinel並詢問目前Redis Server中哪一個服務是master,哪些是slave,而後再去鏈接相應的Redis Server進行操做。
固然目前的第三方庫通常都已經實現了這一調用過程,再也不須要咱們手動去實現(例如Nodejs的ioredis,PHP的predis,Golang的go-redis/redis,JAVA的jedis等)。
然而,咱們實現了Redis Server服務的主從切換以後,又引入了一個新的問題,即Redis Sentinel自己也是個單點服務,一旦Sentinel進程掛了,那麼客戶端就沒辦法連接Sentinel了。因此說,方案2的配置並沒有法實現高可用性。
方案3:主從同步Redis Server,雙實例Sentinel
爲了解決方案2的問題,咱們把Redis Sentinel進程也額外啓動一份,兩個Sentinel進程同時爲客戶端提供服務發現的功能。對於客戶端來講,它能夠鏈接任何一個Redis Sentinel服務,來獲取當前Redis Server實例的基本信息。一般狀況下,咱們會在Client端配置多個Redis Sentinel的連接地址,Client一旦發現某個地址鏈接不上,會去試圖鏈接其餘的Sentinel實例,這固然也不須要咱們手動實現,各個開發語言中比較熱門的redis鏈接庫都幫咱們實現了這個功能。咱們預期是:即便其中一個Redis Sentinel掛掉了,還有另一個Sentinel能夠提供服務。
然而,願景是美好的,現實倒是很殘酷的。如此架構下,依然沒法實現Redis服務的高可用。方案3示意圖中,紅線部分是兩臺服務器之間的通訊,而咱們所設想的異常場景(【異常2】)是,某臺服務器總體down機,不妨假設服務器1停機,此時,只剩下服務器2上面的Redis Sentinel和slave Redis Server進程。
這時,Sentinel實際上是不會將僅剩的slave切換成master繼續服務的,也就致使Redis服務不可用,由於Redis的設定是隻有當超過50%的Sentinel進程能夠連通並投票選取新的master時,纔會真正發生主從切換。本例中兩個Sentinel只有一個能夠連通,等於50%並不在能夠主從切換的場景中。
你可能會問,爲何Redis要有這個50%的設定?假設咱們容許小於等於50%的Sentinel連通的場景下也能夠進行主從切換。試想一下【異常3】,即服務器1和服務器2之間的網絡中斷,可是服務器自己是能夠運行的。以下圖所示:
實際上對於服務器2來講,服務器1直接down掉和服務器1網絡連不通是同樣的效果,反正都是忽然就沒法進行任何通訊了。假設網絡中斷時咱們容許服務器2的Sentinel把slave切換爲master,結果就是你如今擁有了兩個能夠對外提供服務的Redis Server。Client作任何的增刪改操做,有可能落在服務器1的Redis上,也有可能落在服務器2的Redis上(取決於Client到底連通的是哪一個Sentinel),形成數據混亂。即便後面服務器1和服務器2之間的網絡又恢復了,那咱們也沒法把數據統一了(兩份不同的數據,到底該信任誰呢?),數據一致性徹底被破壞。
方案4:主從同步Redis Server,三實例Sentinel
鑑於方案3並無辦法作到高可用,咱們最終的版本就是上圖所示的方案4了。實際上這就是咱們最終搭建的架構。咱們引入了服務器3,而且在3上面又搭建起一個Redis Sentinel進程,如今由三個Sentinel進程來管理兩個Redis Server實例。這種場景下,無論是單一進程故障、仍是單個機器故障、仍是某兩個機器網絡通訊故障,均可以繼續對外提供Redis服務。
實際上,若是你的機器比較空閒,固然也能夠把服務器3上面也開啓一個Redis Server,造成1 master + 2 slave的架構,每一個數據都有兩個備份,可用性會提高一些。固然也並非slave越多越好,畢竟主從同步也是須要時間成本的。
在方案4中,一旦服務器1和其餘服務器的通訊徹底中斷,那麼服務器2和3會將slave切換爲master。對於客戶端來講,在這麼一瞬間會有2個master提供服務,而且一旦網絡恢復了,那麼全部在中斷期間落在服務器1上的新數據都會丟失。若是想要部分解決這個問題,能夠配置Redis Server進程,讓其在檢測到本身網絡有問題的時候,當即中止服務,避免在網絡故障期間還有新數據進來(能夠參考Redis的min-slaves-to-write和min-slaves-max-lag這兩個配置項)。
至此,咱們就用3臺機器搭建了一個高可用的Redis服務。其實網上還有更加節省機器的辦法,就是把一個Sentinel進程放在Client機器上,而不是服務提供方的機器上。只不過在公司裏面,通常服務的提供方和調用方並不來自同一個團隊。兩個團隊共同操做同一個機器,很容易由於溝通問題致使一些誤操做,因此出於這種人爲因素的考慮,咱們仍是採用了方案4的架構。而且因爲服務器3上面只跑了一個Sentinel進程,對服務器資源消耗並很少,還能夠用服務器3來跑一些其餘的服務。
易用性:像使用單機版Redis同樣使用Redis Sentinel
做爲服務的提供方,咱們老是會講到用戶體驗問題。在上述方案當中始終有一個讓Client端用的不是那麼舒服的地方。對於單機版Redis,Client端直接鏈接Redis Server,咱們只須要給一個ip和port,Client就可使用咱們的服務了。而改形成Sentinel模式以後,Client不得不採用一些支持Sentinel模式的外部依賴包,而且還要修改本身的Redis鏈接配置,這對於「矯情」的用戶來說顯然是不能接收的。有沒有辦法仍是像在使用單機版的Redis那樣,只給Client一個固定的ip和port就能夠提供服務呢?
答案固然是確定的。這可能就要引入虛擬IP(Virtual IP,VIP),如上圖所示。咱們能夠把虛擬IP指向Redis Server master所在的服務器,在發生Redis主從切換的時候,會觸發一個回調腳本,回調腳本中將VIP切換至slave所在的服務器。這樣對於Client端來講,他彷彿在使用的依然是一個單機版的高可用Redis服務。
結語
搭建任何一個服務,作到「能用」實際上是很是簡單的,就像咱們運行一個單機版的Redis。不過一旦要作到「高可用」,事情就會變得複雜起來。業務中使用了額外的兩臺服務器,3個Sentinel進程+1個Slave進程,只是爲了保證在那小几率的事故中依然作到服務可用。在實際業務中咱們還啓用了supervisor作進程監控,一旦進程意外退出,會自動嘗試從新啓動。
歡迎工做一到五年的Java工程師朋友們加入Java架構開發:744677563
羣內提供免費的Java架構學習資料(裏面有高可用、高併發、高性能及分佈式、Jvm性能調優、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用本身每一分每一秒的時間來學習提高本身,不要再用"沒有時間「來掩飾本身思想上的懶惰!趁年輕,使勁拼,給將來的本身一個交代!