分佈式系統之緩存的微觀應用經驗談(二) 【主從和主備高可用篇】
html
前言node
近幾個月一直在忙些雜事,幾乎年後都沒怎麼閒過。忙忙碌碌中就進入了2018年的秋天了,不得不感嘆時間老是如白駒過隙,也不知道收穫了什麼和失去了什麼。最近稍微休息,買了兩本與技術無關的書,其一是 Yann Martel 寫的《The High Mountains of Portugal》(葡萄牙的高山),發現閱讀此書是須要一些耐心的,對人生暗喻很深,也有足夠的留白,有興趣的朋友能夠細品下。好了,下面迴歸正題,嘗試寫寫工做中緩存技術相關的一些實戰經驗和思考。
緩存
正文
服務器
在分佈式Web程序設計中,解決高併發以及內部解耦的關鍵技術離不開緩存和隊列,而緩存角色相似計算機硬件中CPU的各級緩存。現在的業務規模稍大的互聯網項目,即便在最初beta版的開發上,都會進行預留設計。可是在諸多應用場景裏,也帶來了某些高成本的技術問題,須要細緻權衡。本系列主要圍繞分佈式系統中服務端緩存相關技術,也會結合朋友間的探討說起本身的思考細節。文中如有不妥之處,懇請指正。
爲了方便獨立成文,原諒在內容排版上的一點點我的強迫症。網絡
第二篇這裏嘗試聊聊緩存的主從(Master-Slave),以及相關的高可用實現(High-Availability)(具體應用依然以Redis 舉例)架構
另見:分佈式系統之緩存的微觀應用經驗談(一) 【基礎細節篇】(https://www.cnblogs.com/bsfz/p/9568591.html )併發
1、先簡單談談主從分離(Master-Slave)運維
(注:因爲目前我的工做中大多數狀況應用的是Redis 3.x,如下如有特性關聯,均是以此做爲參照說明。)異步
1.1 關於主從分離的取捨觀點tcp
是否採用主從分離(這裏特指讀寫分離),我的目前的觀點是,它在不少場景裏,並非一個很好的方案。
我更想說的是,甚至任何涉及數據同步的環節,包括DB讀寫分離、緩存數據複製等等,若是沒有特殊場景強制要求,那麼儘可能規避。
雖然在互聯網的一些應用場景裏,讀遠大於寫,也演變了一套看似完整的讀寫實踐方案,大致歸爲「主寫從讀」、「一寫多讀」、「多讀多寫」。但本質上,在大多數環境下,均存在必定缺陷,不管是基於服務底層的數據同步延遲,仍是開發中的邏輯切換,都帶來了一些可能本末倒置的隱患和生硬。同時,在集羣模式下讀寫分離成本實在太高,不只僅是一致性問題,必須慎重考慮和權衡。
若是沒明白讀寫分離方案基於什麼本質什麼條件、包含哪些細節問題,那你的設計可能就很勉強,甚至出現某些關鍵問題時,反而很難去分析和解決。去年跟一前輩朋友取經,他們一個業務服務兜兜轉轉實際測出的結果是讀QPS約2000,寫QPS不到500,這些的分離啼笑皆非,程序性能也沒獲得優化,反而增長了徹底浪費的技術成本,以及由於讀寫分離帶來的程序本不應處理的例外問題,也是折騰。
1.2 主從分離的一些細節
以 Redis爲例,每一個Redis實例(Instance)都是做爲一個節點(node),而且默認主節點(master node),它們都可以手動轉換爲從節點(slave node)。每一個slave node只能隸屬於一個 master node, 而每一個master node能夠擁有n個slave node。任何主從同步都離不開復制的概念,在Redis中主要命令是 slaveof host port (指定一個master便可),這樣master node的數據將自動異步的同步到各個slave中,這算是最基本的形態了。
在進行相關軟性配置時,我的推薦最好保持配置文件(config-file)的一致,這裏指多個salve node。對於slave node的操做默認是隻讀(read-only),也建議保持這個設置。若是更改成可寫權限,那麼對slave node的修改是不會反向同步至master node中的,並且就算經過其餘方式實現了反向同步,中間將大量存在相似傳統RDBMS裏的幻讀問題,這裏併發不大但足夠繁瑣,追蹤到具體緣由也是得不償失。(而對應程序開發中,每每寫操做也是都直接進master node執行。)
另外順便提下,主從的硬件配置能夠一致,可是我依然推薦slave node 能夠稍微高一些。稍微注意,slave node的內存儘可能不要小於 master node 的預算內存。
對於node之間的數據延遲問題,外在因素通常都是網絡I/O影響爲主,CPU影響爲次,換句話說,每每CPU的負載都足夠(詳見上一篇中提到的一些關聯處),而網絡I/O則會比較明顯。在部署時候,沒有特殊場景,都是同機房內聯而對外隔離,但即便這樣,也須要額外注意延遲的接收程度,每次同步複製的TCP數據包,並不是是真正的實時處理,這個相似於以前提到的 AOF 和 RDB 的設計思想,分爲實時複製和間隔性複製,前者更及時但帶寬消耗大,然後者正好相反。在Redis中主要以 repl-disable-tcp-nodelay切換,默認使用前者,我的也較爲推薦,可是在單次數據量較頻較大,業務場景的時效要求不高,徹底能夠設置爲後者,從而節省很多性能,也連鎖提高了必定穩定性。
對於拓撲結構的設計,應用最多的就是單層拓撲,針對大量相似 keys全表掃描的操做,slave node會作到分擔性能壓力的做用。若是還想極致一些,把總體阻塞降到最低,能夠將拓撲結構轉換爲樹狀,最簡單的作法,將某個slave node直接轉移到最底部,但會帶來更多時效上的犧牲,因此須要考慮場景的接受程度了。同時,這裏可能在具體架構落地的環節裏,會比較分身乏術,須要開始考慮交由專業的運維來參與部署了,涉及上下節點間的通訊、帶寬監控、級聯之間的複製問題,以及一些更獨立的高頻率統計和管理。ps下,這點一直做爲備用,但在截止到目前的工做生涯裏尚未找到必要的機會去採用。
對於複製/同步數據自己,不管是全量、仍是增量,因爲異步性(我的認爲不可能設計爲同步)和必定的時效損耗,一定存在一個偏移值(offset)。以Redis舉例,master node和slave node中,各自對本身的當前複製時的offset作記錄,若是兩個角色的偏移值存在較大差別(可參考查詢對應repl_offset),那麼大機率存在頻繁的阻塞,包括網絡和自身腳本命令的阻塞。通常內部網絡都是專線環境,而且都是獨立部署,因此優先排查命令執行效率,和沒必要要的掃描問題(可參考上篇討論:http://www.javashuo.com/article/p-muuglegt-p.html)。可是不管如何,延遲或者說偏移過大的問題,總不可能徹底規避,因此在開發裏要麼利用專業的監控服務,要麼使用不一樣驅動庫定時判斷,這也無疑增長了編碼複雜度,哪怕一些開源庫已經盡力作了一些工做。
2、嘗試談談相關的高可用(High-Availability)
緩存既然做爲一種通用的中間件(固然,某些場景裏也多是最後一層,見第一篇),決定了在諸多場景裏其交互頻率(包括QPS)大多遠遠高於其餘服務,這就須要其具有極高的穩定性、可用性。我的在前面闡述了一些關於主從分離的細節,下面嘗試談談相關的HA方案和一些思考。
2.1 關於高可用說明
這裏聲明下,我認爲主從分離和高可用本質上是沒有任何一絲關係的,只是有些剛恰好的做用使之結合造成了一些HA方案。
前面提到過,單個相對可靠的緩存服務,除了自己所在服務器自身的內存負載,設計時更須要充分考慮網絡I/O、CPU的負載,以及某些場景下的磁盤I/O的代價。而這些條件所有都會擁有瓶頸,除此,你永遠沒法避免的問題還有服務器形成的直接宕機、服務自身的缺陷形成的某些時候的不可用(單點問題)等等。一套相對可以落地的高可用緩存方案,必然還須要擁有足夠健壯的承載和相對完善的內部故障轉移機制,從而達到對外提供的是整套程序化的高可用的緩存服務。
2.2 實現HA的原始步驟
以Redis爲例,其自己的設計已經足夠優秀和成熟,但在負載過大致使延遲太高、甚至崩潰的過程裏,比較原始的方式是這樣去操做:將一個關聯的備用 slave node升級爲 master node,能夠一個 slaveof no one 基本處理。而後分析是否還作了業務層面上的主從分離,若是存在,那麼還須要手工修改其餘slave node 裏的舊 master node指向,映射爲當前 master node。 最後,當master node 從新上線時,修改自身角色並從新加入集羣。
2.3 談談程序化HA方案的部分實踐
上面提到的主幹思路看起來並不複雜,但實際以人工去操做每個環節所須要解決的問題每每不止這些,好比對於node的不可用的斷定、確認後的選舉邏輯、程序客戶端的事件通知處理、服務的同步處理細節等。在Redis中比較成熟的 HA方案,目前主要包括依賴獨立 node的 Sentinel 和自身基於 Gossip 傳播的 Cluster 方案。
從宏觀角度來談, Sentinel 和 Cluster 對於HA的設計均有互相借鑑,但後者 Cluster 更可能是偏向提供一套可行性集羣分片方案,與這裏主題關聯性不是很大(後續我會嘗試單獨寫一篇,這裏不延伸),圍繞 Sentinel 的HA實踐更直接。
Sentinel 的本質邏輯就是對全部node做按期巡視,當發現存在共同投票認爲不可達的 master node, 會對其作下線標識,同時進行必要的 master選舉升級,並將事件狀態返回給信號方及客戶端。Sentinel 的故障轉移是針對 master node的,一般是把 slave node做爲master的一個熱備。
這裏依然以Redis 3.x 爲主,在 Redis Sentinel方案裏,一般指 n個 Sentinel node來自動監聽Redis普通node。準確的說,每一個Sentinel node 其實會監放任何一個node,包括其餘Sentinel node。
對於選舉和審定的控制,能夠調整配置 monitor的quorum 來確認嚴格性,好比, 在大多數場景裏,設置爲 (n / 2 + 1) 個,這樣表明過半的票數認同,即認爲指定node當前宕機。同時,當須要選舉新的領導者master,這樣也至少是趨勢性客觀判斷。是否能夠設置更小?固然能夠,只是要注意的一個問題是,這樣對失敗的認定流程更短更快,可是偏差也相對越大了,須要看看場景是否適配。我的在權衡時會盡可能優先設置爲前者。
對於內部故障轉移天然能夠獲得相應的事件通知,通常還能夠寫入到對應執行腳本,理論上會適合自動化這塊,但我的目前還沒有應用,這個偏向純運維了,我的這裏依然保持針對架構和開發來作一些討論。
對於監聽通訊,能夠適度調整 failover-timeout等相關配置,這裏並無相應的計算方式,在大多數狀況沒太多講究,可是也須要關注不能過分調整。我的目前採起的方式是,優先設置一個較大值,好比審定時間30秒,五個實例,那麼同步轉移超時時間則不低於150秒。
對於選舉完成後,發起新的數據複製流程,因爲master node會面向多個 slave node 進行瞬間同步, 默認併發複製,而不少時候服務器環境有限,沒有很足夠的配置,甚至常常同一服務器上存在幾個理想上本應該獨立的服務, 這裏則須要重點考慮下網絡IO和磁盤IO的問題,根據實際狀況臨時調整,除此以外,在高峯時這也起到了限流做用。
額外再強調一下,基於主從的HA方案,依然存在 master node同步到 slave node 的延遲問題,這個基本是任何相似熱備方案均存在的問題,系統交互越是密集,或者 slave node 的不斷增長,都會明顯增大這個延遲,因此在權衡的時候,須要考慮業務的初衷,到底可以接受到什麼程度。
任何服務裏的應用,都不是看起來越多越好。假如你打算手動實現一套自定義的HA方案,或者相關的熱備思路,你甚至能夠考慮在業務程序裏,具體點能夠直接是在相關的驅動庫(好比JAVA的Jedis、和.Net的 StackExchange.Redis)修改,插入數據的同時,插入到另外一個備用庫中。這在一些非緩存場景裏,也有相似設計,並非必定不被採用的,畢竟架構設計的初衷必定是考慮總體可行性和利弊權衡。
結語
本篇先寫到這裏,下一篇會圍繞相關主題嘗試擴展闡述。
PS:因爲我的能力和經驗均有限,本身也在持續學習和實踐,文中如有不妥之處,懇請指正。
【預留佔位:分佈式系統之緩存的微觀應用經驗談(三)【集羣場景篇】https://www.cnblogs.com/bsfz/】
End.