一、从外采即时通信系统到自研
苏宁最早出于办公的需要,考虑到外网访问权限控制及企业数据安全,没有采用 QQ、微信这类公有云部署的聊天软件,而是采购了 IBM 的 ST 在公司内做私有化部署。随着日常办公中企业定制化需求的深入,业务部门对 IM 和 OA 一体化诉求的日益凸显,ST 逐渐无法胜任,于是苏宁自研了一套 IM 系统。
苏宁产品定位的发展历程,从基础通讯,到企业办公、社交一体化,再到纯对内办公用途,最终服务于内同时对外提供一体化工作平台。系统架构也随着产品定位和产品架构的改变而改变,经历了从基于 XMPP 开源框架搭建的 1.0 系统到纯自研的 2.0 系统,直至目前正在研发的办公一体化、多活高可用、组件化、可轻量化灵活部署的 3.0 系统。
二、基于 XMPP 的 1.0 系统
上图所示的是苏宁构建于 XMPP 协议系统的实现原理。在实现上每个独立的 Node Server 都包含完整的业务逻辑模块,服务器和服务器之间均建有长连接通道用于交换报文。通过消息的已达已读回执,来确保消息的可靠传输。
上述实现方式有以下四个主要弊病:
1.由于 Node 之间均需维持长连接,随着服务端的横向扩展,长连接数是 N*(N-1),增长非常快。
2.每台服务器都部署全量的服务,在部署上是种浪费,也不利于单个服务的升级和维护。
3.XMPP 报文按照 xml 格式定义,三种通讯原语 message、presence 和 iq 的无效载荷非常重,按以下所示,即使不携带任何信息,头部至少也是上百字节(1 字符=2 字节),如果用 iq 实现业务层面的心跳,那也是上百字节。
4.XMPP 框架下的标准聊天消息以 id 为关键字,已经在本地的消息,往往会在对服务端的历史消息请求中,再次被拉取,无法做到按需增补、查漏补缺。究其本质,是这种消息结构的设置,无法完成增量比对。
采用 XMPP 协议也有以下三个主要优点:
1.XMPP 有很多开源框架支撑,Ios 端有 XMPPFramework,java 的有 smack。能快速的开发和搭建自己的系统。
2.默认支持 SASL 和 TLS 的通道加密,传输更安全。
3.XMPP 实体的地址称为 Jabber Identifier 或 JID,作用相当于用户 id,其格式为:[node’@’]domain[’/'resource],在组网上也配有协议网关。这就支持了与其他通信系统(如 AIM、ICQ、IRC、MSN Massager、RSS0.9 和 Yahoo Massager)的互通,也可以在架构上支持多域名的部署及互通。
三、自研 2.0 系统
为了解决 1.0 系统的上述问题,同时支持高可用、支持 SaaS 多企业,苏宁开发了自研 2.0 系统。
在系统架构层面:
服务端将接入层 Node Server 做薄,只负责通信接入以及协议解析,使用 netty 作为 NIO 通信层框架,同时将所有业务逻辑归并到业务逻辑层 Center Server,Node 和 Center Server 都可以自由扩展, Node 和 Center Server 建立长连接通道交换数据。
Node 实现协议转换层和兼容 XMPP 连接方式,重构过渡期支持新老客户端互通。
提供单独的 passport,支持 OAuth 方式与其他账号体系联合登陆。
在业务逻辑层面:
支持多企业,苏宁作为一个独立的企业,企业之间互相隔离;
优化了消息归档机制;
部分数据实体增加了版本号,变更拉取更省流量;
消息按会话增加了 long 型序列号,离线消息由推送变成按需拉取,消息状态及已达已阅,通过消息区间来计算实现;
单点登录 SOA;
增加批量接口、合并推送。
高可用的基本要求是无单点故障,基本方法是分层 (分而治之) 与冗余 (失效转移),苏宁采取了以下措施:
在应用层:负载均衡,集群;
在数据层:主从复制,读写分离;
在软件质量控制上:制定代码规范标准,代码控制,自动化测试,支持灰度发布;
在日志与监控上:服务端接入调用链监控,客户端接入稳定性、性能监控。
四、消息的多端同步、终端消息的防乱序防丢失办法
2.0 系统业务逻辑层面的最大变化在于对消息的处理。因为 IM 系统的核心问题就是要解决聊天消息的离线和在线处理,解决好这个,系统问题就等于解决了一半。
我们想到的是两点:
1.移除离线缓冲,变离线消息推送为上线后终端的按需拉取,确保离线消息在各个客户端上均能被同步、按需补充。
2.作为配套算法,解决在线消息推送至终端时展示乱序的问题。
为了做到这个我们的技术方案为:
1.除消息的 uuid(全局唯一标识)和 ts(时间戳)以外,给消息定义 long 类型的 seq(序列号),按会话独立编号,编号从 1 开始必须连续。服务端需保证任意两条同一会话内的消息,seq1 > seq2、ts1 > ts2 互为充要条件;
2.终端上线后,由终端主动获取最近会话列表,从而得到每个会话的最大 seq。在某个会话需要展示消息时,检查该会话当前的本地消息区间,向消息服务端请求本地缺失的消息,请求以 seq 区间为参数发起批量请求。
3.终端在收到推送消息后,如果推送消息的 seq 与会话的最后一条消息的 seq 不连续,则先把消息加入到对应的缓冲窗口,并启动定时器。在收到后续的推送消息时,先检查缓冲窗口,把连续的一批消息退出窗口并写入数据库、展示给用户。如果超过定时器的时长,消息 seq 还未接续,则说明消息在传输过程中发生丢失,不再等待,一次性把窗口中的消息写入数据库并展示。丢失消息的补充,则通过前述的终端主动拉取来实现。
苏宁与业内典型的离线消息处理方案的差别如下图:
1.移除了离线缓冲和配套数据库
2.流程 1 改为请求最近会话的最大 seq
3.流程 3 返回最近会话的最大 seq
4.流程 4、6 改为上述技术方案图示的按需拉取
五、对于高性能移动端架构的一些想法
由于用户对 IM 系统的体验是通过终端来感知的,所谓的高性能,最终还是要靠终端的算法来完成。
而具体到高性能的移动端设计这块,我觉得主要弄好以下几点:
1.需要独立的组件用于支撑登陆账号和网络协议这块。
2.在协议层上构建逻辑层,对于移动端的上传下载、接口调用、内部算法调用,都要有统一的线程池管理。其他服务,如断线重连、心跳服务,数据库 DAO 服务,缓冲服务等都在逻辑层统一封装和控制。
3.服务端推送的变更,要先在数据库生效,再通知 UI 层生效,走消息总线通知。通知尽量细分颗粒度,变更的数据携带在通知中,避免通知接收方再去查数据库。
4.如果数据库使用的是系统默认的 SQLite,要避免在主线程直接进行任何数据库操作(增删改查),因为 SQLite 并发锁的颗粒度是文件级别的,会导致后台线程的数据库操作阻塞前台的。一方面,我们要识别和优化端上最慢的数据库操作的耗时,另一方面,全在后台操作数据库,界面就不会随机卡顿。
5.在 UI 层还涉及到要防跳帧(用户可感知的 60 帧/秒–16ms/帧)。这需要持续关注和简化各个 UI 布局的嵌套层次,从而减少布局尺寸计算和渲染的开销。另外在 android 端需要特别关注内存抖动引发的跳帧:避免高频调用的方法中分配局部大对象频繁触发大 GC,从而挂起主线程造成跳帧。
6.没有 6 了,一时大脑堵塞了。
六、正在研发的 3.0 系统
移动办公已成为企业提效、降成本的有效手段之一,在企业建设移动信息化的过程中,存在以下建设痛点:
内部沟通渠道混乱,信息外泄风险大
移动业务繁多,使用和管理混乱
信息系统孤岛现象严重,相互隔离
缺乏通用办公应用及个性化应用开发能力
为此苏宁正在研发办公一体化的 3.0 系统,具体的产品和技术架构稍后公司会通过正式渠道对外公布。
七、结语
个人技术面偏移动端架构,涉及服务端的实现可能阐述的不够透彻,唯希望能以有限文字多少给后来者提供些借鉴吧,谢谢。
作者简介
陈思佳,苏宁科技集团移动端资深架构师,主要负责苏宁移动通讯、IoT 、一体化工作平台等项目研发工作。在移动端架构方面拥有十多年的实践经验,先后就职于华为无线产品线、Motorola GSG(Global Software Group)。精通 Android、iOS 编程架构、开发工具,对 IM 协议及架构也有深厚积累。
赵一唯,苏宁科技集团服务端资深架构师,主要负责易适配、邮件系统、移动办公一体化平台等项目研发工作,并为 IM 产品线技术第一负责人。在分布式架构方面拥有十多年的实践经验,先后就职于 ZTE 中兴通讯、趋势科技、IBM 等公司。精通 java 服务端编程,对多活、高可用的解决方案有独到见解,在 IM 协议及架构方面也有深厚积累。
更多内容,请关注前端之巅。
评论