3. CMDB 数据模型设计:设备信息模型、资产字段定义、数据关系设计、API 接口设计

设备注册流程跑通了,数据往哪儿存?怎么存?这就是 CMDB 数据模型要解决的问题。我个人习惯把这一步叫做「地基工程」——地基没打好,后面盖多少层楼都得塌。今天咱们就聊聊这个地基怎么打。

3.1 设备信息模型:核心实体与属性

设备信息模型说白了就是「一台设备长什么样」。我在项目中遇到过不少团队,上来就建了 200 多个字段,结果录入率不到 30%。嗯,这里要注意:字段不是越多越好,够用就行。

我建议把设备信息模型分成三层:

  • 基础层:设备唯一标识、类型、品牌、型号、序列号、资产编号。这些字段是「身份证」,缺一不可。
  • 运行层:IP 地址、MAC 地址、操作系统、内核版本、CPU/内存/磁盘规格。这些字段决定了设备能干什么活。
  • 管理层:所属机房、机柜位置、U 位、上架时间、维保到期日、负责人。这些字段用于日常运维和资产管理。

核心原则:基础层字段由注册流程自动填充,运行层字段通过 Agent 采集,管理层字段由人工或工单系统维护。三层数据来源不同,更新频率也不同。

举个例子,一台服务器的序列号是出厂就定死的,你不可能改它。但 IP 地址可能因为网络调整而变化,维保到期日更是每年都得更新。所以设计模型时,一定要把字段的「数据源」和「变更频率」考虑进去。

3.2 资产字段定义:类型、约束与默认值

字段定义这事儿,看着简单,坑其实不少。我曾经见过一个 CMDB,把「内存大小」定义成字符串类型,结果有人填了「16G」,有人填了「16 GB」,还有人填了「16384 MB」——查询的时候直接崩溃。

我建议的字段定义规范如下:

字段名 数据类型 约束 默认值 说明
device_id VARCHAR(64) NOT NULL, UNIQUE 自动生成 设备唯一标识,UUID 格式
asset_number VARCHAR(32) NOT NULL, UNIQUE 自动生成 资产编号,规则:ASSET-{日期}-{序号}
device_type ENUM NOT NULL server 可选值:server, switch, router, firewall, storage
memory_size INT NOT NULL 0 单位:GB,禁止填字符串
ip_address VARCHAR(15) NULLABLE NULL IPv4 格式,注册时可能未分配
status ENUM NOT NULL pending 可选值:pending, active, maintenance, retired

小技巧:枚举类型的字段,一定要在代码里做校验,别指望前端帮你过滤。我见过有人直接调 API 传了个「ACTIVE」进来,数据库里存的是小写「active」,查了半天才发现是大小写问题。

3.3 数据关系设计:设备、资产、位置、维保

设备不是孤立存在的。一台服务器放在哪个机房、哪个机柜、哪个 U 位?它属于哪个业务线?维保合同是哪家供应商的?这些关系如果不在模型里体现,后面查起来就是一场灾难。

我常用的关系设计模式是「星型模型」:设备表作为中心,其他表通过外键关联。

-- 设备主表
CREATE TABLE device (
    device_id     VARCHAR(64) PRIMARY KEY,
    asset_number  VARCHAR(32) NOT NULL UNIQUE,
    device_type   ENUM('server','switch','router','firewall','storage') NOT NULL,
    status        ENUM('pending','active','maintenance','retired') NOT NULL DEFAULT 'pending',
    created_at    DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at    DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- 位置表
CREATE TABLE device_location (
    device_id     VARCHAR(64) PRIMARY KEY,
    data_center   VARCHAR(64) NOT NULL,
    cabinet       VARCHAR(32) NOT NULL,
    u_position    INT NOT NULL,
    FOREIGN KEY (device_id) REFERENCES device(device_id)
);

-- 维保表
CREATE TABLE device_warranty (
    device_id     VARCHAR(64) PRIMARY KEY,
    vendor        VARCHAR(64) NOT NULL,
    start_date    DATE NOT NULL,
    end_date      DATE NOT NULL,
    contract_no   VARCHAR(64),
    FOREIGN KEY (device_id) REFERENCES device(device_id)
);

你想想看,如果把这些关系都塞到一张表里,字段会膨胀到 50 多个,而且很多字段是空的。拆成多张表,每张表只存自己关心的数据,查询和维护都清爽很多。

避坑指南:我曾经把维保信息直接放在设备表里,结果一台设备换了三次维保供应商,每次都得改主表。后来拆成维保子表,每次新增一条记录,主表只保留当前有效的维保 ID。这个教训让我明白了「主表只存不变或低频变化的数据」这个道理。

3.4 API 接口设计:注册、查询、更新、同步

模型设计好了,得通过 API 暴露出去。我习惯把 API 分成四类:

  • 注册接口:POST /api/v1/devices — 接收设备注册请求,自动生成 device_id 和 asset_number,写入设备主表。
  • 查询接口:GET /api/v1/devices/{id} — 返回设备完整信息,包括位置、维保、关联业务线等。
  • 更新接口:PATCH /api/v1/devices/{id} — 只更新传入的字段,不要求全量提交。
  • 同步接口:POST /api/v1/devices/sync — 用于批量同步,比如从供应商系统导入资产数据。

这里有个细节:注册接口和同步接口的幂等性一定要做好。我遇到过 Agent 重复上报,结果同一个设备在 CMDB 里出现了两条记录。解决方案是在注册接口里加一个「去重逻辑」:根据序列号 + 设备类型做唯一索引,如果已存在则返回已有记录,不再新建。

// 注册接口伪代码
function registerDevice(request) {
    // 1. 去重检查
    existing = db.query("SELECT * FROM device WHERE serial_number = ? AND device_type = ?", 
                        [request.serial_number, request.device_type]);
    if (existing) {
        return { code: 200, message: "设备已存在", data: existing };
    }
    
    // 2. 生成唯一标识
    device_id = uuid();
    asset_number = generateAssetNumber();
    
    // 3. 写入数据库
    db.insert("INSERT INTO device (...) VALUES (...)", [...]);
    
    // 4. 返回结果
    return { code: 201, message: "注册成功", data: { device_id, asset_number } };
}

个人经验:API 的返回格式一定要统一。我见过一个项目,有的接口返回 {code: 0, msg: "ok"},有的返回 {status: "success", message: ""},前端对接的时候骂娘。统一用 {code: 200, message: "成功", data: {}} 这种格式,大家都省心。

3.5 数据模型全景图

说了这么多,咱们用一张图把整个数据模型串起来。这张图展示了设备注册流程中,数据从采集到存储的完整路径。

CMDB 数据模型全景图 设备主表 device_id, asset_number, type, status 位置信息表 机房、机柜、U位 维保信息表 供应商、起止日期 业务关联表 业务线、负责人 注册流程自动写入 Agent 定时采集 工单系统同步 设计要点 主表只存不变或低频变化字段 | 子表按业务域拆分 | 数据来源明确 | 接口幂等

这张图里,设备主表是核心,三个子表通过外键关联。数据来源有三种:注册流程自动写入基础信息,Agent 定时采集运行状态,工单系统同步管理数据。各司其职,互不干扰。

嗯,CMDB 数据模型设计这块,核心就是「拆」和「约」——拆成合理的粒度,约定清楚的数据来源和约束。做到这两点,后面不管怎么扩展,都不会乱套。