全栈之路5---数据建模与告警实现

前情提要

前面我们讲到了如何实时、安全、可扩展地解决底层通信和数据采集的问题。agent 能上报数据了,server 能转发了,db_service 能入库了。但这一堆原始数据躺在表里,不加工、不告警、不按人分权,就是数字堆砌。

这一篇讲三件事:怎么存、怎么判、怎么看。

一、数据建模:不追时髦,追可读、易于理解和维护

1.1 在这一层为什么不用"监控项"抽象

传统那套  {__name__="cpu_usage", instance="A", job="B"}  的 label 模型很强大,但对我来说过度抽象了:

问题

我的场景

查询写 PromQL,学习曲线陡

运维同事要直接看 SQL

时序库(TSDB)优化针对高基数

就几百台机器,PostgreSQL 够扛

标签自由组合,容易失控

我要的是"磁盘表就是磁盘表,进程表就是进程表"

1.2 三元组:CM / PM / FM

传统网管的三层划分,我直接拿来用:

层级

含义

表举例

CM (Configuration Management)

配置数据,慢变

`cm_hosts`, `cm_alarm_threshold_default`, `cm_alarm_threshold_host`

PM (Performance Management)

性能数据,时序插入

`pm_disk_monitor`, `pm_process_status_min`, `pm_resource_stats_min`

FM (Fault Management)

告警数据,状态驱动

`fm_alarm_data`, `fm_alarm_notify`

关键设计:CM 驱动 PM 和 FM。主机注册时,CM 生成告警门限配置;PM 插入时,触发器读 CM 门限,决定是否写 FM。

1.3 面向数据权限编程,不同的人管理不同的主机

前台呈现基于ruoyi框架二次开发,该框架支持面向数据切面编程,所有数据根据hostid挂接,而hostid根据部门和用户id挂接sys_相关表,实现权限功能对接。详见第四部分权限模型和第六部分数据库表关系图。

二、核心表结构

2.1 CM 层:主机与门限

sql

-- 主机表:agent 注册时插入或更新

CREATE TABLE cm_hosts (

    hostid          SERIAL PRIMARY KEY,     -- 与 server 分配的 hostid 对齐

    ip              INET NOT NULL UNIQUE,

    hostname        VARCHAR(128),

    os_type         VARCHAR(32),            -- Linux/Windows/AIX

    agent_version   VARCHAR(16),

    dept_id         INT REFERENCES sys_dept, -- 关联部门,权限控制用

    status          INT DEFAULT 1,          -- 0离线 1在线 2未注册

    last_register   TIMESTAMP DEFAULT NOW(),

    last_heartbeat  TIMESTAMP

);

-- 默认告警门限:全局模板

CREATE TABLE cm_alarm_threshold_default (

    threshold_id    SERIAL PRIMARY KEY,

    metric_type     VARCHAR(32) NOT NULL,   -- 'cpu'/'mem'/'disk'/'proc'

    metric_name     VARCHAR(64),            -- 具体指标,如 'root_usage'

    condition_op    VARCHAR(8) DEFAULT '>', -- '>' '<' '=' '>=' '<='

    threshold_value NUMERIC(10,2),          -- 门限值

    severity        INT DEFAULT 2,          -- 1紧急 2重要 3一般 4提示

    duration_sec    INT DEFAULT 60,         -- 持续多久才告警

    enabled         BOOLEAN DEFAULT TRUE

);

-- 主机级告警门限:从默认拷贝,可个性化覆盖

CREATE TABLE cm_alarm_threshold_host (

    host_threshold_id SERIAL PRIMARY KEY,

    hostid            INT NOT NULL REFERENCES cm_hosts,

    threshold_id      INT REFERENCES cm_alarm_threshold_default,

    metric_type       VARCHAR(32),

    metric_name       VARCHAR(64),

    condition_op      VARCHAR(8),

    threshold_value   NUMERIC(10,2),

    severity          INT,

    duration_sec      INT,

    enabled           BOOLEAN DEFAULT TRUE,

    UNIQUE(hostid, metric_type, metric_name)

);

2.2 PM 层:三类时序数据

sql

-- 磁盘监控:定时采集 mount 点使用率

CREATE TABLE pm_disk_monitor (

    id          BIGSERIAL PRIMARY KEY,

    hostid      INT NOT NULL REFERENCES cm_hosts,

    clock       TIMESTAMP NOT NULL,         -- 采集时间

    mount_point VARCHAR(64),                -- / /home /var

    total_kb    BIGINT,

    used_kb     BIGINT,

    usage_pct   NUMERIC(5,2),               -- 使用率,触发器读这个

    inode_pct   NUMERIC(5,2)

);

-- 进程状态:agent 执行脚本返回的离散值

CREATE TABLE pm_process_status_min (

    id      BIGSERIAL PRIMARY KEY,

    hostid  INT NOT NULL,

    clock   TIMESTAMP NOT NULL,

    name    VARCHAR(128),                   -- 进程/脚本名

    value   INT,                            -- 退出码或状态值

    taskid  INT,                            -- 关联 admin 下发的任务

    cmd_id  INT                             -- 关联协议层 cmd_id

);

-- 资源统计:CPU/Mem 综合指标

CREATE TABLE pm_resource_stats_min (

    id          BIGSERIAL PRIMARY KEY,

    hostid      INT NOT NULL,

    clock       TIMESTAMP NOT NULL,

    cpu_pct     NUMERIC(5,2),

    mem_pct     NUMERIC(5,2),

    load_1m     NUMERIC(6,2),

    disk_io_wait NUMERIC(5,2)

);

2.3 FM 层:告警生命周期

sql

-- 告警数据:一条记录代表一个"未恢复"的告警

CREATE TABLE fm_alarm_data (

    alarm_id        BIGSERIAL PRIMARY KEY,

    hostid          INT NOT NULL REFERENCES cm_hosts,

    metric_type     VARCHAR(32),

    metric_name     VARCHAR(64),

    alarm_value     NUMERIC(10,2),          -- 触发时的实际值

    threshold_value NUMERIC(10,2),          -- 门限值

    severity        INT,

    status          INT DEFAULT 0,          -- 0活跃 1已恢复 2已确认 3已屏蔽

    first_time      TIMESTAMP DEFAULT NOW(), -- 首次触发

    last_time       TIMESTAMP DEFAULT NOW(), -- 最近更新

    recover_time    TIMESTAMP,

    duration_sec    INT DEFAULT 0,          -- 累计持续时间

    count           INT DEFAULT 1,          -- 重复次数

    notify_status   INT DEFAULT 0           -- 0未通知 1已通知 2通知失败

);

-- 告警通知:记录发送渠道和结果

CREATE TABLE fm_alarm_notify (

    notify_id   BIGSERIAL PRIMARY KEY,

    alarm_id    BIGINT REFERENCES fm_alarm_data,

    notify_type VARCHAR(16),                -- 'sms'/'email'/'webhook'

    notify_to   VARCHAR(256),

    content     TEXT,

    send_time   TIMESTAMP,

    status      INT DEFAULT 0               -- 0待发送 1成功 2失败

);

三、触发器:数据库里的"隐形代码"

不用应用层轮询判告警,用 PostgreSQL 触发器,入库即判定。

3.1 主机注册时:拷贝默认门限

sql

-- cm_hosts 插入触发器:自动生成主机级门限

CREATE OR REPLACE FUNCTION fn_init_host_threshold()

RETURNS TRIGGER AS $$

BEGIN

    INSERT INTO cm_alarm_threshold_host

        (hostid, threshold_id, metric_type, metric_name,

         condition_op, threshold_value, severity, duration_sec)

    SELECT

        NEW.hostid, threshold_id, metric_type, metric_name,

        condition_op, threshold_value, severity, duration_sec

    FROM cm_alarm_threshold_default

    WHERE enabled = TRUE;

    

    RETURN NEW;

END;

$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_host_register

AFTER INSERT ON cm_hosts

FOR EACH ROW

EXECUTE FUNCTION fn_init_host_threshold();

3.2 性能数据插入时:判定告警

sql

-- pm_disk_monitor 插入触发器:检查磁盘使用率

CREATE OR REPLACE FUNCTION fn_check_disk_alarm()

RETURNS TRIGGER AS $$

DECLARE

    v_threshold RECORD;

    v_alarm_id BIGINT;

BEGIN

    -- 查该主机该 mount 点的门限

    SELECT * INTO v_threshold

    FROM cm_alarm_threshold_host

    WHERE hostid = NEW.hostid

      AND metric_type = 'disk'

      AND metric_name = NEW.mount_point

      AND enabled = TRUE;

    

    IF NOT FOUND THEN

        RETURN NEW;  -- 无门限,不告警

    END IF;

    

    -- 判定:使用率 > 门限?

    IF NEW.usage_pct > v_threshold.threshold_value THEN

        -- 查是否已有活跃告警

        SELECT alarm_id INTO v_alarm_id

        FROM fm_alarm_data

        WHERE hostid = NEW.hostid

          AND metric_type = 'disk'

          AND metric_name = NEW.mount_point

          AND status = 0;  -- 活跃

        

        IF FOUND THEN

            -- 更新现有告警:次数+1,时间刷新

            UPDATE fm_alarm_data

            SET last_time = NEW.clock,

                duration_sec = EXTRACT(EPOCH FROM (NEW.clock - first_time)),

                count = count + 1

            WHERE alarm_id = v_alarm_id;

        ELSE

            -- 新建告警

            INSERT INTO fm_alarm_data

                (hostid, metric_type, metric_name, alarm_value,

                 threshold_value, severity, first_time, last_time)

            VALUES

                (NEW.hostid, 'disk', NEW.mount_point, NEW.usage_pct,

                 v_threshold.threshold_value, v_threshold.severity,

                 NEW.clock, NEW.clock);

        END IF;

    ELSE

        -- 使用率回落,恢复告警

        UPDATE fm_alarm_data

        SET status = 1,

            recover_time = NEW.clock,

            duration_sec = EXTRACT(EPOCH FROM (NEW.clock - first_time))

        WHERE hostid = NEW.hostid

          AND metric_type = 'disk'

          AND metric_name = NEW.mount_point

          AND status = 0;

    END IF;

    

    RETURN NEW;

END;

$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_disk_alarm

AFTER INSERT ON pm_disk_monitor

FOR EACH ROW

EXECUTE FUNCTION fn_check_disk_alarm();

3.3 类似触发器:进程状态、资源统计

触发器配置示例:

 pm_process_status_min  和  pm_resource_stats_min  各有一个触发器,逻辑结构相同,只是判定字段不同:

进程: value  与门限比较(如  value != 0  表示异常)

CPU/Mem: cpu_pct  /  mem_pct  与门限比较

cm_hosts INSERT ──► trg_host_register ──► 生成 cm_alarm_threshold_host

pm_disk_monitor INSERT ──► trg_disk_alarm ──► 读 cm_alarm_threshold_host

                                      ├──► 超门限 + 无活跃告警 ──► INSERT fm_alarm_data

                                      ├──► 超门限 + 有活跃告警 ──► UPDATE fm_alarm_data

                                      └──► 未超门限 + 有活跃告警 ──► 恢复 UPDATE

pm_process_status_min INSERT ──► trg_proc_alarm ──► 类似结构

pm_resource_stats_min INSERT ──► trg_resource_alarm ──► 类似结构

四、权限模型:主机归部门,部门归人

基于 RuoYi 框架的  sys_user  /  sys_dept  /  sys_role  表,不做大改,只加关联:

sys_dept (部门表)

    └── dept_id = 1  "运维一部"

            └── sys_user (用户表)

                    └── user_id = 101  "张三"

                            └── cm_hosts (主机表)

                                    └── hostid = 1001, dept_id = 1

数据切面(AOP):RuoYi 的  @DataScope  注解,自动给 SQL 加  WHERE dept_id IN (...)  条件。

java

// RuoYi 控制器示例

@PreAuthorize("@ss.hasPermi('monitor:host:list')")

@DataScope(deptAlias = "h")  // 自动注入当前用户可见的 dept_id 列表

@GetMapping("/list")

public TableDataInfo list(CmHost host) {

    // 最终 SQL: SELECT * FROM cm_hosts h WHERE h.dept_id IN (1, 2, 3)

    List<CmHost> list = hostService.selectHostList(host);

    return getDataTable(list);

}

契合点:传统企业的行政管理方式——"我管哪个部门,就看哪个部门的主机"。不是标签自由组合,是组织树映射。

五、处理流程总览

1. Agent 注册 ──► Server ──► db_service INSERT cm_hosts

                                    │

                                    ▼

                              触发器 trg_host_register

                                    │

                                    ▼

                              生成 cm_alarm_threshold_host(从默认拷贝)

2. Agent 定时上报 ──► Server ──► db_service INSERT pm_xxx

                                    │

                                    ▼

                              触发器 trg_xxx_alarm

                                    │

                                    ├──► 超门限 ──► INSERT/UPDATE fm_alarm_data

                                    │                    │

                                    │                    ▼

                                    │              触发器 trg_alarm_notify

                                    │                    │

                                    │                    ▼

                                    │              生成 fm_alarm_notify

                                    │                    │

                                    │                    ▼

                                    │              Python notify_worker 发送

                                    │

                                    └──► 未超门限 ──► 恢复 UPDATE fm_alarm_data

3. 用户登录 Web ──► RuoYi 鉴权 ──► 查 sys_user_dept ──► 数据切面过滤

                                    │

                                    ▼

                              可见 cm_hosts / pm_xxx / fm_alarm_data

六、数据库表关系图

七、小结

层面

设计要点

数据建模

CM/PM/FM 三元组,专用表替代抽象监控项

告警判定

触发器入库即判,零延迟,零额外组件

通知发送

数据库生成任务,Python 轮询执行

权限控制

RuoYi 数据切面,部门树映射主机

代价

触发器调试难、隐式耦合、未来规模可能存在瓶颈

感悟:这一层的原则也是需要设计先行、流程清晰,所幸AI能帮忙做很多事。

八、预告

下一篇:《全栈之路6---Web 集成与呈现》,RuoYi 二次开发、ECharts 可视化、AI 辅助调测,收尾

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值