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:
@@ -0,0 +1,24 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// 非 PG(sqlite 测试库无 pg_try_advisory_lock):选主不可用 → 退回本地直接跑(单实例/开发态不能因此不跑)。
|
||||
func TestTryRunExclusive_FallbackRunsOnNonPG(t *testing.T) {
|
||||
p := newTestStore(t)
|
||||
ran := false
|
||||
if got := p.TryRunExclusive(context.Background(), 987654, func() { ran = true }); !got || !ran {
|
||||
t.Fatalf("sqlite 无 advisory 锁应退回本地跑:got=%v ran=%v", got, ran)
|
||||
}
|
||||
}
|
||||
|
||||
// 无 DB(降级模式):一样本地直接跑。
|
||||
func TestTryRunExclusive_NilDB(t *testing.T) {
|
||||
p := &Postgres{}
|
||||
ran := false
|
||||
if got := p.TryRunExclusive(context.Background(), 987654, func() { ran = true }); !got || !ran {
|
||||
t.Fatalf("无 DB 应本地跑:got=%v ran=%v", got, ran)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user