初探分布式链路追踪(上):https://developer.aliyun.com/article/1443500
- 数据埋点
在分布式链路追踪系统中,方法增强(通常称为“埋点”)指的是在代码中插入额外的逻辑来捕获和记录执行信息,这是实现追踪的核心技术。Dapper,谷歌的分布式追踪系统,提出的设计原则要求方法增强必须同时满足应用级透明性和低开销这两个关键条件:
- 应用级透明:开发者不需要修改业务代码或仅需要极少的修改即可实现埋点,这意味着追踪逻辑对应用层是不可见或几乎不可见的。
- 低开销:埋点操作对系统的性能影响应当尽可能小,以避免追踪逻辑本身成为系统性能的瓶颈。
现在,我们来比较阿里巴巴的EagleEye与开源项目SkyWalking在埋点技术上的不同做法:
硬编码
EagleEye:阿里巴巴的EagleEye通过中间件层进行数据采集和追踪,通常这由集团的中间件团队负责维护。这种中间件集成方式允许上层应用无需知晓具体的追踪逻辑,因此在应用级别上保持了较高的透明度。然而,这种方法可能会在中间件升级时需要同步更新埋点逻辑,增加了维护的复杂度。
字节码增强
SkyWalking:SkyWalking提供了自动插桩的能力,通常通过Java Agent技术在运行时不侵入地修改字节码,插入追踪代码。这种方式对业务代码完全透明,开发者无需修改任何业务逻辑即可实现埋点。同时,SkyWalking旨在保持低开销,以减少对应用性能的影响。由于它是一个开源项目,SkyWalking更容易适应多样化的技术栈和快速发展的生态。
两种方法各有优势和劣势,EagleEye的中间件集成方式适合阿里巴巴这样拥有统一技术栈和强大内部支持的大型企业环境。而SkyWalking的自动插桩技术则提供了灵活性和广泛的社区支持,更适合多变的技术环境和需要快速适应新技术的公司。选择哪种埋点方式取决于企业的具体需求、技术栈的复杂性以及维护能力。
- 跨线程传递
在多线程并发调用环境下的数据链路埋点也是一个值得关注问题,当一个服务考虑性能问题可能会起多个线程同时调用其他不同的模块。链路系统如何保证这些调用还是符合 openTrace 规范,保证 traceId 和 spanId 有序。
目前Java生态下,普遍的思路是用父线程的ThreadLocal中拿到保存的trace信息,然后作为参数传递给子线程,子线程在初始化的时候设置trace信息来避免丢失。
值得关注的是,集团提供了InheritableThreadLocals的子类TransmittableThreadLocal,解决了InheritableThreadLocal只能再new Thread的时候传递本地变量,无法应用到线程池的问题。可以应用来作链路追踪,传递变量等用途。美团也实现了类似的子类TransmissibleThreadLocal。
日志收集和分析
日志用来记录系统运行期间发生过的离散事件,在目前复杂的分布式系统中,很难只依靠 tail、grep、awk 来从单台机器的日志中挖掘信息了,往往还要有专门的全局查询和可视化功能。此时,从打印日志到分析查询之间,还隔着收集、缓冲、聚合、加工、索引、存储等若干个步骤,如下图所示:
提到日志实时分析,大部分人第一想到是社区很火ELK Stack。ELK方案上手难度小、开源材料众多、在社区中有大量的使用案例。于此同时,阿里巴巴集团也推出了对日志场景的解决方案产品日志服务(SLS/Log) 。下面将先后介绍这两种框架并分析优劣。
▐ 开源的ELK方案
ELK Stack 是一个缩略词,用来描述由三个常见项目组成的堆栈:Elasticsearch、Logstash 和 Kibana。ELK Stack 通常被称为 Elasticsearch,作用是聚合来自所有系统和应用程序的日志,分析这些日志,并创建可视化来进行应用程序和基础设施监控、更快的故障排除、安全分析等。
E = Elasticsearch
Elasticsearch 是在 Apache Lucene 上构建的分布式搜索和分析引擎。对各种语言、高性能和无架构 JSON 文档的支持使 Elasticsearch 成为各种日志分析和搜索使用案例的理想选择。
L = Logstash
Logstash 是一个开源数据摄取工具,允许从各种来源收集数据,转换数据,并将数据发送到希望的目标。
K = Kibana
Kibana 是一种数据可视化和挖掘工具,可以用于日志和时间序列分析、应用程序监控和运营智能使用案例。
- 收集与缓冲
写日志是在服务节点中进行的,但我们不可能在每个节点都单独建设日志查询功能。这不是资源或工作量的问题,而是分布式系统处理一个请求要跨越多个服务节点,为了能看到跨节点的全部日志,就要有能覆盖整个链路的全局日志系统。这个需求决定了每个节点输出日志到文件后,必须将日志文件统一收集起来集中存储、索引,由此便催生了专门的日志收集器。
最初,ELK 中日志收集与下一节要讲的加工聚合的职责都是由 Logstash 来承担的,Logstash 除了部署在各个节点中作为收集的客户端(Shipper)以外,它还同时设有独立部署的节点,扮演归集转换日志的服务端(Master)角色。Logstash 有良好的插件化设计,收集、转换、输出都支持插件化定制,应对多重角色本身并没有什么困难。但是 Logstash 与它的插件是基于 JRuby 编写的,要跑在单独的 Java 虚拟机进程上,而且 Logstash 的默认的堆大小就到了 1GB。对于归集部分(Master)这种消耗并不是什么问题,但作为每个节点都要部署的日志收集器就显得太过负重了。后来,Elastic.co 公司将所有需要在服务节点中处理的工作整理成以Libbeat为核心的Beats 框架,并使用 Golang 重写了一个功能较少,却更轻量高效的日志收集器Filebeat。
日志不追求绝对的完整精确,只追求在代价可承受的范围内保证尽可能地保证较高的数据质量。一种最常用的缓解压力的做法是将日志接收者从 Logstash 和 Elasticsearch 转移至抗压能力更强的队列缓存,譬如在 Logstash 之前架设一个 Kafka 或者 Redis 作为缓冲层,面对突发流量,Logstash 或 Elasticsearch 处理能力出现瓶颈时自动削峰填谷,甚至当它们短时间停顿,也不会丢失日志数据。
- 加工与聚合
将日志集中收集之后,存入 Elasticsearch 之前,一般还要对它们进行加工转换和聚合处理。这是因为日志是非结构化数据,一行日志中通常会包含多项信息,如果不做处理,那在 Elasticsearch 就只能以全文检索的原始方式去使用日志,既不利于统计对比,也不利于条件过滤。
Logstash 的基本职能是把日志行中的非结构化数据,通过 Grok 表达式语法转换为上面表格那样的结构化数据,进行结构化的同时,还可能会根据需要,调用其他插件来完成时间处理(统一时间格式)、类型转换(如字符串、数值的转换)、查询归类(譬如将 IP 地址根据地理信息库按省市归类)等额外处理工作,然后以 JSON 格式输出到 Elasticsearch 中(这是最普遍的输出形式,Logstash 输出也有很多插件可以具体定制不同的格式)。有了这些经过 Logstash 转换,已经结构化的日志,Elasticsearch 便可针对不同的数据项来建立索引,进行条件查询、统计、聚合等操作的了。
- 存储与查询
Elasticsearch 是整个 Elastic Stack 技术栈的核心,其他步骤的工具,如 Filebeat、Logstash、Kibana 都有替代品,有自由选择的余地,唯独 Elasticsearch 在日志分析这方面完全没有什么值得一提的竞争者,几乎就是解决此问题的唯一答案。核心原因是 Elasticsearch 的优势正好与日志分析的需求完美契合:
- 数据特征的角度看,日志是典型的基于时间的数据流。日志虽然增长速度很快,但已写入的数据几乎没有再发生变动的可能。以按日索引为例,由于你能准确地预知明天、后天的日期,因此全部索引都可以预先创建,这免去了动态创建的寻找节点、创建分片、在集群中广播变动信息等开销
- 从数据价值的角度看,日志基本上只会以最近的数据为检索目标,随着时间推移,早期的数据将逐渐失去价值。这点决定了可以很容易区分出冷数据和热数据,进而对不同数据采用不一样的硬件策略。
- 从数据使用的角度看,分析日志很依赖全文检索和即席查询,对实时性的要求是处于实时与离线两者之间的“近实时”,即不强求日志产生后立刻能查到,但也不能接受日志产生之后按小时甚至按天的频率来更新,这些检索能力和近实时性,也正好都是 Elasticsearch 的强项。
Elasticsearch 只提供了 API 层面的查询能力,它通常搭配同样出自 Elastic.co 公司的 Kibana 一起使用,可以将 Kibana 视为 Elastic Stack 的 GUI 部分。Kibana 尽管只负责图形界面和展示,但它提供的能力远不止让你能在界面上执行 Elasticsearch 的查询那么简单。Kibana 宣传的核心能力是“探索数据并可视化”,即把存储在 Elasticsearch 中的数据被检索、聚合、统计后,定制形成各种图形、表格、指标、统计,以此观察系统的运行状态,找出日志事件中潜藏的规律和隐患。
▐ 阿里的SLS方案
阿里云日志服务 SLS 基于阿里云自研灵活索引的一站式云原生可观测数据分析平台。为Log/Metric/Trace 等数据提供大规模、低成本、实时平台化服务。一站式提供数据采集、加工、分析、告警可视化与投递功能。
- 收集与缓存
Logtail是集团云提供的日志服务(SLS)的一个组件,它作为日志采集客户端,提供了一种高效且易于配置的方式来接入日志数据。由于Logtail是用C++语言编写的,它具有较高的性能和较低的资源消耗,适合在各种环境下运行,包括资源受限的环境。
Logtail的主要功能包括:
- 无侵入式采集:Logtail能够监视指定的日志文件,实时采集更新的日志数据,而无需修改现有的应用程序代码。
- 多种日志格式支持:除了采集标准的文本日志文件,Logtail还能够采集其他类型的日志,如MySQL的二进制日志(binlog)、HTTP数据、以及容器日志等。
- 容器化环境支持:Logtail提供对容器友好的支持,无论是标准容器还是处于Kubernetes集群中的容器,Logtail都能够灵活地采集日志数据。
- 异常处理:Logtail有一套完备的机制来处理在日志采集过程中可能出现的异常,如当遇到网络故障或服务端异常时,它可以通过主动重试或将数据缓存到本地等方式来确保数据的安全和完整性。
由于Logtail的这些特性,它成为了一个非常强大且可靠的日志采集工具,特别是在需要确保日志数据不丢失的情况下。它的无侵入性质意味着开发者或运维人员可以在不影响现有应用程序的情况下部署日志采集,而其对容器和微服务架构的友好支持则使其非常适合现代云原生应用。
Logtail作为日志采集客户端,其文件采集流程设计得非常全面,涵盖了从日志生成到传输的多个关键环节。以下是Logtail文件采集流程的六个主要步骤:
- 文件监听:Logtail监控配置中指定的日志文件或目录,以便于检测文件的创建、更新或者删除事件。这通常通过文件系统的通知机制来实现,如Linux上的inotify。
- 文件读取:一旦文件发生变化,Logtail会读取文件内容。对于更新的文件,Logtail保持追踪当前的读取进度,确保从上次停止的地方继续读取,避免重复或遗漏。
- 日志处理:读取到的原始日志数据会经过初步处理,比如分割日志行、解析字段等,将原始文本转换为结构化的日志记录。
- 日志过滤:用户可以定义过滤规则,根据日志内容或属性决定哪些日志记录应该被采集,哪些应该被忽略。这一步可以减少无用数据的传输和存储,优化成本和性能。
- 日志聚合:在发送之前,Logtail可以对日志进行批处理,合并多个日志记录为一个数据包,减少网络请求的次数,提高数据传输效率。
- 数据发送:聚合后的日志数据会被发送到配置的目标,例如阿里云日志服务SLS的服务器。数据发送过程中,Logtail会处理网络异常等问题,确保数据的可靠传输。
以上六个环节构成了Logtail文件采集的完整流程,每个步骤都是为了确保日志数据的准确、高效、可靠采集。在高可用性和高性能的要求下,Logtail的设计使得日志数据能够从源头到目的地的迁移过程中保持完整性和一致性。
对比开源的logstash,和logtail的对比如下:
功能项 |
logstash |
logtail |
日志读取 |
轮询 |
事件触发 |
文件轮转 |
支持 |
支持 |
Failover处理 (本地checkpoint) |
支持 |
支持 |
通用日志解析 |
支持grok(基于正则表达式)解析 |
支持正则表达式解析 |
特定日志类型 |
支持delimiter、key-value、json等主流格式 |
支持key-value格式 |
数据发送压缩 |
插件支持 |
LZ4 |
数据过滤 |
支持 |
支持 |
数据buffer发送 |
插件支持 |
支持 |
发送异常处理 |
插件支持 |
支持 |
运行环境 |
JRuby实现,依赖JVM环境 |
C++实现,无特殊要求 |
线程支持 |
支持多线程 |
支持多线程 |
热升级 |
不支持 |
支持 |
中心化配置管理 |
不支持 |
支持 |
运行状态自检 |
不支持 |
支持cpu/内存阈值保 |
总体来说,logstash支持所有主流日志类型,插件支持最丰富,可以灵活DIY,但性能较差,JVM容易导致内存使用量高。logtail占用机器cpu、内存资源最少,结合阿里云日志服务的E2E体验良好,但目前对特定日志类型解析的支持较弱。
- 加工与聚合
阿里云日志服务(SLS)提供的数据加工功能是一项强大的特性,使得用户能够在日志数据进入存储和分析阶段之前对其进行预处理。这些功能对于数据的清洗、转换、安全保护和增强都非常关键,尤其是在构建复杂的数据处理流水线时。以下是数据加工功能的几个关键点和它们的应用场景。
- 数据规整:这个功能允许用户对无结构或半结构化的日志进行字段提取和格式转换,从而得到结构化数据。这对于后续的实时处理、分析和数据仓库计算来说是非常重要的,因为结构化数据更容易进行查询和分析。
- 数据富化:数据富化功能可以通过字段连接(JOIN)操作,将日志数据(如订单日志)和其他维度数据(如用户信息表)相结合。这样可以为原始日志添加更丰富的上下文信息,提高数据分析的深度和准确性。
- 数据流转:流转功能支持跨地域的数据传输,允许用户将数据从一个地域转移到另一个地域。例如,可以将海外地域的日志数据传输到中心地域,实现日志的集中化管理和分析。
- 数据脱敏:数据脱敏对于符合隐私法规和公司政策是必不可少的。SLS可以在日志数据中识别并隐藏或替换敏感信息,比如密码、手机号或地址等,来保护用户隐私。
- 数据过滤:数据过滤允许用户基于特定条件选择性地提取日志条目。这可以用来隔离关键服务的日志,以便进行重点监控和分析。
应用场景:
- 数据规整(一对一):从单个源Logstore读取数据,经过加工处理后,输出到单个目标Logstore,适用于简单的加工任务。
- 数据分派(一对多):从单个源Logstore读取数据,根据不同的规则或条件将数据加工后分发到多个目标Logstore中,适用于需要将日志按类型或用途分开处理的场景。
- 数据融合(多对一):从多个源Logstore读取数据,将它们合并或加工后输出到一个目标Logstore中,适用于需要综合多个数据源进行统一分析的场景。
通过这些数据加工功能,SLS能够提供灵活、强大的数据处理能力,帮助用户更好地管理和分析日志数据,同时保护敏感信息。这对于需要实时数据洞察和快速响应的企业来说是非常有价值的。
- 存储和查询
阿里云日志服务(SLS)的存储引擎专门为处理可观测数据而设计,包括日志、指标和追踪数据。它旨在提供一个统一和高效的解决方案,用以满足大规模数据存储和快速查询的需求。下面是对这个存储引擎的两个关键特性的进一步解读:
统一性(Unified)
- 多类型数据支持:统一的存储引擎意味着它能够存储和处理多种类型的可观测数据,无论是结构化的日志数据、时间序列的指标数据还是分布式追踪的信息。这样的设计减少了需要使用和管理多个存储系统的复杂性。
- 统一查询接口:有了统一的存储引擎,用户可以通过一个共同的查询接口访问不同类型的数据,这提高了使用效率,并允许跨数据类型的分析和关联。
- 统一的管理和运维:统一存储引擎也简化了数据的管理和运维任务,因为它减少了需要学习和监控的工具和接口的数量。
性能(Fast)
- 高速写入:SLS的存储引擎针对高速数据写入进行了优化,以支持大数据量的实时采集。这对于高流量网站、大型分布式应用和IoT设备等场合至关重要。
- 高效查询:SLS存储引擎还针对快速查询进行了优化,确保即使在涉及大规模数据集时,也能够快速返回结果。
- 适应大规模场景:阿里云天然服务于各种规模的企业,包括那些拥有超大规模数据场景的企业。SLS的存储引擎设计考虑了这些场景的需求,保证了扩展性和稳定性。
总体来说,SLS的存储引擎通过统一和快速的写入与查询功能,为处理大规模的可观测数据提供了一个强大、高效且易于管理的解决方案。整体架构如下:
接入层协议支持:
- 接入层的设计非常灵活,支持多种数据写入协议,这意味着无论数据来源是什么(如应用程序、服务器、网络设备等),都能够顺利与SLS集成。
- 写入的数据首先被放入一个先进先出(FIFO)的管道中,这个管道模型类似于Apache Kafka的消息队列(MQ)模型,提供了高吞吐量和可靠性。
管道和数据消费:
- 管道不仅存储数据,还支持数据消费,允许各类下游系统(如实时分析、告警、可视化工具等)接入并处理数据。
- 这种设计实现了数据的异步处理,增强了系统的整体扩展性和弹性。
索引结构和存储优化:
- 在管道之上,存储引擎为不同类型的数据(Traces/Logs和Metrics)构建了两套索引结构。
- 利用倒排索引(Inverted Index)、列式存储(Columnar Storage)以及压缩(Compaction)等技术,SLS能够提供快速的查询性能,同时优化存储空间的使用效率。
统一的进程架构:
- 上述所有功能都在一个统一的进程内实现,这意味着可以在单一的系统中处理日志、指标和追踪数据,大大简化了运维和部署的复杂性和成本。
纯分布式框架:
- SLS的存储引擎基于纯分布式架构构建,支持水平扩展,可以随着数据量的增长而增加更多的存储节点。
- 单个存储节点(Store)能够处理极高的数据量,最多支持每天处理PB级别的数据写入,适合大规模的数据中心和云环境。
在查询方面,SLS的查询和分析引擎整合了多种查询语法和分析能力,提供了一个强大的查询环境:
- 关键词查询:支持对存储在SLS中的日志数据进行全文搜索,类似于其他日志管理系统中的关键词查询功能。这主要用于快速查找特定日志条目或筛选日志信息。
- PromQL的语法兼容:PromQL是Prometheus查询语言,专门用于时间序列数据的查询。SLS兼容PromQL语法,使得用户能够利用PromQL进行指标数据的查询和分析。
- SQL作为顶层分析语言:1、为了实现对可观测数据的深入分析,SLS扩展了SQL语言的能力,使其能够支持连接关键词查询、PromQL等语法;2、通过SQL,用户可以执行更复杂的数据操作,如聚合、排序、联接和子查询等,这为数据分析提供了极大的灵活性。
- 外部数据源和机器学习模型的集成:SLS的SQL扩展还允许用户连接外部数据库和机器学习模型。这意味着用户可以将SLS中的数据与其他数据源相结合,或使用机器学习算法进行高级分析。
- 可观测数据的融合分析:SLS使得日志、指标和追踪数据能够在一个统一的分析环境中进行关联和分析。这种融合分析能力使用户能够以多维度来理解和分析系统的行为和性能。
通过这样的设计,SLS提供了一个功能全面的查询和分析环境,可以满足从简单的文本搜索到复杂的数据挖掘和分析的各种需求。对于开发人员、运维人员和数据分析师来说,SLS的查询功能极大地提高了他们处理和分析可观测数据的效率和准确性。
▐ ELK 对比 SLS
ELK的核心 ElasticSearch 基于 Lucene 全文索引实现,面向 Document 类型,构建一套完整的可观测分析平台需要组合多款服务,这其中包括 logstash、Kibana、Kafka、Flink、TSDB、Prometheus 等服务,对于规模、查询能力等方面存在一定限制,整体成本较高。
日志服务 SLS 基于阿里云自研灵活索引,支持一站式云原生可观测性,多规格多场景支持。
详细对比下两者在以下方面的差异:
- 性能:在同样成本的介质上部署,SLS 能够提供更低的延时、以及更稳定的查询性能。
- 成本:这里的成本不光是部署成本,还需要包括使用成本。要维护良好状态的 ELK 集群,需要从容量规划、稳定性保障、性能调优、数据高可用,数据如何在不同系统间关联等多个方面下功夫。SLS 全托管免运维无需花费额外人力投入。百 TB 规模下,SLS 综合成本是 ELK 的 44%。
- 易用性:SLS 对开源协议及组件相比 ELK 有更好的兼容性。利用 ELK 构建完整可观测分析平台需组合多款服务,这其中包括Logstash、Kibana、Kafka、Flink、TSDB、Prometheus等。SLS 在这个场景上无需多个组件搭配,支持一站式数据监控分析平台能力。
- 互联互通与二次开发:在可观测场景中,我们需要跨多个数据源进行关联分析SLS 可观测数据统一存储支持 log metric trace 数据存储,打通数据孤岛,并且提供开源友好的 API 接口进行使用。
- 附加能力(新算子、告警等):开源 ELK 仅支持指标分析聚合、分桶聚合、管道分析、矩阵分析有限的聚合算法。SLS 针对数据分析场景支持 30+ 聚合计算函数,丰富的机器学习函数以及多渠道数据源,是 ELK 提供操作符的 5 倍,充分发掘数据分析能力。除此之外,SLS 内置告警功能可以快速开箱即用,无需搭建系统。
参考文章
- https://icyfenix.cn/distribution/observability/tracing.html
- https://tech.meituan.com/2023/04/20/traceid-google-dapper-mtrace.html
- https://tech.meituan.com/2022/07/21/visualized-log-tracing.html
- https://developer.aliyun.com/article/1065820#slide-4
- https://aws.amazon.com/cn/what-is/elk-stack/
团队介绍
我们来自淘天集团的营销与平台策略技术。我们支撑大促 (双11、618 等)和日销业务;同时我们也直面竞对,深入参与淘宝好价、百亿补贴、聚划算等日销业务的价格心智打造。秉承“简单、开放、自驱”的技术文化,在使命与责任中互相成就,共同成长,践行让业务先赢,技术氛国浓郁。