查看原文
其他

百度搜索稳定性问题分析的故事

The following article is from 百度Geek说 Author 搜索架构部

导读:百度搜索系统是百度历史最悠久、规模最大并且对其的使用已经植根在大家日常生活中的系统。坊间有一种有趣的做法:很多人通过打开百度搜索来验证自己的网络是不是通畅的。这种做法说明百度搜索系统在大家心目中是“稳定”的代表,且事实确是如此。百度搜索系统为什么具有如此高的可用性?背后使用了哪些技术?以往的技术文章鲜有介绍。本文立足于大家所熟悉的百度搜索系统本身,为大家介绍其可用性治理中关于“稳定性问题分析”方面使用的精细技术,以历史为线索,介绍稳定性问题分析过程中的困厄之境、破局之道、创新之法。希望给读者带来一些启发,更希望能引起志同道合者的共鸣和探讨。

第1章 困境
在大规模微服务系统下,如果故障未发生,应该归功于运气好。但是永远不要指望故障不发生,必须把发生故障当作常态。从故障发生到解除过程遵循的基本模式抽象如下。


可用性治理主要从这3个角度着手提升:1. 加强系统韧性;2. 完善止损手段,提升止损有效性,加速止损效率;3. 加速原因定位和解除效率。
以上3点,每个都是一项专题,限于篇幅,本文仅从【3】展开。
百度搜索系统的故障原因定位和解除,是一件相当困难的事情,也可能是全公司最具有挑战性的一件事情。困难体现在以下几个方面。

极其复杂的系统 VS. 极端严格的可用性要求

百度搜索系统分为在线和离线两部分。离线系统每天从整个互联网抓取资源,建立索引库,形成倒排、正排和摘要三种重要的数据。然后,在线系统基于这些数据,接收用户的query,并以极快的速度为用户找到他想要的内容。如下图所示。


百度搜索系统是极其庞大的。让我们通过几个数字直观感受一下它的规模:

百度搜索系统的资源占用量折合成数十万台机器,系统分布在天南海北的N大地域,搜索微服务系统包含了数百种服务,包含的数据量达到数十PB级别,天级变更次数达到数十万量级,日常的故障种类达到数百种,搜索系统有数百人参与研发,系统每天面临数十亿级的用户搜索请求。
虽然系统是超大规模,但是百度对可用性的要求是极其严格的。百度搜索系统的可用性是在5个9以上的。这是什么概念呢?如果用可提供服务的时间来衡量,在5个9的可用性下,系统一年不可用时间只有5分钟多,而在6个9的可用性下,一年不可用的时间只有半分钟左右。所以,可以说百度搜索是不停服的。
一个query到达百度搜索系统,要经历上万个节点的处理。下图展示了一个query经历的全部节点的一小部分,大概占其经历节点全集的几千分之一。在这种复杂的路径下,所有节点都正常的概率是极其小的,异常是常态。

复杂的系统,意味着故障现场的数据收集和分析是一项浩大的工程。

多样的稳定性问题种类

百度搜索系统向来奉行“全”、“新”、“快”、“准”、“稳”五字诀。日常中的故障主要体现在“快”和“稳”方面,大体可归为三类:
  1. PV损失故障:未按时、正确向用户返回query结果,是最严重的故障。
  2. 搜索效果故障:预期网页未在搜索结果中展现;或未排序在搜索结果的合理位置;搜索结果页面响应速度变慢。
  3. 容量故障:因外部或内部等各种原因,无法保证系统高可用需要的冗余度,甚至容量水位超过临界点造成崩溃宕机等情况,未及时预估、告警、修复。
这些种类繁多、领域各异的问题背后,不变的是对数据采集加工的需求和人工分析经验的自动化抽象。

第2章 引进来、本土化:破局

在2014年以前,故障原因定位和解除都在和数据较劲,当时所能用到的数据,主要有两种。一是搜索服务在线日志(logging);二是一些分布零散的监控(metrics)。这两类数据,一方面不够翔实,利用效率低,问题追查有死角;另一方面,对它们的使用强依赖于人工,自动化程度低。以一个例子说明。
拒绝问题的分析首先通过中控机上部署的脚本定时扫描线上服务抓取单PV各模块日志,展现到一个拒绝分析平台(这个平台在当时已经算是比较强大的拒绝原因分析工具了)页面,如下图所示;然后人工阅读抓取到的日志原文进行分析。这个过程虽然具有一定的自动化能力,但是PV收集量较小,数据量不足,很多拒绝的原因无法准确定位;数据平铺展示需要依赖有经验的同学阅读,分析效率极其低下。

在问题追查死角和问题追查效率上,前者显得更为迫切。无死角的问题追查呼吁着更多的可观测数据被收集到。如果在非生产环境,获取这些数据是轻而易举的,虽然会有query速度上的损失,但是在非生产环境都能容忍,然而,这个速度损失的代价,在生产环境中是承受不起的。在理论基石《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》的指导下,我们建设了kepler1.0系统,它基于query抽样,产出调用链和部分annotation(query处理过程中的非调用链的KV数据)。同时,基于业界开源的prometheus方案,我们完善自己的metrics系统。它们上线后立即产生了巨大的应用价值,打开了搜索系统可观测性建设和应用的想象空间。

2.1 kepler1.0简介

系统架构如下图所示。
阶段性使命:kepler1.0在于完善搜索系统的可观测性,基于开源成熟方案结合公司内组件实现从0到1的建设,快速完成可观测性能力空白的补齐,具备根据queryID查询query处理过程的调用链以及途径服务实例日志的能力。
引进来:从kepler1.0的架构不难发现,它从数据通路、存储架构等方面完整的参考zipkin
本土化:引进zipkin时数据采集sdk只支持c++,为了满足对非c++模块的可观测性需求,兼顾sdk的多语言维护成本以及trace的侵入性,采用了常驻进程通过日志采集输出格式和c++ sdk兼容的trace数据,即图中的日志采集模块。

2.2 通用metrics采集方案初步探索

系统架构如下图所示。

阶段性使命:2015年前后搜索开始探索大规模在线服务集群容器化混部技术,此时公司内的监控系统对多维度指标汇聚支持较弱,基于机器维度指标的传统容量管理方式已经难以满足容器化混部场景的需求。
引进来:将开源界成熟的metrics方案引入搜索在线服务混部集群,实现了符合prometheus协议的容器指标exporter,并依托prometheus的灵活多维度指标查询接口以及grafana丰富的可视化能力,建设了搜索在线业务混部集群容量管理依赖的底层数据系统。
本土化:容器指标prometheus-exporter和搜索在线PaaS系统深度对接,将服务元信息输出为prometheus的label,实现了容器元信息的指标索引和汇聚能力,满足容器化混部场景下容量管理的需求。指标和PaaS元信息关联是云原生metrics系统的初步探索主要成果。

2.3 应用效果初显

场景1:拒绝、效果问题

阶段性痛点:人工分析强依赖日志,从海量调用链、日志数据中精确检索出某些特定query,通过ssh扫线上机器日志效率很低,且对线上服务存在home盘io打满导致稳定性风险。
解决情况:对命中常态随机抽样拒绝问题、可复现的效果问题开启强制抽样采集,通过queryID直接从平台查询调用链及日志用于人工分析原因,基本满足了这个阶段的trace需求。

场景2:速度问题

阶段性痛点:仅有日志数据,缺乏调用链的精细时间戳;一个query激发的调用链长、扇出度大,日志散落广泛,难收集。通过日志几乎无法恢复完整的时序过程。这导致速度的优化呈现黑盒状态。
解决情况:补全了调用链的精细时间戳,使query的完整时序恢复成为可能。通过调用链可以查找到程序层面耗时长尾阶段或调度层面热点实例等优化点,基于此,孵化并落地了tcp connect异步化、业务回调阻塞操作解除等改进项目。

场景3:容量问题

阶段性痛点:多维度指标信息不足(缺少容器指标、指标和PaaS系统脱节);缺少有效的汇聚、加工、组合、对比、挖掘以及可视化手段。
解决情况:建设了搜索在线的容器层面多维度指标数据采集系统,为容器化的容量管理应用提供了重要的基础输出来源,迈出了指标系统云原生化探索的一步。下图为项目上线后通过容器指标进行消耗审计功能的截图。

第3章 创新:应用价值的释放

虽然kepler1.0和prometheus打开了可观测性建设的大门,但是受限于能力,已经难以低成本地获取更多的使用价值了。

3.1 源动力

基于开源方案的实现在资源成本、采集延迟、数据覆盖面等方面无法满足搜索服务和流量规模,这影响了稳定性问题解决的彻底性,特别是在搜索效果问题层面表现尤为严重,诸如无法稳定复现搜索结果异常问题、关键结果在索引库层面未预期召回问题等。
稳定性问题是否得到解决永远是可观测性建设的出发点和落脚点,毫不妥协的数据建设一直是重中之重。从2016年起,搜索开始引领可观测性的创新并将它们做到了极致,使各类问题得以切实解决。

3.2 全量采集

因为搜索系统规模太庞大,所以kepler1.0只能支持最高10%的采样率,在实际使用中,资源成本和问题解决彻底性之间存在矛盾。
(1)搜索系统大部分故障都是query粒度的。很多case无法稳定复现,但又需要分析出历史上某个特定query的搜索结果异常的原因。让人无奈的是,当时只有备份下来的日志才能满足任一历史query的数据回溯需求,但它面临收集成本高的难题;另外,很多query没有命中kepler1.0的抽样,其详细的tracing数据并未有被激发出来,分析无从下手。能看到任一历史特定query的tracing和logging信息是几乎所有同学的愿望。
(2)公司内部存储服务性价比较低、可维护性不高,通过扩大采样率对上述问题进行覆盖需要的资源成本巨大,实际中无法满足。
对于这个矛盾,业界当时并没有很好的解决方案。于是,我们通过技术创新实现了kepler2.0系统。系统从实现上将tracing和logging两种数据解耦,通过单一职责设计实现了针对每种数据特点极致优化,以极低的资源开销和极少的耗时增长为成本,换取了全量query的tracing和logging能力,天级别数十PB的日志和数十万亿量级的调用链可实现秒查。让大多数故障追查面临的问题迎刃而解。

3.2.1 全量日志索引
首先,我们介绍全量日志索引,对应于上图中日志索引模块。
搜索服务的日志都会在线上机器备份相当长一段时间,以往的解决方案都着眼于将日志原文输出到旁路系统,然而,忽略了在线集群天然就是一个日志原文的现成的零成本存储场所。于是,我们创新的提出了一套方案,核心设计理念概括成一句话:原地建索引。
北斗中通过一个四元组定义一条日志的索引,我们叫做location,它由4个字段组成:ip(日志所在机器)+inode(日志所在文件)+offset(日志所在偏移量)+length(日志长度)。这四个字段共计20字节,且只和日志条数有关,和日志长度无关,由此实现对海量日志的低成本索引。location由log-indexer模块(部署在搜索在线服务机器上)采集后对原始日志建立索引,索引保存在日志所在容器的磁盘。
北斗本地存储的日志索引逻辑格式如下图所示。

查询时,将inode、offset、length发送给索引ip所在的机器(即原始日志所在机器),通过机器上日志读取模块,可根据inode、offset、length以O(1)的时间复杂度定点查询返回日志原文,避免了对文件的scan过程,减少了不必要的cpu和io消耗,减小了日志查询对生产环境服务稳定性的影响。
同时,除了支持location索引以外,我们还支持了灵活索引,例如将检索词、用户标识等有业务含义的字段为二级索引,方便问题追查时拿不到queryID的场景,可支持根据其他灵活索引中的信息进行查询;在索引的使用方式上,除了用于日志查询以外,我们还通过索引推送方式构建了流式处理架构,用于支持对日志流式分析的应用需求。
这里还有一个问题:查询某一query的日志时,是不是仍然需要向所有实例广播查询请求?答案是:不会。我们对查询过程做了优化,方法是:通过下文介绍的callgraph全量调用链辅助,来确定query的日志位于哪些实例上,实现定点发送,避免广播。

3.2.2 全量调用链

在dapper论文提供的方案中,同时存在调用链和annotation两种类型的数据。经过重新审视,我们发现,annotation的本质是logging,可以通过logging来表达;而调用链既可以满足分析问题的需要,又因为它具有整齐一致的数据格式而极易创建和压缩,达到资源的高性价比利用。所以,callgraph系统(kepler2.0架构图中红色部分)就带着数据最简、最纯洁的特点应运而生。全量调用链的核心使命在于将搜索全部query的调用链数据在合理的资源开销下存储下来并高效查询。
在tracing的数据逻辑模型中,调用链的核心元素为span,一个span由4部分组成:父节点span_id、本节点span_id、本节点访问的子节点ip&port、开始&结束时间戳。
全量调用链核心技术创新点在于两点:(1)自研span_id推导式生成算法,(2)结合数据特征定制压缩算法。相比kepler1.0,在存储开销上实现了60%的优化。下面分别介绍这两种技术。
3.2.2.1 span_id推导式生成算法
说明:下图中共有两个0和1两个span,每个span由client端和server端两部分构成,每个方框为向trace系统的存储中真实写入的数据。
左图:kepler1.0随机数算法。为了使得一个span的client和server能拼接起来并且还原出多个span之间的父子关系,所有span的server端必须保存parent_span_id。因此两个span实际需要向存储中写入4条数据。
右图:kepler2.0推导式算法,span_id自根节点从0开始,每调用一次下游就累加该下游实例的ip作为其span_id并将其传给下游,下游实例递归在此span_id上继续累加,这样可以保证一个query所有调用的span_id是唯一性。实例只需要保存自己的span_id和下游的ip,即可根据算法还原出一个span的client端和server端。由此可见,只需要写入2条数据且数据中不需要保存parent_span_id,因此存储空间得到了节省,从而实现了全量调用链的采集能力。
右图中ip1:port1对ip2:port的调用链模拟了对同一个实例ip2:port2访问多次的场景,该场景在搜索业务中广泛存在(例如:一个query在融合层服务会请求同一个排序服务实例两次;调度层面上游请求下游异常重试到同一个实例等),推导式算法均可以保证生成span_id在query内的唯一性,从而保证了调用链数据的完整性。

3.2.2.2 数据压缩
结合数据特征综合采用多种压缩算法。
(1) 业务层面:结合业务数据特征进行了定制化压缩,而非采用通用算法无脑压缩。
(a) timestamp:使用相对于base的差值和pfordelta算法。对扇出型服务多子节点时间戳进行了压缩,只需保存第一个开始时间戳以及相对该时间戳的偏移。以搜索在线服务常见高扇出、短时延场景为例,存储偏移比直接存储两个时间戳节省70%。
(b) ip:搜索内网服务ip均为10.0.0.0/24网段,故只保存ip的后3字节,省去第1字节的10,每个ip节省25%。

(2) protobuf层面:业务层面的数据最终持久化存储时采用了protobuf,灵活运用protobuf的序列化特性节省存储。
(a) varint:变长代替原来定长64位对所有的整数进行压缩保存,对于ip、port、时间戳偏移这种不足64位的数据实现了无存储浪费。
(b) packed repeated:ip和timestamp均为repeated类型,只需要保存一次field number。packed默认是不开启的,导致每个repeated字段都保存一次field number,造成了极大浪费。以平均扇出比为40的扇出链路为例,开启packed可节省了25%的存储空间(40字节的field number)。
最终,一个span的逻辑格式(上图)和物理格式(下图)如下:

3.2.3 应用场景的受益

3.2.3.1 时光穿越:历史上任一特定query的关键结果在索引库层面未预期召回问题

因为召回层索引库是搜索最大规模的服务集群,kepler1.0在索引库服务上只支持0.1%抽样率,使得由于索引库的某个库种和分片故障导致的效果问题追查捉襟见肘。全量调用链采集较好的解决了这一困境。
真实案例:PC搜索 query=杭州 未展现百度百科结果,首先通过工具查询到该结果的url所在数据库A的9号分片,进一步通过全量调用链调用链查看该query对数据库A所有请求中丢失了9号分片(该分片因重试后仍超时被调度策略丢弃),进一步定位该分片所有副本均无法提供服务导致失败,修复服务后预期结果正常召回。

3.2.3.2 链式分析:有状态服务导致“误中副车”型效果问题

有状态服务效果问题分析复杂性:以最常见的cache服务为例。如果没有cache只需通过效果异常的queryID通过调用链和日志即可定位异常原因。但显然搜索在线系统不可能没有cache,且通常cache数据会辅以异步更新机制,此时对于命中了脏cache的query只是“受害者”,它的调用链和日志无法用于问题最终定位,需要时序上前一个写cache的query的调用链和日志进行分析,我们称其为“捣乱者”。

kepler1.0的局限性:kepler1.0采样算法是随机比例抽样,“捣乱者”和“受害者”两个query是否命中抽样是独立事件,由于“捣乱者”在先,当“受害者”在受到效果影响时,已无法倒流时间触发前者抽样了,导致两个query在“时序”维度够成的trace链条中断,追查也随之陷入了困境。
kepler2.0的破解之法:在实现“纵向关联”(某一query处理过程中全量调用链和日志信息)基础上,借助全量调用链建设了“横向关联”能力,支持了对时序上多个关联query的链式追踪需求。写cache时将当前query的TraceId记录到cache结果中,读cache的query就可通过cache结果中的queryID找到“捣乱者”。借助全量调用链功能即可对“捣乱者”写脏cache的原因进行分析定位。另外,用户界面也对时序追踪的易用性进行了特殊设计,例如,对日志中写cache的queryID进行飘红,点击该字段可以直接跳转到对应query的调用链和日志查询页面。

第4章 再创新:应用价值的再释放

4.1 巨浪——故障分析的“终点”

拒绝的分析是一个定性的过程,根据拒绝query激发的日志信息,就可以定位业务层面的原因,或者定位到引起异常的模块。
这个过程可以抽象为下面几步:
(1) 故障(拒绝)信号的感知
(2) 故障单位(query)全量信息(日志)的收集
(3) 根据收集到的信息进行故障单位(query)的归因
(4) 对批量故障单位(query)的原因进行再归类,以及特征挖掘

整个过程需要在秒级完成,时效性要求很高。过程的顺利执行面临下面8个挑战:
挑战1:如何实现快速的日志检索。在采集到拒绝信号之后,拒绝的分析需要快速拿到日志原文,这些信息如果直接从线上扫描,速度和稳定性上显然达不到要求。

挑战2:拒绝定位的实时性和准确性之间的矛盾如何解决。日志越完整,拒绝原因的分析结果越准确。但是因为网络延迟等原因,分析模块无法保证马上拿到所有的日志。接收到拒绝信号后就开始分析,可以确保分析的实时性,但是准确性难以保证。而延迟一段时间再分析,可能会拿到更完整的日志,但是会影响拒绝分析的实时性。

挑战3:如何准确全面地描述故障。生产环境的故障“五花八门”,如果逐个进行表达和管理,维护成本会非常高。需要寻找一种方案,把所有的故障(规则)系统、准确、全面地管理起来。

挑战4:特征工程如何进行。在拿到日志原文之后,我们需要确定从日志中应该拿哪些信息,如何采集这些信息,并且以程序可以理解的方式将这些特征表达出来,最终和拒绝原因关联起来,即特征的选择、提取、表达和应用。

挑战5:如何还原query现场。在线系统为了保证可用性,关键模块上都会有重查。在定位拒绝时,需要还原出完整的调度树,这样才能看到由根节点出发到叶子节点各条路径失败的原因,不然可能会得到矛盾的结果。如下图所示,A-1、B-1和B-2节点都发生了重查,当拼接错误时(C模块的实例挂到了错误的B模块节点下),B-1(或B-2)的错误状态和挂在它之下的C模块的日志状态可能是矛盾的,无法得出正确的定位结论。


挑战6:如何对拒绝特征进行深度挖掘。自动定位难以定位到根因,更精确的定位依赖人工参与的继续分析。分析工具需要能从各种拒绝中找到聚集特征并以一定的优先顺序展示给用户,为根因定位或者止损提供更多线索。query中可以提取的信息包括query的查询词(word),发送query的client端ip,query的语种或者处理query的机器所在的物理机房等。比如,当发现系统拒绝都和某个ip的攻击流量有关时,可以对该ip进行封禁止损。


挑战7:级联故障如何感知。当某个模块故障引起拒绝时,可能会产生级联的次生故障,表现为拒绝直接原因多样化。下图展示了 一种典型的级联故障:E异常后B对C发起了大量重查,首查叠加重查流量彻底把D压垮,最后A对B也开始发起大量重查。拒绝的流量在个个模块都有可能命中限流策略,表现为不同的拒绝原因。因此,在产生故障时,依赖某一个时间点的拒绝统计信息可能会掩盖引起拒绝的根因。


挑战8:如何定位未知故障。故障是偶发的,我们进行拒绝原因划分的时候所使用的划分集合,无法完整体现系统可能出现的拒绝原因。对于未知故障或者未被纳入到拒绝定位规则中的拒绝,我们需要有手段“制造”故障,发现未知或者未采集到的故障。


下面,将依次介绍我们是如何解决这8个问题的。

4.1.1 索引镜像技术

为了实现日志的快速检索,日志索引由在线采集模块提取后,除了推送本机建立索引之外,还将定位需要的子集主动推送至旁路索引模块,该模块会以日志对应的queryID为key写入内存介质的全量索引存储中。这里的索引支持多列稀疏存储,相同queryID的多条日志location可以追加写入。这样,单条拒绝query的location信息可以已O(1)的时间复杂度拿到,接下来并行地到目标机器上捞取日志,并将其写入持久化的故障日志存储中。最后对这些日志进行特征提取并分析拒绝原因。


4.1.2 流式分析

为了解决问题2,我们借鉴了流式分析的理念。无论是分析模块收到了拒绝信号还是增量的拒绝日志信号,都触发一次拒绝原因的分析,并更新结论。这里有2个关键点:一是分析自动触发,线上只要发生拒绝,拒绝分析就开始工作。二是增量更新,只要某个拒绝query的日志有更新,就重新触发故障原因分析。对于入口模块,在线采集端会根据其日志中的指定字段判断是否是拒绝,并将这个信号连同索引一起推送到旁路索引模块。旁路索引模块在收到该信号后会立即通知分析中心对这个queryID进行分析,因此分析流程可以在拒绝报警发出之前触发,最大化故障定位止损效率。当旁路索引模块向分析模块触发完一次分析请求后,会将这个queryID记录到全量索引存储的pvlost表中,当后续有非入口模块日志的索引到达时,旁路索引模块拿该索引中的queryID到这个表中查找,即可判断是否是需要触发增量分析。增量分析会合并所有已知日志,并更新分析结论。

4.1.3 完备labelset

在入口模块接收到用户query后,该query会经多个模块的处理。每个模块都有读取、解析请求包,请求后端,处理后端返回结果,以及最后的打包发送流程。在这个抽象层级上,请求处理各个步骤的划分是足够明确的,并且都可能出现失败而引起query在该模块的拒绝。所以,我们对这个处理过程中可能失败的原因进行了枚举,构建了单模块故障原因完备模板,将该模版应用到所有的必查模块就构成了故障原因的完备集合。

4.1.4 特征工程

在确定了完备labelset(拒绝原因)之后,我们需要在程序中实现自动的特征提取、表达,并和拒绝原因建立映射。不同模块的业务日志差异很大,为了解决特征的提取问题,我们实现规则提取引擎,输入为日志原文和提取规则,输出为采集到的特征。特征的类型主要有2种:指定内容是否存在、值是多少。在提取出特征之后,我们使用一个向量表示各个特征的取值,当向量中某些特征的取值满足指定的条件(等于、在指定范围等)时,就给出对应的拒绝原因。


4.1.5 单query现场还原

日志分析模块拿到的日志只是相互独立的节点,进行query现场还原后才能开始分析。从入口模块开始,搜索系统的各个模块会把自己的span_id,以及所调度的多个后端的span_id打印出来,依据这些信息即可还原调度现场。需要注意的是,模块发起的首查和重查是有先后顺序的,通过对一个节点的孩子节点的span_id进行排序,即可还原这种调度上的先后次序。在还原调度树之后,将调度树由根节点到叶子节点路径上的所有异常日志汇总,从中拿到所有的特征并和规则列表进行比对,即可得到该路径(调用链)的拒绝原因。


4.1.6 智能rank算法

问题6的难点在于:在一批query所有维度的特征中,找到有明显聚集性的一个(一组)维度。这可以进一步表示为:在不同维度之间进行排序,找到排名最考前的维度,而排序的依据就是该维度内部取值是否有高度的聚集性。为了解决这个问题,我们借用了熵的概念——当拒绝的query在某个维度上取值聚集越强时,它的熵就会越低。在构建排序模型时,我们对不同维度的取值进行了变换,确保不同维度可比,并加入了人工经验确定维度权重。这样就可以在出现拒绝时,按照顺序给出拒绝query在不同维度的聚集性,帮助定位根因或制定止损策略。

4.1.7 时间线分析机制
为了准确感知到拒绝的演化过程,我们实现了timeline机制。收到拒绝报警后,该工具会自动从巨浪获取拒绝信息,按照秒级粒度进行拒绝原因数量统计,并进行二维展示,如下图所示。在该展示结果上,可以看到不同秒级时间各种拒绝的数量,以及不同拒绝原因随时间的变化趋势,帮助我们定位根因。


4.1.8 混沌工程技术

问了解决问题8,我们引入了混沌工程的技术。混沌工程提供了向在线服务精确注入各种故障的能力,这样就可以拿到丰富且带标记的样本补充到定位知识库中。这样不仅解决了日志样本问题,还可提升对未知故障的预测能力,从“亡羊补牢”进化到“未雨绸缪”,防患于未然。

这8大技术,很好的解决了前面的8个问题。在定位效果上,准确率可达99%,出现拒绝后,产出模块粒度的拒绝原因可以在秒级完成,分析能力可覆盖大规模拒绝。

4.2 长尾批量分析

搜索系统中存在着一些响应时间长尾,为了解决这个问题,我们基于全量tracing和logging数据,实现了一套例行长尾原因分析机制。该机制定时从入口模块拿到响应时间长尾的query,再对每个query调用全量调用链的接口拿到完整的调度树。在分析长尾原因时,从入口模块开始,通过广度优先遍历的方式,逐步向后端模块推进,直到找到最后一个响应时间异常模块,即认为长尾是由该模块引起的。

模块响应时间异常的定义为:该模块的响应时间超过了正常请求的极限响应时间,并且它所调用的模块的响应时间是正常的。在确定异常模块之后,可以进一步从全量调用链中有针对性的拿到该模块的日志,从日志中根据规则找到该模块处理耗时异常的阶段。


4.3 异常状态全流程追踪

为了确保用户体验的稳定性,搜索会定期分析未召回预期结果的query。query没有返回预期结果,可能是因为它命中了“捣乱者”写入的cache,也有可能它穿透了cache,召回了有问题的结果,这里问题的原因可能是偶发的或者是稳定的。我们需要能筛选出可以稳定复现的问题进行追查。为了实现这个需求,我们先拿到各个query的tracing以及logging信息,根据这些信息可以:
(1)找到哪些query命中了“捣乱者”写入的脏cache;
(2)哪些query穿透到了后端并重新进行了检索。

将命中cache的query和写入cache的query关联起来,即可得到下图所示的结果——异常状态的全流程追踪。只要异常效果持续时间内的cache命中是连续的,并且触发了多次cache的更新,那么就可以认为在这一段时间内,故障是稳定复现的,可以投入人力追查。


第5章 总结

本文首先介绍了百度搜索可用性保障的困境,超大的服务规模、极高频的变更和参与人数、海量数据和请求量、多样且多变的故障种类等构成的复杂系统,对年只能停服5分钟的极端严格可用性目标构成了极大挑战。然后,以时间顺序介绍了我们对百度搜索可用性保障的解决经历和经验。

首先,为了解决问题追查死角的问题,我们建设了可观测基础——logging、tracing、metrics,这些没有精细加工的基础数据解决了可用性保障中的一部分问题,但是我们发现基础数据的自动化程度较低、智能性较差,复杂问题需要大量人力投入,分析效果强依赖人工经验,甚至根本无法分析。

更进一步,为了解决可用性保障的效率问题,我们对体系中的各个组件进行升级,使可观测性的产出变得可观测,复杂的效果故障由不可追查变得可追查,拒绝分析从人工变得自动、准确、高效。在整个体系建设过程中,我们从数据的消费者,变成数据的生产者和加工者,通过数据的生产、加工、分析全流程闭环,使得百度搜索中各种故障无处遁形、无懈可击,使得百度搜索可用性保障摆脱困境,持续维持较好的用户口碑,同时本文也希望给读者带来一些启发,更希望能引起志同道合者的共鸣和探讨。

本期作者 | ZhenZhen;LiDuo;XuZhiMing


招聘信息

更多百度搜索架构部招聘信息可扫描二维码


参考阅读



高可用架构欢迎技术架构领域原创文章,欢迎通过公众号菜单「联系我们」进行投稿。


2021年GIAC调整到7月30-31日在深圳举行,点击阅读原文了解更多详情。
: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存