首页
找靠谱产品
找解决方案
找靠谱公司
找案例
找对的人
专家智库
悬赏任务
SAAS
ToB门户
了解全球最新的ToB事件
论坛
潜水/灌水快乐,沉淀知识,认识更多同行。
ToB圈子
加入IT圈,遇到更多同好之人。
微博
Follow
记录
Doing
博客
Blog
文库
业界最专业的IT文库,上传资料也可以赚钱
下载
分享
Share
排行榜
Ranklist
相册
Album
应用中心
qidao123.com ToB IT社区-企服评测·应用市场
»
论坛
›
软件与程序人生
›
后端开发
›
Java
›
芋道源码解读之多租户
返回列表
发新帖
芋道源码解读之多租户
[复制链接]
发表于 2025-4-17 10:25:44
|
显示全部楼层
|
阅读模式
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要
登录
才可以下载或查看,没有账号?
立即注册
×
博主和芋道源码作者及其官方开辟团队无任何关联
一、概述
租户(Tenant)是体系中的一个逻辑隔离的单位,代表一个独立使用体系的构造(如企业、高校等),在多租户体系中,不同租户共享雷同的应用步伐和基础设施,但各自拥有独立的数据、
配置
、构造架构及用户等。
芋道是一个支持多租户的体系,对多租户
功能
的组件和框架封装的
代码
位于yudao-spring-boot-starter-biz-tenant模块中,对于读写
数据库
和Redis,消息队列中消息的生产消费以及定时任务派发,调用异步方法等都分别实现了租户隔离,实现原理都是利用线程ThreadLocal举行租户标识传递和线程内共享,处置惩罚租户业务的线程(例如WebApi的HTTP请求线程,定时任务执行线程,消息消费回调线程)开始执行时起首获取具体场景下的租户ID,存到当前线程的ThreadLocal中,后续基于该线程执行或调用的各种方法中如遇到读写
数据库
或Redis以及发送消息和调用异步方法的操作时,便能从ThreadLocal获取租户ID再执行进一步操作。
项目通过一个TenantContextHolder类来封装ThreadLocal进而实现不同场景下基于线程的租户隔离,为每个执行带有租户隔离逻辑
代码
的线程都绑定一个TENANT_ID对象来储存和共享当前场景的租户ID,同时还绑定了一个布尔范例的IGNORE用于标识当前线程即将要执行的
代码
是否需要处置惩罚租户。
只有深入正确理解TenantContextHolder中ThreadLocal的原理,才能真正理解多租户的实现原理
public class TenantContextHolder {
/**
* 当前租户编号
*/
private static final ThreadLocal<Long> TENANT_ID = new TransmittableThreadLocal<>();
/**
* 是否忽略租户
*/
private static final ThreadLocal<Boolean> IGNORE = new TransmittableThreadLocal<>();
/**
* 获得租户编号
*
* @return 租户编号
*/
public static Long getTenantId() {
return TENANT_ID.get();
}
/**
* 获得租户编号。如果不存在,则抛出 NullPointerException 异常
*
* @return 租户编号
*/
public static Long getRequiredTenantId() {
Long tenantId = getTenantId();
if (tenantId == null) {
throw new NullPointerException("TenantContextHolder 不存在租户编号!可参考
文档
:"
+ DocumentEnum.TENANT.getUrl());
}
return tenantId;
}
public static void setTenantId(Long tenantId) {
TENANT_ID.set(tenantId);
}
public static void setIgnore(Boolean ignore) {
IGNORE.set(ignore);
}
/**
* 当前是否忽略租户
*
* @return 是否忽略
*/
public static boolean isIgnore() {
return Boolean.TRUE.equals(IGNORE.get());
}
public static void clear() {
TENANT_ID.remove();
IGNORE.remove();
}
}
复制代码
多租户还需要考虑忽略租户和指定租户的情况:
调用某些方法时,租户应当被忽略,例如超级管理员获取体系全部数据、项目启动后获取全部数据去创建全局静态缓存等,因此该项目也提供了租户忽略的实现方案,对于某段需要忽略租户执行的代码,提供了忽略租户去执行某个代码块的公共方法,对于整个方法需要忽略租户的情况,则通过AOP处置惩罚自定义注解的方式,对某个方法标志忽略租户,该方法内执行的代码便不再对多租户的情况举行处置惩罚。
调用某些方法时,应当以指定的某个租户ID去执行,而不是采用当前登录用户的租户ID,例如超管新建了一个租户,并为新租户一并创建管理员用户以及根本的脚色,菜单和权限等数据时,这些数据的租户ID应该是新建的租户的ID,针对这种情况,项目也实现了一个按照指定租户ID执行某个代码块的公共方法。
二、
数据库
的租户隔离
2.1 数据库的租户隔离实现方案
数据库中租户的隔离方案有三种:
库隔离
:每个租户拥有独立的数据库实例
表隔离
:每个租户建属于自己的一套数据表
记录隔离
:表中使用租户标识字段(tenant_id)区分不同租户的数据
三种方案对比:
库隔离表隔离记录隔离隔离性最高较高最低
性能
高中低,受租户数量影响
备份
还原难度简朴中困难硬件成本高中低维护成本高,开租户就要建库中低芋道采用了记录隔离的方式来实现数据库的租户隔离。
2.2 实现原理和源码解读
租户本质上也是一种特别的数据权限,不同于数据权限的是对于涉及租户的表的增、删、改、查四种操作,都需要对SQL语句举行处置惩罚,实现原理是执行SQL前举行拦截,并获取要执行的SQL,然后解析SQL语句中的表,遇到需要租户隔离的表就要举行处置惩罚,对于查询、删除和更新的场景,就在现有的SQL条件中追加一个tenant_id = ?的条件,获取当前操作的用户或要执行的某种任务所属的租户ID赋值给tenant_id,对于添加操作,则是将tenant_id字段参加到INSERT列表中并赋值。
芋道采用MyBatis-Plus的插件拦截机制实现数据库的记录级别的租户隔离,这和
数据权限
的实现原理是完全一样的,实现租户隔离的插件是TenantLineInnerInterceptor,该类也像数据权限插件一样继承了用于解析和追加条件的
BaseMultiTableInnerInterceptor
类来实现表的解析和租户ID过滤条件的追加
TenantLineInnerInterceptor需要一个TenantLineHandler范例的租户处置惩罚器,TenantLineHandler是一个接口,用于给TenantLineInnerInterceptor判断某个表是否需要租户隔离,以及获取租户ID值表达式、租户字段名,我们需要实现这个接口并在回调方法中将这些信息封装好后返回。
回复
使用道具
举报
返回列表
浏览过的版块
Oracle
前端开发
DevOps与敏捷开发
分布式数据库
Mysql
开源技术
网络安全
云原生
MES
渣渣兔
+ 我要发帖
登录后关闭弹窗
登录参与点评抽奖 加入IT实名职场社区
去登录
微信订阅号
微信服务号
微信客服(加群)
H5
小程序
快速回复
返回顶部
返回列表