前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于nginx搭建SocketIO集群

基于nginx搭建SocketIO集群

作者头像
田维常
发布2019-09-12 15:33:57
1.3K0
发布2019-09-12 15:33:57
举报

SocketIO:服务端推送就是这么简单!这篇文章中,我们介绍了SocketIO这款消息推送利器。今天我们来聊下怎么搭建一个生产可用的SocketIO集群。

由于单机资源的限制,一台机器能够支撑的tcp连接是有瓶颈的,而且也存在单点故障的弊端,所以在生产环境上使用SocketIO时,通常来讲,都是基于集群模式的。

将服务器进行水平扩展,最简单的做法就是使用负载均衡技术如nginx来实现。本文正是基于nginx教你如何一步步地搭建SocketIO集群。

搭建SocketIO服务器节点

我们首先在本地搭建2台服务器节点,服务器代码如下:

节点1:

代码语言:javascript
复制
public class Server1 {

  public static void main(String[] args){
    Configuration config = new Configuration();
    config.setPort(1337);
    final SocketConfig socketConfig = new SocketConfig();
    socketConfig.setReuseAddress(true);
    config.setSocketConfig(socketConfig);
    SocketIOServer server = new SocketIOServer(config);
    server.addConnectListener(new ConnectListener() {
      @Override
      public void onConnect(SocketIOClient socketIOClient) {
        System.out.println(socketIOClient.getSessionId()+" has connected.");
      }
    });
    server.start();
  }
}

节点2:

代码语言:javascript
复制
public class Server2 {

  public static void main(String[] args){
    Configuration config = new Configuration();
    config.setPort(1338);
    final SocketConfig socketConfig = new SocketConfig();
    socketConfig.setReuseAddress(true);
    config.setSocketConfig(socketConfig);
    SocketIOServer server = new SocketIOServer(config);
    server.addConnectListener(new ConnectListener() {
      @Override
      public void onConnect(SocketIOClient socketIOClient) {
        System.out.println(socketIOClient.getSessionId()+" has connected.");
      }
    });
    server.start();
  }
}

2个节点除了监听端口不一样之外,其他代码和逻辑都是一模一样的。

配置nginx负载均衡

以下是nginx的配置,可以看到,在upstream块中配置了2台SocketIO服务器节点的主机和端口。然后在location块中使用proxy_pass指令进行转发。这样就实现了最简单的负载均衡配置。

代码语言:javascript
复制

worker_processes  1;
events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       80;
        server_name  localhost;

        location / {
            proxy_pass http://nodes;
        }

    }

  upstream nodes {

    server localhost:1337;
    server localhost:1338;
  }

}

启动服务并测试

接下来分别启动2台SocketIO服务器和nginx服务器。

再写一个客户端,测试下通过nginx转发后,客户端能不能与后端的SocketIO服务器正常建立连接。

客户端代码如下:

代码语言:javascript
复制
public class Client {

  public static void main(String[] args) throws Exception{
    final Socket socket = IO.socket("http://localhost:80");
    socket.on(Socket.EVENT_CONNECT, new Emitter.Listener() {

      @Override
      public void call(Object... args) {
        System.out.println("I have connected to server.");
      }

    });
    socket.connect();
  }
}

需要注意的是,这里客户端建立连接的时候,设置的连接地址是nginx的主机和端口号。如果能够正常建立连接,那么客户端控制台就会打印"I have connected to server."这段话。

我们运行下客户端看看效果:

可以看到,客户端并没有打印任何日志,说明连接并没有建立成功。

再看下SocketIO服务器1的控制台输出:

可以看到,SocketIO服务器1已经接收到连接请求了,但是客户端并没有接收到相应的回调。

再来看下SocketIO服务器2的情况:

可以看到一个奇怪的现象,那就是SocketIO服务器2不断地打印有新连接建立的日志,而且每个连接的id也都不一样。

实际上,这只是其中一部分截图,几乎每隔1秒钟,服务器2就会打印一条新连接建立的日志。这是为什么呢?

我们想要的只是让客户端与具体的某台SocketIO服务器建立连接,然后通过这条连接往客户端推送数据,但是从上述结果看来,似乎客户端会不断地尝试建立连接,但是最终也没能成功建立一条有效的连接,反而大大地浪费了服务器资源。

SocketIO官方的建议配置

接下来我们看下SocketIO官方是如何使用nginx来做水平扩展的。

打开官网,找到using-multiple-nodes这一节。

(地址:https://socket.io/docs/using-multiple-nodes/)

这一节开头就提到了,如果要使用多节点做水平扩展的话,需要保证一个客户端只跟具体的某台SocketIO服务器维持连接,否则的话,在WebSocket协议握手阶段,会因为握手失败而无法正常建立连接。

同时,官网还给出了nginx负载均衡的参考配置,我们将官方的配置搬过来试一下:

代码语言:javascript
复制
worker_processes  1;

events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       80;
        server_name  localhost;


        location / {
            proxy_pass http://nodes;
            
      # enable WebSockets
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
        }

    }

  upstream nodes {
  
    # enable sticky session based on IP
    ip_hash;

    server localhost:1337;
    server localhost:1338;
  }

}

可以看到,在upstream块里面,多了一个ip_hash的配置,这个配置可以使得nginx根据客户端的ip来做负载均衡,最终的效果是某一个ip的客户端只会被路由到某一台服务器上面。

还有一点不同的是,多了2个请求头的设置:Upgrade和Connection。这2个请求头都是HTTP协议升级到WebSocket协议的过程中会用到的。

接下来我们重启nginx服务器,再启动客户端。

以下是客户端控制台输出:

可以看到,现在客户端是成功建立连接了。

接下来再看下SocketIO服务器1:

也可以看到客户端成功建立连接的日志。

再看看SocketIO服务器2:

什么输出也没有。说明nginx将客户端的连接请求路由到服务器1上面去了。

我们将nginx的配置改成官方提供的之后,就可以正常建立连接了。

那么我们之前那么配置,到底有什么问题呢?

之前我们配置upstream块的时候,并没有明确指定具体的负载均衡策略,因此nginx使用的是默认的轮询策略,而由于WebSocket协议握手的过程中不是一步而就的,它分了几个步骤,如果说上一次是与服务器1握手,下一次是与服务器2握手,那么肯定没法正常完成握手流程,连接也就没法正常建立了。

至于proxy_set_header这个配置,目的是将客户端在握手过程中的请求头传输给后端服务器,不过我试了将这个配置注释掉,发现也还是能正常建立连接的,在生产环境,建立还是把这个配置加上为好。

总结

以上就是使用nginx搭建SocketIO服务器的全过程了。虽然不复杂,但是其背后的原理还是有必要了解的。因为只有知道了原理,如果你们公司用的负载均衡技术不是nginx,那么你也能知道怎么配置负载均衡策略来实现SocketIO服务器的水平扩展。万变不离其宗,底层原理才是最重要的。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-09-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java后端技术栈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
负载均衡
负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档