【從一台 Server 到分散式架構】第 28 篇:用同樣的思維看 Twitter——社群動態與時序設計
Twitter 的使用場景看起來很簡單:
- 發推文
- 看追蹤者的動態
- 按讚、轉推、留言
但「看動態牆」這件事,背後藏著一個設計難題。
假設你追蹤了 200 個人,你打開 App,系統要給你顯示這 200 個人最近發的推文,按時間排序,最新的在最上面。
這件事要怎麼做,有兩種截然不同的策略,各有優缺點。
先理解資料結構
Twitter 的基本資料模型:
- User:用戶(user_id、username)
- Tweet:推文(tweet_id、user_id、content、created_at)
- Follow:追蹤關係(follower_id、followee_id)
每個 Tweet 和 Follow 都是相對簡單的記錄,問題出在:
如何快速生成「某個用戶的動態牆(Timeline)」?
策略一:拉取(Fan-out on Read)
每次用戶打開動態牆時,即時計算:
SELECT t.*
FROM tweets t
JOIN follows f ON t.user_id = f.followee_id
WHERE f.follower_id = :current_user
ORDER BY t.created_at DESC
LIMIT 50;邏輯:「查出我追蹤的所有人,找他們最近的推文,按時間排好。」
優點:
- 推文直接存,不需要額外處理
- 動態牆永遠是最新的(沒有時間差)
缺點:
- 每次打開動態牆都要做一次很重的查詢(JOIN 大表)
- 如果你追蹤 5000 個人,每次都要掃 5000 個人的推文
- 如果 Twitter 有 2 億個同時在線用戶每人每幾分鐘刷一次,DB 直接爆
策略二:推送(Fan-out on Write)
每次有人發推文時,立刻把這條推文推送到所有追蹤者的「動態牆收件匣」:
某人發了一條推文
→ 查出所有追蹤這個人的用戶(粉絲列表)
→ 把這條推文的 ID 寫入每個粉絲的「Timeline 快取」(通常是 Redis 的 sorted set)
→ 用戶打開動態牆 → 直接讀 Redis,瞬間回傳
優點:
- 讀取超快:直接從 Redis 拿預先組好的結果
- 不管你追蹤多少人,讀取速度一樣快
缺點:
- 發文的時候,要把這條推文寫入所有粉絲的收件匣
- 如果這個人有 1000 萬個粉絲(大 V),發一條推文 = 寫 1000 萬條記錄 → 寫入延遲極高
- 儲存成本高:同一條推文被複製存了幾百萬份
實際解法:混合策略
Twitter 實際採用的是混合策略,根據用戶的粉絲數決定:
一般用戶(粉絲少)→ Fan-out on Write
發文後立刻推到所有粉絲的 Timeline 收件匣
大 V(粉絲超過閾值,例如 100 萬)→ Fan-out on Read
不預先推送;用戶打開動態牆時,才去拉大 V 的最新推文,merge 進去
按讚、留言:最終一致性就夠
按讚數、留言數這類計數器,和 YouTube 的觀看數一樣的問題:寫入頻率極高。
解法相同:Redis 計數 + 批次寫入 DB(最終一致性)。
按讚這個動作本身(「誰按了哪條推文的讚」)需要記錄(要能取消讚、要能顯示「你的朋友也按讚了」),存在 DB;但「這條推文有幾個讚」這個數字,從 Redis 讀。
搜尋推文:Elasticsearch
和課程平台的全文搜尋一樣,Twitter 的推文搜尋也是用搜尋引擎:
- 推文發出後,文字內容被索引進 Elasticsearch(或類似系統)
- 搜尋「#某個標籤」或關鍵字,全文搜尋回傳相關推文
- 加上時間排序、熱度排序
Twitter 的核心設計取捨總覽
| 問題 | 解法 | 取捨 |
|---|---|---|
| 動態牆要快 | Fan-out on Write + Redis 快取 | 寫入放大;大 V 發文代價極高 |
| 大 V 問題 | 混合策略:大 V 改用 Fan-out on Read | 讀取時要多一次 merge,稍微複雜 |
| 按讚數計數 | Redis 計數 + 批次寫 DB | 短暫不精確(最終一致性) |
| 搜尋推文 | Elasticsearch | 索引和主 DB 有同步延遲 |
| 時序排序 | Tweet ID 帶時間資訊(Snowflake ID) | 分散式環境下生成唯一且有序的 ID |
Snowflake ID:分散式有序 ID
值得特別提一下:Twitter 設計了一個叫 Snowflake 的分散式 ID 生成方案。
資料庫自增 ID 在分散式環境下會有問題:多台機器各自用自增,會出現重複的 ID。
Snowflake ID 是一個 64-bit 的整數,組成:
[時間戳(41 bit)] + [機器 ID(10 bit)] + [序列號(12 bit)]
- 時間戳:精確到毫秒——保證 ID 按時間排序
- 機器 ID:保證不同機器不會生成相同 ID
- 序列號:同一台機器同一毫秒內最多生成 4096 個不重複 ID
結果:唯一、有序、不需要中央資料庫協調——完美適合分散式系統。
小結
Twitter 的架構精髓在於動態牆的設計取捨:
- 純 Fan-out on Write:讀快,但大 V 寫入爆炸
- 純 Fan-out on Read:寫簡單,但讀取太重
- 混合策略:針對不同用戶用不同策略,平衡兩端代價
這個「根據數據特性選不同策略」的思維,也是系統設計的核心能力。
下一篇,我們看最後一個延伸案例:ChatGPT 類的 AI 聊天系統——串流、佇列、對話狀態管理。