egg-cluster: sticky用connection.remoteAddress,如果前面有nginx怎么办
@rachardking https://eggjs.org/zh-cn/tutorials/socketio.html#%E9%83%A8%E7%BD%B2 文档有说明 nginx 配置。
X-Forwarded-For header 可以拿到转发的 ip
@fengmk2
https://github.com/eggjs/egg-cluster/blob/master/lib/master.js#L151
require('net').createServer({ pauseOnConnect: true }, connection => {
// We received a connection and need to pass it to the appropriate
// worker. Get the worker for this connection's source IP and pass
// it the connection.
/* istanbul ignore next */
if (!connection.remoteAddress) {
connection.close();
} else {
const worker = this.stickyWorker(connection.remoteAddress);
worker.send('sticky-session:connection', connection);
}
}).listen(this[REALPORT], cb);
因为启用的是net server, tcp 拿不到请求头的
配置的话参考文档,如果只是拿ip,可以看socket.io 文档
我也遇到这个问题,主要问题如下:
1.connection.remoteAddress拿到的是反向代理的ip,如果反向代理层只有一台,那么这个粘性会话基本只会发送到node的一个固定worker进程里,这样其实只有一个固定的worker在处理socket.io的请求。
2.如果用LVS+2台haproxy做反向代理集群,请求会被随机发送给2台haproxy中的1台进行转发,这样的话,同一个用户发起的请求,用connection.remoteAddress拿到的ip是不固定的,会导致无法建立粘性会话。
个人觉得问题在于connection.remoteAddress拿到不是用户的真实ip。请问这是我的配置问题吗?加了代理层,connection.remoteAddress能获取到用户的真实ip吗?
@fengmk2 试过了,拿到的IP都是127.0.0.1,根本拿不到真实IP,导致websocket的所有请求都负载到一个worker进程了。
@rachardking @marxy @zhuziyu
可以考虑试修改下egg-cluster、egg-scripts 模块来获取用户ip。以下是修改的内容,代码不多。
startMasterSocketServer(cb) {
require('http').createServer((req, res) => {
// 前提:通过nginx proxy_set_header 设置 x-forwarded-for
let remoteAddress = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
if (!remoteAddress) {
req.socket.close();
} else {
const worker = this.stickyWorker(remoteAddress);
worker.send('sticky-session:connection', req, res);
}
}).listen(this[REALPORT], cb);
}
process.on('message', (message, req, res) => {
if (message !== 'sticky-session:connection') {
return;
}
// 这里
server.emit('request', req, res);
});
可以考虑创建两个新包,比如 egg-scripts-x, egg-cluster-x,然后 修改 egg-script-x/index.js (对应原来的 egg-scripts/index.js)
// 加上这段代码,这样不用去改 egg 本身
let egg = require('egg');
egg.startCluster = require('egg-cluster-x').startCluster;
刚接触egg,还没有来得及深入了解。大致看了下启动脚本和sticky模式的代码,只考虑HTTP服务的情况,上面代码应该可行。其他情况还没验证。是否有毒副作用还未仔细验证,如有请指出。
@chyingp 这个改法无法工作的。 socket.io 的 websocket 模式不是 http 协议,在这个地方处理是在 tcp 级别,而不是 http 级别。tcp 级别没有 HEADER 这个概念。所以是行不通的。
解决办法其实很简单,nginx 配置一下即可:https://github.com/eggjs/egg/blob/e904e48f250b09f0fc880782a9d400d9d43210d5/docs/source/zh-cn/tutorials/socketio.md#%E9%83%A8%E7%BD%B2
proxy_bind $remote_addr transparent;
@ngot 是的,所以标注了“只考虑HTTP服务的情况”哈哈。
socket.io 连接阶段,不管transport 是 ajax-polling + ws,还是直接ws,上层都是HTTP协议,理论上应该是可行的的(未经验证)。
prxoy_bind 对权限要求有点高,非不得已不考虑。是否考虑支持支持下proxy_protocol?
proxy_bind $remote_addr transparent;
貌似这个配置也不行,用了之后,客户端会超时,查看nginx的日志,能看到websocket的连接的status code是499,应该是客户端等待连接,一直没反应,超时了后断了连接。
如果用文档上的,是comment掉了上面这个配置的,用这个配置:
location / {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:7001;
# http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_bind
# proxy_bind $remote_addr transparent;
}
connection.remoteAddress取到的会是127.0.0.1,而不是源ip
我是在这行打印了
connection.remoteAddress来检查这个值的取值的
是我配置的有问题么?还是这个有别的配置方式?
我们在生产环境下用docker打包运行eggjs + socket.io,在nginx的reverse proxy后面。因为这个remoteAddress取值的问题,导致几个进程中只有一个进程在承接流量,很快就服务不可用了。因为上面的问题还没找到解决方案,可能临时打算将worker设置成1,然后开多个container来跑,流量在nginx进行负载均衡,这样应该能解决一些现在的负载问题,但是并不是best practice,也有点hack,希望能有人指明一下egg-socket.io应该怎样配置在nginx后面