阿里 10 PB/天日志系统设计和实现
作者 | 孙廷韬 (龙悟)
在 10 年开始,本人参与了飞天分布式系统的建设,主要从事神农监控模块,我们发现在分布式环境中, 问题排查比较困难。例如,在上千台机器的集群上,当一个 job 跑得慢了,或者跑失败了,很难定位原因,不能快速确定到底是哪个模块出现异常,还是机器硬件故障,或者内核问题,抑或本身达到资源使用上限导致。
为了定位问题,往往需要登录机器,查看各种散落的信息,有时候为了简便, pssh + grep 的命令组合,来查询日志,而这样的操作也存在风险,当系统负载本身较高时,全量扫描操作可能把机器直接打死。当时一个简单的想法是,能否有一个统一的平台,将散落的信息集中采集,提供查询,满足业务快速定位问题的需求。
经过多年的演进,我们总结出一个通用的日志系统需要满足这样几个需求:
集中化采集 : 从数十万、百万的机器上,将散落的数据集中化采集到服务端。主要考虑到采集 Agent 的性能、资源占用、可管理性,以及足够的易用性,用户在使用的时候,可以直接在 Web 控制台上进行配置管理,而无需登陆机器;
实时消费:数据产生方和消费方,往往不是同一个团队,大量的团队对日志有实时消费需求,如使用 storm、spark streaming、flink 等各类流式系统进行消费,因此日志系统需要能够和这些系统进行很好对接,提供可靠的实时消费能力,在高吞吐的同时,有快速扩容的能力;
分析和可视化:对数据有交互式分析的能力,在秒级的时间内,查询或者分析数十亿,甚至百亿数据的能力,将结果以可视化的方式进行展示,同时,为了更高效分析问题,在某些领域,可以进行更深度的功能挖掘,如 DevOps/AIOps 领域;
离线分析需求:对于大量历史数据,有更深度离线分析需求的场景,需要使用数仓的方案来解决问题,日志系统需要能够和数仓进行对接。
虽然,以上的每个功能,都有多种开源解决方案,但是这些方案更多以单独软件的方式提供,一个完整的日志平台,需要使用多套软件进行拼接。而在百万级服务器、一天数十 PB、万级应用场景下,对系统的规模、稳定性、可运维性都提出了很大挑战,很难直接使用这些软件完成。针对这种情况,我们从零开始构建一套日志平台,以服务的形式,供用户直接使用,主要聚焦于实时场景,希望日志从产生到最终的查询、分析,可以在秒级时间内完成。
随着日志系统的完善,基本打通了日志从产生到消费使用的各个环节:
采集:
Logtail : 多年百万级服务器锤炼,简便、可靠、高性能;
SDK/Producer : Java/C/Go/iOS/Android/Web tracking;
全球加速 : 集成 DCDN 全球加速。
存储 & 消费:
ConsumerGroup : 自动负载均衡、failover、服务端 checkpoint 持久化;
生态集成:支持 Blink/Flink/Storm/Spark streaming 等多种流系统;
Shipper:支持 Oss、Odps 数据转存。
查询 & 分析:
Text、JSON、Double、Long;
支持中文,JSON 自动展开,全文 / 键值;
百亿级规模查询;
DevOps 场景:上下文、livetail、LogReduce;
分析功能:SQL92、交互式查询、机器学习、安全特色函数。
可视化
原生 Dashboard、十几种图形展示;
JDBC 接口,支持 DataV、Grafana、QuickBI 展示。
接下来,重点介绍日志系统服务端在设计和实现中遇到的技术挑战和我们的设计思路,在之前,先看一下当前系统规模。
以上是当前的系统规模,而技术上的挑战,很大一部分都是由于规模引起的。
核心挑战:
成本: 在当前规模下,成本会成为一个重点关心的因素,一个重要的问题是如何选择存储介质,SSD 性能好,但是单价成本高,HDD 则相反。如果使用 HDD,能否满足海量数据,快速查询的需求;
性能:随着业务发展,对于日志系统的性能提出了更高要求,单一应用日志,日写入量达到上百 TB,已非常常见,单次查询覆盖的数据量超过百亿也是正常;
集群划分:为了提供可靠、稳定服务,是否需要为了重点用户单独构建集群,还是多用户共享同一集群,在当前应用规模下,单独小集群部署会带来非常巨大的资源浪费和运维负担,而变得越来越不可行;
稳定性:如果是共享集群部署,需要保障各用户之间服务的可靠性,资源能否做到相互隔离,互不干扰;
运维管理:当前服务的 Agent 部署已超过 200W,覆盖的应用日志有数十万,需要高效运维和管理这些 Agent。
针对以上挑战,我们考虑日志的特点,以及日志场景业务的需求,做了如下取舍权衡:
核心追求快和低成本;
放弃严格强一致性,只要 99% 的情况下,日志在 1 秒内可以查询,能满足大部分需求;
放弃完全准确性,可以尽快返回部分结果,以满足快速交互式查询的需求;
如果计算量很大场景,在有限的资源情况下,限制查询 QPS。
最终,根据以上的取舍权衡,我们在系统设计上,大规模使用 HDD 磁盘来存储数据,少量使用 SSD 磁盘;部署大规模的集群,不同用户可以共享集群资源;系统性能、稳定性、QoS 等问题,则尽量通过技术手段来解决。
系统大量使用了 HDD 作为存储介质,如何使用 HDD 支持百亿的查询,就需要充分考虑当前系统的劣势和优势:
劣势:HDD 单磁盘 IOPS 比较低,只能提供百级别的随机 IO;
优势:集群规模比较大,通常日志场景更多是写多查少,可以进行资源复用。
因此,考虑劣势和优势之后,我们决定通过扩大单次查询资源使用,来支持大范围数据查询。为了有更好的交互体验,每次查询时间会进行限制,尽快返回结果,如果查询数据范围太大,将采用迭代模式。上次查询的结果缓存在 Cache 中,下次查询可以复用之前的结果,从而得到更完整的结果。
而从数据写入到最终查询过程的几个主要环节,系统也做了特定的优化:
原始日志写入 :降低原始数据写入的磁盘 IOPS 消耗
系统在设计上,希望单机能支持数千分区,同时支持高 QPS 写入,以及毫秒级别的延时控制;
如果每个分区独立写磁盘的话,HDD 有限的 IOPS 无法支撑高 QPS,并且分散的 IO 写入;
因此,系统使用了 SSD 做中转,合并小 IO,后台将 SSD 数据转存到 HDD 上;
这份数据作为索引的数据源,也用于支持全量实时数据消费。
索引阶段:降低写磁盘的索引流量
降低流量分两部分,一部分是索引数据的大小,索引越小,写磁盘量越小,查询时,读取也越少,速度也越快;另一部分则是,为了加快查询而对索引进行 Compaction 产生的流量;
为了降低索引大小,系统引入 Succinct Tree 进行索引字典编码,大小是 FST 模式的 40%~70%;对 BKD-Tree 进行压缩优化,节省 50% 的空间;根据索引的特点,自动选择最合适 Bitmap 结构,来降低索引空间;
为了降低 Compaction 产生的流量,系统实时监控数据查询情况,对于查询频率比较低的数据,适当放慢 Compaction 的速度,对于系统的整体查询延时影响很小,但可以有效降低磁盘压力;
同时,用 Erasure Coding,将三份拷贝变成 1.4 份,也能降低磁盘写入量。
查询阶段:减少不必要的计算和扩大查询并发
通过时间过滤无效的数据段来降低查询计算的数据总量;
使用 cache 缓存各类数据和计算结果,避免数据重复读取;
单次查询并发数,根据系统当时负载,可扩大到数百甚至上千,提高查询能力;
不同节点在参与计算的时候,尽可能降低数据的相互交换,来降低网络开销;
自研的 Bitmap 在参与计算时,可直接在 Encode 的索引 bitmap 上计算获取新的 Encode bitmap,而无需反序列化;
同时对于部分操作使用 SIMD 指令进行加速。
通过以上几个阶段处理,使得日志系统在 HDD 磁盘基础上,也能支持百亿级的查询。
在多租户共享集群资源的情况下,保障服务质量尤为重要,通过多年的发展,我们也遇到和解决了很多问题,总结一下设计上需要重点考虑的因素:
自动反馈、系统自愈:在系统内部实时收集各种数据,并根据这些数据做实时的自动调整,保证系统的稳定和服务 QoS;
指定标准,划清边界:对于系统资源、服务标准做明确的划分,例如每个分区读写流量上限等,保障最低使用限度;
隔离可控,该限则限:所有操作在内部都应该被监控,当某个操作资源使用超过限定,有可能影响服务质量时,需要能够进行限制甚至中止。
接下来介绍一下,我们为了保证系统服务质量的一些具体设计思路。
上图是一个最基础的分布式模型,数据通过 VIP 写入,经由 Nginx 转发到后端进程进行处理,每个后端进程负责多个数据分区。当 1% 的机器出现问题时,比如从正常的 20ms 上升到 5 秒,对于系统的影响会有多大?通过计算,可以发现数据写入平均延时,从 20ms 上升到 69.8ms, 最大吞吐下降超过 70%,如果客户端有足够的并发进行数据写入,则数据还能正常写入,只是个别请求延时较高;如果并发不够,由于最大吞吐下降 70%,数据在客户端将出现堆积,导致数据不能全部写入。
由此可见,在分布式环境中,单机的稳定性,对于整体服务的质量影响也非常明显。
接下来介绍保证单机服务质量的几个手段。
系统内部,通过实时数据收集和统计后,自动做出调整,来消除系统中存在的热点,主要有以下两个手段:
自动负载均衡
系统实时统计各节点的负载,以及节点上每个数据分区对于资源的消耗(CPU、MEM、NET 等资源);
负载信息汇报至调度器,调度器自动发现当前是否有节点处于高负载情况;
对于负载过高节点,通过优化组合的方式,将高压力数据分区,自动迁移到负载低的节点,达到资源负载均衡的目的。
自动分裂
实时监控每个分片负载压力;
如果发现持续超过单分片处理上限,则启动分裂;
旧的分区变成 Readonly,生成 2 个新的分区,迁移至其他节点。
通过以上两种方式,在大部分情况能保证整体集群之间的负载均衡。
在自动热点消除外,系统还设计了主动防御机制来抵御异常流量。上图中第三个节点的 X 分区是热点,同时不允许分裂。当大量流量发送到该节点后,就算拒绝所有流量,该节点的网络也会成为瓶颈。因此,系统需要有更好的方式来抵御异常流量:
系统定义了分区的服务标准,在超过标准后,尽力服务(不影响其他分区服务质量的情况下);
当分区压力持续上升,则开始拒绝流量,同时将拒绝信息上报至流控中心;
流控中心将该信息,同步到所有前端 Nginx 节点;
前端节点感知后端分区已开始拒绝请求,在一定时间范围内,会主动屏蔽该分区的流量,将异常流量直接抵挡在前端节点。
通过以上机制,能保证任意一个节点,不会被异常流量打爆,线上实际观察结果,穿透到后端的异常流量,只占正常流量的 1~3%。
在单节点内部,日志系统也使用了多种手段,进行资源隔离,保障服务稳定。
划分服务主要操作类型和对应的关键指标
实时写入 :QPS、写入流量上限;
流式读取:读取并发、流量;
查询分析:查询并发、扫描数据量、磁盘随机 IO seek 数。
将指标同系统资源进行对应管理
隔离原则
保证每个分区最低资源使用;
在不影响 QoS 情况下,弹性上调资源使用上限;
实时监控分区资源使用,如果超过最大上限,或开始影响服务 QoS,则实时中止操作;
被中止的查询任务,同样会将结果缓存在 Cache 中,等下次复用,避免做无用功。
在日志场景中,数据的交互式分析非常重要和有价值,日志系统在支持海量文本、数值查询的基础上,也支持 SQL 92 语法的 Adhoc 的查询,所有的查询结果以可视化形式,展现在报表上。并且,为了方便使用,任意查询页面或报表可嵌入到用户自己的控制台,用户无需登陆阿里云可以直接查看分析数据。
通常,Metric、Tracing、Logging 是问题调查的三大主要数据来源,可以将一般问题调查手段分为以下几个方式:
看:观察各类系统、业务指标,异常日志,单个请求调用链,发现可疑点;
比: 发现可疑点后,需要和历史数据进行比较,或对比个体和整体的差异,以验证异常是否真实发生;
猜:在调查引起异常的根本原因时,往往会猜测各种可能性,然后从多个角度数据进行比较验证。
而人工进行这些操作的时候,往往存在效率底下,重复操作等问题,同时当猜测的组合维度很多时,会存在维度爆炸,光靠个人很难确保看全所有信息。
针对这类问题, 日志系统也尝试使用机器学习等方式,自动探测异常信息,将繁琐杂乱的数据进行整理,使其更加有序,重点信息突出。
以下,通过两个例子进行相关功能介绍。
这是一个真实的线上例子,当时一个 500 台规模的集群,多个模块在报错,短短几分钟内,产生了上亿的日志,如何从这么多数据中,定位导致问题的根本原因。
在这之前,我们通常采用的调查模式是,首先查看各类指标,观察是否有异常,之后从日志中,查询各类错误日志,当有大量异常日志时,需要不停过滤掉无关的错误日志,期待找到有价值的信息。
对此,系统提供了智能日志聚类功能,可以在数秒内,将上亿日志根据其相似性进行聚类,提取各类日志的功能模式。在这个样例中,首先根据 ERROR 这个关键词,从上亿日志中,过滤出 3 千多万错误日志,得到这样几个聚类结果:
其中大部分是队列堵塞的错误,这个是问题的表现,并非根本原因;
之后发现有不少网络 timeout 的错误,而队列堵塞往往是由 timeout 引起的;
之后发现发送到特定机器和端口的请求出现了 timeout,而这个就是这次问题的根本原因。
从这个样例中,通过智能日志聚类,我们快速定位到异常产生的根源,进行 fix。日志聚类这个功能,就是针对现实环境中,海量日志问题排查所设计,快速将杂乱信息有序化,提取重要信息,配合支持对历史数据进行比较等功能,使我们可以真正一眼看尽上亿日志。
在时序场景下,通过数据的建模,提供包括异常检查、时序预测、大规模时序数据层次聚类等一些列函数,来满足多种场景需求:
自动发现系统关键指标异常,降低报警规则配置的复杂度,提高报警准确率;
自动预测水位、账单等资源,进行更合理的预算管理规划;
在集群中,如果单节点和其他节点在行为上偏差比较大,人眼很难定位,通过层次聚类则可以方便定位这种情况
而在根因分析场景,通过频繁集和差异集的方式,用于快速定位和异常最相关的数据集合,如样例中,将出现错误(status >= 500) 的访问数据集,定义为异常集合 A,在这个集合发现 90% 的请求,都是由 ID=1002 引起,所以值得怀疑,当前的错误和 ID=1002 有关,同时为了减少误判,再从正常的数据集合 B(status <500) 中,查看 ID=1002 的比例,发现在集合 B 中的该 ID 比例较低,所以更加强了系统判断,当前异常和这个 ID=1002 有非常高的相关性。
以上是日志系统主要功能和系统设计思路,以及日志场景下的特色功能介绍。从中,可以看到该系统主要的设计思路:
"快"是核心:无论是支持百亿级数据的查询,还是快速的 AdHoc query 分析,亿级日志智能聚类,在设计上,系统都追求"快",以到达交互式分析目的;
功能有所取舍:为了达到"快"的目的,部分日志场景不太重要的功能被舍去,使得系统更简单、高效;
技术降成本:在"快"的同时,也需要降低成本,因此使用了 HDD 磁盘,通过多种技术手段,在 HDD 上也能达到理想的速度;
自愈保稳定:系统通过自我监控,主动调节等方式,保障稳定性,减少人工干预,减低运维复杂度
上图是阿里内部,使用日志服务的一个典型样例:
通过 Logtail,将部署在线下 IDC、云上环境、阿里内部生产环境的多种格式的数据(JSON、正则、分隔符等)实时收集到日志服务;
使用日志查询功能(上下文、livetail、关键词)可以快速从原始数据中检索关心的数据;
使用流式计算实时消费数据,结合部分 SQL 查询结果,展示系统运行大盘;
关键的告警信息,实时获取,结合告警规则,联动实时告警等一系列后续处理;
告警信息中关联 Trace 信息,可以和阿里内部的 Trace 系统打通,而日志服务为阿里多套 Trace 系统提供数据存储和查询能力。
孙廷韬 (龙悟),阿里云高级技术专家,负责阿里云日志服务架构设计和实现。日志服务是针对实时数据一站式服务,提供日志数据采集、智能查询分析、消费与投递等功能,全面提升海量日志处理 / 分析能力。在阿里集团,覆盖百万服务器,上万应用,单日采集数据超过 10PB,为数千工程师提供简单易用的日志分析服务。
点个在看少个 bug 👇