跨產品餘額同步:用 WebSocket 即時顯示另一個產品的餘額
產品 A 把使用者餘額轉入產品 B、A 的客戶端又要顯示 B 的餘額時,經由 A 的後端輪詢會讓 A 的處理能力受制於 B 的延遲。附流程圖說明 B 透過 WebSocket 推送每次變動如何解決。
兩個產品之間常見的做法:使用者把一部分餘額從產品 A 轉入產品 B,再到 B 裡使用。使用者在 B 裡花掉或贏得金額時,A 的客戶端仍然要即時顯示 B 裡還剩多少。第一版通常用 HTTP 去問:A 的客戶端輪詢 A 的後端,A 的後端再去問 B。每一次輪詢都變成橫跨兩個產品的請求鏈,流量一上來,限制你能服務多少使用者的就不是任何一個產品,而是這條鏈。
請求鏈是怎麼疊起來的
轉入餘額
在 A 的客戶端顯示 B 的餘額
每個 HTTP 請求在等待時都會佔住資源:一個 worker 執行緒、用戶端連線池裡的一條連線,通常還有一條資料庫連線。A 的請求要等 B 回應後才能回應,所以佔住這些資源的時間是自己的處理時間,加上 B 花掉的全部時間。Little 定律可以算出這個數字:處理中的請求數 = 每秒請求數 × 每個請求花的秒數。而且只要有 A 的使用者開著頁面,B 每隔幾秒就會收到一個請求,不管餘額有沒有變。
要付出多少 worker
以下是推算範例,不是壓測結果(撰寫於 2026-11-09):A 有 5,000 名使用者的畫面上顯示著 B 的餘額,A 的客戶端每 5 秒輪詢一次,所以每秒有 1,000 個請求經過 A 送到 B。
| 經由 A 的後端輪詢 | B 透過 WebSocket 推送每次變動 | |
|---|---|---|
| 每秒到達 A 和 B 的餘額請求 | 各 1,000 個,大多數沒有變化 | 沒有;客戶端連線時讀一次 |
| 每個請求佔住 A 的 worker 的時間 | 自身 10 ms + 呼叫 B 的 60 ms = 70 ms | 完全不經過 A |
| A 忙著轉送 B 餘額的 worker 數 | 1,000 × 0.07 秒 = 70 | 0 |
| B 變慢到 600 ms 時 | 1,000 × 0.61 秒 = 610,是 200 個 worker 連線池的三倍 | 仍然是 0:只是下一次更新晚一點到 |
痛點在最後一列。假設連線池有 200 個 worker,B 一時變慢就足以把 A 的用光:A 自己的請求,包括登入和付款,都在佇列裡排隊、逾時,用戶端再重試,又加進更多請求。A 的併發量正好在流量高峰時下滑,原因卻是一個 A 根本不經營的產品。同時,B 也把自己的處理能力花在回應大多是「沒變」的輪詢上。
改之後:B 每次變動只發佈一次
B 每寫入一次變動(包括轉入)就發佈一次。A 的客戶端保持一條 WebSocket 連線,一趟網路往返就收到新餘額。B 的餘額不再經過 A 的後端,所以變慢或忙碌的 B 佔不住 A 的 worker,B 也不用再回應成千上萬的輪詢。
怎麼設定
- B 的餘額由 B 負責。 B 的帳本是 B 裡剩多少的唯一事實來源。每次提交後,B 用任何 Pusher 伺服器端函式庫的 HTTP API 發佈
balance.updated,帶上新餘額和版本號。 - 每個使用者一個私有頻道。 例如
private-b-balance-42。如果 A 和 B 用同一個應用,A 的授權端點確認登入的使用者是 42 之後簽署訂閱。如果 B 有自己的應用,A 的客戶端就用 B 的應用金鑰連線,由 B 的授權端點簽署。如果中繼服務只該看到密文,就用private-encrypted-b-balance-42;其他私有頻道在傳輸中由 TLS 保護,儲存時加密。 - 善用版本號。 版本比畫面上低的事件直接忽略。A 的客戶端連線或重新連線時,先用 HTTP 讀一次 B 的餘額,再套用之後的事件。
- A 的後端也能收。 如果 A 在伺服器上需要 B 的餘額,例如讓使用者把錢轉回來之前,可以用 Pusher 用戶端函式庫靠同樣的事件維護一份副本,不必每次都呼叫 B。
不變的部分
從 A 轉到 B 仍然是帶冪等鍵的 HTTP 請求,重試也絕不會把錢轉兩次;B 每次寫入時也仍會自己檢查餘額。WebSocket 傳送的是「B 的餘額變了」這個消息,不是變動本身。如果另一端的產品是外部 API 供應商,轉帳本身也可能塞爆佇列:請看與 API 供應商的錢包轉帳。
常見問題
為什麼不乾脆降低輪詢頻率?
間隔拉長,請求變少,但餘額也變舊:在 B 贏得的金額要晚一整個間隔才出現在 A 的客戶端,而且每次輪詢仍然要等 B。推送一次變動只要一趟網路往返,沒有變動時什麼都不送。
A 的客戶端斷線時漏掉事件怎麼辦?
pusher-js 和其他用戶端函式庫會自動重新連線。重新連線後先用 HTTP 讀一次 B 的餘額,只套用版本更高的事件。版本號也讓重複或遲到的事件無害。
A 需要改 B 的程式嗎?
B 只要加一件事:寫入餘額後發佈 balance.updated,如果 B 有自己的應用,再加上簽署訂閱。它的轉帳 API 維持不變。
這些事件在 Hibira 上怎麼計算?
每次發佈算一則訊息。付費方案不計算送給訂閱者的份數,所以同一個餘額同時顯示在 A 和 B 的客戶端,費用和只顯示在一個客戶端相同。請看價格。
產品名稱為其所有者的商標。