風起
2023年微博剛剛上線的時候,微博關係服務使用的是最傳統的 memcache+mysql 的方案。mysql 按 uid hash 進行了分庫分表,表結構非常簡單:
業務方存在兩種查詢:
兩種查詢的業務需求與分庫分表的架構設計存在矛盾,最終導致了冗餘儲存:以 fromuid 為hash key存乙份,以 touid 為hash key再存乙份。memcache key 為 fromuid.suffix ,使用不同的 suffix 來區分是關注列表還是粉絲列表,cache value 則為 php serialize 後的 array。後來為了優化效能,將 value 換成了自己拼裝的 byte 陣列。
雲湧
2023年微博進行平台化改造過程中,業務提出了新的需求:在核心介面中增加了「判斷兩個使用者的關係」的步驟,並增加了「雙向關注」的概念。因此兩個使用者的關係存在四種狀態:關注,粉絲,雙向關注和無任何關係。為了高效的實現這個需求,平台引入了 redis 來儲存關係。平台使用 redis 的 hash 來儲存關係:key 依然是 uid.suffix,關注列表,粉絲列表及雙向關注列表各自有乙個不同的 suffix,value 是乙個hash,field 是 touid,value 是 addtime。order by addtime 的功能則由 service 內部 sort 實現。部分大v的粉絲列表可能很長,與產品人員的溝通協商後,將儲存限定為「最新的5000個粉絲列表」。
微博關係儲存redis結構
需求實現:
後來又增加了幾個更複雜的需求:「我與他的共同關注列表」、「我關注的人裡誰關注了他」等等,就不展開來講了。
平台在剛引入 redis 的一段時間裡踩了不少坑,舉幾個例子:
1、運維工具和流程從零開始做,運維成熟的速度趕不上業務增長的速度:在還沒來得及安排效能調優的工作,fd 已經達到預設配置的上限了,最後我們只能趁凌晨業務低峰期重啟 redis 集群,以便設定新的 ulimit 引數;
2、平台最開始使用的 redis 版本是 2.0,因為 redis **足夠簡單,從引入到微博起,我們就開始對其進行了定製化開發,從主從複製,到寫磁碟限速,再到記憶體管理,都進行了定製。導致的結果是,有一段時間,微博的線上存在超過5種不同的 redis 修改版,對於運維,bugfix,公升級都帶來了巨大的麻煩。後來由田風軍 @果爸果爸 為內部 redis 版本提供了不停機公升級功能後,才慢慢好轉。
3、平台有乙個業務曾經使用了非預設 db ,後來費了好大力氣去做遷移
4、平台還有乙個業務需要定期對資料進行 flush db ,以騰出空間儲存最新資料。為了避免在 flush db 階段影響線上業務,我們從 client 到 server 都做了大量的修改。
5、平台每年長假前都會做一些線上業務排查,和故障模擬(2023年甚至做了乙個名叫 touchstone 的容災壓測系統)。2023年十一假前,我們用 iptables 將 redis 埠的所有包都 drop 掉,結果 client 端等了 120 秒才返回。於是我們在放假前熬夜加班給 client 新增超時檢測功能,但真正上線還是等到了假期回來後。
破繭
對於微博關係服務,最大的挑戰還是容量和訪問量的快速增長,這給我們的 redis 方案帶來了不少的麻煩:
第乙個碰到的麻煩是 redis 的 hgetall 在 hash size 較大的場景下慢請求比例較高。我們調整了 hash-max-zip-size,節約了1/3的記憶體,但對業務整體效能的提公升有限。最後,我們不得不在 redis 前面又擋了一層 memcache,用來抗 hgetall 讀的問題。
第二個麻煩是新上的需求:「我關注的人裡誰關注了他」,由於使用者的粉絲列表可能不全,在這種情況下就不能用關注列表與粉絲列表求交集的方式來計算結果,只能降級到需求的字面描述步驟:取我的關注人列表,然後逐個判斷這些人裡誰關注了他。client 端分批並行發起請求,還好 redis 的單個關係判斷非常快。
第三個麻煩,也是最大的麻煩,就是容量增長的問題了。最初的設計方案,按 uid hash 成 16 個埠,每台 64g 記憶體的機器上部署 2 個埠,每個業務 idc 機房部署一套。後來,每台機器上就只部署乙個埠了。再後來,128g 記憶體的機器還沒有進入公司採購目錄,64g 記憶體就即將 oom 了,所以我們不得不做了一次埠擴容:16埠拆64埠,依然是每台 64g 記憶體機器上部署 2 個埠。再後來,又只部署乙個埠。再後來,公升級到 128g 記憶體機器。再後來,128g 機器上出現 oom 了!現在怎麼辦?
化蝶
為了從根本上解決容量的問題,我們開始尋找一種本質的解決方案。最初選擇引入 redis 作為乙個 storage,是因為使用者關係判斷功能請求的資料熱點不是很集中,長尾效果明顯,cache miss 可能會影響核心介面效能,而保證乙個可接受的 cache 命中率,耗費的記憶體與 storage 差別不大。但微博經過了 3 年的演化,最初作為選擇依據的那些假設前提,資料指標都已經發生了變化:隨著使用者基數的增大,冷使用者的絕對數量也在增大;redis 作為儲存,為了資料可靠性必須開啟 rdb 和 aof,而這會導致業務只能使用一半的機器記憶體;redis hash 儲存效率太低,特別是與內部極度優化過的 rediscounter 對比。種種因素加在一起,最終確定下來的方向就是:將 redis 在這裡的 storage 角色降低為 cache 角色。
前面提到的微博關係服務當前的業務場景,可以歸納為兩類:一類是取列表,一類是判斷元素在集合中是否存在,而且是批量的。即使是 redis 作為 storage 的時代,取列表都要依賴前面的 memcache 幫忙抗,那麼作為 cache 方案,取列表就全部由 memcache 代勞了。批量判斷元素在集合中是否存在,redis hash 依然是最佳的資料結構,但存在兩個問題:cache miss 的時候,從 db 中獲取資料後,set cache 效能太差:對於那些關注了 3000 人的微博會員們,set cache 偶爾耗時可達到 10ms 左右,這對於單執行緒的 redis 來說是致命的,意味著這 10ms 內,這個埠無法提供任何其它的服務。另乙個問題是 redis hash 的記憶體使用效率太低,對於目標的 cache 命中率來說,需要的 cache 容量還是太大。於是,我們又祭出 「redis定製化」的法寶:將 redis hash 替換成乙個「固定長度開放hash定址陣列」,在 redis 看來就是乙個 byte 陣列,set cache 只需要一次 redis set。通過精心選擇的 hash 演算法及陣列填充率,能做到批量判斷元素是否存在的效能與原生的 redis hash 相當。
通過微博關係服務 redis storage 的 cache 化改造,我們將這裡的 redis 記憶體占用降低了乙個數量級。它可能會失去「最大的單個業務redis集群」的頭銜,但我們比以前更有成就感,更快樂了。
WebRequest使用 呼叫新浪天氣
待請求的位址 string url 建立 webrequest 物件,webrequest 是抽象類,定義了請求的規定,可以用於各種請求,例如 http,ftp 等等。httpwebrequest 是 webrequest 的派生類,專門用於 http 請求的方式通過 method 屬性設定 預設為...
Redis計數在新浪微博的應用
作為微博中一項重要的資料,計數類業務在微博業務中佔的比重和重要性逐步提高。計數結果的準確度直接影響使用者體驗,並且很容易引起使用者的投訴。在計數業務上,在不斷的優化和改進中,我們主要經歷了以下三個階段 從2010年開始,使用redis 2.0版本。在最初業務資料比較少的時候,表現相當不錯。但隨著資料...
新浪微博API的使用
在開發平台可以看見很多微博api 點開微博介面看見下面詳細介面 以獲取最新公共微博為例 獲取當前登入使用者及其所關注使用者的最新微博。parameters since id若指定此引數,則返回id比since id大的微博 即比since id時間晚的微博 預設為0 max id若指定此引數,則返回...