正文
我们的量级比较庞大(同时在线1.5亿+,日消息量50亿+),别的业务不容易遇到的事情在我们这边更容易发生,例如性能问题。
我们面临的运营环境不尽完善,机房故障、网络故障、磁盘故障、机器死机等情况时有发生,如何从设计上避免这些故障带给我们的风险也是我们需要考虑的重点。
我们使用的一些第三方组件不一定是非常可靠的,如何选取合适的组件,如何规避地基不稳带来的影响,在架构设计和技术选型时也要特别注意。
来自我们自身的挑战,我们无法保证自己的程序不出bug,也无法保证自己的操作不出意外,如何从流程和规范上尽量避免人为因素造成的影响也是非常重要的。
理清风险因素之后,剩下的事情就是去一一解决这些风险,规避风险的发生,良好的架构设计、谨慎的技术选型和合理规范的流程是其中的三剂良方。下面将重点从缓冲、解耦、服务去状态、服务分级等几方面介绍一下小米推送在提高系统可用性方面做的一些尝试。
架构设计是高可用性的根基,一个好的架构可以避免绝大多数风险的发生,将影响可用性的风险因素扼杀在摇篮里。在做架构设计时,我们需要明白我们要解决的首要矛盾是什么。
对于推送系统来说,我们面临的主要问题是系统流量随时间分布不均衡以及系统容易过载的问题。我们面临的请求来源主要是两个,一是来自设备的请求,这部分连接数多,请求量大,但总体可控,只要我们设计好足够的系统容量,基本不会出很大的问题;另一个是来自开发者的请求,这类请求属于不可控类型,所有的开发者都希望在尽可能短的时间内将自己的消息推送出去,我们无法提前得知开发者请求发送的时间以及发送的数量,它属于脉冲式的访问类型。由于设备活跃时间的原因,开发者的请求时间一般极为集中。
对于这类请求,我们不可能为峰值准备足够的容量,这会造成极大的资源浪费。但如果我们不做提前预防,极有可能我们的系统会被高峰期的瞬发流量压垮,因此我们需要引入一个缓冲机制。
这属于典型的消息队列(Message Queue)的使用场景。消息队列是一种服务间数据通信的常见中间件,一般使用producer-consumer模式或publisher-subscriber模式,除了缓冲的作用之外,解耦和扩展性也是我们采用它的重要原因。常见的消息队列组件有Kafka、RabbitMQ、ActiveMQ等等,可以根据业务性质以及队列的特点选择合适的组件。
在推送系统中我们大量使用了消息队列(MQ)组件,将开发者的请求缓存在消息队列中,然后逐渐消费,缓解开发者集中式的推送带给我们系统的瞬间压力。上面第一张图是我们接入层接收到的开发者请求量,高峰期的请求量是平时的数倍甚至数十倍,第二张图是我们业务层使用MQ之后处理的请求量,可以看到曲线平滑了许多,缓冲效果相当明显。(这是在我们系统本身处理能力非常强大的情况下,否则缓冲作用会更加明显)
耦合度是判断一个系统是否健壮的重要标准之一。耦合度高的系统在稳定性、容灾和扩展性方面都不容乐观,常常会因局部故障扩散传染到其他模块,而导致故障恶化,受影响面扩大,甚至影响整个系统的可用性,给系统带来较高风险。因此,系统解耦是我们设计一个分布式系统时需要重点考虑的问题。架构分层、服务拆分、通信解耦、代码重构等是降低系统耦合度的比较常见的解决方案。
首先是代码解耦。
代码耦合会使代码的维护变得异常困难,极大的增加了代码阅读和理解的难度,并增大了出现bug的几率,另一方面,代码的耦合也常常使模块逻辑上的关系变得复杂。因此,采取一定的手段进行代码解耦是我们提高系统可用性的基础一步,例如更加良好的代码结构设计,更加巧妙的抽象层次,定期的代码重构等等。