feat(cluster): gateway 后台定时器加 leader 锁——多副本安全(B5)
此前两个 gateway 定时器(订阅推进/掉单补偿)每实例各扫一遍:多副本下重复查库, 且对微信查单调用量随副本线性放大(微信有频控,先被限流的是自己)。 store.TryRunExclusive:用 PG advisory try-lock 做集群级单实例执行(leader 选举)——每轮 tick 非阻塞抢锁,抢到才跑、跑完释放;抢不到说明别的实例是 leader、本轮跳过。自愈:锁随 持有连接释放,leader 挂了下一轮别的实例自然抢到接管,无需显式故障转移。订阅/补偿用不同 锁键(可由不同实例分别 lead)。非 PG(sqlite 测试)/无 DB → 退回本地直接跑(单实例安全)。 两个定时器的 tick 各包一层 TryRunExclusive。至此 gateway 可安全多副本(SSE 走共享 Redis 流无需粘性、状态全外置、JWT 无状态,剩数据层 HA 属 C 层 ops)。带 fallback 单测。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,9 @@ import (
|
||||
|
||||
const reconcileInterval = 1 * time.Minute
|
||||
|
||||
// reconcileLeaderKey 是掉单补偿的 leader 选举锁键(多副本下只让一个实例查单,见 store.TryRunExclusive)。
|
||||
const reconcileLeaderKey int64 = 20260723
|
||||
|
||||
// 主动查单节流:前端支付弹窗每 2.5s 轮一次单态,而 reconcileOrder 见 pending 就直连渠道
|
||||
// 查单——单笔订单在 30min TTL 内能打出约 720 次微信查单调用,微信侧有频控,多用户并发时
|
||||
// 先被限流的反而是我们自己。回调才是入账主路径,查单只是兜底,给它一个最小间隔即可:
|
||||
@@ -64,10 +67,12 @@ func (h *Handler) StartReconcile(ctx context.Context) {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-t.C:
|
||||
// 单轮兜底:某轮 DB/查单 panic 不该终止整个补偿定时器,下一轮继续。
|
||||
// 单轮兜底 + leader 选举:多副本下只有抢到锁的实例查单补偿,避免对微信查单量随副本翻倍。
|
||||
safeCall("payment-reconcile-tick", func() {
|
||||
h.reconcilePending(ctx)
|
||||
pruneQueryMarks()
|
||||
h.db.TryRunExclusive(ctx, reconcileLeaderKey, func() {
|
||||
h.reconcilePending(ctx)
|
||||
pruneQueryMarks()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user