未记录事实核验 · 状态:维护中

记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。

warning 适用范围与待维护事项

本文适用于具备ClickHouse系统表、Linux、Prometheus及Kubernetes经验的工程人员,在隔离测试环境设计监控原型。医院人物、事件记录及阈值均按教学示例理解,不代表真实医院成果或通用SLA。原稿未注明验证版本,exporter指标、鉴权和Grafana插件需按实际环境核对;diagnosis_efficiency、monitoring_metrics及部署配置等材料未提供。清日志、OPTIMIZE FINAL和副本同步具有资源与数据风险,不应直接定时执行;三实例StatefulSet也不等于已配置复制集群。

📊 ClickHouse监控运维

📚 学习目标

通过本章节的学习,你将能够:

  • 建立完整的监控体系,覆盖系统和业务各个层面
  • 掌握告警机制设计,实现主动式运维管理
  • 学会性能调优方法,持续优化系统性能
  • 具备故障处理能力,快速定位和解决问题
  • 实现运维自动化,提升运维效率和质量

tip 💡 学习建议

监控运维是ClickHouse生产应用的重要保障,就像医生监护病人的生命体征一样重要。先建立监控体系,再实现自动化运维。重点理解各种指标的含义和关联关系,这样才能准确诊断和处理问题。

📈 监控体系架构

建立完善的监控体系是保障ClickHouse稳定运行的基石。一个有效的监控架构需要考虑数据收集、存储、分析和展示的全流程,并且要能够在问题发生前提供预警。

1. 监控架构设计

现代监控架构遵循 "USE" 方法论(Utilization, Saturation, Errors),通过多层次的指标体系全面监控系统状态。在ClickHouse环境中,还需要特别关注查询性能、数据质量和集群健康度。

张护士长来到医院的中央监控室,自豪地向大家介绍:"欢迎来到我们医院的'生命监护中心'!这里就像ClickHouse的监控运维系统一样,24小时不间断地监护着整个医院的'健康状态'。"

"你们看,"她指着墙上密密麻麻的监控屏幕,"我们建立了四个层级的全方位监护体系,就像给医院做了全面的'体检系统'。这套监控体系不仅能实时监测医院各个科室的运行状态,更重要的是能够预警潜在问题,就像患者的生命体征监护一样。"

🏥 医院智能监护体系的四个层级:

  1. 患者体验层:直接关系到患者感受的指标,如挂号等待时间、就诊效率等
  2. 医疗服务层:医生、护士、设备的工作状态和服务质量
  3. 基础设施层:医院的水、电、气、网络等基础保障系统
  4. 管理决策层:医院运营数据的分析和决策支持

"每一层都有专门的监控指标和告警机制,"张护士长继续解释,"一旦某个环节出现异常,系统会立即发出警报,相关负责人会第一时间收到通知并采取行动。"

多层监控模型(医院生命监护体系)

"我们的监护体系分为四个层级,"张护士长详细介绍道,"从最直接的患者体验,到最底层的设备状态,每一个环节都不能出问题。"

%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
flowchart TB
    classDef default fill:#FDFBF5,stroke:#6E6A60,color:#2D2A26,stroke-width:1.5px
    classDef startend fill:#2D2A26,stroke:#2D2A26,color:#FDFBF5,font-weight:bold
    classDef decision fill:#F6EAD8,stroke:#C08A1A,color:#2D2A26,font-weight:bold
    classDef hub fill:#3B5A80,stroke:#3B5A80,color:#FDFBF5,font-weight:bold
    classDef io fill:#E5EAF0,stroke:#3B5A80,color:#2D2A26
    classDef accent fill:#A6432C,stroke:#A6432C,color:#FDFBF5,font-weight:bold
    classDef storage fill:#F3EEDF,stroke:#9A9488,color:#2D2A26
    subgraph "业务层监控"
        A1[查询响应时间]
        A2[业务指标准确性]
        A3[数据freshness]
        A4[用户体验指标]
    end
    subgraph "应用层监控"
        B1[ClickHouse服务状态]
        B2[连接池状态]
        B3[查询执行情况]
        B4[错误率统计]
    end
    subgraph "系统层监控"
        C1[CPU/内存使用率]
        C2[磁盘I/O性能]
        C3[网络流量]
        C4[进程状态]
    end
    subgraph "基础设施监控"
        D1[服务器硬件]
        D2[网络设备]
        D3[存储系统]
        D4[电源/环境]
    end
    subgraph "数据采集层"
        E1[Prometheus]
        E2[ClickHouse System Tables]
        E3[Node Exporter]
        E4[Custom Exporters]
    end
    subgraph "展示告警层"
        F1[Grafana Dashboard]
        F2[AlertManager]
        F3[PagerDuty/钉钉]
        F4[监控大屏]
    end
    A1 --> E2
    A2 --> E2
    B1 --> E1
    B2 --> E4
    C1 --> E3
    C2 --> E3
    E1 --> F1
    E2 --> F1
    E1 --> F2 --> F3
    class A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3,C4,D1,D2,D3,D4,E1,E2,E3,E4,F1,F2,F3,F4 default

建立医院智能监控指标体系

陈技师补充道:"我们的智能监控系统就像医院的'健康档案',需要记录各种'生命体征'数据。每一个指标都像患者的血压、心率、体温一样重要,需要持续监控并建立历史趋势。"

-- 创建医院监控指标汇总表(类比ClickHouse系统监控)
CREATE TABLE hospital_monitoring_metrics (
    timestamp DateTime,                       -- 监控时间戳
    metric_category LowCardinality(String),  -- 指标分类:患者服务、医疗设备、基础设施、人员
    metric_name LowCardinality(String),      -- 具体指标名称
    metric_value Float64,                    -- 指标数值
    department LowCardinality(String),       -- 所属科室
    equipment_id Nullable(String),          -- 设备编号(如果适用)
    severity_level LowCardinality(String),   -- 严重程度:正常、警告、紧急
    labels Map(String, String)              -- 扩展标签信息
) ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(timestamp)          -- 按日期分区,便于数据管理
ORDER BY (metric_category, department, metric_name, timestamp)
TTL timestamp + INTERVAL 90 DAY;            -- 90天后自动清理历史数据

-- 收集医疗设备性能指标(类比系统资源监控)
INSERT INTO hospital_monitoring_metrics
SELECT 
    now() as timestamp,
    '医疗设备' as metric_category,
    CASE 
        WHEN metric LIKE '%Memory%' THEN '设备内存使用率'
        WHEN metric LIKE '%CPU%' THEN '设备CPU使用率'
        WHEN metric LIKE '%Disk%' THEN '设备存储使用率'
        ELSE metric
    END as metric_name,
    value as metric_value,
    '信息科' as department,
    concat('server_', toString(rand() % 10 + 1)) as equipment_id,
    CASE 
        WHEN value > 90 THEN '紧急'
        WHEN value > 70 THEN '警告'
        ELSE '正常'
    END as severity_level,
    map('unit', 'source_metric_unit_unverified', 'source', 'system_metrics') as labels
FROM system.asynchronous_metrics
WHERE metric LIKE '%Memory%' OR metric LIKE '%CPU%';

-- 收集诊疗服务性能指标(类比查询性能监控)
INSERT INTO hospital_monitoring_metrics
SELECT 
    toStartOfMinute(now()) as timestamp,
    '诊疗服务' as metric_category,
    '平均诊疗时长' as metric_name,
    avg(examination_time_seconds / 60.0) as metric_value,  -- 转换为分钟
    department,
    NULL as equipment_id,
    CASE 
        WHEN avg(examination_time_seconds / 60.0) > 60 THEN '警告'
        WHEN avg(examination_time_seconds / 60.0) > 30 THEN '注意'
        ELSE '正常'
    END as severity_level,
    map('unit', 'minutes', 'time_range', '1min') as labels
FROM diagnosis_efficiency
WHERE diagnosis_timestamp >= now() - INTERVAL 1 MINUTE
GROUP BY department;

-- 患者体验辅助指标收集:使用诊疗时长,不直接测量等待时间或满意度
INSERT INTO hospital_monitoring_metrics
SELECT 
    now() as timestamp,
    '患者体验' as metric_category,
    '平均诊疗时长' as metric_name,
    avg(examination_time_seconds / 60.0) as metric_value,
    department,
    NULL as equipment_id,
    CASE 
        WHEN avg(examination_time_seconds / 60.0) > 45 THEN '警告'
        WHEN avg(examination_time_seconds / 60.0) > 30 THEN '注意'
        ELSE '优秀'
    END as severity_level,
    map('unit', 'minutes', 'kpi', 'examination_duration') as labels
FROM diagnosis_efficiency
WHERE diagnosis_date = today()
GROUP BY department;

info 💡 监控指标设计原则

医院监控体系的核心理念:

  • 全面覆盖:从患者体验到设备运行,无死角监控
  • 分层分级:不同级别的指标有不同的响应机制
  • 预警机制:提前发现问题,而不是被动响应
  • 数据驱动:基于历史数据建立智能预警阈值

2. 关键监控指标

ClickHouse的监控指标可以分为四大类别:系统资源指标、服务状态指标、查询性能指标和业务指标。每类指标都有其特定的采集方式和分析方法,需要根据业务特点设置合适的阈值和告警策略。

系统资源指标

-- CPU使用率监控
CREATE VIEW cpu_usage_monitor AS
SELECT 
    toStartOfMinute(now()) as timestamp,
    hostName() as host,
    value as cpu_usage_percent
FROM system.asynchronous_metrics
WHERE metric = 'CPUUsageNormalized';

-- 进程内存指标监控:下列比例为MemoryDataAndStack/MemoryVirtual,不是主机物理内存使用率,不应直接用于主机内存告警
CREATE VIEW memory_usage_monitor AS
SELECT 
    toStartOfMinute(now()) as timestamp,
    hostName() as host,
    formatReadableSize(value) as memory_used,
    value as memory_bytes,
    round(value / (SELECT value FROM system.asynchronous_metrics WHERE metric = 'MemoryVirtual') * 100, 2) as memory_usage_percent
FROM system.asynchronous_metrics
WHERE metric = 'MemoryDataAndStack';

-- 磁盘使用监控
CREATE VIEW disk_usage_monitor AS
SELECT 
    name as disk_name,
    path,
    formatReadableSize(total_space) as total_space,
    formatReadableSize(free_space) as free_space,
    formatReadableSize(total_space - free_space) as used_space,
    round((total_space - free_space) / total_space * 100, 2) as usage_percent
FROM system.disks;

-- 磁盘相关异步指标历史:具体指标名称、单位及速率计算需按版本核对
SELECT 
    event_time,
    metric,
    value
FROM system.asynchronous_metrics_log
WHERE metric LIKE '%Disk%'
  AND event_time >= now() - INTERVAL 5 MINUTE
ORDER BY event_time DESC
LIMIT 10;

服务状态指标

-- ClickHouse服务状态
CREATE VIEW service_status_monitor AS
SELECT 
    hostName() as host,
    version() as version,
    uptime() as uptime_seconds,
    formatReadableTimeDelta(uptime()) as uptime_readable,
    (SELECT count() FROM system.processes WHERE query != '') as active_queries,
    (SELECT count() FROM system.replicas WHERE is_readonly = 1) as readonly_replicas,
    (SELECT count() FROM system.replicas WHERE queue_size > 0) as replicas_with_queue
FROM system.one;

-- 连接状态监控
SELECT 
    hostName() as host,
    metric,
    value
FROM system.metrics
WHERE metric IN (
    'TCPConnection',
    'HTTPConnection', 
    'InterserverConnection',
    'MySQLConnection'
)
ORDER BY metric;

-- 副本同步状态
CREATE VIEW replica_sync_monitor AS
SELECT 
    database,
    table,
    replica_name,
    is_leader,
    is_readonly,
    is_session_expired,
    future_parts,
    parts_to_check,
    queue_size,
    inserts_in_queue,
    merges_in_queue,
    part_mutations_in_queue,
    last_queue_update,
    absolute_delay,
    total_replicas,
    active_replicas
FROM system.replicas
ORDER BY queue_size DESC, absolute_delay DESC;

查询性能指标

-- 查询性能统计
CREATE VIEW query_performance_monitor AS
SELECT 
    toStartOfMinute(event_time) as minute,
    type,
    count() as query_count,
    avg(query_duration_ms) as avg_duration_ms,
    quantile(0.95)(query_duration_ms) as p95_duration_ms,
    quantile(0.99)(query_duration_ms) as p99_duration_ms,
    max(query_duration_ms) as max_duration_ms,
    sum(read_rows) as total_read_rows,
    sum(read_bytes) as total_read_bytes,
    sum(written_rows) as total_written_rows,
    sum(memory_usage) as total_memory_usage,
    avg(length(thread_ids)) as avg_threads
FROM system.query_log
WHERE event_time >= now() - INTERVAL 1 HOUR
GROUP BY minute, type
ORDER BY minute DESC, query_count DESC;

-- 慢查询监控
CREATE VIEW slow_queries_monitor AS
SELECT 
    query_id,
    user,
    query_duration_ms,
    formatReadableSize(memory_usage) as memory_used,
    read_rows,
    formatReadableSize(read_bytes) as data_read,
    length(thread_ids) as threads_used,
    event_time,
    substring(query, 1, 200) as query_preview
FROM system.query_log
WHERE type = 'QueryFinish'
  AND query_duration_ms > 10000  -- 超过10秒的查询
  AND event_time >= now() - INTERVAL 24 HOUR
ORDER BY query_duration_ms DESC
LIMIT 20;

-- 错误查询监控
CREATE VIEW error_queries_monitor AS
SELECT 
    toStartOfHour(event_time) as hour,
    exception_code,
    exception,
    count() as error_count,
    uniq(query_id) as unique_errors,
    any(query) as sample_query
FROM system.query_log
WHERE type = 'ExceptionWhileProcessing'
  AND event_time >= now() - INTERVAL 24 HOUR
GROUP BY hour, exception_code, exception
ORDER BY hour DESC, error_count DESC;

🚨 告警系统设计

1. 告警规则配置

Prometheus告警规则

# clickhouse_alerts.yml
groups:
  - name: clickhouse_critical
    rules:
 # 服务可用性告警
      - alert: ClickHouseDown
        expr: up{job="clickhouse"} == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "ClickHouse服务不可用"
          description: "节点 {{ $labels.instance }} 的ClickHouse服务已停止"

 # 磁盘空间告警
      - alert: DiskSpaceHigh
        expr: (clickhouse_disk_total_bytes - clickhouse_disk_free_bytes) / clickhouse_disk_total_bytes > 0.9
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "磁盘空间不足"
          description: "节点 {{ $labels.instance }} 磁盘使用率超过90%"

 # 内存使用告警
      - alert: MemoryUsageHigh
        expr: clickhouse_memory_usage_bytes / clickhouse_memory_total_bytes > 0.9
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "内存使用率过高"
          description: "节点 {{ $labels.instance }} 内存使用率超过90%"

  - name: clickhouse_performance
    rules:
 # 查询性能告警
      - alert: SlowQueriesHigh
        expr: rate(clickhouse_slow_queries_total[5m]) > 10
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "慢查询数量过多"
          description: "最近5分钟窗口内慢查询平均速率超过每秒10个,并持续2分钟;阈值需按业务调整"

 # 副本延迟告警
      - alert: ReplicaLagHigh
        expr: clickhouse_replica_queue_size > 100
        for: 3m
        labels:
          severity: warning
        annotations:
          summary: "副本同步延迟过高"
          description: "副本 {{ $labels.replica }} 队列长度超过100"

 # 错误率告警
      - alert: ErrorRateHigh
        expr: rate(clickhouse_query_errors_total[5m]) / rate(clickhouse_queries_total[5m]) > 0.05
        for: 3m
        labels:
          severity: warning
        annotations:
          summary: "查询错误率过高"
          description: "5分钟内查询错误率超过5%"

基于ClickHouse表和视图的自定义告警检查

-- 创建告警配置表
CREATE TABLE alert_rules (
    rule_id String,
    rule_name String,
    metric_query String,
    threshold Float64,
    comparison_operator LowCardinality(String),
    severity LowCardinality(String),
    description String,
    is_active UInt8 DEFAULT 1,
    created_at DateTime DEFAULT now()
) ENGINE = MergeTree()
ORDER BY rule_id;

-- 插入告警规则
INSERT INTO alert_rules (rule_id, rule_name, metric_query, threshold, comparison_operator, severity, description, is_active) VALUES
    ('disk_space', '磁盘空间不足', 'SELECT usage_percent FROM disk_usage_monitor', 90, '>', 'critical', '磁盘使用率超过90%', 1),
    ('memory_usage', '进程内存指标比例过高', 'SELECT memory_usage_percent FROM memory_usage_monitor', 85, '>', 'warning', 'MemoryDataAndStack/MemoryVirtual比例超过85%(教学阈值,非主机物理内存使用率告警)', 1),
    ('slow_queries', '慢查询过多', 'SELECT count() FROM slow_queries_monitor WHERE event_time >= now() - INTERVAL 5 MINUTE', 5, '>', 'warning', '5分钟内慢查询超过5个', 1),
    ('replica_lag', '副本延迟过高', 'SELECT max(queue_size) FROM replica_sync_monitor', 50, '>', 'warning', '副本队列长度超过50', 1);

-- 告警检查函数
CREATE VIEW alert_checks AS
WITH rule_evaluations AS (
    SELECT 
        rule_id,
        rule_name,
        threshold,
        comparison_operator,
        severity,
        description,
        CASE rule_id
            WHEN 'disk_space' THEN (SELECT max(usage_percent) FROM disk_usage_monitor)
            WHEN 'memory_usage' THEN (SELECT memory_usage_percent FROM memory_usage_monitor LIMIT 1)
            WHEN 'slow_queries' THEN (SELECT count() FROM slow_queries_monitor WHERE event_time >= now() - INTERVAL 5 MINUTE)
            WHEN 'replica_lag' THEN (SELECT max(queue_size) FROM replica_sync_monitor)
            ELSE 0
        END as current_value
    FROM alert_rules
    WHERE is_active = 1
)
SELECT 
    rule_id,
    rule_name,
    current_value,
    threshold,
    comparison_operator,
    severity,
    description,
    CASE comparison_operator
        WHEN '>' THEN current_value > threshold
        WHEN '<' THEN current_value < threshold
        WHEN '>=' THEN current_value >= threshold
        WHEN '<=' THEN current_value <= threshold
        WHEN '=' THEN current_value = threshold
        ELSE false
    END as is_triggered,
    now() as check_time
FROM rule_evaluations;

-- 获取当前触发的告警
SELECT * FROM alert_checks WHERE is_triggered = 1;

2. 告警通知集成

告警历史记录

-- 创建告警历史表
CREATE TABLE alert_history (
    alert_id String,
    rule_id String,
    rule_name String,
    severity LowCardinality(String),
    status LowCardinality(String),  -- 'fired', 'resolved'
    current_value Float64,
    threshold Float64,
    description String,
    host_name String,
    fired_at DateTime,
    resolved_at Nullable(DateTime),
    duration_seconds Nullable(UInt32)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(fired_at)
ORDER BY (severity, fired_at)
TTL fired_at + INTERVAL 6 MONTH;

-- 告警触发处理
INSERT INTO alert_history
SELECT 
    generateUUIDv4() as alert_id,
    rule_id,
    rule_name,
    severity,
    'fired' as status,
    current_value,
    threshold,
    description,
    hostName() as host_name,
    check_time as fired_at,
    NULL as resolved_at,
    NULL as duration_seconds
FROM alert_checks
WHERE is_triggered = 1;

-- 告警统计分析
SELECT 
    toStartOfDay(fired_at) as date,
    severity,
    count() as alert_count,
    uniq(rule_id) as unique_rules,
    avg(duration_seconds) as avg_duration_seconds
FROM alert_history
WHERE fired_at >= now() - INTERVAL 30 DAY
  AND status = 'resolved'
GROUP BY date, severity
ORDER BY date DESC, severity;

通知渠道集成

# alert_notifications.模型部署 - 告警通知脚本
import requests
import json
from datetime import datetime
from clickhouse_driver import Client

class AlertNotifier:
    def __init__(self, clickhouse_host='localhost'):
        self.ch_client = Client(clickhouse_host)
        
    def check_alerts(self):
        """检查当前触发的告警"""
        query = "SELECT * FROM alert_checks WHERE is_triggered = 1"
        return self.ch_client.execute(query)
    
    def send_dingtalk_alert(self, webhook_url, alert_data):
        """发送钉钉告警"""
        message = {
            "msgtype": "markdown",
            "markdown": {
                "title": f"ClickHouse告警 - {alert_data['severity']}",
                "text": f"""
### ClickHouse告警通知

**告警规则**: {alert_data['rule_name']}
**严重级别**: {alert_data['severity']}
**当前值**: {alert_data['current_value']}
**阈值**: {alert_data['threshold']}
**描述**: {alert_data['description']}
**时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
**主机**: {alert_data.get('host_name', 'unknown')}
                """
            }
        }
        
        response = requests.post(webhook_url, 
                               json=message, 
                               headers={'Content-Type': 'application/json'})
        return response.status_code == 200
    
    def send_email_alert(self, smtp_config, recipients, alert_data):
        """发送邮件告警"""
        import smtplib
        from email.mime.text import MIMEText
        from email.mime.multipart import MIMEMultipart
        
        msg = MIMEMultipart()
        msg['From'] = smtp_config['username']
        msg['To'] = ', '.join(recipients)
        msg['Subject'] = f"ClickHouse告警 - {alert_data['severity']} - {alert_data['rule_name']}"
        
        body = f"""
        ClickHouse告警详情:
        
        告警规则: {alert_data['rule_name']}
        严重级别: {alert_data['severity']}
        当前值: {alert_data['current_value']}
        阈值: {alert_data['threshold']}
        描述: {alert_data['description']}
        检查时间: {alert_data['check_time']}
        """
        
        msg.attach(MIMEText(body, 'plain'))
        
        server = smtplib.SMTP(smtp_config['server'], smtp_config['port'])
        server.starttls()
        server.login(smtp_config['username'], smtp_config['password'])
        server.send_message(msg)
        server.quit()
        
        return True
    
    def run_alert_check(self, config):
        """运行告警检查和通知"""
        alerts = self.check_alerts()
        
        for alert in alerts:
            alert_data = {
                'rule_id': alert[0],
                'rule_name': alert[1], 
                'current_value': alert[2],
                'threshold': alert[3],
                'severity': alert[5],
                'description': alert[6],
                'check_time': alert[8]
            }
            
 # 发送通知
            if config.get('dingtalk_webhook'):
                self.send_dingtalk_alert(config['dingtalk_webhook'], alert_data)
            
            if config.get('email') and config.get('smtp'):
                self.send_email_alert(config['smtp'], config['email']['recipients'], alert_data)

# 使用示例
if __name__ == "__main__":
    config = {
        'dingtalk_webhook': 'https://oapi.dingtalk.com/robot/send?access_token=xxx',
        'email': {
            'recipients': ['admin@example.com', 'dba@example.com']
        },
        'smtp': {
            'server': 'smtp.example.com',
            'port': 587,
            'username': 'alerts@example.com',
            'password': 'password'
        }
    }
    
    notifier = AlertNotifier()
    notifier.run_alert_check(config)

🔧 运维自动化

1. 自动化部署

Docker化部署

# Dockerfile for ClickHouse with monitoring
FROM clickhouse/clickhouse-server:latest

# 安装监控工具
RUN apt-get update && apt-get install -y \
    prometheus-node-exporter \
    curl \
    netcat \
    htop \
    iotop

# 复制配置文件
COPY config.xml /etc/clickhouse-server/
COPY users.xml /etc/clickhouse-server/
COPY monitoring.xml /etc/clickhouse-server/config.d/

# 复制启动脚本
COPY start-with-monitoring.sh /start-with-monitoring.sh
RUN chmod +x /start-with-monitoring.sh

# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD clickhouse-client --query="SELECT 1" || exit 1

EXPOSE 8123 9000 9009 9100

CMD ["/start-with-monitoring.sh"]
#!/bin/bash
# start-with-monitoring.sh
set -e

# 启动node exporter
prometheus-node-exporter --web.listen-address=":9100" &

# 启动ClickHouse服务器
/entrypoint.sh &

# 等待ClickHouse启动
until clickhouse-client --query="SELECT 1" >/dev/null 2>&1; do
    echo "等待ClickHouse启动..."
    sleep 2
done

echo "ClickHouse已启动,开始监控..."

# 保持容器运行
wait

Kubernetes部署

# clickhouse-deployment.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: clickhouse
  labels:
    app: clickhouse
spec:
  serviceName: clickhouse-headless
  replicas: 3
  selector:
    matchLabels:
      app: clickhouse
  template:
    metadata:
      labels:
        app: clickhouse
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9363"
    spec:
      containers:
      - name: clickhouse
        image: clickhouse/clickhouse-server:latest
        ports:
        - containerPort: 8123
          name: http
        - containerPort: 9000
          name: native
        - containerPort: 9009
          name: interserver
        env:
        - name: CLICKHOUSE_DB
          value: "default"
        - name: CLICKHOUSE_USER
          value: "default"
        - name: CLICKHOUSE_PASSWORD
          valueFrom:
            secretKeyRef:
              name: clickhouse-secret
              key: password
        volumeMounts:
        - name: clickhouse-data
          mountPath: /var/lib/clickhouse
        - name: clickhouse-config
          mountPath: /etc/clickhouse-server/config.d
        resources:
          requests:
            memory: "4Gi"
            cpu: "2"
          limits:
            memory: "8Gi"
            cpu: "4"
        livenessProbe:
          exec:
            command:
            - clickhouse-client
            - --query=SELECT 1
          initialDelaySeconds: 30
          periodSeconds: 30
        readinessProbe:
          exec:
            command:
            - clickhouse-client
            - --query=SELECT 1
          initialDelaySeconds: 5
          periodSeconds: 10
      
      - name: clickhouse-exporter
        image: f1yegor/clickhouse-exporter:latest
        ports:
        - containerPort: 9363
          name: metrics
        env:
        - name: CLICKHOUSE_URL
          value: "http://localhost:8123"
        resources:
          requests:
            memory: "64Mi"
            cpu: "100m"
          limits:
            memory: "128Mi"
            cpu: "200m"
            
      volumes:
      - name: clickhouse-config
        configMap:
          name: clickhouse-config
          
  volumeClaimTemplates:
  - metadata:
      name: clickhouse-data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 100Gi
      storageClassName: fast-ssd

---
apiVersion: v1
kind: Service
metadata:
  name: clickhouse-headless
spec:
  clusterIP: None
  selector:
    app: clickhouse
  ports:
  - port: 8123
    name: http
  - port: 9000
    name: native

---
apiVersion: v1
kind: Service  
metadata:
  name: clickhouse-service
spec:
  selector:
    app: clickhouse
  ports:
  - port: 8123
    targetPort: 8123
    name: http
  - port: 9000
    targetPort: 9000
    name: native
  type: LoadBalancer

2. 自动化运维脚本

日常维护脚本

#!/bin/bash
# clickhouse-maintenance.sh - ClickHouse日常维护脚本

set -e

# 配置参数
CLICKHOUSE_HOST="localhost"
CLICKHOUSE_PORT="9000"
BACKUP_DIR="/backup/clickhouse"
LOG_FILE="/var/log/clickhouse-maintenance.log"
ALERT_EMAIL="admin@example.com"

# 日志函数
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a $LOG_FILE
}

# 错误处理
error_exit() {
    log "ERROR: $1"
    echo "ClickHouse维护脚本出错: $1" | mail -s "ClickHouse维护告警" $ALERT_EMAIL
    exit 1
}

# 检查ClickHouse连接
check_clickhouse() {
    log "检查ClickHouse连接..."
    clickhouse-client --host $CLICKHOUSE_HOST --port $CLICKHOUSE_PORT --query="SELECT 1" >/dev/null 2>&1 || \
        error_exit "无法连接到ClickHouse服务器"
    log "ClickHouse连接正常"
}

# 清理查询日志
cleanup_query_logs() {
    log "清理查询日志..."
    clickhouse-client --host $CLICKHOUSE_HOST --port $CLICKHOUSE_PORT --multiquery --query="
        ALTER TABLE system.query_log DELETE WHERE event_time < now() - INTERVAL 30 DAY;
        ALTER TABLE system.part_log DELETE WHERE event_time < now() - INTERVAL 30 DAY;
        ALTER TABLE system.trace_log DELETE WHERE event_time < now() - INTERVAL 7 DAY;
    " || error_exit "清理查询日志失败"
    log "查询日志清理完成"
}

# 优化表
optimize_tables() {
    log "开始优化表..."
    
 # 获取需要优化的表列表
    tables=$(clickhouse-client --host $CLICKHOUSE_HOST --port $CLICKHOUSE_PORT --query="
        SELECT DISTINCT table 
        FROM system.parts 
        WHERE active = 1 
          AND database = 'default'
          AND table IN (SELECT name FROM system.tables WHERE database = 'default' AND engine LIKE '%MergeTree%')
        GROUP BY table 
        HAVING count() > 10
    " --format=TSV)
    
    for table in $tables; do
        log "优化表: $table"
        clickhouse-client --host $CLICKHOUSE_HOST --port $CLICKHOUSE_PORT --query="
            OPTIMIZE TABLE $table FINAL;
        " || log "WARNING: 优化表 $table 失败"
    done
    
    log "表优化完成"
}

# 检查磁盘空间
check_disk_space() {
    log "检查磁盘空间..."
    
 # 检查数据目录磁盘使用率
    usage=$(df /var/lib/clickhouse | awk 'NR==2 {print $5}' | sed 's/%//')
    
    if [ $usage -gt 85 ]; then
        error_exit "磁盘使用率过高: ${usage}%"
    elif [ $usage -gt 75 ]; then
        log "WARNING: 磁盘使用率较高: ${usage}%"
    else
        log "磁盘空间正常: ${usage}%"
    fi
}

# 检查副本状态
check_replicas() {
    log "检查副本状态..."
    
    readonly_replicas=$(clickhouse-client --host $CLICKHOUSE_HOST --port $CLICKHOUSE_PORT --query="
        SELECT count() FROM system.replicas WHERE is_readonly = 1
    " --format=TSV)
    
    if [ $readonly_replicas -gt 0 ]; then
        log "WARNING: 发现 $readonly_replicas 个只读副本"
        
 # 尝试同步副本
        clickhouse-client --host $CLICKHOUSE_HOST --port $CLICKHOUSE_PORT --query="
            SELECT 'SYSTEM SYNC REPLICA ' || database || '.' || table || ';'
            FROM system.replicas 
            WHERE is_readonly = 1
        " --format=TSV | while read sync_cmd; do
            log "执行同步命令: $sync_cmd"
            clickhouse-client --host $CLICKHOUSE_HOST --port $CLICKHOUSE_PORT --query="$sync_cmd" || \
                log "WARNING: 同步副本失败: $sync_cmd"
        done
    else
        log "所有副本状态正常"
    fi
}

# 备份系统配置
backup_configs() {
    log "备份系统配置..."
    
    backup_date=$(date +%Y%m%d_%H%M%S)
    config_backup_dir="$BACKUP_DIR/configs/$backup_date"
    
    mkdir -p $config_backup_dir
    
 # 备份配置文件
    cp -r /etc/clickhouse-server/* $config_backup_dir/ 2>/dev/null || \
        log "WARNING: 配置文件备份失败"
    
 # 压缩备份
    tar -czf $config_backup_dir.tar.gz -C $BACKUP_DIR/configs $backup_date
    rm -rf $config_backup_dir
    
    log "配置备份完成: $config_backup_dir.tar.gz"
}

# 性能报告
generate_performance_report() {
    log "生成性能报告..."
    
    report_file="$BACKUP_DIR/reports/performance_$(date +%Y%m%d).txt"
    mkdir -p $(dirname $report_file)
    
    {
        echo "ClickHouse性能报告 - $(date)"
        echo "=================================="
        echo "系统信息:"
        clickhouse-client --host $CLICKHOUSE_HOST --port $CLICKHOUSE_PORT --query="
            SELECT 
                'ClickHouse版本' as metric, 
                version() as value
            UNION ALL
            SELECT 
                '运行时间', 
                formatReadableTimeDelta(uptime())
            UNION ALL
            SELECT 
                '总查询数', 
                toString(sum(ProfileEvent_Query))
            FROM system.metric_log 
            WHERE event_time >= today()
        " --format=PrettyCompact
        
        echo "查询性能统计 (最近24小时):"
        clickhouse-client --host $CLICKHOUSE_HOST --port $CLICKHOUSE_PORT --query="
            SELECT 
                type,
                count() as queries,
                round(avg(query_duration_ms), 2) as avg_duration_ms,
                round(quantile(0.95)(query_duration_ms), 2) as p95_duration_ms,
                formatReadableSize(sum(read_bytes)) as total_read
            FROM system.query_log 
            WHERE event_time >= now() - INTERVAL 24 HOUR
            GROUP BY type 
            ORDER BY queries DESC
        " --format=PrettyCompact
        
        echo "资源使用情况:"
        clickhouse-client --host $CLICKHOUSE_HOST --port $CLICKHOUSE_PORT --query="
            SELECT 
                metric,
                formatReadableSize(value) as value
            FROM system.asynchronous_metrics 
            WHERE metric IN ('MemoryDataAndStack', 'MemoryVirtual')
            UNION ALL
            SELECT 
                name as metric,
                formatReadableSize(total_space - free_space) || ' / ' || formatReadableSize(total_space) as value
            FROM system.disks
        " --format=PrettyCompact
        
    } > $report_file
    
    log "性能报告已生成: $report_file"
}

# 主函数
main() {
    log "开始ClickHouse日常维护..."
    
    check_clickhouse
    check_disk_space
    check_replicas
    cleanup_query_logs
    optimize_tables
    backup_configs
    generate_performance_report
    
    log "ClickHouse日常维护完成"
}

# 执行主函数
main "$@"

监控数据收集脚本

# monitoring_collector.模型部署 - 监控数据收集器
import time
import json
import requests
from datetime import datetime
from clickhouse_driver import Client
import psutil
import threading
from queue import Queue

class MonitoringCollector:
    def __init__(self, clickhouse_host='localhost', prometheus_gateway=None):
        self.ch_client = Client(clickhouse_host)
        self.prometheus_gateway = prometheus_gateway
        self.metrics_queue = Queue()
        
    def collect_system_metrics(self):
        """收集系统指标"""
        metrics = {}
        
 # CPU使用率
        metrics['cpu_percent'] = psutil.cpu_percent(interval=1)
        metrics['cpu_count'] = psutil.cpu_count()
        
 # 内存使用
        memory = psutil.virtual_memory()
        metrics['memory_total'] = memory.total
        metrics['memory_used'] = memory.used
        metrics['memory_percent'] = memory.percent
        
 # 磁盘使用
        disk = psutil.disk_usage('/')
        metrics['disk_total'] = disk.total
        metrics['disk_used'] = disk.used
        metrics['disk_percent'] = (disk.used / disk.total) * 100
        
 # 网络I/O
        network = psutil.net_io_counters()
        metrics['network_bytes_sent'] = network.bytes_sent
        metrics['network_bytes_recv'] = network.bytes_recv
        
        return metrics
    
    def collect_clickhouse_metrics(self):
        """收集ClickHouse指标"""
        try:
 # 服务状态
            service_metrics = self.ch_client.execute("""
                SELECT 
                    'uptime' as metric,
                    toString(uptime()) as value
                UNION ALL
                SELECT 
                    'version',
                    version()
                UNION ALL
                SELECT 
                    'active_queries',
                    toString(count())
                FROM system.processes
                WHERE query != ''
            """)
            
 # 查询性能
            query_metrics = self.ch_client.execute("""
                SELECT 
                    'queries_per_minute' as metric,
                    toString(count()) as value
                FROM system.query_log
                WHERE event_time >= now() - INTERVAL 1 MINUTE
                  AND type = 'QueryFinish'
                UNION ALL
                SELECT 
                    'avg_query_duration_ms',
                    toString(round(avg(query_duration_ms), 2))
                FROM system.query_log
                WHERE event_time >= now() - INTERVAL 5 MINUTE
                  AND type = 'QueryFinish'
            """)
            
 # 副本状态
            replica_metrics = self.ch_client.execute("""
                SELECT 
                    'readonly_replicas' as metric,
                    toString(count()) as value
                FROM system.replicas
                WHERE is_readonly = 1
                UNION ALL
                SELECT 
                    'max_replica_queue_size',
                    toString(max(queue_size))
                FROM system.replicas
            """)
            
            return service_metrics + query_metrics + replica_metrics
            
        except Exception as e:
            print(f"收集ClickHouse指标失败: {e}")
            return []
    
    def store_metrics(self, metrics_data):
        """存储指标数据"""
        try:
 # 准备插入数据
            insert_data = []
            timestamp = datetime.now()
            
            for metric_name, metric_value in metrics_data.items():
                insert_data.append((
                    timestamp,
                    'system',
                    metric_name,
                    float(metric_value) if isinstance(metric_value, (int, float, str)) and str(metric_value).replace('.', '').isdigit() else 0,
                    'localhost',
                    {}
                ))
            
 # 插入到监控表
            self.ch_client.execute("""
                INSERT INTO monitoring_metrics 
                (timestamp, metric_category, metric_name, metric_value, host_name, labels)
                VALUES
            """, insert_data)
            
        except Exception as e:
            print(f"存储指标失败: {e}")
    
    def send_to_prometheus(self, metrics_data):
        """发送指标到Prometheus Gateway"""
        if not self.prometheus_gateway:
            return
            
        try:
            prometheus_metrics = []
            for metric_name, metric_value in metrics_data.items():
                if isinstance(metric_value, (int, float)):
                    prometheus_metrics.append(f"clickhouse_{metric_name} {metric_value}")
            
            metrics_text = '\n'.join(prometheus_metrics)
            
            response = requests.post(
                f"{self.prometheus_gateway}/metrics/job/clickhouse_monitoring",
                data=metrics_text,
                headers={'Content-Type': 'text/plain'}
            )
            
            if response.status_code != 200:
                print(f"发送到Prometheus失败: {response.status_code}")
                
        except Exception as e:
            print(f"发送到Prometheus失败: {e}")
    
    def collect_and_store(self):
        """收集并存储所有指标"""
 # 收集系统指标
        system_metrics = self.collect_system_metrics()
        
 # 收集ClickHouse指标
        ch_metrics = self.collect_clickhouse_metrics()
        ch_metrics_dict = {metric[0]: metric[1] for metric in ch_metrics}
        
 # 合并所有指标
        all_metrics = {**system_metrics, **ch_metrics_dict}
        
 # 存储指标
        self.store_metrics(all_metrics)
        
 # 发送到Prometheus
        self.send_to_prometheus(all_metrics)
        
        print(f"[{datetime.now()}] 收集了 {len(all_metrics)} 个指标")
    
    def run(self, interval=60):
        """运行监控收集器"""
        print(f"启动监控数据收集器,采集间隔: {interval}秒")
        
        while True:
            try:
                self.collect_and_store()
                time.sleep(interval)
            except KeyboardInterrupt:
                print("监控收集器已停止")
                break
            except Exception as e:
                print(f"收集过程出错: {e}")
                time.sleep(10)  # 出错后等待10秒再重试

if __name__ == "__main__":
 # 配置
    collector = MonitoringCollector(
        clickhouse_host='localhost',
        prometheus_gateway='http://prometheus-gateway:9091'
    )
    
 # 运行收集器
    collector.run(interval=30)  # 每30秒收集一次

📊 Grafana仪表板

1. 仪表板配置

ClickHouse概览仪表板

{
  "dashboard": {
    "id": null,
    "title": "ClickHouse监控概览",
    "tags": ["clickhouse", "monitoring"],
    "timezone": "browser",
    "panels": [
      {
        "id": 1,
        "title": "服务状态",
        "type": "stat",
        "targets": [
          {
            "expr": "up{job=\"clickhouse\"}",
            "legendFormat": "{{instance}}"
          }
        ],
        "fieldConfig": {
          "defaults": {
            "color": {
              "mode": "thresholds"
            },
            "thresholds": {
              "steps": [
                {"color": "red", "value": 0},
                {"color": "green", "value": 1}
              ]
            }
          }
        }
      },
      {
        "id": 2,
        "title": "查询QPS",
        "type": "graph",
        "targets": [
          {
            "expr": "rate(clickhouse_queries_total[5m])",
            "legendFormat": "QPS"
          }
        ]
      },
      {
        "id": 3,
        "title": "查询延迟",
        "type": "graph",
        "targets": [
          {
            "expr": "histogram_quantile(0.95, rate(clickhouse_query_duration_seconds_bucket[5m]))",
            "legendFormat": "P95"
          },
          {
            "expr": "histogram_quantile(0.99, rate(clickhouse_query_duration_seconds_bucket[5m]))",
            "legendFormat": "P99"
          }
        ]
      },
      {
        "id": 4,
        "title": "内存使用",
        "type": "graph",
        "targets": [
          {
            "expr": "clickhouse_memory_usage_bytes / clickhouse_memory_total_bytes * 100",
            "legendFormat": "内存使用率 (%)"
          }
        ]
      },
      {
        "id": 5,
        "title": "磁盘使用",
        "type": "graph",
        "targets": [
          {
            "expr": "(clickhouse_disk_total_bytes - clickhouse_disk_free_bytes) / clickhouse_disk_total_bytes * 100",
            "legendFormat": "磁盘使用率 (%)"
          }
        ]
      },
      {
        "id": 6,
        "title": "副本状态",
        "type": "table",
        "targets": [
          {
            "expr": "clickhouse_replica_queue_size",
            "legendFormat": "{{database}}.{{table}} - {{replica}}"
          }
        ]
      }
    ],
    "time": {
      "from": "now-1h",
      "to": "now"
    },
    "refresh": "30s"
  }
}

性能分析仪表板

{
  "dashboard": {
    "title": "ClickHouse性能分析",
    "panels": [
      {
        "title": "慢查询Top 10",
        "type": "table",
        "targets": [
          {
            "rawSql": "SELECT query_id, user, query_duration_ms, memory_usage, read_rows FROM system.query_log WHERE type = 'QueryFinish' AND query_duration_ms > 1000 ORDER BY query_duration_ms DESC LIMIT 10"
          }
        ]
      },
      {
        "title": "查询类型分布",
        "type": "piechart",
        "targets": [
          {
            "rawSql": "SELECT type, count() as count FROM system.query_log WHERE event_time >= now() - INTERVAL 1 HOUR GROUP BY type"
          }
        ]
      },
      {
        "title": "内存使用趋势",
        "type": "graph",
        "targets": [
          {
            "rawSql": "SELECT toStartOfMinute(event_time) as time, avg(memory_usage) as avg_memory FROM system.query_log WHERE event_time >= now() - INTERVAL 2 HOUR GROUP BY time ORDER BY time"
          }
        ]
      }
    ]
  }
}

🎯 运维最佳实践

1. 监控运维规范

info 📋 监控运维最佳实践

监控策略:

  • 分层监控:基础设施 → 系统 → 应用 → 业务
  • 指标收集:延迟、错误率、流量、饱和度
  • 告警设置:基于SLA和业务影响程度

运维流程:

  • 预防性维护:定期检查、性能调优
  • 故障响应:快速诊断、及时恢复
  • 变更管理:版本升级、配置变更

自动化程度:

  • 监控自动化:指标收集、告警通知
  • 部署自动化:CI/CD、滚动更新
  • 运维自动化:日常维护、故障恢复

2. 故障处理流程

-- 故障处理标准流程
CREATE TABLE incident_management (
    incident_id String,
    severity LowCardinality(String),
    status LowCardinality(String),
    title String,
    description String,
    affected_services Array(String),
    start_time DateTime,
    detection_time DateTime,
    resolution_time Nullable(DateTime),
    root_cause String,
    resolution_summary String,
    assignee String,
    created_by String,
    created_at DateTime DEFAULT now(),
    updated_at DateTime DEFAULT now()
) ENGINE = MergeTree()
ORDER BY (severity, start_time);

-- 故障记录教学示例:日期、人员、原因和处置时长不代表已核实事件,也不构成通用SLA
INSERT INTO incident_management (incident_id, severity, status, title, description, affected_services, start_time, detection_time, resolution_time, root_cause, resolution_summary, assignee, created_by, created_at) VALUES
    ('INC-001', 'Critical', 'Resolved', 'ClickHouse集群节点宕机', '节点ch-node-1无响应', ['clickhouse'], '2024-01-01 10:00:00', '2024-01-01 10:02:00', '2024-01-01 10:15:00', '硬件故障', '重启节点并迁移流量', 'admin', 'monitoring', now()),
    ('INC-002', 'High', 'Resolved', '查询性能异常', '查询响应时间超过30秒', ['clickhouse'], '2024-01-01 14:00:00', '2024-01-01 14:05:00', '2024-01-01 14:30:00', '大查询阻塞', '优化查询SQL并增加索引', 'dba', 'user_report', now());

-- SLA统计分析
SELECT 
    severity,
    count() as incident_count,
    avg(dateDiff('minute', start_time, detection_time)) as avg_detection_minutes,
    avg(dateDiff('minute', detection_time, resolution_time)) as avg_resolution_minutes,
    countIf(dateDiff('minute', detection_time, resolution_time) <= 30) / count() * 100 as sla_compliance_percent
FROM incident_management
WHERE start_time >= now() - INTERVAL 30 DAY
  AND resolution_time IS NOT NULL
GROUP BY severity
ORDER BY 
    CASE severity 
        WHEN 'Critical' THEN 1 
        WHEN 'High' THEN 2 
        WHEN 'Medium' THEN 3 
        ELSE 4 
    END;

🎯 监控运维总结

本章节介绍了以下监控运维设计与示例,完整落地仍需补齐配置并经过测试:

  1. 完整监控体系 - 多层监控、关键指标、实时告警
  2. 自动化运维 - 部署自动化、维护脚本、故障恢复
  3. 告警通知机制 - 智能告警、多渠道通知、告警收敛
  4. 可视化监控 - Grafana仪表板、性能分析、趋势预测
  5. 运维规范流程 - 最佳实践、故障管理、SLA监控

abstract 📚 下一步学习

完成本章节后,你可以结合测试环境进一步验证ClickHouse监控运维方案,接下来我们将学习ClickHouse实践案例,通过项目案例练习综合运用所学知识;是否具备生产运维能力仍需实际演练与评估。

🔗 相关资源


💡 记住:监控运维是系统稳定运行的基石。建立完善的监控体系、实现运维自动化,能够大大提升系统可靠性和运维效率。在生产环境中,预防胜于治疗,持续的监控和主动的维护是确保系统健康的关键!