导读:百度搜索系统是百度历史最悠久、规模最大而且对其的使用已经植根在你们平常生活中的系统。坊间有一种有趣的作法:不少人经过打开百度搜索来验证本身的网络是否是通畅的。这种作法说明百度搜索系统在你们心目中是“稳定”的表明,且事实确是如此。百度搜索系统为何具备如此高的可用性?背后使用了哪些技术?以往的技术文章鲜有介绍。本文立足于你们所熟悉的百度搜索系统自己,为你们介绍其可用性治理中关于“稳定性问题分析”方面使用的精细技术,以历史为线索,介绍稳定性问题分析过程当中的困厄之境、破局之道、创新之法。但愿给读者带来一些启发,更但愿能引发志同道合者的共鸣和探讨。前端
全文7741字,预计阅读时间17分钟。node
第1章 困境
在大规模微服务系统下,若是故障未发生,应该归功于运气好。可是永远不要期望故障不发生,必须把发生故障看成常态。从故障发生到解除过程遵循的基本模式抽象以下。c++
可用性治理主要从这3个角度着手提高:1. 增强系统韧性;2. 完善止损手段,提高止损有效性,加速止损效率;3. 加速缘由定位和解除效率。算法
以上3点,每一个都是一项专题,限于篇幅,本文仅从【3】展开。数据库
百度搜索系统的故障缘由定位和解除,是一件至关困难的事情,也多是全公司最具备挑战性的一件事情。困难体如今如下几个方面。网络
极其复杂的系统 VS. 极端严格的可用性要求
百度搜索系统分为在线和离线两部分。离线系统天天从整个互联网抓取资源,创建索引库,造成倒排、正排和摘要三种重要的数据。而后,在线系统基于这些数据,接收用户的query,并以极快的速度为用户找到他想要的内容。以下图所示。架构
百度搜索系统是极其庞大的。让咱们经过几个数字直观感觉一下它的规模:app
百度搜索系统的资源占用量折合成数十万台机器,系统分布在天南海北的N大地域,搜索微服务系统包含了数百种服务,包含的数据量达到数十PB级别,天级变动次数达到数十万量级,平常的故障种类达到数百种,搜索系统有数百人参与研发,系统天天面临数十亿级的用户搜索请求。ssh
虽然系统是超大规模,可是百度对可用性的要求是极其严格的。百度搜索系统的可用性是在5个9以上的。这是什么概念呢?若是用可提供服务的时间来衡量,在5个9的可用性下,系统一年不可用时间只有5分钟多,而在6个9的可用性下,一年不可用的时间只有半分钟左右。因此,能够说百度搜索是不停服的。异步
一个query到达百度搜索系统,要经历上万个节点的处理。下图展现了一个query经历的所有节点的一小部分,大概占其经历节点全集的几千分之一。在这种复杂的路径下,全部节点都正常的几率是极其小的,异常是常态。
复杂的系统,意味着故障现场的数据收集和分析是一项浩大的工程。
多样的稳定性问题种类
百度搜索系统向来奉行“全”、“新”、“快”、“准”、“稳”五字诀。平常中的故障主要体如今“快”和“稳”方面,大致可归为三类:
-
PV损失故障:未按时、正确向用户返回query结果,是最严重的故障。
-
搜索效果故障:预期网页未在搜索结果中展示;或未排序在搜索结果的合理位置;搜索结果页面响应速度变慢。
-
容量故障:因外部或内部等各类缘由,没法保证系统高可用须要的冗余度,甚至容量水位超过临界点形成崩溃宕机等状况,未及时预估、告警、修复。
这些种类繁多、领域各异的问题背后,不变的是对数据采集加工的需求和人工分析经验的自动化抽象。
第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的调用链和日志查询页面。
小结
以上,极致的数据建设解决了问题追查的死角,此时问题分析效率成为主要矛盾,下篇咱们为你们带来百度搜索如何经过对人工分析经验进行抽象,实现自动化、智能化的故障问题,从而保障百度搜索稳定性。未完待续,敬请期待……
本期做者 | ZhenZhen;LiDuo;XuZhiMing
招聘信息:
关注同名公众号百度Geek说,输入内推便可加入搜索架构部,咱们期待你的加入!
推荐阅读:
---------- END ----------
百度Geek说
百度官方技术公众号上线啦!
技术干货 · 行业资讯 · 线上沙龙 · 行业大会
招聘信息 · 内推信息 · 技术书籍 · 百度周边
欢迎各位同窗关注