1、内容概述
一套完整IM系统中,除开基本的业务设计,消息模型的设计是其中最为关键的一环,它关系到整个IM系统的可靠性、高效性、稳定性,因此需要设计一套合理的消息传递收发机制以承载IM的各种需求,OpenIM结合具体的业务场景,提炼出IM后端的重要接口,并经过反复推敲,验证,设计出了自己的一套通信协议OMTP,协议中每个字段尽量的减少冗余,使其精简高效、最大限度的满足现有的业务需求并能够支持业务的水平拓展,同时为了达到消息的实时可靠与高效,消息模型也经过了精心设计。
(1)消息的实时性:消息的实时性是考验IM系统的重要标准,传统的IM通过http长短轮询的方式来模拟实时消息,存在“实时性盲区”,所以OpenIM也同样使用了HTML5的Websocket技术,Websocket是真正的全双工双向通信技术,能够实现服务器主动推送消息到客户端,实现真正的实时双向通信(服务器通知客户端有信息到达,客户端随时向服务器发送新消息),一次连接,随时都可以使用,不用像轮询技术中不断使用http请求,这样降低了服务器的负载,并减少一些高频无用的请求。
(2)消息的可靠性:消息的可靠性,是指消息的不丢失,不重复,由于网络环境的各种复杂情况,包括数据在到达客户端后,数据需要进行各种解析与处理,包括写入db,存入cache,UI的各种展现等等,而且还包括客户端的各种异常情况,当项目越庞大时候,应用层出现错误的可能性会更高,都可能会造成消息的丢失,通常为了使得消息更为可靠,业务层通常采取的方案有:1、在应用层增加ACK消息,这种方案可以理解为模拟TCP的流程去保证消息的可靠性,server发送数据到达客户端后,客户端处理完毕(通常是保证数据已经存入db中)会向server发送一条ACK消息确认自己已经收到了消息,当server收到ACK消息后才确认消息已经成功交付,否则就会使用某种策略进行消息的重发;这个方案也会出现如果ACK消息发送后,server出于网络的原因没有成功收到,然后server进行了重发消息,客户端会重复的收到了这条消息,所以客户端需要进行去重处理。2、消息增加一条序列号,为每条消息分配一个消息ID(OpenIM便是使用的这种方案),对于每个用户而言,在自己的消息收件箱中,消息的收取队列中seq应该是连续的,如果客户端层面,或者网络原因造成消息没能够实时的到达,客户端可以在应用层增加逻辑采取某种策略通过seq对比,本地的seq和服务器的seq之间的差值去拉取没能正确交付到客户端的消息,这种方案相比于ACK消息来说,不用每一条消息都需要server判定ACK来确认消息是否已经到达客户端,消息的获取获取逻辑更加简单,但是消息的拉取依赖于客户端,如果pull拉取频率过于高,其流量的损耗的也是不低的,所以OpenIM在客户端断网重连,消息拉取策略上做了优化处理以确保消息拉取频率在一个合适的范围。
(3)兼具轻量、高效、低成本:OpenIM设计之初,便是以“一切从简”的原则出发,去除了一些不必要、不合理的设计,使得整个消息通道更加简洁,系统更加轻量级,系统设计使用的是微服务架构方式,每个节点都能够支持集群部署,各个节点之间通过注册中心互连,在集群模式下能够自动的进行负载均衡与流量切换,并且消息通道提供了给应用服务器回调接口,方便应用服务器对于消息的控制,方便其他应用能够高效的接入OpenIM并拓展自己的业务,由于OpenIM是一个开源系统,任何企业和个人都能免费获取源码,并自行构建OpenIM服务,快速搭建私有IM服务器,并迅速让IM功能集成到自己的系统中,数据自我掌握,不用依赖于他人,相比云服务商,极大的降低了获取IM能力所需要的成本。
服务器层面从消息发送到接收的流程大致可以分为两个阶段:
客户端发送消息到服务器阶段
服务器传输消息到客户端阶段
OpenIM消息的发送阶段,也就是消息服务的上行阶段,流程如下:
(1)主要由客户端主动发送消息到服务器,目前所支持的应用层协议主要是http和websocket,为了应对不同客户端的需求,我们在消息网关接口中分别设置了https和wss的接口来作为OpenIM的网关接入层。
(2)接口层依赖于ETCD集群(用于RPC服务的注册和发现)调用RPC共同调用消息处理单元,处理单元处理完毕后将消息放入MQ中,并返回给客户端相应结果,消息只要成功落入MQ中,就可以视为发送成功,消息发送的可靠性依赖于MQ集群。
(3)值得一提的是,在消息的处理单元,我们增加了外部调用接口(由具体的业务服务器实现的http接口),可以实现业务服务器对消息的控制处理,例如消息的禁止发送,过滤等具体的业务需求,这种消息的回调业务服务器的设定方便使用者可以根据自己的业务需求对消息进行处理,设立在服务器回调这种方式也更加安全可靠,可见在OpenIM的上行消息发送阶段,我们通过ACK、集群化部署等方式,来保证消息的发送中数据传输的可靠性。
OpenIM消息的传输阶段(包括将消息交付到接收者客户端或发送者的其他客户端),即是消息服务的下行阶段,流程如下:
(1)上游将拆分处理后的消息放入MQ中,传输阶段主要的任务,是从MQ中拿到消息,并进行持久化存储以及交付push模块。
(2)在消息的存储方面,我们所使用的是收件箱模式,即每个用户都带一个信箱,一条消息发送后,发送者和接收者的信箱都会增加一条信息,这种写扩散的的方式将写进行了放大,它的优点是,对于客户端而言,无论是直接接收数据,还是主动拉取数据逻辑都比较简单,直接获取自己收件箱里面所有的消息,我们主要使用Redis存储消息的seq(每一条消息对于接收者和发送者都会产生一个唯一的递增的消息序列号),使用MongoDB存储历史消息(用户直接拉取的消息),MySQL存储全量的历史消息(漫游消息)。
(3)消息异步交付持久层后,通过RPC调用push模块,为了增加消息的可靠性,我们使用了RPC+MQ双向保证消息推送的到达,当RPC直接调用失败后,会通过MQ中转消息到达push模块。
(4)push模块主要的任务是负责消息的传递(包括接收者多端收到消息,以及发送者多端的消息同步等),主要是通过将从RPC接口中或MQ中获取的消息通过接入网关使用websocket进行消息的在线推送,当用户不在线时,调用三方的离线推送确保消息能够到达客户端。
在本文中主要阐述了OpenIM的特点和在服务器层面上的消息模型架构设计,方便开发者对后端消息的整个流程有一个清晰的认识,在后续的文章中,我们会继续讲解OpenIM客户端层面的消息的架构设计。
源链接:
更多技术文章: