去中心化的分布式websocket通信架构演进
#去中心化的分布式websocket通信架构演进
概念:
- cluster:代表一个集群,集群中有多个节点,集群是去中心化的,字面上理解就是无中心节点,与任何节点连接websocket来通信都是等价的
- node: 代表一个节点,是集群中的一个组成元素,也是用户websocket长连接状态下的某个实例
- roomId: 房间ID,将用户分组,实现组内用户的消息广播
- clientId: 用户ID,用户身份的唯一标识
- channelId: node在分布式环境下的唯一标识
需求:
- 房间内消息广播,类似群聊
一. 首次设计
- 消息发送者使用Redis广播消息给所有node
- 每个node接受到消息后解析,过滤并将消息推送人与该节点绑定的clientId上
问题:
- 每个节点需要解析全量消息。在部署了10个node的cluster里,每个node都需要处理10个node的数据,极大的浪费网络传输与数据处理
优化方向:
- 每个节点只处理该节点需要的消息
二. 再次设计
- 每个node启动时生成本node信息(可能是主机名,可能是IP等),这里叫channelId
- node启动成功后向Redis监听该channelId相关的消息,这样就能直接过滤掉不需要的消息,就像对讲机指定频道一样
- 当用户建立websocket连接时,向redis添加roomId与channelId一对多的映射关系
- 用户A 在向Redis广播时,通过注册表找到该消息需要广播的channelId列表,来完成消息推送
问题:
roomId -> channelId 注册表生命周期维护问题
- 比如用户断开了,需要移除注册表中的通道信息
- 比如服务器宕机,需要移除注册表中的通道信息
- 比如用户A首次注册在ChannelA上,断线重连后注册在了ChannelB上,就需要执行移除和添加两个操作
- 如果首次用户B,C注册在了同一个channel上,用户B断线后重连在了channelA上,那就只需要在注册表中增加一个channelA,不需要删除
话外音:分布式服务,每个node可能启动在任何机器上,可能不是物理机只是一个docker,因此channelId设定为不会固定
优化方向:
- 在redis中维护一个roomId->clientId = channelId 的用户通道明细列表,如下:
- 在某个用户信息发生变更时,通过下面的用户通道明细列表,能够刷新roomId->channelId的映射表
话外音:不要想着发送消息时直接使用`redis keys(roomId+"*")` 在用户通道明细列表中直接获取该房间的所有通道信息,从而可以忽略房间和通道的映射表,这样效率极低
三. 再次设计
问题:
- Redis中的所有映射信息需要有生命周期,否房间使用后将会不断的产生僵尸数据,不能销毁
优化方向:
- 加入过期时间概念,在未使用后自动销毁
- 加入心跳概念,确保在使用期间不会被销毁
四. 再次设计
- 每一个node都启动一个定时任务,定时刷新该node所属数据的过期时间,这样该node宕机后,相关信息就能得到及时的清理,生命周期也得到了维护
问题:
- 如果某房间的用户凑巧建立websocket连接在了同一个node上,那Redis广播这一步是否有些多余 - (话外音:局域网概念)
- 既然局域网概念可以忽略Redis广播,那能否将局域网概念深挖一下,比如修改nginx路由策略,指定为IP hash,这样客户端侧的局域网能够大概率落在服务侧的同一台node上
还没有评论,来说两句吧...