美图每天亿级消息存储演进——从Redis到Titan,完美解决扩容问题
作者簡介:王鴻佳,系統研發工程師 ,現任職于美圖公司,主要負責通用長連接服務、美圖推送系統基礎服務研發。對分布式研發技術及開源項目有濃厚的興趣,DistributedIO 團隊核心成員。
導讀
美圖公司擁有眾多的產品以及海量活躍用戶,我們的日推消息也達到了上億次,這對消息推送也提出了較高的要求。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 做消息存儲,部署方式則采用一主兩從客戶端做分片寫入的集群模式。
隨著業務接入量的增加,數據量越來越大,服務的維護性越來越難。其中主要難題如下:
????頻繁的消息過期和刪除導致Redis 的內存碎片比居高不下;
????單節點數據量增大,持久化耗時加劇,導致服務抖動,短時間不可用;
????存儲擴容導致服務有損;
????服務成本越來越高。
以上困難,只有替換存儲才可以從根本解決這些問題。我們需要選擇一種適合海量數據存儲、水平彈性擴容、對業務遷移友好的存儲。Titan 便是我們的不二人選,接下來要和大家簡要介紹下 Titan 。
Titan
Titan 是美圖公司研發并開源的 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 如下表。
| ? | hset | hgetall | hdel |
| 壓測數據 | 31 k/s | 13k/s | 47k |
| 實際需求 | 150k/s | 20k/s | 30k/s |
表一:業務評估表
通過上圖對比分析發現,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 的數據結構的完善,未來準備推進更多業務接入。?
?
感謝張同學(nioshield@gmail.com)及時實現的 hashes 數據結構。?
感謝 PingCAP 公司在遷移過程中對于TiKV 的技術支持。
Redis 的壓測工具:https://github.com/fperf/redis
Titan 項目地址:https://github.com/distributedio/titan
TiKV 項目地址:https://github.com/tikv
總結
以上是生活随笔為你收集整理的美图每天亿级消息存储演进——从Redis到Titan,完美解决扩容问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java Bean 为什么必须要有一个无
- 下一篇: Java中的门面设计模式,非常有用!