网上有很多对于 IM 的教程和技术博文,有亿级用户的 IM 架构,有各种浅谈原创自研 IM 架构,也有微信技术团队分享的技术文章,有些开发者想依据这些材料自研 IM。现实很饱满,事实很骨感,最初做进去的产品很难达到商用规范。事实上,很多架构没有通过海量用户的考验,当然咱们也不会评判某种架构的好坏,如果开发者希图依据网上教程做出一个商用的 IM,可能有点过于乐观了。本文次要从我集体角度深度分析 100% 开源的 OpenIM 架构。当然,世界上没有最完满的架构,只有最合适的架构,也没有所谓的通用计划,不同的解决方案都有其优缺点,只有最满足业务的零碎才是一个好的零碎。而且,在无限的人力、物力,综合思考工夫老本,通常须要做出很多衡量。咱们 OpenIM 的设计初衷,充分考虑了中小企业的需要,轻量级部署,同时也反对集群扩大,能反对几万用户,也能轻松扩大到上亿用户,是一个可信赖的开源我的项目。
IM 零碎技术挑战
可靠性
IM 音讯零碎的可靠性,通常就是指音讯投递的可靠性,即咱们常常听到的“音讯必达”,通常用音讯的不失落和不反复两个技术指标来示意。确保音讯被发送后,能被接收者收到。因为网络环境的复杂性,以及用户在线的不确定性,音讯的可靠性(不失落、不反复)无疑是 IM 零碎的外围指标,也是 IM 零碎实现中的难点之一。总体来说,IM 零碎的音讯“可靠性”,通常就是指聊天音讯投递的可靠性(精确的说,这个“音讯”是狭义的,因为还存用户看不见的各种指令和告诉,包含但不限于进群退群告诉、好友增加告诉等,为了不便形容,统称“音讯”)。
从音讯发送者和接收者用户行为来讲,音讯“可靠性”应该分为以下几种状况:
(1)发送失败,对于这种状况 IM 零碎必须要感知到,明确反馈发送方。如果此音讯没有发送胜利,发送方能够抉择重试或者稍后再试。
(2)发送胜利,如果接管方处在“在线”状态,应该立刻收到此音讯。如果接管方处在“离线”状态不能收到音讯,一旦上线则立即收到音讯。
(3)音讯不能反复,用数学术语示意:“有且仅有这条音讯”,如果反复了,可能表白的意思就变了。
总之,一个商用 IM 零碎,必须蕴含音讯“可靠性”逻辑,能力谈根本可用,这是 IM 零碎最根本也是最外围的逻辑。
有序性(一致性)
IM 零碎中,特地须要思考音讯时序问题,如果后发送的音讯先显示,可能重大扰乱聊天音讯所要表白的意义,会造成聊天语义不连贯,引起误会。音讯的时序性,也称为音讯收发一致性,次要指标是:保障聊天音讯的相对时序。IM 零碎中音讯时序的一致性问题看似简略,实则是十分有难度的技术热点话题之一。为什么会呈现时序问题 1、分布式系统的呈现导致时序不统一。IM 零碎模块泛滥,接入层、音讯逻辑层等、每层都分布式集群化,这些利用散布在不同的机器上,如何保障时序是个难点。2、网络传输提早导致时序不统一。不同用户发送的音讯达到服务器的延时差别较大,给音讯时序性带来挑战。
音讯时序是分布式系统架构设计中十分难的问题,一个分布式的 IM 零碎必须要解决这个问题,如何高效、低成本解决这个问题,是咱们 OpenIM 要思考的方向。
实时性
实时性,即音讯实时达到接管方,如果用户在线,则实时可达,如果用户不在线,则登录时可达。因为网络稳定,以及挪动端操作系统对利用前后台切换的治理,如何实现用户连贯治理、音讯实时推送,推送失败的解决形式,客户端重连机制,音讯如何补齐等,都是须要 IM 零碎思考,同时要联合挪动端的特点,兼顾耗电量,网络,性能等。因为 TCP 开发稍微简单,晚期的基于 HTTP 短轮询、长轮询的低效的技术计划,也无奈达到实时性的要求。
扩展性
一般来说互联网零碎的扩展性蕴含多个含意,咱们偏重解说对于 IM 音讯的扩展性。IM 业务个性多,功能丰富,从聊天类型来看,分为:单聊、群聊,聊天室等;从音讯类型来看,分为:文本、图片、视频、地理位置、自定义音讯等;从音讯性能来看,分为:撤回、在线状态、对方正在输出、阅后即焚等;从告诉角度来看,分为:进群、退群、增加好友、验证好友等各种告诉。如何无效撑持、扩大性能,高效实现,是考验 IM 扩展性的一个方面,也是对系统架构设计能力的考验。为了更好地进步数据通道对业务撑持的扩展性,咱们独创了“所有皆音讯”的音讯模型,即通信单方产生的所有音讯、告诉,服务端以音讯对立解决,表演了音讯通道的角色,客户端针对不同音讯类型做不同的 UI 展现,完满解决了扩展性问题。
IM 零碎术语以及本文档专有名词解释
conversationId:会话 Id,会话是指用户和用户之间,以及用户和群之间,进行通信后产生的关联。
userId:用户 Id:注册应用 IM 的用户 Id,从音讯的发送和接管来看有两个身份:发送者和接收者
sendId:音讯发送者 Id
receiverId:音讯接收者 Id
msg:音讯是指用户之间的沟通内容,个别指用户被动产生的。同时也包含用户看不见的各种指令和告诉,包含但不限于进群退群告诉、好友增加告诉等
inbox:用户收件箱,给某人发送音讯,实际上是往接收者“信箱”写入音讯,这个信箱就是收件箱
seq:用户收件箱中音讯序列号,分为 local seq,和 server seq,前者示意 app 本地音讯 seq,后者示意服务端音讯 seq,seq 是间断且递增的。
conn:登录用户的连贯信息,用于音讯推送;
MQ:音讯队列,个别用来解决利用解耦,异步音讯,流量削峰等问题,实现高性能,高可用,可伸缩和最终一致性架构,本文采纳 kafka 组件。
点击学习 IM 技术文章
1. OpenIM 官网
2. 基于 Tablestore Timeline 的 IM(即时通讯)音讯零碎架构 – 架构篇
OpenIM 的诞生
随着挪动互联网的蓬勃发展,IM 作为一种通信能力,曾经成为互联网上的基础设施,也是许多 APP 不可或缺的性能。如何让每一个利用都具备 IM 性能,同时思考企业的接入老本、服务器资源以及最重要的数据安全性和私密性。自己从微信到职后,开办了开源 OpenIM,是寰球首家 100% 开源、收费我的项目,并提供 IMSDK,笼罩所有支流开发平台,iOS、Android、Flutter、react native、Windows、Linux、Unity、web、小程序等。
开源 IM 现状
github 上 IM 开源我的项目不少,但开发者却难以使用,次要有几点起因(1)集体我的项目居多,但近几年都无人保护,遇到问题无人解决,企业商业化产品不敢冒险应用(2)大部分我的项目不是 IM 技术业余团队实现的,技术实力和技术架构存疑,也没有通过大我的项目和海量用户测验;(3)只开源服务端或者客户端,只开源某一端,须要开发者实现另外一端,研发老本同样不小,另外,开源我的项目大部分都是以聊天 app 模式开源,开发者如何把 IM 集成到本身 app 中,同样存在大量的批改和适配老本。(4)局部我的项目打着开源的旗号,社区版收费,但外围性能缺失,商业版免费。
云服务商的弊病
IM 云服务商提供 IM SDK 和 API,让开发者简略集成 IM 性能,当然这里也存在显著的问题(1)老本问题:企业每年额定领取上万乃至数十万的云服务费用,从长期来看是个不小的老本;(2)数据隐衷问题:企业的用户数据、聊天记录等外围数据托管在 IM 云服务商,如何保障客户的数据隐衷和安全性;(3)需要定制问题:IM 需要多样化,IM 性能只能由 IM 云服务商通过 SDK 的模式提供给大家应用,开发受限,所有性能都须要封装成接口;(4)捆绑问题:一旦应用 IM 云服务,造成捆绑关系,迁徙老本高,受制于人。
自研的难堪
IM 是一个看起来门槛很低的我的项目,网上有很多所谓的 IM 开发教程,甚至很多毕业设计也是做一个 IM 零碎。因为这个误区的存在,很多企业盲目乐观组建 3-5 人的 IM 团队,历时一年半载,最初只实现了一个 demo 版本。因为架构设计不合理,demo 版本存在音讯失落、零碎异样等 bug,无奈达到商用的要求。IM 零碎除了面临互联网业务零碎自身的挑战,还存在上文剖析的可靠性、时序性、扩展性等问题,所以,自研 IM,对于中小企业来说,可能是最蹩脚的抉择。
OpenIM 的整体架构
OpenIM 分为两大块
(一)Open-IM-SDK-Core 采纳 golang 实现客户端逻辑,次要负责本地 db 存储及更新;断网重连及治理;音讯及各种告诉回调。本地音讯、会话等数据存储,通过告诉机制实现本地数据实时同步,同时兼顾客户端缓存的作用,无效缓解了服务端压力。另外,golang 跨平台的个性,使得各挪动平台都能无缝调用,开发者只需依据产品需要编写 UI 界面,通过回调机制和 SDK 实现数据交互和告诉。
(二)Open-IM-Server 由接入层、逻辑层和存储层组成,益处在于各层可能根据业务特点专一于本人的事件,进步零碎复用性,升高业务间的耦合。
(1)接入层:音讯通过 websocket 协定接入,其余业务通过 http/https 协定提供 REST API 实现。音讯是高频及外围性能,通过双协定路由,体现了轻重拆散的设计思维。
(2)逻辑层:通过 rpc 实现无状态逻辑服务,易于平行扩大,模块通过 MQ 解耦。
(3)存储层:redis 存储 token 和 seq;mongodb 存储离线音讯,并定时删除 14 天内(可自行配置)数据;mysql 存储全量历史音讯以及用户相干材料。数据分层存储,充分利用不同存储组件的个性。
(4)Etcd:服务注册和发现、以及分布式配置核心。
音讯网关 msg_gateway
音讯接入层,采纳 websocket 协定接入,import gorilla 具体实现,服务模块无状态,柔性伸缩,运维简略。通过 MQ 让业务模块之间解耦,音讯写入 MQ 即示意发送胜利。
(1)负责用户连贯治理,放弃长连贯,存储 uid->conn 映射关系;
(2)负责音讯接管落地,胜利写入 MQ 后给客户端返回胜利;
(3)负责把音讯推送给在线状态的接收者;
下图是客户端发送音讯流程
音讯转发 msg_transfer
音讯解决 rpc,作为消费者从 MQ 中生产(读取)音讯,递增接收者收件箱 seq,关联 seq 和 msg,并存储到 mongodb。全量历史音讯无收件箱概念,音讯作为流水记录落地 mysql 即可,两者通过协程独立解决,单方互不影响。msg 作为无状态服务节点,如果音讯量减少,能够启动冗余节点服务,放慢音讯解决流程。
(1)负责生产 MQ 中的音讯,作为消费者,实时感知新信息达到,并触发回调逻辑;
(2)生成 msgId 作为全局音讯 Id;
(3)读取 receiver userId,并通过 redis 的 incr 操作递增服务端对应的 seq;
(4)关联 seq 和 msgid,并存入以 receiver userid 为 key 的 mongodb 中,作为离线音讯,个别在 14 天后会删除;
(5)同时,把音讯作为历史记录存入 mysql 中,作为音讯备份,或其余用处。
(4)和(5)是两个独立的协程并行执行的,mysql 写入快慢不会影响 mongodb 的写入,这样既实现了冷热数据拆散,也充分利用了机器资源。
下图是音讯解决入库流程
音讯推送 push
msg_transfer 实现存储音讯到后,向 push 发动音讯推送工作,msg_gateway 查问本地 userId->conn 表,如果用户在线则推送给接收者,对于 msg_gateway 的推送架构设计,做成了“半状态”服务,即在节点本地存储了用户连贯信息,作为部分信息,没有通过 redis 全局共享。push 推送音讯时,向所有 msg_gateway 发送推送申请,带来肯定的“惊群效应”,因为 msg_gateway 节点不多,所以影响无限,带来的益处则是在不影响性能的前提下,msg_gateway 设计和实现简略,运维也更简略。
(1)msg_transfer 把音讯写入 mongodb 后,发送 push 音讯推送申请;
(2)push 提供 rpc 推送服务,通过 etcd 找到所有注册的 msg_gateway,并发送推送申请;
(3)msg_gateway 从本节点内存中查问 userId->conn,如果找到 conn,则向客户端推送音讯;
(4)如果音讯接收者不在线,msg_gateway 无奈推送音讯,但客户端网络重连时会及时同步历史音讯,进行音讯补齐;
下图是音讯实时推送流程:
音讯同步及对齐 seq
因为网络的稳定以及负责的网络环境,导致音讯推送存在不确定性。OpenIM 采纳 local seq 和 server seq 音讯对齐,同时联合拉取和推送的形式,简略高效地解决了音讯的可靠性问题。这里分两种场景进行表述:
(1)客户端接管推送音讯时,比方客户端收到推送音讯的 seq 为 100,如果 local seq 为 99,因为 seq 递增且间断,所以音讯失常显示即可。如果 local seq 大于 100,阐明反复推送了音讯,摈弃此音讯即可。如果 local seq 小于 99,阐明两头有历史音讯失落,拉取 (local seq+1, 100) 的音讯,进行补齐即可;
(2)用户在登录、或者断网重连时,客户端会从服务端拉取最大 seq(max seq),读取客户端本地 seq(local seq),如果 local seq 小于 max seq,阐明存在历史音讯未同步的状况,调用接口同步本身收件箱 [local seq+1, max seq] 的数据实现音讯对齐。
下图是音讯同步流程图
本文次要简略论述了 OpenIM 的架构以及音讯流程,让开发者对其有初步意识,在接下来的文章中,咱们会具体解说 OpenIM 服务端音讯架构,OpenIM 客户端架构,同时会详细分析 OpenIM 如何简略高效解决音讯的可靠性、实时性、一致性和扩展性问题。