IM

710 阅读8分钟

什么是IM:IM是一个可以为业务系统提供实时交互能力的模块。

IM架构体系层面 - 二期

image.png

image.png

[客户端]:用户用于收发消息的终端设备。内置于APP的客户端程序和服务端进行网络通信,来承载用户的请求收发。 [接入服务(网关层)]: 用户上线时向网关推送一个全局的uid,网关维护uid与对应实例服务的映射,同时网关机也会维护客户端-channel的映射,服务端通过网关得到对应客户端的channel。客户端发送的消息经由网络先到服务端的接入服务,然后再由接入服务递交给业务层。我们可以把接入服务理解为IM服务端的门户,为客户端提供消息收发的出入口。 接入服务主要有四个功能:连接保持、协议解析、Session维护、消息推送。 [业务处理服务]:消息的业务逻辑处理、消息存储、未读数变更、更新最近联系人。 [存储服务]:账号信息、关系链、消息本身等的持久化。 [外部接口服务]:这是因为有手机操作系统的限制,APP在长时间后台信息时,与IM服务端的连接会被断开。所以我们需要收集提供的SDK第三方接口服务,通过手机操作系统自身的公共连接服务来进行操作系统级别的“消息推送”。

【架构拆分】: 1.接入服务和业务处理服务要独立拆分。 ①接入服务需要维持网络通道,是消息收发的出入口,直面用户,必须要保证高可用、高稳定性。而业务处理服务会随着产品需要的迭代而不断变更,需要频繁重启。所以业务处理层的不稳定会导致用户连接不稳定、消息下推不及时、消息发送流畅性差等,影响用户消息收发的体验。

[针对万人群聊场景]:我们可以不维护全局中央的所有用户的在线状态,而是让各网关自治,维护接入到本网关机的用户连接与群的映射关系。用户A在群中发送消息后,业务逻辑层把这条消息扇出到群接收人的维度,投递到一个全局的消息队列中。每台网关机都要订阅这个群消息的Topic,就都能得到这条消息,然后与它维护的在线用户连接进行匹配,匹配成功后通过长连接推送下去。这样把全局的中央依赖变成了分片的本地依赖,避免了服务端维护全局在线状态的资源开销和压力。 [使用Cache多级缓存](分布式缓存,极客17)

[普通聊天-高可用] 用户通过接入网关进入聊天室,网关会上报用户的在线状态。发送方发送消息后,服务器会通过网关查询用户在线状态,并查询聊天室中的其他用户都在哪台网关服务器,然后把消息投递到网关服务器,网关服务器再通过长连接把消息推送给用户(也可以网关接收到消息后,把消息放入MQ中,服务端使用数据抽取工具周期性地将数据提交给服务器进行持久化,消息接收逻辑与存储逻辑解耦)【而我的设计是维护一个全局的用户-channel,每台网关机都有单独收发的能力。你不觉得你的设计有问题吗?!!!问题看下面的ZK集群】

[直播互动-高峰值] 1.一个直播间的在线用户数量巨大,我们不可能去查找所有在线用户所接入的网关机。所以我们可以在每个网关机都维护一个直播间-用户-连接 的关系,然后维护进入直播间且接入在本网关机用户。然后网关机要订阅一个全局的消息队列。直播间中的用户发送消息时,服务端业务逻辑处理后投递到全局消息队列中,所有网关机都得到此直播间的消息,分发给直播间的在线用户。(普通聊天的消息扇出在业务逻辑层,而直播的消息扇出从业务逻辑层下推到了网关层) 2.微服务拆分:出现单机瓶颈时,只对核心服务扩容,所以需要微服务拆分。所以我们把[短连接接收服务、业务处理服务、DB存储服务]、[长连接推送服务、路由分发服务]、[回放、第三方同步等非核心服务]通过消息队列的方式解耦。在这里我们把长连接的推送服务和短连接的接收服务拆分成各自独立的管道,避免用户上行操作被服务端下行推送通道所影响。 3.自动扩缩容:...超出范围了。但有时候还没来得及自动扩容,服务器就已经扛不住了;而且对于调用外部API接口,依赖接口不可用导致业务被拖慢,影响核心业务的可用性。所以需要流控。 4.流控和熔断:也得RE。 有突发的高热度直播时,我们可以通过流控扔掉一些非核心消息来保证弹幕消息高可用。 流控算法:漏桶算法、令牌算法(Redis+Lua实现流控组件)

[多终端一致性] 1.消息序号生成器。多端多线程并发地收发数据时,保证消息时序的一致性,时序基准+服务端包内整流(相当于分布式ID+帧合并?)【极客05-二期】

2.即用户在任意一个设备登录后都能获取到历史聊天记录,即实现离线消息同步。 需求:①用户在消息索引表中删除了一条之前的历史消息的索引,则我们也需要把这个删除命令同步到离线消息表中,所以离线消息表不能直接使用消息索引表(总不能每次都全量同步吧,而且这仅仅是个命令,没必要持久化到会话维度的消息索引表中,我们应该以单个用户维度来理解离线消息表)②我们并不知道后面还有几个终端要来获取离线消息,所以使用Redis-ZSet一直保存离线消息。那么对于不同终端需要拉取多少离线消息、避免消息的重复拉取和命令的重复执行也成了一个问题,即需要按需拉取。 解决:使用版本号来实现多终端和服务端的同步。 ①为每个用户维护一个版本号。服务端有消息或命令要推送给用户时,会为消息生成一个版本号并一同存入离线存储中,同时更新服务端维护的该用户的版本号。 ②离线用户设备上线时,会提交客户端保存的版本号到服务端,服务端比对版本号并推送增量离线消息。但是,太久不上线离线消息可能会太多。但IM服务端可能出现自增版本号后写离线消息失败的情况,而下一次就成功了,所以会漏写这条离线消息到Redis队列中,客户端也没法感知。所以我们可以在离线消息表中存储上一条消息的版本号,在客户端拉取时要求整体消息前后版本号串联起来。若串联失败则客户端就只能根据联系人表获取第一条消息,然后在客户端打开某个会话时,再批量获取并根据消息ID去重。

[高并发系统设计、RPC实战与核心原理、项目结合算法]

发消息简要步骤:image.png

IM实时性 (1)客户端短轮询立即返回会产生大量的无用网络开销和功耗开销,而且也会降低服务端处理请求的QPS。而客户端长轮询,服务端就需要维持住连接等待请求,会增加后端资源轮询的压力。所以短轮询和长轮询都无法做到基于事件的“边缘触发”,也就是状态变化时发送一个IO事件,所以我们需要服务端来主动推送消息,避免客户端的轮询请求。而服务端并不记录客户端的状态,所以就需要全双工的WebSocket。客户端和服务端只需要一次握手就可以创建持久的长连接。HTTP可以升级为WebSocket。Netty使用WebSocket编解码器就可以自动升级为WebSocket。