SaaS 租户上下文传播架构

一、引言

在多租户(Multi-Tenant)SaaS 系统中,"租户上下文传播" 是确保租户隔离、访问安全与数据正确性的基础设施能力。

它决定了系统能否在请求链路、异步任务和分布式微服务中保持租户标识一致,从而实现全局的数据与逻辑隔离。

本文从架构层面系统解析 SaaS 租户上下文传播机制 的设计原则、技术路径与工程实现。


二、背景与核心问题

SaaS 系统中的多租户架构主要分为三种:

模式 描述 优缺点
共享表(Shared Schema) 多个租户共用同一数据库表,通过 tenant_id 字段区分 成本低、隔离弱
独立 Schema 每个租户单独 Schema,表结构相同 中等成本、较好隔离
独立数据库(Database per Tenant) 每个租户独立数据库连接 隔离强、运维复杂

不论哪种模式,都需要解决同一个核心问题:

在请求链路中,如何让系统的每一层都"知道"当前请求属于哪个租户?

这就是"租户上下文传播"的问题。


三、架构原则

SaaS 租户上下文传播的设计遵循以下三大原则:

  1. 上下文透明性(Transparency)

    业务层不感知租户传递过程,只需访问 TenantContext.getTenantId()。

  2. 线程安全性(Thread Safety)

    同一请求线程内上下文一致,不同请求间互不干扰。

  3. 跨域传播能力(Propagation Capability)

    能在异步任务、消息队列、微服务调用等场景中正确传递租户标识。


四、架构分层设计

1. 入口层(API 层)

职责: 解析并设置租户标识。
典型实现: Servlet Filter 或 Spring WebFilter。

java 复制代码
public class TenantFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        String tenant = request.getHeader("X-Tenant-ID");
        try {
            TenantContext.setTenant(tenant);
            chain.doFilter(req, res);
        } finally {
            TenantContext.clear();
        }
    }
}

入口层通过 ThreadLocal 存储租户标识,实现请求线程内可见。


2. 上下文层(ThreadLocal 存储)

java 复制代码
public class TenantContext {
    private static final ThreadLocal<String> TENANT_HOLDER = new ThreadLocal<>();
    public static void setTenant(String tenantId) { TENANT_HOLDER.set(tenantId); }
    public static String getTenant() { return TENANT_HOLDER.get(); }
    public static void clear() { TENANT_HOLDER.remove(); }
}

设计要点:

  • 线程级上下文,访问性能极高;

  • 必须在请求结束后清理;

  • 可扩展为多维上下文(如 UserContext、TraceContext)。


3. 框架层(数据访问与ORM)

在数据访问层,租户上下文的任务是"被利用"。

典型场景包括:

(1) 动态数据源路由
java 复制代码
public class TenantRoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return TenantContext.getTenant();
    }
}

每个租户的数据库连接池独立,框架根据租户上下文动态选择数据源。

(2) ORM 层租户隔离
  • Hibernate: 通过 MultiTenantConnectionProvider 与 CurrentTenantIdentifierResolver 支持多租户;

  • MyBatis: 使用 Interceptor 拦截 SQL,自动注入租户条件;

  • JPA: 可结合 EntityManager 的多租户机制实现。


4. 异步任务层(线程传播)

在使用 @Async、线程池、定时任务时,ThreadLocal 无法自动传播。

必须通过 TaskDecorator 或包装 Runnable 实现上下文复制:

java 复制代码
@Bean
public TaskDecorator tenantContextDecorator() {
    return runnable -> {
        String tenant = TenantContext.getTenant();
        return () -> {
            TenantContext.setTenant(tenant);
            try {
                runnable.run();
            } finally {
                TenantContext.clear();
            }
        };
    };
}

这样可以确保异步任务与原始请求保持同一租户上下文。


5. 分布式传播层(跨服务)

在微服务架构中,请求经过多个服务,需要在调用链上传递租户信息。

解决方案:
  • Feign 拦截器 自动注入租户头:
java 复制代码
public class TenantFeignInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        String tenant = TenantContext.getTenant();
        if (tenant != null) {
            template.header("X-Tenant-ID", tenant);
        }
    }
}
  • 下游服务 再次通过 Filter 解析并写入 ThreadLocal;

  • 消息队列场景:在消息 Header 中附带租户信息。


五、架构整体流程图(逻辑示意)

java 复制代码
        ┌─────────────────────────────────────┐
        │             Client / API            │
        └─────────────────────────────────────┘
                          │
                          ▼
             ┌────────────────────────────┐
             │ TenantFilter(提取租户)   │
             │   └── 设置 ThreadLocal      │
             └────────────────────────────┘
                          │
                          ▼
           ┌──────────────────────────────┐
           │ Spring 调用链 (Controller→Service) │
           │ 通过 TenantContext 获取租户       │
           └──────────────────────────────┘
                          │
                          ▼
             ┌────────────────────────────┐
             │ AbstractRoutingDataSource   │
             │  └── determineCurrentLookupKey() │
             │      从 ThreadLocal 取租户ID      │
             └────────────────────────────┘
                          │
                          ▼
             ┌────────────────────────────┐
             │ 异步任务 / 微服务调用        │
             │  └── 通过 Header / TaskDecorator 传递租户 │
             └────────────────────────────┘

六、扩展与前瞻设计

1. 上下文容器化

未来的 SaaS 框架可将租户上下文扩展为独立容器(如 ContextMap),不仅包含 tenant,还可携带 user、traceId、locale 等信息,实现全链路上下文统一管理。

2. Reactor / WebFlux 场景

对于响应式系统,应使用 Reactor Context 替代 ThreadLocal:

java 复制代码
Mono.deferContextual(ctx -> {
    String tenant = ctx.get("tenantId");
    return processTenant(tenant);
});

3. 全局传播机制(Context Propagation)

未来趋势是引入标准化上下文传播协议(如 W3C Context Propagation、OpenTelemetry Baggage)以支持跨语言传播租户标识。


七、总结

层级 职责 关键机制
API 层 解析租户并注入上下文 Filter
上下文层 线程级存储租户标识 ThreadLocal
数据访问层 动态切换数据源 / Schema AbstractRoutingDataSource
异步层 上下文跨线程传播 TaskDecorator
分布式层 上下文跨服务传播 Header / Feign Interceptor
相关推荐
马剑威(威哥爱编程)9 小时前
【AI全栈后端12-09】Spring Boot 用流式输出做打字机对话:点下去不再干等
java·人工智能·spring boot·后端
步行cgn9 小时前
Spring 事务传播行为详解
java·数据库·spring
海宇服务9 小时前
零信任架构实战:基于海宇车辆vin码查车辆信息详版构建自动化汽配云仓参配核验网关
java·人工智能·架构·自动化
wno7049 小时前
Spring Boot整合Flyway
java·spring boot·后端
vx_Biye_Design9 小时前
springboot旅游管理系统18006-计算机课程设计、毕业设计
java·spring boot·后端·python·elasticsearch·django·课程设计
Wang's Blog10 小时前
Java 中间件之 RabbitMQ 快速入门: 简单队列模型快速入门
java·中间件·java-rabbitmq
骇客野人10 小时前
Java 开发组件大全覆盖后端主流技术栈(基础、Web、ORM、中间件、微服务、安全、工具、测试、运维、信创适配常用组件)
java·前端·中间件
暖核10 小时前
Redis 从基础到集群实战:数据类型、客户端、高可用架构完整梳理
数据库·redis·架构
FfHUCisI10 小时前
pprof 火焰图(Flame Graph)阅读与热点代码重构实战
架构·golang
2401_8888597110 小时前
STM32H733 MPU、AXI、FMC学习
java·开发语言·stm32·spring