美图每天亿级消息存储演进——从Redis到Titan,完美解决扩容问题
作者簡介:王鴻佳,系統(tǒng)研發(fā)工程師 ,現(xiàn)任職于美圖公司,主要負(fù)責(zé)通用長連接服務(wù)、美圖推送系統(tǒng)基礎(chǔ)服務(wù)研發(fā)。對分布式研發(fā)技術(shù)及開源項(xiàng)目有濃厚的興趣,DistributedIO 團(tuán)隊(duì)核心成員。
導(dǎo)讀
美圖公司擁有眾多的產(chǎn)品以及海量活躍用戶,我們的日推消息也達(dá)到了上億次,這對消息推送也提出了較高的要求。2017 年,美圖自研推送服務(wù),采用 Redis 作為消息存儲,但隨著業(yè)務(wù)接入量增加、數(shù)據(jù)量快速增加,服務(wù)的可維護(hù)性變得困難。公司已經(jīng)搭建了一套新型的數(shù)據(jù)庫 - Titan (已經(jīng)開源),底層以 PingCAP 的 TIKV 做數(shù)據(jù)引擎,上層實(shí)現(xiàn) Redis 協(xié)議解析,既可以水平彈性擴(kuò)容,適合海量數(shù)據(jù)存儲管理,性能又可以隨水平擴(kuò)容而提高。
在本文中,先簡要的介紹推送現(xiàn)狀及難題,后詳細(xì)的說明Titan 在全面替換原有的存儲的過程、以及在接入過程中遇到的問題和解決方案。
推送現(xiàn)狀
2017 年初,美圖自研推送服務(wù)(Thor),完成 APP 的定向推送、批量推送、離線推送、消息過期、 token 管理等功能。至今已經(jīng)接入美圖全部的 App 中,在線到達(dá)率為 99% 以上,消息秒級觸達(dá)用戶,日常服務(wù)端消息存儲量約為 700G ,最高峰為 1T 。
隨著服務(wù)接入量級的提升系統(tǒng)的架構(gòu)模型,存儲模型也不斷在演進(jìn),下面將講述推送系統(tǒng)目前的架構(gòu)模型和存儲模型以及當(dāng)前推送服務(wù)面臨的難題問題。
架構(gòu)模型
推送服務(wù)整體拆分為三個部分:長鏈接服務(wù)(bifrost)、推送服務(wù)(thor)、路由分發(fā)(route_server)。服務(wù)之間串聯(lián)通過發(fā)現(xiàn)服務(wù)(etcd)實(shí)現(xiàn)。長鏈接服務(wù)負(fù)責(zé)保持客戶端和服務(wù)端的鏈路通暢。推送服務(wù)負(fù)責(zé)管理客戶端的 token 信息和存儲管理消息。
圖一:美圖推送整體架構(gòu)圖?存儲模型
消息存儲管理作為推送核心模塊,構(gòu)建一個恰到好處的模型至關(guān)重要。在推送業(yè)務(wù)中消息的操作可以劃分為兩個維度:
-
站在客戶端維度來看,需要保證精準(zhǔn)的接受消息和上報(bào)回執(zhí)信息。
-
站在系統(tǒng)維度看,推送系統(tǒng)應(yīng)該具備消息靈活管理功能,保證在系統(tǒng)異常崩潰、網(wǎng)絡(luò)隔離等異常場景下消息不丟不亂能力。
綜上所述,美圖推送將數(shù)據(jù)模型抽象圖如下,每個客戶端(cid)擁有唯一的消息下發(fā)隊(duì)列,每個消息會被綁定唯一的標(biāo)識(mid),服務(wù)端通過指定 cid 下發(fā)消息,消息前先寫入存儲后進(jìn)行消息投遞,監(jiān)測到客戶端登陸后服務(wù)端查詢離線消息重新下發(fā),降低了消息在異常場景損失的概率。通過上報(bào)回執(zhí)消息中的 mid 清除已下發(fā)成功消息,保證了隊(duì)列的消息的有效性。
圖二:數(shù)據(jù)模型圖?當(dāng)下難題
在開發(fā)初期選擇消息存儲的時候,一方面考慮到在推送業(yè)務(wù)場景中消息留存時間在可控的時間內(nèi)且數(shù)據(jù)量較少,Redis 自身支持豐富的數(shù)據(jù)結(jié)構(gòu),提供高速的訪問能力。另外一方面公司內(nèi)部對 Redis 使用和管理有豐富的經(jīng)驗(yàn)。因此選擇 Redis 做消息存儲,部署方式則采用一主兩從客戶端做分片寫入的集群模式。
隨著業(yè)務(wù)接入量的增加,數(shù)據(jù)量越來越大,服務(wù)的維護(hù)性越來越難。其中主要難題如下:
????頻繁的消息過期和刪除導(dǎo)致Redis 的內(nèi)存碎片比居高不下;
????單節(jié)點(diǎn)數(shù)據(jù)量增大,持久化耗時加劇,導(dǎo)致服務(wù)抖動,短時間不可用;
????存儲擴(kuò)容導(dǎo)致服務(wù)有損;
????服務(wù)成本越來越高。
以上困難,只有替換存儲才可以從根本解決這些問題。我們需要選擇一種適合海量數(shù)據(jù)存儲、水平彈性擴(kuò)容、對業(yè)務(wù)遷移友好的存儲。Titan 便是我們的不二人選,接下來要和大家簡要介紹下 Titan 。
Titan
Titan 是美圖公司研發(fā)并開源的 NoSQL,目前交給第三方DistributedIO 組織維護(hù)。DistributedIO 組織由源 Titan 開發(fā)團(tuán)隊(duì)發(fā)起構(gòu)建。主要適合要求具備分布式事務(wù)的大規(guī)模數(shù)據(jù)存儲,適用海量數(shù)據(jù),少量事務(wù)沖突的場景。Titan 劃分為兩層:下層使用 PingCAP 的 TiKV 數(shù)據(jù)庫做數(shù)據(jù)持久化;?上層通過解析 Redis 協(xié)議將各類數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)化為 KV 數(shù)據(jù)方式存儲。?
TIKV 是 PingCAP 開源的分布式 Key-Value 存儲,它使用 Rust 開發(fā),采用 Raft 一致性協(xié)議保證數(shù)據(jù)的強(qiáng)一致性,以及穩(wěn)定性,同時通過 Raft 的 Configuration Change 機(jī)制實(shí)現(xiàn)了系統(tǒng)的可擴(kuò)展性,提供了支持 ACID 事務(wù)的 Transaction API。雖然 PingCAP 也開源一個名字叫的 Titan 的存儲引擎,但與本文介紹的 Titan 是不同的,只不過名字一樣而已。
Titan 自身是無狀態(tài)服務(wù),提供 Redis 命令翻譯執(zhí)行功能。在設(shè)計(jì)上支持在共享一套集群情況下業(yè)務(wù)數(shù)據(jù)隔離,遵從 Redis ?5.0 開發(fā)實(shí)現(xiàn),自身集成 prometheus 監(jiān)控。?目前 Ttian 已經(jīng)支持 lists ,strings ,hashes ,sets ,sorted sets 等基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)。自從開源以來,收到了廣泛的關(guān)注,目前已經(jīng)收到 700 多的 star 和 50 多次的 fork,在業(yè)內(nèi)存在成功接入并投入線上使用的公司案例,如北京轉(zhuǎn)轉(zhuǎn)精神科技有限公司(轉(zhuǎn)轉(zhuǎn))已經(jīng)遷移 800G 的數(shù)據(jù)到 Titan中。
存儲平滑遷移
現(xiàn)在大家對Titan 有了基本的了解,但存儲的替換不是一蹴而就的事情。Titan集群的大小,存儲遷移平滑過度,數(shù)據(jù)的遷移,這都是我們的絆腳石。下面將從業(yè)務(wù)角度出發(fā)給出答案。
業(yè)務(wù)評估及優(yōu)化
推送服務(wù)經(jīng)常面臨高頻的消息讀寫場景,因此對存儲的性能要求也是極高的,我們首先要做的就是對Titan 進(jìn)行壓測。推送服務(wù)依賴 Redis 的 Hash 數(shù)據(jù)結(jié)構(gòu)完成對消息的管理,使用的命令有 hset ,hgetall ,hdel。壓測 Titan 在 1 臺 sas 盤 CPU 40核 內(nèi)存 96 G 機(jī)器和 3 臺 ?ssd 盤 CPU 40 核 內(nèi)存 96 G 機(jī)器上,部署采用了 1 個 Titan 12 個TiKV 實(shí)例方式。通過整理壓測數(shù)據(jù)和統(tǒng)計(jì)線上監(jiān)控給出對每個命令的期待的 QPS 如下表。
| ? | hset | hgetall | hdel |
| 壓測數(shù)據(jù) | 31 k/s | 13k/s | 47k |
| 實(shí)際需求 | 150k/s | 20k/s | 30k/s |
表一:業(yè)務(wù)評估表
通過上圖對比分析發(fā)現(xiàn),Titan 按照這個集群配置hset 和 hgetall 兩個命令明顯不滿足如線上要求。可以通過擴(kuò)容 Titan 集群的方式提高系統(tǒng)吞吐,但初于對成本的考慮,我們從業(yè)務(wù)角度嘗試優(yōu)化。
hset?
在Redis 中消息的保存使用 hset 命令根據(jù)客戶端 cid 進(jìn)行 hash 寫到固定的 Redis 存儲中,需要逐條寫入。在 Titan 中我們嘗試將 hset 命令從單條的執(zhí)行改為 batch 操作,經(jīng)過測試確立方案為每 100 條命令執(zhí)行一次事務(wù)提交, 雖然延遲從 10 ms 增到到 100 ms 以內(nèi)波動,但優(yōu)化后性能要求從 150 k/s 將為 20 k/s 且延遲在可以接受的范圍內(nèi)。
hgetall
在Redis 中客戶端登陸可以通過 hgetall 獲取當(dāng)前所有離線消息重新接收,在調(diào)研發(fā)現(xiàn)客戶端存在少量離線消息或者不存在離線消息。針對這種情況重新對 hgetall 進(jìn)行壓測,hgetall 在這種場景下可以提供 25 k/s QPS 滿足需求。
在經(jīng)過優(yōu)化之后,Titan 可以滿足目前推送線上的基本要求。按照上述配置決定將所有消息存儲逐步灰度到 Titan上。
平滑替換
消息存儲作為美圖推送核心單元,要保證7x 24小時可用,在遷移過程中,如何在Titan 異常情況下保證服務(wù)正常訪問,舊數(shù)據(jù)如何業(yè)務(wù)無損的同步到新集群是我們面臨的兩大難題。
在推送業(yè)務(wù)中消息生命周期都在一周之內(nèi),如果采用雙寫模式,將數(shù)據(jù)順序?qū)懭隦edis ,Titan兩個集群,讀取只在 Redis 集群上。一周后在將讀切到 Titan 上,在穩(wěn)定運(yùn)行一段時間后,下掉 Redis 集群完成存儲集群遷移。其中 Titan 集群在任何時間下發(fā)生異常都可以下掉,操作切回 Redis 集群。
問題和解決方法
在美圖推送服務(wù)接入Titan 的過程中,我們團(tuán)隊(duì)也遇到了不少的問題,比如事務(wù)沖突,短時間內(nèi) Titan 堆積大量命令,導(dǎo)致雪崩效應(yīng)等問題。我們在具體實(shí)踐中也總結(jié)了一些解決方法。
事務(wù)沖突
現(xiàn)象:在推送的高峰期期間事務(wù)沖突頻繁,Titan 節(jié)點(diǎn)內(nèi)存升高,TiKV 機(jī)器內(nèi)存耗盡節(jié)點(diǎn)OOM。
原因:Titan 中對相同的 key 寫入和刪除并發(fā)操作會導(dǎo)致事務(wù)沖突,消息的寫入采用 batch 方式寫入,一旦發(fā)生事務(wù)沖突,這批數(shù)據(jù)會集體產(chǎn)生回滾、重試操作,短時間內(nèi)Titan 會積壓大量命令導(dǎo)致內(nèi)存上升,TiKV 操作事務(wù)回滾導(dǎo)致內(nèi)存耗盡 節(jié)點(diǎn)OOM。
?解決方式:舍棄meta中數(shù)據(jù)數(shù)量記錄字段,減少單個key 的操作沖突。通過這種方式僅僅降低了單個 key 的沖突,在 hash 操作中針對單個 felid 的修改仍然存在沖突。但此種優(yōu)化已經(jīng)到達(dá)業(yè)務(wù)接受水平。
TiKV OOM
原因:線上采用內(nèi)存96G 機(jī)器部署 4 個 TiKV 模式,在高峰期隊(duì)列請求處理隊(duì)列積壓,導(dǎo)致機(jī)器內(nèi)存耗盡。
解決方式:減少TiKV 配置中 block-cache-size 的大小,降低內(nèi)存占用。
Raft Store CPU 使用率過高
現(xiàn)象:redis 命令執(zhí)行存在卡頓,TiKV 監(jiān)控中 Raft Store CPU 使用率超過 90%。
原因:在TiKV 中 raftstore 是單線程工作。
解決方案:集群擴(kuò)容,增加一個服務(wù)器,增加一個4個 TiKV 節(jié)點(diǎn),提高 Titan 服務(wù)的處理能力,從根本上解決了問題。
TiKV channel full
現(xiàn)象:Redis 命令執(zhí)行超時。
原因:在數(shù)據(jù)短時間持續(xù)大量寫入,導(dǎo)致Raft 熱點(diǎn),直接導(dǎo)致TiKV 的 Region leader 遷移。
解決方式:通過配置調(diào)整TiKV 的 scheduler-notify-capacity 大小,增加scheduler 一次獲取最大消息個數(shù),降低了問題發(fā)生的頻率。
總結(jié)
在經(jīng)歷半年的嘗試后,推送存儲整體替換為Titan,存儲也由16臺 Redis 機(jī)器切換為 4 臺的 ssd TiKV 專屬服務(wù)器和 2 臺混部 Titan 服務(wù)器上,成本節(jié)約60%,可維護(hù)性大大提高,現(xiàn)在推送服務(wù)已經(jīng)穩(wěn)定跑了半年,期間未發(fā)生故障。隨著 Titan 的數(shù)據(jù)結(jié)構(gòu)的完善,未來準(zhǔn)備推進(jìn)更多業(yè)務(wù)接入。?
?
感謝張同學(xué)(nioshield@gmail.com)及時實(shí)現(xiàn)的 hashes 數(shù)據(jù)結(jié)構(gòu)。?
感謝 PingCAP 公司在遷移過程中對于TiKV 的技術(shù)支持。
Redis 的壓測工具:https://github.com/fperf/redis
Titan 項(xiàng)目地址:https://github.com/distributedio/titan
TiKV 項(xiàng)目地址:https://github.com/tikv
總結(jié)
以上是生活随笔為你收集整理的美图每天亿级消息存储演进——从Redis到Titan,完美解决扩容问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java Bean 为什么必须要有一个无
- 下一篇: Java中的门面设计模式,非常有用!