'# ClickHouse 最近跟Es杠上了,日志场景谁更适合
一、背景与问题
在日志系统建设中,ClickHouse 和 Elasticsearch 的技术路线之争愈演愈烈。这两大 OLAP 引擎在日志场景中的应用场景差异源于其底层架构的根本性区别:
- ClickHouse 基于列式存储 + 向量化执行引擎,适合高并发分析查询
- Elasticsearch 基于倒排索引 + 分布式架构,适合全文搜索和实时日志分析
在实际项目中,我们遇到了典型的场景冲突:日志数据既需要快速写入(10万+条/秒),又需要支持多维度聚合分析(如按时间、地域、设备类型),同时要求支持全文搜索(如日志内容检索)。这种场景下,传统方案需要在 ClickHouse 和 Elasticsearch 之间做选择,或者采用混合架构。
二、基本原理
1. ClickHouse 的核心特性
ClickHouse 采用列式存储架构,每个列存储为独立的向量。其核心优势在于:
- 向量化查询:通过 SIMD 指令集加速列数据处理
- 列式压缩:LZ4 压缩算法实现 10倍压缩率
- MergeTree 引擎:支持实时写入和后台合并操作
- 分布式架构:支持水平扩展的分布式查询
典型数据存储结构:
CREATE TABLE logs (
`timestamp` DateTime,
`level` String,
`ip` String,
`user_id` UInt64,
`request` String,
`status` UInt16
) ENGINE = MergeTree()
ORDER BY (timestamp, ip)2. Elasticsearch 的核心特性
Elasticsearch 基于 Lucene 的倒排索引技术,其核心优势在于:
- 分布式架构:支持水平扩展的集群模式
- 实时搜索:基于倒排索引的全文检索能力
- 动态映射:自动识别字段类型并创建索引
- 分片机制:数据分片和查询路由机制
典型索引创建:
PUT /logs
{
"mappings": {
"properties": {
"timestamp": { "type": "date" },
"level": { "type": "keyword" },
"ip": { "type": "ip" },
"user_id": { "type": "long" },
"request": { "type": "text" },
"status": { "type": "integer" }
}
}
}三、环境准备
1. 系统环境
# 安装 ClickHouse
sudo apt-get install clickhouse-server clickhouse-client
# 安装 Elasticsearch
sudo apt-get install elasticsearch
# 验证版本
clickhouse-client --version
elasticsearch --version2. 日志生成工具
使用 Fluentd 作为日志采集工具:
<source>
@type tail
path /var/log/nginx/access.log
format json
</source>四、核心实现
1. ClickHouse 日志存储方案
-- 创建日志表(按时间分区)
CREATE TABLE logs (
`timestamp` DateTime,
`level` String,
`ip` String,
`user_id` UInt64,
`request` String,
`status` UInt16
) ENGINE = MergeTree()
ORDER BY (timestamp, ip)
PARTITION BY toYYYYMMDD(timestamp)
TTL toDateTime(timestamp) + 30 DAY
-- 插入数据
INSERT INTO logs
FORMAT JSONEachRow关键点说明:
- 使用
MergeTree引擎保证数据一致性 - 按时间分区提升查询性能
- 使用
TTL实现自动数据归档
2. Elasticsearch 日志存储方案
# 索引日志数据
POST /logs/_doc
{
"timestamp": "2023-04-01T12:34:56Z",
"level": "INFO",
"ip": "192.168.1.1",
"user_id": 123456,
"request": "/api/v1/data",
"status": 200
}3. 查询性能对比
-- ClickHouse 查询
SELECT count(*) FROM logs
WHERE status = 404
AND timestamp >= today()-- Elasticsearch 查询
GET /logs/_search
{
"query": {
"bool": {
"must": [
{ "term": { "status": "404" } },
{ "range": { "timestamp": { "gte": "now/d" } } }
]
}
}
}五、完整案例
1. 混合架构日志系统设计
架构图:
[日志采集] -> [Fluentd] -> [Kafka] -> [ClickHouse]
|
v
[Elasticsearch] -> [Kibana]数据流:
- 实时日志:通过 Kafka 写入 ClickHouse
- 全文检索:通过 Elasticsearch 处理
- 分析查询:通过 ClickHouse 提供高性能分析
ClickHouse 配置:
CREATE TABLE logs_clickhouse (
`timestamp` DateTime,
`level` String,
`ip` String,
`user_id` UInt64,
`request` String,
`status` UInt16
) ENGINE = Kafka()
SETTINGS
kafka_broker_list = 'kafka1:9092,kafka2:9092',
kafka_topic_list = 'logs',
kafka_group_name = 'clickhouse_logs',
kafka_format = 'JSONEachRow'Elasticsearch 配置:
PUT /logs
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"timestamp": { "type": "date" },
"level": { "type": "keyword" },
"ip": { "type": "ip" },
"user_id": { "type": "long" },
"request": { "type": "text" },
"status": { "type": "integer" }
}
}
}六、源码解析
1. ClickHouse 的 MergeTree 引擎
核心模块包括:
- MergeTreeData:管理列式数据存储
- IndexGranularity:基于行数的索引粒度
- Partitions:分区管理模块
关键代码:
class MergeTreeData :
public IOutputFormat,
public IInputFormat,
public IStorage
{
public:
MergeTreeData(const StorageID & table_id, const Context & context)
: IOutputFormat(table_id, context)
, IInputFormat(table_id, context)
, IStorage(table_id, context)
{
// 初始化分区和索引
}
};2. Elasticsearch 的倒排索引
核心模块包括:
- IndexReader:管理索引数据
- FieldCache:缓存字段信息
- QueryParser:查询解析器
关键代码:
public class IndexReader {
private final IndexWriter indexWriter;
public IndexReader(IndexWriter indexWriter) {
this.indexWriter = indexWriter;
}
public void addDocument(Document document) {
indexWriter.addDocument(document);
}
public Query parseQuery(String query) {
return QueryParser.parse(query);
}
}七、进阶使用
1. 热点数据缓存
ClickHouse 可通过 Cache 引擎实现热点数据缓存:
CREATE TABLE hot_logs (
`timestamp` DateTime,
`level` String,
`ip` String
) ENGINE = Cache(1000000)2. 分布式查询优化
ClickHouse 的分布式查询:
SELECT count(*) FROM remote('node1:9000', 'logs')
WHERE status = 4043. 索引优化策略
Elasticsearch 的索引优化:
PUT /logs/_settings
{
"index": {
"number_of_replicas": 2,
"refresh_interval": "30s"
}
}八、性能与工程实践
1. 索引策略对比
| 特性 | ClickHouse | Elasticsearch |
|---|---|---|
| 索引类型 | 哈希索引、范围索引 | 倒排索引、字段索引 |
| 查询性能 | 基于列式压缩的快速查询 | 基于倒排索引的全文检索 |
| 写入吞吐 | 10万+条/秒 | 5万+条/秒 |
| 内存占用 | 低 | 高 |
2. 性能优化方法
ClickHouse:
- 使用
MergeTree引擎的TTL策略 - 启用
min_merge_block_size配置 - 使用
ProfileEvents监控系统资源
Elasticsearch:
- 调整
thread_pool线程池配置 - 使用
bulkAPI 批量写入 - 启用
index_compression压缩
3. 安全风险分析
ClickHouse:
- 默认开启
readonly模式 - 需要配置
users.xml控制访问 - 支持 TLS 加密传输
Elasticsearch:
- 默认开放未授权访问
- 需要配置
elasticsearch.yml的xpack.security.enabled - 使用
transport加密传输
九、常见问题与踩坑
1. 常见错误及解决方法
错误1:ClickHouse 查询性能下降
- 原因:未使用合适的索引
- 解决:添加
index字段并重新创建表
错误2:Elasticsearch 写入失败
- 原因:分片配置不当
- 解决:调整
number_of_shards为 3 的倍数
错误3:数据类型不匹配
- 原因:字段类型未正确映射
- 解决:使用
mapping显式定义字段类型
2. 索引策略选择误区
- 错误做法:对所有字段都创建索引
- 正确做法:只对高频查询字段创建索引
- 反例:对
request字段创建索引,但实际查询中未使用该字段
十、最佳实践
1. 使用场景建议
选择 ClickHouse 的场景:
- 需要高频聚合分析(如按时间、地域统计)
- 数据写入量大(10万+条/秒)
- 需要复杂分析(如多维交叉查询)
- 无需全文搜索
选择 Elasticsearch 的场景:
- 需要全文搜索功能
- 需要实时日志分析
- 需要复杂查询(如布尔查询、范围查询)
- 数据量较小(百万级以下)
2. 混合架构建议
- 使用 Kafka 作为数据缓冲
- 通过 Fluentd 实现日志采集
- 使用 ClickHouse 处理分析查询
- 使用 Elasticsearch 处理全文搜索
- 通过 Kibana 提供可视化界面
十一、总结
ClickHouse 和 Elasticsearch 在日志场景中各具优势,其适用性取决于具体业务需求:
- ClickHouse 更适合需要高性能分析查询的场景,其列式存储和向量化执行引擎在处理大数据量时表现卓越,但需要合理的索引策略和分区设计。
- Elasticsearch 更适合需要全文搜索和实时分析的场景,其分布式架构和倒排索引技术在处理复杂查询时有独特优势,但需要权衡写入性能和资源消耗。
在实际项目中,建议根据数据量、查询复杂度、写入吞吐等维度综合评估。对于同时需要分析查询和全文搜索的场景,可以采用混合架构,充分发挥两者的优势。在实施过程中,需要特别注意索引策略、分区设计、安全配置等关键点,避免常见的性能瓶颈和安全风险。