利用Nodejs实现即时通讯的选择最常见的就是socket.io,
首先来说说socket.io
优势:
1. 上手容易
2. 社区活跃(评价优秀开源项目的重要指标)
3. 对开发者友好, 避免开发者适配IE等不支持websocket的浏览器
4. 生态完整, 有对应Android,iOS SDK
缺点:
坑1. 一上生产环境就各种内存高或者cpu占用高
坑2. 扩展性
坑3. 从0.9x的不兼容性升级
坑1 内存问题的解决
socket.io在跑一段时间后, 内存占用并没有下来,猜测难道断开连接后,内存没有释放? 但通过heapdump分析快照,记录下三个状态进行对比,1>服务启动时 2>使用高峰时 3>使用低谷时 , 三者进行对比后发现并不是猜测的情况, socket.io自身的变量是已经销毁了的, 经历多次优化尝试后,原来是v8引擎并没有释放内存, 解决方案:启用--expose-gc,定时global.gc(),
坑2 CPU问题的解决
当时socket.io版本还为0.9.16, 并没有官方推荐的负载方案, 使用cluster做多进程时, 无论用redis store 还是 memory store都会造成nodejs服务或redis服务cpu高压力,导致服务响应变慢,解决方案:仅使用单进程,不做多进程负载(服务横向扩展后面会讨论)
其余优化: v8引擎在64位机器侠默认在允许一个进程占用1.4G内存, 通过设置启动参数--max-old-space-size 和 --max-new-space-size 提高 v8 使用内存的上限,提高服务器内存使用率
下面来说说服务最重要的横向扩展
需求1. 用户可能同时打开多个网页和手机App, 用户的操作在多个页面都需要同步(如,切换聊天对象,发送消息等)
需求2. 尽可能减少服务之间的消息同步
现状1. 所有认证通过nodejs 读取用户请求cookie获取会话信息
现状2. 常见的Nginx负载方案有round-robin,least-connected,ip-hash , 但这三种并不能很好的满足需求, 当backend增加时, 如果使用nodejs官方方案用所有消息在服务之间同步,会造成同步量非常大
解决方案: 将会话前置到Nginx,并动态路由
使用Nginx+lua(openresty)来读取会话信息, 并动态路由至指定backend服务,如 将user1的5个网页连接和1个app连接全部路由至server1
这里的用户连接路由规则参照的是常见的数据库分表规则, 根据用户ID计算出对应chat server, 将单一用户连接锁定在单台服务上, 这样做的好处是, 不管是lua,nodejs或者其他语言都可简单的计算出用户对应连接的chat server, 有利于聊天服务与其他服务之间的对接.
服务之间消息同步: 使用redis的pub/sub, 每台server订阅自己的频道, 当用户1要发送消息至连接到另外一台服务的用户2时,nodejs根据上面描述的路由规则计算出用户2对应的server, publish至用户2对应的server2, server2再将消息推送给user2
写完才发现不能贴代码, 下次用markdown来写...
参考链接
Nginx lua wiki
https://www.nginx.com/resources/wiki/modules/lua/
OpenResty 中文wiki
https://github.com/iresty/nginx-lua-module-zh-wiki
OpenResty 系列课程
http://www.stuq.org/course/detail/1015
socket.io