资讯专栏INFORMATION COLUMN

一个JAVA码农的Node之旅

rollback / 3261人阅读

摘要:的重连机制会尝试重连至其他伺服器并重新建立起对应关系。使用进行中文分词曹操在操场操美女对分词后的名词和动词转换为简体中文并查询命中则替换。返回替换后的字符串得到曹操在操场美女打包部署本身是单线程的虽然本身提供模块但需要修改代码。

本篇是一个Node新手做完实际项目后的心得总结。Node高手完全可以略过本文。

摘要

如果BOSS要求你在短期内快速实现一套聊天云服务平台, 你的第一反应是什么?

让我细细一想实现成本:

要维护社交关系, 一大波僵尸POJO正在向你袭来。
要存储数据库, 找个ORM工具那是必须的。
你怎么也得用长连接吧?好, 那就WebSocket标准吧, Netty或Mina系的亲儿子框架选一个呗。什么?!你只用过Tomcat写WebSocoket?好吧,乖乖翻文档API去吧亲。
完事了?没呢! 连接断了你得实现下重连机制吧?服务器端写完了, 客户端呢?你得帮助指导下实现吧?
本猿的大脑一片黑暗。

我们的征途是偷懒

如果有现成的轮子偷个懒岂不是很好?google后发现有个socket.io的轮子比较适合:

轻量级, 扩展便捷, API简单易用。
周边完善,重连、路由、隔离、单播、广播等等都已经帮我们实现好了。
丰富的客户端支持,涵盖了浏览器端, ANDROID, iOS。
在仔细研读了Flexi传授如何说服自己的老板采用Node.js,并成功说服BOSS后,本猿正式开始自己的Node之旅。

工欲善其事必先利其器 开发环境

对于开发环境, 青菜萝卜各有所爱, 无论你是使用神的编辑器/编辑器之神, 或是sublime/atom/npp之流, 亦或是WebStorm高富帅有钱任性, 都是很不错的选择。
supervisor是个好东西, 它可以帮你watch代码变更, 自动重启服务。节省了手工重启程序的时间。

关于调试

高富帅款: WebStorm

高逼格款: 原始打断点

屌丝款: node-inspector, 可以在Chrome中直接调试, 强烈推荐:

离不开的中间件

首先, 勾勒出一个核心的聊天系统大致的雏形:

基本功能

登录,注销

在线,离线等状态维护

好友

加好友,删好友

好友之间聊天,发文字发图片发音频发视频啥的

群组

创建,加入,退出群组

群组内广播聊天

聊天历史记录 扩展与周边

集群实现

敏感词过滤

分析下大致需要的存储层和中间件以及是否有相关的Node实现:

MySQL: 存储一些重要的元数据, 主要是用户关系类的, 需要事务支持。(node-mysql)

ZooKeeper: 用户在线离线状态存储。 (node-zookeeper-client)

Redis: 使用缓存加速一些查询, PubSub特性用于实现集群通讯。 (ioredis)

HBase: 典型的列式存储, 用于实现一些非核心数据的快速存储查询。 (hbase-rpc-client)

LevelDB: 本地快速读写一些键值对。(LevelUP)

技术栈 对比

常年滋润在JAVA这片润土之上, 先来做个比较, 让我们对陌生的技术栈有所了解。

图表链接
maven
Vert.x
Hibernate/MyBatis/jOOQ
rxjava
npm
expressjs
socket.io
sequelize
promise

ES6

ES6是个好东西, 我觉得比较好用的三点:

const: 终于可以方便地对不可变的东西进行声明了。
let: 作为一只 javascript 菜鸟再也不用担忧不知不觉把变量提升的问题了。
lambda表达式: 神器不解释。

核心实现 流程时序

仔细思考, 勾勒出大致的时序图:

时序图

系统架构:

设计下大致的架构:

架构图

步步为赢, 各个击破 状态管理

典型的 IM 系统中必然存在用户在线离线的状态。每一个在线状态, 对于服务器来说,等价于与客户端存在一个Socket连接。所以对于单机环境下,在内存中维护用户和Socket的关系即可,当Socket连接和断开时分别做更新操作。

当切换至集群环境时,情况变得稍微复杂,所以我们需要借助zookeeper来实现。除了本机每个用户与Socket关联关系,另外以临时节点的方式在zookeeper中进行存储,目录结构为根节点/命名空间/用户标识/Socket标识(临时节点)。 当socket连接被建立的时候,创建对应的临时节点,socket断开时移除临时节点。 当服务器意外退出时,除了socket连接全部断开之外,在其zookeeper session上的所有对应的临时节点也会被销毁。SocketIO 的重连机制会尝试重连至其他伺服器并重新建立起对应关系。

优点:

判断一个用户是否在线只需判断用户标识节点的numChildren是否大于零即可。

获取用户所有已连接的Socket只需读取用户标识下的所有孩子节点即可。

缺点:

多了额外读写zookeeper的开销。

用途:

实现集群的基础

有了状态判定才能实现离线消息推送

好友关系、群组关系

关系表原本存储于HBase, 但因为缺乏事务支持,实际效果不佳, 经常导致关系不一致。传统关系型数据库在这一方面依旧强势。这块比较简单,即常见的关系模型表,在此略过。

点对点聊天实现 单机

A对B发送消息,除了基础的权限判定,只需查询内存表中对应B的所有socket,然后对其发射消息即可。见下图:

集群

由于B可能登录在不同的服务器上,需要借助消息中间件(Redis Pub/Sub),发布消息,每个服务器订阅消息列表,如果存在消息接收者,则找到其注册在本机的socket进行发射消息。流程如下图:

广播聊天实现

广播类似以上的点对点实现,只是多了一步查询成员表依次处理的步骤。略过。

聊天历史记录的实现

考虑到只需要根据时间范围做分页查询的简单需求,这里使用了 HBase 的宽表。 点对点形式的聊天我们可以对两个用户标识进行排序,并结合命名空间生成唯一的哈希值,作为行健,而每个CELL的值则是时间戳, 因为我们需要令其自然倒序排列, 所以针对时间戳做了LONG.MAX-时间戳的处理。综合起来, 大致的存储结构如下:

敏感词过滤

维护脏词字典,对消息进行字符串替换?图样图森破!!! 为了实现正确辨认“曹操在操场操美女”中的动词“操”,需要实现中文分词和词性判断, 于是处理逻辑转变成:

拉取最新的脏词列表,转换为简体中文并写入LevelDB中。

使用nodejieba进行中文分词: 曹操(n)/在(p)/操场(n)/操(v)/美女(n)

对分词后的名词和动词转换为简体中文并查询LevelDB,命中则替换。

返回替换后的字符串得到:曹操在操场*美女

打包部署 PM2

Node本身是单线程的,虽然Node本身提供Cluster模块,但需要修改代码。通过PM2这个工具可以简便地让其多进程部署,充分利用多核CPU资源:

pm2

Docker
可以使用官方的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"]
C1000K测试

著名的单机100万连接, 由于项目是第一个版本, 限于各方面原因我们暂时没有完成这个测试。

但在这里简短介绍所需的一些配置, 以备后续使用:

服务器端

修改tcp连接的最小内存为4k, 编辑/etc/sysctl.conf

net.ipv4.tcp_wmem = 4096 87380 4161536
net.ipv4.tcp_rmem = 4096 87380 4161536
net.ipv4.tcp_mem = 786432 2097152 3145728

修改系统最大文件描述符, 编辑/etc/sysctl.conf

fs.file-max = 1000000

修改进程最大文件描述符, 编辑/etc/security/limits.conf

重载下配置(sysctl -p)或者重启,检查下当前的设置 cat /proc/sys/fs/file-nr

客户端

因为每个IP最多可以创建6万多个连接,不可能找很多服务器进行测试。所以客户端除了上述修改,还需要创建多个虚拟IP,这样每个IP可以提供大约6万的连接,如:

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

修改本地端口范围,编辑/etc/sysctl.conf

net.ipv4.ip_local_port_range = 1024 65535

重载配置或重启开始测试

总结

存在即合理,不要卷入无谓的语言之争,本猿觉得干这行的最重要莫过于学习能力。

写代码之前先理清楚思路和结构,不打没有准备的仗。

良好的代码规范,遵循KISS原则。

本文作者来自 MaxLeap 团队_数据分析组 成员:蔡伟伟

原文链接

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/65719.html

相关文章

  • 微服务横行的今天, 你的文档跟上节奏了么?

    摘要:纳尼隔壁少林派表示自家金刚技压群雄在座各位都是。。。纳尼你觉得写太繁琐了你不喜欢我们还有或者等等一大堆工具呢。纳尼没有你还是觉得无法接受好吧那么笔者推荐类似这类更友好的工具你可以导入导出其他格式也可以使用其来撰写。 说起微服务, 想必现在的技术圈内人士个个都能谈笑风云, 娓娓道来。的确, 技术变革日新月异, 各种工具框架雨后春笋般涌现, 现在我们可以轻巧便捷地根据自己的业务需求, 构建...

    liaoyg8023 评论0 收藏0
  • 农的黑客反击战(三)

    摘要:前言最近服务器被黑客做了肉鸡,前后已经折腾了两次码农的黑客反击战,码农的黑客反击战二,查杀病毒文件,修改服务器默认配置,经过观察发现,依然没有完全清除干净。至此,只能宣布黑客反击战其实算不上反击,只是修复失败。 前言 最近服务器被黑客做了肉鸡,前后已经折腾了两次(码农的黑客反击战,码农的黑客反击战(二)),查杀病毒文件,修改服务器默认配置,经过观察发现,依然没有完全清除干净。具体表现为...

    zhangyucha0 评论0 收藏0
  • 我是一个码农

    摘要:我是一个码农什么是码农百度百科的定义是一般指从事软件开发职位的职员,学不到新技术,同时也是部分从事软件开发工作人员的一个自嘲的称号。我是一个家有果园的老码农。应该做一个写写代码,卖卖水果,也打理自己果园的码农了。 我是一个码农 showImg(/img/bVEenN?w=550&h=306); 什么是码农?百度百科的定义是:一般指从事软件开发职位的职员,学不到新技术,同时也是部分从事软...

    darkbug 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<