2. 多租户配置模型设计

配置中心的核心难点不在“存配置”,而在“同一份配置如何被不同租户、不同应用、不同环境正确解析”。如果数据模型只设计成一张 key-value 表,很快就会遇到三个问题:租户之间数据串扰、环境之间配置覆盖混乱、公共配置无法复用。因此,多租户配置模型必须先把领域对象和它们之间的关系定义清楚,再谈存储与缓存。

一、六个核心领域对象

一个可落地的多租户配置模型通常包含六个核心对象:租户(Tenant)、应用(Application)、环境(Environment)、配置项(ConfigItem)、配置版本(ConfigVersion)、继承关系(Inheritance)。它们不是并列的,而是有明确的归属和层级。

  • 租户:数据隔离的最高边界,拥有独立的命名空间、配额和权限体系。租户之间默认不可见。
  • 应用:隶属于某个租户,代表一个可独立部署的服务或产品线,如 order-service、payment-gateway。
  • 环境:隶属于应用,常见取值为 dev、test、staging、prod,用于隔离同一应用在不同生命周期阶段的配置。
  • 配置项:最小配置单元,由 key、value、type、description 等字段构成,归属于某个“应用 + 环境”组合。
  • 配置版本:一次发布产生的不可变快照,包含版本号、发布时间、发布人、变更 diff,用于回滚和审计。
  • 继承关系:描述配置项之间的复用路径,使子级可以覆盖父级,而不必重复定义。

二、继承关系:模型中最容易出错的部分

继承不是简单的“父子复制”,而是一条解析链。推荐采用“租户公共 → 应用公共 → 环境专属 → 实例覆盖”的四层继承模型。解析时从最具体的一层向上查找,遇到第一个命中的 key 即停止,这就是“就近覆盖”原则。

例如,租户 t-acme 下应用 order-service 的 prod 环境需要读取 db.pool.size。解析顺序为:

1. 实例级覆盖   tenant=t-acme / app=order-service / env=prod / instance=i-01
2. 环境级配置   tenant=t-acme / app=order-service / env=prod
3. 应用级公共   tenant=t-acme / app=order-service / env=*
4. 租户级公共   tenant=t-acme / app=* / env=*

若四层都未命中,则返回“未配置”错误,而不是静默使用空值。这一点必须在领域模型中显式定义,否则线上会出现难以排查的默认值问题。

继承链必须避免环。例如应用 A 继承应用 B,B 又继承 A,会导致解析死循环。建模时应在写入继承关系前做一次有向无环图(DAG)校验。

三、数据模型与领域建模的对应

领域模型建议采用聚合根设计:租户是聚合根,应用、环境、配置项都通过租户外键约束;配置版本是独立聚合,因为它一旦发布就不可变,只追加不修改。配置项的每次变更不直接改原记录,而是生成一条新的版本记录,当前生效版本通过指针指向最新已发布版本。

下面这张图展示了六个对象之间的归属、继承与版本关系,是本节最需要建立的整体认知。

租户 Tenant t-acme / t-beta 应用 Application order-service 环境 Environment dev / test / prod 配置项 ConfigItem key / value / type 配置版本 Version v1 / v2 / v3 继承解析链(就近覆盖) ① 实例级覆盖 instance=i-01 ② 环境级配置 env=prod ③ 应用级公共 env=* ④ 租户级公共 app=* / env=* 1:N 1:N 1:N 1:N 继承 当前生效版本指针 active_version = v3 租户隔离边界:所有查询必须携带 tenant_id,跨租户不可见

四、关键字段与约束

为了让模型可落地,每个对象至少需要以下字段和约束。这里给出的是示例性设计,实际项目可按需裁剪。

对象 关键字段 约束
Tenant tenant_id, name, status, quota tenant_id 全局唯一
Application app_id, tenant_id, name, owner (tenant_id, name) 唯一
Environment env_id, app_id, name, order (app_id, name) 唯一
ConfigItem item_id, env_id, key, value, type (env_id, key) 唯一
ConfigVersion version_id, env_id, version_no, diff, published_at version_no 单调递增,发布后不可改
Inheritance parent_id, child_id, priority 必须无环,priority 决定覆盖顺序

五、领域建模中的两个实践要点

第一,隔离维度必须前置。所有查询接口的第一个参数都应该是 tenant_id,而不是在 SQL 末尾追加过滤条件。领域服务层应提供 TenantContext,由网关解析请求头中的租户标识后注入,业务代码不感知租户切换。这样可以从结构上避免“忘记加租户条件”导致的越权读取。

第二,版本与配置项分离。配置项保存“当前值”,版本保存“历史快照”。发布时先写入新版本记录,再原子更新配置项的当前值指针。回滚时只需把指针指回旧版本,无需反向修改数据。这种设计让审计、回滚和灰度发布都变得简单。

在实现继承解析时,建议把解析结果缓存为“租户 + 应用 + 环境”维度的扁平 key-value 映射,并设置较短的 TTL(如 30 秒)。当任一父级配置发生变更时,通过变更事件主动失效对应缓存,避免轮询带来的延迟。

总结来看,多租户配置模型的设计顺序应当是:先确定隔离边界(租户),再确定归属层级(应用、环境),然后定义最小单元(配置项)与不可变快照(版本),最后用继承关系把复用路径串起来。只要这六个对象的归属、唯一约束和解析顺序定义清楚,后续的存储选型、缓存策略和发布流程都会变得有据可依。