编辑回复: 感谢您的建议,已增加了两张流程图来具体说明一下在离线消息推送过程中服务端整流的实现机制。
作者回复: 具体的量级是多少呀?消息使用场景是写多读少还是读多写少?量级小的话存储用mysql就可以,量级大的话也可以考虑hbase。nio框架用netty就可以,离线缓存buffer可以用redis或者pika,未读数放redis的hash里就可以。手机端mqtt改造一下或者直接json也可以,浏览器端就websocket + json就行。
作者回复: 👍
作者回复: 1. 是的,在最终推送前收集整流。
2. 在线消息由于基本上不会有同时多条消息和信令一起发送的场景,所以这种在服务端就大量乱序的情况比较少,一般不需要进行整流,交给端上就可以了。
3. 基本上理解正确,服务端整流一般用在离线消息推送时,如果上线的这个用户有多条消息,会把这多条消息通过同一个packageID来归类,并且从0自增的给每一条消息打上标记,离线推送的网关机通过这个packageID和seqID来识别这些离线消息的顺序,待消息全部到达后,按照seqID顺序再下推这些消息。但是这里的seqID并不会给到接收端,这里的seqID只是服务端这单次离线消息下推用到的,和接收到没关系。正常的在线消息推送没有这么多并发的情况,没必要整流。
作者回复: 1. 同一个接收人的消息个人感觉没必要用一个线程处理呀,这样还会让服务有状态,如果这个线程挂了后续消息处理的迁移啥的都会比较复杂。对于同一时间有多条消息发送给这个用户的情况,业务上实际也不需要严格区分这几条消息到底哪个先到服务端,只需要保证消息落地存储后时序性固定好就行。
2. 服务器集群规模太大的话依赖服务器时钟也是个问题,所以可以通过”全局的时间相关的序号生成器“来缓解。
3. 服务端整流一般用在离线消息处理的场景,因为这个时候会同时有很多条消息会推送给用户,需要保证时序性。比如一次离线消息的下推共用一个packageId,然后自增的针对每一条消息来绑定一个序号(0 1 2 3 ...),这样下推网关根据packageId来有序收集所有消息,如果序号有缺失就知道有消息丢失了(但至少是有序的),这个时候可以根据业务需要来决定直接下推已收集消息还是放弃这一次离线下推。
作者回复: 比如离线消息推送时,用户的多条消息需要推送,这多条消息在服务端进行多线程处理时可能出现乱序的情况,通过在取离线消息时,给每条消息使用同一个packageId并自增一个seq,那么网关机在最终推送时,就可以根据这个packageId来进行一次整流,保证最终下推时消息的有序。
作者回复: 嗯,也考虑如果有“最近联系人列表”页的需求,需要按照多个群或者多个直播间的最新一条消息的产生先后来排序,这种情况可能还需要考虑使用其他属性来进行全局排序了(比如消息产生的时间戳)。
作者回复: 加了两张流程图来说明一下服务端整流这一块的应用场景和过程,更新后再看下能不能理解。
作者回复: 是这样的,会话内的排序只需要保证会话内序号递增就可以。当然,如果有“最近联系人列表”这种需求,还需要考虑跨多个会话进行排序的情况,这个可能需要其他属性来协助全局排序了。
作者回复: 1. 一致性这个要看具体的应用场景呀,比如说只是关注在一个群里的聊天,当然因果一致性就够了,但IM产品里面类似“最近联系人”的需求,是需要把多个群和多个点对点进行时序倒排的,这个时候只是单个会话维度的因果时序是不够的。
2. 类似于离线消息的同步也不一定需要是顺序自增的呀,而且光是自增是不够的,自增只能保证时序性,不能保证同步时不丢消息。如果要在分布式场景下保证这个ID是“连续自增”实现上是非常困难的。如文中介绍,除了有序ID,还可以通过两个uuid组成的链表方式来保证同步时的顺序和不丢消息。
另外,全局唯一的“时间相关”的消息ID既可以用来去重,当然也可以用于客户端排序呀。
作者回复: 添加了两张流程图来具体说明在离线消息推送过程中服务端整流的实现机制。
作者回复: 多多交流~
这里讲的服务端整流可以参考离线消息的的推送,比如离线消息推送时,用户的多条消息需要推送,这多条消息在服务端进行多线程处理时可能出现乱序的情况,通过在取离线消息时,给每条消息使用同一个packageId并自增一个seq,那么网关机在最终推送时,就可以根据这个
packageId来进行一次整流,保证最终下推时消息的有序。
方案1的思路上没问题哈,考虑下很多IM场景,由于服务端一般是多层的架构,比如业务层,网关层,会涉及到多个进程的处理,中间的流转可能需要经过消息队列,这些过程也可能会导致乱序出现。
方案2的话主要是通用性上可能不是太好,需要区分消息类型啥的,处理逻辑也稍微复杂一些。
实际上,大部分IM场景有了接收端的整流是不太需要服务端整流的,除了服务端可能存在短时间内推送多条连续消息的情况才可能需要服务端进行整流。
作者回复: 全局序号生成器不管是点对点还是群聊,不需要针对会话维度来创建,都是可以共用的。
作者回复: 没问题的,11篇开始会有一个简单的聊天系统的示例,可以边看边试试。
作者回复: 1. 我们自己的实现目前是一条消息一个ack,当然也可以参考tcp的delay ack机制来减少ack。服务端还可以通过支持多条消息打包来减少ack,比如推送离线消息时进行多条消息打包下推。
2. 连续性也可以用两个版本号组成的链表(每条消息携带前一条消息的版本号和当前这条消息的版本号)来检测消息的连续性和时序性。
作者回复: 保证服务端推送是必须有序也是可以的,看具体的需求场景是否真的需要,另外实现成本也需要考虑。接收端整流也可以是如果乱序先不显示而是等一段时间,来尽量避免显示上的跳变。
作者回复: 新补充了两个图,等更新了大家可以看一下哈~
比如离线消息推送时,用户的多条消息需要推送,这多条消息在服务端进行多线程处理时可能出现乱序的情况,通过在取离线消息时,给每条消息使用同一个packageId并自增一个seq,那么网关机在最终推送时,就可以根据这个packageId来进行一次整流,保证最终下推时消息的有序。
作者回复: 如果只是消息推送的话,接收端的整流基本就ok了,但是通道里推送的不仅仅是消息,还有信令(比如删除某一个会话的信令),这种情况服务端整流能够减少消息和信令乱序推送到接收端后导致端上逻辑异常的问题。
作者回复: 单连接单线程的话TCP层的“有序接收”能够保证消息的有序到达。但这种模型的性能和可用性基本不能用在真实业务场景里。