新浪微博「點贊功能」資料庫如何設計的?

明星的一條微博的點贊數可能有幾十萬,甚至百萬以上。那麼這個「點贊功能」(會記錄誰點了贊),新浪微博的資料庫是如何設計的呢?

網上說用到了 Redis,那麼難道是直接用 Redis 保存的(指的是持久化保存)?還是說邏輯處理在 Redis 中,之後會定時同步/持久化到 MySQL 等磁碟資料庫?

如果是關係型資料庫,那麼可能會記錄 |weibo_id|user_id| 欄位(weibo_id 表示一條微博的 id,user_id 表示點過贊的用戶 id),而一條微博的點贊數就可能有幾十萬,這個數據量對關係型資料庫來說也是大到超乎想像的,當然肯定得分庫分表了,但是即使這樣,感覺仍然不能較好的解決問題。如果用其他 NoSQL 資料庫,又如何處理呢?

概括一下:就是想弄明白「點贊」這種數據量龐大的功能,資料庫是如何設計、保存的呢?


從微博幾年前放出來的 slides [1][2] 上看,有將關注/被關注視為 『長列表』 來針對性的解決關注/被關注的擴展性問題,猜點贊/我的點贊列表該也差不多。

這類 『長列表』 場景有一定共性:

  • 數據規模顯著高於內容類數據:發微博來自用戶深思熟慮的行為,而點贊、關注是無意識行為,容易產生大規模數據;
  • 數據分布極度不均勻:大V 與小透明的數據量差異極大,前者輕鬆幾百萬贊/關注,後者零星可憐幾個贊/關注,這時按後者沒有性能問題的查詢在前者身上容易跪慘,而這類性能問題在早期用戶活動較少時不會暴露;
  • 有反向查詢的需求:比如我點贊了你的微博,你的微博下面需要列出點贊的人的列表,我也需要看到自己點過贊的微博列表;
  • 有基數查詢的需求:需要查看點贊者數量,也需要看我點贊過的數量;

有這幾個問題在,早期的單表點贊/被點贊 + 反向索引的做法會很容易遭遇存儲瓶頸(數據規模大)和性能瓶頸(計數類操作開銷大)乃至穩定性問題(趕上無分頁撈全部數據時壓力山大)。

不過題主講的 "一條微博的點贊數就可能有幾十萬,這個數據量對關係型資料庫來說也是大到超乎想像的" 數據規模並不大,一個 shard 上突發的熱點數據訪問,可以通過緩存 + 讀寫分離 hold 住大部分問題。

更具體的設計上,結合一點自己的經驗:

schema 設計上:

避免反向索引,允許分庫分表:

  • 單表多維度查詢的存在使數據表難以 shard;
  • 替代以業務層雙寫,或者統一的關係服務中實現雙寫邏輯;

業務層的數據訪問上:

  • 首先一定要緩存,乃至直接用持久性的 redis 做存儲,嚴格避免一個 query 撈回幾十萬 id 的行為直接落資料庫上:點贊操作發生時,務必要『更新』緩存,而非『失效』緩存;
  • 計數類操作一定要緩存:同樣,點贊操作發生時,務必先『增加』緩存中的計數,而非『失效』計數緩存;

服務架構上:

類似的『長列表』場景在社交網站中無處不在,往往也是最顯容易暴露瓶頸的場景,根據問題共性,開發類似的『關係服務』統一解決擴展性問題。

References

[1]: 微博關係服務與Redis的故事

[2]: https://www.slideshare.net/mysqlops/redis-9806617


微博咋做的我不知道... 不過這類問題早就有定論了, 一般帖子內聯點贊數據即可 (各種 Web 框架甚至 MongoDB 的文檔都寫著). 例如 PG 可以用 array 欄位

create table posts (
...
liked_user_ids integer[]
);

create table users (
...
liked_post_ids integer[]
);

高贊帖可以分層計算來減少資料庫的更新次數. 對這類帖子, Web 伺服器可以先緩存臨時的點贊用戶 id, 一段時間才一併 update 一次資料庫. 伺服器的負載均衡如果和帖子不相關, 那就存到外部服務中去, 而這個服務, 例如 Redis, 按 post id sharding 即可.


你是不是在cnode論壇問過,是我回答的你。。。我現在在微博負責這塊代碼

每個微博都有一個點贊數,跟微博內容是分開存儲的

資料庫用了基於redis二次開發的計數器,裡面有很多亮瞎的細節具體就不方便多說了,主要優化了存儲效率,內存存儲了最近幾年的點贊數據,在達到存儲極限的時候會將最舊的數據轉到磁碟,如果緩存miss會讀磁碟,而且只有特別老的數據才會讀磁碟,所以影響不大,性能和原生的redis一樣,但是同樣的內存會存儲更多的數據,具體有多少倒是沒有算過

最近也在考慮將計數器的代碼開源出來,如果你們對這方面有需要,可以關注一下

找到了前輩在設計計數器思路的文章

[WeiDesign]微博計數器的設計(下) · Cydu"s Blog


我最後一次了解到的方案,應該是在討論用 hbase 的寬表還是高表。在那之前,則是經典的 mysql + redis

話說,微博的兄弟們真的把 redis 玩出花來了呢


謝謝邀請。我也不知道微博點贊是怎麼實現的,只能講講我的設計。

題主的問題應該是兩個,一個是點贊數的記錄,一個是點贊人列表的存儲。

對於第一個問題,設計一個schema-&>(messageID,likedCount),記錄每條微博的點贊數。messageID是微博的編號,likedCount是該微博的點贊人數。但是這裡有兩個問題需要解決,第一是並發,第二是數據量。

每條微博都有可能有很多人同時點贊,為了保證點贊人數精確就需要保證likedCount++是原子操作,這個可以由應用程序來實現,也可以用redis的事務來實現(如果redis有事務機制或者自增功能的話),但是我覺得為了性能考慮,也可以不用實現原子操作,具體原因就不展開了。

每天都上億可能更多的微博內容產生,這樣就會有上億個新的(messageID,likedCount)生成,這樣的數據量是比較大的,單機資料庫比較難提供高效的服務,所以需要採取sharding的功能(有時候也叫分表分庫),可能根據messageID把這些schema分散到十個或者更多的shards上(據說,sina微博有600個節點,如何三個節點組成一個shard,就有200個shards),這樣每個shard處理的請求就只有原來的十分之一,從而就能提高服務的性能。

關於點贊人列表的設計,一般來說,可能想到的schema是(messageID,userID),但是這樣的設計有一個小問題,就是有些大發的微博可能會得到幾十萬的點贊,這樣就會產生幾十萬個條數據,這樣數據有點多,讀取起來可能也慢。所以可以用這樣一個schema(messageID,partID,userIDs),讓一個messageID對於多個userID,同時比對應太多的userID,所以加入一個新的partID,一個part存1000個userID,這樣幾十萬個點贊,只需要存幾百條數據。這樣做還有一個好處,用戶點擊查看點贊人時的,一般都不是完全顯示所有點贊人,而是一批一批顯示,這樣可以一次只讀一條數據,就可顯示一批點贊用戶信息。

這是我對這個功能的簡單設計,是不是能有效,不能肯定,還需要真實用例的考驗。


這個問題可以分成兩部分來分析:

  1. 顯示『此微博的點贊情況』,只需要顯示總贊數和最近幾個贊的人,數據量很小,更新頻繁,不要求較高的一致性。必須緩存,可以非同步反範式化保存,用Redis或別的什麼NoSQL,也可用RDBMS。
  2. 檢查『用戶是否贊了這條微博』,這個是防止重複點贊的,要求較高的一致性。需要保存所有的點贊記錄,可以用|weibo_id|user_id|的結構保存上億條,每次只查詢一條,要對weibo_id, user_id建聯合索引。也可以用NoSQL,就保存weibo_id+user_id格式的key。

資料庫可以這樣設計,至於怎樣設計緩存,就是另外一個問題了。


越來越不想登微博了,每次一些微博下的智障回復總是能置頂,因為同意的人點贊,不同意的人回復罵。微博官方也是蠢萌,也不想想一個微博粉絲幾百萬幾千萬的賬戶回復從來就是那麼小几萬(不算明星那些),他們真的以為是絕大部分人只喜歡看不喜歡錶達自己的態度嗎,你設置一個「不贊」或者「踩」等表達不認同態度的快捷標籤看看。


推薦閱讀:

如何用redis/memcache做Mysql緩存層?
各種內存NoSQL,什麼情況下才有必要使用,SQL性能真的很糟糕?
怎樣理解分析王垠文章《SQL,NoSQL 以及資料庫的實質》的觀點?
Redis集群方案應該怎麼做?
MySQL為主,NoSQL為輔正成為網站技術的標配嗎?

TAG:資料庫 | MySQL | Redis | NoSQL | 資料庫設計 |