项目场景:
项目中使用socketIo推送结果到前端页面,开发环境调试完后,部署到测试和生产环境遇到问题问题描述:
-
测试和生产环境用了nginx做反向代理和负载均衡
-
测试和生产环境socketio服务端绑定的是域名
-
当第三方系统调用结果回调前端时,不知道前端socket-client连的时哪台socket-server
解决方案:
- nginx做http请求的负载均衡的时候是采用轮询的方式,但是websocket需要保持长连接,所以Nginx必须配置支持长连接
upstream socket{
//websocket的负载均衡必须使用Iphash的策略来保证,客户端是连的同一个服务端
ip_hash;
server xxx.xxx.xxx:9092;
server xxx.xxx.xxx:9093;
}
server {
listen 8080;
server_name www.baidu.com;
location /socket.io {
proxy_set_header X-Real_IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X_Forward_For $proxy_add_x_forwarded_for;
//显示设置http协议1.1,这个版本才支持长连接
proxy_http_version 1.1;
//连接从HTTP连接升级到WebSocket连接
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_pass http://socket;
}
}
- 当服务部署到阿里云时,服务端绑定的主机名要改成0.0.0.0
public class SocketServer {
private final Logger logger = LoggerFactory.getLogger(getClass());
private SocketIOServer server;
//9092
public static String WSS_PORT = PropUtil.getProperty("wss.port");
//0.0.0.0
public static String WSS_HOST = PropUtil.getProperty("wss.host");
public void run() {
if (server == null) {
SocketConfig socketConfig = new SocketConfig();
socketConfig.setReuseAddress(true);
Configuration config = new Configuration();
config.setSocketConfig(socketConfig);
//设置主机
config.setHostname(WSS_HOST);
//设置端口
config.setPort(Integer.parseInt(WSS_PORT));
//添加链接身份验证监听器
config.setAuthorizationListener(new SocketIoAuthListener());
//添加错误处理监听器
config.setExceptionListener(new SocketIoExListener());
server = new SocketIOServer(config);
}
//添加客户端断开连接监听器
server.addDisconnectListener(new SocketIoDisConnListener());
//添加客户端连接监听器
server.addConnectListener(new SocketIoConnListener());
server.start();
logger.info("================Socket服务端启动成功==============");
}
}
- 服务端采用redis做消息队列,监听一个topic,来进行消息群发,然后每个服务器拿到消息后都尝试往client发送。
<!-- redis -->
<bean id="remindListener" class="org.springframework.data.redis.listener.adapter.MessageListenerAdapter">
<constructor-arg>
<bean class="com.xxx.xxx.websocket.CallBackMessageListener"/>
</constructor-arg>
</bean>
<bean id="redisContainer" class="org.springframework.data.redis.listener.RedisMessageListenerContainer">
<property name="connectionFactory" ref="jedisConnectionFactory"/>
<property name="messageListeners">
<map>
<entry key-ref="remindListener">
<bean class="org.springframework.data.redis.listener.ChannelTopic">
<constructor-arg value="socketio:topic"/>
</bean>
</entry>
</map>
</property>
</bean>
public class CallBackMessageListener implements MessageListener {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private RedisTemplate redisTemplate;
@Override
public void onMessage(Message message, byte[] bytes) {
try {
byte[] body = message.getBody();
//反序列化
String str = (String) redisTemplate.getValueSerializer().deserialize(body);
String channel = (String) redisTemplate.getValueSerializer().deserialize(bytes);
logger.info("redis监听到消息内容:" + str);
logger.info("消息监听通道:" + channel);
String[] msg = str.split(":");
if(msg.length == 2){
SocketIOClient client
= SocketIoClientCache.getSocketIOClientByClientID(msg[0]);
if (client == null) {
logger.info("没有找到socket client,clientId:{}", msg[0]);
return;
}
SocketIoClientCache.sendMessageToClient(client, EventContanst.CALL_BACK_EVENT, msg[1]);
}
} catch (Exception e) {
logger.error("-----------------------消息提醒redis监听处理失败-------------------------");
logger.error(e.getMessage(), e);
}
}
}
本文介绍了在项目中使用nginx作为反向代理和负载均衡器的场景,特别是针对socketio服务端绑定域名的情况。当面临第三方系统回调时,前端socket-client无法确定连接的socket-server。解决方案是通过nginx配置支持长连接的websocket负载均衡,并将服务端绑定地址设为0.0.0.0。同时,服务端利用redis进行消息队列和topic监听,实现消息群发。
5104





