一、需求
1、问题
由于需要做负载均衡
,但做了负载均衡
之后,当请求向新的服务器分发时,websocket
的fd
则从 0
开始,故当pc
和app
不在同一服务器时,则会出现pc
无法直接向app
传递消息。
具体问题:PC
发送请求,被转发到服务器A
,APP
发送请求,被转发到服务器B
,这时PC
和APP
由于不在同一服务器,故不能直接通过websocket
进行传递消息。
二、环境配置
1、准备
使用三台服务器做负载均衡
,分别为 A
、B
、C
,其中A
用来做负载均衡
,B
、C
存储websocket
源码
2、负载均衡配置
- 主服务器
A
配置
upstream taishan {
server server B:9502 weight=1;
server server C:9502 weight=1;
}
server {
listen 80;
server_name domain;
location ~* /wss(.*)$ {
proxy_redirect off;
proxy_pass http://taishan;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header DIY-RUN-ENV "TEST";
}
location / {
#proxy_redirect off;
#proxy_set_header X-Real-IP $remote_addr;
#proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
#proxy_set_header Host $http_host;
#proxy_http_version 1.1;
proxy_pass http://taishan;
}
}
ps:若不想把服务器B、C的9502端口暴露在外,则可以用反向代理,如果是阿里云服务器,则需要在阿里云开放端口
3、主服务器redis配置
参考链接:https://blog.joniding.com/index.php/archives/26/
4、php redis扩展、swoole扩展
具体安装请使用搜索引擎
三、实现思路
go(function () use ($conf){
$redis = new \EasySwoole\Redis\Redis(new RedisConfig([
'host' => $conf['host'],
'port' => $conf['port'],
'auth' => $conf['auth'],
]));
$ip = UtilHelper::get_server_ip();
echo "订阅频道为:". $this->channel.'_'.$ip . "\n";
//订阅完成设置已订阅
$redis->set($this->channel.'_'.$ip,1);
//订阅
$redis->subscribe(function ($redis, $pattern, $str) {
echo '订阅频道已收到消息' . "\n";
$data = json_decode($str,true);
echo $str . "\n";
$fd_list = isset($data['fd_list'])?$data['fd_list']:'';
$server_ip = UtilHelper::get_server_ip();
//判断服务器ip与绑定设备的ip是否一致
if ($server_ip == $fd_list['server_ip']){
echo 'ip is ok' . "\n";
$push_arr = [
'receive_data' => $data['receive_data'],
'fd_list' => $data['fd_list'],
'current_fd' => $data['current_fd'],
'type' => $data['type'],
'msg' => $data['msg'],
'status' => isset($data['status'])?$data['status']:100200,
'flag' => $data['flag']
];
TaskManager::getInstance()->async(new BroadCastTask($push_arr));
}
}, $this->channel.'_'.$ip);
});
如上述代码所示,在配置文件中设置好服务器ip
,然后使用redis
订阅当前服务器频道,以服务器ip
来区分,如:redis_192.168.1.10
、redis_192.168.1.12
,接下来就是请求时,判断PC
和APP
是否在同一服务器,如果不在则发消息给订阅频道,代码示例如下:
/**
* 发布消息
* @param $channel
* @param $message
* @return bool
* @author:joniding
* @date:2019/12/20 10:47
*/
public function lPublish($channel,$message)
{
$conf = Config::getInstance()->getConf('REDIS');
go(function () use ($conf,$channel,$message){
$redis = new \EasySwoole\Redis\Redis(new RedisConfig([
'host' => $conf['host'],
'port' => $conf['port'],
'auth' => $conf['auth'],
]));
$redis->publish($channel,$message);
});
return true;
}
//调用示例
$subcribe = new Subscribe();
$result = $subcribe->lPublish(self::$channel.'_'.$fd_server_ip,json_encode($push_arr));
注意:
* 使用redis
订阅时,建议使用协程或注册进程,若不使用协程,则订阅的回调不会触发
* 若出现订阅回调出现多次收到回调消息,则是因为订阅了多次该频道,解决方案,第一次订阅时,在redis
设置一个已订阅的标识字段