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

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 fieldsbar.String,              Stringfoo.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
SELECTstatus,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全量日志功能
在这里插入图片描述


APO v0.6.0更新日志:

新增功能

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

缺陷修复

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

其他

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

APO介绍:

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

apo.kindlingx.com

https://github.com/CloudDetail/apo

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/454571.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AOP 面向切面编程

1.准备工作&#xff0c;创建maven项目 1. pom.xml 加入依赖 <dependencies><!--spring核心坐标--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.0.6</version&…

算术移位的学习

术移位&#xff08;Arithmetic Shift&#xff09;是一种位移操作&#xff0c;主要用于有符号整数。它与逻辑移位相似&#xff0c;但在处理负数时有一些显著的不同。算术移位能够保持符号位的完整性&#xff0c;因此在有符号数的移位运算中非常有用。 算术移位的类型 算术左移&…

打造商业数字化生态,价值何在?

​在当今数字化时代&#xff0c;商业格局正发生着深刻变革。打造商业数字化生态&#xff0c;成为众多企业的战略选择。那么&#xff0c;它的价值究竟几何呢&#xff1f; 商业数字化生态是利用数字技术&#xff0c;将企业、消费者、合作伙伴等各方连接起来&#xff0c;形成相互依…

AI时代,谷歌会像当年的IBM一样陨落吗?

​从2015年到2024年 在2015年的某个清晨&#xff0c;阳光透过硅谷的薄雾&#xff0c;照亮了谷歌总部那标志性的入口。那时&#xff0c;谷歌正处于技术的巅峰&#xff0c;人们怀着激动的心情讨论着它如何将机器学习和人工智能&#xff08;AI&#xff09;无缝融入到所有产品中。它…

ubuntu 开启haproxy UI

一、修改haproxy.cfg nano /etc/haproxy/haproxy.cfg 添加一段 listen statsbind *:8080stats enablestats uri /uistats refresh 10sstats auth admin:123456stats admin if TRUE 重启 sudo systemctl restart haproxy 浏览器访问&#xff1a; http://192.168.31.182:80…

CMakeLists.txt 编写规则

目录 1. 注释 1.1 注释行 1.2 注释块 2. CMakeLists.txt的编写 2.1 同意目录下的源文件 2.2 SET指令 2.3 file和aux_source_directory 2.4 包含头文件 2.5 生成动态库和静态库 2.6 链接库文件 2.7 message指令 2.8 移除操作 2.9 find_library和find_package 3. 常…

Node.js初学者指南:搭建HTTP服务器、获取请求信息及响应、变量声明与NPM包管理

精神畅快&#xff0c;心气平和&#xff1b;饮食有节&#xff0c;寒暖当心&#xff1b;起居以时&#xff0c;劳逸均匀 文章目录 node.js使用http搭建服务器的问题获取请求相关的信息响应相关的信息var、let、const对比npm使用全局安装包 node.js 概念 node.js是一个基于Chrome …

渗透实战 JS文件怎么利用

1.前言 关于JS在渗透测试中的关键作用&#xff0c;想必不用过多强调&#xff0c;在互联网上也有许多从JS中找到敏感信息从而拿下关键系统的案例。大部分师傅喜欢使用findsomething之类的浏览器插件&#xff0c;也有使用诸如Unexpected.information以及APIFinder之类的Burp插件…

QT--文本框 QLineEdit、qtextedit

在Qt中&#xff0c;文本框&#xff08;QLineEdit 或 QTextEdit&#xff09;和标签&#xff08;QLabel&#xff09;是两种不同的部件&#xff08;widget&#xff09;&#xff0c;它们的主要区别在于用途和功能&#xff1a; QLabel&#xff08;标签&#xff09; 用途&#xff1…

企业数字化转型的理论指南:构建未来企业的关键策略与实践路径

数字化转型已经成为当今企业发展和市场竞争的核心驱动力&#xff0c;而在这一过程中&#xff0c;企业架构&#xff08;EA&#xff09;发挥着至关重要的作用。这本白皮书《世界级企业架构&#xff1a;建立和发展EA能力的领导者方法》提供了深入的理论指导&#xff0c;为企业如何…

专线物流公告服务平台痛点分析:重塑信任、效率与透明度的新篇章

专线物流公告服务平台痛点分析&#xff1a;重塑信任、效率与透明度的新篇章 在当今全球化的商业环境中&#xff0c;专线物流服务作为连接生产与消费的关键纽带&#xff0c;其重要性不言而喻。然而&#xff0c;随着市场竞争的加剧和消费者需求的日益多样化&#xff0c;专线物流…

禁止VMware Service进程开机自动启动

方法一&#xff1a;使用服务管理器 (Services.msc) 打开服务管理器&#xff1a; 按 Win R 打开运行对话框&#xff0c;输入 services.msc&#xff0c;然后按 Enter。 找到 VMware Authorization Service&#xff1a; 在服务列表中找到 VMware Authorization Service。 更改启动…

由云智慧发起的《数字政府统一运维 第1部分:运维平台建设指南》团标正式发布

2024年9月&#xff0c;由云智慧和中国信通院联合主导的《数字政府统一运维 第1部分&#xff1a;运维平台建设指南》团体标准完成编写&#xff0c;经中国互联网协会相关专家审查通过并准予发布&#xff0c;标准编号&#xff1a;T/ISC 0062-2024。 中国数字政府建设已经进入了高…

java高频面试题汇总

Java 基础 Java 中的序列化和反序列化是什么&#xff1f; 序列化是将 Java 对象转换为字节流的过程&#xff0c;以便可以将其存储在文件中或通过网络进行传输。反序列化则是将字节流恢复为 Java 对象的过程。通过实现 Serializable 接口&#xff0c;Java 对象可以支持序列化。…

基于Arduino的LED亮灭按键控制

一、项目简介 通过一个按键控制LED的亮灭&#xff0c;实现按键按一下LED点亮&#xff0c;再按一下LED熄灭&#xff0c;交替循环。 二、控制原理 1. 按键检测原理&#xff1a; 将Arduino的监测端口设置为输入上拉模式&#xff08;INPUT_PULLUP&#xff09;&#xff0c;用于连…

Python实现文本数据可视化:构建动态词云

引言 在信息爆炸的时代&#xff0c;如何有效地从海量的文本数据中提取关键信息并直观展示&#xff0c;成为数据分析师和研究人员面临的重要挑战。词云作为一种流行的文本可视化工具&#xff0c;通过不同大小、颜色和字体的文字展示文本中关键词的出现频率或重要性&#xff0c;…

某ai gpt的bug

某ai gpt的bug 背景 遇到了一个奇怪的现象&#xff1a; 输入内容 2024-10-21 10:09:31,052 ERROR o.a.j.t.JMeterThread: Test failed! java.lang.IllegalArgumentException:输出结果

Java应用程序的测试覆盖率之设计与实现(二)-- jacoco agent

说在前面的话 要想获得测试覆盖率报告&#xff0c;第一步要做的是&#xff0c;采集覆盖率数据&#xff0c;并输入到tcp。 而本文便是介绍一种java应用程序部署下的推荐方式。 作为一种通用方案&#xff0c;首先不想对应用程序有所侵入&#xff0c;其次运维和管理方便。 正好…

动态路由:RIP实验

1.划分IP 2.配置环回 3.接口配置IP地址 4.进入RIP中&#xff0c;关闭手工汇总&#xff0c;选择版本号&#xff0c;宣告 5.ping命令查看是否全网通 6.在R3上配置缺省路由 [R3-rip-1]default-route originate 在边界路由器上下发缺省 7.为了安全配置手工认证 [R1-Gigab…

WordPress+Nginx 安装教程

WordPress 是一个开源的网站建设工具&#xff0c;可以用它来“快速”搭建个人博客&#xff0c;官网等等。它本身是用 php 开发的&#xff0c;本身部署不复杂&#xff0c;主要是需要一些配套的东西才能跑起来&#xff0c;网上的一些教程也是写的不清不楚&#xff0c;本文针对非 …