默认
发表评论 13
想开发IM:买成品怕坑?租第3方怕贵?找开源自已撸?尽量别走弯路了... 找站长给点建议
IM群消息投递可靠性的技术方案探讨(欢迎讨论)
阅读(40654) | 评论(13 收藏1 淘帖1 1
在讨论群消息的解决方案之前,我们假定6报本 + 离线消息是写扩散的背景下进行探讨

常规流程


我们联想一个场景:一个999人的群,500人在线,499人不在线,用户X在群里发送一条消息。流程如下
1、消息发送到服务器中(生成消息Id、序号ID)
2、根据群ID,查询群用户ID
3、查询该群所有用户的在线信息,得到在线用户列表、离线用户列表
4、设置fallback = 500数,发送消息
    4-1:给所有在线用户发实时消息
    4-2、给所有离线用户写入离线消息
    4-3、给所有离线用户写入推送消息
5、接收反馈(在线其他群成员的ack:R,或者的接入层发送失败反馈)
6、反馈处理
    6-1、收到ack:R(客户端发给接入层,接入层在发给服务层),fallback--
    6-2、收到发送失败反馈(接入层反馈给服务层ack),写入离线消息,fallback--
    6-3、fallback处理之后,如果结果fallback为0,则给用户X发送ack:N
7、等待用户X的客户端重发


存在问题和解决思路



1、给500个在线用户发送消息,服务层到接入层的500次IO
2、给499个不在线用户写入离线消息,发送push,涉及到499 * 2 次IO
3、如果有个别用户处理失败,如有一个接收端的ack:R没有收到,重发时又需要把所有流程走一遍。
4、写扩散指数(这里是999)越大,处理出现故障的概率就越大。重试的次数将特别多次。
那么存在的问题分为两个方面:IO次数写扩散重试成本高


实时消息写扩散优化


       关于实时消息的发送,由于给socket层面是一定要写500次的,这个无法规避;我们可以规避的是服务层到接入层这里的IO次数。假设我们的接入层节点有10个,这500个用户分散在接入这10个节点下。我们拿到这500个用户的在线信息时,同时拿到对应的接入节点信息,进行合并发送。最终达到的效果:发送10次RPC

离线消息写扩散优化


       我们的离线消息是采用redis的zset进行存储,但key是基于用户ID进行隔离的,所以无法通过lua这种方式进行处理。这里我们采用分批 + 异步 + pipeline(redis)的方式进行优化。举例:499个用户,我们分成10批用户,然后打包成10个任务,每任务的逻辑是通过redis的pipeline进行写操作。
推送的优化手段也是类似,但是它的一致性要求就没离线消息高。允许处理失败。

重试优化


        每次重试,所需要触发的流程都一样,假如998个用户都处理成功,只有一个用户失败了。真的是一颗老鼠屎!我们的解决方案是,只给失败的用户进行重试。针对实时消息举例:
fallback从计数的结构改成zset,member是接收端的唯一标识。第一次发送消息时,记录的全量数据。当收到接收者的ack时,进行remove操作,删掉其中的member,并且判断fallback长度,为0标识全都接收完毕。如果运气不好,有个别ack丢失了。在重试的时候,复用当前fallback即可,只针对当前fallback的member进行重发。

优化之后的流程

一个999人的群,500人在线,499人不在线,用户X在群里发送一条消息。流程如下
1、消息发送到服务器中(生成消息Id、序号ID)
2、根据群ID,查询群用户ID
3、查询该群所有用户的在线信息,得到在线用户列表、离线用户列表
4、生成fallback_online, fallback_offline
    4-1:在线用户消息合并发到接入层,接入层逐个发送(根据fallback_online)
    4-2、离线消息批量并行处理(根据fallback_offline)
    4-3、离线推送异步处理(根据fallback_offline)
5、设置客户端消息Id和服务端消息的绑定关系(设置过期时间10min),接收反馈。
6、反馈处理
    6-1、收到ack:R(客户端发给接入层,接入层在发给服务层),删除fallback_online中的member
    6-2、收到发送失败反馈(接入层反馈给服务层ack),写入离线消息,删除fallback_offline中的member
    6-3、fallback处理之后,如果结果fallback长度为0,则给用户X发送ack:N
   
重发流程:
1、消息发送到服务器中(根据客户端消息Id判断是重发)
2、根据群ID,查询群用户ID
3、查询该群所有用户的在线信息,得到在线用户列表、离线用户列表
4、查询fallback_online, fallback_offline,此后就都一样了。









即时通讯网 - 即时通讯开发者社区! 来源: - 即时通讯开发者社区!

上一篇:求教使用tcp传输10G大文件的分包疑问下一篇:求教socket.io库报socket.io.js.map文件泄露漏洞?

本帖已收录至以下技术专辑

推荐方案
评论 13
等我仔细看完后回复你,稍等
不是很理解这句:“3、如果有个别用户处理失败,如有一个接收端的ack:R没有收到,重发时又需要把所有流程走一遍。”。

为啥一条消息失败,所有的用户都要重推?
引用:JackJiang 发表于 2023-06-13 20:56
不是很理解这句:“3、如果有个别用户处理失败,如有一个接收端的ack:R没有收到,重发时又需要把所有流程走 ...

感谢阅读和回复。这里是有两个方案吧,后续确实有做优化处理,但不知道是否合理
引用:JackJiang 发表于 2023-06-13 20:56
不是很理解这句:“3、如果有个别用户处理失败,如有一个接收端的ack:R没有收到,重发时又需要把所有流程走 ...

在重试优化环节的做法是否正确?想和Jack这样的经验大佬交流下
引用:JackJiang 发表于 2023-06-13 20:56
不是很理解这句:“3、如果有个别用户处理失败,如有一个接收端的ack:R没有收到,重发时又需要把所有流程走 ...

嗯,在重发优化小节中确实通过fallback进行了优化,但第一次写,不知道是否是常规的操作。也想学习下Jack这边使用的方案
引用:JackJiang 发表于 2023-06-13 20:56
不是很理解这句:“3、如果有个别用户处理失败,如有一个接收端的ack:R没有收到,重发时又需要把所有流程走 ...

未收到“一个接收端的ack:R” 针对的是服务器来说(在6个报文的背景下接收者未给服务器发送ack:R),被视为消息有人未收到。
触发了发送者的重发流程
引用:黄小贱 发表于 2023-06-13 22:25
嗯,在重发优化小节中确实通过fallback进行了优化,但第一次写,不知道是否是常规的操作。也想学习下Jack ...

对于每一条要发给群成员的实时消息来说,都是原子化的消息,可以对单条消息进行重发和应答管理,跟是不是群消息无关。你发送端的逻辑,应该是该条消息成功发给服务器,ack应答由服务器回发给发送者即可。这样逻辑就简单多,因为职责进行了分段。
引用:JackJiang 发表于 2023-06-14 11:04
对于每一条要发给群成员的实时消息来说,都是原子化的消息,可以对单条消息进行重发和应答管理,跟是不是 ...

嗯,Jack的意思我明白。那是不是可以这样理解:服务器接收到消息之后,群消息的必达,是依赖服务器的重试,而不是依赖发送端的重试?
引用:黄小贱 发表于 2023-06-14 12:07
嗯,Jack的意思我明白。那是不是可以这样理解:服务器接收到消息之后,群消息的必达,是依赖服务器的重试 ...

对,发送者只管送到服务器,余下的分段,由服务器去保证
引用:JackJiang 发表于 2023-06-14 15:08
对,发送者只管送到服务器,余下的分段,由服务器去保证

原来如此,我这个方案背景是由发送者重发来解决的。构思来源于Jack这边的6报文流程,所以思路是 确保群消息的写扩散完全处理完毕,服务器再回复发送者ack:N

http://www.52im.net/forum.php?mo ... B%CF%A2%CB%CD%B4%EF
引用:黄小贱 发表于 2023-06-14 16:22
原来如此,我这个方案背景是由发送者重发来解决的。构思来源于Jack这边的6报文流程,所以思路是 确保群消 ...

文章里的那篇6报文太啰嗦了,那篇不是我写的,我其实是不赞同的
引用:JackJiang 发表于 2023-06-14 17:46
文章里的那篇6报文太啰嗦了,那篇不是我写的,我其实是不赞同的

理解了,那方案上可以进化成4报文。而且服务器只要消息前期处理成功,就可以回复发送者处理成功了。接收者的消息可达方案, 我能想到的是起一个延迟任务,失败了再起延迟任务,直达成功或者次数限制。

Jack这边的重试发起一般建议如何处理?
其实我一直觉得,群这种消息模型,读扩散比较好,没这么多复杂的问题
打赏楼主 ×
使用微信打赏! 使用支付宝打赏!

返回顶部