fix: 按 K-00 约定修复 Go SDK 断线重交与退避

This commit is contained in:
Nixevol
2026-09-30 16:24:13 +08:00
parent 3749b9bdf1
commit f6f8ccf269
20 changed files with 1544 additions and 324 deletions
+84 -73
View File
@@ -6,36 +6,76 @@ import (
"time"
)
// reconnectBackoff 按 DEVELOPMENT 第 9 节:1s 起、加倍、上限 30s、±30% 抖动;
// 稳定在线 60s 后恢复到 1s。
// 重连 / 限速重交共用标称间隔:第 n 次 min(1s×2^(n-1), 30s),n≥1。
func nominalDelay(n int) time.Duration {
if n < 1 {
return 0
}
if n > 6 {
return 30 * time.Second
}
d := time.Second
for i := 1; i < n; i++ {
d *= 2
if d >= 30*time.Second {
return 30 * time.Second
}
}
return d
}
var backoffJitter = withJitter
func withJitter(d time.Duration) time.Duration {
if d <= 0 {
return 0
}
f := 0.7 + rand.Float64()*0.6
return time.Duration(float64(d) * f)
}
// reconnectBackoff 只维护一个连续失败计数 n。
// 应用调用 Connect 后的第一次连接不等待;之后第 n 次等待 nominalDelay(n)×抖动。
type reconnectBackoff struct {
mu sync.Mutex
base time.Duration
onlineAt time.Time
online bool
stable bool
timer *time.Timer
mu sync.Mutex
n int
skipFirst bool
online bool
onlineAt time.Time
counted bool
}
func newReconnectBackoff() *reconnectBackoff {
return &reconnectBackoff{base: time.Second}
return &reconnectBackoff{skipFirst: true}
}
// Func 供 autopaho;忽略其 attempt,只用本对象的 n。
func (b *reconnectBackoff) Func(attempt int) time.Duration {
_ = attempt
return b.NextWait()
}
func (b *reconnectBackoff) NextWait() time.Duration {
return b.nextWait(true)
}
func (b *reconnectBackoff) nextWait(jitter bool) time.Duration {
b.mu.Lock()
defer b.mu.Unlock()
if attempt <= 0 {
b.counted = false
if b.skipFirst {
b.skipFirst = false
return 0
}
d := b.base
for i := 1; i < attempt; i++ {
d *= 2
if d > 30*time.Second {
d = 30 * time.Second
break
}
n := b.n
if n < 1 {
n = 1
}
return withJitter(d)
d := nominalDelay(n)
if jitter {
return backoffJitter(d)
}
return d
}
func (b *reconnectBackoff) MarkOnline() {
@@ -43,80 +83,51 @@ func (b *reconnectBackoff) MarkOnline() {
defer b.mu.Unlock()
b.online = true
b.onlineAt = time.Now()
b.stable = false
if b.timer != nil {
b.timer.Stop()
}
b.timer = time.AfterFunc(60*time.Second, func() {
b.mu.Lock()
defer b.mu.Unlock()
if b.online {
b.stable = true
b.base = time.Second
}
})
b.counted = false
}
func (b *reconnectBackoff) MarkOffline() {
b.mu.Lock()
defer b.mu.Unlock()
if b.timer != nil {
b.timer.Stop()
b.timer = nil
if b.counted {
return
}
wasOnline := b.online
b.counted = true
was := b.online
onlineAt := b.onlineAt
b.online = false
if !wasOnline {
// 连接尚未成功就失败:在 Func 内已按 attempt 加倍,这里把 base 提到下次周期的起点。
next := b.base * 2
if next > 30*time.Second {
next = 30 * time.Second
if !was {
b.n++
if b.n < 1 {
b.n = 1
}
if next < time.Second {
next = time.Second
}
b.base = next
return
}
if b.stable || time.Since(b.onlineAt) >= 60*time.Second {
b.base = time.Second
b.stable = false
if time.Since(onlineAt) >= 60*time.Second {
b.n = 1
return
}
next := b.base * 2
if next > 30*time.Second {
next = 30 * time.Second
b.n++
if b.n < 1 {
b.n = 1
}
b.base = next
b.stable = false
}
func (b *reconnectBackoff) Base() time.Duration {
func (b *reconnectBackoff) N() int {
b.mu.Lock()
defer b.mu.Unlock()
return b.base
return b.n
}
func withJitter(d time.Duration) time.Duration {
// ±30%
f := 0.7 + rand.Float64()*0.6
return time.Duration(float64(d) * f)
func (b *reconnectBackoff) setOnlineAtForTest(t time.Time) {
b.mu.Lock()
defer b.mu.Unlock()
b.online = true
b.onlineAt = t
}
// computeBackoffDelay 供单测:无 attempt 与 base 计算无抖动前的标称延迟。
func computeBackoffDelay(base time.Duration, attempt int) time.Duration {
if attempt <= 0 {
return 0
}
d := base
for i := 1; i < attempt; i++ {
d *= 2
if d > 30*time.Second {
return 30 * time.Second
}
}
if d > 30*time.Second {
return 30 * time.Second
}
return d
// computeBackoffDelay 保留给旧单测:忽略 base,按 n 计算标称延迟。
func computeBackoffDelay(base time.Duration, n int) time.Duration {
_ = base
return nominalDelay(n)
}