核心是租户键唯一性与访问路径收口:外层concurrenthashmap以tenantid为key,值为该租户专属的concurrenthashmap;所有操作必须带tenantid参数封装,禁止直连内层map,确保单jvm内内存级隔离。

在多租户场景下,用并发 Map 做极简隔离,核心不是“嵌套多深”,而是“租户键的唯一性”和“访问路径的收口控制”。Java 中 ConcurrentHashMap 本身不提供租户级隔离能力,需靠设计约束访问逻辑——即:所有读写必须通过租户 ID 定位到专属子 Map,且子 Map 也必须是线程安全的。
租户维度建模:外层 Map 管租户,内层 Map 管数据
用 ConcurrentHashMap<string concurrenthashmap v>></string> 结构,外层 key 是租户 ID(如 "tenant-a"),值是该租户独享的内存数据空间。这样天然隔离,不同租户操作互不影响。
- 初始化时懒创建内层 Map:
computeIfAbsent(tenantId, k -> new ConcurrentHashMap()),避免预分配和空租户占用资源 - 禁止直接暴露内层 Map 引用,所有操作封装在服务方法中,例如
put(tenantId, key, value)和get(tenantId, key) - 内层用
ConcurrentHashMap而非HashMap,因单租户内仍可能有多线程并发读写(如批量导入、定时任务)
访问收口:所有数据操作必须带租户上下文
隔离失效往往源于“绕过租户路由”。必须切断任何不带 tenantId 的直接访问路径。
- HTTP 接口强制校验并透传租户标识(如从 Header、JWT 或子域名提取),拒绝无租户上下文的请求
- DAO 层不暴露原始 Map,只提供带 tenantId 参数的方法,例如:
userCache.put("tenant-b", "u1001", user) - 避免静态工具类缓存全局 Map 实例;若用 Spring,推荐
@Scope("prototype")或基于 tenantId 的自定义 Scope
轻量清理与可观测性:租户生命周期可管理
极简不等于不可维护。需支持租户停用、数据清理和基本监控。
- 租户注销时调用
outerMap.remove(tenantId),触发内层 Map 被 GC(前提是无外部引用) - 加简单指标:用
outerMap.size()查活跃租户数;对高频租户可记录内层 Map 的size()做趋势观察 - 日志中统一打标
[tenant=xxx],便于链路追踪和问题定位
注意边界:这不是分布式或持久化方案
该模型仅适用于单 JVM 进程内的内存级租户隔离,适合原型验证、SaaS 后台管理后台或中小规模租户场景。
- 不解决跨节点共享问题——集群部署时需配合 Redis 分片或数据库分库
- 不保证数据持久化——重启即丢失,生产环境需搭配 DB 或序列化存储
- 不替代权限系统——租户隔离 ≠ 权限控制,仍需 RBAC 校验操作合法性











