一、导言
要实现一套完整的即时消息群聊,能够在大量用户和高并发场景下使用,其技术难度远远超过即时消息系统中的其他功能。原因是即时通讯群聊消息的实时书写和传播特性带来了一系列技术问题。
例如,在2000人的人群中,发送普通信息的问题将从即时通信扩展到接收2000条信息的问题。如何确保及时、有序和有效地传递这些信息涉及太多的技术问题,更不用说个别场景中10,000人的爆炸问题了。
这就是为什么在一般的大中型即时通信系统中,采用群聊来考虑体系结构设计,并对体系结构进行单独优化,从而降低了整个系统的设计难度。
本文将分享一套在生产环境下高可用性、可扩展性和高并发性的即时消息群聊系统的架构设计实践,属于原创的第一手资料,内容更加专业,适合有一定即时消息架构经验的后端程序员。
推荐:如果你感兴趣,作者的另一篇论文“一套原创的分布式即时通讯系统理论架构方案”也适合正在学习即时通讯系统架构设计的学生。
此外,以下关于即时消息实际开发的文章也值得一读。如果你感兴趣,你可以看看:
适合初学者:从头开始开发即时消息服务器(基于Netty,带有完整的源代码)
"拿起键盘是干的:和我一起开发一个分布式即时通讯系统. "
自己开发即时通讯有这么难吗?手工教你一个Andriod版本的简单即时消息(带源代码)
安卓即时通讯智能心跳算法的设计与实现探讨(含示例代码)
教你用Netty实现网络通讯程序的心跳机制和断线重连机制
“WebSocket实时聊天室演示:基于node . js+Socket . io[附件下载]”
第二,群聊技术文章
即时消息群聊消息是一份拷贝(即扩散阅读)还是多份拷贝(即扩散写作)?》
如何实现即时通讯群聊信息的阅读回执功能?》
即时通讯群聊信息混乱的探讨
现代即时通讯系统中聊天信息同步存储方案的探讨
如何保证在移动即时通讯中推送大规模群组消息的效率和实时性?》
微信后台团队:微信后台异步消息队列的优化升级实践共享
即时消息群聊新闻如此复杂,如何确保它不丢失或沉重?》
我应该使用“推”还是“拉”来同步即时消息单聊和群聊的在线状态?》
如何确保即时消息的“时间”和“一致性”?》
“快速裂变:见证微信强大后台架构从0到1的演变(一)”
第三,一开始一切都很困难:最初的最低限度的实现
所谓的群聊消息系统是一种多对多群聊模式。例如,与直播房间中的聊天室相对应的服务器端是群组聊天消息系统。
2017年9月初,我们初步实施了一个最低限度的群组聊天信息系统,其总体结构如下:
一套高度可用、可扩展和高度并发的即时消息群聊和单一聊天架构设计实践_1.gif
系统名词解释:
1)客户端:消息发布者[或服务器群聊天消息系统调用者],发布者;
2)代理:系统代理,统一外部接口,收集客户端发送的消息并转发给代理;;
3)代理:系统消息转发服务器。代理将组织一个房间网关列表【根据网关消息的密钥【密钥是房间号,值是网关IP:端口地址列表】,然后将代理发送的消息转发到房间中所有成员登录的所有网关;
4)路由器:用户登录消息转发器将网关转发的用户登录和注销消息转发给所有代理;;
5)网关:所有服务器的入口,接收合法客户端的连接,并将客户端的登录和注销消息转发给所有代理;通过路由器;
6)房间信息:房间聊天信息;
7)网关消息:房间中的成员登录或注销网关消息,包括用户UIN/房间号/网关地址{IP:端口}等消息。
当一个房间中的多个客户端连接到网关时,代理将根据RoomID仅将房间中的消息转发给网关一次,网关将制作消息的多个副本,并将它们发送给连接到网关的房间中所有用户的客户端。
该系统具有以下特点:
1)系统只转发房间内的聊天消息,各节点收到后立即转发,不在房间内存储任何聊天消息,不考虑消息丢失和消息重复的问题;
2)系统由一个代理、三个代理和一个路由器组成;
3)代理接收后端发送的房间消息,然后根据一定的负载均衡算法将消息发送给代理,代理将消息发送给与房间网关相关的所有接口机器;;
4)路由器接收网关转发的某个房间成员的注销或登录消息,然后将该消息发送给所有代理;;
5)在接收到路由器转发的网关消息后,代理更新(添加或删除)与房间相关的网关收集记录;
6)整个系统的通信链路采用UDP通信方式。
从以上特点来看,整个消息系统足够简单,没有考虑容量的扩展和收缩问题。当系统负载达到极限时,系统将被重新部署以应对后端客户端的消息压力。
这种处理方法的本质是放弃后端客户端和前端网关的系统扩展能力:每次系统扩展时,所有客户端都需要在本地配置文件中添加一个代理地址,然后全部重启,而所有网关都需要在本地配置文件中添加一个路由器地址,然后全部重启。
这种“我很快乐,我为成千上万个家庭努力工作”的扩展应对方式将不可避免地导致公司内部使用该系统的用户的抱怨,下一阶段的升级是不可避免的。
第四,进一步关注设计:“可扩展性”
4.1、基本理念
大道之旅也是为了公众,不同的系统有不同的架构,相同的系统总是有相似的实现。类似于数据库子数据库子表[关于子数据库子表,目前看到的最好的文章是一个副本扩展方案,支持自由规划,无需数据迁移和修改路由代码]。其扩展实现的核心思想是分区和副本,但副本之间还有两种机制:领导者(领导者-追随者(只有领导者可以接收写请求)和非领导者(所有副本都可以接收写请求)
从数据的角度来看,该系统接收两种消息:房间消息和网关消息。两种消息的交汇点是代理,所以处理扩展的关键点是代理。代理的每个分区采用非领导机制,每个副本可以接收网关消息写请求和房间消息转发请求。
首先,当房间消息的数量增加时,代理可以水平扩展,并且代理的多个部署可以应对房间消息的流动。
其次,当网关消息数量增加时,路由器可以水平扩展,更多的路由器可以处理网关消息的流量。
最后,在两个消息相遇的地方,代理如何扩展?您可以将多个代理副本放入一个分区中,因为网关消息在一个分区中广播,并且所有代理副本将具有相同的RoomGatewayList数据,所以当网关消息增加时,可以扩展该分区。当房间消息的数量增加时,水平扩展分区中的代理副本就足够了,因为房间消息将只发送给分区中的副本。
根据个人经验,在一段时间内,房间标识和房间成员数量的增加可视为线性增加,而房间消息可能呈指数增长,因此如果设计得当,分区扩展的概率非常小,而副本在分区中水平增长的概率几乎是100%。
无论是分区级别的水平扩展还是分区副本级别的水平扩展,都不可能像系统的最小版本那样在每次扩展后更新配置文件并使用客户端或网关重新启动它。对此的反应是由动物园管理员扮演角色。通过这个管理员注册中心,当相关角色扩展后,在注册表中注册后,其他相关模块可以在得到通知后得到他们的地址和其他信息。当zookeeper被用作注册表时,采用实时监视和定时轮询策略来确保程序执行时的数据可靠性,因为一旦网络出现任何抖动,zk就会认为客户端已经利用了停机时间来关闭链接。
经过分析,相关架构图如下:
一套高度可用、可扩展和高度并发的即时消息群聊和单一聊天架构设计实践_2.gif
以下小节将描述每个模块的详细过程。
4.2、客户
客户详细流程如下:
1)从配置文件中加载注册表地址;
2)从注册表上的代理注册路径/发布路径/代理获取所有代理,并根据每个代理标识的大小顺序递增,形成代理数组;;
3)启动线程实时关注注册表路径/发布/代理,以获取代理的动态变化并更新代理线程;及时。
14)启动线程定期轮询,获取注册表路径/pubsub/Proxy下的每个代理实例,作为注意策略的补充,以保持本地代理中的每个代理成员与注册表上的每个代理保持一致;定期向每个代理发送心跳,并异步获取心跳数据包;定期清除代理中心跳超时的代理成员;
5)发送消息时,雪花算法用于为每条消息分配一个消息标识,然后使用相关的负载平衡算法将消息转发给代理。
4.3、代理
代理的详细流程如下:
1)读取配置文件并获取注册表地址;
2)在注册表路径/发布/代理下注册您自己的信息,并将注册表返回的复制标识作为您自己的标识;
3)获取每个副本;注册表路径/pub sub/代理/分区中每个代理分区的(x);
4)获取当前有效的代理分区号;从注册表路径/发布/代理/分区号;
5)启动一个线程来关注注册表上的代理路径/pubsub/broker,以实时获取以下信息:
{代理分区号}
-新的代理分区(此时正在扩展);
-代理分区中的新代理副本(分区中发生副本扩展);
-在代理奇偶校验中挂起的副本的信息;
6)定期向每个代理分区副本发送心跳,并异步等待代理返回的心跳响应包检测其活动,以确保房间消息;不转发给过期的副本;
7)启动线程定期读取注册表上代理路径/pubsub/broker下的每个子节点的值,并观察代理分区号的变化和每个分区的变化,以定期轮询的策略作为实时策略的补充;同时,检查心跳数据包定期超时的代理,并将其从有效的代理列表中删除;
8)根据规则[代理分区号=房间号%代理分区号,代理复制号=房间号%代理分区复制号],将房间消息转发给某个分区的副本,并在收到客户端的热拍数据包时及时响应。
在一个线程中处理房间消息和心跳消息的原因是为了防止进程伪造死亡。
当/pubsub/broker/Partition_num的值改变时(例如,该值被改变为4),这意味着路由器分区已经被扩展,并且代理应该及时获得新分区路径下的实例(例如/pubsub/broker/Partition2和/pubsub/broker/Partition3),注意这些路径并获得新分区下的实例。
代理在获得注册表下的所有当前代理实例信息后注册自己的信息的原因是它此时有资格转发消息。
转发会议室消息时,代理仅将其发送给处于运行状态的代理。根据注册中心分配的副本标识,代理分区中的所有副本以增量方式排序,形成代理分区副本阵列。在规则中,代理分区副本是数组的大小,代理副本是数组中副本的下标。
4.4、管道
接收到的房间消息需要完成三个任务:接收房间消息、转换消息协议和向代理发送消息。
如果初始系统的所有三个步骤都在一个线程中处理,代理的总吞吐率只有50 000 msg/s。
最终的实现方法是按照消息处理的三个步骤在管道中进行以下过程处理:
1)启动一个消息接收线程和n [n = =代理奇偶校验数]个一次写入多的无锁队列(称为消息协议转换队列),消息接收线程启动一个epoll循环进程分别收集消息,然后通过相应的哈希算法将消息写入相应的消息协议转换队列[队列标识= UIN % N];
2)启动N个线程和N * 3个无锁队列(称为消息发送队列),每个消息协议专家线程接收来自消息协议转换队列的消息并进行协议转换,然后根据相应的哈希算法将它们写入消息发送队列[队列标识= UIN % 3N];
3)启动3N个消息发送线程,分别创建相应代理的连接,每个线程独立接收来自相应消息发送队列的消息并发送出去。
经过上述流水线转换后,代理的整体吞吐率可以达到20万消息/秒
关于管道本身的解释,本文不做详细说明,但可以参考下图:
一套高度可用、可扩展和高度并发的即时消息群聊和单一聊天架构设计实践_1111.jpg
4.5、大房间信息处理
每个房间的人数不均衡。最简单的解决方案是在不修改任何代码的情况下,为不同数量级的房间建立一个消息系统。
然而,所谓的需求驱动架构改进在系统迭代升级过程中满足了这样一个需求:业务端有一个全国性的空间向所有在线用户推送消息。鉴于这种需求,不可能单独为这样一个房间建立一个系统,并且这个房间中的消息量非常小。
如果本房间的信息直接发送到现有系统,可能会影响到其他房间的信息发送:信息系统是一个扩写系统,系统的所有在线用户都在国家房间,每次都会干扰其他房间的信息发送。
最终的解决方案是使用类似于分区的方法,将如此大的房间映射到64个虚拟房间(称为虚拟房间)。在分配给房间号码段的服务线路的配合下,为消息系统保留一个号码段来划分这个大房间。在代理层,每个用户按照哈希方法分配到相应的虚拟房间,其他模块代码可以不加修改地完成大型房间消息的路由。
4.6、经纪人
经纪人的详细流程如下:
1)代理加载配置并获取其所在分区的标识(假设为3);
2)向注册表路径/pubsub/broker/partition3注册,将其状态设置为初始化,注册表返回的标识将是它自己的标识(复制标识););
3)接收路由器转发的网关消息,并将其放入网关消息队列;;
4)从数据库加载数据,并加载代理分区应该负责的RoomGatewayList数据;
5)异步处理网关消息队列中的网关消息,仅处理符合规则[PartitionId = = roomid % PartitionNum]的消息,并将数据存储在本地路由信息缓存中;
6)将注册表路径/pubsub/broker/partition3下自己节点的状态修改为“正在运行”;;
7)启动线程,实时关注注册表路径/发布/代理/分区号的值;
8)启动线程定期查询注册表路径/发布/代理/分区号的值;
9)当注册表路径/pubsub/broker/partition_num的值改变时,根据规则[分区id = = rootid% partition num]清除本地路由信息缓存中的每一条数据;
10)接收代理发送的房间消息,根据房间号从路由信息缓存中搜索房间成员登录的所有网关,并将消息转发给这些网关。
请注意,Broker先注册然后在数据库中加载数据的原因是为了在加载数据的同时接收路由器转发的网关消息,但是这些接收到的数据在加载数据之前被缓存,并且这些数据将在加载所有RoomGatewayList数据之后被重放;
代理区分状态的原因是,在加载RoomGatewayList数据之前,它不为代理提供消息转发服务,并且当消息量增加时,代理分区也便于水平扩展。
当在代理中进行分区扩展时,新分区的数量必须是2的幂,并且只有在新分区中的所有代理副本完成加载实例之后,才应该更改/pubsub/代理/分区_ num的值。
旧的代理还需要watch path/pub sub/Broker/partition _ num的值。当该值增加时,还需要清除本地路由信息表。
Broker的扩展过程类似于细胞分裂,形成的两个细胞具有相同的数据。在分割完成后[注册表路径/pubsub/broker/partition_num的值加倍],有必要清除垃圾信息。这种方法叫做加倍法。
4.7、路由器
路由器的详细流程如下:
1)路由器加载配置、注册表地址;
2)在注册表路径/pubsub/router下注册您自己的信息,并将注册表返回的复制标识作为您自己的标识;
3)获取每个副本;注册表路径/pub sub/代理/分区中每个代理分区的(x);
4)获取当前有效的代理分区号;从注册表路径/发布/代理/分区号;
5)启动一个线程来关注注册表上的代理路径/pubsub/broker,以实时获取以下信息:
{代理分区号}
-新的代理分区(此时正在扩展);
-代理分区中的新代理副本(分区中发生副本扩展);
-在代理奇偶校验中挂起的副本的信息;
6)定期向每个代理分区副本发送心跳,异步等待代理返回的心跳响应包检测其活动,以确保网关消息;不转发给过期的副本;
7)启动线程定期读取注册表上代理路径/pubsub/broker下的每个子节点的值,并观察代理分区号的变化和每个分区的变化,以定期轮询的策略作为实时策略的补充;同时,检查心跳数据包定期超时的代理,并将其从有效的代理列表中删除;
18)从数据库中加载路线房间网关列表的数据,并将其放入本地缓存;
9)从网关接收心跳消息,并及时返回确认包;
10)收集网关转发的网关消息,并根据特定规则将其转发给代理分区下的所有代理副本[代理分区id%代理分区编号= roomid%代理分区编号]。确保分区下的所有副本都具有相同的路由RoomGatewayList数据,然后将消息中的数据存储到本地缓存中,并在检测到数据没有重复时将数据异步写入数据库。
4.8、网关
网关详细流程如下:
1)读取配置文件并加载注册表地址;
2)从注册表路径/pubsub/router/中获取所有路由器副本,并根据每个副本的标识对其进行增量排序,形成副本阵列路由器阵列;;
3)启动线程,实时关注注册表路径/pubsub/Router,获取路由器的动态变化,更新RouterArray及时。
4)启动线程定期轮询,获取注册表路径/pubsub/router下的每个Router实例,作为注意策略的补充,以便及时更新本地RouterArray定期向每台路由器发送心跳,并异步获取心跳数据包;定期清除路由器阵列中心跳超时的路由器成员;
5)当房间中的成员客户端已连接或房间中的所有成员未连接到当前网关节点时,发送网关消息;根据规则[routerarrayindex = roomid % routernam]连接到路由器;
6)当接收到代理转发的房间消息时,根据消息标识执行重复删除。如果没有重复,消息将被发送到连接到当前网关的房间中的所有客户端,并且消息标识将被缓存以进行重复删除判断。
网关本地有一个基于共享内存的LRU缓存,用于存储最近发送的消息的消息标识。
第五,下一个亟待解决的问题:系统稳定性
系统的可扩展性只是系统可用性的初始阶段。为了确保最低粒度的服务级别协议(0.99),整个系统必须从两个维度来感知系统的可靠性:消息延迟和系统内部组件的高可用性。
5.1。信息延迟
为了准确统计消息延迟,一般的方法可以是基于日志系统对系统中的所有消息进行统计,或者以一定的概率进行抽样后进行统计,但是目前人工还没有这样做。
目前使用的方法是:通过构造一组伪用户id,定期向代理发送消息,每条消息都经过一层,然后将该层的传入时间和传出时间以及组件本身的一些信息填充到消息中。这组伪用户的消息最终将被发送到伪网关,伪网关在合并和统计这些消息的信息后,可以计算当前系统的平均消息延迟时间。
系统的整体性能可以通过所有消息的平均延迟来评估。同时,由于系统消息路由的哈希方式是已知的,当伪网关在固定时间内没有收到消息时,消息被视为发送失败,当某条链路出现一定次数的故障时,会产生告警。
5.2。高可用性
上述方法可以同时检测链路是否有问题,但无法判断链路的具体问题点,无法保证实时性。
为了保证每个组件的高可用性,系统引入了另一种评估方法:每层向后端组件发送一个心跳包,并通过心跳包的延时和成功率来判断下一级组件的当前可用状态。
例如,代理定期向每个分区中的每个代理发送心跳,它可以根据心跳的成功率快速判断代理是否处于“暂停动画”状态(最近,业务遇到了代理进程仍在运行,但不处理任何接收到的消息的情况)。
同时,代理的处理能力也可以通过心跳包的延迟来判断。基于该延迟值,多个代理可以平衡同一分区中的负载。
第六,进一步优化:消息可靠性
公司以前有一个使用tcp通道的群聊消息系统,但是在元旦发生了一次大事故(几乎整条线都瘫痪了)之后,一些相关业务的重要消息都变成了这个基于UDP的群聊消息系统。这些消息,例如服务器给客户端的游戏动作指令,是不允许丢失的,但是与聊天消息相比,它们的特征非常小(最多每秒一条消息),因此有必要基于当前的UDP链路建立可靠的消息链路。
中国一家即时消息制造商的消息系统也是基于UDP链接的(请参阅为什么QQ使用UDP协议而不是TCP协议?”),他们的方法是消息重试加ack来建立可靠的消息稳定传输链路。然而,这种方法会降低系统的吞吐量,所以我们需要找到一种新的方法。
UDP通信的本质是伪装的IP通信,而TCP本身的稳定性只不过是重传、重复数据消除和确认,因此可以通过重传和重复数据消除来保证消息的可靠性,而无需考虑消息的顺序。
基于当前系统的可靠消息传输过程如下:
1)客户端根据雪花算法为每个命令消息配置一个标识,复制三个副本,并立即将其发送到不同的代理;
2)代理随机发送命令消息给代理;收到后;
3)代理接收并将其传输到网关;;
4)网关收到命令消息后,根据消息标识进行重复判断,如果重复则丢弃,否则发送给应用并缓存。
在群聊消息系统中传输正常消息时,代理将根据消息的房间号将其传递给固定代理,以确保消息的顺序。
七.路由器需要进一步加强
7.1。简要描述
当需要在线部署多套群组聊天消息系统时,网关需要将同一个房间消息复制并转发给多套群组聊天消息系统,这将增加网关的压力。路由器可以独立部署,然后房间信息可以转发到所有群组聊天信息系统。
路由器系统的原始流程是:网关根据房间号向路由器转发消息,然后路由器向下游代理实例转发消息。当新部署群组聊天消息系统时,新系统代理的模式需要通过一组协议机制通知路由器,这使得路由器自身的逻辑过于复杂。
一套高度可用、可扩展且高度并发的即时消息群聊和单一聊天架构设计练习_3.gif
重构后的路由器架构参照上图,也采用了分区和副本的设计,分区中副本之间采用了非领导机制;路由器副本不会主动将网关消息内容推送给代理,但代理会主动以心跳数据包的形式向路由器分区中的副本注册,然后该副本会将消息转发给该代理。
与代理类似,路由器分区也以双重扩展的方式水平扩展分区,并在扩展或分区中的每个实例停止运行或重新启动时尽力确保数据一致性。
收到网关消息后,路由器副本将网关消息转发给分区中的每个对等副本,然后再转发给每个订户。路由器在转发消息时异步将消息数据写入数据库。
在独立的路由器架构下,以下部分将详细介绍网关、路由器和代理的详细过程。
7.2、网关
网关详细流程如下:
1)获得每个复制品;注册表路径/发布/路由器/分区中每个分区的(x);
2)获取当前有效的路由器分区号;从注册表路径/发布/路由器/分区号;
3)启动线程,关注注册表上的路由器路径/pubsub/router,以便实时获取以下信息:{路由器分区号}->新路由器分区(此时发生扩展);分区中的新副本(分区中发生了副本扩展);一个复制品挂在巴黎的信息;
4)定期向每个分区副本发送心跳,异步等待路由器返回心跳响应包,检测其活动,以确保网关消息;不转发给过期的副本;
5)启动线程,定期读取注册表中路由器路径/pubsub/router下的每个子节点的值,观察路由器分区号的变化和每个分区的变化,以定期轮询的策略作为实时策略的补充;同时,定期检查心跳数据包超时的路由器,并将其从有效的代理列表中删除;
6根据规则将网关消息转发到分区的副本。
步骤6中的规则确定了网关消息、分区和副本的用途,规则的内容如下:
如果路由器分区标识满足条件(roomid % routerPartitionnumber = = routerpartitionid % routerpartitionnumber),消息将被转发到此分区;
路由器分区不是通过直接哈希获得的原因(routerpartitionid = rootid % routerpartitionnumber),认为只有当所有新分区的所有副本都启动时,才会修改注册表路径/pubsub/Router/partitionnum的值,并且当路由器扩展两次时,数据是一致的。根据该规则的计算公式,可以保证新分区的每个副本在启动过程中都可以得到网关消息,即每个网关消息在此时都将被发送到两个路由器分区。当路由器被扩展并且注册表路径/pubsub/router/Partitionnum的值被修改时,新的集群进入稳定期,并且每个网关消息将仅被发送到固定的分区。条件(root id % routerpartitionnumber = = routerpartitionid % routerpartitionnumber)等效于条件(routerpartitionid = root id % routerpartitionnumber)。
如果路由器分区中的副本满足条件(replacartionid = roomid % routerpartitionreplicanum),消息将被转发到此副本。
副本在注册表注册时获得的标识称为副本标识,路由器分区中的所有副本按照副本标识按升序排序,形成副本数组RouterPartitionreplicaArray,副本分区标识是副本在数组中的下标。
网关消息数据一致性:
网关向路由器发送两种类型的路由器消息:用户进入当前网关上的房间,用户退出当前网关上的房间。数据项是UIN(用户标识)、房间标识、网关地址和用户操作(登录或注销)。
因为所有消息都是通过UDP链路转发的,所以这些消息的顺序可能会有问题。网关可以为网关发送的所有消息统一分配一个全局递增的标识(以下称为网关消息标识,网关消息标识),以确保消息的唯一性和全局排序。
当网关向注册表注册临时有序节点时,注册表将为网关分配一个标识,网关可以使用该标识作为自己的实例标识[假设该标识的上限为65535]。
GatewayMsgID字长为64位,其格式如下:
12//63 48 47 38 37 0
7.3、路由器
在部署路由器系统之前,将注册表路径/发布/路由器/分区号的值设置为1。
路由器的详细流程如下:
1)路由器加载配置并获取其所在分区的标识(假设为3);
2)向注册表路径/pubsub/router/partition3注册,将其状态设置为初始化,注册表返回的标识将是它自己的标识(复制标识););
3)注册后,您将收到来自网关的网关消息和来自代理的心跳消息,它们将缓存在消息队列中;首先;
4)获取每个副本;从注册表路径/pubsub/router/partition3找到您所在的分区;
5)获取当前有效的路由器分区号;从注册表路径/发布/路由器/分区号;
6)启动一个线程来关注注册表路径/pubsub/router,以实时获取以下信息:{router partition number}->分区中的新副本(副本扩展发生在分区中);一个复制品挂在巴黎的信息;
7)从数据库加载数据;
8)启动线程异步处理消息队列中的网关消息,将网关消息转发给同一分区中的其他对等副本,然后将其转发给每个代理;根据规则[Roomid % BrokerPartitionNumBer = = BrokerReplicationId % BrokerPartitionNumBer]在代理列表中;处理代理发送的心跳数据包,将代理信息存储在本地代理列表中,然后将数据包发送回代理;
9)将注册表路径/发布/路由器/分区3下的节点状态修改为正在运行;;
10)启动线程定期读取注册表路径/pubsub/Router下的每个子路径的值,并以定期轮询策略作为实时策略的补充,观察路由器每个分区的变化;检查已超时的代理,并将其从代理列表中删除;
11)当RouterPartitionNum倍增时,路由器根据规则[roomid % brokerpartionNumber = = brokerprepolicartionid % brokerpartionNumber]清除其自身路由信息缓存中的数据;
12)路由器本地存储每个网关的最大网关消息数,接收到的小于网关消息数的网关消息可以不经处理就丢弃;否则,GatewayMsgID将根据上述逻辑进行更新和处理。
网关消息和心跳消息在一个线程中处理的原因是为了防止进程伪造死亡。
代理还采用了分区和副本机制,因此将网关消息转发到代理的路由规则与将网关消息转发到路由器的路由规则相同。
此外,当新启动的分区中的所有副本在水平扩展后运行时,启动一个工具将注册表路径/pubsub/router/Partition _ num的值修改为所有分区的数量。
7.4、经纪人
经纪人的详细流程如下:
1)代理加载配置并获取其所在分区的标识(假设为3);
2)向注册表路径/pubsub/broker/partition3注册,将其状态设置为初始化,注册表返回的标识将是它自己的标识(复制标识););
3)获取当前有效的路由器分区号;从注册表路径/发布/路由器/分区号;
4)获取副本;注册表路径/发布/路由器/分区中每个路由器分区的(x);
5)启动线程关注注册表路径/pubsub/router,以便实时获取以下信息:{ Router Partition Number }-> New Router Partition(此时发生扩展);分区中的新副本(分区中发生了副本扩展);一个复制品挂在巴黎的信息;
6)根据规则[RouterPartitionId % BrokerPartitionNum = = BrokerPartitionId % BrokerPartitionNum,routereplicaid = Brokerprepolicaid % Brokerpartionnum]在目标RouterPartition下选择一个Routerreplica,并向其发送心跳消息。包括精确到第二级的代理分区号、代理分区号、代理主机号和时间戳,异步等待所有路由器应用程序的回复,并将路由器转发的所有网关消息放入网关消息队列;;
7)根据规则[代理分区号= = Roomid%代理分区号]从数据库加载数据;
8)根据规则[brokerpartionid % brokerpartionnum = = roomid % brokerpartionnum],网关消息队列中的网关消息是异步处理的,只留下符合规则的消息数据;
9)将注册表路径/pubsub/broker/partition3下自己节点的状态修改为“正在运行”;;
10)启动线程定期读取注册表路径/pubsub/Router下的每个子路径的值,并以定期轮询策略作为实时策略的补充,观察路由器每个分区的变化;定期检查超时的路由器。路由器超时后,用它所在分区中的其他路由器替换它,并定期发送心跳数据包;
11)当注册表路径/pubsub/broker/partition_num的值代理分区数改变时,根据规则[分区id = = rootid% partition num]清除本地路由信息缓存中的每一条数据;
12)接收代理发送的房间消息,根据房间号从路由信息缓存中搜索房间成员登录的所有网关,并将消息转发给这些网关;
13)代理在本地存储每个网关的最大网关消息数,接收到的小于网关消息数的网关消息可以不经处理就丢弃;否则,GatewayMsgID将根据上述逻辑进行更新和处理。
BrokerPartitionNumber可以小于、等于或大于RouterPartitionNumber,这两个数字都应该是2的幂,并且这两个群集可以在不相互影响的情况下分别扩展。例如,代理分区号=4,路由器分区号=2,代理分区3只需要向路由器分区1的追随者发送心跳消息。如果代理分区号=4,路由分区号=8,代理分区3需要向路由器分区3的追随者发送心跳消息,还需要向路由器分区7的追随者发送心跳消息,以获取全部网关消息。
代理需要注意/pub sub/Router/parion num和/pub sub/代理/partitionnum的值的变化。当路由器或代理扩展分区级别时,代理需要及时重建与路由器的对应关系。更改及时发送心跳的路由器副本对象[RouterPartitionID = BrokerReplicaId % RouterPartitionNum,RouterPartitionId是分区中路由器副本的下标RouterReplicaArray]。
当路由器分区中的副本死亡或发送心跳数据包的副本对象死亡时(无论是注册表通知还是心跳数据包超时),代理应及时更改发送心跳的路由器副本对象。
此外,网关使用UDP通信向路由器发送网关消息。如果此消息丢失,此网关上的房间中的所有成员在一段时间内将无法再收到该消息(当新成员加入当前网关上的房间时,将生成新的网关消息)。为了确保消息的可靠性,可以使用一个约束来解决这个问题:当登录到这个网关的房间中的人数少于3时,网关将把网关消息复制成两个不连续的副本(例如,以10ms的时间间隔),并重复地将其发送给分区领导。由于网关消息处理的幂等性,重复网关消息不会导致房间消息发送错误,但只有在极少数情况下,当网关收到消息时,房间中没有成员登录到此网关,网关将丢弃消息而不进行处理。
当发送实时消息的群组聊天消息系统的代理将房间消息转发到特定网关时,它会将登录该网关的用户列表带到房间中。如果网关根据此用户列表发送消息时检测到此用户已脱机,它还应该向路由器发送此用户已脱机的消息,同时放弃向此用户转发消息。路由器将该消息转发给代理后,代理会将该用户从用户列表中删除。这种负反馈机制确保了用户状态更新的及时性。
八、离线消息处理
8.1。简要描述
以前的系统只考虑用户在线时的实时消息传输,用户离线时无法获取消息。
如果系统考虑用户离线消息,应考虑以下因素:
1)消息固化:确保用户在线时在离线期间收到消息;
2)有序消息:离线消息和在线消息都在一个消息系统中传输,每个消息都分配一个标识来区分消息序列,消息序列越晚,标识越大。
离线消息的存储和传输需要考虑用户的状态和每条消息的发送状态,整个消息核心链接过程将被大大重构。
新的消息体系结构如下:
一套高度可用、可扩展和高度并发的即时消息群聊和单一聊天架构设计实践_4.gif
系统名词解释:
1)Pi:消息标识存储模块,存储每个人未发送的消息标识的有序增量集;
2)秀:消息存储KV模块,存储每个人的消息,并为每个消息分配标识,标识为关键字,消息为值;;
3)网关消息(HB):用户登录和注销消息,包括APP保持活动的监听消息。
该系统的内部代码是勇敢的,男性和女性,这源于上述两个新模块。
这个版本架构过程的核心思想是“消息标识与消息内容分离,消息与用户状态分离”。消息发送过程涉及客户端/代理/Pi/Xiu模块,消息推送过程涉及Pi/Xiu/代理/路由器/网关模块。
接下来的部分首先详细介绍了皮与秀的接口,然后详细介绍了发送和推送的过程。
8.2、秀
秀模块的功能名称为消息存储。用户缓存和固化消息,并为消息分配id。Xi集群采用分区和副本机制,初始分区数必须是2的倍数,在扩展集群时采用加倍方法。
8.2.1存储消息
用于存储消息请求的参数列表是{ UIN雪片节,消息},其过程如下:
1)接收客户端发送的消息,获取消息接收方标识(UIN)和雪片标识;由客户端分配给消息;
2)检查uin % Xiu _ partition _ num = = Xiu _ partition _ id % Xiu _ partition _ num的添加是否为真(即收件人的消息是否应对当前Xiu负责),如果不是,返回错误并退出;
3)检查雪片标识对应的消息是否已经存储,如果已经存储,返回对应的消息标识并退出;
4)为消息分配一个MsgID:
每个分区都有自己唯一的分区号和一个初始值为0的分区号。MsgID = 1B[秀分区标识] + 1B[消息类型] + 6B[ ++分区消息标识].每次分配分区时,分区号都会增加一。
5)以MsgID为密钥,基于共享内存将消息存储到哈希表中,存储CRC32哈希值和消息的插入时间,并将MsgID存储到LRU列表中:
LRU列表本身不存储在共享内存中。当进程重新启动时,可以根据哈希表中的数据重建这个列表。在哈希表中存储消息时,如果哈希表已满,哈希表已满的消息将根据LRU列表被删除。
6)将MsgID返回给客户端;
7)异步通知消息固化线程MsgID,消息固化线程根据MsgID从Hashtable中读取消息,并根据CRC32哈希值判断消息内容是否完整,如果完整,将消息保存在本地RocksDB中。
阅读信息
读取消息请求的参数列表为{UIN,MsgIDList},其流程如下:
1)获取请求的MsgIDList,并判断每个msgid的条件{ xiu _ partition _ id } = = xiu _ partition _ id是否为真;如果不是,返回错误并退出;
2)从哈希表中获取每个MsgID对应的消息;
13)如果哈希表不存在,从RocksDB读取对应于MsgID的消息;
4)读取后,将所有获取的消息返回给客户端。
8.2.3主从数据同步
就目前而言,只有一份《秀》。
当秀节点启动时,根据在它自己的配置文件中分配的秀分区id,在注册表的路径/pubsub/秀/分区ID下注册一个临时有序节点。如果注册成功,注册表将返回SUI的节点标识。
秀节点获取/pubsub/秀/partition_ID下所有节点的标识和地址信息,并根据节点标识最小的领导者就是领导者的原则来判断自己的角色。只有领导者可以接受读写数据的请求。
数据同步过程如下:
1)跟随者定期向领导者发送心跳信息,心跳信息包含最新本地消息的id;
2)领导者启动数据同步线程处理追随者的心跳信息,领导者的数据同步线程从LRU列表中搜索追随者_latest_msg_id后的N条消息的标识,读取消息并同步到追随者(如果获得的话),如果没有获得,则回复其与领导者之间的消息间隙过大;
3)跟随者从领导者处获得最新一批消息并存储;
4)如果跟随者从领导者处得到消息间隙过大的响应,他请求领导者的代理同步RocksDB的全部固化数据,并在完成后再次开始与领导者的数据同步过程。
跟随者将注意注册表路径/pubsub/xiu/partition_id下所有节点的变化,如果领导者挂断,它将及时改变身份并接受客户端请求。如果追随者和领导者之间的心跳超时,追随者会删除领导者的注册表路径节点,并及时执行身份转换以处理客户端请求。
当引导程序重新启动或跟随程序转换为引导程序时,有必要增加一个较大的值(例如,增加1000),以防止可能的消息标识混乱。
8.2.4集群扩展
Xi集群的扩展采用双重方式,扩展过程中新分区节点启动后的工作流程如下:
1)在注册表的路径/pubsub/xiu/partition_id下的您自己的节点的状态正在运行时,注册您自己的外部服务地址信息;
2)启动另一个工具。当所有新启动的分区中的所有副本在水平扩展后运行时,注册表路径/pubsub/xiu/partition_num的值将更改为扩展后的分区数。根据开头的示例,从2升级到4。
当它像代理和路由器一样启动时,Sui不需要与旧分区同步数据的原因是因为每个Sui分配的MsgID已经包含Sui的分区标识信息,并且即使集群扩展,该标识也不会改变,并且它所属的分区可以根据该标识来定位,而不是使用哈希方法。
8.3、Pi
Pi模块的功能名为消息标识存储,存储每个用户的消息标识列表。Xi集群还采用了分区和副本机制,初始分区数必须是2的倍数,并且在扩展集群时采用了双倍的方法。
8.3.1存储消息标识
MsgID存储的请求参数列表为{UIN,MsgID},Pi工作流程如下:
1)判断条件uin % pi _ partition _ num = = pi _ partition _ id % pi _ partition _ num是否为真,否则返回错误退出;
2)将MsgID插入UIN的MSGID列表中,使MSGID列表中的所有MSGID不重复且有序增加,将请求的内容写入本地日志,并向请求者返回成功的响应。
Pi有一个特殊的日志记录线程,它为每个日志操作分配一个LogID,每个日志文件记录一定数量的写操作。当文件大小超过配置的上限时,它将被删除。
8.3.2阅读消息标识列表
读取请求参数列表为{UIN,startmsgID,MsgIDNum,ExpireFlag},这意味着获取用户UIN的msgIDnum消息ID列表,因为起始id为StartMsgID(不包括StartMsgID),而ExpireFlag意味着是否删除所有小于或等于StartMsgID的消息id。
过程如下:
1)判断条件uin % pi _ partition _ num = = pi _ partition _ id % pi _ partition _ num是否为真,否则返回错误退出;
2)获取(StartID,StartMsgID+MsgIDNum)范围内的所有MsgID,并将结果返回给客户端;
3)如果过期标志有效,则删除MsgIDList中[0,开始MsgID]范围内的所有MsgID,并将请求的内容写入本地日志。
8.3.3主从数据同步
有了修模块,就只有一份《圆周率》了。
当Pi节点启动时,根据其自己的配置文件中分配的Pi_Partition_ID向注册表路径/pubsub/Pi/partition_ID注册临时有序节点。如果注册成功,注册表将返回pi的节点id。
Pi节点获取/pubsub/pi/partition_ID下所有节点的标识和地址信息,并根据节点标识最小的领导者就是领导者的原则来判断自己的角色。只有领导者可以接受读写数据的请求。
数据同步过程如下:
1)跟随者定期向领导者发送心跳信息,心跳信息包含最新的本地LogID;
2)领导者启动数据同步线程处理追随者的心跳信息,并根据追随者上报的logID放置logID;
3)跟随者从领导者处获得最新一批日志,并首先存储它们,然后重放它们。
跟随者将注意注册表路径/pubsub/pi/partition_id下所有节点的变化。如果领导者挂断电话,它将及时改变身份并接受客户的请求。如果追随者和领导者之间的心跳超时,追随者会删除领导者的注册表路径节点,并及时执行身份转换以处理客户端请求。
8.3.4集群扩展
Pi集群扩展采用双重方法。节点启动后的工作流程如下:
1)在注册表中注册并获取值分区号;注册表路径/pubsub/xiu/partition_num的;
2)如果您发现您的分区号满足分区号> =分区号的条件,这意味着当前分区在扩展后是一个新的集群,并在注册表中更新您的状态以启动;;
13)读取注册表路径/pubsub/xiu下的分区的所有前导,并根据条件本身partitionid % partitionnumber = = partitionid % partitionnumber,即parent_leader,搜索相应旧分区的前导;;
4)缓存接收到的代理转发的用户请求;
5)获取日志;来自parent _ leader
6)将内存数据同步到父引线;
7)重放日志;家长领导;
8)将您自己在注册表中的状态更新为正在运行;;
9)重放用户请求;
10)当注册表路径/pubsub/xiu/partition_num的值partition num满足条件PartitionID >= PartitionNumber时,这意味着扩展已完成,并且在处理用户请求时应向用户返回响应。
代理通过参考读写请求条件uin% pi分区\u num = = pi分区\u id % pi分区\u num,将用户请求转发给相关分区的领导。假设原始分区号值为2,扩展值为4,则最初转发到分区0的写请求需要同时转发到分区0和分区2,而最初转发到分区1的写请求需要同时转发到分区1和分区3。
此外,当所有新启动的分区中的所有副本在水平扩展后运行时,启动一个工具将注册表路径/pubsub/xiu/Partition _ num的值修改为扩展后的分区数。
8.4。数据传输过程
消息从PixU的外部客户端(客户端,所有使用PixU的服务提供商统称为客户端)按照一定的负载平衡规则发送到代理,然后存储在Pi的Suu和MsgID中。
具体过程如下:
1)客户端根据雪花算法为消息分配雪花号,并将消息发送给代理;根据比邻星= UIN%比邻星的规则;
2)代理接收消息并转发给秀;;
3)代理收到秀返回的回复后,将回复转发给客户端;;
14)如果代理接收到秀返回的带有微指令的响应,则启动微指令写进程,并将微指令同步到微指令中;
5)如果代理收到秀返回的带有MsgID的响应,它将向代理发送一个通知,通知它有关一个UIN的最新MsgID。
8.5。数据转发过程
转发消息的主体是代理。原始的在线消息转发过程是接收代理转发的消息,并根据用户是否在线将其转发给网关。
在PiXiu架构下,代理将接收以下类型的消息:
1)用户登录消息;
2)用户心跳消息;
3)用户注销消息;
4)通知消息;
5)Ack消息。
代理进程由这五种消息驱动。下面详细介绍了它接收这五种消息时的处理流程。
用户登录消息流程如下:
1)检查用户的当前状态,如果是OffLine,其状态值为online online
2)检查用户要发送的消息队列是否为空,如果不是,退出;
3)向Pi模块发送获取N个消息标识的请求{uin: uin,startmsgid: 0,msgidnum: n,expireflag: false},将用户状态设置为GettingMsgIDList并等待响应;
4)根据Pi返回的消息标识队列,向秀发送消息获取请求{UIN: uin,消息标识列表:消息标识列表},将用户状态设置为获取消息列表,等待响应;
5)在5)Suu返回消息列表后,它将状态设置为发送消息并将消息转发到网关。
用户心跳消息可以被视为用户登录消息。
生成网关的用户注销消息有三种情况:
1)用户自愿退出;
2)用户心跳超时;
3)向用户转发消息时出现网络错误。
用户注销消息处理流程如下:
1)检查用户状态,如果离线,退出;
2)如果用户状态不是OffLine,请检查用户发出的消息列表中的最后一条消息标识(LastMsgID),向pi发送获取MsgID的请求{uin: uin,startmsgid: last msgid,msgidnum: 0,exPireflag: true},并在Pi返回响应后退出。
代理发送通知消息的处理流程如下:
1)如果用户状态为OffLine,退出;
2)更新用户标识,如果用户发送的消息队列不为空,则退出;
3)向Pi模块发送获取N个消息标识的请求{uin: uin,startmsgid: 0,msgidnum: n,expireflag: false},将用户状态设置为GettingMsgIDList并等待响应;
4)根据Pi返回的消息标识队列,向秀发送消息获取请求{UIN: uin,消息标识列表:消息标识列表},将用户状态设置为获取消息列表,等待响应;
5)在5)Suu返回消息列表后,它将状态设置为发送消息并将消息转发到网关。
确认消息是应用程序在代理程序通过网关将消息转发给应用程序后回复给代理程序的消息,通知代理程序它最近已成功接收到消息。
Ack消息处理流程如下:
1)如果用户状态为OffLine,退出;
2)更新LatestAckMsgID的值;
3)如果用户发送的消息队列不为空,发送下一条消息并退出;
4)如果LatestAckMsgID >= LatestMsgID,退出;
5)向Pi模块发送获取N个消息标识的请求{uin: uin,startmsgid: 0,msgidnum: n,expireflag: false},将用户状态设置为GettingMsgIDList并等待响应;
6)根据Pi返回的消息标识队列,向秀发送消息获取请求{UIN: uin,消息标识列表:消息标识列表},将用户状态设置为获取消息列表,等待响应;
7)在7)Xiu返回消息列表后,它将状态设置为发送消息并将消息转发到网关。
一般来说,PiXiu的消息转发过程采用拉式转发模型,由上述五条消息驱动进行状态转换并做出相应的动作。
九、单聊新闻
前面章节描述的系统架构都是基于实时群聊通信场景。该群聊短信系统在公司已经稳定运行了近一年半,其间经历了六大版本的迭代演进,赢得了各条业务线同事的信任,承担了群聊、聊天室、语音传输、游戏信令传输等各种场景下群聊的实时通信服务。
当该系统于2017年9月首次被接管时,服务提供商建议将发送给一个人的消息(以下称为聊天消息)连接到该系统,即消息系统不仅是发送群组聊天消息的渠道,也是发送单个聊天消息的渠道。然而,考虑到当时它处于系统实施的初始阶段,个人基于对小规模系统工作的考虑直接拒绝了它。随着过去一年半接入业务的扩张,该公司正在考虑拆分网关,因此不可避免地需要对该消息系统进行改造,以处理单一聊天消息。
公司的网关可能是我见过的最复杂的接口层系统。应该在逻辑层处理的各种子系统已经集成到接口层网关的单个模块中。除了公司的几位创始人之外,很少有人知道其他内部逻辑。据说,在接管该系统后的三个月内,四名收入颇丰的大师已经潜逃。网关中有两种与聊天场景相关的状态数据:用户的群组信息[以下称为路由器数据]和用户自己的状态信息[包括在线状态和客户端地址信息,以下称为中继数据]。
路由器数据已被群聊系统的路由器模块取代,网关不需要存储路由器数据。如果系统被扩展为处理单个聊天消息并存储用户的在线状态和用户访问的网关信息,则网关不需要存储任何关于用户的状态数据。
9.1。实时消息系统架构
中继数据不同于路由器数据:中继数据的关键是UIN,其数据传输依赖于中继消息;;而路由器数据的关键是组标识,其数据传输依赖于网关消息。它们之间的差异决定了这两种数据的处理不能在一个模块中进行。
新的实时通信系统增加了一个中继模块,用于处理中继消息和存储中继数据。
具有单条聊天消息处理能力的实时消息系统的架构如下:
一套高度可用、可扩展和高度并发的即时消息群聊和单一聊天架构设计实践
新架构下每个模块的功能和消息处理流程将在下面详细描述。诸如系统注册、系统扩展和注册通知之类的相关过程类似于先前的处理机制,因此在下文中将不再详细描述。
注意:在新的体系结构图中,专用于存储路由器数据的原始数据库模块被重命名为路由器数据库,并添加了一个中继数据库来存储中继数据。
9.2、网关
网关不再存储路由器数据和中继数据,几乎成为应用程序的透明代理。
其功能如下:
1)接收APP的连接请求,并以中继消息的形式向中继发送用户连接消息;
2)当2)APP和网关之间的连接断开时,以中继消息的形式发送给中继;
3)将用户登录和退出房间的消息转发给路由器;;
4)接收中继转发的房间信息和聊天信息,并将其分发给应用程序。
9.3、继电器
中继是一个新的模块,但它的组织类似于路由器,它也分为分区和副本来处理中继消息。
中继模块根据用户的UIN将不同用户的中继数据放入不同的分区,这与分区中的所有中继副本数据是一致的。
继电器功能列表如下:
1)接收网关转发的中继消息,将其存储为中继数据,并将中继数据存储在中继数据库中;异步;
2)开始时,从中继数据库获取分区中所有用户的中继数据,以保持与分区中其他副本的数据一致性;
13)接收代理转发的房间消息,并根据中继数据记录的用户所在的网关将消息复制转发给网关;
4)接收代理转发的聊天消息,根据中继数据记录用户的网关将消息复制转发给网关;
5)上述所有处理房间信息的系统都是扩写系统。原因是房间信息需要复制并分发给房间里的每个人。因此,当中继处理房间消息时,它需要根据房间消息中的房间UIN列表复制消息,然后将它们分发到连接到每个用户应用程序的网关。
当然,如果用户不在线,中继将丢弃房间消息和聊天消息,因为这个系统只处理实时消息。
中继负责发送群组聊天消息和单一聊天消息。
9.4、经纪人
在新的架构下,代理模块不再将房间消息直接转发给网关,而是转发给中继。使用中继,代理本身只需要存储每个组的UIN列表,这大大减少了自己的任务流。代理可以将中继视为伪网关。
代理功能列表如下:
1)接收路由器转发的网关消息,将其存储为路由器数据,并将路由器数据异步存储在路由器中;
2)开始时,从路由器数据库获取分区中所有用户的路由器数据,以保持与分区中其他副本的数据一致性;
13)接收代理转发的房间消息,并将该消息复制转发给每个中继站;根据路由器数据记录的组中的UIN列表;
4)经纪人仅负责群聊消息的分发。
9.5、代理
当代理收到客户端发送的消息时,它需要区分该消息是房间消息还是聊天消息。
新架构下的功能列表如下:
1)注意注册表代理路径以获得正确的代理列表;实时的;
2)注意注册表中继路径以获得正确的Relya列表;实时的;
3)接收客户发送的房间信息,并将该信息转发给经纪人;根据集团;
14)接收客户端发送的聊天消息,并将消息转发给中继;根据UIN;
5)代理仅转发组消息,不需要复制,因此不存在写放大问题。
实际上,代理也可以分为群组消息代理和聊天消息代理,以达到逻辑清晰、职责明确的效果。
X.本文摘要
该群聊留言系统有以下需要改进的任务列表:
1)消息通过UDP链路传输,不可靠[2018年1月29日解决];
2)当前的负载均衡算法采用最少循环算法,可以根据成功率和延迟增加一个基于权重的负载均衡算法来实现;
3)只考虑传输,不考虑消息复制,根据消息标识实现此功能[2018年1月29日解决];
4)各模块之间不考虑心跳方案,整个系统的稳定性取决于注册表[2018年1月17日解决];
5)离线消息处理[2018年3月3日解决];
6)区分消息优先级。
IM下载体验 - 哇谷IM-企业云办公IM即时聊天社交系统-JM 沟通下载
哇谷im_im即时通讯_私有云_公有云-哇谷云科技官网-JM沟通
IM功能与价格 - 哇谷IM-提供即时通讯IM开发-APP搭建私有化-公有云-私有化云-海外云搭建
新闻动态 - 哇谷IM-即时通讯热门动态博客聊天JM沟通APP
关于哇谷-哇谷IM-提供企业即时通讯IM开发-语音通话-APP搭建私有化-公有云-私有化云-海外云搭建
联系我们 - 哇谷IM-即时通讯IM私有化搭建提供接口与SDK及哇谷云服务