http://www.open-open.com/lib/view/open1470387376121.html
王曉波,同程旅游首席架構(gòu)師,專注于高并發(fā)互聯(lián)網(wǎng)架構(gòu)設(shè)計(jì)、分布式電子商務(wù)交易平臺設(shè)計(jì)、大數(shù)據(jù)分析平臺設(shè)計(jì)、高可用性系統(tǒng)設(shè)計(jì),基礎(chǔ)云相關(guān)技術(shù)研究,對 Docker 等容器有深入的實(shí)踐。另對系統(tǒng)運(yùn)維和信息安全領(lǐng)域也大量的技術(shù)實(shí)踐。曾設(shè)計(jì)過多個(gè)并發(fā)百萬以上、每分鐘 20 萬以上訂單量的電商交易平臺,熟悉 B2C、B2B、B2B2C、O2O 等多種電商形態(tài)系統(tǒng)的技術(shù)設(shè)計(jì)。熟悉電子商務(wù)平臺技術(shù)發(fā)展特點(diǎn),擁有十多年豐富的技術(shù)架構(gòu)、技術(shù)咨詢經(jīng)驗(yàn),深刻理解電商系統(tǒng)對技術(shù)選擇的重要性。
旅游大家現(xiàn)在比較熟悉,同程旅游涵蓋的業(yè)務(wù)比較多,從火車票到住宿都有。今天我們講一下同程旅游的緩存系統(tǒng),包括整個(gè)緩存架構(gòu)如何設(shè)計(jì)。先來看一下緩存我們走過了哪些歷程。
從 memcache 開始使用緩存
再從 memcache 轉(zhuǎn)到 Redis
從單機(jī) Redis 升級到集群 Redis
從簡單的運(yùn)維升級到全自動(dòng)運(yùn)維
重開發(fā) Redis 客戶端
開發(fā) Redis 調(diào)度治理系統(tǒng)
Redis 部署全面 Docker 化
旅游是比較復(fù)雜的業(yè)務(wù),住、吃、玩相關(guān)功能都會壓到平臺上,整個(gè)在線旅游應(yīng)用量非常大,酒店、機(jī)票,或者是賣一個(gè)去泰國旅行團(tuán),業(yè)務(wù)邏輯完全不一樣,因此眾多業(yè)務(wù)會帶來大量系統(tǒng)的訪問壓力。
Redis 遍地開花的現(xiàn)狀及問題
Redis 集群的管理
所有互聯(lián)網(wǎng)的應(yīng)用里面,可能訪問最多的就是 cache。一開始時(shí)候一些團(tuán)隊(duì)認(rèn)為 cache 就像西游記里仙丹一樣,當(dāng)一個(gè)系統(tǒng)訪問過大扛不住時(shí),用一下 Redis,系統(tǒng)壓力就解決了。在這種背景下,我們的系統(tǒng)里面 Redis 不斷增多,逐漸增加到幾百臺服務(wù)器,每臺上面還有多個(gè)實(shí)例,因此當(dāng)存在幾千個(gè) Redis 實(shí)例后,可能運(yùn)維也很難說清楚哪個(gè) Redis 是什么業(yè)務(wù)。
單點(diǎn)故障
這種背景下會存在什么樣痛苦的場景?正常情況下 cache 是為了增加系統(tǒng)的性能,是畫龍點(diǎn)睛的一筆,但是當(dāng)時(shí)我們 cache 會是什么樣?它掛了就可能讓我們整個(gè)系統(tǒng)崩潰。比如說 CPU 才 5%,也許就由于緩存問題系統(tǒng)就掛了。
高可用與主從同步問題
因?yàn)?cache 有單點(diǎn),我們想放兩個(gè)不就好了嗎,所以就做了主從。這時(shí)候坑又來了,為什么呢?比如有些 Redis 值非常大,如果偶爾網(wǎng)絡(luò)質(zhì)量不太好,就會帶來主從不同步,當(dāng)兩邊主和從都死或者出問題的時(shí),重啟的時(shí)間非常長。
監(jiān)控
為了高可用,我們需要全面的監(jiān)控。當(dāng)時(shí)我們做了哪些監(jiān)控呢?
connected_clients :已連接客戶端的數(shù)量
client_longest_output_list :當(dāng)前連接的客戶端當(dāng)中,最長的輸出列表
client_longest_input_buf: 當(dāng)前連接的客戶端當(dāng)中,最大輸入緩存
blocked_clients: 正在等待阻塞命令(BLPOP、BRPOP、BRPOPLPUSH)的客戶端的數(shù)量
used_memory_human: 以人可讀的格式返回 Redis 分配的內(nèi)存總量
used_memory_rss: 從操作系統(tǒng)的角度,返回 Redis 已分配的內(nèi)存總量(俗稱常駐集大小)。這個(gè)值和 top 、 ps 等命令的輸出一致。
replication: 主/從復(fù)制信息
instantaneous_ops_per_sec: 服務(wù)器每秒鐘執(zhí)行的命令數(shù)量。
下面是一個(gè)接近真實(shí)場景運(yùn)維與開發(fā)的對話場景。
開發(fā):Redis 為啥不能訪問了?
運(yùn)維:剛剛服務(wù)器內(nèi)存壞了,服務(wù)器自動(dòng)重啟了
開發(fā):為什么 Redis 延遲這么大?
運(yùn)維:不要在 Zset 里放幾萬條數(shù)據(jù),插入排序會死人啊
開發(fā):寫進(jìn)去的 key 為什么不見了?
運(yùn)維:Redis 超過最大大小了啊,不常用 key 都丟了啊
開發(fā):剛剛為啥讀取全失敗了
運(yùn)維:網(wǎng)絡(luò)臨時(shí)中斷了一下,從機(jī)全同步了,在全同步完成之前,從機(jī)的讀取全部失敗
開發(fā):我需要 800G 的 Redis,什么時(shí)候能準(zhǔn)備好?
運(yùn)維:線上的服務(wù)器最大就 256G,不支持這么大
開發(fā):Redis 慢得像驢,服務(wù)器有問題了?
運(yùn)維:千萬級的 KEY,用 keys*,慢是一定了。
因此我們一個(gè)架構(gòu)師最后做了以下總結(jié)
從來沒想過,一個(gè)小小的 Redis 還有這么多新奇的功能。 就像在手上有錘子的時(shí)候,看什么都是釘子 。漸漸的,開發(fā)規(guī)范倒是淡忘了,新奇的功能卻接連不斷的出現(xiàn)了,基于 Redis 的分布式鎖、日志系統(tǒng)、消息隊(duì)列、數(shù)據(jù)清洗等,各種各樣的功能不斷上線,從而引發(fā)各種各樣的問題。 運(yùn)維天天疲于奔命,到處處理著 Redis 堵塞、網(wǎng)卡打爆、連接數(shù)爆表……
總結(jié)了一下,我們之前的緩存存在哪些問題?
我們需要一個(gè)什么樣的完美緩存系統(tǒng)?
我相信上面這些情況在很多大量使用 Redis 的團(tuán)隊(duì)中都存在,如果發(fā)展到這樣一個(gè)階段后,我們到底需要一個(gè)什么樣的緩存?
服務(wù)規(guī)模:支持大量的緩存訪問,應(yīng)用對緩存大少需求就像貪吃蛇一般
集群可管理性:一堆孤島般的單機(jī)服務(wù)器緩存服務(wù)運(yùn)維是個(gè)迷宮
冷熱區(qū)分:現(xiàn)在緩存中的數(shù)據(jù)許多并不是永遠(yuǎn)的熱數(shù)據(jù)
訪問的規(guī)范及可控:還有許多的開發(fā)人員對緩存技術(shù)了解有限,胡亂用的情況很多
在線擴(kuò)縮容:起初估算的不足到用時(shí)發(fā)現(xiàn)瓶頸了
這個(gè)情況下,我們?nèi)タ紤]使用更好的方案,本來我們是想直接使用某個(gè)開源方案就解決了,但是我們發(fā)現(xiàn)每個(gè)開源方案針對性的解決 Redis 上述痛點(diǎn)的某一些問題,每一個(gè)方案在評估階段跟我們需求都沒有 100% 匹配。每個(gè)開源方案本身都很優(yōu)秀,也許只是說我們的場景的特殊性,沒有任何否定的意思。
下面我們當(dāng)時(shí)評估的幾個(gè)開源方案,看一下為什么當(dāng)時(shí)沒有引入。
CacheCloud:跟我們需要的很像,它也做了很多的東西,但是它對我們不滿足是部署方案不夠靈活,對運(yùn)維的策略少了點(diǎn)。
Codis:這個(gè)其實(shí)很好,當(dāng)年我們已經(jīng)搭好了準(zhǔn)備去用了,后來又下了,因?yàn)橹坝幸粋€(gè)業(yè)務(wù)需要 800G 內(nèi)存,后來我們發(fā)現(xiàn)這個(gè)大集群有一個(gè)問題,因?yàn)橛玫貌皇呛芤?guī)范,如果在這種情況下給他一個(gè)更大的集群,那我們可能死的機(jī)率更大,所以我們也放棄了。另外 800G 也很浪費(fèi),并不完全都是熱數(shù)據(jù),我們想把它存到硬盤上一部分,很多業(yè)務(wù)使用方的心理是覺得在磁盤上可能會有性能問題,還是放在 Redis 放心一點(diǎn),其實(shí)這種情況基本不會出現(xiàn),因此我們需要一定的冷熱區(qū)分支持。
Pika:Pika 可以解決上面的大量數(shù)據(jù)保存在磁盤的問題,但是它的部署方案少了點(diǎn),而且 Pika 的設(shè)計(jì)說明上也表示主要針對大的數(shù)據(jù)存儲。
Twemproxy:最后我們想既然直接方案不能解決,那可以考慮代理治理的方式,但是問題是它只是個(gè)代理,Redis 被濫用的問題還是沒有真正的治理好,所以后面我們準(zhǔn)備自己做一個(gè)。
全新設(shè)計(jì)的緩存系統(tǒng)——鳳凰
我們新系統(tǒng)起了一個(gè)比較高大上的名字,叫鳳凰,愿景是鳳凰涅磐,從此緩存不會再死掉了。
鳳凰是怎么設(shè)計(jì)的?

自定義客戶端方式與場景配置能力
在支持 Redis 本身的特性的基礎(chǔ)上,我們需要通過自定義的客戶端來實(shí)現(xiàn)一些額外的功能。
支持場景配置,我們考慮根據(jù)場景來管控它的場景,客戶端每次用 Redis 的時(shí)候,必須把場景上報(bào)給我,你是在哪里,用這件事兒是干什么的,雖然這個(gè)對于開發(fā)人員來說是比較累的,他往往嵌在它的任務(wù)邏輯里面直接跟進(jìn)去。曾江場景配置之后,在緩存服務(wù)的中心節(jié)點(diǎn),就可以把它分開,同一個(gè)應(yīng)用里面兩個(gè)比較重要的場景就會不用同一個(gè) Redis,避免掛的時(shí)候兩個(gè)一起掛。
同時(shí)也需要一個(gè)調(diào)度系統(tǒng),分開之后,不同的 Redis 集群所屬的服務(wù)器也需要分開。分開以后我的數(shù)據(jù)怎么復(fù)制,出問題的時(shí)候我們怎么把它遷移?因此也需要一個(gè)復(fù)制和遷移的平臺去做。

另外這么一套復(fù)雜的東西出來之后,需要一個(gè)監(jiān)控系統(tǒng);客戶端里也可以增加本地 cache 的支持。在業(yè)務(wù)上也可能需要對敏感的東西進(jìn)行過濾。在底層,可以自動(dòng)實(shí)現(xiàn)對訪問數(shù)據(jù)源的切換,對應(yīng)用是透明的,應(yīng)用不需要關(guān)心真正的數(shù)據(jù)源是什么,這就是我們自己做的客戶端。
代理層方式
客戶端做了之后還發(fā)生一個(gè)問題,前面分享的唯品會的姚捷說了一個(gè)問題,很多情況下很難升級客戶端。再好的程序員寫出來的東西還是有 bug,如果 Redis 組件客戶端發(fā)現(xiàn)了一個(gè) bug 需要升級,但我們線上有幾千個(gè)應(yīng)用分布在多個(gè)業(yè)務(wù)開發(fā)團(tuán)隊(duì),這樣導(dǎo)致很難驅(qū)動(dòng)這么多開發(fā)團(tuán)隊(duì)去升級。另外一個(gè)現(xiàn)狀就是是中國在線旅游行業(yè)好像都喜歡用 .net,我們之前很多的系統(tǒng)也都是 .net 開發(fā),最近我們也把客戶端嵌到 .net,實(shí)現(xiàn)了 .net 版本,但是由于各種原因,要推動(dòng)這么多歷史業(yè)務(wù)進(jìn)行改造切換非常麻煩,甚至有些特別老的業(yè)務(wù)最后沒法升級。
因此我們考慮了 proxy 方案,這些業(yè)務(wù)模塊不需要修改代碼,我們的想法就是讓每一個(gè)項(xiàng)目的每一個(gè)開發(fā)者自己開發(fā)的代碼是干凈的,不要在他的代碼里面嵌任何的東西,業(yè)務(wù)訪問的就是一個(gè) Redis。

那么我們就做了,首先它是 Redis 的協(xié)議,接下來剛才我們在客戶端里面支持的各種場景配置錄在 proxy 里面,實(shí)現(xiàn)訪問通道控制。然后再把 Redis 本身沉在我們 proxy 之后,讓它僅僅變成一個(gè)儲存的節(jié)點(diǎn),proxy 再做一些自己的事情,比如本地緩存及路由。冷熱區(qū)分方面,在一些壓力不大的情況下,調(diào)用方看到的還是個(gè) Redis ,但是其實(shí)可能數(shù)據(jù)是存在 RocksDB 里面了。
緩存服務(wù)的架構(gòu)設(shè)計(jì)

可擴(kuò)容能力

多協(xié)議支持
還有一塊老項(xiàng)目是最大的麻煩,同程有很多之前是 memcache 的應(yīng)用,后來是轉(zhuǎn)到 Redis 去的,但是轉(zhuǎn)出一個(gè)問題來了,有不少業(yè)務(wù)由于本身事情較多沒有轉(zhuǎn)換成 Redis,這些釘子戶怎么辦?同時(shí)維護(hù)這兩個(gè)平臺是非常麻煩的,剛才 proxy 就派到用場了。因?yàn)?memcache 本身它的數(shù)據(jù)支持類型是比較少的,因此轉(zhuǎn)換比較簡單,如果是一個(gè)更復(fù)雜的類型,那可能就轉(zhuǎn)不過來了。所以我們 proxy 就把這些釘子戶給拆掉了,他覺得自己還是在用 memcache,其實(shí)已經(jīng)被轉(zhuǎn)成了 Redis。

管理與可監(jiān)控能力
最后一塊,我們這樣一個(gè)平臺怎么去監(jiān)控它,和怎么去運(yùn)維它?
整體的管制平臺,
運(yùn)維操作平臺,讓它可以去操作,動(dòng)態(tài)的在頁面上操作做一件事情,
整體監(jiān)控平臺。我們從客戶端開始,到服務(wù)器的數(shù)據(jù),全部把它監(jiān)控起來。
自擴(kuò)容自收縮。動(dòng)態(tài)的自擴(kuò)容,自收縮。
一些業(yè)務(wù)應(yīng)用場景
也是用了一些場景,比如說同程前兩年沖的比較狠的就是一元門票,大家肯定說搶購,這個(gè)最大的壓力是什么,早上的九點(diǎn)半,這是我們系統(tǒng)最大的壓力,為什么呢,一塊錢的門票的從你買完票到景區(qū)里面去,這件事情是在九點(diǎn)半集中爆發(fā)的,你要說這個(gè)是系統(tǒng)掛了入不了園了,那十幾萬人不把這個(gè)景區(qū)打砸了才怪。那個(gè)時(shí)候系統(tǒng)絕對不能死。搶購沒有關(guān)系,入園是我們最大的壓力,
我們是靠新的系統(tǒng)提供了訪問能力及可用性的支持,把類似這種場景支撐下來,其實(shí)緩存本身是可以支撐住的,但是如果濫用管理失控,可能碰到這種高可用的需求就廢了。
還有一個(gè)是火車票系統(tǒng),火車票查詢量非常大,我們這主要是用了新系統(tǒng)的收縮容,到了晚上的時(shí)候查的人不多了,到了早上的時(shí)候特別多,他查詢量是在一個(gè)高低跌蕩的,所以我們可以根據(jù)訪問的情況來彈性調(diào)度。
Q&A
提問:你們監(jiān)控是用官方的,還是用自己開發(fā)的監(jiān)控軟件?
王曉波:我們監(jiān)控是執(zhí)行命令查過來,是我們開發(fā)的。
提問:收縮和擴(kuò)容這塊你們是怎么做的?
王曉波:收縮和擴(kuò)容,其實(shí)現(xiàn)在我們 Redis 本身就是 Redis Cluster,直接往里面加節(jié)點(diǎn),加完節(jié)點(diǎn)有一個(gè)問題,要去執(zhí)行一些命令把數(shù)據(jù)搬走,我們就會自動(dòng)的把它數(shù)據(jù)平衡掉。
上面說的是 Redis 3.0 集群的情況,還有一些不是 3.0 的怎么辦?我們有一個(gè)程序模擬自己的從機(jī)拷出來,然后分配到另外兩個(gè)點(diǎn)上去,本來是一臺,我有個(gè)程序模擬自己是一個(gè) Redis 從,它同步過來之后就根據(jù)這個(gè)數(shù)據(jù)分到另外兩臺里面去,這個(gè)我的成本比較低,因?yàn)槲业?Redis 集群可以做的很小。為什么會這樣呢,我們之前也是碰到一些麻煩點(diǎn),一個(gè)機(jī)器很大,但是往往我要的 Redis 沒這么多,但是最高的時(shí)候是每家隔開,對一些小的應(yīng)用是最好的,但是我服務(wù)器那么多,所以當(dāng)我們 Redis Cluster 上來之后我們第一個(gè)用的。