← 返回题目列表

多租户或多环境下如何设计抽象工厂?

高频 困难 第 14 / 25 题 更新于 2026/08/01
抽象工厂模式多租户多环境配置管理

简化版

多租户或多环境下,抽象工厂通常由租户、环境、region、厂商、版本等配置共同决定。设计重点是把工厂选择放在清晰的上下文边界内,缓存 key 覆盖所有影响因素,并保证同一租户或同一次请求使用同一产品族,避免配置串用和资源泄漏。

详细版

在多租户 SaaS、混合云、测试/预发/生产多环境中,抽象工厂不能只按一个 type 选择。不同租户可能使用不同云厂商、不同凭证、不同 region;不同环境可能使用 mock、沙箱或生产实现。

一个较稳的设计会包含:

  1. FactoryKey:描述租户、环境、厂商、region、凭证版本;
  2. FactoryProvider:根据 key 获取或创建具体工厂;
  3. 工厂缓存:避免重复初始化昂贵资源;
  4. 生命周期管理:配置更新后关闭旧工厂;
  5. 启动和运行时校验:防止产品族和配置不一致。

不要让每个产品分别读取租户配置,否则同一请求可能拿到不同环境或不同厂商的产品。入口处确定工厂,后续统一从该工厂创建产品,是更清晰的边界。

完整版教学

一、多租户让“产品族”多了一层业务边界

普通抽象工厂常按平台或厂商选择产品族;多租户系统还要按租户隔离配置。租户 A 使用 AWS 新加坡区域,租户 B 使用阿里云杭州区域,租户 C 使用私有化环境,它们都可能实现同一组抽象产品接口。

Tenant A -> AwsFactory(region=ap-southeast-1)
Tenant B -> AliyunFactory(region=cn-hangzhou)
Tenant C -> PrivateCloudFactory(region=local)

同一个 StorageClient 接口背后可能有完全不同的凭证和数据边界。抽象工厂在这里不只是创建模式,还承载租户隔离和产品族一致性的结构约束。

二、FactoryKey 要覆盖所有影响创建的维度

多租户抽象工厂最容易出错的地方是缓存 key 过窄。只按 vendor 缓存,看起来能复用工厂,但会把不同租户的凭证和 region 混在一起。

record FactoryKey(
    String tenantId,
    String environment,
    String vendor,
    String region,
    String credentialVersion
) {}

如果有 50 个租户、2 个环境、3 个 region,理论工厂配置组合可能达到:

50 * 2 * 3 = 300

这不表示一定要创建 300 个长生命周期工厂,但说明 key 设计必须能区分这些组合。缓存可以惰性创建和淘汰,不能用错误复用换性能。

三、请求入口确定工厂,流程内不要散读配置

一个危险写法是每个产品创建时都去配置中心读取租户信息。配置更新发生在请求中途时,同一流程可能前半段使用旧配置,后半段使用新配置。

更稳的流程:

请求进入
  -> 解析 tenantId/environment
  -> 构造 FactoryKey
  -> 获取 CloudFactory
  -> 后续 Storage/Queue/Monitor 都从同一工厂创建
请求结束

这条流程让一次请求有清晰的配置快照。配置可以在下一次请求生效,而不是在一次业务流程中间突然改变产品族。

四、配置变更时要处理旧工厂

多租户配置会变更,比如凭证轮换、region 迁移、从沙箱切生产。工厂如果持有连接池、线程池或缓存,旧工厂不能简单丢在 Map 里。

常见流程:

发现配置版本 v1 -> 创建 Factory(v1)
凭证轮换到 v2 -> 新请求使用 Factory(v2)
等待 v1 上正在执行的请求结束
关闭 Factory(v1) 持有的资源

如果每个工厂持有 4 个线程,凭证轮换 20 次但旧工厂都不关闭,就可能残留 4 * 20 = 80 个线程。配置系统和工厂缓存必须配套设计。

记忆钩子:多租户抽象工厂最怕两件事,key 少维度串租户,旧工厂不关闭泄资源。

五、环境隔离不要只靠命名约定

测试、预发、生产往往使用不同产品族或不同配置。如果只靠字符串命名,例如 "prod-aliyun""test-aliyun",很容易拼错或误传。更好的做法是把环境做成明确字段,并在启动阶段校验。

字段校验示例
environmentprod 不能使用 mock 工厂
tenantId必须存在租户配置
vendor必须在允许列表中
region必须匹配厂商支持区域
credentialVersion必须可解析且未过期

这些校验不是抽象工厂模式自带的,但在多环境落地时非常重要。模式负责结构,配置治理负责安全边界。

六、和依赖注入容器怎么配合

Spring 容器适合管理固定的工厂实现,例如 AwsFactoryBuilderAliyunFactoryBuilder。但具体租户工厂可能需要按运行时 key 动态创建,不能简单用一个全局 singleton 解决。

一种做法是:

Spring 管理 FactoryBuilder
FactoryProvider 按 FactoryKey 创建/缓存具体 CloudFactory
业务服务注入 FactoryProvider
请求入口用 tenantId 获取具体工厂

这样既利用容器管理基础依赖,又保留运行时多租户选择能力。不要把所有租户工厂都硬编码成 Bean 名称,否则租户数量一多会失控。

七、常见误区与追问

  • 误区:多租户只要按 vendor 选择工厂就够。 租户、环境、region、凭证版本都可能影响创建结果。
  • 误区:配置更新后直接覆盖 Map 即可。 旧工厂可能仍被请求使用,还可能持有线程池和连接池。
  • 误区:每个产品自己读配置更灵活。 这会破坏同一流程内的产品族一致性。
  • 误区:所有租户工厂都注册成 Spring Bean 最简单。 动态租户数量大时会让容器配置膨胀,运行时 provider 更合适。
  • 追问:如何防止测试环境误连生产? 环境字段显式建模,启动校验禁止 test 环境使用 prod 凭证。
  • 追问:工厂缓存什么时候淘汰? 配置版本变化、租户下线、长时间未访问或资源压力达到阈值时。
  • 追问:同一次请求是否允许中途切换工厂? 通常不允许,应使用请求入口确定的配置快照。

八、加强记忆

多租户抽象工厂要把“产品族一致性”扩展成“租户和环境一致性”。答题时讲清 FactoryKey、入口选厂、流程内固定、配置变更关闭旧资源、环境校验和容器配合,就能覆盖真实项目的高频追问。