随着业务的发展,系统日益复杂,功能愈发强大,用户数量级不断增多,设备cpu、io、带宽、成本逐渐增加,当发展到某个量级时,这些因素会导致系统变得臃肿不堪,服务质量难以保障,系统稳定性变差,耗费相当的人力成本和服务器资源。这就要求我们:要有勇气和自信重构服务,提供更先进更优秀的系统。--导读
在传统单机系统的使用过程中,如果某个请求响应过慢或是出错,开发人员可以通过查看日志快速定位到具体服务。而随着业务的越来越复杂,架构由单体逐渐演变为微服务架构。特别是随着容器, Serverless等技术的广泛应用,它将庞大的单体应用拆分成多个子系统和公共的组件单元。这一理念带来了许多好处:复杂系统的拆分简化与隔离、公共模块的重用性提升与更合理的资源分配、大大提升了系统变更迭代的速度以及可扩展性。但反之,业务架构也随之变的越来越复杂,一个看似简单的业务后台可能有几百甚至几千个服务在支撑,当接口出现问题时,开发人员很难及时从错综复杂的服务调用中找到问题的根源,从而错失了止损的黄金时机,排查问题的过程也需要耗费大量的时间和人力成本。为了应对这一问题,业界诞生了许多优秀的面向Devops的诊断分析系统,包括Logging、Metric、Tracing。三者关系如图所示:
此图来源自网络
三者互相重叠,又各自专注于自己的领域,将三者结合起来就可以快速定位问题。而已知的业界优秀开源组件有诸如:
随着时间的推移可能会集成更多的功能,但同时也不断地集成其他领域的特性到系统中来。而天机阁正是集三位于一体的分布式链路追踪系统,提供了海量服务下的链路追踪、故障定位、架构梳理、容量评等能力。
从数据流转角度来看,天机阁整体可以分为数据生产与消费链路,其中数据生产链路主要包括数据接入层、数据处理层、数据存储层。整体如下图所示。
在海量流量的冲击下,日志集群与Metric集群一直比较稳定,处理耗时基本在秒级。影响较大的是Trace集群,Trace集群主要通过滚动窗口接收一个Trace请求的所有RPC 的Span信息。由于业务接入量的上涨以及不少业务的放量,Trace集群的日均处理量由3月份的40亿/day爆发式上涨到340亿/day,且集群还要经常面临业务热点push、错误埋点等场景的挑战。这些问题直接导致数据实时性开始下降,期间经常收到用户反馈数据延时大,数据丢失的问题。而系统层面,则频繁出现集群抖动、延时飙升、Checkpoint失败等现象。同时存储也面临巨大的写入压力:Hbase与ES均出现写入延时上涨、毛刺的现象,而这些因素最终导致计算集群的处理性能变弱,稳定性下降。产生消费滞后,数据堆积的问题。具体有如下四个表象:
本着先抗住再优化的思想,当出现上述问题时,为保证系统的可用性,我们会采取各种快速恢复策略,诸如计算资源扩容、数据降级、关闭数据可靠性等策略来提升集群的处理性能,达到快速恢复的目的。但这些策略都治标不治本,性能问题周而复始的出现。这不但浪费了大量计算集群资源,集群处理性能,吞吐,稳定性都没有实质上的提升。
针对上述四种现象,结合业务分别从接入层、存储层、计算层对系统进行了全面分析,找出了目前Trace系统存在的问题以及瓶颈,并制定了对应的优化方案:
图4.1: Trace系统架构图
如上图所示,一次RPC的请求和回包最终会合并成一个Span,而每个Span包含Traceid、Spanid,以及本次RPC调用涉及的主被调服务信息。在接入层进行数据采样上报时,会将相同Traceid的Span集合路由的到同一个数据通道中,而计算层会对不同通道的数据做隔离,不同通道采用不同的计算任务对数据进行处理。大致流程如下:首先根据Traceid高位字节进行Reducekeby,确保同一个RPC请求的数据能路由到同一个窗口,再通过窗口函数对同一个请求的数据集合进行聚合计算,实时生成拓扑图,调用链等数据模型,批量写入ES和Hbase等列式存储。在业务量少,集群相对稳定的情况下,Trace集群平均处理时长在20-40s左右,即从一次Trace数据的上报到可展示的过程大概要经过半分钟。当系统不稳定或者处理性能下降时,数据延时会上涨至小时甚至天级别,而主要导致系统不稳定的因素有两种:一是数据量的上涨给存储系统带来了较大的摄入压力,底层数据的刷盘时间越来越长。二是系统经常要面临业务方错误埋点或热点Push产生的热key、脏数据等场景的考验。具体表现为:
天机阁既是一个写密集型系统,也是一个时延敏感型系统,对数据的实时性有比较高的要求。系统的不稳定会导致消息通道大量数据堆积,数据实时性下降,最终影响用户体验,这是不能被容忍的。所以针对上述问题,我们需要对原系统进行全面的优化升级。
Elasticsearch 是一个实时的、Restful 风格的分布式搜索数据分析引擎,内部使用lucene做索引与搜索,能够解决常规和各种类型数据的存储及检索需求,此外ES还提供了大量的聚合功能可以对数据进行分析,统计,生成指标数据等。典型的应用场景有:数据分析,站内搜索,ELK,电商等,主要特点为:
天机阁使用站长素材网的ES组件,专门用于建立热门Trace倒排索引,用户在使用天机阁进行链路追踪查询时,首先可以指定Tag或者染色Key查询到任意时刻上报的Trace元数据,天机阁会根据查询到的Trace数据绘制出完整的服务调用过程。同时在UI上可以支持瀑布、调用树的多种样式的数据展示。如下图所示:
随着进入量的上涨,ES集群内部写入峰值达到80w/s,日均文档总量达到280亿,索引占用总量达到 67T,每天新增索引量达到1000+,而每日文档新增存储总量达到10T。机器配置采用为:64个4C 16g的数据节点,平均CPU使用率在45-50%之间;最大CPU使用率在80%左右;内存使用率60%左右,而磁盘平均使用率达到了53%。数据写入整体流程为:
天机阁是基于业务Appid维度按天索引的策略,而伴随业务量的极速上涨主要暴露出来的问题为:
分片过多的缺点主要有以下三个方面: 第一,ES每个索引的分片都是一个Lucene索引,它会占用消耗CPU、内存、文件句柄。第二:分片过多,可能导致一个节点聚集大量分片,产生资源竞争。第三:ES在计算相关度词频统计信息的时候也是基于分片维度的,如果分片过多,也会导致数据过少相关度计算过低。
2.分片大小不均匀。 部分索引的分片容量超过50g,侧面反应了这些索引分片策略的不合理,最终会导致索引的查询性能变慢。
3.写入耗时过大,部分索引查询性能慢。ES写入耗时达到(1500ms-2000ms),此外分片过大也直接影响到索引的查询性能。
5.系统出现大量异常日志. ES服务器异常,主要分为两类,一类是:数据解析异常,另一类是:Fields_limit异常。
优化点1:优化集群内部分片过多、分片不合理、节点负载不均等问题。
其中主要涉及了二个问题:
上述问题可以阅读ES官方文档和站长素材网ES文档得到全面的答案,这里不再赘述,总而言之,查询和写入的性能与索引的大小是正相关的,要保证高性能,一定要限制索引的大小,而索引的大小取决于分片与段的大小,分片过小,可能导致段过小,进而导致开销增加,分片过大可能导致分片频繁Merge,产生大量IO操作,影响写入性能。通过阅读相关文档,我提炼了以下三条原则:
当然最好的方法是根据自身业务场景来确定分片大小,看业务是注重读还是注重写以及对数据实时性、可靠性的要求。天机阁的索引设计模式是非常灵活的,属于典型的时序类型用例索引,以时间为轴,按天索引,数据只增加,不更新。在这种场景下,搜索都不是第一要素,查询的QPS很低。原先的分片针对策略针对容量过低的索引统一采用5个分片都默认配置,少数超过500g的大索引才会重新调整分片策略,而随着近期接入业务的不断增多以及索引进入量的暴涨,集群内部出现了许多容量大小不一,且分布范围较广的索引。老的配置方式显然已经不太合理,既会导致分片数急剧增长,也影响索引的读写性能。所以结合业务重新评估了集群中各个索引的容量大小,采用分级索引模版的分片控制策略,根据接入业务每天的容量变化,实现业务定制化的自适应分片。
索引模版 | 容量范围 | 分片策略 | 索引占用量 |
---|---|---|---|
template-01 | 0-40G | 单分片 | 55% |
template-02 | 40-100G | 4分片 | 20% |
template-03 | 100-200G | 8分片 | 15% |
template-04 | 200-400G | 12 | 8% |
template-n | ... | ... | ... |
一般而言:当用户遇到性能问题时,原因通常都可回溯至数据的索引方式以及集群中的分片数量。对于涉及多租户和用到时序型索引的用例,这一点尤为突出。
优化点2:优化写入性能。
上述优化,其实是对ES集群一种性能的取舍,牺牲数据可靠性以及搜索实时性来换取极致的写入性能,但其实ES只是存储热门数据,天机阁有专门的Hbase集群对全量数据进行备份,详细记录上报日志流水,保证数据的可靠性。
优化点3:优化索引创建方式
优化点4:优化ES服务器异常
1.cpu使用率:CPU使用率45% => 23%,内部写入量从60万/s => 40万/s。
2.磁盘使用率:53% => 40%。
3.写入拒绝率:索引写入拒绝率降为0。
4.集群宕机问题被修复:
5.查询耗时:大索引跨天级别查询在500ms左右。
6.分片数量:7万 => 3万,减少了50%,同时索引存储量优化了20%。
7.写入耗时:从2000ms => 900ms左右。
经过一期的优化ES写入性能有了明显提升,但还存在一些痛点,包括:
我们希望构建一个优雅的索引自动化运维管理系统,而这个系统主要解决两个问题:
ES在索引管理这一块一直在进行迭代优化,诸如Rollover、日期索引、Curator等都是对索引管理的一种策略,但是这些方式都不够自动化,直到ES6.7以后,官方推出了ILM(index lifestyle management)索引生命周期管理策略,能同控制多个索引的生命流转,配合索引模板、别名、Rollover能实现自动化索引生命周期与容量的管理闭环。
ILM策略主要有四个阶段:
天机阁使用ILM 策略配合分级索引模板可以比较优雅的实现索引的自动化管理过程。ILM 策略主要分为四个阶段:热、温、冷和删除。对于定义好的各个阶段的相应策略,ILM 会始终会顺序执行。我们只需要根据索引每个阶段的数据特性定义合适的管理方式,诸如:索引滚动更新用于管理每个索引的大小;强制合并操作可用于优化索引;冻结操作可用于减少集群的存储压力。
天机阁通过Flink Stream读取Kafka数据实时写入ES,峰值QPS接近35w,每天新增索引超过1000+。在这么大数据量上进行操作是一件很麻烦的事。我们希望ES能够自动化对分片超过100g的索引进行滚动更新,超过3天后的索引进行自动归档,并自动删除7天前的索引,同时对外以提供索引别名方式进行读写操作。这个场景可以通过ILM配置来实现,具体策略是:对于一些小于40G的索引,在Warm阶段执行Shrink策略压缩成单分片,并设定写入低峰期执行Forcemerge操作合并集群中小的段,Cold阶段可以执行Allocate操作来减少副本数,而针对集群内部1%的大索引,可以执行Freeze操作来释放部分存储空间。具体策略如下表所示:
ILM可以高效的进行索引生命周期与容量自动化管理,使用起来也很简单。但是还是有不少要注意的地方。
后续优化:ILM + 冷热架构,ILM 可支持为时序索引实现热温冷架构从而节约一些成本。
1.写入耗时:2500万/min写入量 2000ms - > 320ms。
3.索引存储总量:67T -> 48T 节约存储 30%。
4.创建索引速度:分钟级 -> 秒级。
HBase是一种构建在HDFS之上面向列的分布式数据库,能支持海量数据的存储。主要具备如下特点:
天机阁主要采用公司内部的Hbase组件,主要用于存储Trace元数据、关系链、拓扑图等数据模型,其中Trace元数据的存储以Traceid作为Rowkey、调用树以(时间戳_appid_svr_ifc)做为rowkey、关系链以(时间戳_appid, 时间戳_svr) 作为rowkey。通过Hbase 客户端的批量 API并发写入同一张表中,写入流程如下图所示:
目前天机阁Hbase写入峰值在30w/s,机器配置为16台 Ts60,每天数据写入总量在15-20t左右,单机写入峰值在2万/s 。我们分别从CPU、磁盘、IO、内存等角度查看了各个机器使用状况,主要归纳为以下三个问题:
1.调用链容易产生超级Trace,造成大Key、热Key等问题:通过对线上数据的监控,一般Rowkey写入的ValueKey平均大小在300-700字节左右,但是部分业务存在热点Push或者框架埋点错误等问题,会产生1M-2M的超级Trace ,这种case对Hbase的影响是巨大的,会频繁触发Hbase Memstore Flush、Hlog文件写入切换以及底层Storefile文件的Compaction等操作,从而造成写入性能急剧下降,同时也会影响到表中其他业务的吞吐量。
2.关系链、调用树存在写热点问题:热点问题会造成节点请求不均衡,导致部分节点负载过高。Hbase表是以Rowkey进行排序,再把Key拆分成若干个Region,分散到不同机器上,如果拆分合理,各个机器Region分布均匀,那负载就是均衡的,如果拆分不合理,就会造成写入过于集中,部分机器负载过高。
图7.4: 热点请求路由到同一台机器上
3.表设计耦合过重:关系链、拓扑图、调用链等数据都存储在同一张表中产生了大表问题, 同时调用链容易产生大Key问题,大key的写入会造成该表所在的节点吞吐量大大降低,从而影响到了表中的其他业务,诸如拓扑图数据的写入性能。
1.预分区 + Hash散列 + 分桶: Hbase本身是热点存储. 在创建表时只会存在一个没有Start-key End-key边界的Region,当数据写入时,Region会不断增加,Split成2个Region,产生Midkey。在此过程中,数据只往一个Region上写,会有写热点问题,而大量数据写入导致的频繁Split也会消耗IO资源。针对这个问题,业界通用的解决方案是对数据进行预分区,同时合理设计Rowkey来保证数据均匀到各个Region。天机阁Key的设计考虑了随机数、时间戳、机器IP、Tid等多个因子来保证Rowkey生成的随机性,再根据Rowkey的特点以及数据容量增长预期,合理划分了整个分区的范围,保证数据能均匀到各个分区。此外,也采用分桶的方式来缓解单个Region的压力,基本策略是在Rowkey前面加随机数,将相同Rowkey数据散列到若干桶里,在查询时候再进行并发读取,这种策略可以非常有效的处理关系链数据形成的热点问题。
随机散列与预分区二者结合起来,是业界比较常规做法。预分区一开始就预建好了一部分Region,这些Region都维护着自己的Start-End Keys,再配合上随机散列,基本能保证请求能均匀命中这些预建的Region,从而大大提高性能。
图7.6 RS 上Region数基本均匀
事实上Hbase的热点问题一直是比较棘手的,而天机阁的热点数据是比较频繁的。上述一些方案虽然能缓解热点问题,但是确增加了业务复杂度。并没有彻底根治这个问题,期待后续Hbase的迭代。
2.对重要业务进行分表,计算层进行逻辑隔离,存储层进行资源隔离。大表会存在很多问题,诸如Region过多、查询性能差、数据导出迁移困难等。所以需要对天机阁拓扑图、关系链、调用链的等业务存储进行分表,同时底层存储在物理层面进行资源隔离,将几部分数据解耦出来,不同数据路由到不同表中,写入互不影响,以此提升各个业务的写入性能。
3.业务上避免大Key写入:对超过300个Trace的数据进行降级处理。
4.采用批量异步提交API,根据需要动态修改HWal、Memstore flush等参数。
1. 解决了大量写入毛刺的问题,当天写入延时由1800ms -> 150ms。
2. 写关系链逻辑被独立出来,写入延时优化至5-15ms以内。
3. 大key过滤量。
Flink优化篇后面会进行文章整理并进行讲解,这里暂时先简单介绍一下背景、存在的问题以及优化后的效果。
首先由Flink定义的Source算子对数据通道进行实时消费, 经过FilterFunction与FlatMapFuntion算子完成对数据的清洗与格式转换工作,再根据TraceId进行ReduceKeyby,将同一个Traceid的数据路由到同一个窗口进行实时聚合计算,生成调用链与拓扑图等数据模型,写入存储。
1. 数据反压:
图8.2: Flink反压监控面板
生产环境中造成反压的情况有很多:1. 底层Sink算子摄入速度慢,会导致上游算子出现反压。2. 窗口逻辑过重,处理性能慢,也会导致上游Source算子出现反压。3. 代码有bug或者有比较耗时的操作也会造成反压现象。
反压不一定直接影响集群的可用性,但是说明集群一直处于一种亚健康的状态,有潜在的性能瓶颈。优化前天机阁所有Trace任务都是处于这样一种亚健康状态。如下图红线框所示。
2. 数据倾斜,集群资源利用率低:由于受到拓扑图逻辑的影响,同一个Trace的所有Span会路由到同一个窗口(节点)用于还原拓扑图。而当业务存在热点问题时,产生的大量超级Trace也都会路由到同一个节点,从而产生该节点过载,节点窗口处理速度减慢等问题。而大量的数据倾斜会导致集群资源无法得到充分利用,最终造成集群吞吐量下降,处理延时上升。
3. 窗口函数性能差,内存溢出:由于集群一直受到反压问题的困扰,且采用了全局窗口来处理大量的计算逻辑,导致当窗口聚合的数据量较大时,容易发生内存溢出的问题,特别是当开启Checkpoint机制时,内存溢出问题更加频繁。Chekcpoint是保证数据一致性的关键,而反压会影响到Checkpoint 时长和 State 大小,当数据处理被阻塞时, Checkpoint Barrier 流经整个数据管道的时间会变长,其中State 过大会直接导致OOM,而Checkpoint时间变长超过设定时间,最终会导致Chekpoing的失败。
1. 轻重分离:在问题分析一节中有提到过Trace系统很多突发流量不可预测,而系统需要在流量不可控的时候,实现影响面的可控,当遇上业务放量,热点Push等场景造成的流量洪峰时,能够让影响仅仅局限在局部系统,不扩散影响到整个系统。所以我们在系统设计时要考虑轻重分离。天机阁采用了一些常规的分离方法,例如接入层,计算层都是按通道分离,后续又加入了存储分离。原天机阁实时计算系统耦合了调用链与拓扑图两部分逻辑,而天机阁大部分用户对调用链系统的使用场景比较多,业务依赖性比较强。所以在这个基础上我们继续对内容进行细分,对计算逻辑进行解耦,将原系统独立拆分成拓扑图子系统与调用链子系统两部分,对逻辑较轻的调用链子系统进行重新建设,而对逻辑较重的拓扑图系统进行单独优化。其中分离出来的调用链子系统如图所示。
图8.5: 天机阁拓扑图子系统
其中主要涉及两个点的改变:
(1).修改Flink Stream模式,去调Keyby,并将KeyedStream修改为Union Stream模式,可以同时读取多个不同Kafka通道数据,合并为同一个流,这里优化的本质是修改算子的路由策略,将Hash修改为Rebanlance,通过再平衡来减轻数据倾斜的问题。
图8.6: Flink数据路由策略
(2).修改算子流程,将GlobalWindows窗口优化为增量窗口 + HbaseSinkFunction + EsSinkFunction的模式。 (3).调整各个算子并行度,采用Operators-Chain模式。尽可能的将Operator的Subtask链接一起形成Task,从而让每个Task在一个线程中执行。这样可以大量减少线程之间的切换以及数据在缓冲之间的交换,提高整体吞吐量。
2. 窗口算子优化:Flink 窗口是无限数据流处理的核心,将一个无限流的Stream拆分成有限大小的“Buckets”桶,业务可以在这些桶上做计算操作。天机阁使用KeyedStream + Globalwindow + ProcessFunction 模式来处理业务,这种模式提供了一定灵活性,可以将窗口中的所有元素缓存起来,从而获得全量数据的迭代能力,但这确带来了性能成本与资源的损耗,会占用更多的内存以及窗口耗时,也意味着无法进行增量迭代操作。所以这里直接对窗口逻辑进行解耦,简化窗口函数的逻辑同时采用中间状态更小的增量窗口函数算子,整体基于TimeWindow + ReduceFunction/AggregateFunction + SinkFuntion模式代替原先的全局窗口模式。
3. 存储优化:Flink是优化的最后一部,事实上在优化完存储后,反压现象明显减轻了许多。
4. 拓扑图子系统优化:拓扑图优化会借鉴Zipkin-Depencency,Jeager Analytics 两阶段预聚合的思路来进行优化,,后面会有专门文章详细介绍
1. 算子反压,资源倾斜问题得到解决。
2. 虫洞集群平均处理延时由30-60s 下降到1s,由分钟级优化到秒级,且集群更加稳定。
3. 集群资源利用率显著提高,目前只灰度了一个任务,节约了120VCores,预计全量后可以节约35%(300VCores)左右的资源。
目前新系统已经在生产环境,稳定运行1个月左右,数据延时也从之前分钟缩短到10s内,数据实时性大大提高,同时资源利用率的提升也节省了大量的计算资源。
Flink实时计算系统是天机阁链路追踪平台的重要组成部分,而日益增长的数据量给计算集群带来了很大的挑战。面对这些问题,我们重新梳理了整个链路架构,找到系统的瓶颈所在,并展开了一系列有效的优化措施。而在未来,我们会继续在大数据领域的探索研究工作,更进一步的打磨系统数据处理能力,提供更好的服务。
整体从计算层、存储层、架构、服务质量等几个维度对系统进行了优化,同时也加强了系统的容灾能力
9.2 优化后效果图
9.3 思考
性能是用户体验的基石,而性能优化的最终目标是优化用户体验,俗话说:“天下武功,唯快不破”,这句话放到性能优化上也是适用的,我们优化存储摄入速度,优化Flink的处理速度以及接入层的数据采集能力,都是为了保证数据的“快”。而优化的过程则需要我们做好打持久战的准备,既不能过早优化,也不能过度优化。最好的方式是深入理解业务,了解系统瓶颈所在,建立精细化的的监控平台,当系统出现问题时,我们就可以做到有条不紊,从应用,架构,运维等层面进行优化分析,设定一些期望的性能指标,并对每次优化措施和效果做总结思考,从而形成自己的方法论。