同程旅行王晓波:同程凤凰缓存系统在基于 Redis 方面的设计与实践(上篇)
王曉波?
同程旅行機票事業群 CTO
讀完需要
12
分鐘速讀僅需 4 分鐘
本章和大家分享一下同程鳳凰緩存系統在基于 Redis 方面的設計與實踐。在本章中除了會列舉我們工作過程中遇到各種問題和誤區外,還會給出我們相應的解決辦法,希望能夠拋磚引玉為大家帶來一定的啟示。??
? 本文節選自中生代技術社區出品圖書《深入分布式緩存》
1
? ?
同程鳳凰緩存系統遇到的問題
2012 年~2014 年,我們的業務開始使用一種新的互聯網銷售模式——秒殺搶購,一時間,各個產品線開始紛紛加人,今天秒殺門票,明天秒殺酒店。各種活動輪番登場,用戶在不亦樂乎地玩著秒殺活動的同時,也對后端技術的支撐提出了一波又一波的挑戰。
在第一個秒殺搶購系統上線后不久,流量越來越大,發現不對了:只要秒殺搶購一開始,卡頓、打不開的故障就會此起彼伏。一旦出現故障,所有人都急得直跳腳,因為秒殺搶購流量一下而過,沒有機會補救。其實問題也很簡單,一個有點經驗的兄弟就很快就將問題定位出來:搶購那一下太耗費服務器資源,在同一時間段內涌入的人數大大超過了服務器的負載,服務器根本承受不了,CPU 占用率很多時候都接近了 100%,請求的積壓也很嚴重,從請求接人到數據的讀取都有問題,尤以數據的讀取最為嚴重。在原來的設計方式中雖然也考慮了大并發量下的數據讀取,但是因為數據相對分散,讀取時間相對拉長,不像秒殺搶購是對同一批或同一條數據進行超高并發的讀取。當然秒殺搶購不僅僅是數據讀取的集中并發,同時也是數據寫入的集中并發。
問題是發現了,表面上看起來解決沒那么簡單。應用層的問題解決起來相對容易,實在不行多加點機器也能解決;但數據的問題就不是那么簡單了,靠增加機器來解決是不行的。大部分關系型數據庫沒有真正的分布式解決方案,最多做一個主從分離或多加從庫分擔讀取的壓力。但因為秒殺搶購是數據集中式超高并發的讀,所以一般的關系型數據庫因為它本身局限性很難支撐這樣瞬間突發的高并發,就算勉強頂上,也會因為秒殺搶購還有寫的高并發,影響到讀節點的數據同步問題。當然也可以拼命提升一下服務器的硬件性能,比如換最好的 CPU、把硬盤換成 SSD 等等,但效果應該不會太顯著,沒有解決本質的問題,還比較費錢。
其實尋找新的解決方案也很簡單,因為在當時那個年代的開源社區中有很多的 NoSQL 明星產品(如 Redis 等等)方案,這些方案也都提供了豐富的數據類型,擁有原子性操作和強大的并發性能特性,感覺簡直就是為搶購量身定做的。于是我們也基于此做了一些方案,例如:數據在搶購活動開始前被先放到 NoSQL 數據庫里,產生的訂單數據先被放到隊列中,
然后通過隊列慢慢消化……這一系列的操作解決了搶購的問題,這里主要不是講搶購技術方案,我們不再細化下去。
其實這樣的解決方案在技術蠻荒時代還是相對靠譜的,在我們技術強壯的今天,這個方案還是單薄和弱小了一些,但是所有的技術點都是這樣一路走來的。下面我們來看一下,從弱小走向長大,經歷了哪些?
1.1
? ?
Redis 用法的凌亂
從運維角度來想,Redis 是很簡單的東西,安裝一下,配置一下,就輕松上線了,再加上 Redis 的一些單進程、單線程等特性,可以很穩定地給到應用層去隨便使用。就像早期的我們,在很短的時間內,Redis 實例部署達到了千個以上,用得多了真正的問題開始出現。什么問題?亂的問題。Redis 從使用的角度來講是需要像應用服務一樣去治理的。為什么是需要治理的?我們先來看一些常見的運維與開發的聊天記錄,大家會不會有一些風趣的感覺:
開發:“Redis 為啥不能訪問了?”
運維:“剛剛服務器內存壞了,服務器自動重啟了。”
開發:“為什么 Redis 延遲這么久?”
運維:“大哥,不要在 Zset 里面放幾萬條數據,插入排序的后果很嚴重啊!”
開發:“我寫進去的 key 呢,為什么不見了?”
運維:“你的 Redis 超過最大大小了,不常用的 key 都丟了呀!”
開發:“剛剛為啥讀取全部失敗了?”
運維:“剛剛網絡臨時中斷了一下,slave 全同步了,在全同步完成之前,slave 的讀取全部失敗。”
開發:“我剛剛想到一個好方案,我需要 800GB 的 Redis,什么時候能準備好呢?”
運維:“大哥,我們線上的服務器最大也就 256GB,別玩這么大好嗎?”
光看這么一小點就感覺問題很多了,開發和運維都疲于奔命地解決這些看上去很無聊的問題。這些問題從本質上來講還只是麻煩,談不上困難。但是每當這些麻煩演變成一次 Redis 的故障時,哪怕是小故障,有時也會造成大痛苦,因為畢竟保存在內存里的數據太脆弱了,一不小心數據就會全部消失了。為此,當時也是絞盡腦汁,想了很多種辦法
單機不是不安全嗎?那么就開啟主從+Keepalived,用虛 IP 地址在 master 和 slave 兩邊漂移,master 掛了直接切換到 slave。
數據放內存不是不安全嗎?可以開啟數據落盤,根據業務需要決定落盤規則,有 AOF 的,也有 RDB 的。
使用上不是有問題嗎?那么多開幾場培訓,跟大家講講 Redis 的用法和規范。
以上策略在當時似乎很完美,但是沒多久,均宣告失敗,這是必然的。
為什么呢?先看那個主從+Keepalived 的方案,這本來是個很好的方案,但是忽略了主數據節點掛掉的情況。我們在前面說過,Redis 的單進程、單線程設計是其簡單和穩定的基石,只要不是服務器發生了故障,在一般情況下是不會掛的。
但同時,單進程、單線程的設計會導致 Redis 接收到復雜指令時會忙于計算而停止響應,可能就因為一個 Zset 或者 keys 之類的指令,Redis 計算時間稍長,Keepalived 就認為其停止了響應,直接更改虛 IP 的指向,然后做一次主從切換。過不了多久,Zset 和 keys 之類的指令又會從客戶端發送過來,于是從機上又開始堵塞,Keepalived 就一直在主從機之間不斷地切換 IP。終于主節點和從節點都堵了,Keepalived 發現后,居然直接將虛 IP 釋放了,然后所有的客戶端都無法連接 Redis 了,只能等運維到線上手工綁定才行。
數據落盤也引起了很大的問題,RDB 屬于非阻塞式的持久化,它會創建一個子進程來專門把內存中的數據寫人 RDB 文件里,同時主進程可以處理來自客戶端的命令請求。但子進程內的數據相當于是父進程的一個拷貝,這相當于兩個相同大小的 Redis 進程在系統上運行,會造成內存使用率的大幅增加。如果在服務器內存本身就比較緊張的情況下再進行 RDB 配置,內存占用率就會很容易達到 100%,繼而開啟虛擬內存和進行磁盤交換,然后整個 Redis 的服務性能就直線下降了。
另外,Zset、發布訂閱、消息隊列、Redis 的各種功能不斷被介紹,開發者們也在利用這些特性,開發各種應用,但從來沒想過這么一個小小的 Redis 有這么多新奇的功能,它的缺點在什么地方,什么樣的場景是不合適用的?
這時 Redis 在大部分的開發者手上就是像是一把錘子,看什么都是釘子,隨時都一錘了事。同時也會漸漸地淡忘了開發的一些細節點和規范,因為用它解決性能的問題是那么輕松簡單,于是一些基于 Redis 的新奇功能就接連不斷地出現了:基于 Redis 的分布式鎖、日志系統、消息隊列、數據清洗,等等,各種各樣的功能不斷上線使用,從而引發了各種各樣的問題。這時候原來那個救火神器就會變成
四處點火的神器,Redis 堵塞、網卡打爆、連接數爆表等問題層出不窮,經過這么多折騰,Redis 終于也變成了大家的噩夢了。
1.2
? ?
從實際案例再看 Redis 的使用
第一個案例
在一個炎熱的夏天,引爆了埋藏已久的大炸彈。首先是一個產品線開發人員搭建起了一套龐大的價格存儲系統,底層是關系型數據庫,只用來處理一些事務性的操作和存放一些基礎數據;在關系型數據庫的上面還有一套 MongoDB,因為 MongoDB 的文檔型數據結構,讓他們用起來很順手,同時也可以支撐一定量的并發。在大部分情況下,一次大數據量的計算后結果可以重用但會出現細節數據的頻繁更新,所以他們又在 MongoDB 上搭建了一層 Redis 的緩存,這樣就形成了數據庫→ MongoDB → Redis 三級的方式,對方案本身不評價,因為這不是本文重點,我們來看 Redis 這層的情況。由于數據量巨大,所以需要 200GB 的 Redis。并且在真實的調用過程中,Redis 是請求量最大的點,當然如果 Redis 有故障時,也會有備用方案,從后面的 MongoDB 和數據庫中重新加載數據到 Redis,就是這么一套簡單的方案上線了。
當這個系統剛開始運行的時候,一切都還安好,只是運維同學有點傻眼了,用 200GB 的 Redis 單服務器去做,它的故障可能性太大了,所以大家建議將它分片,沒分不知道,一分嚇一跳,各種類型用得太多了,特別是里面還有一些類似消息隊列使用的場景。由于開發同學對 Redis 使用的注意點關注不夠,一味地濫用,一錘了事,所以讓事情變得困難了。
有些僥幸不死的想法是會傳染的,這時的每個人都心存僥幸、懶惰心理,都想著:“這個應該沒事,以后再說吧,先做個主從,掛了就起從”,這種僥幸也是對 Redis 的虛偽的信心,無知者無畏。可惜事情往往就是怕什么來什么,在大家快樂并放肆地使用時,系統中重要的節點 MongoDB 由于系統內核版本的 BUG,造成整個 Mongodb 集群掛了!(這里不多說 MongoDB 的事情,這也是一個好玩的“哭器”)。當然對天天與故障為朋友的運維同學來說這個沒什么,對整個系統來說問題也不大,因為大部分請求調用都是在最上層的 Redis 中完成的,只要做一定降級就行,等拉起了 MongoDB 集群后自然就會好了。
但此時可別忘了那個 Redis,是一個 200GB 大的 Redis,更是帶了個從機的 Redis,所以這時的 Redis 是絕對不能出任何問題的,一旦有故障,所有請求會立即全部打向最底層的關系型數據庫,在如此大量的壓力下,數據庫瞬間就會癱瘓。但是,怕什么來什么,還是出了狀況:主從 Redis 之間的網絡出現了一點小動蕩,想想這么大的一個東西在主從同步,一旦網絡動蕩了一下下,會怎么樣呢?主從同步失敗,同步失敗就直接開啟全同步,于是 200GB 的 Redis 瞬間開始全同步,網卡瞬間打滿。為了保證 Redis 能夠繼續提供服務,運維同學,直接關掉從機,主從同步不存在了,流量也恢復正常。不過,主從的備份架構變成了單機 Redis,心還是懸著的。俗話說,福無雙至,禍不單行。這 Redis 由于下層降級的原因并發操作量每秒增加到 4 萬多,AOF 和 RDB 庫明顯扛不住。同樣為了保證能持續地提供服務,運維同學也關掉了 AOF 和 RDB 的數據持久化。連最后的保護也沒有了(其實這個保護本來也沒用,200GB 的 Redis 恢復太大了)。
至此,這個 Redis 變成了完全的單機內存型,除了祈禱它不要掛,已經沒有任何方法了。懸著好久,直到修復 MongoDB 集群,才了事。如此僥幸,沒出大事,但心里會踏實嗎?不會。在這個案例中主要的問題在于對 Redis 過度依賴,Redis 看似簡單而方便地為系統帶來了性能提升和穩定性,但在使用中缺乏對不同場景的數據的分離造成了一個邏輯上的單點問題。當然這個問題我們可以通過更合理的應用架構設計來解決,但是這樣解決不夠優雅也不夠徹底,還增加了應用層的架構設計的麻煩。
Redis 的問題就應該在基礎緩存層來解決,這樣即使還有類似的情況也沒有問題,因為基礎緩存層已經能適應這樣的用法,也會讓應用層的設計更為簡單(簡單其實一直是架構設計所追求的,Redis 的大量隨意使用本身就是追求簡單的副產品,那我們為什么不讓這種簡單變為真實呢?)
第二個案例
有個部門用自己現有 Redis 服務器做了一套日志系統,將日志數據先存儲到 Redis 里面,再通過其他程序讀取數據并進行分析和計算,用來做數據報表。當他們做完這個項目之后,這個日志組件讓他們覺得用得很過癮。他們都覺得這個做法不錯,可以輕松地記錄日志,分析起來也挺快,還用什么公司的分布式日志服務啊。于是隨著時間的流逝,這個 Redis 上已經悄悄地掛載了數千個客戶端,每秒的并發量數萬,系統的單核 CPU 使用率也接近 90%了,此時這個 Redis 已經開始不堪重負。終于,壓死駱駝的最后一根稻草來了,有程序向這個日志組件寫入了一條 7MB 的日志(哈哈,這個容量可以寫一部小說了,這是什么日志啊),于是 Redis 堵死了,一旦堵死,數千個客戶端就全部無法連接,所有日志記錄的操作全部失敗。
其實日志記錄失敗本身應該不至于影響正常業務,但是由于這個日志服務不是公司標準的分布式日志服務,所以關注的人很少,最開始寫它的開發同學也不知道會有這么大的使用量,運維同學更不知有這個非法的日志服務存在。這個服務本身也沒有很好地設計容錯,所以在日志記錄的地方就直接拋出異常,結果全公司相當一部分的業務系統都出現了故障,監控系統中“5XX”的錯誤直線上升。一幫人欲哭無淚,頂著巨大的壓力排查問題,但是由于受災面實在太廣,排障的壓力是可以想象的。這個案例中的問題看似是因為一個日志服務沒做好或者是開發流程管理不到位導致的。而且很多日志服務也都用到了 Redis 做收集數據的緩沖,好像也沒什么問題。其實不然,像這樣大規模大流量的日志系統從收集到分析要細細考慮的技術點是巨大的,而不只是簡單的寫人性能的問題。
在這個案例中 Redis 給程序帶來的是超簡單的性能解決方案,但這個簡單是相對的,它是有場景限制的。在這里這樣的簡單就是毒藥,無知地吃下是要害死自己的,這就像“一條在小河溝里無所不能傲慢的小魚,那是因為它沒見過大海,等到了大海……”。
在這個案例中的另一問題:一個非法日志服務的存在,表面上是管理問題,實質上還是技術問題,因為 Redis 的使用無法像關系型數據庫那樣有 DBA 的監管,它的運維者無法管理和提前知道里面放的是什么數據,開發者也無須任何申明就可以向 Redis 中寫人數據并使用;
所以這里我們發現 Redis 的使用沒這些場景的管理后在長期的使用中比較容易失控,我們需要一個對 Redis 使用可治理和管控的透明層。
通過兩個小例子可以看到,在 Redis 亂用的那個年代里,使用它的兄弟們一定是痛的,承受了各種故障的狂轟濫炸:
Redis 被 keys 命令堵塞了。
Keepalived 切換虛 IP 失敗,虛 IP 被釋放了。
用 Redis 做計算了,Redis 的 CPU 占用率成了 100%了。
主從同步失敗了。
Redis 客戶端連接數爆了。
... ...
(未完待續)
想要加入中生代架構群的小伙伴,請添加群合伙人大白的微信
申請備注(姓名+公司+技術方向)才能通過哦!
阿里技術精彩文章推薦
往期推薦
深度:揭秘阿里巴巴的客群畫像
多隆:從工程師到阿里巴巴合伙人
阿里技術專家楚衡:架構制圖的工具與方法論
螞蟻集團技術專家山丘:性能優化常見壓測模型及優缺點
阿里文娛技術專家戰獒: 領域驅動設計詳解之What, Why, How?
阿里專家馬飛翔:一文讀懂架構整潔之道
阿里專家常昊:新人如何上手項目管理?
螞蟻集團沈凋墨:Kubernetes-微內核的分布式操作系統
阿里合伙人范禹:常掛在阿里技術人嘴邊的四句土話
阿里技術專家都鐸:一文搞懂技術債
支付寶研究員兼OceanBase總架構師楊傳輝:我在數據庫夢之隊的十年成長路
阿里技術專家麒燁:修煉測試基本功
阿里計算平臺掌門人賈揚清:我對人工智能方向的一點淺見
螞蟻資深算法專家周俊:從原理到落地,支付寶如何打造保護隱私的共享智能?
阿里高級技術專家簫逸:如何畫好一張架構圖?
阿里高級技術專家張建飛:應用架構分離業務邏輯和技術細節之道
螞蟻科技 Service Mesh 落地實踐與挑戰 | GIAC 實錄
阿里6年,我的技術蛻變之路!
螞蟻集團涵暢:再啟程,Service Mesh 前路雖長,尤可期許
阿里P9專家右軍:大話軟件質量穩定性
阿里合伙人程立:阿里15年,我撕掉了身上兩個標簽
阿里高工流生 | 云原生時代的 DevOps 之道
阿里高級技術專家邱小俠:微服務架構的理論基礎 - 康威定律
阿里P9專家右軍:以終為始的架構設計
阿里P8架構師:淘寶技術架構從1.0到4.0的架構變遷!12頁PPT詳解
阿里技術:如何畫出一張合格的技術架構圖?
螞蟻資深技術專家王旭:開源項目是如何讓這個世界更安全的?
阿里資深技術專家崮德:8 個影響我職業生涯的重要技能
儒梟:我看技術人的成長路徑
阿里高級技術專家宋意:平凡人在阿里十年的成長之旅
阿里技術專家甘盤:淺談雙十一背后的支付寶LDC架構和其CAP分析
阿里技術專家光錐:億級長連網關的云原生演進之路
阿里云原生張羽辰:服務發現技術選型那點事兒
螞蟻研究員玉伯:做一個簡單自由有愛的技術人
阿里高級技術專家至簡: Service Mesh 在超大規模場景下的落地挑戰
阿里巴巴山獵:手把手教你玩轉全鏈路監控
阿里涉江:你真的會學習嗎?從結構化思維說起
螞蟻金服資深技術專家經國:云原生時代微服務的高可用架構設計
深入分布式緩存之EVCache探秘開局篇
? ?END ? ?? #架構師必備#點分享點點贊點在看總結
以上是生活随笔為你收集整理的同程旅行王晓波:同程凤凰缓存系统在基于 Redis 方面的设计与实践(上篇)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: nyoj-20--吝啬的国度-DFS+v
- 下一篇: hdu-4501-小明系列故事——买年货