Golang观察者模式处理Websocket多房间消息分发

1次阅读

观察者模式优于hub广播,因其按房间隔离订阅、支持动态扩缩容且查询为o(1);room需含observers map[*client]bool、sync.rwmutex及带错误处理的broadcast方法。

Golang观察者模式处理Websocket多房间消息分发

为什么不用 Hub 广播而要上观察者模式

Hub 模式适合全站广播,但一进多房间(比如 /room/a/room/b)就硬编码或靠字符串前缀路由,很快会失控:消息误投、房间状态难隔离、扩缩容时 map 锁竞争加剧。观察者模式把“谁该收哪条消息”从 Hub 的遍历逻辑里解耦出来,让每个房间自己维护订阅者,天然支持动态创建/销毁。

  • 场景明确:用户连接时带 room_id 参数,只订阅对应房间;断开时自动退订,不污染其他房间
  • 性能差异:Hub 全量遍历 clients 是 O(n),观察者按频道查 observers[room_id] 是 O(1)
  • 坑点提示:map[String]*Room 必须用 sync.RWMutex 保护,否则并发注册房间时 panic:”fatal Error: concurrent map writes

Room 结构体怎么设计才不漏状态

Room 不是单纯存 client 列表,它得管三件事:谁在、谁要收、谁掉线了。漏掉任意一个,就会出现“人还在界面上但收不到消息”或“已离线用户还占着内存”。

  • 必须包含 observers map[*Client]bool(用指针作 key,避免 copy 副本)
  • 必须带 mu sync.RWMutex —— 所有对 observers 的读写都走 mu.Lock()/mu.RLock()
  • 必须提供 Broadcast(message []byte) 方法,内部先 mu.RLock(),再遍历发送;失败时触发 Unregister 而非静默丢弃
  • 示例片段:
    func (r *Room) Broadcast(msg []byte) {     r.mu.RLock()     defer r.mu.RUnlock()     for client := range r.observers {         select {         case client.send <- msg:         default:             // 写失败 → 客户端可能已断,清理             r.Unregister(client)         }     } }

Client 怎么安全注册到多个 Room

一个用户可能同时在「技术讨论」和「摸鱼闲聊」两个房间,Client 实例不能只挂在一个 Room.observers 里,得支持多房间注册,且注销时不能误删其他房间的引用。

  • 每个 Client 维护 rooms map[string]*Room,记录自己所属的所有房间
  • Register(room *Room, roomID string) 方法里:先 room.Register(c),再存入 c.rooms[roomID] = room
  • Unregister(roomID string) 方法里:先从 room.observers 删除自己,再从 c.rooms 删除该 roomID 键 —— 顺序反了会导致残留
  • 关键检查:http 升级时解析 URL 参数用 url.ParseQuery(r.URL.RawQuery),别直接用 r.URL.Query().Get("room"),后者对重复参数或编码字符处理不可靠

消息路由时最容易卡死的 channel 场景

观察者模式依赖 channel 传递事件,但 client.send 是带缓冲的 chan []byte,缓冲区设太小或客户端网络抖动,就会堵住整个 Room.Broadcast 流程,连带拖慢其他房间。

立即学习go语言免费学习笔记(深入)”;

  • 缓冲大小建议设为 64128,别用 0(无缓冲)或过大值(OOM 风险)
  • 发送时必须用 select + default,不能直写 client.send ,否则 <a style="color:#f60; text-decoration:underline;" title="go" href="https://www.php.cn/zt/15863.html" target="_blank">go</a>routine 永久阻塞
  • 更稳做法:在 writePump 里加超时控制,比如 case 后主动 close <code>client.send
  • 典型错误现象:goroutine stack size exceeded 日志频出,本质是 writePump 协程卡死,新协程不断 fork 出来抢资源

观察者模式本身不难,难的是所有 Register/Unregister 调用点必须成对、所有 mu 锁范围必须精确覆盖 map 操作、所有 channel 发送必须带 fallback 机制——少一个,上线三天后准出问题。

text=ZqhQzanResources