声明: CSDN作者原创投稿文章,未经许可,禁止转载。
作者:蔡伟伟,本科毕业于同济大学,从事Java开发多年,后端码农一枚。先后从事ETL、AdHoc报表、垂直爬虫、App制作云服务、动态用户分群等产品的设计研发工作。在互联网领域混迹多年,各方面均有所涉猎。现任MaxLeap数据分析组开发人员,负责Hadoop、Spark相关的分析系统架构设计与开发。
责编:钱曙光,关注架构和算法领域,寻求报道或者投稿请发邮件[email protected],另有「CSDN 高级架构师群」,内有诸多知名互联网公司的大牛架构师,欢迎架构师加微信qshuguang2008申请入群,备注姓名+公司+职位。
本篇是一个Node新手做完实际项目后的心得总结。
如果BOSS要求你在短期内快速实现一套聊天云服务平台,实现成本主要是:
如果有现成的轮子偷懒的话,socket.io比较适合:
在仔细研读了Flexi传授如何说服自己的老板采用Node.js,并成功说服BOSS后,正式开始自己的Node之旅。
对于开发环境,无论你是使用编辑器/编辑器之神,或是sublime/atom/npp之类,亦或是WebStorm高富帅有钱任性,都是很不错的选择。
Supervisor可以帮你watch代码变更,自动重启服务,从而节省了手工重启程序的时间。
首先,勾勒出一个核心的聊天系统大致的雏形:
基本功能
好友
群组
聊天历史记录
扩展与周边
分析下大致需要的存储层和中间件以及是否有相关的Node实现:
对比
常年滋润在Java这片润土之上,先来做个比较,让我们对陌生的技术栈有所了解。
ES6是个好东西, 我觉得比较好用的三点:
仔细思考,勾勒出大致的时序图:
设计下大致的架构:
状态管理
典型的IM系统中必然存在用户在线离线的状态。每一个在线状态,对于服务器来说,等价于与客户端存在一个Socket连接。所以对于单机环境下,在内存中维护用户和Socket的关系即可,当Socket连接和断开时分别做更新操作。当切换至集群环境时,情况变得稍微复杂,所以我们需要借助ZooKeeper来实现。除了本机每个用户与Socket关联关系,另外以临时节点的方式在ZooKeeper中进行存储,目录结构为根节点/命名空间/用户标识/Socket标识(临时节点)。当socket连接被建立的时候,创建对应的临时节点,socket断开时移除临时节点。当服务器意外退出时,除了socket连接全部断开之外,在其ZooKeeper session上的所有对应的临时节点也会被销毁。SocketIO的重连机制会尝试重连至其他伺服器并重新建立起对应关系。
优点:
缺点:
用途:
好友关系、群组关系
关系表原本存储于HBase,但因为缺乏事务支持,实际效果不佳,经常导致关系不一致。传统关系型数据库在这一方面依旧强势。这块比较简单,即常见的关系模型表,在此略过。
点对点聊天实现
单机
A对B发送消息,除了基础的权限判定,只需查询内存表中对应B的所有socket,然后对其发射消息即可。见下图:
集群
由于B可能登录在不同的服务器上,需要借助消息中间件(Redis Pub/Sub),发布消息,每个服务器订阅消息列表,如果存在消息接收者,则找到其注册在本机的socket进行发射消息。流程如下图:
广播聊天实现
广播类似以上的点对点实现,只是多了一步查询成员表依次处理的步骤。此处略。
聊天历史记录的实现
考虑到只需要根据时间范围做分页查询的简单需求,这里使用了HBase的宽表。点对点形式的聊天我们可以对两个用户标识进行排序,并结合命名空间生成唯一的哈希值,作为行健,而每个CELL的值则是时间戳,因为我们需要令其自然倒序排列,所以针对时间戳做了LONG。MAX-时间戳的处理。综合起来,大致的存储结构如下:
敏感词过滤
维护脏词字典,对消息进行字符串替换?为了实现正确辨认“曹操在操场操美女”中的动词“操”,需要实现中文分词和词性判断,于是处理逻辑转变成:
Node本身是单线程的,虽然Node本身提供Cluster模块,但需要修改代码。通过PM2这个工具可以简便地让其多进程部署,充分利用多核CPU资源:
可以使用官方的node镜像。但体积比较大,这里推荐基于alpine-node,体积比较小巧,例如:
FROM mhart/alpine-node:4
RUN apk add --no-cache make gcc g++ python
RUN apk add --no-cache imagemagick
WORKDIR /src
ADD . .
RUN npm install --registry=http://registry.npm.taobao.org/
EXPOSE 3000
CMD ["npm","start"]
著名的单机100万连接,由于项目是第一个版本,限于各方面原因我们暂时没有完成这个测试。但在这里简短介绍所需的一些配置,以备后续使用:
net.ipv4.tcp_wmem = 4096 87380 4161536
net.ipv4.tcp_rmem = 4096 87380 4161536
net.ipv4.tcp_mem = 786432 2097152 3145728
fs.file-max = 1000000
* hard nofile 1000000
* soft nofile 1000000
root hard nofile 1000000
root soft nofile 1000000
(sysctl -p)
或者重启,检查下当前的设置 cat /proc/sys/fs/file-nr*客户端
ifconfig eth0:0 192.168.77.10 netmask 255.255.255.0 up
ifconfig eth0:1 192.168.77.11 netmask 255.255.255.0 up
ifconfig eth0:2 192.168.77.12 netmask 255.255.255.0 up
ifconfig eth0:3 192.168.77.13 netmask 255.255.255.0 up
ifconfig eth0:4 192.168.77.14 netmask 255.255.255.0 up
ifconfig eth0:5 192.168.77.15 netmask 255.255.255.0 up
ifconfig eth0:6 192.168.77.16 netmask 255.255.255.0 up
ifconfig eth0:7 192.168.77.17 netmask 255.255.255.0 up
ifconfig eth0:8 192.168.77.18 netmask 255.255.255.0 up
ifconfig eth0:9 192.168.77.19 netmask 255.255.255.0 up
ifconfig eth0:10 192.168.77.20 netmask 255.255.255.0 up
ifconfig eth0:11 192.168.77.21 netmask 255.255.255.0 up
ifconfig eth0:12 192.168.77.22 netmask 255.255.255.0 up
ifconfig eth0:13 192.168.77.23 netmask 255.255.255.0 up
ifconfig eth0:14 192.168.77.24 netmask 255.255.255.0 up
ifconfig eth0:15 192.168.77.25 netmask 255.255.255.0 up
ifconfig eth0:16 192.168.77.26 netmask 255.255.255.0 up
ifconfig eth0:17 192.168.77.27 netmask 255.255.255.0 up
ifconfig eth0:18 192.168.77.28 netmask 255.255.255.0 up
net.ipv4.ip_local_port_range = 1024 65535
存在即合理,不要卷入无谓的语言之争,本猿觉得干这行的最重要莫过于学习能力。
写代码之前先理清楚思路和结构,不打没有准备的仗。
良好的代码规范,遵循KISS原则。