文档:http://doc2.workerman.net/326146
目标:
项目需要研发im聊天功能,若用户量逐渐增大,单服务器的部署方式恐怕无法满足,因此,考虑使用多服务器分布式部署共同支撑大量人群同时在线。
首先我们需要先了解运行原理,官方文档的解释是:
1、Register、Gateway、BusinessWorker进程启动
2、Gateway、BusinessWorker进程启动后向Register服务进程发起长连接注册自己
3、Register服务收到Gateway的注册后,把所有Gateway的通讯地址保存在内存中
4、Register服务收到BusinessWorker的注册后,把内存中所有的Gateway的通讯地址发给BusinessWorker
5、BusinessWorker进程得到所有的Gateway内部通讯地址后尝试连接Gateway
6、如果运行过程中有新的Gateway服务注册到Register(一般是分布式部署加机器),则将新的Gateway内部通讯地址列表将广播给所有BusinessWorker,BusinessWorker收到后建立连接
7、如果有Gateway下线,则Register服务会收到通知,会将对应的内部通讯地址删除,然后广播新的内部通讯地址列表给所有BusinessWorker,BusinessWorker不再连接下线的Gateway
8、至此Gateway与BusinessWorker通过Register已经建立起长连接
9、客户端的事件及数据全部由Gateway转发给BusinessWorker处理,BusinessWorker默认调用Events.php中的onConnect onMessage onClose处理业务逻辑。
10、BusinessWorker的业务逻辑入口全部在Events.php中,包括onWorkerStart进程启动事件(进程事件)、onConnect连接事件(客户端事件)、onMessage消息事件(客户端事件)、onClose连接关闭事件(客户端事件)、onWorkerStop进程退出事件(进程事件)
也就是说多服务启动服务时,registerAddress需要一致,各服务的businessWorker的lanIp会同步到其他gateway。当client进行send message操作时,client连接的gateway把业务推到worker,worker找到接收信息client的gateway并发送信息,其中gateway作收发信息用,worker用作处理业务逻辑。
进行实践:
由于没有那么多服务器进行试验,因此使用了docker代替。
docker1 ip:172.17.0.2
docker2 ip:172.17.0.3 #设置为register服务器
代码部分,重点设置registerAddress为主服务器ip
#docker1 start_gateway.php
use \Workerman\Worker;
use \GatewayWorker\Gateway;
use \Workerman\Autoloader;
require_once __DIR__ . '/../../vendor/autoload.php';
// gateway 进程
$gateway = new Gateway("Websocket://0.0.0.0:7373");
$gateway->name = 'gateway';// 设置名称,方便status时查看
$gateway->count = 4;// 设置进程数,gateway进程数建议与cpu核数相同
$gateway->lanIp = '172.17.0.2'; // 分布式部署时请设置成内网ip(非127.0.0.1)此处做演示用,需填写真实的服务器内网ip
$gateway->startPort = 2310;// 不可被其他项目占用端口,2300-2303 , 2310-2313, 必须1236,1238端口不能重复,不能2个项目都用1236端口,可以全部替换其他端口!同时startPort不能冲突
$gateway->pingInterval = 10;// 心跳间隔
$gateway->pingData = '{"type":"ping"}';// 心跳数据
$gateway->registerAddress = '172.17.0.3:1238';// 服务注册地址
if(!defined('GLOBAL_START'))
{
Worker::runAll();
}
#docker1 start_businesswork.php
use \Workerman\Worker;
use \GatewayWorker\BusinessWorker;
use \Workerman\Autoloader;
require_once __DIR__ . '/../../vendor/autoload.php';
$worker = new BusinessWorker();// bussinessWorker 进程
$worker->name = 'businessWorker';// worker名称
$worker->count = 4;// bussinessWorker进程数量
$worker->registerAddress = '172.17.0.3:1238';// 服务注册地址
// 如果不是在根目录启动,则运行runAll方法
if(!defined('GLOBAL_START'))
{
Worker::runAll();
}
#docker2 start_gateway.php
use \Workerman\Worker;
use \GatewayWorker\Gateway;
use \Workerman\Autoloader;
require_once __DIR__ . '/../../vendor/autoload.php';
// gateway 进程
$gateway = new Gateway("Websocket://0.0.0.0:7373");
$gateway->name = 'gateway';// 设置名称,方便status时查看
$gateway->count = 4;// 设置进程数,gateway进程数建议与cpu核数相同
$gateway->lanIp = '172.17.0.3'; // 分布式部署时请设置成内网ip(非127.0.0.1)此处做演示用,需填写真实的服务器内网ip
$gateway->startPort = 2310;// 不可被其他项目占用端口,2300-2303 , 2310-2313, 必须1236,1238端口不能重复,不能2个项目都用1236端口,可以全部替换其他端口!同时startPort不能冲突
$gateway->pingInterval = 10;// 心跳间隔
$gateway->pingData = '{"type":"ping"}';// 心跳数据
$gateway->registerAddress = '172.17.0.3:1238';// 服务注册地址
if(!defined('GLOBAL_START'))
{
Worker::runAll();
}
#docker2 start_businesswork.php
use \Workerman\Worker;
use \GatewayWorker\BusinessWorker;
use \Workerman\Autoloader;
require_once __DIR__ . '/../../vendor/autoload.php';
$worker = new BusinessWorker();// bussinessWorker 进程
$worker->name = 'businessWorker';// worker名称
$worker->count = 4;// bussinessWorker进程数量
$worker->registerAddress = '172.17.0.3:1238';// 服务注册地址
// 如果不是在根目录启动,则运行runAll方法
if(!defined('GLOBAL_START'))
{
Worker::runAll();
}
#测试页面
Aliplayer Online Settings .div-a{ position:absolute; left:30px; top:30px;}
.div-b{ position:absolute; left:80px; top:316px; width:250px; height:25px; }
.div-c{ position:absolute; left:44px; top:86px; width:300px; height:200px; background:white;border-style: solid;}
var countNum=0;
var ws;
$(function(){
for(i=0;i<1;i++){
link();
}
})
function link () {
ws = new WebSocket("ws://127.0.0.1:7373");//连接服务器
ws.onopen = function(event){
console.log(3);
console.log(event);
};
ws.onmessage = function (event) {
console.log(event);
var msg = "
"+event.data+"
"+countNum;$("#msgArea").append(msg);
countNum ++;
}
ws.onclose = function(event){
/* alert("已经与服务器断开连接\r\n当前连接状态:"+this.readyState); */
};
ws.onerror = function(event){console.log("WebSocket异常!");};
}
function sendMsg(){
var msg = $("#userMsg").val();
$("#userMsg").val('');
ws.send(msg);
}
然后分别启动:php start.php start
测试:web端测试:打开两个窗口,websocket ip分别填两个docker映射出来的端口,发送信息,对方聊天框能收到信息。
结论:两个docker都运行了gateway进程和worker进程,client可以连接任意一个gatewayworker进行互相通讯。
注意事项:
1、start_gateway.php中如果$gateway->startPort=2300; $gateway->count=4;,则2300 2301 2302 2303四个端口需要被设置成能被其它服务器访问,也就是起始端口$gateway->startPort到$gateway->startPort + $gateway->count - 1这 $gateway->count个端口要设置成能被其它内网服务器访问。
2、如果多机部署服务器不在一个局域网,部署时ip参数可以使用外网ip(注意云服务器一般没有外网网卡,所以阿里云、腾讯云等云服务器一般无法外网部署集群),对应端口防火墙应该设置成能被外网服务器访问。
3、为了方便前端接入和扩容,可以再gateway前面加一层DNS、LVS等负载均衡策略。
4、如果有了前端接入的负载均衡策略,就可以用同样的方法增加服务器。
附录:官方的并发估计
http://doc3.workerman.net/315236
并发连接数是指服务器当前时刻一共维持了多少TCP连接,而这些连接上是否有数据通讯并不关注,例如一台消息推送服务器上可能维持了百万的设备连接,由于连接上很少有数据通讯,所以这台服务器上负载可能几乎为0,只要内存足够,还可以继续接受连接。
并发请求数一般用QPS(服务器每秒处理多少请求)来衡量,而当前时刻服务器上有多少个tcp连接并不十分关注。例如一台服务器只有10个客户端连接,每个客户端连接上每秒有1W个请求,那么要求服务端需要至少能支撑10*1W=10W每秒的吞吐量(QPS)。假设10W吞吐量每秒是这台服务器的极限,如果每个客户端每秒发送1个请求给服务端,那么这台服务器能够支撑10W个客户端。
并发连接数受限于服务器内存,一般24G内存workerman服务器可以支持大概120W并发连接。
并发请求数受限于服务器cpu处理能力,一台24核workerman服务器可以达到45W每秒的吞吐量(QPS),实际值根据业务复杂度以及代码质量有所变化。