3、系统架构设计:审计追踪系统的分层架构、数据库设计原则、日志存储策略
好,咱们进入第三章。前面聊了审计追踪的法规要求和风险场景,现在该落地了。这一章我打算把系统架构怎么搭、数据库怎么设计、日志怎么存,掰开了讲清楚。
说实话,我见过太多项目在架构阶段就埋了坑。有的把审计日志和业务数据混在一个库,有的日志表连个索引都没有,还有的存储策略根本扛不住三年数据量。嗯,咱们今天就把这些坑一个个填上。
3.1 分层架构:别让所有逻辑挤在一层
我个人习惯把审计追踪系统拆成四层。你想想看,如果不分层,日志采集、校验、存储、查询全揉在一起,后期维护就是噩梦。
核心原则:每一层只干一件事,层与层之间通过接口通信,不直接依赖内部实现。
下面这张图是我在项目里常用的分层结构,你可以直接拿来当参考模板。
我来解释一下每层的职责:
- 采集层:负责从业务系统里把原始事件捞出来。我在项目中常用 AOP 切面拦截关键方法调用,或者用数据库 Trigger 捕获数据变更。注意,采集层不要做任何数据清洗,保持原始性。
- 校验与处理层:这是核心。数据进来后先做完整性校验,比如哈希比对、数字签名验证。然后标准化格式,把不同来源的日志统一成 JSON 或 XML 结构。我曾经遇到过一个客户,业务系统时间戳格式五花八门,有的用 yyyy-MM-dd,有的用 yyyy/MM/dd,这层就是干这个的。
- 存储层:负责把处理好的日志持久化。后面我会详细讲数据库设计和存储策略。
- 查询与展示层:给审计员用的。支持按时间、用户、操作类型筛选,还要能回溯某个时间点的完整数据状态。
我的经验:采集层和校验层之间一定要加一个缓冲队列(比如 Kafka 或 RabbitMQ)。这样即使校验层挂了,采集层不会丢数据。我在一个药企项目里吃过这个亏,校验服务重启了 30 秒,丢了 200 多条日志,后来被 QA 开了 CAPA。
3.2 数据库设计原则:别把审计表当成普通业务表
审计追踪的数据库设计,和普通业务系统完全不一样。业务表追求的是读写性能,审计表追求的是不可篡改和可追溯。
我总结了几条硬性原则:
- 只追加,不更新,不删除。审计表里不应该出现 UPDATE 或 DELETE 操作。如果发现数据错了,只能追加一条更正记录。
- 每条记录必须有全局唯一 ID。别用自增 ID,用 UUID 或雪花算法。自增 ID 在分布式环境下会重复,而且容易被猜到数据量。
- 必须包含时间戳和用户标识。这两个字段是审计追踪的命根子,缺一不可。
- 用链式哈希保证完整性。每条记录都包含上一条记录的哈希值,形成一条链。如果有人改了中间某条数据,整条链就断了。
下面是一个典型的审计日志表结构,我在多个 GxP 项目里都用过这个模板:
CREATE TABLE audit_log (
id VARCHAR(36) PRIMARY KEY, -- UUID
prev_hash VARCHAR(64) NOT NULL, -- 上一条记录的 SHA-256
current_hash VARCHAR(64) NOT NULL, -- 本条记录的 SHA-256
event_type VARCHAR(50) NOT NULL, -- 事件类型:CREATE/UPDATE/DELETE/LOGIN
table_name VARCHAR(100) NOT NULL, -- 操作的表名
record_id VARCHAR(100) NOT NULL, -- 被操作记录的 ID
user_id VARCHAR(50) NOT NULL, -- 操作人
user_ip VARCHAR(45), -- 操作 IP
occurred_at DATETIME(3) NOT NULL, -- 操作时间,精确到毫秒
old_value JSON, -- 变更前的数据快照
new_value JSON, -- 变更后的数据快照
signature TEXT NOT NULL, -- 数字签名
created_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3)
);
-- 索引设计
CREATE INDEX idx_audit_user_id ON audit_log(user_id);
CREATE INDEX idx_audit_occurred_at ON audit_log(occurred_at);
CREATE INDEX idx_audit_event_type ON audit_log(event_type);
注意:索引不要建太多。审计表是写密集型的,索引多了会拖慢写入速度。我一般只给 user_id、occurred_at、event_type 这三个最常用的查询条件建索引。其他字段的查询走全表扫描,反正审计数据一般不会频繁查。
3.3 日志存储策略:冷热分离,分级存储
审计日志的数据量增长很快。一个中型药企,每天可能产生几十万条审计记录。如果全部存在一个地方,一年下来就是上亿条数据,查询会慢到让你怀疑人生。
我的做法是冷热分离:
| 存储层级 | 时间范围 | 存储介质 | 访问频率 | 压缩策略 |
|---|---|---|---|---|
| 热数据 | 最近 30 天 | SSD 数据库 | 高频查询 | 不压缩 |
| 温数据 | 31 天 ~ 1 年 | HDD 数据库 或 归档表 | 偶尔查询 | 行级压缩 |
| 冷数据 | 1 年以上 | 对象存储(S3/MinIO) | 几乎不查,仅合规保留 | 列式压缩 + 加密 |
为什么要这么分?说白了就是成本和性能的平衡。SSD 贵,但查询快;对象存储便宜,但查询慢。审计数据超过一年后,除了监管检查,基本没人会去翻。所以把它丢到对象存储里,省成本。
我的习惯:冷数据迁移时,我会按月份打包成 Parquet 格式,然后用 GPG 加密。Parquet 是列式存储,压缩比高,查询时只需要读取相关列,效率比 JSON 高很多。我曾经把一个 2TB 的审计日志库压缩到 200GB,省了 90% 的存储成本。
另外,存储策略里还有一个容易被忽略的点:日志轮转。如果你的审计日志是写到文件里的(比如某些旧系统),一定要配置日志轮转策略。我见过一个案例,某工厂的审计日志文件写满了磁盘,导致整个生产系统宕机。后来他们加了 logrotate,每天切割一次,保留 90 天,超过的自动删除。
嗯,这一章的内容差不多就这些。分层架构让你知道系统怎么搭,数据库设计让你知道表怎么建,存储策略让你知道数据怎么管。这三块搞定了,审计追踪系统的骨架就立起来了。