iT邦幫忙

2026 iThome 鐵人賽

DAY 18
0
Claude AI

Claude Code陪跑:一人開發者的訂閱制SaaS架構與十大地雷實戰記系列 第 18

# Day 18|推播成本斷路器:怎麼防止一次全炸的帳單

  • 分享至 

  • xImage
  •  

一個思想實驗

想像這個場景:某次改版引入一個 bug,通知排程的去重邏輯失效,每 30 秒一輪的排程,每輪都把同一批通知重發一次。你睡了 8 小時,醒來時它跑了快一千輪。

如果每則推播都是真金白銀,這一晚燒掉多少?這不是罕見劇本——排程 + 迴圈 + 按量計費的外部呼叫,三個元素湊齊,就具備了「帳單炸彈」的全部條件。Day 13 的資料庫額度事故是同一個家族,只是那次燒的是資料庫流量,這次的假想敵是訊息費用。

斷路器:最後一道防線的設計

解法借用電路的概念:斷路器(circuit breaker)——平常不參與邏輯,只在流量異常時切斷電源。實作的骨架:

class PushBudgetGuard:
    """推播成本斷路器。
    邏輯層應該自己做好去重與節制——這裡是邏輯全部失效時的最後防線。"""

    def try_acquire(self, count: int = 1) -> bool:
        used = self._get_window_usage()          # 目前時間窗內已發送量
        if used + count > self.window_limit:
            self._trigger_alert(used)            # 通知我:斷路器跳了
            return False                          # 拒發,但不拋例外
        self._incr_window_usage(count)
        return True

def send_push(user, message):
    if not budget_guard.try_acquire():
        log.warning("push blocked by budget guard: %s", user)
        return                                    # 靜默擋下,服務繼續跑
    _actually_send(user, message)

幾個設計決策值得展開:

上限怎麼定? 取正常日用量的合理倍數(例如 3-5 倍)。太緊會誤傷正常尖峰(一個大事件觸發大量正當警報),太鬆失去保護意義。這個數字需要先有幾週的正常用量數據才定得準——所以斷路器不是第一天就能調好的,先上一個寬鬆版,再用數據收緊。

跳閘之後的行為是「擋下+告警+服務繼續」,不是「拋例外」。 斷路器的職責是止血,不是診斷。它跳了代表上游一定有 bug,但讓整個服務跟著當機只會雪上加霜——擋下超額發送、大聲通知我、其餘功能照常運作,這才是正確的失效姿態。

告警本身不能走推播。(想一下為什麼。)斷路器的告警走獨立管道,否則「推播壞了」的通知永遠送不到你手上。

這個模式的適用範圍

檢查你的系統裡所有「內部邏輯 × 外部計費」的乘積點:AI API 呼叫、簡訊、email、雲端資源建立……每一個都值得一句靈魂拷問:

「如果這段邏輯今晚跑成無限迴圈,明早的帳單是多少?有東西擋著嗎?」

答不出來的那幾個點,就是還沒裝斷路器的地方。一人維運沒有值班同事幫你半夜發現異常——斷路器就是你睡覺時的值班工程師,它不會修 bug,但它保證你醒來面對的是一個「被控制住的事故」,而不是一張災難性的帳單。


上一篇
# Day 17|第三方訊息平台整合:免費互動 vs 主動推播的成本差異
系列文
Claude Code陪跑:一人開發者的訂閱制SaaS架構與十大地雷實戰記18
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言