• Feeds

  • 在线服务的黑天鹅

    软件随想录(More Joel on Software)有这样一段话

    提高服务稳定性的最大困难,就是”黑天鹅难题”(problem of black swans)。这个名词是由Nassim Taleb提出来的(www.edge.org/3rd_culture/taleb04/taleb_indexx.html),他这样定义:”黑天鹅代表外来因素,是一个超出正常预料的事件。”几乎所有的互联网服务中断,都来自于意料之外的突发事件,属于极其小概率的非主流意外。这类事件是如此罕见,以至于常规的统计方法—-比如”故障间隔平均时间”—-都失效了。”请问新奥尔良市发生特大洪水的平均间隔时间是多少?”

    Tim这两天也是刚忙完InfoQ的ArchSummit的演讲,正在利用周日休息一下,但是一醒来就收到消息说某服务有些问题。于是赶紧连线了一堆正在处理事件的工程师,等拿到初步的原因分析之后,已经4小时之后了。

    “墨菲定律”,一位工程师说,“前几天觉得这个地方有些隐患,已经存在一段时间了,打算下周来处理,未料它今天就出了问题……”。墨菲定律是指“凡是可能出错的事必定会出错”,指的是任何一个事件,只要具有大于零的机率,就不能够假设它不会发生。因此在线服务发生问题之后,总有工程师随即证明墨菲定律的有效性。

    不过我觉得用黑天鹅难题更能体现在线服务可用性的不可控,可用性是指一个系统中提供服务与设计时间的比例,通常用百分比来表示。在线服务通常看到最多的是以下3种

    • 99.9%,服务中断时间:525.6分钟/年
    • 99.99%,服务中断时间:52.56分钟/年
    • 99.999%,服务中断时间:5.256分钟/年

    当一个系统有大量用户使用之后,对系统可用性有较高要求,互联网服务通常会把可用性目标定在99.99%及以上,但极少能达到99.999%的。Tim有一个服务由于功能简单且稳定,较少需要变更代码,且有容错的设计,服务池没有单点问题,所以跟同事们说2014年可以达成99.999%了。未料这个服务池最近被一个极偶然的扫描脚本全部干掉了,尽管运维工程师马上进行了处理,但是最后也较难满足1年低于5分钟中断的期望。虽然这个是个偶然的案例,但是它却是典型的黑天鹅事件。

    对于系统服务可用性的问题,在专业领域其实有3个词汇去描述的。描述的顺序通常是 fault -> error -> failure。这方面大多定义引用来自《Patterns for Fault Tolerant Software》一书,在书中描述如下。
    fault-error-failure
    用一个通俗的例子来描述三者的定义是,如果把fault比作是数据库网线断了,则error是网络不通连不上数据库,导致的failure是不能注册用户。由于error及failure是不可避免的,所以在代码设计上更多的是做容错(fault-tolerance)。

    我们可以通过服务之间进行良好的容错设计,进一步用代码review,关键路径的梳理来确保容错策略的落实。上线的checklist来确保变更出现异常时候的应对。即便如此,容错只能帮助我们规避大部分已知的问题,随着系统长时间运行,总是有意外情况出现,曾经有同事碰到关键的服务中出现内存出错这种小概率事件,查出来之后,当事的工程师的肯定为了怎么写好问题总结那一段话在绞尽脑汁。

    当团队规模比较小的时候,服务本身可控性还是比较强,关键路径中的每一段代码团队成员非常熟悉。当出现异常问题,团队成员也可以快速拿出处理方法来解决。但是当系统变大,每个团队只参与大系统一个环节时,问题会变得更复杂。从概率的角度,大的系统中小的模块的failure不可避免,容错流程总是存在疏忽的地方。当系统中存在复杂的网状调用,无法完全做到松耦合(理想的松耦合是指一个服务的失败不会引起另外一个依赖服务的失败),因此任何一个模块中,可能由于一个缺乏经验工程师的一行不经意的代码造成整个大系统不可控的后果。

    大型系统不可预知问题的排查通常需要更多时间,需要多个团队共同参与来定位及解决问题。但在线服务由于可用性的要求,出了问题之后,解决问题的紧急程度会比分析问题更高,因此并不会第一时间来讨论及分析问题,现场的工程师需要凭有限的现象迅速做出判断,将问题消灭在萌芽阶段。但正是由于缺少完整分析问题的时间,故障也往往难以第一时间有效解决,问题延续的时间比预期的要久也成为常见的现象。

    对于公司来说,肯定希望所有的服务都有99.999%的可用性;但由于黑天鹅的现象,完美的可用性较难达到,这也很容易成为工程师的心理负担。当稳定性出现问题后,负责的技术团队心理沮丧程度不会比一个战败的队伍更好,甚至一些工程师还会造成长期心理上的压力及阴影。夜深人静时候电话突然响起,第一反应会心头一紧,“莫非是服务出问题了?”。

    工程师应该用什么样的心态去看待出现的问题?

    一方面,各种故障failure它确实是一种客观存在,给用户访问及体验带来了不便。我们不会通过回避问题来避免它的出现。当出现问题后,需要通过问题复盘的方式,帮助我们来重审事件的经过,检查流程、机制、代码等共性层面的问题,避免在同一个地方再次踩坑。同时也可以反思团队在项目中的表现是否达到了平均以上的水平,是否存在一些低级错误?

    在另外一方面,如果问题超出了之前的认知及应对策略的范围,属于黑天鹅式的问题,则没必要太多的自责。具有完美心态的工程师需要理性的看待各种批评及质疑,毕竟在一定程度,这是业界在共同应对的一个类型的难题,这些不稳定问题的出现也是在线服务的一部分。

    Feed架构-我们做错了什么

    在过去几年,所在的微博技术团队在一定程度成功解决了feed架构的扩展性与性能的问题,大部分精力已经从应对峰值性能或者数据扩展中解放出来。

    feed arch

    几天前,拿着上面这张架构图问内部一些架构师,目前完成的工作及存在的主要问题是什么?

    完成的工作不出意料,大家的观点比较类似,主要在架构的工程成熟度方面。

    • 解决了数据规模大且长尾访问的问题,通过按时间线的冷热分区设计,在MySQL等数据库上成功实现了低成本的长尾分区实现。由于社交产品中大部分访问(90%以上)发生在最近的数据,而较旧的数据(长尾数据)相对访问偏少,因此在存储上利用时间维度进行不同存储区域区分,来达到性能及成本的收益。
    • 解决了数据存储可扩展的问题,可以满足3年左右数据扩展的需要。这个主要利用成熟的数据库拆分方案,简单的实现通常设计成倍扩容,在最初建表时候预留足够多的分表,当容量增长时将这些表迁移到更多服务器上去。
    • 解决了百万QPS访问的问题,主要依赖缓存分区及复制机制的成功应用,通过缓存复制及分级,可以让每秒上十万次访问的数据获得非常好的访问性能,同时数据复制机制可以提高可用性,防止单点机器故障时给数据库带来范围压力。
    • 解决了可用性及错误隔离问题,得益于历年来建设的SLA体系,服务之间有良好的服务契约及错误隔离手段,因此核心功能 有99.99%+的可用性。

    但是对于不足呢?如果架构重新再来,你会怎么做?这个问题回答比较多元,经过整理一些主要观点如下。

    首先是从解决架构的问题说起,上述feed架构主要解决了在社交媒体环境中,实时及可扩展的解决基于关注关系的数据分发问题。

    由于微博是一个实时的网络,人们常把Twitter/微博这种社交媒体产品比作是地球的脉搏,它确实无时不刻都在产生海量的数据,并且产生着不可预测的大量随机峰值访问,业界也是首次随着社交网络的出现遭遇这种架构上的scalability问题,记得在2009-2010年左右,Twitter由于压力过大经常出现鲸鱼页面错误。经过几年的努力,包括微博在内的业界主要公司都已经成功解决了关系模型的数据分发架构问题。

    但在用户的层面,使用社交媒体产品依旧被信息过载的问题所困扰。

    • 虽然每天收到大量信息,但是大部分不是自己感兴趣的信息。
    • 基于用户关注维度的兴趣阅读效率不高,用户关注了一个兴趣领域的同行,但这个同行却大部分时候在谈风花雪月。
    • 由于传播的低成本,大号在过度的使用消息通道,生产大量用户未必感兴趣的低质内容。
    • 广告及普通内容的界限很模糊。
    • 由于利益关系,僵尸帐号及内容通过变换形式在大行其道。
    • 内容同质化及雷同问题,内容被精英占领,小人物声音不能得到有效传递。

    在社交网络中,上述问题,不可能全部像Google的使命那样,通过技术手段得到解决。但是大部分又跟技术密切相关。

    • 基于用户维度组织内容高效满足兴趣阅读的难度。在上图中,绿色的框中是数据流经的主要通道,但在传统的SNS架构中,这些数据的组织都是基于用户纬度。从用户纬度组织数据较好解决了传统feed模型基于关系聚合数据的问题。但正是由于数据是从关系维度来组织的结构,造成并不能给改进上述阅读效率问题比如兴趣维度的阅读目的带来便利。由于在一定程度信息过载,兴趣阅读是发展方向,但是在架构上目前并没有清晰的解法。
    • 信息识别及低质内容鉴定的技术挑战。虽然业界已经在大数据、自然语言处理等方面积累了不少经验,但在具体场景中,信息识别仍然还在比较初级的阶段。低质内容不一定是垃圾广告,只是当前浏览者不感兴趣。
    • 反垃圾算法的难度。僵尸及垃圾数据变换着出现的形式,目前,至少从实时性算法上识别垃圾及僵尸仍然有很大挑战。

    社交网络的信息架构和搜索引擎有很大的不同,在浏览feed是并不像使用搜索有明确的搜索意图,Facebook曾经尝试使用EdgeRank来解决newsfeed算法的效率问题,但是结果并不如预期理想。由于社交关系的存在,用户对出现(及不出现)什么内容的可解释性非常敏感,不能接受好友的信息在排序结果不能看到。除了技术,还有用户体验的问题需要考虑。

    综上所述,feed架构虽然取得了架构及工程在scalability方面的一些问题,但更大的问题及依旧在思考及解决的途中。

    Feed消息队列架构分析

    最近一两年,大部分系统的数据流由基于日志的离线处理方式转变成实时的流式处理方式,并逐渐形成几种通用的使用方式,以下介绍微博的消息队列体系。

    feed mq

    当前的主要消息队列分成如图3部分:

    1、feed信息流主流程处理,图中中间的流程,通过相关MQ worker将数据写入cache、Redis及MySQL,以便用户浏览信息流。传统的队列使用主要是为了将操作异步处理,起到削峰填谷的作用,并解除多个序列操作之间的耦合关系。
    2、流式计算,图中左边的流程,主要进行大数据相关实时处理。
    3、多机房处理,将数据分发到多个机房,由于微博使用了一种多机房数据对等架构,通过消息队列将数据同步到多个机房,因此在每个机房都会有一个类似虫洞这样的消息收发模块IDC exchange MQ。

    系统的主要单元
    mq:消息队列,新浪的memcacheq,是一种单机的类似redis queue的一个一对一队列系统。
    firehose:微博数据统一队列,使用http及memcache协议,由于在业务上每份数据会被多个项目使用,firehose主要用在一对多场景,且支持at-least-once投递保证,类似Apache Kafka。
    worker:队列处理程序,自己开发的程序,完成特定的任务如将数据从队列取出入库,类似Apache Storm的bolt。

    架构主要特点
    实时性:所有的数据在100ms之内处理完成,包括存储型任务。
    可扩展性:系统主要单元mq, firehose, worker都是无状态设计,同一个pool每个节点可以处理相同的工作,因此可以线性扩展。
    可用性:由于上述的无状态设计,可用性可达 99.999%,自动failover,无单点。

    数据流设计特点
    统一的数据推送通道 firehose
    统一的标准化的格式,所有数据采用内部的protocol buffers格式输出

    目前在实时数据流及处理方面存在多种技术,为什么没有采用相关成熟的解决方案?
    LinkedIn Databus
    LinkedIn databus主要原理是基于数据库变化事件的触发写入一个统一的databus,所有的业务都消费databus,和firehose解决的问题比较类似,不过firehose是业务主动写入的事件,在早期做多机房架构时候也曾经尝试过类似databus数据库触发方案,但在一个高度拆分的数据库场景中,一个业务的操作可能对应多个数据库操作(比如修改索引表和实体表),多个数据库操作并没有严格的时序性。因此基于数据库的变化的事件比较难实时合并成一个完整的业务事件。而通过业务写入事件只需要一个简单的mq操作写入即可,因此在数据库有复杂拆分的场景下,业务独立写入变化事件结构上更简单,也保证了事件本身的原子性。

    Storm
    Apache Storm是和微博架构同一时间发展的技术,主要对worker的管理抽象成一个框架,支持任务调度,任务容灾,任务多级传递(bolt A处理完后传递给bolt B),数据转换从worker中分离出来变成独立的spout。对于一个复杂且数据源多样的环境,Storm确实具有更好的优势。

    Kafka
    Apache Kafka是一个分布式的消息队列产品,支持按照partition线性扩展,投递上也默认at-least-once。而图中Firehose是一种面向feed业务的消息服务,同时根据社交关系的特点,还具备根据关系将数据fan-out的能力。

    PS:
    1、上述内容会在 2014.12.19 ArchSummit Beijing 大数据feed架构演讲中介绍,欢迎参会的同行前来交流。
    2、有关大数据及流式计算,可参阅前微博同事张俊林的《大数据日知录》一书。