分布式架构 WebSocket 解决方案,学会了你就是那个架构师

前导近期有个同事跟我说遇到一件很奇怪的事情,时不时收到售后反馈说 部分用户无法接收到聊天室(WebSocket 服务)消息,然而在测试服以各种方式测试都无法复现这种现象 。于是陷入沉思,因为这个问题必须解决,用户必须要退出聊天室再重新进去才能看到这些丢失的消息,已经严重影响到业务间客服与用户的正常沟通 。
这到底是什么原因呢?而且没法在测服复现 。
这个架构服务采用的是 php Swoole , 用户与客户端FD 的关系绑定是通过 Swoole Table (服务进程间内存共享) 实现, 同事反映说在各个环节确认了关系绑定都没问题情况下还出现 客户端FD 丢失,那么我想到 这可能是因为服务器被负载均衡 (SLB)了,无法测服复现是因为测试服是单机 。
第二天一早, 为了验证猜测,同事查看了在阿里云上的负载均衡服务配置,果然破案了!!!这个项目此前一直是单机服务,也不知道从何时开始 变成多节点服务了 。
我来描述下为什么分布式服务的 WebSocket 会存在这种现象,而分布式服务的 HTTP 却没有这样的问题呢?因为 WebSocket 有个用户与客户端标识(FD)关系需要绑定,而 HTTP 服务一般是不需要关注客户端标识(FD)的 。
WebSocket 服务端需要推送消息到用户所连接的客户端时,例如A、B两台服务器,用户1连接到聊天室(服务器A),客服1也连接到聊天室(服务器B), 这种情况下 显然用户1发消息给客服1 是对牛弹琴了,因为用户1发送消息后,服务器A会遍历该服务器内的所有用户与客户端标识(FD),然后取出所有客服1的FD 进行消息推送,而客服1连接的是服务器B,则对于用户1来说 客服1是不在线的,所以用户1推送消息是推了个寂寞啊!!! 再如 你的服务是支持用户多设备、多平台同时在线也是一样的道理,这种情况下也就意味着可能用户的客户端标识(FD)会同时分布在 服务器A、服务器B、服务器C …,那么用户在其中一台设备发送消息,在其他端登陆的该用户都应该要收到这条消息,单纯地根据用户所连接的服务去发送消息 那么其他端在线的该用户都无法收到此消息了,群发也是一样的道理 。
多节点问题【分布式架构 WebSocket 解决方案,学会了你就是那个架构师】在开始思考分布式会有什么问题时,先来回答一个问题: 服务端如何与客户端交流?
在 WebSocket 服务端,每当与客户端连接成功后,会生成一个 唯一的客户端标识符 FD,WebSocket 会维护一个与客户端所有连接的 Connections 。在业务层,你需要将每个连接进来的客户端标识(FD)与项目的用户ID绑定起来,比如用 redis 将用户和客户端标识(FD) 保存起来,当客户端断开连接时解绑(删除掉对应的客户端标识(FD)),因为服务是用的PHP Swoole,用 Swoole Table (服务进程间内存共享) 实现用户与客户端标识(FD)绑定关系 。这样你就可以知道某个用户在不在线,并且这个用户的客户端标识(FD)有哪些,然后遍历 Swoole Table 把用户的所有客户端标识(FD)取出来循环推送消息给客户端 。
那如何给所有人广播消息呢?
服务器只需要与它自身的所有客户端连接 Server.Connections 挨个发消息就是广播,所以它只是一个伪广播: 我要给群里所有人发消息,但我不能在群里发,只能挨个私发 。
单节点
当单节点时,流程如下:

分布式架构 WebSocket 解决方案,学会了你就是那个架构师

文章插图
 
这时所有用户都能收到消息通知 。
多节点
当多节点时,就会有部分用户无法正常收到通知 (就是我文中开头所描述的现象),从以下流程图中可以很清楚地看到问题所在:
分布式架构 WebSocket 解决方案,学会了你就是那个架构师

文章插图
 
负载到节点B 的所有用户都没有收到消息通知 。
如何解决说了这么多,怎么解决这个问题呢?
网上的很多教程,有些是通过 WebSocket 中间服务转发器、网关转发器 等实现方案,但这些实现方式有局限性,因为这些方案大部分是需要判断用户在哪台服务器上(需要知道IP),然后转发层将请求转发到用户所在服务器上 。这种方案用户单端登录还好,如果用户多端登录 请求被转发到多服务器上同时处理相关逻辑显然是有问题的,比如新增数据、修改数据…这些操作等,这种架构解决方案 用户多点平台登录时调整复杂度会变得较高 。
将 Swoole Table (服务进程间内存共享) 改造为 Redis 哈希 来实现用户与客户端标识(FD)绑定关系,主要目的是在单节点处理逻辑的时候经常需要判断对端用户是否在线,单服务内的共享内存并不能知道其他服务内该用户是否在线,所以这个方案不可取了 。改用 分布式缓存 就可以判断出对端用户是否在线了 。


推荐阅读