Nginx Web IM 调研

需求是一个 基于 Epoll的 高并发的 Push 的 Web IM服务器 , 打算是用 Nginx实现,经过2个星期的调研,最终是放弃了的 ,同事找到了一套用Jetty 的更成熟的架构。

Epoll简介,

他是一个Linux 2.6以后才有的新的IO机制。 大体原理是把所以的连接放在一个数组,或者队列中,给每个连接一个回调函数, OS API 负责对产生事件响应的连接进行回调。

和select的区别是,select需要遍历这个连接数组。


关于Push技术 是基于comet框架的long pulling, 大体原理是 Client 端 的HTTP请求发过来 ,Server这边不回应,还是让连接阻塞住。 等有需要发送给相应Client的消息需要发送是,这个消息才被“推送”到Client端去。 这样一个标准的 HTTP 连接完成, 断开连接后 。Client马上又重新连上,阻塞 ,等待消息。


最初的想法是有一个第三方的插件 交NGINX_HTTP_PUSH 和NGINX_HTTP_PUSH_STREAM  , 两个实现的功能是一样的 都是 接受一个HTTP 请求,然后把消息发送到另外一个PEER上去。

区别是 后者比前者多了以STREAM 保持长连接的功能。 但是网上有文章说前者存在内存泄漏是因为他对小的内存没有回收,具体的要爬源码。不过源码不长,3000多行的样子。


这个插件的大体原理是有两个主要的指令 subscriber 和 publisher 。 subscriber 发一个HTTP GET REQUEST 附带一个Channel ID 到服务器,服务器用这个ID创建一个channel 里面生成一个队列 用来存放消息。 这时候服务器会检查队列中有没有已经存在的消息如果有则subscriber 立即获取这条消息。 然后断开连接完成HTTP 再立即重新连上

如果没有消息,则subscriber阻塞在服务器,就是上文说的long polling。


publiser的作用是发送消息。 他发送消息到指定channel ID ,如果不存在这个channel ID 更具配置文件决定是不是创建一个新的,如果已经存在则放到队列中,这时候服务器会检查是不是有sub定于这个channel如果有的话,则把消息推送到所有订阅的sub上面。


这个插件的问题在于 ,他只是一个WEB 到 NGINX 到WEB的 循环, 但是显然他并没有实现一个和后台应用服务器交互的过程。 

这就是我需要做的工作。

一开始以为只要在后面保持一个TCP的长连接就OK了 。但是事情并不是这样简单。

NGINX暴露给程序员操作的还是一个事件出发的机制,你可以在那么几个事件上挂上的回调函数来处理一些事情,但是这些事件是非常有限的,而并没有一个地方适合去开启一个TCP连接,不论是你作为服务器端发起还是客户端发起。更没有那么一个事件是你listen到一个tcp连接 而触发的。如果要做那只能去改下源码了。毕竟NGINX还是有一个收到HTTP请求那么一个事件的,HTTP是基于TCP的,所以要改肯定还是能改的。

然后想到了使用一个UPSTREAM 转发请求,原来版本的是直接把HTTP转发给后台,但是因为我们的后台接受的是自己的协议,所以需要自己包一下。这个理论上是可行的,上行也是没有问题的。

但是问题在于下行。这个模块是你发送包给后台之后这个连接就阻塞在那里等待后台的回应,后台有回应了然后再出发下一个处理后台回应并转发给client的事件。这是一套固定的流程,除非去改源码。

我们也有可能收到从客户端而不是其他WEB发过来的消息,这样这套流程就不能走了。

所以我最后想到的办法是 通过upstream 把从WEB 发过来的消息转发到 后台应用服务器,然后这条路就结束了。

后台应用服务器处理无论是从client还是其他WEB端发过来的消息,如果是他要发送给以个WEB的话,那他就发起一个HTTP请求到NGINX 。这样就是publisher的功能,同时HTTP 默认打开了 KEEP ALIVE功能那样 就可以然HTTP的连接一直保持着,而不用释放然后重新建立,把TIME OUT事件设长一点就相当于一个长连接。


对于WEB端下线的问题。如果WEB端是主动下线的 即他CLICK了某个BUTTON 那么 可以PUBLISHER一条消息过来,可以通过解包来告诉应用服务器他下线了。

或者他通过PUBLISHER带一个DELETE参数这样就能删除指定的CHANNEL。然后如果是这种情况或者意外情况导致断线,那么后台应用服务器可以每间隔一段时间通过一个GET CHANNEL STATISTICS的命令来得到一个所有的CHANNEL的信息 通过JSON返回的,这样比一下就能知道谁下线了。

需要注意的是NGINX的这两个插件会有一个设TIMEOUT的功能,即WEB断线 TIME OUT事件之后才会认为他们是真的断线了,这时候才释放和回收资源。

所以如果后台服务器认为WEB端客户在线,而发消息过来,但是到NGINX的时候客户端掉线了 ,那么这条消息就会丢失。所以后台服务器发送的MSG应该是需要的到一个从客户端发送来的ACK来决定是否需要重发。


最后我和同事都互相交流了一下大家调研的两个框架。 Jetty也是基于 COMET 和 EPOLL的,虽然是JAVA实现的,性能上可能比起C实现的NGINX有所不如,但是JETTY的那一套是专为WEB IM而实现的很成熟的一套机制。

而NGINX的做法是我自己想出来的,实现起来有很多的不可预知性。 虽然看上去路是走通了额,但是因为NGINX 回调函数的注入点很有限,所以除了改源码之外,自己可操控的余地很小,如果有什么新的feature加起来很容易导致整个流程要重新设计。所以这是最后没有采用的原因

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页