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

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

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

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

可擴容能力

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

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