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: 通过 MultiTenantConnectionProviderCurrentTenantIdentifierResolver 支持多租户;

  • 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
相关推荐
洛卡卡了8 小时前
一次上线事故,我干脆写了套灰度发布系统
后端·面试·架构
常先森8 小时前
【解密源码】 RAGFlow 切分最佳实践- naive parser 语义切块(docx 篇)
架构·llm
西岭千秋雪_8 小时前
Zookeeper监听机制
java·linux·服务器·spring·zookeeper
毕设源码-林学长8 小时前
计算机毕业设计java和Vue的安全教育科普平台设计与实现 安全知识普及与教育平台 安全教育信息化管理平台
java·开发语言·课程设计
ruleslol8 小时前
java-接口适配器模式 & jsk8 接口默认实现
java·适配器模式
鬼火儿8 小时前
网卡驱动架构以及源码分析
java·后端
老华带你飞8 小时前
房屋租赁|房屋出租|房屋租赁系统|基于Springboot的房屋租赁系统设计与实现(源码+数据库+文档)
java·数据库·spring boot·vue·论文·毕设·房屋租赁系统
TDengine (老段)8 小时前
TDengine 数学函数 ASCII 用户手册
java·大数据·数据库·物联网·时序数据库·tdengine·涛思数据
小马哥编程8 小时前
【软考架构】案例分析-web应用设计:SSH 和 SSM(Spring + Spring MVC + MyBatis ) 之间的区别,以及使用场景
前端·架构·ssh