ClickHouse 日志分析平台集群部署与运维实战 一、概述 ClickHouse 是 Yandex 开源的列式存储 OLAP 数据库,以其卓越的查询性能和压缩比在日志分析领域广受欢迎。相比 ELK 栈,ClickHouse 在海量日志查询场景下具有更高的性价比和更低的资源消耗。
1.1 核心特性
列式存储 : 适合聚合分析查询,压缩比高达 10:1
向量化执行 : 充分利用 CPU SIMD 指令,查询性能卓越
分布式架构 : 原生支持分片集群和副本高可用
SQL 兼容 : 支持标准 SQL 语法,学习成本低
实时写入 : 支持高吞吐实时数据写入
1.2 适用场景
日志检索与分析
用户行为分析
监控指标存储
实时数据报表
二、环境规划 2.1 硬件配置建议
节点角色
数量
CPU
内存
磁盘
网络
ClickHouse Server
3+
8 核 +
32GB+
SSD 1TB+
10GbE
ZooKeeper
3
4 核
8GB
SSD 100GB
1GbE
2.2 软件版本
ClickHouse: 24.8 LTS
ZooKeeper: 3.8+
操作系统:Ubuntu 22.04 LTS / CentOS Stream 9
2.3 网络规划 1 2 3 4 节点 1 (192.168.1.101): shard1, replica1 节点 2 (192.168.1.102): shard1, replica2 节点 3 (192.168.1.103): shard2, replica1 节点 4 (192.168.1.104): shard2, replica2
三、ClickHouse 集群部署 3.1 安装 ClickHouse 1 2 3 4 5 6 7 curl https://clickhouse.com/ | sh sudo apt-get install -y clickhouse-server clickhouse-clientcurl https://clickhouse.com/ | sh sudo yum install -y clickhouse-server clickhouse-client
3.2 配置 ZooKeeper ClickHouse 集群依赖 ZooKeeper 进行元数据管理和分布式 DDL 同步。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <clickhouse > <zookeeper > <node index ="1" > <host > zk1.example.com</host > <port > 2181</port > </node > <node index ="2" > <host > zk2.example.com</host > <port > 2181</port > </node > <node index ="3" > <host > zk3.example.com</host > <port > 2181</port > </node > </zookeeper > </clickhouse >
3.3 配置集群拓扑 1 2 3 4 5 6 7 <clickhouse > <macros > <shard > 01</shard > <replica > 01</replica > </macros > </clickhouse >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <clickhouse > <remote_servers > <logs_cluster > <shard > <replica > <host > 192.168.1.101</host > <port > 9000</port > </replica > <replica > <host > 192.168.1.102</host > <port > 9000</port > </replica > </shard > <shard > <replica > <host > 192.168.1.103</host > <port > 9000</port > </replica > <replica > <host > 192.168.1.104</host > <port > 9000</port > </replica > </shard > </logs_cluster > </remote_servers > </clickhouse >
3.4 配置用户权限 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <clickhouse > <users > <logging_user > <password > your_secure_password</password > <networks > <ip > ::/0</ip > </networks > <profile > logging_profile</profile > <quota > logging_quota</quota > <access_management > 1</access_management > </logging_user > </users > <profiles > <logging_profile > <max_memory_usage > 16000000000</max_memory_usage > <use_uncompressed_cache > 0</use_uncompressed_cache > <load_balancing > random</load_balancing > <max_execution_time > 300</max_execution_time > </logging_profile > </profiles > <quotas > <logging_quota > <interval > <duration > 3600</duration > <queries > 1000</queries > <result_rows > 1000000</result_rows > <read_rows > 100000000</read_rows > </interval > </logging_quota > </quotas > </clickhouse >
3.5 启动服务 1 2 3 4 5 6 7 8 9 sudo systemctl start clickhouse-serversudo systemctl enable clickhouse-serversudo systemctl status clickhouse-serverclickhouse-client --query "SELECT version()"
四、日志表结构设计 4.1 创建日志数据库 1 2 3 4 5 CREATE DATABASE IF NOT EXISTS logsENGINE = Atomic ; USE logs;
4.2 创建分布式表和本地表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 CREATE TABLE logs_local ON CLUSTER logs_cluster( `timestamp ` DateTime64(3 ) DEFAULT now64(3 ), `trace_id` String, `span_id` String, `service_name` LowCardinality(String), `log_level` LowCardinality(String), `message` String, `host` LowCardinality(String), `container_id` String, `namespace` LowCardinality(String), `pod_name` LowCardinality(String), `labels` Map(String, String), `attributes` Map(String, String) ) ENGINE = ReplicatedMergeTree( '/clickhouse/tables/{shard}/logs' , '{replica}' ) PARTITION BY toYYYYMMDD(timestamp )ORDER BY (service_name, timestamp )TTL timestamp + INTERVAL 30 DAY SETTINGS index_granularity = 8192 , compress_part_header = 1 ; CREATE TABLE logs ON CLUSTER logs_cluster AS logs_localENGINE = Distributed(logs_cluster, logs, logs_local, rand());
4.3 创建物化视图 (可选) 1 2 3 4 5 6 7 8 9 10 11 12 13 CREATE MATERIALIZED VIEW logs_error_stats ON CLUSTER logs_clusterENGINE = SummingMergeTree() PARTITION BY toYYYYMMDD(timestamp )ORDER BY (service_name, log_level, toStartOfHour(timestamp ))AS SELECT toStartOfHour(timestamp ) AS timestamp , service_name, log_level, count () AS log_count FROM logs_localWHERE log_level IN ('ERROR' , 'FATAL' )GROUP BY timestamp , service_name, log_level;
五、日志采集与写入 5.1 Vector 采集配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 sources: kubernetes_logs: type: kubernetes_logs pod_annotation_fields: container_name: container_name pod_labels: pod_labels pod_name: pod_name sinks: clickhouse: type: clickhouse inputs: - kubernetes_logs endpoint: http://192.168.1.101:8123 database: logs table: logs_local encoding: codec: json healthcheck: enabled: true batch: max_events: 10000 timeout_secs: 5 buffer: type: memory max_events: 500000 request: retry_attempts: 3 retry_max_duration_secs: 300
5.2 Fluent Bit 采集配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 [INPUT] Name tail Path /var/log/containers/*.log Parser docker Tag kube.* Refresh_Interval 10 Mem_Buf_Limit 500MB [FILTER] Name kubernetes Match kube.* Kube_URL https://kubernetes.default.svc:443 Kube_CA_File /var/run/secrets/kubernetes.io/serviceaccount/ca.crt Kube_Token_File /var/run/secrets/kubernetes.io/serviceaccount/token Kube_Tag_Prefix kube.var.log.containers. Merge_Log On Keep_Log Off K8S-Logging.Parser On K8S-Logging.Exclude On [OUTPUT] Name clickhouse Match * Host 192.168.1.101 Port 8123 Database logs Table logs_local User logging_user Password your_secure_password Format json Batch_Size 10000 Flush_Interval 5
5.3 写入性能优化 1 2 3 4 5 6 7 8 9 10 ALTER TABLE logs_local MODIFY SETTING max_insert_block_size = 1000000 , min_insert_block_size_rows = 100000 , min_insert_block_size_bytes = 104857600 ; SET async_insert = 1 ;SET wait_for_async_insert = 0 ;SET max_async_insert_size = 1048576 ;
六、查询优化与实践 6.1 常用查询示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 SELECT timestamp , service_name, message FROM logsWHERE log_level = 'ERROR' AND timestamp >= now() - INTERVAL 1 HOUR ORDER BY timestamp DESC LIMIT 100 ; SELECT service_name, count () AS error_count FROM logsWHERE log_level IN ('ERROR' , 'FATAL' ) AND timestamp >= now() - INTERVAL 24 HOUR GROUP BY service_nameORDER BY error_count DESC ;SELECT timestamp , service_name, message FROM logsWHERE message LIKE '%Connection refused%' AND timestamp >= now() - INTERVAL 1 HOUR LIMIT 50 ; SELECT timestamp , service_name, log_level, message FROM logsWHERE trace_id = 'abc123xyz' ORDER BY timestamp ASC ;
6.2 性能优化技巧 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 ALTER TABLE logs_local ADD PROJECTION service_hour_stats( SELECT service_name, toStartOfHour(timestamp ) AS hour , log_level, count () AS cnt GROUP BY service_name, hour , log_level ); ALTER TABLE logs_local ADD INDEX idx_message message TYPE tokenbf_v1(10240 , 3 , 0 ) GRANULARITY 4 ;SELECT service_name, count () AS cnt FROM logs SAMPLE 0.1 WHERE timestamp >= now() - INTERVAL 7 DAY GROUP BY service_name;SELECT * FROM logs_local FINAL WHERE trace_id = 'xxx' ;
6.3 查询性能监控 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 SELECT query, query_duration_ms, read_rows, read_bytes, memory_usage FROM system.query_logWHERE type = 'QueryFinish' AND query_duration_ms > 1000 AND event_date >= today() - 1 ORDER BY query_duration_ms DESC LIMIT 20 ; SELECT table , formatReadableSize(sum (data_compressed_bytes)) AS compressed, formatReadableSize(sum (data_uncompressed_bytes)) AS uncompressed, round(sum (data_uncompressed_bytes) / sum (data_compressed_bytes), 2 ) AS compression_ratio, sum (rows ) AS rows FROM system.partsWHERE table = 'logs_local' GROUP BY table ;
七、运维管理 7.1 备份策略 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #!/bin/bash BACKUP_DIR="/backup/clickhouse" DATE=$(date +%Y%m%d_%H%M%S) CLICKHOUSE_HOST="localhost" CLICKHOUSE_USER="default" mkdir -p ${BACKUP_DIR} /${DATE} clickhouse-backup create logs_backup_${DATE} \ --tables="logs.*" \ --user=${CLICKHOUSE_USER} clickhouse-backup upload logs_backup_${DATE} clickhouse-backup delete remote $(clickhouse-backup list remote | grep logs_backup | head -n -7 | awk '{print $1}' )
7.2 监控指标 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 SELECT shard_num, replica_num, host_name, is_local, is_read_only, is_session_expired, errors_count FROM system.clustersWHERE cluster = 'logs_cluster' ;SELECT table , is_read_only, is_session_expired, zookeeper_exception, total_replicas, active_replicas, has_lost_parts, has_mutated_parts FROM system.replicasWHERE database = 'logs' ;SELECT table , partition , active, marks, rows , bytes_on_disk, modification_time FROM system.partsWHERE table = 'logs_local' ORDER BY modification_time DESC ;
7.3 常见故障处理 故障 1: ZooKeeper 连接丢失 1 2 3 4 5 SELECT * FROM system.zookeeper WHERE path = '/' ;sudo systemctl restart clickhouse- server
故障 2: 副本不同步 1 2 3 4 5 6 7 SELECT * FROM system.replicas WHERE is_readonly = 0 ;SYSTEM SYNC REPLICA logs.logs_local;
故障 3: 磁盘空间不足 1 2 3 4 5 6 7 8 9 10 11 SELECT name, path, formatReadableSize(free_space) AS free , formatReadableSize(total_space) AS total, round((free_space / total_space) * 100 , 2 ) AS free_percent FROM system.disks;ALTER TABLE logs_local DELETE WHERE timestamp < now() - INTERVAL 7 DAY ;
7.4 扩容操作 1 2 3 4 5 6 7 8 9 10 11 sudo systemctl restart clickhouse- server SELECT * FROM system.clusters WHERE cluster = 'logs_cluster' ;
八、与 Grafana 集成 8.1 安装 ClickHouse 数据源 1 2 3 4 5 grafana-cli plugins install grafana-clickhouse-datasource sudo systemctl restart grafana-server
8.2 配置数据源 在 Grafana 中添加 ClickHouse 数据源:
URL: http://192.168.1.101:8123
Database: logs
User: logging_user
Password: your_secure_password
8.3 常用面板查询 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 SELECT toStartOfMinute(timestamp ) AS time , service_name, count () AS errors FROM logsWHERE log_level IN ('ERROR' , 'FATAL' ) AND timestamp >= $__timeFrom() AND timestamp <= $__timeTo() GROUP BY time , service_nameORDER BY time ASC ;SELECT histogramQuantile(0.95 , attributes['response_time' ]) AS p95_latency FROM logsWHERE timestamp >= $__timeFrom() AND timestamp <= $__timeTo() GROUP BY service_name;
九、最佳实践总结 9.1 表设计
使用 LowCardinality 优化低基数字段
合理设置 TTL 自动清理过期数据
选择合适的主键顺序 (高频过滤字段在前)
使用物化视图预计算常用聚合
9.2 写入优化
批量写入,避免单条插入
使用异步插入提高吞吐
合理设置缓冲大小
监控写入队列积压
9.3 查询优化
避免 SELECT *
利用分区裁剪
使用采样处理大数据量
为高频查询创建投影
9.4 运维要点
定期监控磁盘使用率
设置合理的 TTL 策略
建立备份和恢复流程
监控 ZooKeeper 健康状态
定期分析慢查询日志
十、参考资源