Webhook dead-letter 抽象 behaviour。
Webhook 派发在 CMDCGateway.Webhook.dispatch/3 内做 3 次指数退避
重试后若仍失败,本来 v0.4.0 仅 log error 即丢弃。v0.4.1 引入
dead-letter 机制:派发最终失败 → 写入 dead-letter backend → 后台
CMDCGateway.Webhook.Dispatcher 定时 reload 待重试条目,按指数
退避策略再尝试派发。
Behaviour 4 callback
@callback init(opts :: keyword()) :: {:ok, state} | {:error, term()}
@callback save_failed(state, entry :: entry()) :: :ok | {:error, term()}
@callback list_pending(state) :: {:ok, [entry()]} | {:error, term()}
@callback delete(state, id :: String.t()) :: :ok | {:error, term()}
@callback mark_retried(state, id :: String.t(), updates :: map()) ::
:ok | {:error, term()}entry 结构
%{
id: String.t(), # 入库时生成的唯一 ID
callback_url: String.t(),
body: binary(), # JSON-encoded payload
secret: String.t() | nil,
retry_count: non_neg_integer(), # 当前已重试次数(不含原始 3 次)
max_retries: pos_integer(), # 永久放弃前的最大次数
next_retry_at_ms: integer(), # System.system_time(:millisecond) 时间戳
last_error: term() | nil, # 上次失败原因(atom 或 inspect 字符串)
first_failed_at_ms: integer(), # 第一次失败时间
last_attempted_at_ms: integer() # 上次重试时间
}内置实现
CMDCGateway.Webhook.DeadLetter.DETS— 默认;DETS 文件持久化, BEAM 重启后 reload。- PG backend 留
cmdc_memory_pg子库发布后接入(schema 兼容)。
Summary
Types
@type entry() :: %{ :id => id(), :callback_url => String.t(), :body => binary(), :secret => String.t() | nil, :retry_count => non_neg_integer(), :max_retries => pos_integer(), :next_retry_at_ms => integer(), :first_failed_at_ms => integer(), :last_attempted_at_ms => integer(), optional(:last_error) => term() }
@type id() :: String.t()
@type state() :: term()
Callbacks
Functions
构造一个标准 entry,方便 backend 实现 / Dispatcher 复用。
@spec next_retry( non_neg_integer(), keyword() ) :: {non_neg_integer(), integer()}
计算下次重试时间(指数退避)。
返回 {retry_count + 1, next_retry_at_ms}。
退避策略
- 第 1 次重试:base_ms * 1(默认 30s)
- 第 2 次:base_ms * 2(默认 60s)
- 第 3 次:base_ms * 4(默认 120s)
- 第 n 次:base_ms * 2^(n-1),最大 cap_ms(默认 3600s = 1 小时)