跳至主要內容
ESC
engineering-architecture-breakdown — 第 4/6 篇

經典架構拆解 · 04 — Slack 即時訊息與已讀同步

CLOUD

Slack 表面上就是個聊天室,底層卻是一套同時要扛長連線、訊息扇出(fanout)、已讀同步、全文搜尋的分散式系統。對架構師來說,它是看「即時 + 最終一致」混合設計怎麼做的好教材。

這是 經典工程架構拆解 系列的第 4 篇,上一篇討論了 Stripe 的冪等性(idempotency)設計


為什麼值得拆解

Slack 把看起來很簡單的「聊天」做成了企業級協作平台。對工程師來說,它的架構正好點出三個關鍵問題:怎麼維持數百萬條長連線而不讓伺服器爆掉?一個 channel 有上萬人時,訊息要怎麼送?搜尋和即時訊息這兩種差很多的工作負載,要怎麼共存?

PCA 考試常考的 messaging 服務選型(Pub/Sub、Cloud Tasks、WebSocket)、event-driven 架構、一致性 vs 可用性的取捨,在 Slack 身上都找得到真實案例。


商業規模與壓力

根據 Slack 官方與 Salesforce 財報公開資訊:

  • 每日活躍使用者(DAU)超過 2,000 萬,付費組織超過 20 萬家(Salesforce FY2024 財報)1
  • 高峰期維持超過 數百萬條同時在線的 WebSocket 連線(根據 Slack Engineering Blog 2017 年的 “How Slack Built Shared Channels” 與後續演講)2
  • 單一 workspace 最多可達上萬使用者,單一 channel 訂閱者規模從數人到數萬人都有(Slack Help Center 官方文件)3

壓力點:訊息平均延遲得壓在 幾百毫秒內,不然使用者馬上就「感覺到卡」。企業客戶還要求 SLA 99.99%。


架構演進簡史

階段年份(約)關鍵變化
單體 PHP + MySQL2013–2015每個 workspace 一組 MySQL shard,訊息從 DB 撈再推
加入 Job Queue2015用自家 Job Queue(後來換成 Kafka)做非同步 fanout
Flannel Edge Cache2017邊緣節點快取每個使用者的 channel 清單,降低登入延遲 2
全面 Vitess 化2020MySQL 改跑 Vitess,解決單 shard 容量瓶頸(Slack Engineering Blog 2020)4
Search 分離為獨立服務2016–至今用 Solr / 自家 indexer 做全文搜尋,與即時訊息分流

核心技術決策

決策為何替代方案
WebSocket 長連線推訊息延遲低於 polling,省頻寬HTTP long-polling(早期用過)、Server-Sent Events
每 workspace 一組 shard資料隔離清楚、好做 compliance全局分散式資料庫(成本與一致性代價太高)
Edge cache(Flannel)登入一次要傳的 channel/user metadata 太大,邊緣快取後只同步 diff 2每次從核心服務撈(延遲高)
Job Queue 做 fanout大 channel 要把一則訊息複製給上萬人,同步做會卡住寫入DB trigger(無法橫向擴充)
讀取游標(read cursor)只存「最後已讀的 ts」不用為每則訊息存「誰讀過」,節省儲存per-message read receipt(像 WhatsApp,代價大太多)

已讀同步的關鍵: Slack 的「已讀」不是一條一條記,而是只存一個 last_read_ts(最後已讀時間戳)。客戶端切換 channel 時,WebSocket 上送一個事件,後端更新游標,再推到使用者其他裝置同步。這樣已讀資料量就從 O(訊息 × 使用者) 降到 O(channel × 使用者)。


如果用 GCP 重新蓋

Slack 元件GCP 對應
WebSocket gatewayGKE 跑自家 gateway(Cloud Run 近期支援 WebSocket,但長連線仍偏好 GKE 管控更細)
訊息儲存Cloud SpannerBigtable(超大量選 Bigtable;需跨 channel 交易選 Spanner)
已讀游標FirestoreMemorystore for Redis(低延遲讀寫,每使用者 per-channel 一筆)
訊息 fanoutPub/Sub 做 topic per workspace 或 per channel;超大 channel 用 fan-out worker 拆
搜尋索引Dataflow 做 CDC(change data capture)管線 → Vertex AI Search 或自建 Elasticsearch on GKE
Edge cacheCloud CDN + Memorystore 做 per-user metadata cache
檔案附件Cloud Storage + 簽章 URL
稽核與合規Cloud Logging + BigQuery 做分析、Cloud DLP 掃敏感資料

PCA 考點映射

  1. Pub/Sub vs Cloud Tasks 選型:Slack 的 fanout 是典型「一個事件、多個訂閱者」的場景,對應 Pub/Sub;至於「每則訊息要保證送達一次、retry 可控」就偏 Cloud Tasks。PCA 常在 case study 問你「分析型 vs 工作排程型」這兩種訊息場景該選哪個服務。
  2. 一致性 vs 可用性權衡:已讀游標可以最終一致(幾秒內同步就好),訊息順序必須強一致。考試會問「哪些資料可以放 Firestore eventual consistency、哪些要 Spanner strong consistency」。
  3. 資料駐留與合規:企業版 Slack 讓客戶指定資料駐留區域。GCP 對應是 regional Spanner / regional GKE,配合 VPC Service Controls 隔離。

常見誤解

  • 「Slack 一定是把每則訊息寫進每個收件者的 inbox。」 實際上核心儲存是「channel → messages」的寫一次,推播時才扇出,不是 fan-out on write 的 per-user inbox 模型(Slack 公開演講多次提及此設計)2
  • 「WebSocket 一定比 HTTP 快。」 只有在訊息頻繁、又是雙向的情境才划算。登入、上傳檔案這類一次性請求,Slack 還是走 HTTP。
  • 「長連線多 = 伺服器要開很多。」 單台 server 撐十萬條 idle WebSocket 其實不難,真正吃資源的是訊息廣播跟序列化。

來源與延伸閱讀


下一篇:經典架構拆解 · 05 — Airbnb 搜尋與金流,來看一個完全不一樣的負載類型(搜尋排名 + 多幣別帳本)怎麼設計。

想看完整五大案例?到 PCA 案例資料庫 對照 Google 官方 case study。

🎯 換你練習

想動手設計類似系統?到 架構師設計工作坊 用這套思考步驟走一遍。

Footnotes

  1. Salesforce FY2024 Annual Report — Slack 在 Salesforce 財報中揭露的付費客戶與營收規模。

  2. How Slack Built Shared Channels — Slack Engineering — 官方部落格解釋跨 workspace 的 channel 設計與 fanout。 2 3 4

  3. Slack Help Center — Workspace Limits — 官方文件列出的 workspace 與 channel 規模上限。

  4. Scaling Datastores at Slack with Vitess — Slack Engineering — Slack 從 MySQL 轉 Vitess 的工程記錄。

engineering-architecture-breakdown — 4/6 完成 查看系列全覽 →

留言討論

徽章解鎖!