SHIHUC

好记性不如烂笔头,还可以分享给别人看看! 专注基础算法,互联网架构,人工智能领域的技术实现和应用。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

HTTP请求回调IM系统LB,确保服务定向调用

Posted on 2018-03-23 19:25  shihuc  阅读(1204)  评论(0编辑  收藏  举报

1. 背景介绍

基于websocket的及时通信中,客户端与服务端建立ws连接后,服务端将业务继续传递到下一级业务服务系统Business server后,下一级服务系统处理完毕后,要将结果反馈给客户端,而此时的客户端ws服务器存在多个实例时,处理方式上存在几种策略:

1) 比较常见的是基于ip哈希;

2)基于参数的hash;

这些,一般情况下可以满足业务需求,但是呢,在ws服务动态增减的时候,可能就会出现消息丢失。例如:当前有3台websocket的tomcat服务器,有一个ws连接建立在tomcat1上面,基于轮询的模式下,且websocket前端采用了心跳机制时,客户端的重连机制会让这个客户端连接转而连接到其他两个tomcat当中之一,例如tomcat2,而客户是无感知的, 请参照websocket连接的后台反向代理问题。若这个时候Business server回调及时通信系统IM后(HTTP),不做特别处理,在轮询的方式下(或者hash方式),会出现消息去往的websocket的tomcat服务器不是这个回复消息该去往的tomcat服务器。导致回复消息无法到达该客户的websocket连接通道上,从而出现消息丢失的问题。

如上图所示:客户请求如红色线条,起初,经过LB-》nginx1-》websocket server1-》other business server. 但是处理过程中,websocket server1宕机了,或者其他什么原因,不能对外服务了,客户端的心跳机制,使得websocket重连,连到了websocket server2上了。此时,other business server处理完了客户的请求,回调IM系统,投递客户请求的答案,有可能出现上图绿色的箭头所示,这个时候,该客户的消息是没有办法投递出去的,因为websocket的长连接是在客户端和websocket server2.

 

即使还有一种,就是基于redis的订阅发布,进行消息回传给ws服务器,这种做法,要增加redis服务器的压力,且可能存在多次发布操作,性能不好,放弃。

 

这里要介绍的是,基于http的接口调用,通过url后面带上query信息,指定ws所在的服务器的ip和port信息。然后在nginx上做特殊配置,实现消息回传时,指定调用ws服务器集群中的服务器,因为这个服务器上存在websocket的连接实例。

nginx的配置如下:

upstream ims_svr {
     server 10.130.215.143:8080;
     server 10.130.215.144:8080;
}

#用于BI回调IMS系统定位消息具体去往那个tomcat服务,涉及websocket的连接要原路来还要原路回去
upstream 10.130.215.1438080 {
     server 10.130.215.143:8080;
}
upstream 10.130.215.1448080 {
     server 10.130.215.144:8080;
}

server {
    listen       80;
    server_name  localhost;

    #charset koi8-r;

    #access_log  logs/host.access.log  main;
    default_type text/html;

    location / {
        root   html;
        index  index.html index.htm;
    }

    location /IMS {
        proxy_pass http://ims_svr;
        proxy_set_header Host $host:$server_port;
        proxy_set_header Remote_Addr $remote_addr;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    location ~ /IMS/(replyMessage|callback) {
        proxy_pass http://$arg_ip$arg_port;
        proxy_set_header Host $host:$server_port;
        proxy_set_header Remote_Addr $remote_addr;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

上面的配置中,技巧就在于获取query中的ip字段和port字段的值,拼接在一起构成upstream的block名称。在proxy_pass的代理下,就可以去往特定服务器,即连接有websocket连接的那个tomcat上。

注意这里你可能会说,websocket server同样会出现宕机啊,在other Business server回调的时候,对的。我们可以在other Business server和websocket server之间搭建redis服务器,作为信息中心节点,通过visistorId+sessionid作为redis的key,ip_port值作为这个key对应的键值,当other Business server回调IM前,获取一下这个visistorId在当前sessionid下的websocket建立在那个websocket server上,即获取IP和Port信息。在回调的HTTP接口的URL部分附着query信息,例如?ip=1.1.1.1&port=8080.

 

如上图所示的案例,客户请求经过LB-》nginx1-》websocket server1-》other business server-》LB-》nginx2-》websocket server1-》resp去往客户端(基于websocket长连接),因为HTTP请求,在LB上是基于轮询的,所以,HTTP回调的response信息,也可能是经过nginx1的,这个时候,基于我们的nginx配置规则,依然会使得resp反馈去往websocket server1上,保障resp响应一定是在websocket连接建立的通道上回复给客户端,确保resp消息不会丢失。

 

测试案例截图:

 

到此,整个方案介绍完毕,是不是觉得nginx很厉害?的确,nginx现在的web应用领域占有率非常高。

 

另外,这里要注意一个小细节:就是nginx获取query部分的变量时,$arg_<xxx>这里的xxx部分,不要出现下划线“_”了,否则会导致query部分的变量获取不到,我经历过这个血的教训

例如我开始的配置如下:

upstream ims_svr {
     server 10.130.215.143:8080;
     server 10.130.215.144:8080;
}

#用于BI回调IMS系统定位消息具体去往那个tomcat服务,涉及websocket的连接要原路来还要原路回去
upstream 10.130.215.143_8080 {
     server 10.130.215.143:8080;
}
upstream 10.130.215.144_8080 {
     server 10.130.215.144:8080;
}

server {
    listen       80;
    server_name  localhost;

    #charset koi8-r;

    #access_log  logs/host.access.log  main;
    default_type text/html;

    location / {
        root   html;
        index  index.html index.htm;
    }

    location /IMS {
        proxy_pass http://ims_svr;
        proxy_set_header Host $host:$server_port;
        proxy_set_header Remote_Addr $remote_addr;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    location ~ /IMS/(replyMessage|callback) {
        proxy_pass http://$arg_ip_$arg_port;
        proxy_set_header Host $host:$server_port;
        proxy_set_header Remote_Addr $remote_addr;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

测试过程中,nginx后台报错如下:

access.log

10.130.207.217 - - [23/Mar/2018:13:03:28 +0800] "POST /IMS/replyMessage?ip=10.130.215.143&port=8080 HTTP/1.1" 502 541 "-" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36"

error.log

2018/03/23 13:03:28 [error] 2358#0: *253758 no resolver defined to resolve 8080, client: 10.130.207.217, server: localhost, request: "POST /IMS/replyMessage?ip=10.130.215.143&port=8080 HTTP/1.1", host: "10.130.207.217"

这里显然是$arg_ip_$arg_port值没有计算对。根本原因是$arg_ip_搞错了

 

 

切记,切记nginx的query的arg取值规则。