跳到主要内容

什么是eBPF

· 阅读需 3 分钟
Kindling-OriginX
故障根因推理引擎

eBPF 技术

eBPF(extended Berkeley Packet Filter)是一种先进的系统内核技术,最初源自于 Berkeley Packet Filter(BPF)。它是一种在 Linux 内核中执行高度优化的程序的框架,允许用户编写并插入小型程序(称为 eBPF 程序)来对系统内核的行为进行扩展和控制。 eBPF 允许用户在不更改内核源代码或重新编译的情况下,通过加载在内核中运行的小型程序来扩展内核功能。这些程序可以捕获和分析系统运行时的事件、执行网络分析、实现安全策略、执行性能调优等操作。 eBPF 的一些关键特性和优势包括:

  • 安全性:eBPF 程序在一个受限的虚拟机中运行,不会影响系统稳定性和安全性。
  • 灵活性:可以动态加载、卸载和更新eBPF程序,无需重新启动系统。
  • 性能:eBPF 程序经过优化,能够在内核中高效执行。
  • 可观察性:能够捕获和分析系统运行时的各种事件,有助于故障排除和性能分析。

eBPF 技术在多个领域得到应用,包括网络编程(例如网络包过滤、路由)、性能分析、安全监控、容器技术等。它已经成为许多现代系统中实现高级功能和控制的重要工具之一。 这里可以简单概括为 eBPF 是一套通用执行引擎,提供了可基于系统或程序事件高效安全执行特定代码的通用能力,通用能力的使用者不再局限于内核开发者;eBPF 可由执行字节码指令、存储对象和 Helper 帮助函数组成,字节码指令在内核执行前必须通过 BPF 验证器 Verfier 的验证,同时在启用 BPF JIT 模式的内核中,会直接将字节码指令转成内核可执行的本地指令运行。 68747470733a2f2f7069632e6372617a7974617869692e636f6d2f6f766572766965772d62663436333435356135363636666333666238343162393234306435383866662e706e67.png

什么是北极星排障指标体系

· 阅读需 10 分钟
Kindling-OriginX
故障根因推理引擎

简介

北极星排障指标体系是一组指向性明确的指标,通过这些标准化的指标体系指出问题根因和结论,在排障过程中给予参与人员明确的方向指导,给出问题的明确界限。通过北极星排障指标体系,可以根据企业业务流程和自身工具情况构建出标准化的排障流程,进而真正能够在实际生产环境中落地1-5-10。

北极星指标

北极星指标也叫唯一关键指标(OMTM,One metric that matters),产品现阶段最关键的指标,其实简单说来就是公司制定的发展目标,不同阶段会有不同的目标。为什么叫“北极星”指标,其实大概的寓意就是要像北极星一样指引公司前进的方向,目标制定最好是能符合SMART原则,具体指具体(Specific)、可以衡量的(Measurable)、可实现(Attainable)、相关性(Relevant)、有明确的截止期限(Time-bound)。 北极星指标通常具有三个价值,指引未来、团队协同和结果导向。例如电子商务类的商业模式,他们的核心价值通常是用户下单购物,因此常见的北极星指标是GMV或订单量。对于媒体网站或内容类商业框架,企业更关注用户的阅读和创作,那么北极星指标通常为阅读量、停留时长、作品数等。而即时通讯类APP的核心价值是用户间的信息传输,所以企业通常会把消息数、发送消息的用户数等作为北极星指标。

北极星排障指标

目前问题

  • 传统可观测性工具存在盲区

目前虽然 Tracing、Metrics、Logging 已经成为绝大多数云原生业务的标配,但在实际生产环境中可观测性工具仍旧存在着很多盲区。实际应用过程中,大多数情况下往往仍旧采用填坑的方式,一方面在有可能有问题的部分增加日志及相关追踪信息,另一方面根据历史数据和经验在发生过问题的部分增加指标和相关事件记录信息,导致很容易丢失故障现场信息,同时无法真正发现问题根因。同时,传统可观测工具提供和记录的各类相关指标往往只有专家才能对其进行解读,并将其理解运用解决生产环境中的实际问题,这便使得数据、指标记录了,但是盲区却仍旧存在。

  • 排障周期不可预期

排障周期是指从发现问题到最终解决问题的整个过程。在理想情况下,这个周期应该是可预测的,这意味着团队可以估计出从识别问题到解决问题所需的时间。然而,在实际操作中,排障周期往往是不可预期的。主要原因有:排障极大程度上依赖参与排障人员的主观经验,排障知识的积累因人而异,导致其周期没法预估,受当次故障处置参与人员的经验和能力影响大;另一方面排障周期往往取决于故障发生时或发生前先看到什么现象,其会对处置人员的定位思路和处置决策产生影响,存在一定运气成分,进而使得同样的故障,即使是相同的根因,也会由于不同的现象导致排障周期不同。

北极星排障指标体系理论

基于目前的问题及困境,龙蜥社区与 Kindling 社区联合发布了北极星排障指标体系,通过将传统北极星指标的概念和方法论引入到排障流程中,构建出了一套排障指标体系与标准化的步骤,力求在目前困境中找到一条可操作可落地的标准化排障之道。

北极星排障指标-CPU时间

程序在CPU资源上所消耗的时间

  • OnCPU

程序代码执行所消耗的CPU cycles,可以通过程序火焰图确认代码在 CPU上执行消耗的时间与代码堆栈.

  • Runqueue

线程的状态是Ready,如果CPU资源是充分,线程应该被调度到 CPU上执行,但是由于各种原因,线程并未调度到CPU执行,从而产生的等待 时间。

北极星排障指标-网络时间

网络时间属于两次OnCPU时间之间的OffCPU时间

  • 网络时间打标过程

第一次OnCPU最后一个系统调用执行为sock write与第二次 OnCPU第一个系统调用为sock read,也可以理解为网络包出网卡至网络包从网卡收回的 时间。

  • 网络时间分类

DNS,TCP建连,常规网络调用

北极星排障指标-存储时间

属于两次OnCPU时间之间的OffCPU时间

  • 存储时间打标过程

第一次OnCPU最后一个系统调用执行为VFS read/write与第二次 OnCPU第一个系统调用为VFS read/wirte。

  • 存储时间真实情况

存储真实执行情况,由于内核的pagecache存在,所以绝大多数VFS read/write从程序视 角看:执行时间不超过1毫秒。

北极星排障指标-等待时间

  • futex

通常指的是一个线程在尝试获取一个futex锁时因为锁已经被其他线程占用而进入等待状态的时间。在这段时间内,线程不会执行任何操作,它会被内核挂起。

  • mutex_lock

线程在尝试获取互斥锁时,因为锁已经被其他线程持有而进入等待状态的时间长度。这段时间对于程序的性能至关重要,因为在等待期间,线程不能执行任何有用的工作。

实现要点

  • OnCpu时间

进程真正获取cpu的时间,从获取cpu(sched_switch)到失去cpu(sched_switch)的时间。

  • Runqueue时间

进程在运行队列中的时间,记录线程从被唤醒(sched_wakeup)到获取cpu(sched_switch)的时 间;如果线程主动让出cpu,则记录失去cpu到重新获取cpu的时间。

  • 等待时间

进程从失去cpu(sched_switch)到被唤醒(sched_wakeup)的时间,然后通过sched_switch off 时抓的堆栈,进行阻塞原因分析。

  • 等待网络的时间

通过系统调用来辨别:__sys_recvfrom, sendmsg, recvmsg。

  • 等待存储的时间

通过系统调用来辨别: vfs_write, vfs_read, do_sys_openat。

  • 等待锁时间

futex: futex_wait, mutex_lock lock: mutex_lock, mutex_lock_interruptible

  • server端实际执行时间

应用采用同步方式向其他服务器发送请求或者发起新链接,或者进行 DNS,记录发送请求时的四元组: S1=(source_ip, source_port, dest_ip, dest_port);在 server 端进行监听,若获取的四元组 S2 和 S1 相同,则记录 server 端开始执行的时间戳 T1;当 server 端进行请求响应,获取四元组 S3,若 S3 的四元组与 S1 对应相同,则记录结束执行的时间戳 T2;T2 - T1 则为 server 端实际执行时间。

什么是根因分析

· 阅读需 10 分钟
Kindling-OriginX
故障根因推理引擎

根因分析(Root Cause Analysis,RCA)是一种问题解决方法,用于识别问题的根本原因,然后通过解决这些原因来防止问题再次发生。它是一种回溯性过程,旨在发现问题的深层原因,而不仅仅是处理表面的症状或后果。

在可观测领域和IT领域内,根因分析是指在软件工程和系统管理中,当服务或应用出现故障时,通过监控和日志数据来识别导致问题的基本原因的过程。可观测性通常涉及三个主要的数据源:日志(记录事件的详细信息)、指标(系统性能的定量数据)和跟踪(记录请求流程的路径)。

不同人眼中的根因分析

运维工程师眼中的根因分析

在运维工程师或SRE部门承担着业务稳定的职责,所以出现问题之后,第一职责是确认到底该如何操作才能恢复业务。那么这里面需要根因分析吗?可能会有部分人认为运维不需要对故障做根因分析,只需关注进程是否存在,机器是否正常就好了,如果没有问题,就无脑重启业务就应该恢复业务了。 在一些IT架构不复杂,问题故障原因单一的情况下,很多时候是可以通过重启、回滚等手段恢复,但是IT架构一旦庞大起来,或问题根因比较复杂,特别是以微服务架构运行在云原生环境中时,重启能够恢复业务的几率就大大降低了,往往重启也只是一种无目的的猜测手段了。 在云原生环境中,运维工程师需要通过根因分析来达到恢复业务的目的。所谓根因就是运维工程师在故障发生时能够依据的结论,其后依靠这些结论来指导业务的恢复和故障的处置。 在这里例举下运维工程师可以采取的恢复手段,即使同类故障仍旧会在不久的将来再次发生。

  • 识别是代码缺陷,例如内存泄露、GC频率过高等,重启恢复业务。
  • 识别是代码缺陷导致CPU飙升、数据库QPS过高,在允许代码回滚的情况下通过回滚恢复业务。
  • 识别是流量过大的问题,如果确认是流量过大超过系统设计负载能力,在不变更架构设计的情况下,只能通过扩容或者限流措施来快速恢复业务。
  • 识别是共享资源的问题,取得明确的证据,减少跨公司跨团队的协作沟通成本,提交工单提供数据或相关日志,协助公有云或者云供应商更快的解决故障,恢复业务。
  • 识别是第三方软件依赖的问题,取得明确的证据,减少跨公司跨团队的协作沟通成本,督促第三方软件依赖快速解决故障,恢复业务。

运维工程师不可能在不了解故障根因的情况下,依靠穷举猜测试验的方式恢复故障,所以运维工程师非常需要了解到底发生了什么故障,该故障的根因是什么,才能真正的快速恢复业务,做到快速止血。所以针对运维工程师,故障根因结论是以下几种:

  • 位于哪个微服务节点的内存泄露、代码死锁——重启能够解决,确认是否能够重启还是扩容机器,减少风险。
  • 位于哪个服务节点的CPU飙升——确认是否能够回滚,或者扩容机器,减少风险。
  • 访问量激增——确认是扩容哪些节点还是限流某些节点来恢复业务。
  • 共享资源的问题
    • 如果是网络问题,确定哪个节点与节点之间的网络有问题,还是整个网段都有网络问题,提取证据,提交给公有云或者云供应商。
    • 如果是存储问题,确定是何种存储,提取证据,提交给供应商解决。
    • 如果是节点硬件或者配置故障,迁移机器。
  • 第三方依赖的问题,提交证据,交于第三方依赖厂商解决问题。

开发工程师眼中的根因分析

开发工程师往往承担业务实现的职责,但在系统故障时常需协助运维工程师定位运维中的根本原因。同时,开发工程师还需要承担软件缺陷和架构设计问题所致故障的复盘工作,以避免未来类似故障再次发生,并确保对系统及时进行优化。

因此,开发工程师视角下的根因分析需要有效指导故障复盘,能更好地还原故障现场,帮助开发人员理解故障发生的代码层或配置层原因,并及早发现业务架构设计层面的潜在风险。

常见的开发人员视角下的根因包括:

  • 代码死锁现象——开发工程师想知道的根因是锁住的代码块是哪些,代码分别持有锁的时间长度是多久。
  • CPU飙升——开发工程师想知道哪段代码导致的CPU飙升。
  • 内存泄露——开发工程师想知道哪些函数申请的内存具体多少。
  • 网络问题——开发工程师想知道具体访问哪些请求,做了哪些操作从而提取优化思路。
  • 存储问题——开发工程师想知道具体访问了什么文件,I/O是多少,访问频度如何,从而提前设计优化方案。

故障根因分析实践中的困境

虽然在现代IT系统中,各环境参与人员都急需合适的根因分析工具介入来提高效率,优化故障处置流程,但是目前根因分析在真正落地实施时面临多种挑战和困境:

  • 复杂性

现代系统的分布式和微服务架构增加了系统的复杂性,特别是云原生环境下资源服务的动态变化和基础设施故障也会对定位造成干扰,这些都使得故障的定位和根因分析难以实现。

  • 信噪比

系统可能会产生大量的日志和各类指标数据,特别是发生故障时尤为突出,但并非所有数据都与问题相关。过滤出有用信息并从中识别和定位出故障根因非常具有挑战性。

  • 文化和流程

组织文化和相关流程需要能够有所支持,一方面组织内部必须能够切实有效的认识到根因分析对于提高系统可靠性和发生故障时真正快速止血的必要性,另一方面团队内部也需要有适宜的协作机制和流程,促进各团队间能够快速修复问题,并且深入挖掘其根本原因,能够做到快速止血的同时也能够彻底解除隐患,落地实践 1-5-10 等高效的故障处置流程。

依赖报错怎么办

· 阅读需 4 分钟
Kindling-OriginX
故障根因推理引擎

依赖报错怎么办

在开发过程中,经常会依赖各种库,可能是公共的、可能是公司内部的。这些依赖给我们开发带来了很多便利,避免了重复造轮子。但是这也导致程序中的很多部分彻底变成了黑盒,出现问题时更加难以定位和确定根因,使得很多时候既没有明确的排查方向,又要面对程序执行的黑盒,根本无从下手。

常常会遇到通过日志发现底层依赖库抛出了异常,例如依赖的某个库中抛出了网路连接的错误,通过日志看到产生了Connection Refused或着响应发生了超时,但通过 APM 工具发现网络和其他依赖服务都没有问题,只能看着错误信息干着急。

依赖报错怎么办

要不先求助依赖库的开发同学看看

依赖报错怎么办

再求助负责基础设施的同事帮帮忙

依赖报错怎么办

最后只能重新想办法,可办法又是什么呢?

依赖报错怎么办

通过查看日志,我们定位到抛出异常的库。但是,我们并没有对这个库进行过任何变更,那么该如何进行接下来的排查呢?又需要哪些数据来佐证内心对问题的各种推测和猜想呢?这些推测和猜想又真的是正确的方向么? 依赖报错怎么办

需要明确方向,打开盲区

一方面需要对 Trace 数据做更加深入和全面的分析及数据关联来明确排查方向,另一方面需要真正能够彻底消除盲区,真实还原程序执行过程。

Kindling-OriginX 通过分析和关联 Trace 、Log、Metrics 数据,补充并细化 trace-profiling 的指标,深入并明确问题方向;另一方面利用 eBPF 结合 trace-profiling 技术打开程序执行和系统调用盲区,从根本上彻底还原程序执行过程,不再有黑盒存在。 依赖报错怎么办

定位关键指标,关联相关数据

依赖的相关代码中确实存在bug。 依赖报错怎么办

智能化给出可解释性的根因报告

根据报告和数据发现网络设施确实存在波动,提供相关交接数据。 依赖报错怎么办

自动化 Tracing 关联分析异常数据,彻底打开全部盲区

消除传统 Trace、Log、Metrics 数据中的盲区,找到盲区中的问题点,发现存在DNS耗时异常。 依赖报错怎么办 对于难以明确排查方向,无法定界的故障,Kindling-OriginX 快速组织和分析故障线索,通过自动化 Tracing 关联分析,以给出可解释的根因报告的方式,为这类故障提供可操作的排查方法。

关于故障注入平台

· 阅读需 6 分钟
Kindling-OriginX
故障根因推理引擎

开源模拟故障案例集系统soma-chaos是针对train-ticket注入故障,并实时查看故障状态的图形化故障案例模拟系统。 龙蜥社区系统运维联盟由清华大学、复旦大学、浙大大学、信通院、阿里云、浪潮集团、中兴通讯、统信软件,云观秋毫、云杉网络、乘云科技作为首批联盟发起成立,联盟的宗旨是以推动系统运维技术进步、促进产学研合作为⽬的的⾮营利性组织。开源模拟故障案例集项目由龙蜥社区系统运维联盟发起,联合各大运维厂商、平台厂商和科研院校、事业单位共同参与的,旨在通过建立一套故障演练平台,为平台厂商、运维厂商和广大客户建立起沟通的桥梁和纽带,让客户对运维产品拼图有全局认识。 云观秋毫与复旦大学在系统运维联盟中的主要工作是负责构建故障案例集演示系统,提供开源开放的故障案例集合,推动并促成行业面对故障的时候能以统一的案例话术沟通。更多信息可参考:

故障注入平台

故障注入使用指南

[gitee] soma

Chaos Mesh

故障案例集

故障注入平台中涉及的案例集来源于开源开放的故障案例合集,欢迎各团队和组织共享相关案例集。构建该案例集宗旨是:

  • 覆盖主流开发技术
  • 开源开放,欢迎所有人一起完善故障案例
  • 任何人都可以在私有环境构建部署
  • 任何人可以产生一致理解的典型故障
  • 绝大多数故障案例可以恢复,案例可以重复实验

关于Train Ticket

"Train Ticket" 是由复旦大学开源的微服务基准系统,用于教学、研究和实践微服务技术和云原生应用开发。该项目模拟了一个在线火车票预订平台,包含一系列协作的微服务,每个服务都承担着不同的业务职责,如用户认证、票务查询、订单管理等。项目为学生、研究者和开发者提供了一个实际的微服务系统案例,帮助他们学习现代软件工程的实践,尤其是在微服务架构设计和运维方面。更详细的资料可参见该项目 GitHub 主页: Train Ticket:A Benchmark Microservice System

故障注入简介

故障注入(Fault Injection),也称为错误注入或故障注射,是一种软件测试技术,用于增强系统的鲁棒性和可靠性。这种方法通过人为地在系统中引入故障或异常条件,来模拟软件、硬件或网络的潜在错误,目的是为了验证系统在面对真实世界中可能遇到的故障时的行为。

故障注入的作用

  • 验证系统的错误处理和恢复能力

确保当系统遇到故障时,构建的日志与监控系统能够正确地记录错误信息,响应的容错与恢复机制能够执行必要的回滚操作,并在可能的情况下自动恢复服务。

  • 评估系统的容错能力

测试系统在面对故障时是否能够继续运行,即使是以降低的性能或功能。

  • 提高系统的可靠性

通过发现和修复在故障注入测试中暴露出的问题,提高系统的整体可靠性,优化系统整体架构和设计。

  • 理解系统的行为

在极端情况下,更好地了解系统可能的行为,以便在设计和实施阶段对这些极端情况加以合理的容错和恢复机制,保证在条件允许的情况下尽可能的提高系统的可靠性和稳定性。

内核视角下持续剖析 VS 代码视角下的持续剖析

· 阅读需 15 分钟
Kindling-OriginX
故障根因推理引擎

内核视角下持续剖析 VS 代码视角下的持续剖析

火热的eBPF技术当下在可观测性领域的应用热点有两个主要的方向:

  • 从内核中打开网络黑盒拿到相关的数据
  • 利用eBPF技术实现Continues Profiling

在GitHub上,国内外的 eBPF 主流开源软件是打开网络黑盒提高可观测性,另外一部分就是Continues Profiling,其中Continues Profiling最有名的就是Pyroscope项目,并被Grafana收购。

什么是Continues Profiling?

以下内容来自于GPT4的回答:

持续剖析(Continuous Profiling)是一种软件性能优化技术,旨在实时收集程序运行时的性能数据,如CPU使用率、内存分配、线程锁等待时间等。这些数据通常通过在代码中嵌入剖析器(Profiler)来收集,剖析器能够监测和记录应用程序在执行过程中的各种性能指标。

持续剖析的目标是帮助开发者理解应用程序在生产环境中的实际运行性能,从而发现性能瓶颈和优化机会。与传统的剖析(通常在开发或测试阶段进行)不同,持续剖析强调在应用程序的整个生命周期内,尤其是在生产环境中不断进行性能监控和优化。

持续剖析的好处包括:

  • 实时性能监控:通过持续收集性能数据,开发者可以实时监控应用性能,及时发现和解决问题。
  • 发现隐蔽的性能问题:某些性能问题可能只在特定条件下或在生产环境中出现,持续剖析帮助发现这些难以在测试阶段发现的问题。
  • 优化资源使用:分析性能数据可以帮助开发者优化代码,减少资源浪费,提升应用效率和响应速度。
  • 提升用户体验:通过优化应用性能,可以直接提升最终用户的体验。

持续剖析通过集成到CI/CD流程、自动化工具和监控系统中,成为现代软件开发和运维的重要组成部分。它使得性能优化成为一个持续的过程,而不是一次性的任务,有助于构建更高效、更稳定的软件系统。

最原始的Continues Profiling概念主要停留在CPU代码上的采集,形成火焰图帮助大家理解代码在CPU上是如何运行的。但是这点对于分析清楚线上的性能问题是不够的,因为线上代码是复杂的,程序依赖的条件不仅仅有CPU,还有锁、网络IO、磁盘iO等各种事件, 所以可观测性巨头如Datadog在他们的Continues Profiling理解中已经增加了很多。

代码视角下的持续剖析

Datadog对Continues Profiling的理解,可观测性的终极大杀器

Datadog其官网对Continues Profiling的能力定义:

  • Never miss production issues originating from your code by continuously profiling every line across all hosts and containers.
  • Enable engineers of all levels to swiftly spot and resolve problems using Watchdog AI's insights and suggested fixes.
  • Pinpoint methods and code lines that are inefficient under production load, despite having performed well in pre-production environments.
  • Find the root cause of slow requests by correlating spans with profiling data.
  • Gain thread-level visibility into your requests to investigate parallelization, deadlocks, inefficient garbage collection, locks, and more.
  • Determine exactly what your slow methods are spending time on — CPU, locks, I/O, garbage collection, and more.

从能力角度来看,Datadog的Continues Profiling能力像银弹一样,成为了可观测性的终极方案,无论什么问题,CPU, locks, I/O, garbage collection等各种问题,Datadog的Continues profiling都能够给轻松给出答案。

开源项目对Continues Profiling的理解,像Datadog学习

以Pyroscope为例,最开始的Pyroscope提供了eBPF为内核的OnCPU数据采集和内存相关数据采集能力,主要的分析的能力是CPU、内存分配对象、内存分配空间、在用对象、在用空间、Goroutines等多种类型。

最近去Pyroscope的官方Demo上看,发现其演进思路也基本上往Datadog的方向靠近了,出现了对于lock的持续剖析功能。

内核视角下持续剖析 VS 代码视角下的持续剖析

国内同行业专家也是非常认可Datadog的思路

某公有云可观测性软件发版了其持续剖析的功能,虽然没有实际使用过,从功能列表来看,已经很接近Datadog的能力了。

国内其他的可观测性公司也在致敬Datadog的方法论,提供类似的数据。

Datadog的Continues Profiling核心设计哲学

  • 内核是稳定的

  • 代码是业务开发的,开发者只关心代码要如何修改才能解决问题

  • 火焰图长时间执行的代码块能帮助用户理解问题,并启发用户产生解决思路

  • 正因为Datadog是沿着这个思路设计产品的,其持续剖析功能的实现就是从代码层面上去剖析问题,但是这样的设计哲学有一定局限性。

Datadog代码层面的持续剖析局限性

为了让大家更好的理解问题,举例说明,如果大家看到了这张火焰图能够得出什么结论:

内核视角下持续剖析 VS 代码视角下的持续剖析

从代码层面的持续剖析数据来看,代码是由于记录日志时间很长,那为什么呢?可能有常见的排查思路继续深入排查:

  • 日志由于并行写,产生了锁

  • 磁盘可能出现了问题,导致日志写变慢了

  • 长时间的GC

如果上述的排查思路都没有结果,方向就比较迷茫了。

从内核视角的持续剖析

基于TSA的性能定位问题方法论

有兴趣的朋友可以深入理解大神brendan的文章The TSA Method,或者参阅TSA方法:基于线程时间分布分析性能瓶颈

对于没有耐心的朋友,这里做个快速简介:就是将线程在不同状态下切换过程,按照下图所示对程序执行过程进行分析,从而理解线程在执行程序的时候到底卡在哪个环节。

内核视角下持续剖析 VS 代码视角下的持续剖析

从内核视角的持续剖析有什么优势和劣势

优势

  • 真实反应程序正卡在什么地方

  • 准确与精准没有任何其他可能性

  • 内核反应出来的卡住的问题比代码卡住的问题场景少很多,线程只可能卡在以下几个方面:CPU执行,CPU调度,网络IO,磁盘IO,锁。这个为后续的智能化分析提供很好的基础

劣势

  • 内核视角缺少业务属性,不知道哪个线程在哪个时间段为哪个业务服务,除非在性能压测场景,URL访问单一,可以在线程行为中找到模式匹配业务开始与结束,否则人很难分析

  • 内核系统调用和相关知识对于普通业务开发人员而言太陌生,导致即便看到了内核相关系统调用比如说futex执行时间很长,不能理解这在代码层面上意味着什么

TSA的方法论结合Trace带来不一样的视角

开源项目Kindling的Trace-Profiling解决了单独TSA的持续剖析方法的劣势之一:内核视角缺少业务属性。Trace-Profiling通过TSA的方法论产生的数据与Trace打通,做到了与业务关联,这样在生产环境就可以做到某个业务因为什么原因卡住了。

还是以这个火焰图为例,代码剖析反应程序卡住在了日志代码之上。

内核视角下持续剖析 VS 代码视角下的持续剖析 如果使用Trace-Profiling,应该能够明确给出当时业务是卡住下列事件之一

  • RunQ

  • Futex

  • CPU

  • DISKIO

代表代码执行的线程卡在什么地方知道了,那接下来就是如何理解和翻译了。

开发人员对内核的理解是推广TSA方法论的阻碍

云原生环境内核由于资源竞争带来的问题

绝大多数开发人员在正常业务开发当中较少接触内核,而且也会认为内核是不会产生问题。也许从单机而言,出现任何问题都极少的可能是内核调度产生的。但是一旦涉及到云原生环境,要提高资源利用率的场景,就需要多业务都在竞争对内核的使用,这个时候产生的竞争就超出了普通开发人员的常规认识,也就意味着云原生产生的问题比以往问题更多。

开发者对于内核理解有限

CPU调度产生的RunQ对于绝大多数开发而言太遥远,上述例子可能事件中也就DISKIO是开发人员能够快速理解的,其它都需要花费更多的精力去学习。学习成本带来了推广难度,所以业界几乎没有看到基于TSA方法论的工业化产品在推广。

问题总结

  1. 基于代码的持续剖析虽然对于开发而言足够友好,开发能看懂哪段代码带来的问题,但是由于代码的丰富多样性,看到代码卡住了,很多时候仍然不知道代码为什么卡住,并且现象可能有一定随机性。

  2. 基于TSA内核持续剖析的方法虽然能够精准定位业务卡在了什么内核事件之上,但是对于开发者不友好,不利于推广。

智能运维中的决策树结合TSA实现对内核事件的自动化翻译,完美解决当前持续剖析存在的问题

对于可观测性领域,AIOPS已经推广了很久,但是由于缺少一锤定音的数据特征,从而难以高准确率方式判定故障根因。

基于TSA的线程模型机制给出了程序的精准内核事件,相比于代码或者日志的N种场景,内核事件要精简得多。

举例说明:

一旦线程出现上内核事件RunQ,这说明线程在执行代码的时候,由于内核CPU调度的原因卡住了,这个时候在代码层面上可能在做一些做普通业务操作,比如前文例子觉得记录日志的代码卡住了,在代码层面上也可能是做了个网络操作卡住,从代码层面分析非常复杂,可能性非常多。但是从RunQ事件分析,就非常精准,只有这一个事件特征,不存在其他事件特征。

为了帮助用户理解,这个RunQ的事件就可以翻译成为当前CPU资源不足,应该扩容了,这样开发和运维就能很容易理解。

基于内核事件统计分析,可以形成这样的决策树,最终就能完美定义出故障的根因。

内核视角下持续剖析 VS 代码视角下的持续剖析

可以不断演进基于LLM和思维链的故障根因辅助定位

· 阅读需 18 分钟

Cover 图

业界目前使用LLM做根因定位的思路

基于LLM构建智能体,比如日志智能体、网络数据智能体、Trace智能体。每种智能体负责两个事情:异常检测和自身范围内异常根因数据的推荐。目前很多公司的实际效果还处于实验阶段,还未达到实际生产效果。

目前做法太复杂,复杂体现在以下几个方面:

第一:可观测性的数据属于海量低价值的数据,要针对这些数据依赖AI寻找人能理解的异常太复杂。多数情况是噪音大于实际收益。

第二:多智能体的交互太复杂,而且是建立在第一步异常检测的基础上分析出根因。

我们的思路:

  1. 依赖人为经验的基于规则告警和异常检测体系。告警和异常检测是可以对接已有告警体系,实现快速接入落地。
  2. 依赖思维链在完成疑似根因节点的识别,这里面专家经验可以调整进化。
  3. 依赖北极星指标,利用报告快速确认疑似节点的根因。

用户需求

从用户需求侧而言,故障根因辅助定位才是需要的。

异常检测的价值太缥缈:

绝大多数公司内部告警体系已经完善,虽然可能并没有办法覆盖所有的情况,但是基本上都认为已经做到相当充分。

那异常检测的价值就是发现未知的未知问题,就未知的未知问题而言,用户是没有认知的。用户要从未知的未知真实问题中和噪音问题中,区分哪个问题有价值,还是噪音,这个对用户要求太高。

基于规则的异常或者告警已经能实现绝大多数异常检测的目标,而且具有充分的可解释性。所有的规则都可以梳理,并确认,所以这一步我们认为还是基于规则的效果较好。

根因辅助定位是绝大多数用户的需求:

这块我们交流下来是用户最有需求的,因为排障的标准化流程缺失,导致严重依赖专家,排障的时间周期并不能如预期完成,也就很难完成业界的1-5-10目标。

如何才能实现基于LLM和思维链实现根因辅助定位

大模型的限制

可观测性数据是属于海量低价值的数据,而大模型而言是有上下文限制的,目前主流的大模型也只能支持128K的数据。

一条Trace的数据大概2K,也就是几十条的Trace的数据量就将大模型的上下文撑满了,大模型也就没有办法更好的解释数据了。

业界领先者做法

受限于大模型的限制,所以目前很常见的大模型用法是将某个具体的数据提交给大模型,并请大模型帮忙分析出相应的结论。

  • 比如将火焰图数据提交给大模型,请大模型帮忙分析出最长的代码段
  • 比如将单次Trace数据提交给大模型,请大模型帮忙分析哪个Span最有问题
  • 比如将指标曲线给大模型,请大模型帮忙分析异常时间点

用户期望的需求

从告警初始就开始使用大模型,而不是人为先筛选出具体的Trace或者具体火焰图数据,然后交给大模型。诚然,基于大模型帮忙解释部分数据是有价值,但是还有很多用户问题是:

“告警啦,我该如何从告警分析着手,在海量数据中找到最能反映故障的Trace然后深入分析这条Trace或者日志、指标,最终确定故障根因”

标准化数据

标准化,有价值的数据。我们认为将海量低价值的可观测性数据全都丢给LLM,让LLM去算出结论是不现实的。所以我们需要提前从海量低价值的可观测性数据中,先抽象出一套数据,这套数据能够被用来高效故障定位:

第一、能够反应程序调用关系的精准拓扑关系(没有此类数据,故障传播过程无法确定)

第二、成体系的告警,能将所有的告警和异常都关联至程序调用关系中(相互割裂的异常告警,很难综合起来分析)

第三、北极星指标体系 利用前两个数据,确认疑似的根因服务、利用第三个数据实现最终指标级根因的确定

精准拓扑关系 VS 传统拓扑结构

故障传播在传统拓扑结构存在不确定性,导致思维链规则很难编写。

程序精准的调用关系如下:

程序的每一种URL调用,都能在拓扑中反映出来,这也是Dynatrace 的service-flow功能,目前可观测性相关产品中,也只有Dynatrace与APO实现了这种精准调用关系的拓扑。

1 图

2 图

传统的拓扑的调用关系如下:

ts-travel2-service->ts-basic-service->ts-route-service

ts-travel2-service->ts-route-service

故障的传播链路只要有ts-route-service有告警,就需要往上排查ts-travel2-service的告警情况。而真实的情况故障传播链路在basic-service这里就可能不再往上传递,ts-travel2-service的告警和ts-route-service是没有关系的,但是传统基于应用服务的拓扑结构是无法区分此种情况。

拓扑成环的问题

如果一旦拓扑出现成环的问题,就会导致告警的根源很难查找,因为整个环上的所有节点都互相依赖,都可能成为根因节点,就得将所有节点都列为潜在根因节点排查。

传统拓扑结构更大概率成环,因为是按照节点维度,但是实际调用过程业务是不可能成环,因为一旦成环之后,业务就无法正常执行了,只是拓扑结构再生产的时候舍去了某些关键信息才导致拓扑成环。

精准拓扑反应调用关系,虽然理论上无法100%避免成环,但是相比传统拓扑结构而言,绝大部分在传统拓扑结构成环的现象,在精准拓扑结构都不会成环。

拓扑数据太大的处理

大家都知道大模型对上下文的长度是有限制的,拓扑数据太大了会占据大量上下文,最终导致大模型在推理过程中不能很好的记住拓扑结构,推理工作也就进展不下去。我们采用相似性,对一个大的拓扑进行收缩,确保只有关键的拓扑结构与大模型交互,并不是所有结构都与大模型交互。

利用延时曲线或者错误曲线相似性,将大拓扑结构收缩。

原始拓扑结构

3 图

利用相似性收缩之后的拓扑结构:

4 图

相似性原理:

只有曲线相似的节点才被考虑进拓扑结构,曲线不相似的节点会被收缩。举例如下图:比如黄线所代表的节点一直很平稳,所以不应该作为疑似节点考虑,可以收缩起来。

5 图

告警的数据模型:

告警的关联方式:

  • 应用层告警(URL接口延时异常和错误异常)
  • 网络层告警(节点之间连接的网络周期性Ping值异常)
  • 基础设施主机层告警(主机的CPU、 内存、磁盘、网卡吞吐量异常)
  • 容器告警(k8s事件比如容器killed, 容器重启等异常)
  • 错误告警(Java程序使用java Exception,非Java程序,使用日志错误数异常。解决微服务场景中会识别不了错误的场景、将错误封装到error code中,导致http status code永远是200)
  • 应用层的告警是直接关联至拓扑结构
  • 基础设施层告警,间接关联至拓扑结构,比如主机的CPU高,可能潜在的影响主机上的所有业务,但是并不是主机上所有的业务就会一定受到影响,所以说基础设施告警是间接影响应用层的告警
  • 网络层告警,间接关联至拓扑结构,只要TCP四元组出现ping延时异常,即认为应用延时和错误可能受到网络的潜在影响,同理也是间接影响,并不能认为网络一旦出现抖动,应用延时就一定有问题
  • 容器告警,k8s相关事件也间接影响应用的执行
  • 错误告警,为了能更好识别应用错误,因为微服务框架导致错误识别不了,http statuscode永远为200,所以需要额外错误告警的弥补错误场景识别不精准的问题

基于LangChain的思维链,利用大模型完成根因推理。

我们的目标是提供一个可以不断完善的根因推理思维链条,所以每个规则其实都可以修改,规则一定要符合当下人员排障的惯性思路。


可进化的规则

目前我们提取了符合用户习惯的常规规则,这些规则可以根据不同的专家经验不断完善。

规则:

从应用接口层告警(如 URL 接口延时异常、错误异常)出发。沿着业务入口的拓扑结构,从上游向下游每个节点依次追踪(使用拓扑图数据,不要搞错上下游)。 请输出推理过程
在追踪过程中分析每个节点: 如果存在接口层告警,继续向其依赖的下游节点追踪,该节点在两种情况下作为疑似根因节点,第一该节点存在其它类型的告警,第二其下游所有节点没有1类型接口告警,其它情况该节点都不是疑似根因节点。
如果没有接口层告警,停止追踪,返回上一级节点,当前节点排除。
在追踪过程中,对于每个有1类型接口告警的节点:
检查是否存在其他类型的告警(如应用层、容器层、基础设施层告警),如果存在,则该节点是疑似根因,记录并继续分析下游节点追踪。
如果不存在其他告警,同时该节点所有下游节点都不存在1类型接口的告警,则该节点是疑似根因节点。
如果节点就是是最下游的节点,同时节点也存在1类型接口告警,该节点也是疑似根因节点。
最下游节点定义:依赖链路上没有进一步下游依赖的节点。 最下游节点如果有告警,是疑似根因的优先候选。
回答格式要求给当前规则推理过程,不用附带完整数据,需要给出精简数据。

解释:

上游节点的应用告警是由下游传递导致的,所以上游节点不是疑似根因节点

示例中应用告警传递过程 ts-route-service -> ts-travel-service -> ts-route-plan-service -> ts-gateway-servcie

6 图

规则:

上游节点的应用告警是由下游传递导致的,所以上游节点不是疑似根因节点
示例中应用告警传递过程 ts-route-service -> ts-travel-service -> ts-route-plan-service -> ts-gateway-servcie。

新增发生在:11:19:35秒

7 图

已解决发生在:11:20:35秒

8 图

规则:

如果某个节点发生异常,但是上游节点未发生应用异常告警事件,可以将这个节点及其分支全部排除

解释:说明这些告警都是干扰项,可以排查

9 图

基于北极星的闭环确认根因

当我们基于规则疑似故障根因节点之后,根据北极星指标可以确认疑似根因节点到底发生了什么情况,导致的问题。

北极星指标曲线是利用eBPF技术,将请求延时完整的拆解成了主要的几个故障方向,当延时变化之时,可以根据哪个分项的变化曲线最相似,从而判断疑似故障节点的故障方向。

10 图

11 图

从这张图中,可以很快确认程序后期的延时上升主要的原因就是lock_gc_time的上升导致的,但是在最开始程序波动的原因是由于net-workTime导致的。

如果这个时候有CPU-IO wait高,说明这些IO异常对程序的执行过程其实没有任何影响。

最终的证据报告

最终通过关联可观测性数据,形成最终的证据报告,说明该问题。

12 图


点击下方链接观看演示视频:通过大模型对话来分析告警事件

(https://www.ixigua.com/7441033779788907046?utm_source=xiguastudio)

APO介绍:

国内开源首个 OpenTelemetry 结合 eBPF 的向导式可观测性产品

apo.kindlingx.com

https://github.com/CloudDetail/apo

可观测性体系建设后,该如何挖掘数据及工具价值?

· 阅读需 12 分钟
Kindling-OriginX
故障根因推理引擎

可观测性体系建设后,该如何挖掘数据及工具价值?

在现代企业的运维管理中,构建高效且可靠的可观测性体系是保障系统稳定性和业务连续性的关键。然而,运维团队成员的技术能力参差不齐往往成为实现这一目标的障碍。尤其在处理复杂系统故障时,高度依赖专业知识和经验的可观测性工具很难被全员有效利用,进而影响到其建设价值的体现。

可观测性体系建设后,该如何挖掘数据及工具价值?

可观测体系建设的意义

可观测性体系建设后,该如何挖掘数据及工具价值?

可观测性是近几年来最热门的话题之一,许多企业和团队都投入了很多人力、物力来进行可观测体系的建设,以期能获得可观测性的核心价值:快速排障(troubleshooting)。可观测性体系是指通过一系列技术手段和方法,对系统的运行状态、性能指标、业务流程等进行实时监控、分析、预警和优化的一种体系。它可以帮助企业及时发现和解决问题,提高运维效率,降低故障风险,为业务发展提供有力支持。

1. 提高运维效率

通过实时监控云原生应用的运行状态,运维人员可以快速发现并解决问题,减少故障排除时间,提高运维效率。

2. 保障系统稳定性

可观测性体系可以帮助开发者及时了解应用在云环境中的表现,发现并修复潜在的性能瓶颈和错误,从而保障系统的稳定性。

3. 优化资源利用率

通过收集应用的性能数据,可以对资源的使用情况进行分析,实现资源的合理分配和优化利用。

4. 持续迭代与优化

可观测性体系建设和数据挖掘是一个持续的过程。企业应不断收集反馈,优化体系架构和数据处理方法,实现体系的持续迭代和提升。同时,关注行业新技术、新理念的发展,将先进经验融入自身建设中,保持体系的竞争力。

可观测体系建设完成后存在的问题和挑战

可观测性体系建设后,该如何挖掘数据及工具价值?

很多团队在完成了一定规模的可观测性体系建设后,却在具体落地推广,乃至实际价值体现上都遇到了阻碍,这些问题和挑战主要体现在两方面,管理层面与技术层面:

管理层面的挑战

技术能力的不均衡

团队内技能水平的差异导致高级工具和数据的利用率低下。可观测性体系建设完成后,需要将其向各相关团队推广,期望能帮助各团队提效,协助开发团队排查定位问题。

但实际情况下,往往把可观测工具提供给开发团队后,一方面业务开发团队使用工具存在学习使用成本,另一方面不是所有开发都有能力看懂和定位问题。这需要有平台或工具提供整合能力,来解决人员能力差异性。

经验知识难以传递

缺乏有效的机制将高级用户的经验和知识快速传递给新手或非专家用户。导致仍旧是依靠团队专家和骨干才能完成诸如故障排查等工作,团队内部长期存在差异性。

故障响应的差异性

在发生故障时,需要快速有效的响应,但技术水平不一致可能导致延迟处置,甚至处置结果不一致,这种差异性也导致不利于故障响应流程的标准化和故障处理手段的规范化。

技术培训和能力提升存在成本

提升团队整体技术水平需要大量的时间和资源投入,且往往是一项需要长期坚持的工作,只有这样才能逐步对齐各团队间对于可观测性工具和数据的理解和使用水平。但仍旧会存在长时间不使用导致的生疏问题。

技术层面的挑战

工具使用和指标含义都会生疏遗忘

对于一些团队来说,可观测性工具并不是需要经常使用,加之其存在一定的学习成本,所以会导致每次使用的时候都得学习或者咨询专家。同理对于一直较深入的指标数据,其具体含义也会遗忘,使用的时候也需要查阅相关文档,这都加大的使用门槛。

使用方式和术语不统一

对于工具的使用和可观测数据的理解,不同团队都有其各自的使用场景和理解,这也导致了需要团队协作时增大了沟通成本,例如用户中心的团队使用Skywalking,负责消息推送的团队使用了OpenTelemetry。

故障响应的差异性

在发生故障时,需要快速有效的响应,但技术水平不一致可能导致延迟处置,甚至处置结果不一致,这种差异性也导致不利于故障响应流程的标准化和故障处理手段的规范化。

工具和标准的不统一

作为当今热门话题之一,各类可观测性工具及产品百花齐放,导致很多团队为了建设可观测性而不停的追热点,忙于工具的更新换代,方法和思路越没有同步进行更迭,更没有能够真正挖掘出可观测数据的价值。

需要更先进的工具和方法挖掘可观测性体系价值

可观测性体系建设后,该如何挖掘数据及工具价值?

Kindling-OriginX 通过Trace-profiling关键数据,以专家经验串联起来所有的可观测性数据,并推理成故障结论,最大程度发挥可观测性数据的价值。通过推理分析能力来平衡团队内的技术能力差异,确保每位团队成员都能有效利用可观测性数据,从而提升其建设价值的认可。

很多企业可观测性数据上了很多,但是推广效果不是很好,价值体现不佳。其主要原因是故障并不是经常发生,所以导致用户对于可观性工具使用生疏,加上一些疑难杂症的故障需要看深入的指标,这些指标含义不用就会忘记。这都需要有更先进的工具对数据指标进行提炼分析,直接给出可解释的结论。

  • 简化的操作界面

    为所有技术水平的用户提供易于理解和操作的界面,降低使用门槛。直接根据故障结论进行预案执行。

  • 自动化智能故障推理

    利用 eBPF 技术与自动化 Tracing 分析将多而杂的链路数据、指标数据、日志数据转化为直观的故障分析报告,无需深入的专业知识即可理解。

  • 最大化可观测性数据价值

    自动关联各类可观测性数据,完成可观测性数据价值挖掘。

  • 内化的排障知识库

    既是推理引擎,也是一个排障专家经验知识库,借助专家经验知识库平台能力能够迅速提升团队能力。

结语

本文探讨了可观测性体系的建设的意义及其根本目的,同时随着可观测性体系的建设也遇到了很多问题和挑战,对于这些问题和挑战都需要更先进的工具和方法,这样才能够充分挖掘和发挥可观测性工具和数据的价值。

在实践中,应当持续优化可观测性体系,确保数据的全面性和准确性,同时不断提升数据处理和分析能力。这不仅需要技术的进步,更需要方法的革新,一方面将可观测性融入到我们的开发和运维文化中,另一方面通过使用诸如 Kindling-OriginX 的创新型工具里帮助快速提升对于可观测性数据的使用水平,帮助提高团队综合能力。

可观测性工具的盲区与故障排查困局

· 阅读需 9 分钟
Kindling-OriginX
故障根因推理引擎

云原生常见可观测性工具的用法

Tracing

Tracing 可以追踪一次用户的请求,从而大致定位问题节点。如果运气好,是可以直接呈现某段代码的问题,比如问题就是SQL语句慢,或者执行了非常多次的redis操作导致整个请求慢,但是仍然有很多的时候只呈现了 Controller 方法执行时间长。

Logging

如果请求出现错误,在整个 Logging 体系中搜索错误日志是很快能够定位出错误的原因的,但是如果是请求发生了慢的现象,就得结合 Tracing。Tracing 基本定位到某个Controller 的问题,日志提供进一步的问题,排查到底是为什么慢,能否排查出问题取决于日志记录完备情况,所以经常出现的情况是补充日志进一步排查问题。

Metrics

通过 Metrics 中的SRE黄金指标能够很快确定业务是否正常,是否需要人为干预。但是一旦到某个业务慢,通过tracing和日志也没有发现直接线索,这个时候就只能通过 Metrics 找到有问题节点资源饱和度指标,看各种指标异常,不断地猜测试错验证了。

这里面存在两个大的问题:工具集成性差和盲区导致排障困难

集成性差是工程性问题,是次要问题

根据前文提到 Tracing、Logging、Metrics 工具在不同场景下使用,在不同工具之间跳转很麻烦会导致排查故障效率不高,但这是个工程问题,很多开源项目都在致力于解决这个问题。比如 OpenTelemetry 社区就致力于解决这个问题,会将三者从不同的线头糅合成一个线头,包括很多商业工具也都在界面跳转等易用性上发力,这个问题终将能够解决。 trace_metrics_logs

盲区是理论问题,是主要问题

盲区从理论上分析就存在的,不管是何种可观测性工具都没有办法完全还原程序的执行过程。Tracing 理论上就不可能针对每行代码执行都做插桩,因为会导致程序的执行性能下降很快。 Skywalking 有 trace-profiling 技术,目标就是动态探测某个程序在干什么,这个有一定的价值,能够发现用户代码层面的盲区。

国内使用很广泛的 Arthas 也是起着类似的作用,就是发现用户代码层面的盲区。国外一些在线debug工具,lightingRun 等工具也是往这个目标努力。

用户代码盲区并不意味着真实的程序执行盲区

程序执行过程是用户代码调用公共库、公共库调用JVM虚拟机代码、然后触发glibc库,最终触发syscall。

用户熟悉程度

现有工具理论上也只是工作在用户代码和公开库之上来帮助用户理解程序执行过程。

打开用户代码盲区之后仍然存在哪些可能的盲区

  • 用户在代码层执行一次带域名的http请求,实际在glibc中会分成两次网络请求,一次是获得dns解析,一次是真实的网络请求。用户代码层面无法理解到底是如何执行的。

  • 程序执行过程中,由于CPU时间片使用完,无法获得CPU执行,用户代码层面会将等待CPU时间片执行时间算成代码执行时间

  • 隐藏锁的使用,前文介绍了用户代码不可能对每行代码都做插桩,这样就会导致某些代码执行过程中可能在调用过程中使用了锁,但是对于用户而言是完全无意识的。典型就是Java常用的池化技术,连接池、线程池都是用锁来确保逻辑的正确执行。

  • 背锅的网络质量,用户代码调用网络发送代码,网络数据真的发送出去了吗?程序这个时候如果执行了GC操作或者CPU时间片用完了呢?从用户代码和日志层面看出应该是发出网络数据了,但是中间可能存在各种原因导致网络数据发送是滞后的,开发人员会倾向于认为网络质量有问题,但是网络运维人员发现不了网络质量问题。

如何才能在理论上真实还原程序执行过程,打开所有盲区

学习过操作系统的同学稍微回忆下基础知识,从操作系统层面看程序的执行过程,才是程序的真实执行过程,这里面是没有任何遗漏的。重点回忆下图。 程序执行过程 程序代码是以线程为载体进行执行,线程执行过程中可能会因为disk、sleep、lock、idle等各种原因放弃CPU上执行转入等待状态。 等待事件完成之后,线程状态变成Runnale等待cpu调度,如果此时CPU资源紧张,就会出现很长的等待时间。 开源项目 Kindling 的 trace-profiling 就是利用eBPF获取各个点位信息,同时结合Trace,真实地还原出程序的执行过程。从 Kindling 的 trace-profiling 去看trace的完整执行过程,每一个毫秒都知道程序在干什么。

Kindling-OriginX 利用trace-profiling理念构建故障推理引擎

Kindling-OriginX 相比于 Kindling 开源探针而言,使用Rust语言完全重构了eBPF探针。主要目的是获得更好的性能和稳定性。Kindling 开源探针使用go语言,由于go gc的存在,导致内存资源消耗相对而言比较大,而且go gc的时间不可控。 Kindling-OriginX 商业产品定位为故障推理引擎,通过分析各种开源工具的数据,补充 trace-profiling 的指标,比如通过 trace-profiling 已经能够看出网络执行慢了,这个时候通过补充网络质量指标如RTT、重传等进一步确认网络到底为什么慢。

Kindling-OriginX 完美解决集成性问题,同时彻底消除所有盲区

Kindling-OriginX 的故障报告中,完成了相关指标,日志和tracing的完美集成,只呈现用户需要看的故障传播链路分支和指标,旁路无关分支和故障不相干指标也不会呈现,日志也是故障时刻前后的相关节点日志。同时利用 eBPF 结合 trace-profiling 技术打开程序执行和系统调用盲区,从根本上彻底还原程序执行过程。 故障推理引擎利用智能算法结合 trace-profiling 自动化推导出故障根因,想更多了解 Kindling-OriginX,请点击阅读原文访问 Kindling-OriginX 官方网站。

告别ELK,APO提供基于ClickHouse开箱即用的高效日志方案——APO 0.6.0发布

· 阅读需 17 分钟

Cover 图

ELK一直是日志领域的主流产品,但是ElasticSearch的成本很高,查询效果随着数据量的增加越来越慢。业界已经有很多公司,比如滴滴、B站、Uber、Cloudflare都已经使用ClickHose作为ElasticSearch的替代品,都取得了不错的效果,实现了降本增效,费用节约大多在50%以上。但是目前使用ClickHose作为日志方案,存在以下问题。

  • 主流的Vector+ClickHose并未实现开箱即用,有许多的管理配置工作
  • 绝大多数方案不支持近似全文检索的功能(该功能很重要)
  • 使用双数组或者Map的表结构查询效率不高
  • ClickVisual是最接近的开箱即用的日志方案,也存在以下问题:

○强依赖Kafka,对于某些中小用户而言方案不够灵活,不友好

○未引入Vector,原生的ClickHose Kafka引擎在大流量情况下可能导致ClickHose内存爆掉(感谢社区大佬 十四反馈)

主流的Vector+ClickHouse方案并未实现开箱即用

目前业界很多公司都是基于Vector+ClickHouse的方案来实现日志的采集和存储,该方案需要管理维护的工作量相对而言比较高,适用于动手能力强的公司。

维护工作:为每种日志手动维护一张表

每个公司的部门团队可能日志规范都不完全一致,如果需要对日志内容进行快速搜索定位故障,就需要提前想好ClickHouse的表结构,然后调整Vector的配置文件,最终实现Vector根据不同日志格式,parse成不同的日志表字段,写入不同的日志表。

比如每种日志都得建立以下类似的表结构,才能完成日志按照ip、url等字段的索引实现快速搜索。但是另外一个部门的日志也许就不需要IP和url字段,那么该部门得重新设计表结构。

CREATE TABLE log
(
`ip` String,
`time` Datetime,
`url` String,
`status` UInt8,
`size` UInt32,
`agent` String
)
ENGINE = MergeTree
ORDER BY date(time)

使用双数组或者Map的表结构查询效率不高

为了能够规避这些维护工作,所以很多公司对固定日志表结构进行了调整,常见的有两种方案,一种是双数组方案,另外一种就是Map方案。

Uber和Signoz的日志实现方案都是基于双数组

其日志表结构类似于下面这种

CREATE TABLE <table_name>
(
//Common metadata fields.
_namespace String,
_timestamp Int64,
hostname String,
zone String,
...

//Raw log event.
_source String,

//Type-specific field names and field values.
string.names Array(String),
string.values Array(String),
number.names Array(String),
number.values Array(Float64),
bool.names Array(String),
bool.values Array(UInt8),

//Materialized fields
bar.String, String
foo.Number Float64,
...
)
...

滴滴、B站等日志实现是基于Map结构

引入Map结构能够动态实现日志关键字段搜索

CREATE TABLE ck_bamai_stream.cn_bmauto_local
(
`logTime` Int64 DEFAULT 0, --Log打印的时间
`logTimeHour` DateTime MATERIALIZED toStartOfHour(toDateTime(logTime / 1000)),--将Log
`odinLeaf` String DEFAULT '',
`uri` LowCardinality(String) DEFAULT '',
`traceid` string DEFAULT '',
`cspanid` String DEFAULT '',
`dltag` String DEFAULT '',
`spanid` String DEFAULT '',
`message` String DEFAULT '',
`otherColumn` Map<String,String>
`_sys_insert_time` DateTime MATERIALIZED now()
)
ENGINE =MergeTree
PARTITION BY toYYYYMMDD(logTimeHour)
ORDER BY(logTimeHour,odinLeaf,uri,traceid)
TTL _sys_insert_time +toIntervalDay(7),_sys_insert_time + toIntervalDay(3)To VOLUME 'hdfs
SETTINGS index_granularity = 8192,min_bytes_for_wide_part=31457280
Create Table <log_app_name> ON CLUSTER ...
{
_timestamp Datetime64(3),
`log,level` String CODC(ZSTD(1)),
`log.msg` String CODC(ZSTD(1)),
`log.trace_id` String CODC(ZSTD(1)),
...
string_map MapV2(String, Nullable(String))
CODEC(ZSTD(1))
number_map MapV2(String, Nullable(Float64))
CODEC(ZSTD(1))
bool_map MapV2(String, Nullable(UInt8))
}
ENGIN = ReplicatedMergeTree(...)
PARTITION BY toYYYYMMDD(_timestamp)
ORDER BY timestamp
TTL toDateTime(timestamp) + toIntervalDay(...),
toDateTime(timestamp) + toIntervalDay(...) TO VOLUME `cold_volume`

Map的动态字段搜索效率低

https://clickhouse.ac.cn/docs/knowledgebase/improve-map-performance

根据社区反馈,map底层实现为线性数组,map查询效率通常低于列查询3~10倍,特别是日志量规模越大,map查询效率越低。

同时支持Map类型的最低clickhosue版本为21.11

  • 列式存储优势: ClickHouse 的核心优势在于它是列式存储数据库,这意味着当执行查询时,只需要读取查询中涉及的列,而不必加载不相关的列。列式存储还能够通过数据类型特定的压缩技术显著减少 IO 操作,从而加快查询速度

  • 基于 Map 的查询: Map 是一种键值对数据结构,在查询时需要额外的开销来解析嵌套结构,并且无法像列式存储那样直接跳过不相关的数据。虽然 ClickHouse 对 Map 数据类型有一些优化,但它在处理复杂结构时往往会比简单的列查询慢

双数组的搜索效率也不高

虽然 ClickHouse 对Array有一定的优化,但双数组结构仍然比单纯的列查询开销大。

影响性能的因素:

  • 多级解析开销:查询双数组时,需要进行多层嵌套解析。例如,访问数组中的子数组意味着需要遍历父数组,然后进一步解析子数组的结构,这比单纯读取一个列复杂得多
  • 随机存取:双数组的访问模式往往比简单的列查询更加随机化。访问数组中的元素可能导致更多的跳转,影响缓存命中率,从而降低性能
  • 内存使用和数据存储:嵌套数组会使得 ClickHouse 的数据存储和内存管理更加复杂,因为数组中的数据长度不固定,导致压缩效果比单纯列差,数据块的大小也更加难以优化

性能上的差距取决于具体的查询模式和数据结构:

  • 在简单查询场景(例如,读取一个基本的列数据),单纯的列查询会比双数组快得多,特别是在处理大规模数据时。性能差距可能达到 数倍甚至十倍 以上,尤其是当查询不涉及嵌套结构时。
  • 在复杂查询场景(例如,查询涉及嵌套数组、需要频繁地进行数组拆解和操作),双数组的查询性能通常明显低于单纯的列查询。查询双数组的额外解析和处理开销,会使查询时间增加。根据不同的嵌套深度和数据量,性能可能下降 数倍。

ClickHouse的官方文档中日志方案也由于引入了Map效率不高

ClickHouse官方blog :

https://ClickHouse.com/blog/storing-log-data-in-ClickHouse-fluent-bit-vector-open-telemetry

提到有以下几种表结构:

OTEL的日志字段表

CREATE TABLE otel.otel_logs
(
`Timestamp` DateTime64(9) CODEC(Delta(8), ZSTD(1)),
`TraceId` String CODEC(ZSTD(1)),
`SpanId` String CODEC(ZSTD(1)),
`TraceFlags` UInt32 CODEC(ZSTD(1)),
`SeverityText` LowCardinality(String) CODEC(ZSTD(1)),
`SeverityNumber` Int32 CODEC(ZSTD(1)),
`ServiceName` LowCardinality(String) CODEC(ZSTD(1)),
`Body` String CODEC(ZSTD(1)),
`ResourceAttributes` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
`LogAttributes` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
//数据索引
INDEX idx_trace_id TraceId TYPE bloom_filter(0.001) GRANULARITY 1,
INDEX idx_res_attr_key mapKeys(ResourceAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
INDEX idx_res_attr_value mapValues(ResourceAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
INDEX idx_log_attr_key mapKeys(LogAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
INDEX idx_log_attr_value mapValues(LogAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
INDEX idx_body Body TYPE tokenbf_v1(32768, 3, 0) GRANULARITY 1
)
ENGINE = MergeTree
PARTITION BY toDate(Timestamp)
ORDER BY (ServiceName, SeverityText, toUnixTimestamp(Timestamp), TraceId)
SETTINGS index_granularity = 8192, ttl_only_drop_parts = 1

Vector 字段表

CREATE TABLE vector.vector_logs
(
`file` String,
`timestamp` DateTime64(3),
`kubernetes_container_id` LowCardinality(String),
`kubernetes_container_image` LowCardinality(String),
`kubernetes_container_name` LowCardinality(String),
`kubernetes_namespace_labels` Map(LowCardinality(String), String),
`kubernetes_pod_annotations` Map(LowCardinality(String), String),
`kubernetes_pod_ip` IPv4,
`kubernetes_pod_ips` Array(IPv4),
`kubernetes_pod_labels` Map(LowCardinality(String), String),
`kubernetes_pod_name` LowCardinality(String),
`kubernetes_pod_namespace` LowCardinality(String),
`kubernetes_pod_node_name` LowCardinality(String),
`kubernetes_pod_owner` LowCardinality(String),
`kubernetes_pod_uid` LowCardinality(String),
`message` String,
`source_type` LowCardinality(String),
`stream` Enum('stdout', 'stderr')
)
ENGINE = MergeTree
ORDER BY (`kubernetes_container_name`, timestamp)

fluent字段表

CREATE TABLE fluent.fluent_logs
(
`timestamp` DateTime64(9),
`log` String,
`kubernetes` Map(LowCardinality(String), String),
`host` LowCardinality(String),
`pod_name` LowCardinality(String),
`stream` LowCardinality(String),
`labels` Map(LowCardinality(String), String),
`annotations` Map(LowCardinality(String), String)
)
ENGINE = MergeTree
ORDER BY (host, pod_name, timestamp)

日志需要近似全文检索

基于ElasticSearch的日志方案,由于可以基于ElasticSearch实现的日志内容分词,所以很容易实现全文检索,但是基于ClickHouse就很难实现该功能。

那是不是基于ClickHouse的方案就完全没有办法呢?

ClickHouse的索引介绍

  • tokenbf_v1 按非字母数字字符(non-alphanumeric)拆分。相当于按符号分词,而通常日志中会有大量符号

在大牛的文章中,

https://juejin.cn/post/7130514546069864456

详细介绍了全文检索的实现,有兴趣的可以仔细看下大牛的文章。

最理想的日志方案应该满足什么条件?

我们认为理想的基于ClickHouse的日志方案应该满足以下几条:

  • 使用列来进行检索,而不是map或者双array,保证高效的查询效率
  • 用户不需要为了不同部门的日志内容,进行维护单独的表结构
  • 支持对原始日志内容进行近似的全文检索

我们调研了国内外几乎所有基于ClickHouse的日志方案,最后发现国内开源项目ClickVisual项目的思路最相近,ClickVisual几乎可以做到开箱即用。


ClickVisual的方案不足

ClickVisual工作原理:

  • 针对每种日志格式定义不同的parse规则,ClickVisual为每种规则生成一张新的日志表。该日志表存的就是解析之后的日志,之后的日志查询都是针对该日志表。因为解析之后的日志已经按照ClickHouse列存储了,所以关键字段查询是非常快的
  • 基于Kafka表引擎,读取Kafka原始日志,落库至临时表中
  • 魔术开始的地方: 基于ClickHouse的物化视图,将原始日志中的新增日志_raw_log_内容按照日志解析规则parse成日志列格式,并将解析好的日志存入该规则对应的日志表中

临时表

CREATE TABLE default.test_stream
(
`status` String,
`timestamp` Float64,
`message` String CODEC(ZSTD(1))
)
ENGINE = Kafka
SETTINGS kafka_broker_list = '127.0.0.1:9092',
kafka_topic_list = 'test',
kafka_group_name = 'default_test',
kafka_format = 'JSONEachRow',
kafka_num_consumers = 1,
kafka_skip_broken_messages = 0

物化视图

CREATE MATERIALIZED VIEW default.test_view TO default.test
(
`status` String,
`_time_second_` DateTime,
`_time_nanosecond_` DateTime64(9),
`_raw_log_` String,
// 日志表的列
`level` Nullable(String)
//根据需要调整列
...
) AS
SELECT
status,
toDateTime(toInt64(timestamp)) AS _time_second_,
fromUnixTimestamp64Nano(toInt64(timestamp * 1000000000)) AS _time_nanosecond_,
message AS _raw_log_,
// 物化视图处理成列
toNullable(toString(replaceAll(JSONExtractRaw(message, 'level'), '"', ''))) AS level
// 根据需要添加更多解析规则
...
FROM default.test_stream
WHERE 1 = 1

按照日志解析规则将_raw_log_parse成新的真实日志表

CREATE TABLE default.test
(
`status` String,
`_time_second_` DateTime,
`_time_nanosecond_` DateTime64(9),
`_raw_log_` String CODEC(ZSTD(1)),
// 该列通过物化视图解析得到
`level` Nullable(String),
// 根据需要添加更多列
...
INDEX idx_raw_log _raw_log_ TYPE tokenbf_v1(30720, 2, 0) GRANULARITY 1
)
ENGINE = MergeTree
PARTITION BY toYYYYMMDD(_time_second_)
ORDER BY _time_second_
TTL toDateTime(_time_second_) + toIntervalDay(1)
SETTINGS index_granularity = 8192

每当需要分析新的索引字段,clickvisual会执行Add Colum为日志表添加新的列,同时更新物化视图添加新的解析处理规则。

ClickVisual的不足:

根据上述的原理:

  • 不支持高效的近似全文检索,工作原理可以看出由于ClickVisual并未对_raw_log_进行跳数索引,所以也就导致ClickVisual不能高效的支持近似的对原始日志全文检索
  • 由于ClickVisual完全依赖Kafka表引擎来实现日志的摄入,虽然ClickVisual也支持引入ClickHouse的已有日志表结构进行查询,但是很可能并不是直接针对列查询,而是针对map数据查询,只有通过Kafka引擎来摄入的日志才能生成新日志表结构,最终查询才是针对列式查询,才能有较高的查询效率
  • Kafka表引擎读写日志速度无法控制,如果日志量非常多,导致ClickHouse物化视图工作过程中内存爆掉

APO 日志设计方案

ClickVisual已经非常接近理想日志方案了,只是我们需要对ClickVisual的逻辑进行调整。

  1. 不使用ClickHouse的Kafka表引擎来完成日志的摄取工作,而是改成Vector的方式完成日志的摄取工作。这样就不再依赖Kafka,对于中小用户日志规模没有那么大的用户,可以直接使用,而不需要维护Kafka。虽然去掉了Kakfa,同时增加了Vector,但是Vector的运维工作相比kafka而言,Vector几乎不需要运维
  2. 引入了Vector之后,可以通过配置Vector来调整参数,确保在大量日志洪锋的时候,也不至于将ClickHouse内存打爆
  3. 用户如果真的需要引入Kafka,也有已经维护好的Kafka,完全可以使用Vector先将原始日志写入Kafka,然后使用Vector从Kafka读取出来,继续实现后续的日志处理
  4. APO引入了ClickHouse null表引擎,来实现原始日志(从Vector写入的) 转换成按照日志解析格式解析之后的真实日志表。
  5. 所有的查询都是针对真实日志表的列查询,所以性能比较高
  6. 在真实日志表中,额外存储了_raw_log_,配合跳数索引完成 近似日志全文检索。

欢迎使用APO全量日志功能 1图


APO v0.6.0更新日志:

新增功能

  • 支持全量日志的采集、处理与展示功能

缺陷修复

  • 修复服务端点存在特殊字符时,无法获取到依赖延时曲线的问题
  • 修复无故障场景下频繁采集故障现场数据的问题
  • 修复部分场景下数据库调用无指标的问题
  • 修复传统服务器场景下,网络质量状态无法关联到告警的问题
  • 修复传统服务器场景下,OneAgent配置注入失败的问题

其他

  • 允许在创建 ClickHouse 表时选择是否创建副本
  • 向下兼容 ClickHouse 版本,当前支持最低版本为 22.8.x

APO介绍:

国内开源首个 OpenTelemetry 结合 eBPF 的向导式可观测性产品

apo.kindlingx.com

https://github.com/CloudDetail/apo