主要模块
客户端:一般用于用户收发消息的终端设备,内置的客户端程序和服务端进行网络通讯,用来承载用户的互动请求和消息接收到功能,
接入服务:接入服务是服务端的门户,为客户端提供收发消息的出入口,发送消息一般客户端发送消息到接入服务,然后再由接入服务交到业务层进行处理,
接入服务分为,连接保持、协议解析、session维护和消息推送一般使用的socket保持长连接
连接保持:一般我们使用socket保持长链接
协议解析:提供协议的编解码工作,为了节省流量系统会对传输的内容进行紧凑的编码(protobuf)
session维护:标示用户在那个tcp连接(socket可以基于tcp连接来实现通讯),
消息退送:把消息从服务器传输到用户设备上
业务处理服务:业务处理服务是消息业务逻辑处理层,对于的是服务器,如消息的存储,未读数变更等,包含存储服务,外部接口服务,
存储服务:数据的持久化存储,在服务器储存的好处;
三方外部接口服务:当app没有打开或者在后台运行,消息给到第三方外部接口服务,来通过手机操作系统自身的公共连接服务来进行操作系统级的“消息推送”,通过这种方式下发的消息一般会在手机的“通知栏”对用户进行提醒和展示
实时性 :立马接受到消息,怎么保证消息的实时性是要解决的问题?
可靠性:分为两块
1.不丢消息:发送的消息不会丢失
2.消息不重复:消息不会重复发送
一致性:同一条消息在不能端接受的消息顺序是一致的。怎么解决消息的一致性? 消息序号生成器
安全性:消息数据的安全,“数据传输安全” “数据储存安全”,“消息内容安全”,怎么保证消息的安全性?
整体分为:制订好消息内容、消息存储、未读消息的存储,需要建立高效的实时消息收发通道等
消息存储:历史消息或者用于暂存离线消息,都需要对消息进行服务端存储。也会根据业务进行本地存储,
消息内容:对消息进行分类(根据业务进行划分)
消息发送通道:一般有两种
接受消息通道
结构图
IM服务器端和消息接受设备之间维护一条tcp长连接(准备使用websocket)tcp全双工能力,可以同时接受和发送数据。当对放不在线的时候,可以通过三方操作系统辅助通道,重点在于维护可靠的长连接,可靠的长连接,
消息未读数:如果消息接收方当前不在线,还可以通过第三方操作系统级别的辅助通道,来实时地将消息通过手机通知栏等方式推送给接收方。但是三方服务通道受限制比较大,
总结
为了解决消息的实时到达问题
短轮询场景:早起使用的方式,一般是"请求响应式"的模式,y一般的请求都是这种请求响应式,不太适合实时性要求比较高的场景,采用"短轮询",来定期、高频地轮训服务器的消息会给服务器造成的压力比较大,而且即费电又费流量
长轮询场景:短轮询模式下,服务端不管本轮有没有新消息产生,都会马上响应并返回。而长轮询模式当本次请求没有获取到新消息时,并不会马上结束返回,而是会在服务端“悬挂(hang)”,等待一段时间;如果在等待的这段时间内有新消息产生,就能马上响应返回。这种场景用于对实时性要求比较高,但是整体用户量不太大。但是长轮询还是有问题的,长轮询在超时时间内没有获取到消息时,会结束返回,没有解决无效请求,而且对后段的压力并没有减少
不管是短轮询还是长轮询都是基于HTTP 协议实现的,由于 HTTP 是一个无状态协议,同一客户端的多次请求对于服务端来说并没有关系,也不会去记录客户端相关的连接信息。
WebSocket:WebSocket 是一种服务端推送的技术代表,不同于轮训的客户端推送,基于 WebSocket 实现的 IM 服务,客户端和服务端只需要完成一次握手,就可以创建持久的长连接,并进行随时的双向数据传输。当服务端接收到新消息时,可以通过建立的 WebSocket 连接,直接进行推送,真正做到“边缘触发”(当状态变化时,发生一个 IO 事件),也保证了消息到达的实时性。
WebSocket 的优点是:
消息的可靠投递主要是指:消息在发送接收过程中,能够做到不丢消息、消息不重复两点。
第一种:用户 A 发消息是一个“请求”和“响应”的过程,如果用户 A 在把消息发送到 IM 服务器的过程中,由于网络不通等原因失败了;或者 IM 服务器接收到消息进行服务端存储时失败了;或者用户 A 等待 IM 服务器一定的超时时间,但 IM 服务器一直没有返回结果,那么这些情况用户 A 都会被提示发送失败。
第二种:消息在 IM 服务器存储完后,响应用户 A 告知消息发送成功了,然后 IM 服务器把消息推送给用户 B 的在线设备。在推送的准备阶段或者把消息写入到内核缓冲区后,如果服务端出现掉电,也会导致消息不能成功推送给用户 B。这种情况实际上由于连接的 IM 服务器可能已经无法正常运转论。即使我们的消息成功通过 TCP 连接给到用户 B 的设备,但如果用户 B 的设备在接收后的处理过程出现问题,也会导致消息丢失。比如:用户 B 的设备在把消息写入本地 DB 时,出现异常导致没能成功入库,这种情况下,由于网络层面实际上已经成功投递了,但用户 B 却看不到消息。所以比较难处理。
对于消息丢失会有两种方案:
业务层 ACK 机制:在 TCP 协议中,默认提供了 ACK 机制,通过一个协议自带的标准的 ACK 数据包,来对通信方接收的数据进行确认,告知通信发送方已经确认成功接收了数据。那么,业务层 ACK 机制也是类似,解决的是:IM 服务推送后如何确认消息是否成功送达接收方。具体实现如下图:
IM 服务器在推送消息时,携带一个标识 SID(安全标识符,类似 TCP 的 sequenceId),推送出消息后会将当前消息添加到“待 ACK 消息列表”,客户端 B 成功接收完消息后,会给 IM 服务器回一个业务层的 ACK 包,包中携带有本条接收消息的 SID,IM 服务器接收后,会从“待 ACK 消息列表”记录中删除此条消息,本次推送才算真正结束
ACK 机制中的消息重传
消息推给用户 B 的过程中丢失了怎么办?比如:
解决这个问题的常用策略其实也是参考了 TCP 协议的重传机制。类似的,IM 服务器的“等待 ACK 队列”一般都会维护一个超时计时器,一定时间内如果没有收到用户 B 回的 ACK 包,会从“等待 ACK 队列”中重新取出那条消息进行重推
消息重复推送的问题
如果在一定时间内没有收到 ACK 包,就会触发服务端的重传。收不到 ACK 的情况有两种,除了推送的消息真正丢失导致用户 B 不回 ACK 外,还可能是用户 B 回的 ACK 包本身丢了
一般的解决方案是:服务端推送消息时携带一个 Sequence ID,Sequence ID 在本次连接会话中需要唯一,针对同一条重推的消息 Sequence ID 不变,接收方根据这个唯一的 Sequence ID 来进行业务层的去重,这样经过去重后,对于用户 B 来说,看到的还是接收到一条消息,不影响使用体验。
补救措施:消息完整性检查
针对服务器宕机可能导致的重传失效的问题我们来分析一下,这里的问题在于:服务器机器宕机,重传这条路走不通了
有剋TCP 协议本身的 ACK 机制为什么还需要业务层的ACK 机制?:
总结一下就是: 发送方的应用层程序,调用send()方法返回成功的时候,数据实际是写入到了TCP的发送缓冲区,而非已经被接收方的应用层程序处理。怎么办呢?只能借助于应用层的ACK机制。
消息的一致性是很重要的,在我们的聊天过程和后续聊天记录的保存都需要保证正确的顺序,
解决网络的不确定性
我们通过长连接来实现投递,“长连接”底层使用的 TCP 连接并不是一个真正存在的物理连接。我们需要用心跳机制维护,当网络出问题的时候,可能还维护这长连接,
心跳机制:TCP Keepalive ,应用层心跳,智能心跳,智能心跳。IM 都采用了应用层心跳方案来解决连接保活和可用性探测的问题