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 数据模型全景图
说了这么多,咱们用一张图把整个数据模型串起来。这张图展示了设备注册流程中,数据从采集到存储的完整路径。
这张图里,设备主表是核心,三个子表通过外键关联。数据来源有三种:注册流程自动写入基础信息,Agent 定时采集运行状态,工单系统同步管理数据。各司其职,互不干扰。
嗯,CMDB 数据模型设计这块,核心就是「拆」和「约」——拆成合理的粒度,约定清楚的数据来源和约束。做到这两点,后面不管怎么扩展,都不会乱套。