分类: 云计算
2022-10-08 10:27:45
经历过稍有些规模的IM系统开发的同行们都有体会,要想实现大规模并发IM(比如亿级用户和数十亿日消息量这样的规模),在架构设计上需要一些额外的考虑,尤其是要解决用户高并发、服务高可用,架构和实现细节上都需要不短时间的打磨。
具体解释就是:
1)高可靠:确保不丢消息;
2)高可用:任意机房或者服务器挂掉,不影响服务;
3)实时性:不管用户在哪里,在线用户消息在1秒内达到(我们实际是75%消息可以做到120ms);
4)有序性:确保用户消息的有序性,不会出现发送和接受的乱序。
从整体架构上来说,亿级用户量的IM架构整体上偏复杂。
传统开源的IM服务喜欢把所有服务做到1-2个服务里(Connector+Service模型),这样带来的问题比较严重。
传统开源的IM的问题主要体现在:
1)服务代码复杂,难以持续开发和运维;
2)单一业务逻辑出问题,可能会影响到其它逻辑,导致服务的全面不可用。
因此,我在做架构设计的时候尽量追求微服务化。即把整体架构进行分拆为子系统,然后子系统内按照业务逻辑分拆为微服务。
4个子系统的职责是:
1)IM业务系统:服务IM相关的业务逻辑(比如好友关系、群关系、用户信息等);
2)信令系统:负责用户登录,用户在线状态的维护,以及在线用户的下行推送;
3)推送系统:负责消息的在线推送和离线推送;
4)存储系统:负责消息和文件的存储和查询;
其中:信令系统和推送系统是基础设施,不只是可以为IM业务服务,也可以承载其它类似的业务逻辑(比如客服系统)。
在部署层面:采用存储3核心机房,信令和推送节点按需部署的方式(国内业务推荐8-10个点)。实际上我们只做了了北京3个机房,上海1个机房和香港一个机房的部署,就基本上满足了大陆+香港的业务需求。
一说到IM,很多人脑海里跳出的{BANNED}中国第一个关键就是“即时通信”,技术上理所当然的联想到了socket,也就是大家成天嘴上说的:“长连接”。换句话说,很多对IM不了解或了解的不多的人,认为IM里的所有数据交互、业务往来都是通过“长连接”来实现的,这样话,对于本文章中拆分出的“IM业务系统”就有点不理解了。
实际上,早期的IM(比如20年前的QQ、MSN、ICQ),确实所有数据基本都是通过“长连接”(也就是程序员所说的“socket”)实现。
但如今,移动端为主端的IM时代,IM系统再也不是一个条“长连接”走天下。
现在,一个典型的IM系统数据往来通常拆分成两种服务:
1)socket长连接服务(也就是本文中的“推送服务”);
2)http短连接服务(就是{BANNED}最佳常用的http rest接口那些,也就是本文中的“IM业务系统”)。
通俗一点,也也就现在的IM系统,通常都是长、短连接配合一起实现的。
对于socket长连接服务就没什么好说,就是大家{BANNED}最佳常理解的那样。
IM业务系统详细来说,就是专注处理IM相关的业务逻辑,比如:
1)维护用户数据:用户基本信息等;
2)维护好友关系:好友请求、好友列表、好友信息等;
3)维护群组信息:群创建、解散、成员管理等;
4)提供数据:离线拉取、历史记录同步;
5)其它逻辑:比如通过存储和推送系统,存储消息和发送通知;
按照微服务的原则,IM业务系统也被分拆为多个服务,比如:
1)GInfo服务:群组信息维护;
2)IM服务:处理1V1消息;
3)GIM服务:处理群组消息。
信令系统主要职责是3部分:
1)维护用户在线状态:
因为用户规模庞大,必然是多个集群,每个集群多台服务器为用户提供服务。
考虑到服务器运维的复杂性,我们要假定任何一个集群,任何一个服务器都可能会挂掉,而且在这种情况下要能够继续为用户提供服务。
在这种情况下,如果用户A给用户B发消息,我们需要知道用户B在哪个服务器上,才能把消息正确推送给用户B。用户在哪个信令服务,这个信息就是在线状态数据。
2)下行消息推送:
跟上一个职责有关,用户在线的时候,如果有其它用户给他发消息,那就{BANNED}最佳好不要走离线推送,而是走在线推送。
在线推送的{BANNED}最佳后一个环节,是把用户消息推送给用户设备,因为就需要知道用户登录到哪个服务器上。
3)业务分发:
信令服务不只可以处理IM请求,也可以处理其它类型的业务请求。为了处理不同的业务,就需要有分发能力。
具体做法是通过一个SVID(service id)来实现,不同的业务携带不同的SVID,信令服务就知道如何分发了。
用户通过登录服务把数据(比如IM消息)发送到信令系统,信令系统根据SVID转发给IM系统。不管后台有多少个业务,用户只需要一条链接到信令。
信令系统为了实现以上这3个职责,同时要确保我们服务可平行扩展的能力和稳定性,在实际的技术实现上,我们实际上把信令服务分拆为3个服务模块。即时通讯聊天软件app开发可以加蔚可云的v:weikeyun24咨询
Login服务主要负责维护用户长链接:
1)每个用户一条链接到Login服务,并按时间发心跳包给Login服务;
2)服务定时检查用户链接状态和心跳包,比如发现2个心跳周期都没收到心跳,就认为用户掉线了(有假在线问题,有兴趣同学可回贴讨论)。
Login服务收到用户登录请求以后,验证uid/cookie,如果成功就把这个用户的登录信息发送给online。
此过程主要记录的信息包含:
1)uid(用户id);
2)Login服务器IP/Port;
3)Route服务器的IP/Port。
如果用户发送IM消息,先发送到Login,Login转发给Route,Route根据服务的类型(SVID),发现是IM协议就发送给后端的IM服务。
Login对并发要求比较高,一般要支持TCP+UDP+Websocket几种方式,单服务可以做到10-250万之间。从服务稳定性角度触发,建议是控制VM的CPU/内存,单服务器以20-50万为合适。
Login服务器本身没有状态,任何一个Login服务断掉,用户端检测到以后重连另一个Login服务器就可以了,对整体服务可靠性基本没有影响。
Online服务主要负责维护用户的在线信息:
1)如果用户掉线,Online服务里信息就是空;
2)如果用户在线,Online就能找到用户登录在哪个集群,哪个Login服务器上。
Online业务相对简单:多个Login服务器会连接到Online,定期同步用户登录和离线信息。
Online主要职责是:把用户状态信息存储在Redis集群里。因此也是无状态的,任何一个Online服务挂掉,不影响整体服务能力。
如果集群规模不大,用户规模也不大,Online服务也可以收到Login服务里去。
如果规模比较大,建议分拆出来,一方面简化Login的逻辑复杂度,同时避免写Redis的慢操作放在Login服务里。因为Login要同时处理50万以上的并发链接,不适合在循环里嵌入慢操作。
Route服务的设计核心,是作为信令系统跟其它子系统的交互层。Route下接Login服务,可以接受用户业务信息(IM),也可以往用户推送下行消息。
多个后端业务系统可以接入到Route,按照服务类型(SVID, service id)注册。比如IM服务可以接入到Route, 注册SVID_IM。这样Login接收到SVID=SVID_IM的消息,转发给Route,Route就可以根据SVID转发给IM相关的服务。
Route简单的根据SVID做转发,不处理具体的业务逻辑,因此也是无状态的。一个信令集群可以有多个Route服务,任何服务挂了不影响整体服务能力。
推送系统的核心任务:是接收到给用户发送下行消息的请求以后,去信令服务查询用户是否在线,如果在线走信令推送,如果不在线走离线推送(如iOS的APNS、华为推送、小米推送等)。
因为推送服务可能出现大规模并发蜂拥,比如大群激烈讨论的时候,会触发亿级的TPS。因此推送服务用Kafka做了削峰。
具体就是:
1)PushProxy:接受用户的推送请求,写入Kafka;
2)Kafka:缓存推送服务;
3)PushServer:从Kafka获取推送请求,判断用户是否在线;
4)PushWorker:真正推送给信令或者APNS,华为推送等。
这里同样,除了Kafka以外每个服务都是无状态的,因为也可以实现平行扩展和容错,任何服务挂掉不影响整体服务可用性。