【從一台 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 聊天系統——串流、佇列、對話狀態管理。

相關推薦

2026-04-07
【從一台 Server 到分散式架構】第 31 篇:從面試題反推架構——用本系列思維拆解系統設計考題
「設計一個 URL 短網址服務」「設計一個訊息系統」——系統設計面試問的不是背答案,而是思考方式。這篇整理了一套可以重複使用的面試框架,並用兩道例題示範如何從需求出發,一步步推導出架構、說清楚取捨。
2026-04-06
【從一台 Server 到分散式架構】第 30 篇:限流、排隊與降級實戰——開賣與直播場景
平常流量是 1000 QPS,但「開賣」的那一刻,瞬間湧入 50 萬個請求——系統要怎麼活下來?這篇用課程平台的限量課程開賣和直播開播場景,把第 13 篇學到的限流、降級、熔斷,落地到一個真實的高流量設計,走過每個防護層是怎麼工作的。
2026-04-05
【從一台 Server 到分散式架構】第 29 篇:用同樣的思維看 ChatGPT——AI 聊天系統架構
ChatGPT 看起來像一個聊天視窗,背後卻有幾個特殊的設計挑戰:回應是串流的、推理非常耗資源、每輪對話要記住上下文、系統要支援幾千萬用戶同時使用。這篇用熟悉的架構思維,拆解 AI 聊天系統的關鍵設計。
2026-04-03
【從一台 Server 到分散式架構】第 27 篇:用同樣的思維看 Uber——即時位置與派車系統
Uber 的核心問題看起來簡單:乘客在哪、司機在哪、把最近的司機派過去。但背後有幾個棘手的設計挑戰:海量的司機位置每秒更新、要在幾秒內完成司機配對、派單要公平且不重複。這篇用熟悉的架構思維,拆解 Uber 的即時調度系統。