架构师于小波:魅族实时消息推送架构
系统介绍
这个系统数据情况是这样的,实时在线的用户是2500万左右,下面有一个趋势图,从今年1到10月份的都列出来了,这个系统一天PV量是50亿左右,这个系统推送速度可以达到600万条/分钟。
数据结构
系统架构设计
系统架构逻辑上划分,划分为四层,最下面的一个是提供魅族手机的接入。第二层是消息分发服务,主要的作用就是提供上行消息的路由和用户下行消息的路,这边有一个用户路由表。第三层是订阅信息,第四层是存储,包括离岸消息存储,包括订阅消息的存储。
推送系统架构
左上角有一个服务管理和业务监控,其实是两个独立的东西。有一个推送平台,是提供给业务使用的推送业务的接口。我们都有独立的服务,是有单独的DEMO的集群,可以独立的部署和独立的扩容。
踩过的坑&心得
手机的功耗问题
手机功耗问题
手机功耗问题主要涉及两个点,第一个是流量,第二个是电量。先看流量的问题,怎么样解决流量的问题,通过协议选择,现在传统的互联网上,有比较典型的使用通讯协议的话,是这两个协议,XMPP、SIP,这两个协议有很多的优点,第一个是开元组件非常多,如果想快速的搭建一脱系统出来,这两个协议是比较好的选择。不足的地方是协议很复杂,单独的标准文档就有几十页,如果完全把它看完,弄懂,估计要花长的时间,因为这两个协议是基于互联网的,并不是针对移动互联网进行优化的,所以协议是比较重的,像XMPP,有很多无用的标签,我们根本用不到这些标签,包括SIP协议,也有很多头,和XMPP协议差不多的。最重要的一点是非常的耗流量,所以我们就自己定义了IDG的协议,轻量、编解码属于快,是上面两个协议的10倍左右的编解码速度,最重要的一点是节约流量,使用中发现节约流量到50%到70%。
还有一个电量的问题我们怎么样解决?洲际电量就要涉及到长连接的保持,要想保持,就是要发送心跳包到用户手机上,固定的心跳,固定3分钟、5分钟或者是10分钟来发送心跳,网络情况是比较复杂的,有时候网络情况是好的,有时候网络情况是差的,选择最短的时间。前面有一个协议,IDG的协议来降低用户的流量问题,通过智能心跳的问题,来降低手机的电量的消耗。
下面讲一下有个延迟推送,系统使用过程中,我们发现有些用户对实时性的要求并不是特别的敏感,比如说系统的升级、应用的升级,早几分钟,或者是晚几分钟,并不会影响用户的使用体验。我们对于这种实时性要求不高的消息,可以使得手机在唤醒的状态下才把信息推送下去,我们怎么知道手机处于唤醒状态呢?这个服务端是可以感知得到的,前面讲过手机要维持长连接,就要发心跳,发心跳就要唤醒手机,服务端收到心跳包的时候,再把消息推送下去,这样就可以相对来说降低一点手机的功耗问题。通过这三个点的优化,基本上相对来说是比较完美的解决了手机的功耗问题,不过这个只是其中一步其中一个坑,我们革命还未成功。
移动网络的问题
国内的移动网络有一个特点,不稳定,因为我们不知道什么时候会掉线,还有延迟是非常大的,移动网络的不稳定和高延迟,会让我们碰到一些什么问题?首先是碰到的重复消息的问题,重复消息是怎么产生的,可以看这个图的流程。
服务端向客户端推送一条消息的时候,正常情况下,消息推送下去,客户端到这个消息,给它回一个应答,如果回应答的过程中,服务端就收不到这个应答,服务端就有两种处理方式,一种是离线,一种是超时重试,重试几次,直到收到为止,这样服务端要保持每一条推送消息的状态,这样子如果服务端后端因为不单是一个服务组成的,是有很多服务组成的,后端的调动页就会拉得很长,每一个节点上面都需要保持状态,任何一个节点出现问题,整个都会认为这个消息是推送失败了。重复消息的话,客户端发送应答,服务端没有接到这个应答,而网络好的时候,再推送一次,那就出现重复了。
那怎么解决这个问题?设置了消息基于序列号的交互方式,首先推送消息的时候,不是把消息直接推送下去,是发一个通知到客户端,告诉你有消息,客户端拿到这个通知,发送一个指令上来,说获取这个消息,会带上一个收到最近消息的最大的序列号。这里有一个大坑,也就是DNS也容易出现问题,相信很多人都碰到过这样的情况。怎么解决这个问题?用全IP的方式,要接入服务器的话,我们这边有一个WEBService,里面有很多的IP列表,IP列表都拉下来,客户端直接选取一个IP地址直接去连接,看下面第一个图的第一步,通过HTTP访问客户端的,但是也存在一个域名的问题,我们做了预埋,直接用DNS访问,如果这个DNS访问不通,就可以用预埋的IP来访问,就可以拿到一个IP地址。
海量连接出现问题
解决了IP的DNS的问题,革命胜利还有半天,还有最后一个问题了,就是海量连接的问题。大并发话,我们的目标,单机可以达到400万的长连接,我们实现C++来实现的,是性能的原因才用C++的。还有多进程+epoll,内存池防止内存碎片。这里比较推荐Tcmalloc这个是谷歌开元的组件,非常好用。最后我们还做了内核参数调优的优化,内核参数调优,系统在一台服务器上跑的时候,达到百万级连接的时候,我们发现有一个问题,其中有一个CPU的负载会非常高,其他的CPU负载相对来说是比较低的,是比较空闲的,最后我们发现说,因为我们的网卡是多个,网卡的中断都由这个CPU响应,既要响应网卡的中断,又要响应业务的需求,这个非常的繁忙,怎么解决?把这个网卡中断绑定,转到其他CPU上,达到负载均衡。第二个是TCP RTO修改,我们用2.0的系统内核,默认的只是200毫秒的,如果200毫秒出现超时重传,但是网络延迟比较大的,200毫秒的话,会经常会出现重传,会很频繁的重传,会导致协议站的性能会降低,所以我们就在内核系统打了补丁,改为3秒左右,性能会得到一部分的提升。
解决了大并发,下一个是负载均衡的问题,比较常规的做法是前面加LVS,后台有几台服务器,但是LVS并不适合我们的业务场景,一台服务器可以接受400万的长连接,一个LVS,下面挂三台服务器,LVS首先存在单点问题,LVS一台就要承受1200万连接,这个是肯定扛不住这样多的连接数的,我们就放弃了使用单点运营。第一个是服务端做,第二个是客户端做,前面也讲过,获取IP列表,在获取的时候,我们LVS已经对那些IP进行过排序了,负载比较低的服务器是排在前面,拉下来之后,客户端直接拿前面的IP地址连就可以了,在这个地方直接解决负载均衡。解决负载均衡的同时也解决了跨运营商网络慢的问题。第一次客户端拉取IP列表,前面的服务器都是负载比较低的,但是客户端是对这个IP列表进行缓存的,缓存之后,如果断线重连,再连的时候,就不知道哪台服务器负载高,哪台服务器负载高,这里就有一个策略,客户端把IP划分成多个IP区间,每一个区间有多个IP,选取IP发一个探测包到服务器,服务器会针对探测包有响应包回来的,哪个先回来,就用哪个IP地址,这样就同时解决了跨运营商的网络慢的问题。
但是这样做的话,其实还是有问题的,不能单独在客户端简单做一个发一个探测包,先收到就用这个,这个也不行,服务端也需要做一些相应的策略假设某一台服务器负载比较高的时候,刚好收到探测包,立刻回一个响应,很快的客户端收到这个响应就会连上来,但是本身的负载已经比较高了,那服务端怎么解决这个问题?根据自身的负载情况来做延迟响应,负载达到一定的阀值的时候,比如说是300万,每超过10万,延迟时间稍微加50毫秒,收到你的响应包,我晚个50毫秒之后再回应答,这样跑马策略是可以解决整个的负载均衡。
系统的监控和灰度发布
系统的监控
我们的系统是由很多小服务组成的,我们很难依靠简单的业务监控来发现未来可能存在的问题,为什么要用“可能”这个词?假如说每一个业务都是有一个单独的集群,集群中有一个节点如果出了问题,并不会影响整个业务的使用,只有当累加到足够大的程度的时候,错误累积到很大的程度,才会导致整个系统不可用,这样其实是不行的,所以我们必须要引入一套严格的监控体系,来发现未来可能发生的问题,快速的定位出来,所以针对每一个集群里面的每一个节点都要有监控,有一些比较严格的监控指标,这里列了几个我们认为比较重要的。第一个是错误计数,假如说我有一个节点出现了很多的错误计数,其实这个是需要报警通知开发人员。第二个是接发队列,接收和发送队列,如果队列中积累很多的服务,那这个很可能是过载了。第三个是请求数,为什么要做请求数的监控和报警呢?假如说请求开始前面是一个比较低的水平,慢慢的用户量越来越多,如果没有很好的报警体系的话,有可能有一天瞬间把你整个集群冲垮,也就是说达到一定量的时候提前预警,整个业务需要加服务器了。第四个是处理的接口的调用延时,正常业务调用是1毫秒以内,如果超过了,假如说某一天发现有大狼的100毫秒或者是200毫秒的处理延时,这个业务可能是出现问题的。最后是服务可用性检查,是系统服务不可用了,这个肯定是需要报警的,这个是强指标。
灰度发布
灰度发布,是用户无感知的发布,还有用户可以平滑的迁移,用户平滑迁移之后,我们做在灰度里面,负载均衡里面也是可以用到这个功能,假设一个集群里面,某一个节点的用户数比较多的时候,可以把负载高的迁一部分用户到负载比较低的节点上面去。
灰度发布的流程是这样的,首先是一个节点的发布,一个节定发布之后,跑几天看一下,如果没有什么问题,灰度放量,扩散到几个节点,这个集群里面选几个节点,让这些用户去用,如果还是OK,没有什么问题,我们就扩散到全部的节点。
在没有灰度发布之前,开发人员的状态大概是这样的,因为以前发布的话,基本上都是深夜,都是夜深人静的时候发布,看是否有异常的情况。引入了灰度之后,开发人员就是这样一种状态。
作者介绍:于小波,2011年加入魅族,现在在移动互联网部门,主要从事服务端后台开发工作,专注于系统高并发,分布式等解决方案。