互联网+时代,消息量级的大幅上升,消息形式的多元化,给即时通讯云服务平台带来了非常大的挑战。高并发的IM系统背后究竟有着什么样的架构和特性?
以上内容由网易云信首席架构师内部分享材料整理而成。
相关阅读推荐
IM推送保障及网络优化详解(一):如何实现不影响用户体验的后台保活
IM推送保障及网络优化详解(二):如何做长连接加推送组合方案
IM即时通讯推送保障及网络优化详解(三):如何在弱网环境下优化大数据传输
本文要点:
- 网易云信整体架构解析
- 云信中的客户端连接和接入点管理
- 服务化和高可用
网易IM云分层架构图解析
- 底层客户端SDK,覆盖了安卓,iOS,windows PC桌面端,web网页端和嵌入式设备等多个平台。在SDK层使用的网络协议有4层的TCP协议和基于7层的Socket.IO协议,后者专门用于Web SDK中提供长连接能力;除了集成到应用App中的SDK之外,还提供了供第三方服务器调用的API接口,基于Http协议;最后的A/V SDK是基于UDP协议的实时音视频SDK,用于实现基于网络的语音和视频通话。
- 网关层:提供客户端直接接入并维护与服务器之间的长连接;其中WebSDK直连的是Weblink服务,这是一个基于Socket.IO协议实现的长连接服务,而供AOS/IOS/PC等客户端SDK直连的是基于TCP协议的Link服务;在Link和WebLink服务中承担的一个非常重要的功能就是所有客户端长连接的管理,后面基于HTTP协议上的网关有API服务,和LBS服务等,其中LBS服务用于帮助客户端SDK选取最合适自己的网关接入点,优化网络效率;而API服务则直接提供来自第三方服务器的业务请求;
- HA层:在网关接入层之上是HA层,网关接入层可提供给客户端直连,在link层和Service层之间有一个HA层用来解耦并提供高可用和易扩展等特性;在HA的具体实现方式上,对Link和WebLink这两个维持客户端长连接的服务,云信提供了协议路由的服务,代为分发业务请求,路由层会按照预定义的规则将来自客户端的请求转发到相应的业务节点上,当业务集群扩容之后路由服务马上能发现新的可用节点,并将请求转发过去,当发现业务节点出现异常时也会被路由层标记并隔离下线以备替换。
- 业务节点集群:在HA层上就是具体的业务节点集群,我们称为App服务,该服务处理具体的客户端请求,后端直连DB、cache等各种基础服务,这个集群中的节点的特点是轻量,并且每个节点都是无状态的,云信在实际部署这个集群时会跨网络环境部署,比如在同城双机房中分别部署一套业务服务节点,前端通过路由层来分发业务请求,平时正常时业务互为热备,平均分担线上的业务流量;当单一网络环境或者基础设施出现故障时马上会被路由服务检测到,并将该环境下的计算节点标记下线,将线上的流量请求全部转发到正常工作的集群中;从而提高了服务的整体可用性;配合监控平台等运维工具,业务节点的实时处理能力和容量使用情况都会被动态监测起来,当处理能力达到预设的水位线时会立即出发报警,运维人员可以非常方便快捷得通过自动部署平台对业务节点集群进行扩容。
- 业务层:其中包含了一些关键功能:核心的单聊消息、群聊消息和聊天室,通知等;以及用户信息托管,特殊关系管理等;还有面向API提供的如短信业务,回拨电话和专线会议等;还有实时音视频和直播功能等相关能力。
最右边列出的是从服务层上单独列出来的更重要的功能,包括与开发者应用的第三方数据同步,个性化的内容审核支持,超大群服务,登陆登出事件日志,漫游消息和云端消息历史功能,推送服务等等。
网易IM云部署拓扑
通过以下这张简化后的部署拓扑图可以对云信整体技术体系的有初步了解。最右边是客户端,客户端通过LBS服务获取到网关接入点列表,再与Link和WebLink这类长连接服务器建立起长连接,并进行RPC操作,所有来自客户端的请求都会通过路由层转发到后端的APP层,APP层实时处理并下发同步请求的处理结果,并把一些异步任务通过队列服务送到异步任务中,这些异步服务如大群消息的发送,推送服务,云端历史消息的存储和第三方的数据抄送同步服务等;在最下面的API接口上也是类似,API直接提供给第三方的服务器调用请求,API后端是各种独立的业务,如回拨电话,短信等;同样的所有的API后端业务请求也会产生相应的日志;和APP上的日志样,这些日志都会被通过日志采集平台收集到大数据平台中,一方面这类数据会存储到HDFS上用于作为数据统计分析的数据源;另一方面会被导入到Hbase等数据仓库中,用于提供日志检索和二次分析。
高并发IM系统连接层的优化实践
即时通讯功能中最重要的连接管理服务怎么做?消息快速到达的前提是客户端和服务器之间保持了稳定的连接;可以理解为奠定云信服务稳定性的基石。网关接入层需要解决的最重要的问题是什么?核心依然是稳定,安全和快速。
如何保证稳定?
网易云信SDK采用长连接机制来实现,并且由心跳的方式来检测断线和自动做重连,同时云信的SDK对移动网络等弱网环境非常多的优化工作,对移动端/PC端使用TCP来连接客户端与服务器,对与Web端使用socketIO协议,实现长连接的同时解决浏览器的兼容性问题;
如何实现安全?
云信要求所有在公网传输的数据都必须被加密;在SDK与服务器的连接建立过程中有一个复杂的秘钥协商过程,首先客户端需要生成一个一次性使用的加密秘钥,并使用非对称加密方式将这个秘钥加密之后传给服务器,加密数据会被服务器解密,之后该加密秘钥被保留在该长连接的会话信息中,数据来往均使用该秘钥加密,这是一个流式加密,可以有效防止中间人攻击和数据包回放等攻击手段。
如何保证快速?
首先是在网关接入点的选择上,借助LBS服务可以帮助客户端寻找到最适合自己的网关接入点,比如从ip等信息判断到的物理距离最近节点,其次在连接建立之后,长连接的机制可以极大提升消息上下行的速度,并且在数据传输过程中,云信会对数据包压缩传输,降低网络开销来升消息收发的速度;对频繁的前后台切换和重登陆这种移动客户端场景,SDK提供自动登录和重连等机制,即在UI界面起来的同时已经提前把消息通道建立;在接入网关的选择策略中,通过并行来提升连接建立的速度。
客户端与服务器建立长连接的过程展现
SDK接入的第一步是先请求LBS服务,获取可以进入的接入网关地址列表,LBS服务会根据多种策略条件来给客户端分配地址,常见的条件如下:
- appkey, 通过appkey可以将一个特定的应用请求全部指向到一组特定的接入点,可用于专属服务器方案;
- 客户端ip,用于根据客户端所处的地理位置,为其就近分配接入网关,常见于海外节点的配置;
- SDK版本号,将特定版本范围的客户端指向到特定网关,常用于新老版本升级的兼容方案,目前无实际使用案例;
- 特定环境标识,如智能客服环境等,用于将特定类型的app指向到特定网关,用于较大粒度环境隔离需求。
在从LBS服务请求到接入网关地址之后,客户端会按列表中的地址依次尝试建立连接;如果严格按照这样的顺序,那客户端建立连接的过程就会偏慢,为了加速接入过程,实际上在操作时,SDK都会使用本地缓存的最后一次LBS请求返回的地址列表来建立连接,同从LBS上拿一次新的地址列表缓存在本地,以备下次使用;当列表中的所有地址在尝试过一遍均失效,则会使用默认的link地址来建立连接;默认地址也失败是会出现415或者408这种网络错误码;
在获取到目标地址之后就会尝试建立TCP长连接,连接建立之后就会与服务器协商加密秘钥,并发出第一个鉴权包,鉴权完成之后这个长连接就是一个安全有效的连接,客户端可以发起后续的RPC请求;服务器也可以往这个连接上下发消息通知;如果秘钥协商失败或者鉴权失败,这个连接就会被认为是一个非法的连接请求,服务器会强制断开;
最后聊一下加速节点的问题,为了实现连接的快速,在网关接入点的分配时会优先距离该客户端最近的节点;这里将的加速节点就是为了更靠近用户提供的一种特殊节点。
加速节点的原理背景是运营商提供给个人用户的线路,不管是移动网络还是有线网络,其质量和IDC中心之间的网络总是有差异的;如果将整个用户链路中的关键路径替换成IDC之间的网络线路,那么对提升连接的稳定性和速度是有帮助的。
假设一个处于美国的客户通过手机网络访问位于杭州的一个网关接入点,由于客户端所在的网络是一个移动网络,直连到杭州服务器需要经过的链路非常长而且可能跳转的中间节点不可预期,在中国来说,还要跨越防火墙;所以直连的情况大部分可能就是无法连接,或者连接之后频繁断线。
我们提供了多层的加速节点:加入了加速节点之后,用户的整体链路中原来不可预期的那段链路都换成了质量较好的线路,用户直连到本地的加速节点的网络往往就会好很多。
下面说说不同的投递模式对消息送达效率的影响:
问题一:怎么让消息投递并发能力倍增?
在这张图中,上半部分表示的是一个点对点型的Link服务器,当发送者A发送一条消息之后,通过Link这条消息提交到APP中处理,APP中查询到该消息接收者B所在的Link服务器是Link y,于是向Link y服务器下发一条下行通知包,Link y上再找到用户B对应的长连接并将通知下发到客户端;这种模式下,所有的接入点Link对于所有的用户来说都是对等的,他可以接入到任何一个服务器中,任何消息的发送都必须在业务层查询到目标接收者所在的Link服务器,并往相应的Link服务器下发通知包,如果是一次群发行为,那就需要在业务APP上把所有群内的成员所在的Link列表都查询一遍;这是一个比较耗时的操作;并且是随着消息接收成员的数量不断上升开销不断增大;所以如果是需要往聊天室内发送消息,由于聊天室内的成员数量非常庞大,这种模式很快就会遇到性能瓶颈,消息投递的延时会非常严重;
对于广播型的Link服务器,云信在分配接入点时首先遵循一个原则,那就是同个聊天室内的成员在分配聊天室时,尽量分配在同一组接入点上;在Link上维护了每个房间内所有的成员的长连接集合;而在App上维护的不再是特定用户和Link之前的映射关系,而是维护了特定房间分配的Link的集合;于是在任何一个成员发出一条聊天室广播消息之后,消息通过link上行到App,App只要找到该聊天室已经分配的Link地址列表,往每个Link上下发一个广播消息,Link在收到下行的广播消息之后再在本地做广播分发;这个效率比点播的模式高出了不止一个数量级;
问题二:怎么解决单节点的性能瓶颈?
在讲完了点对点型和广播型这两种Link的区别之后;云信再回头来看看另外一类基于socket.io实现的weblink的代理方案在云信中的演变优化过程;
在这之前需要再强调下WebLink中两个关键点,首先WebLink是基于Socket.io协议的,为了保证数据通道的可靠,云信需要使用Https来对通道加密,其次由于是Https的请求所以必须提供独立的域名。
图一中显示的是最早的方案,后端Weblink提供连接,并实现SSL加密,多个节点前面通过LVS做代理,域名绑定在LVS代理之上,LVS代理之上再做Keepalived方案来保证HA;这种方案对外暴露的域名只有一个,而内部实际有很多的节点,扩容对外也是透明的;Web客户端在连接时只需要直连这个唯一域名就可以,对于单一产品来说这种方式最简便快捷,客户端可以绕过地址分配的过程;缺点也集中在单一出口,如果这个单一出口受到DDOS攻击,只能通过域名换绑来规避,而域名换绑需要一定的生效时间,带来和一些运维上的代价,其次对于云信这种服务来说,单一出口就丧失了灵活性;所有客户直连到同一个入口,也无法实现专属服务和业务隔离,无法实现加速节点方案;
于是便有了第二种方案,这种方案借鉴了Link业务中的LBS分配的方式,还是在Weblink节点上实现SSL加密,并为每个Weblink节点分配独立域名,客户端在接入前先通过LBS服务来分配到合适的接入点;这种方案好处就是提供了更大的灵活性,随时可以给集群扩容,也可以动态调整特定应用的接入点地址,也提供做加速节点的可能性;但是这种方案的问题是每个节点都是单点,而且节点内还是需要做SSL编码,由于java的SSL对cpu资源开销比较大,在突发用户流量是会影响单个节点的服务能力;
于是又有了第三种方案,这种方案前端使用Nginx做七层代理,并在Nginx配置SSL和域名绑定,后端可以同时使用一组Weblink;由于使用了Nginx,在端口的分配逻辑上也更加科学,提高了运维的便捷性;最后云信就得到了目前在使用的一个组合方案,前端还是通过LBS服务来为SDK分配接入点,以此提供灵活性;后端使用多个Nginx集群做代理集群,每个集群分组的性能都得到了提高。
即时通讯平台服务化和高可用实践
前面重点介绍了云信在客户端接入层的实现和接入点的管理上使用的一些方法,通过这些技术手段为IM服务建立了一条稳定可靠的消息通道,现在来聊聊在业务层做的服务化和高可用上面的工作。
网关接入层负责客户端长连接的维护和管理,所有的接入节点甚至可以是无状态的对等节点,只负责客户端与服务器之间请求的传递的转发,并优化转发效率;而真正的业务处理逻辑还是需要有业务层来实现。
业务层需要处理大量请求并负责和DB,缓存,队列,第三方接口等组件的交互,其稳定性,可用性和扩展能力直接影响了整个云服务的质量;为了使业务层具有更好的弹性,云信在网关接入层和业务层之间引入了一个路由层来解耦;业务节点在上线之后会将自己注册到服务中心,路由节点会转接网关层的请求包,并从服务节点中挑选匹配的节点分发请求;这种三层架构使系统整体具有更好的弹性。
为了提高业务的可用性,云信会将业务节点分布到分属于不同网络的环境中,正常情况下可以同时提供服务,一旦其中一个环境的网络或者基础设施出现故障,就可以快速得通过路由层来将故障集群下线。
灵活支持灰度升级模式,云信可以将其中部分业务节点升级,然后通过路由层的配置将指定的用户流量导入到新升级的节点中;
专属服务的灵活支持,对于一些对资源独占需求比较强烈的客户,云信可以通过路由层将该客户应用下的所有流量导入到独立的集群中。