于小波,系统架构师,2011年加入魅族,主要从事服务端后台开发工作,专注于系统高并发,分布式等解决方案。

直播实录
关于实时推送系统的那点事

大家好,我是于小波,2011年加入魅族,现在在魅族移动互联网部门,主要负责服务端后台架构设计和开发工作。

很感谢ChinaUnix给的这个机会,非常荣幸可以和大家在这里分享我们魅族的一些技术。下面,我们进入今天的主题:关于实时推送系统的那点事。

 

今天的内容主要分4个方面:系统介绍、架构设计&微服务、踩过的坑&心得、监控和灰度发布。重点介绍一下第三点,也就是一个心得分享。

6402

我们先介绍一下这个系统。

6403

魅族推送系统主要为魅族用户提供以下服务:系统&应用升级、查找手机、联系人同步、应用商店、在线音乐、阅读、游戏中心等,这里就不一一列举了。

6404

我们实时在线用户是2500W左右,日PV 50亿,在现有资源的情况下,推送速度最快可以到600W/分钟。

6405

这个是我们的系统架构图。

从逻辑上划分了4层,最下面的是接入层,为用户提供TCP长连接的接入和http服务。

第二层是消息分发层,主要功能是上行业务消息的分发到各个service,下行推送消息路由到用户所接入的接入服务器,再由接入服务器发送到指定的用户。路由表就是用来保存用户的长连接信息和所在的接入服务器的位置。Webservice的功能后面会提到。

第三层是业务逻辑层,主要处理不同的业务逻辑。

第四层是存储层,存储用户的离线消息和订阅消息。

还有两个比较独立的监控平台和服务管理。

这个系统的是由很多小的服务,每一个服务功能都比较单一,而且是独立的集群,可以单独部署。这里的服务都是异步无状态,要求高并发消息处理延迟低于1ms。

还有一个推送平台,不在今天的讨论范围。

我们在开发这套系统的过程中,碰到了很多问题,下面列了几个比较典型的问题和大家一起分享。

6406

首先,是微服务的问题:

因为所有服务都要求高性能,所以我们开发了一套RPC框架 在魅族内部叫kiev。kiev碰到了两个问题:

1、同步调用

最开始我们这套框架都是同步调用,使用简单,服务的开发效率高。可是随着用户量的增多,性能已经满足不了我们的要求。而且同步调用,我们为了提高性能使用了多线程,很多多线程的问题随之而来。于是我们改进了我们的框架,使用异步。

2、异步问题

非常多的回调函数,一套完整的业务逻辑被打散在各个回调函数来实现,代码的可维护性差,开发效率也不高,而且还有一个很突出的问题,我们在项目中使用了redis、mongodb、mysql的lib库,而这些库都是同步的,如果要做成全异步 那工作量会非常大。后面我们参照go语言用C++实现了一个协程版本的kiev,hook系统的IO调用 比如 send,recv等,把这些系统调用改成异步,达到的效果就是同步的调用,异步的性能。

6407

我们碰到的第二个问题就是手机功耗问题。主要有两个点:

1、手机流量消耗

6408

这里就涉及到选择怎样的协议,传统的方式就是XMPP和sip 这两个协议是纯文本协议,非常多的开源组件,能够快速的搭建一套系统,但是这两个协议都是互联网时代的产物,非常消耗流量。协议本身也非常复杂、冗余,单标准文档就是几十页。

为了降低流量,我们的系统使用的是自定义的二进制协议,可以高度定制,编解码的速度是上面两个协议的10倍以上,流量节约了50%-70%。

2、手机电量消耗

6409

因为我们是tcp长连接服务,手机端为了保持这个长连接需要定期的发送心跳来维持。

一般的做法就是固定3分钟或者5分钟发一次心跳。因为发送心跳需要唤醒手机,如果心跳消息太频繁 就会导致电量的消耗比较大,如果太久发一次心跳又没法保证连接的稳定性。

所以我们根据不同的网络情况 指定了一套智能心跳模式,根据当前的网络情况来设定发送心跳消息的间隔。

64010

还有我们有一个延迟推送的策略。其实很多消息对实时性的要求并没有那么高,比如说系统升级的推送,用户早几分钟或者晚几分钟收到升级的推送并没有多大的影响。针对这种情况,我们对于实时性要求不高的消息可以在手机处于唤醒状态才推送,那问题来了,服务端怎么知道手机是唤醒的呢? 其实很简单,收到用户的心跳包,再推送消息。

第三个问题消息重复问题。

移动网络的特点是不稳定、高延迟。服务端发送消息给客户端,客户端收到消息返回应答,如果应答返回失败了,服务端没有收到这个应答怎么办?

64011

1、超时重传 这里就需要服务器保存每条消息的状态那么服务端的逻辑就会非常复杂

2、等下次客户端连接上来之后再重传。

不管是怎么样,这条消息都会重新发送,就导致客户端收到重复消息。

解决办法:改进消息流程如下图。

64012

当客户端有消息的时候只发送一个通知告诉他,然后客户端自己上来拉取消息,当然需要告诉服务端从什么地方开始拉取。所以客户端需要保存最近收到的消息最大的序列号。

这个流程的好处就是客户不会拉取到重复消息,而且服务端不用保存每条消息的状态,真正做到了业务无状态。

第四个问题,移动网络的DNS问题。

64013

运营商的DNS服务是很不靠谱的,经常宕机,延迟也很大,还容易被劫持。我们采用了全IP的接入方式。

具体就是客户端通过http服务拉取接入层的IP列表,然后选择一个IP直连。

这里访问http服务的时候也可能会被劫持,我们使用预埋IP的策略,优先使用域名访问,域名不可用使用预埋的IP访问。

第五个问题,海量连接的负载均衡问题。

64014

我们单台接入服务器可以承受400W的长连接。如果使用LVS来做负载均衡肯定是不行的,首先LVS存在单点问题。

我们解决的方式:

64015

1、在客户端获取IP列表的时候,其实就是已经排序过的,负载低的服务器IP排在前面。

2、客户端跑马策略,客户端随机选择几个IP 同时发送探测包 哪个IP响应快就用哪个IP,这里服务端需要一个策略就是收到探测包 需要根据自己的实际负载情况决定是否延迟返回。这个跑马策略解决了负载均衡的同时也解决了跨运营商网络访问慢的问题。

64016

关于监控:我们的系统是由很多的小的服务构成,每个服务都是单独的集群,如果集群中一个服务出问题,并不会影响整个业务的使用,但是如果这种问题累计起来,最后可能会导致系统不可用。

所以,只是单纯的依靠简单的业务监控,很难发现未来可能出现的系统故障,所以 我们需要一套严格的监控体系来发现潜在的问题。我们针对每一个服务都定义了一些强监控指标。比如:

64017

最后说一下灰度发布,灰度发布非常重要,线上的很多问题都是发布引起的,我们为了降低发布的风险,引入了灰度发布。灰度发布流程如下图:

64018

在没有灰度发布之前我们开发人员都是凌晨才敢发布,所以我们的状态就像下面这张图。

 

有了灰度之后再也不用熬夜发布了。

 

好了,今天的分享就到这里结束了,感谢大家的支持,如果大家有什么问题的话,我们可以直接在这里一起探讨,也可以加我微信私下探讨。

【互动筛选】

Q1:灰度是什么?

A1:灰度是介于黑白之间的,平滑发布。

Q2:重复消息的问题,可否客户端保存最近接收成功但应答失败的消息id,重复推送的消息客户端直接忽略?

A2:这个客户端不知道应答发送失败了。

Q3:推送的到达时间和一次到达率能达到多少?

A3:推送到达时间300ms以内可以到达。到达率理论上是100% 因为我们有离线消息存储,推送不成功的消息会下次再推送。

Q4:移动端的消息推送,和PC端的消息推送,区别在哪儿?实现的难点又在哪儿?

A4:移动端有尽量少的功耗( 电量和流量),pc端不用考虑这个问题。而且移动网络非常不稳定。

Q5:接入服务器均衡,负载高低度量指标是什么,链接数还系统级资源负载,或者其他的?

A5:链接数是其中一个指标。

Q6:如果移动端三天或三十天未上线,服务端要保存这么久么?

A6:不会,我们离线消息有超时机制,一般都是7天。

Q7:关于预埋ip策略,老师能讲解下吗?

A7:预埋IP就是在手机端写死IP地址,如果需要变更 直接推送新的IP地址。

Q8:关于灰度发布这项技术魅族有打算开源吗?

A8:目前还没有计划。

Q9:还有一个非技术问题,设计数百万规模的推送系统,由于公司不太自信,是购买商业产品还是自研好?

A9:如果你们有100W用户了 当然是自己做,如果还没有 可以用一些开源的。有100W用户了说明你们的产品很好,当然要做的更好,吸引更多的用户

关于实时推送系统的那点事
标签: