用最少的机器支撑万亿级访问,微博6年Redis优化历程
https://mp.weixin.qq.com/s?__biz=MzAwMDU1MTE1OQ==&mid=2653547263&idx=1&sn=fe484b24660b7e1dc4beabca71fe1cb1&scene=21#wechat_redirect
微博是從 2010 年開始引入 ?Redis ,現(xiàn)在 Redis 已經(jīng)廣泛應(yīng)用于微博的多個業(yè)務(wù)場景,如關(guān)系、計(jì)數(shù)、通知提醒等,目前 Redis 集群存儲超過百億記錄,每天上萬億的讀取訪問。隨著業(yè)務(wù)的快速發(fā)展,我們在使用過程中碰到的問題及解決方法給大家做一個分享。主要包括以下方面: 實(shí)現(xiàn)機(jī)制高可用、業(yè)務(wù)極致定制以及服務(wù)化。
?
Redis 2.0 時代(2010 - 2011)
實(shí)現(xiàn)機(jī)制高可用優(yōu)化
?
微博最早使用的是 Redis 2.0 版本,在初期業(yè)務(wù)規(guī)模不大的時候, Redis 服務(wù)運(yùn)行比較穩(wěn)定。但是隨著業(yè)務(wù)數(shù)據(jù)量和訪問量的增加,一些問題逐漸暴露出來:
?
持久化問題
?
在我們大多數(shù)業(yè)務(wù)場景中 Redis 是當(dāng)做存儲來使用,會開啟持久化機(jī)制。線上采用單機(jī)多實(shí)例的部署結(jié)構(gòu),服務(wù)器的內(nèi)存使用率也會比較高。由于官方版本觸發(fā)?bgsave?和?bgrewriteaof?操作的時間點(diǎn)是不可控的,依賴于相關(guān)的配置項(xiàng)和業(yè)務(wù)的寫入模型,因此可能會出現(xiàn)單機(jī)部署的多個 Redis 實(shí)例同時觸發(fā)?bgsave?或?bgrewriteaof?操作,這兩個操作都是通過 fork 出一個子進(jìn)程來完成的,由于 copy-on-write 機(jī)制,可能會導(dǎo)致服務(wù)器內(nèi)存很快耗盡, Redis 服務(wù)崩潰。
?
此外在磁盤壓力較大時(生成 rdb、aof 重寫),對 aof 的寫入及 fsync 操作可能會出現(xiàn)阻塞,雖然從 2.4 版本開始 fsync 操作調(diào)整到 bio 線程來做,主線程 aof 的寫入阻塞仍會導(dǎo)致服務(wù)阻塞。
?
主從同步問題
?
為了提高服務(wù)可用性,避免單點(diǎn)問題,我們線上業(yè)務(wù) Redis 大多采用主從結(jié)構(gòu)部署。官方版本的主從同步機(jī)制,在網(wǎng)絡(luò)出現(xiàn)問題時(如瞬斷),會導(dǎo)致主從重新進(jìn)行一次全量復(fù)制。對單個端口來說,如果數(shù)據(jù)量小,那么這個影響不大,而如果數(shù)據(jù)量比較大的話,就會導(dǎo)致網(wǎng)絡(luò)流量暴增,同時 slave 在加載 rdb 時無法響應(yīng)任何請求。當(dāng)然官方 2.8 版本支持了 psync 增量復(fù)制的機(jī)制,一定程度上解決了主從連接斷開會引發(fā)全量復(fù)制的問題,但是這種機(jī)制受限于復(fù)制積壓緩沖區(qū)大小,同時在主庫故障需要執(zhí)行切主操作場景下,主從仍然需要進(jìn)行全量復(fù)制。
?
版本升級及管理問題

早期 Redis 版本運(yùn)行不夠穩(wěn)定,經(jīng)常需要修復(fù) bug、支持新的運(yùn)維需求及版本優(yōu)化,導(dǎo)致版本迭代很頻繁。官方版本在執(zhí)行升級操作時,需要服務(wù)重啟,我們大多數(shù)線上業(yè)務(wù)都開啟了持久化機(jī)制,重啟操作耗時較長,加上使用 Redis 業(yè)務(wù)線比較多,版本升級操作的復(fù)雜度很高。由于統(tǒng)一版本帶來的運(yùn)維工作量實(shí)在太高,線上 Redis 版本曾經(jīng)一度增加到十幾個,給版本管理也帶來很大的困難。
?
為了解決以上問題我們對 Redis 原生實(shí)現(xiàn)機(jī)制做了以下優(yōu)化:
?
1. 對于持久化機(jī)制,采用 rdb + aof 的持久化方式。
?
aof 文件按固定大小滾動,生成 rdb 文件時記錄當(dāng)前 aof 的 position,全量的數(shù)據(jù)包含在 rdb 和所記錄位置點(diǎn)之后的 aof 文件,廢棄 aof 重寫機(jī)制,生成 rdb 后刪除無效的 aof 文件;增加了定時持久化操作的配置項(xiàng) cronsave,將單機(jī)部署的多個 Redis 實(shí)例的持久化操作分散在不同的時間點(diǎn)進(jìn)行,并且錯開業(yè)務(wù)高峰;將對 aof 的寫入操作也放到 bio 線程來做,解決磁盤壓力較大時 Redis 阻塞的問題。
?
2. 對于主從同步機(jī)制,借鑒 MySQL 的復(fù)制機(jī)制并做了簡化。
?
使用 rdb + aof 的方式,支持基于 aofpositon 的增量復(fù)制。從庫只需與主庫進(jìn)行一次全量同步同步,后續(xù)主從連接斷開或切主操作,從庫都是與主庫進(jìn)行增量復(fù)制。
?
?
對于版本升和管理級的問題, Redis 的核心處理邏輯封裝到動態(tài)庫,內(nèi)存中的數(shù)據(jù)保存在全局變量里,通過外部程序來調(diào)用動態(tài)庫里的相應(yīng)函數(shù)來讀寫數(shù)據(jù)。版本升級時只需要替換成新的動態(tài)庫文件即可,無須重新載入數(shù)據(jù)。通過這樣的方式,版本升級只需執(zhí)行一條指令,即可在毫秒級別完成代碼的升級,同時對客戶端請求無任何影響。
?
除了以上幾點(diǎn),也做了很多其它的優(yōu)化,如主從延遲時間檢測,危險(xiǎn)命令認(rèn)證等。通過逐步的優(yōu)化,內(nèi)部的 Redis 版本也開始進(jìn)入穩(wěn)定期,應(yīng)用規(guī)模也在持續(xù)的增加。
?
業(yè)務(wù)極致定制化時代(2012 - 2013)
RedisCounter / LongSet
?
在某些特定的業(yè)務(wù)場景下,隨著業(yè)務(wù)規(guī)模的持續(xù)增加, Redis 的使用又暴露出來一些問題,尤其是服務(wù)成本問題(小編:是省服務(wù)器的意思?)。為此結(jié)合特定的業(yè)務(wù)場景我們對 Redis 做了一些定制的優(yōu)化。這里主要介紹一下在關(guān)系和計(jì)數(shù)兩個業(yè)務(wù)場景下做的定制優(yōu)化。
?
-
關(guān)系
?
微博關(guān)系業(yè)務(wù)包含添加、取消關(guān)注,判斷關(guān)注關(guān)系等相關(guān)的業(yè)務(wù)邏輯,引入 Redis 后使用的是 hash 數(shù)據(jù)結(jié)構(gòu),并且當(dāng)作存儲使用。但是隨著用戶規(guī)模的快速增長,關(guān)系服務(wù) Redis 容量達(dá)到十幾 TB,并且還在快速的增長,如何應(yīng)對成本壓力?
?
為了解決服務(wù)成本問題,我們把 Redis 的角色由 storage 調(diào)整為 cache。
?
這是因?yàn)殡S著用戶數(shù)量的增長,業(yè)務(wù)模型由初期的熱點(diǎn)數(shù)據(jù)不集中已經(jīng)轉(zhuǎn)變?yōu)橛忻黠@的冷熱之分。對于關(guān)注關(guān)系變更、判斷關(guān)注關(guān)系,hash 數(shù)據(jù)結(jié)構(gòu)是最佳的數(shù)據(jù)結(jié)構(gòu),但是存在以下問題:
?
cache miss 后回寫關(guān)注列表性能差,對于關(guān)注數(shù)較多的微博會員,回寫操作耗時可達(dá)到 10ms,這對于單線程的 Redis 來說是致命的;
Redis hash 結(jié)構(gòu)的內(nèi)存使用率不高,要保證 cahce 的命中率所需的 cache 容量仍然是很大的。
?
于是,我們定制了 longset 數(shù)據(jù)結(jié)構(gòu),它是一個“固定長度開放尋址的 hash 數(shù)組”,通過選擇合適的 hash 算法及數(shù)組填充率,可實(shí)現(xiàn)關(guān)注關(guān)系變更及判斷的性能與原生 Redis hash 相當(dāng),同時 cache miss 后通過 client 重建 longset 結(jié)構(gòu),實(shí)現(xiàn) O(1) 復(fù)雜度回寫。
?
通過定制 longset 數(shù)據(jù)結(jié)構(gòu),將關(guān)系 Redis 內(nèi)存占用降低了一個數(shù)量級(小編:這該節(jié)約了多少服務(wù)器……發(fā)獎金了嗎?),同時保證了服務(wù)性能。
?
計(jì)數(shù)
?
微博有很多計(jì)數(shù)場景,如用戶緯度的關(guān)注數(shù)、粉絲數(shù),微博緯度的轉(zhuǎn)發(fā)數(shù)、評論數(shù)等。計(jì)數(shù)作為微博中一項(xiàng)很重要的數(shù)據(jù),在微博業(yè)務(wù)中承擔(dān)了很重要的角色。為更好的滿足計(jì)數(shù)業(yè)務(wù)需求,我們基于 Redis 定制了內(nèi)部的計(jì)數(shù)服務(wù)。
?
原生 Redis 為了支持多數(shù)據(jù)類型,需要維護(hù)很多指針信息,存儲一份業(yè)務(wù)計(jì)數(shù)要占到約 80 個字節(jié),內(nèi)存利用率很低。為此我們定制了第一版計(jì)數(shù)器 Redis counter,通過預(yù)先分配內(nèi)存數(shù)組存儲計(jì)數(shù),并且采用 doublehash 解決沖突,減少了原生 Redis 大量的指針開銷。通過以上優(yōu)化將內(nèi)存成本降低到原來的 1/4 以下。(小編:又節(jié)約了 3 / 4 服務(wù)器……)
?
隨著微博的發(fā)展,微博緯度的計(jì)數(shù)不斷增加,在原來的轉(zhuǎn)發(fā)數(shù)、評論數(shù)基礎(chǔ)上,又增加了表態(tài)數(shù),2013 年還上線了閱讀數(shù)。 Redis counter 已不能很好的解決這類擴(kuò)展問題:
?
存儲單條微博相關(guān)的計(jì)數(shù),需要重復(fù)存儲微博 mid 信息,并且數(shù)據(jù)全部存儲在內(nèi)存,服務(wù)成本較高;
獲取單條微博全部的計(jì)數(shù),需要調(diào)用多次計(jì)數(shù)接口,對服務(wù)端壓力很大。
?
為此我們又設(shè)計(jì)了改進(jìn)版的計(jì)數(shù)器 CounterService,增加如下特性:
?
-
Schema 支持多列:支持動態(tài)加列,內(nèi)存使用精簡到 bit
-
冷熱數(shù)據(jù)分離:頻繁訪問的熱數(shù)據(jù)存儲在 memory,訪問較少的冷數(shù)據(jù)存儲在磁盤,降低服務(wù)成本
-
LRU 緩存冷數(shù)據(jù):增加 LRU 模塊,緩存訪問到的冷數(shù)據(jù),保證冷數(shù)據(jù)的訪問性能。
-
異步 IO 線程訪問冷數(shù)據(jù):避免冷數(shù)據(jù)的訪問影響服務(wù)的整體性能
?
通過以上的定制優(yōu)化,我們從根本上解決了計(jì)數(shù)業(yè)務(wù)的成本及性能問題。
?
除了以上關(guān)系、計(jì)數(shù)業(yè)務(wù)場景的定制優(yōu)化,為了滿足判斷類業(yè)務(wù)場景需求,定制了 BloomFilter 服務(wù);為了滿足 feed 聚合業(yè)務(wù)場景需求,定制了 VerctorService 服務(wù);為了降低服務(wù)成本,定制了 SSDCache 服務(wù)等。(小編:老板感動得流淚了)
?
服務(wù)化時代(2014 -)
Cache Service、SSD Cache
?
隨著微博業(yè)務(wù)的快速增長,Redis 集群規(guī)模也在持續(xù)增加,目前微博 Redis 集群內(nèi)存占用數(shù)十 TB,服務(wù)于數(shù)百個業(yè)務(wù)線,Redis 集群的管理依然面臨很多的問題。
?
數(shù)據(jù)遷移問題
?
隨著時間推移,越來越多的業(yè)務(wù)由于數(shù)據(jù)量的增加,單端口到內(nèi)存占用已經(jīng)達(dá)到上限,微博內(nèi)部建議單端口內(nèi)存不超過 20GB,因此需要重新拆分端口,這就涉及到數(shù)據(jù)遷移,目前遷移操作是通過內(nèi)部開發(fā)的一個遷移工具來完成的,遷移操作的成本相對較高。
?
數(shù)據(jù)路由問題
?
目前的使用方式,需要在業(yè)務(wù)代碼中實(shí)現(xiàn)數(shù)據(jù)路由規(guī)則,路由規(guī)則的變更需要重新上線代碼,業(yè)務(wù)變更復(fù)雜度較高。同時節(jié)點(diǎn)配置采用 DNS 的方式,存在實(shí)時性和負(fù)載不均的問題,雖然使用過程中有對應(yīng)的解決策略,但是需要一定的運(yùn)維干預(yù),運(yùn)維復(fù)雜度較高。
?
HA 系統(tǒng)不成熟
?
當(dāng)前的 HA 系統(tǒng)更多的是采用自動發(fā)現(xiàn)問題,手動確認(rèn)處理的策略,沒有實(shí)現(xiàn)真正意義的自動化,運(yùn)維成本依然很高。
?
為了解決以上問題,我們在 Redis 基礎(chǔ)上實(shí)現(xiàn)服務(wù)化框架 CacheService。
?
CacheService 最早是為了解決內(nèi)部使用 memcached 遇到的問題而開發(fā)的服務(wù)化框架,主要包含以下幾個模塊:
?
-
配置中心 ConfigServer
?
微博內(nèi)部的配置服務(wù)中心,主要是管理靜態(tài)配置和動態(tài)命名服務(wù)的一個遠(yuǎn)程服務(wù),并能夠在配置發(fā)生變更的時候?qū)崟r通知監(jiān)聽的 ConfigClient。
?
-
資源層
?
實(shí)際的數(shù)據(jù)存儲引擎,初期支持 memcached,后續(xù)又?jǐn)U展了 Redis、SSDCache 組件,其中 SSDCache 是為了降低服務(wù)成本,內(nèi)部開發(fā)的基于 SSD 的存儲組件,用于緩存介于 memory 和 DB 之間的 warm 數(shù)據(jù)。
?
-
代理層
?
代理業(yè)務(wù)端的請求,并基于設(shè)定的路由規(guī)則轉(zhuǎn)發(fā)到后端的 cache 資源,它本身是無狀態(tài)的。proxy 啟動后會去從 ConfigServer 加載后端 cache 資源的配置列表進(jìn)行初始化,并接收 ConfigServer 的配置變更的實(shí)時通知。
?
-
客戶端
?
提供給業(yè)務(wù)方使用的 SDK 包,通過它不需要在業(yè)務(wù)代碼中實(shí)現(xiàn)數(shù)據(jù)路由規(guī)則,業(yè)務(wù)方也無需關(guān)心后端 cache 的資源。只需要簡單配置所使用的服務(wù)池名 group 和業(yè)務(wù)標(biāo)識 namespace 即可使用 cache 資源,client 從 ConfigServer 獲取 proxy 的節(jié)點(diǎn)列表,選擇合適的 proxy 節(jié)點(diǎn)發(fā)送請求,支持多種負(fù)載均衡策略,同時會自動探測 proxy 節(jié)點(diǎn)變更。
?
-
集群管理系統(tǒng) ClusterManager
?
管理集群中各個組件的運(yùn)行狀態(tài)以保證業(yè)務(wù)的 SLA 指標(biāo),當(dāng)出現(xiàn)異常時會自動執(zhí)行運(yùn)維處理。同時配置變更、數(shù)據(jù)遷移等集群操作也都是由它來負(fù)責(zé)。
?

為支持 Redis 服務(wù)化,在服務(wù)化框架擴(kuò)展支持了 Redis proxy,同時為了實(shí)
現(xiàn)在線數(shù)據(jù)遷移,參照 Redis cluster 的設(shè)計(jì)思想,對內(nèi)部 Redis 存儲做了改造,支持 slot 數(shù)據(jù)分片,數(shù)據(jù)遷移操作由 ClusterManager 組件執(zhí)行,完成 slot 的重新規(guī)劃及數(shù)據(jù)遷移。此外還支持 Redis 的 failover 機(jī)制,在master 或 slave 節(jié)點(diǎn)故障時會自動執(zhí)行容錯處理。我們 Redis 服務(wù)化項(xiàng)目 tribe 是從 2015 年底開始上線,處于逐步完善過程中。
?
總結(jié)
?
從對 Redis 的優(yōu)化歷程可以看出,技術(shù)的進(jìn)步是由業(yè)務(wù)的需求推動的,我們需要擁抱需求。同時對于一個服務(wù)我們需要持續(xù)優(yōu)化并保證服務(wù)的運(yùn)維友好性才能保證服務(wù)的生命力。后續(xù)的一些計(jì)劃,完善服務(wù)化體系中冷熱數(shù)據(jù)分級存儲機(jī)制以降低服務(wù)成本;引入新的組件以更好的滿足業(yè)務(wù)需求、進(jìn)一步完善集群管理組件降低運(yùn)維復(fù)雜度
?
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/articles/8360779.html
總結(jié)
以上是生活随笔為你收集整理的用最少的机器支撑万亿级访问,微博6年Redis优化历程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Lambda架构与推荐在电商网站实践
- 下一篇: MySQL 四种事务隔离级别详解及对比-