• Feeds

  • 应用层的容错与分层设计

    针对在项目中碰到的一些容错设计问题,团队最近进行了一次技术沙龙,讨论了以下话题。

    为什么需要应用层的容错设计?

    一个完整的系统在内部是由很多小服务构成,服务之间以及服务与资源之间会存在远程调用。

    • 每个系统的可用性不可能达到100%
    • 各种网络及硬件问题,如网络拥堵、网络中断、硬件故障……
    • 远程服务平均响应速度变慢

    服务器平均响应速度如果慢下来,慢慢消耗掉系统所有资源,进而导致整个系统不可用。因此在分布式系统中,除了远程服务本身需要有容错设计之外,在应用层的远程调用的环节,需要有良好的容错设计。

    应用层的容错设计有哪些方法?以下是微博团队使用过的一些实践。


    访问MySQL的容错设计

    • 写操作:如果master异常,直接抛异常。
    • 读操作:如果slave有多个,先选择其中一个slave,如果获取连接失败,再选择其他的slave,如果全部不可用,最后选择master。


    访问Memcached/Redis的容错设计

    首先设置so_timeout,避免无限制等待;服务器连接如果IO异常,设置错误标志,一段时间停止访问;出错后定期主动(比如ping Redis)或被动(当被再次访问时)探测服务是否恢复。

    Failover机制:
    如果连接某个node失败, 当前pool启用一致性hash切换到backup node;如果backup node没有数据,则通过另外一个服务池(数据副本)获取数据。


    访问远程HTTP API的容错设计

    设置so_timeout;部分场景:短超时,重试一次;另外由于HTTP service情况的多样性,业务层面还有通用的降级机制。


    访问不同资源使用不同方法存在的问题

    从上面列举的部分场景来看,在访问不同资源时候,每种client访问都有一些相通的原理,但却要使用不同的重复实现。由于各个client独立实现,实现时候由于各个远程服务协议及行为的差异,导致这些容错原理无法直接复用。另外在代码层面,不同的client也使用了不同年代的一些底层库,一些早期client的实现,数据层,连接层,协议层全部耦合在一起,也造成维护成本进一步加大。

    比如之前一些服务开发中碰到的类似如下的问题:

    • hbase-client由于没有实现容错设计,导致访问出现了抖动,影响了同一服务池的其他调用,需要增加类似MySQL client的容错及快速失败策略;
    • MySQL slave流量出现不均衡了,由于多个slave IP之间没有使用公用的负载均衡策略,因此需要重新添加、上线及验证。

    另外目前分布式系统中大部分远程资源都是IO bound而不是CPU bound,而client大部分又是同步调用,造成大部分调用都在等待远程返回,同时也消耗了工作线程资源,以及大量线程context switch。


    有没有可能统一的client?

    这些策略原理上是可以公用的,能否出一个统一的client层来一劳永逸?不过这个需求不是twitter干过吗?

    Finagle,不仅是平时理解的RPC框架,还有目标是想成为一个commons client,从另外一个层面,广义上访问远程资源也都可以理解成RPC,所以Finagle也常称为RPC框架。

    Finagle implements uniform client and server APIs for several protocols, and is designed for high performance and concurrency.

    在Twitter体系,分布式服务可以从future, service, filter三个层次理解,容错、超时、授权、tracing、重试等机制都是体现在filter中;而future则将client从多线程、队列、连接池、资源管理释放出来,从关注控制流到关注数据流。并且默认变成异步方式。

    Finagle的FailFast模块会避免分发请求到出现问题的服务,它通过来记录到每个host的错误来进行标记,当出错以后,Finagle会通过一个后台线程定期重连以检查是否恢复。当host宕机时,相关的service会标记成不可用。


    如果来redisign一个通用的网络client,它应该包括哪些元素?

    • 具有服务的分层设计,借鉴Future/Service/Filter概念
    • 具有网络的分层设计,区分协议层、数据层、传输层、连接层
    • 独立的可适配的codec层,可以灵活增加HTTP,Memcache,Redis,MySQL/JDBC,Thrift等协议的支持。
    • 将多年各种远程调用High availability的经验融入在实现中,如负载均衡,failover,多副本策略,开关降级等。
    • 通用的远程调用实现,采用async方式来减少业务服务的开销,并通过future分离远程调用与数据流程的关注。
    • 具有状态查看及统计功能
    • 当然,最终要的是,具备以下通用的远程容错处理能力,超时、重试、负载均衡、failover……

    分布式服务框架的4项特性

    在移动及云时代,尽管大部分可扩展的问题可以通过云平台解决,但是服务本身的扩展性挑战仍然存在。比如一个新的项目,用PHP或JSP实现了基本功能,部署在Apache或Tomcat等容器上,在业界这种部署在一个容器内的功能模块通常可以称为一个service。服务容器很容易通过EC2或者docker等方式来扩展部署更多的实例。但service本身的管理的以下几个方面的问题仍然需要架构师去设计及解决。

    1、服务的远程调用(RPC)。

    随着系统的用户访问规模增大,以及系统功能的增多,众多的功能模块(service)很难用单个service来承载,这些不同功能的service可能由不同的开发团队开发,甚至使用不同的开发语言,最终部署在不同的服务器容器内。Service之间的调用需要一种协议及远程调用的实现,需要具备灵活的data type支持,对调用双方透明(理想情况它就像在执行本地调用),并且具有良好的性能。比较典型的RPC实现有
    Google: Protocol Buffers RPC
    Facebook: Thrift
    Twitter: Finagle

    2、服务的分布式调用链及服务状态跟踪统计

    随着系统内部的服务增多,一个功能的调用可能会通过系统内部多个服务相互调用来完成,并且这些服务可能由不同的团队开发,并且分布在不同的服务器容器,甚至可能在多个地域不同的机房内。因此分布式系统需要有一种方式来清晰的了解系统的调用及运行状况,测量系统的运行性能,方便准确的指导系统的优化及改进。

    由于trace的主要功能都是依赖日志输出来完成,因此通常也需要建设相关的分布式日志系统及数据实时分析展示系统等,分布式日志收集及数据实时分析也是一个非常大的话题,本文不展开详谈。比较典型的trace系统有
    Google: Dapper
    Twitter: zipkin

    3、服务的配置管理。包括服务发现、负载均衡及服务依赖管理。

    比较常用的服务发现是域名方式,调用方通过域名来定位目标服务。域名寻址方式可通过配置多IP(或VIP)来实现负载均衡。域名方式存在一些弊端,常用的DNS服务器通常是上个世纪的产物,管理繁琐,更新域名记录后由于协议设计上存在的TTL缓存机制,修改不能立即生效,也无法做变更的push操作。更高级的特性如控制流量、灰度发布等也无法实现。

    目前,成熟的分布式服务较多使用基于ZooKeepr的配置服务,ZooKeeper由于与client保持长连,因此具有push能力,可以迅速的调整配置及生效。但由于ZooKeeper本身只是一个通用工具,分布式服务具体场景各种高级特性还需要自行在此基础上实现。

    基于DNS的服务发现在负载均衡方面具有明显缺点,由于多IP或VIP使用类似round robin的策略,在实践中,同一服务的多个IP之间负载通常并不均衡。而服务提供方并没有有效手段对runtime期间不均衡进行调整。基于ZooKeeper的配置服务可以支持各种复杂的特性,但需要做一些定制化开发,但服务发现作为系统中最核心的一个环节,这些定制化开发需要较多的经验的积累及沉淀,之前在线上就碰到过配置服务将一个服务池中疑似不稳定的节点移除过多,从而导致出现雪崩的情况。

    除了ZooKeeper之外,业界还有一些类似工具,如serfdomconsui

    4、服务之间的调度及生命周期管理

    目前大部分服务的部署都是按照事先的规划安装在机房不同的服务器上,配置服务通常只是起服务节点的failover作用,业务中真正按弹性调度来运作的系统还不普遍。但原理上所有的service可以看做是MapReduce中的task,它的调度及生命周期可以很高效的由分布式容器来管理,并且根据service的属性来灵活的分配资源,比如控制CPU的核及内存大小。

    业界比较成熟的有Apache旗下的MesosYARN。它们的特性有点类似,但是由不同的组织开发。Mesos主要功能是由C++来实现,可以支持docker container来进行调度,因此它的实现更偏底层一些。Yarn是Hadoop 2的一部分,可以更灵活的来调度MapReduce jobs,它是一种JVM内部的实现,可以很好的支持JVM级别的任务分配及调度。

    上面介绍了这么多,主要是最近考虑团队在上述1-4之间做一些事情。一方面目前业界在这几点之间还有一些缺失或者欠优化之处,另外1-4点之间也可以适当做一些实现的整合。整合并不是要产出一个大而庞杂的软件,我个人是极力反对大而全,也不喜欢沉重的框架,业务的service实现方不应该import太多工具或者SDK,因此将要做的功能肯定是透明及可插拔的。

    由于这件事情并没有严格的可参考目标,因此对于最终实现的是哪个子集还存在一些不确定因素,这个项目如果具有通用性,也考虑以开源的方式来开发。对这方面有什么想法,欢迎留言。

    开源移动通讯架构与XMPP

    XMPP由于上下游良好的开源生态得到了广泛的采纳与应用,但是到了移动为主的时代,XMPP的不足也暴露出来。

    XMPP全称是Extensible Messaging and Presence Protocol(也称为Jabber),是一种支持消息及状态的协议,但在线状态在移动场景并是一个必需的feature。由于智能手机具有随时在线的特点,状态可以视为永远在线;即便在app没有打开的情况下,系统可以通过push等方式发送最新的信息,因此大部分面向移动的通讯软件直接去掉了状态的特性。因此设计成支持多终端状态的XMPP在移动领域并不是擅长之地。另外一方面XMPP是一种基于XML的协议,它的请求及应答机制也是主要为稳定长连网络环境所设计,对于带宽偏窄及长连不稳定的移动网络并不是特别优化,因此它的弊端就充分暴露出来了。

    移动领域也有不少非XMPP的开源实现,尽管赢得主流认可的不多。比较商业化的有telegram,其特性跟微信比较类似,在一些国家也取得了飞速发展,尽管其宣称的开源还不完全。国内有前几天蘑菇街发布的的TeamTalk, 其开源地址是 https://github.com/mogutt 从发布的短短几天来看, star/fork 数非常可观。其介绍支持企业内部通讯场景,且具有完整的移动端支持。

    最近也在考虑在这方面做一些尝试,一个理想的适合移动时代的IM开源软件,它应该具有哪些因素?

    • 分布式扩展能力。在XMPP领域,由于Openfire的简单易用,成为很多团队首选的方案,但使用Openfire的团队都需要接着思考扩展openfire的分布式扩展能力,以便承担更大的用户访问规模。
    • 移动友好的协议,协议具有良好的长连及短连自适应能力,具有数据的增量更新能力,较低的重连成本等。有网友推荐MQTT http://mqtt.org/,尚未深入评估。
    • 移动SDK,主要实现协议层及网络逻辑,以简化客户端接入及开发成本。
    • 开发语言,更多考虑一些能带来编程乐趣的新型语言,在一定程度上,scale的问题可以做到与语言无关。

    在微博上交流的时候,一些网友还提到了XMPP的federation功能,federation类似邮件协议,利用XMPP可以实现多个域的用户互联互通。但由于此功能对于企业的商业化利益较难看清,因此在过去很长一段时间都没有得到充分发展。Google最近也放弃了多年来坚持的XMPP federation的支持。

    需要补充的是,尽管前面提到XMPP种种问题,但是也别低估一种新协议的认知成本,很多时候选择XMPP并不是因为它协议强大或多么适合用户的场景,而是当大众群体已经了解一种协议之后,即使这种方式存在种种问题,但还是较难广泛认可及接受一种方式或协议。在没有特殊原因的情况下,普通的通讯场景仍然建议使用XMPP方式。