80、数据库监控系统:指标采集(QPS/TPS/连接数)、告警阈值、Grafana集成

数据库监控这事儿,说实话,我早年吃过不少亏。那时候项目上线,数据库跑着跑着突然就挂了,查日志才发现QPS早就飙到天上去了,连接数也爆了。从那以后,我养成了一个习惯——任何生产环境的数据库,必须先搭好监控。今天咱们就聊聊,怎么用C语言写一个轻量级的数据库监控系统,把QPS、TPS、连接数这些核心指标抓出来,再配上告警和Grafana展示。

数据库监控系统核心架构 监控采集引擎 QPS 采集 TPS 采集 连接数采集 告警阈值判断 日志/文件输出 Grafana 集成 采集 → 判断 → 输出 → 可视化,形成完整闭环

一、指标采集:QPS、TPS、连接数

先说说QPS和TPS。QPS是每秒查询数,TPS是每秒事务数。这两个指标,说白了就是看数据库忙不忙。我一般用gettimeofday()配合计数器来实现,每秒钟统计一次差值。

连接数就更直接了——每次客户端连上来就+1,断开就-1。但有个坑:连接泄漏。我曾经遇到过一个服务,连接池没释放,连接数一路涨到数据库上限,直接拒绝服务。所以监控连接数,其实是在监控你的代码质量。

核心思路:用三个全局变量做计数器,一个定时器每秒触发一次,计算差值并记录。

// 监控指标结构体
typedef struct {
    long long qps;       // 每秒查询数
    long long tps;       // 每秒事务数
    int       conn_count; // 当前连接数
    int       conn_max;   // 历史最大连接数
    double    avg_latency_ms; // 平均延迟(毫秒)
} db_metrics_t;

// 全局计数器(实际项目中建议用原子操作)
static volatile long long g_query_count = 0;
static volatile long long g_trans_count = 0;
static volatile int       g_conn_count  = 0;

// 每次查询/事务时调用
void inc_query_count() {
    __sync_fetch_and_add(&g_query_count, 1);
}

void inc_trans_count() {
    __sync_fetch_and_add(&g_trans_count, 1);
}

// 连接建立/断开时调用
void on_connect() {
    __sync_fetch_and_add(&g_conn_count, 1);
}

void on_disconnect() {
    __sync_fetch_and_add(&g_conn_count, -1);
}

小技巧:__sync_fetch_and_add做原子操作,比加锁轻量得多。多线程环境下不会出现计数错乱。

二、定时采集与阈值告警

采集不能只靠手动调用,得有个定时器。我习惯用setitimer()或者timerfd_create(),每秒触发一次。在回调函数里,把上一秒的计数差值算出来,就是QPS/TPS。

告警阈值这块,我踩过坑。一开始我设了固定值,比如QPS超过10000就告警。结果业务高峰期天天报警,运维都麻木了。后来我改成动态阈值——根据过去5分钟的平均值,超过2倍标准差才告警。这样误报少了很多。

// 定时采集回调(每秒执行一次)
void collect_metrics(int signum) {
    static long long last_query = 0;
    static long long last_trans = 0;
    
    long long cur_query = g_query_count;
    long long cur_trans = g_trans_count;
    
    db_metrics_t metrics;
    metrics.qps        = cur_query - last_query;
    metrics.tps        = cur_trans - last_trans;
    metrics.conn_count = g_conn_count;
    
    // 更新历史最大值
    static int max_conn = 0;
    if (metrics.conn_count > max_conn) {
        max_conn = metrics.conn_count;
    }
    metrics.conn_max = max_conn;
    
    // 告警判断
    if (metrics.qps > QPS_THRESHOLD) {
        log_alert("QPS 超过阈值: %lld > %d", metrics.qps, QPS_THRESHOLD);
    }
    if (metrics.conn_count > CONN_THRESHOLD) {
        log_alert("连接数超过阈值: %d > %d", metrics.conn_count, CONN_THRESHOLD);
    }
    
    // 写入日志或发送到Grafana
    write_metrics_to_file(&metrics);
    
    last_query = cur_query;
    last_trans = cur_trans;
}

注意:定时器回调里不要做耗时操作,比如写数据库、发网络请求。我一般只做计数和简单判断,把日志写入放到另一个线程里处理。

三、数据输出与Grafana集成

采集到的数据怎么给Grafana?常见的方式有两种:

  • 写日志文件:用fprintf()按固定格式写入,然后让Prometheus的node_exporter或者Grafana的日志插件去采集。
  • 直接暴露HTTP接口:用libmicrohttpd或者自己写个简单的socket监听,返回JSON格式的指标数据。

我个人更推荐第二种。为什么呢?因为日志文件有轮转问题,采集延迟也大。直接暴露HTTP接口,Grafana可以实时拉取,延迟在毫秒级。

// 输出JSON格式的指标数据(供Grafana拉取)
void metrics_to_json(char *buf, size_t size) {
    db_metrics_t m = get_current_metrics(); // 获取最新指标
    snprintf(buf, size,
        "{"
        "\"qps\":%lld,"
        "\"tps\":%lld,"
        "\"conn_count\":%d,"
        "\"conn_max\":%d,"
        "\"avg_latency_ms\":%.2f"
        "}",
        m.qps, m.tps, m.conn_count, m.conn_max, m.avg_latency_ms
    );
}

// 简单的HTTP响应处理
void handle_http_request(int client_fd) {
    char json[256];
    metrics_to_json(json, sizeof(json));
    
    char response[512];
    snprintf(response, sizeof(response),
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: application/json\r\n"
        "Content-Length: %zu\r\n"
        "\r\n"
        "%s",
        strlen(json), json
    );
    write(client_fd, response, strlen(response));
    close(client_fd);
}

Grafana配置要点:

  • 数据源选择「Prometheus」或「JSON API」
  • 如果是JSON API,需要安装Grafana的JSON数据源插件
  • 面板类型选「Stat」或「Graph」,展示QPS/TPS/连接数
  • 告警规则可以在Grafana里配置,也可以直接在C代码里触发

四、避坑指南与实战经验

做监控系统,有几个坑我不得不提:

  1. 计数器溢出:QPS如果达到百万级,32位int很快会溢出。一定要用long long或者uint64_t
  2. 时间同步问题:如果你用多台机器采集,时间不同步会导致数据对不上。建议统一用NTP同步。
  3. 监控本身的开销:采集代码不能影响业务性能。我见过有人用printf打日志,结果监控线程把CPU吃满了。建议用异步写入。
  4. 告警风暴:阈值设置不合理,或者网络抖动,可能会触发大量告警。我一般加一个「静默期」——同一个指标5分钟内只告警一次。

我的习惯:在代码里加一个#define MONITOR_DEBUG 1开关,开发阶段打开可以看到详细日志,上线后关掉只保留关键告警。这样既方便调试,又不会影响性能。

最后说一句,监控系统不是搭完就完事了。你得定期看看告警日志,调整阈值。我每个月都会回顾一次监控数据,看看有没有异常模式。数据库监控,说白了就是给你的数据库装个「心电图」,平时看着正常,一旦波形不对,马上就能发现。


公众号:蓝海资料掘金营,微信deep3321