源起
群里的一个提问
问题
这是一个比较典型的场景,当用户发展到一定程度后,单机的支撑能力是有上限的,这个时候必需用多台机器进行水平扩展了,那针对长连接这种有状态的情况下,就必需面临以下2个问题?
万变不离其宗,我们先看单机的场景下如何处理
单机的广播场景
如果要进行全局广播,那非常easy, 可以用swoole内置的方法:
foreach($server->connections as $fd)
{
$server->send($fd, "hello");
}
echo "当前服务器共有 ".count($server->connections). " 个连接\n";
(PS: 广播可以放到task进程,避免堵塞worker进程)
这个只是所有的广播,如果我想做组播怎么办?
单机组播
以聊天室为例,广播就相当于整个系统是一个大聊天室,但实际情况往往是需要多个聊天室,相互之间不影响,对某一个聊天室进行广播,称之为组播,那需要怎么做呢? 实现想来也非常简单,这个时候,我们可以引入redis,以房间Id做为key,创建一个list,进入该房间时,用户加入到这个list中,退出房间时,从list删除, 相关伪代码如下:
单机场景下,服务非常简单,就一个websocket server外加一个redis 时序图基本如下:
OK,现在我们回到最开始提到的问题,当一台机器变成N台机器之后,怎么办?
分配用户到哪台机器上?
这时,建议专门把登录服务提出来,这是一个http服务,用户登录成功之后,除了返回token之外,还返回一台聊天服务器的ip和端口,原因有:
这里面还有一个关键的一步: 把用户的uid 和 对应的服务器ip/port 关系绑定,这有助于我们知道用户在哪台机器上
Proxy
除了要把登录服务单独拎出来,还需要一个proxy,专于来处理各服务之间的通信和redis的交互
如我们进入房间的操作,退出房间,都通过这个proxy把数据存放在redis里
聊天室广播
聊天服务器的核心代码如下:
proxy的关键代码如下:
通过引入中间的proxy层,可以很方便的进行server之间的转发,不管同一个房间的用户分散到哪,通过redis都能找到对应的关系
大致的思路如上,当然做好一个分布式聊天室的细节还非常多,有何问题,随时交流, 希望能给大家带来帮助。
----------伟大的分割线-----------