導讀
美圖公司擁有眾多的產品以及海量活躍使用者,我們的日推訊息也達到了上億次,這對訊息推送也提出了較高的要求。2017 年,美圖自研推送服務,採用 Redis 作為訊息儲存,但隨著業務接入量增加、資料量快速增加,服務的可維護性變得困難。公司已經搭建了一套新型的資料庫 - Titan (已經開源),底層以 PingCAP 的 TIKV 做資料引擎,上層實現 Redis 協議解析,既可以水平彈性擴容,適合海量資料儲存管理,效能又可以隨水平擴容而提高。
在本文中,先簡要的介紹推送現狀及難題,後詳細的說明Titan 在全面替換原有的儲存的過程、以及在接入過程中遇到的問題和解決方案。
推送現狀2017 年初,美圖自研推送服務(Thor),完成 APP 的定向推送、批量推送、離線推送、訊息過期、 token 管理等功能。至今已經接入美圖全部的 App 中,線上到達率為 99% 以上,訊息秒級觸達使用者,日常服務端訊息儲存量約為 700G ,最高峰為 1T 。
隨著服務接入量級的提升系統的架構模型,儲存模型也不斷在演進,下面將講述推送系統目前的架構模型和儲存模型以及當前推送服務面臨的難題問題。
架構模型推送服務整體拆分為三個部分:長連結服務(bifrost)、推送服務(thor)、路由分發(route_server)。服務之間串聯通過發現服務(etcd)實現。長連結服務負責保持客戶端和服務端的鏈路通暢。推送服務負責管理客戶端的 token 資訊和儲存管理訊息。
儲存模型
訊息儲存管理作為推送核心模組,構建一個恰到好處的模型至關重要。在推送業務中訊息的操作可以劃分為兩個維度:
綜上所述,美圖推送將資料模型抽象圖如下,每個客戶端(cid)擁有唯一的訊息下發佇列,每個訊息會被繫結唯一的標識(mid),服務端通過指定 cid 下發訊息,訊息前先寫入儲存後進行訊息投遞,監測到客戶端登陸後服務端查詢離線訊息重新下發,降低了訊息在異常場景損失的概率。通過上報回執訊息中的 mid 清除已下發成功訊息,保證了佇列的訊息的有效性。
當下難題
在開發初期選擇訊息儲存的時候,一方面考慮到在推送業務場景中訊息留存時間在可控的時間內且資料量較少,Redis 自身支援豐富的資料結構,提供高速的訪問能力。另外一方面公司內部對 Redis 使用和管理有豐富的經驗。因此選擇 Redis 做訊息儲存,部署方式則採用一主兩從客戶端做分片寫入的叢集模式。
隨著業務接入量的增加,資料量越來越大,服務的維護性越來越難。其中主要難題如下:
• 單節點資料量增大,持久化耗時加劇,導致服務抖動,短時間不可用;
• 儲存擴容導致服務有損;
• 服務成本越來越高。
以上困難,只有替換儲存才可以從根本解決這些問題。我們需要選擇一種適合海量資料儲存、水平彈性擴容、對業務遷移友好的儲存。Titan 便是我們的不二人選,接下來要和大家簡要介紹下 Titan 。
TitanTitan 是美圖公司研發並開源的 NoSQL,目前交給第三方DistributedIO 組織維護。DistributedIO 組織由源 Titan 開發團隊發起構建。主要適合要求具備分散式事務的大規模資料儲存,適用海量資料,少量事務衝突的場景。Titan 劃分為兩層:下層使用 PingCAP 的 TiKV 資料庫做資料持久化; 上層通過解析 Redis 協議將各類資料結構轉化為 KV 資料方式儲存。
TIKV 是 PingCAP 開源的分散式 Key-Value 儲存,它使用 Rust 開發,採用 Raft 一致性協議保證資料的強一致性,以及穩定性,同時通過 Raft 的 Configuration Change 機制實現了系統的可擴充套件性,提供了支援 ACID 事務的 Transaction API。雖然 PingCAP 也開源一個名字叫的 Titan 的儲存引擎,但與本文介紹的 Titan 是不同的,只不過名字一樣而已。
Titan 自身是無狀態服務,提供 Redis 命令翻譯執行功能。在設計上支援在共享一套叢集情況下業務資料隔離,遵從 Redis 5.0 開發實現,自身整合 prometheus 監控。 目前 Ttian 已經支援 lists ,strings ,hashes ,sets ,sorted sets 等基礎資料結構。自從開源以來,收到了廣泛的關注,目前已經收到 700 多的 star 和 50 多次的 fork,在業記憶體在成功接入並投入線上使用的公司案例,如北京轉轉精神科技有限公司(轉轉)已經遷移 800G 的資料到 Titan中。
儲存平滑遷移現在大家對Titan 有了基本的了解,但儲存的替換不是一蹴而就的事情。Titan叢集的大小,儲存遷移平滑過度,資料的遷移,這都是我們的絆腳石。下面將從業務角度出發給出答案。
業務評估及優化推送服務經常面臨高頻的訊息讀寫場景,因此對儲存的效能要求也是極高的,我們首先要做的就是對Titan 進行壓測。推送服務依賴 Redis 的 Hash 資料結構完成對訊息的管理,使用的命令有 hset ,hgetall ,hdel。壓測 Titan 在 1 臺 sas 盤 CPU 40核 記憶體 96 G 機器和 3 臺 ssd 盤 CPU 40 核 記憶體 96 G 機器上,部署採用了 1 個 Titan 12 個TiKV 例項方式。通過整理壓測資料和統計線上監控給出對每個命令的期待的 QPS 如下表。
表一:業務評估表
通過上圖對比分析發現,Titan 按照這個叢集配置hset 和 hgetall 兩個命令明顯不滿足如線上要求。可以通過擴容 Titan 叢集的方式提高系統吞吐,但初於對成本的考慮,我們從業務角度嘗試優化。
hset
在Redis 中訊息的儲存使用 hset 命令根據客戶端 cid 進行 hash 寫到固定的 Redis 儲存中,需要逐條寫入。在 Titan 中我們嘗試將 hset 命令從單條的執行改為 batch 操作,經過測試確立方案為每 100 條命令執行一次事務提交, 雖然延遲從 10 ms 增到到 100 ms 以內波動,但優化後效能要求從 150 k/s 將為 20 k/s 且延遲在可以接受的範圍內。
hgetall
在Redis 中客戶端登陸可以通過 hgetall 獲取當前所有離線訊息重新接收,在調研發現客戶端存在少量離線訊息或者不存在離線訊息。針對這種情況重新對 hgetall 進行壓測,hgetall 在這種場景下可以提供 25 k/s QPS 滿足需求。
在經過優化之後,Titan 可以滿足目前推送線上的基本要求。按照上述配置決定將所有訊息儲存逐步灰度到 Titan上。
平滑替換訊息儲存作為美圖推送核心單元,要保證7x 24小時可用,在遷移過程中,如何在Titan 異常情況下保證服務正常訪問,舊資料如何業務無損的同步到新叢集是我們面臨的兩大難題。
在推送業務中訊息生命週期都在一週之內,如果採用雙寫模式,將資料順序寫入Redis ,Titan兩個叢集,讀取只在 Redis 叢集上。一週後在將讀切到 Titan 上,在穩定執行一段時間後,下掉 Redis 叢集完成儲存叢集遷移。其中 Titan 叢集在任何時間下發生異常都可以下掉,操作切回 Redis 叢集。
問題和解決方法在美圖推送服務接入Titan 的過程中,我們團隊也遇到了不少的問題,比如事務衝突,短時間內 Titan 堆積大量命令,導致雪崩效應等問題。我們在具體實踐中也總結了一些解決方法。
事務衝突
現象:在推送的高峰期期間事務衝突頻繁,Titan 節點記憶體升高,TiKV 機器記憶體耗盡節點OOM。
原因:Titan 中對相同的 key 寫入和刪除併發操作會導致事務衝突,訊息的寫入採用 batch 方式寫入,一旦發生事務衝突,這批資料會集體產生回滾、重試操作,短時間內Titan 會積壓大量命令導致記憶體上升,TiKV 操作事務回滾導致記憶體耗盡 節點OOM。
解決方式:捨棄meta中資料數量記錄欄位,減少單個key 的操作衝突。通過這種方式僅僅降低了單個 key 的衝突,在 hash 操作中針對單個 felid 的修改仍然存在衝突。但此種優化已經到達業務接受水平。
TiKV OOM
原因:線上採用記憶體96G 機器部署 4 個 TiKV 模式,在高峰期佇列請求處理佇列積壓,導致機器記憶體耗盡。
解決方式:減少TiKV 配置中 block-cache-size 的大小,降低記憶體佔用。
Raft Store CPU 使用率過高
現象:redis 命令執行存在卡頓,TiKV 監控中 Raft Store CPU 使用率超過 90%。
原因:在TiKV 中 raftstore 是單執行緒工作。
解決方案:叢集擴容,增加一個伺服器,增加一個4個 TiKV 節點,提高 Titan 服務的處理能力,從根本上解決了問題。
TiKV channel full
現象:Redis 命令執行超時。
原因:在資料短時間持續大量寫入,導致Raft 熱點,直接導致TiKV 的 Region leader 遷移。
解決方式:通過配置調整TiKV 的 scheduler-notify-capacity 大小,增加scheduler 一次獲取最大訊息個數,降低了問題發生的頻率。
總結在經歷半年的嘗試後,推送儲存整體替換為Titan,儲存也由16臺 Redis 機器切換為 4 臺的 ssd TiKV 專屬伺服器和 2 臺混部 Titan 伺服器上,成本節約60%,可維護性大大提高,現在推送服務已經穩定跑了半年,期間未發生故障。隨著 Titan 的資料結構的完善,未來準備推進更多業務接入。
作者簡介王鴻佳,系統研發工程師 ,現任職於美圖公司,主要負責通用長連線服務、美圖推送系統基礎服務研發。對分散式研發技術及開源專案有濃厚的興趣,DistributedIO 團隊核心成員。
-
1 #
採用現成開源系統,做拼接和改進,這是個比較不錯的方案,比開發都喜歡的從0開始開發要好很多。然後,如果是專用於訊息推送,其實大可不必:可以訊息儲存時合併(一批訊息裡大部分訊息只是cid不同),訊息推送一般實時性要求也不高,還有可以事先篩選不同活躍使用者,等等,應該不需要搞到上T的資料量。