fix(realtime): evict fanout 增加时效校验,防止延迟重放误踢新登录
deliverRealtimeSessionControl 分发前校验 Ctrl.Timestamp,超过 10s 的 session-control(evict)消息直接丢弃并记 skip_stale 指标,避免 consumer 重启重放 PEL / stream 积压时旧消息的 skipSid 与新会话不符导致新登录被误踢。无时间戳消息放行以兼容旧发布方。 验证:TestRealtime 3/3、regression_kick_notification 14/14、regression_mobile_kick 21/21。
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tinode/chat/server/logs"
|
||||
immetrics "github.com/tinode/chat/server/metrics"
|
||||
@@ -223,11 +224,33 @@ func deliverRealtimeTopicMessage(envelope *store.RealtimeFanoutEnvelope, msg *Se
|
||||
recordRealtimeFanoutDeliverResult(envelope.Kind, realtimeTargetTopic, delivered)
|
||||
}
|
||||
|
||||
// fanoutSessionControlStaleLimit caps how long after publication a
|
||||
// session-control (evict) fanout message remains actionable. Delayed replays
|
||||
// (consumer backlog, pending-entry redelivery after restart) carry the original
|
||||
// skipSid, so a replayed evict would otherwise terminate freshly established
|
||||
// sessions whose sid differs from the stale skipSid (新登录被旧消息误踢).
|
||||
const fanoutSessionControlStaleLimit = 10 * time.Second
|
||||
|
||||
// fanoutSessionControlStale reports whether the session-control fanout message
|
||||
// is too old to act on. Messages without a timestamp (unknown origin) are
|
||||
// delivered to stay compatible with legacy publishers.
|
||||
func fanoutSessionControlStale(msg *ServerComMessage) bool {
|
||||
if msg == nil || msg.Ctrl == nil || msg.Ctrl.Timestamp.IsZero() {
|
||||
return false
|
||||
}
|
||||
return time.Since(msg.Ctrl.Timestamp) > fanoutSessionControlStaleLimit
|
||||
}
|
||||
|
||||
func deliverRealtimeSessionControl(envelope *store.RealtimeFanoutEnvelope, msg *ServerComMessage) {
|
||||
if globals.sessionStore == nil {
|
||||
immetrics.IncRealtimeFanout("deliver", envelope.Kind, envelope.TargetType, "skip_no_session_store")
|
||||
return
|
||||
}
|
||||
if fanoutSessionControlStale(msg) {
|
||||
immetrics.IncRealtimeFanout("deliver", envelope.Kind, envelope.TargetType, "skip_stale")
|
||||
logs.Warn.Printf("redis realtime fanout: dropped stale session-control message target=%s older=%s", envelope.Target, fanoutSessionControlStaleLimit)
|
||||
return
|
||||
}
|
||||
switch envelope.TargetType {
|
||||
case realtimeTargetDevice:
|
||||
uidText, deviceID, ok := strings.Cut(envelope.Target, ":")
|
||||
|
||||
Reference in New Issue
Block a user