package bridge // bounded_uuid_set.go - 有界 ID 去重集合,O(1) 查询和淘汰. // // 使用场景:长轮询(LongPoll)传输模式下,同一条消息可能被拉取多次. // BoundedUUIDSet 在内存中维护一个有界窗口,检测重复 ID. // // 升华改进(ELEVATED): 早期方案 BoundedUUIDSet 用 Set + Array 组合, // JavaScript Array.shift() 是 O(n),在大量消息时性能退化. // 我们用环形数组(ring buffer)替代 Array,淘汰操作 O(1). // 替代方案:<纯 LRU cache> - 否决:LRU 需要双向链表,内存开销大且实现复杂; // 去重场景只需 FIFO 淘汰(先来先出),不需要 LRU 的访问频率感知. import "sync" // BoundedUUIDSet 是固定容量的 ID 去重集合. // // 内部结构: // - ring: 环形数组,记录 ID 的插入顺序(FIFO 淘汰) // - set: 哈希表,O(1) 查询 // - head: 下一次写入位置(当 size == cap 时,覆盖 head 处的旧 ID) // // 精妙之处(CLEVER): ring[head] 既是"下一个写入位置"也是"最老的元素位置"-- // 当容量满时,直接覆盖 head 就完成了"淘汰旧 + 写入新"两步操作,无需额外指针. // 如果用 head/tail 两个指针,逻辑更复杂但性能没有提升. type BoundedUUIDSet struct { mu sync.Mutex ring []string // 环形数组,FIFO 淘汰顺序 set map[string]struct{} // 快速查询 cap int // 容量上限 head int // 下一写入位置(同时是最老元素位置) size int // 当前元素数量 } // NewBoundedUUIDSet 创建指定容量的 ID 集合. // cap 应大于"最坏重试窗口内的最大重复消息数". // 推荐默认值:1000(约覆盖 5 分钟 × 200 msg/min 的重试窗口). func NewBoundedUUIDSet(cap int) *BoundedUUIDSet { if cap <= 0 { cap = 1000 } return &BoundedUUIDSet{ ring: make([]string, cap), set: make(map[string]struct{}, cap), cap: cap, } } // Add 将 id 加入集合.如果 id 已存在,返回 false(重复). // 如果容量已满,淘汰最老的 ID 后再插入,始终返回 true. func (s *BoundedUUIDSet) Add(id string) bool { s.mu.Lock() defer s.mu.Unlock() if _, exists := s.set[id]; exists { return false // 重复 } if s.size == s.cap { // 容量已满:淘汰最老的 ID(ring[head]) oldest := s.ring[s.head] if oldest != "" { delete(s.set, oldest) } // head 不变,直接覆盖 } else { s.size++ } s.ring[s.head] = id s.set[id] = struct{}{} s.head = (s.head + 1) % s.cap return true } // Contains 检查 id 是否已在集合中(不改变状态). func (s *BoundedUUIDSet) Contains(id string) bool { s.mu.Lock() defer s.mu.Unlock() _, exists := s.set[id] return exists } // Len 返回当前集合中的元素数量. func (s *BoundedUUIDSet) Len() int { s.mu.Lock() defer s.mu.Unlock() return s.size } // Cap 返回集合容量上限. func (s *BoundedUUIDSet) Cap() int { return s.cap }