从零实现一个分布式监控:Prometheus的核心设计

前言

你有没有想过:在微服务架构中,你是怎么知道系统CPU飙高了、内存泄漏了、接口变慢了的?这些指标是怎么采集、存储、告警的?

Prometheus是云原生监控的事实标准,采用拉取模式采集指标。

今天我们从零实现一个分布式监控系统:

· 指标采集(Counter/Gauge/Histogram)

· 指标存储(时间序列数据库)

· 查询语言(PromQL简化版)

· 告警规则引擎

· 可视化(简单Dashboard)


一、监控系统核心原理

  1. 架构图

```

┌─────────────────────────────────────────────────────────────┐

│ 应用服务 │

│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │

│ │ 服务A │ │ 服务B │ │ 服务C │ │

│ │ 指标暴露 │ │ 指标暴露 │ │ 指标暴露 │ │

│ │ /metrics │ │ /metrics │ │ /metrics │ │

│ └─────────────┘ └─────────────┘ └─────────────┘ │

└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐

│ Prometheus │

│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │

│ │ 指标抓取 │→│ 时间序列 │→│ 告警评估 │ │

│ │ (Pull) │ │ 存储 │ │ (Alert) │ │

│ └─────────────┘ └─────────────┘ └─────────────┘ │

└─────────────────────────────────────────────────────────────┘

┌─────────────┐

│ Grafana │

│ (可视化) │

└─────────────┘

```

  1. 核心概念

概念 说明 示例

Counter 只增不减 请求总数

Gauge 可增可减 CPU使用率

Histogram 分布统计 请求延迟

Label 维度标签 service, endpoint, status


二、完整代码实现

  1. 基础数据结构

```c

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <pthread.h>

#include <time.h>

#include <errno.h>

#include <math.h>

#define MAX_METRIC_NAME 128

#define MAX_LABELS 8

#define MAX_LABEL_KEY 32

#define MAX_LABEL_VALUE 64

#define MAX_SERIES 10000

#define MAX_HISTOGRAM_BUCKETS 20

// 指标类型

typedef enum {

METRIC_COUNTER = 0,

METRIC_GAUGE,

METRIC_HISTOGRAM,

METRIC_SUMMARY

} metric_type_t;

// 标签

typedef struct label {

char keyMAX_LABEL_KEY;

char valueMAX_LABEL_VALUE;

} label_t;

// 指标值

typedef struct metric_value {

metric_type_t type;

char nameMAX_METRIC_NAME;

label_t labelsMAX_LABELS;

int label_count;

double value;

double sum;

double count;

double bucketsMAX_HISTOGRAM_BUCKETS;

double bucket_upperMAX_HISTOGRAM_BUCKETS;

int bucket_count;

time_t timestamp;

struct metric_value *next;

} metric_value_t;

// 时间序列

typedef struct time_series {

char metric_nameMAX_METRIC_NAME;

label_t labelsMAX_LABELS;

int label_count;

metric_value_t *values;

int value_count;

struct time_series *next;

} time_series_t;

// 告警规则

typedef struct alert_rule {

char name64;

char expr128;

double threshold;

char condition8; // >, <, >=, <=

int for_seconds;

char severity16;

char message256;

struct alert_rule *next;

} alert_rule_t;

// 告警事件

typedef struct alert_event {

char rule_name64;

char status16; // firing, resolved

time_t start_time;

time_t end_time;

char value64;

struct alert_event *next;

} alert_event_t;

// Prometheus

typedef struct prometheus {

time_series_t *series;

alert_rule_t *alert_rules;

alert_event_t *alert_events;

int retention_days;

int scrape_interval_ms;

pthread_mutex_t mutex;

int running;

pthread_t scrape_thread;

pthread_t alert_thread;

} prometheus_t;

```

  1. 指标采集

```c

// 创建Prometheus

prometheus_t *prom_create(int scrape_interval_ms, int retention_days) {

prometheus_t *p = malloc(sizeof(prometheus_t));

memset(p, 0, sizeof(prometheus_t));

p->scrape_interval_ms = scrape_interval_ms;

p->retention_days = retention_days;

p->running = 1;

pthread_mutex_init(&p->mutex, NULL);

printf("Prometheus 启动,抓取间隔: %dms\n", scrape_interval_ms);

return p;

}

// 查找时间序列

time_series_t *find_series(prometheus_t *p, const char *name,

label_t *labels, int label_count) {

time_series_t *ts = p->series;

while (ts) {

if (strcmp(ts->metric_name, name) != 0) {

ts = ts->next;

continue;

}

if (ts->label_count != label_count) {

ts = ts->next;

continue;

}

int match = 1;

for (int i = 0; i < label_count; i++) {

if (strcmp(ts->labelsi.key, labelsi.key) != 0 ||

strcmp(ts->labelsi.value, labelsi.value) != 0) {

match = 0;

break;

}

}

if (match) return ts;

ts = ts->next;

}

// 创建新时间序列

ts = malloc(sizeof(time_series_t));

strcpy(ts->metric_name, name);

ts->label_count = label_count;

for (int i = 0; i < label_count && i < MAX_LABELS; i++) {

strcpy(ts->labelsi.key, labelsi.key);

strcpy(ts->labelsi.value, labelsi.value);

}

ts->values = NULL;

ts->value_count = 0;

ts->next = p->series;

p->series = ts;

return ts;

}

// 添加指标值

void prom_add_metric(prometheus_t *p, const char *name, metric_type_t type,

label_t *labels, int label_count, double value) {

pthread_mutex_lock(&p->mutex);

time_series_t *ts = find_series(p, name, labels, label_count);

// 限制历史数据量

if (ts->value_count >= 10000) {

metric_value_t *old = ts->values;

ts->values = old->next;

free(old);

ts->value_count--;

}

metric_value_t *mv = malloc(sizeof(metric_value_t));

mv->type = type;

strcpy(mv->name, name);

mv->label_count = label_count;

for (int i = 0; i < label_count; i++) {

strcpy(mv->labelsi.key, labelsi.key);

strcpy(mv->labelsi.value, labelsi.value);

}

mv->value = value;

mv->timestamp = time(NULL);

mv->next = ts->values;

ts->values = mv;

ts->value_count++;

pthread_mutex_unlock(&p->mutex);

}

// 抓取目标(模拟)

void prom_scrape(prometheus_t *p, const char *target) {

// 模拟从/metrics端点抓取指标

label_t labels2;

strcpy(labels0.key, "service");

strcpy(labels0.value, target);

strcpy(labels1.key, "env");

strcpy(labels1.value, "prod");

// 模拟CPU使用率

double cpu = 30 + (rand() % 60);

prom_add_metric(p, "cpu_usage", METRIC_GAUGE, labels, 2, cpu);

// 模拟请求总数

double req = 100 + rand() % 900;

prom_add_metric(p, "http_requests_total", METRIC_COUNTER, labels, 2, req);

// 模拟内存使用

double mem = 1024 + rand() % 4096;

prom_add_metric(p, "memory_usage_mb", METRIC_GAUGE, labels, 2, mem);

printf("Scrape %s: CPU=%.1f%%, 请求=%.0f, 内存=%.0fMB\n",

target, cpu, req, mem);

}

```

  1. 告警规则引擎

```c

// 添加告警规则

void prom_add_alert_rule(prometheus_t *p, const char *name, const char *metric,

const char *condition, double threshold,

int for_seconds, const char *severity,

const char *message) {

pthread_mutex_lock(&p->mutex);

alert_rule_t *rule = malloc(sizeof(alert_rule_t));

strcpy(rule->name, name);

strcpy(rule->expr, metric);

strcpy(rule->condition, condition);

rule->threshold = threshold;

rule->for_seconds = for_seconds;

strcpy(rule->severity, severity);

strcpy(rule->message, message);

rule->next = p->alert_rules;

p->alert_rules = rule;

pthread_mutex_unlock(&p->mutex);

printf("告警 规则: %s (%s %s %.0f)\n", name, metric, condition, threshold);

}

// 检查告警条件

int check_condition(double value, const char *condition, double threshold) {

if (strcmp(condition, ">") == 0) return value > threshold;

if (strcmp(condition, "<") == 0) return value < threshold;

if (strcmp(condition, ">=") == 0) return value >= threshold;

if (strcmp(condition, "<=") == 0) return value <= threshold;

return 0;

}

// 评估告警

void prom_evaluate_alerts(prometheus_t *p) {

pthread_mutex_lock(&p->mutex);

alert_rule_t *rule = p->alert_rules;

while (rule) {

// 查找指标

time_series_t *ts = p->series;

while (ts) {

if (strcmp(ts->metric_name, rule->expr) == 0) {

if (ts->values) {

double value = ts->values->value;

int triggered = check_condition(value, rule->condition, rule->threshold);

if (triggered) {

// 检查是否已有告警

alert_event_t *evt = p->alert_events;

int exists = 0;

while (evt) {

if (strcmp(evt->rule_name, rule->name) == 0 &&

strcmp(evt->status, "firing") == 0) {

exists = 1;

break;

}

evt = evt->next;

}

if (!exists) {

evt = malloc(sizeof(alert_event_t));

strcpy(evt->rule_name, rule->name);

strcpy(evt->status, "firing");

evt->start_time = time(NULL);

evt->end_time = 0;

sprintf(evt->value, "%.2f", value);

evt->next = p->alert_events;

p->alert_events = evt;

printf("告警 %s: %s (当前值: %.2f)\n",

rule->severity, rule->name, value);

}

} else {

// 关闭告警

alert_event_t *evt = p->alert_events;

while (evt) {

if (strcmp(evt->rule_name, rule->name) == 0 &&

strcmp(evt->status, "firing") == 0) {

strcpy(evt->status, "resolved");

evt->end_time = time(NULL);

printf("告警 %s 已恢复\n", rule->name);

}

evt = evt->next;

}

}

}

break;

}

ts = ts->next;

}

rule = rule->next;

}

pthread_mutex_unlock(&p->mutex);

}

```

  1. 查询语言(PromQL简化)

```c

// 查询指标

metric_value_t *prom_query(prometheus_t *p, const char *metric_name,

label_t *labels, int label_count) {

pthread_mutex_lock(&p->mutex);

time_series_t *ts = p->series;

while (ts) {

if (strcmp(ts->metric_name, metric_name) != 0) {

ts = ts->next;

continue;

}

if (ts->label_count != label_count) {

ts = ts->next;

continue;

}

int match = 1;

for (int i = 0; i < label_count; i++) {

if (strcmp(ts->labelsi.key, labelsi.key) != 0 ||

strcmp(ts->labelsi.value, labelsi.value) != 0) {

match = 0;

break;

}

}

if (match && ts->values) {

pthread_mutex_unlock(&p->mutex);

return ts->values;

}

ts = ts->next;

}

pthread_mutex_unlock(&p->mutex);

return NULL;

}

// 聚合查询(rate/sum/avg)

double prom_query_rate(prometheus_t *p, const char *metric_name,

label_t *labels, int label_count) {

metric_value_t *current = prom_query(p, metric_name, labels, label_count);

if (!current || !current->next) return 0;

metric_value_t *prev = current->next;

double rate = (current->value - prev->value) /

(current->timestamp - prev->timestamp);

return rate * 1000; // 每秒速率

}

```

  1. 测试代码

```c

void test_prometheus() {

printf("=== Prometheus监控测试 ===\n\n");

prometheus_t *p = prom_create(5000, 7);

// 添加告警规则

prom_add_alert_rule(p, "CPU过高", "cpu_usage", ">", 80, 30,

"critical", "CPU使用率超过80%%");

prom_add_alert_rule(p, "内存不足", "memory_usage_mb", ">", 3000, 60,

"warning", "内存使用超过3GB");

// 模拟抓取10次

for (int i = 0; i < 10; i++) {

prom_scrape(p, "user-service");

prom_scrape(p, "order-service");

prom_scrape(p, "payment-service");

prom_evaluate_alerts(p);

printf("---\n");

usleep(100000);

}

// 查询测试

printf("\n=== 查询测试 ===\n");

label_t labels2;

strcpy(labels0.key, "service");

strcpy(labels0.value, "user-service");

strcpy(labels1.key, "env");

strcpy(labels1.value, "prod");

metric_value_t *cpu = prom_query(p, "cpu_usage", labels, 2);

if (cpu) {

printf("user-service CPU: %.2f%%\n", cpu->value);

}

printf("\n活跃告警:\n");

alert_event_t *evt = p->alert_events;

while (evt) {

if (strcmp(evt->status, "firing") == 0) {

printf(" %s: %s (值: %s)\n", evt->rule_name, evt->status, evt->value);

}

evt = evt->next;

}

free(p);

}

int main() {

srand(time(NULL));

test_prometheus();

return 0;

}

```


三、编译和运行

```bash

gcc -o prometheus prometheus.c -lpthread -lm

./prometheus

```


四、Prometheus vs 本实现

特性 本实现 Prometheus

指标采集 ✅ Pull ✅ Pull

时间序列 ✅ ✅ TSDB

告警规则 ✅ ✅ Alertmanager

查询语言 ✅ 简化 ✅ PromQL

服务发现 ❌ ✅

可视化 ❌ ✅ Grafana


五、总结

通过这篇文章,你学会了:

· 监控系统核心原理(Pull模型)

· 指标类型(Counter/Gauge/Histogram)

· 时间序列存储

· 告警规则引擎

· 查询语言基础

Prometheus是云原生监控的事实标准。掌握它,你就理解了监控系统的核心设计。

下一篇预告:《从零实现一个分布式追踪:Jaeger的核心设计》


评论区分享一下你用Prometheus监控过什么系统~

相关推荐
An_s1 小时前
c++对接pdfium(一)win系统篇
开发语言·c++
众少成多积小致巨2 小时前
C++ 规范参考(上)
c++
一拳一个呆瓜5 小时前
【STL】iostream 编程:使用提取运算符
c++·stl
我不是懒洋洋6 小时前
从零实现一个分布式日志平台:ELK的核心设计
c++
神仙别闹6 小时前
基于QT(C++)实现Windows 自启动项查看和分析
c++·windows·qt
WWTYYDS_6667 小时前
ProtoBuf超详细使用教程
c++
野生风长7 小时前
c++类和对象(this指针,重载operator,习题总结)
java·开发语言·c++
雪的季节7 小时前
Python「假多态」与 C++「真多态」的核心区别
开发语言·c++
zmzb01037 小时前
C++课后习题训练记录Day166
开发语言·c++