告别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 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 数据类型有一些优化,但它在处理复杂结构时往往会比简单的列查询慢