
多租户SaaS数据隔离方案
独立数据库模式
为每个租户分配独立的MySQL数据库实例,通过动态数据源切换实现隔离。代码层面通过租户ID识别当前请求所属数据库。
// 动态数据源配置示例
public class TenantDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return TenantContext.getCurrentTenant();
}
}
共享数据库分Schema
同一MySQL实例下为不同租户创建独立Schema,通过Hibernate或MyBatis拦截器动态修改SQL表名前缀。
-- 创建租户Schema示例
CREATE SCHEMA tenant_1;
CREATE TABLE tenant_1.users (id INT, name VARCHAR(50));
共享表租户ID隔离
所有表增加tenant_id字段,SQL查询自动附加租户过滤条件。需在DAO层统一处理。
// MyBatis拦截器示例
@Intercepts(@Signature(type= Executor.class, method="query",
args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class TenantInterceptor implements Interceptor {
public Object intercept(Invocation invocation) {
BoundSql boundSql = ((MappedStatement)invocation.getArgs()[0]).getBoundSql();
String newSql = boundSql.getSql() + " WHERE tenant_id = " + TenantContext.getCurrentTenant();
resetSql(invocation, newSql);
return invocation.proceed();
}
}
行级安全策略
MySQL 8.0+可使用行级安全策略自动过滤数据,无需修改应用代码。
CREATE POLICY tenant_policy ON users
USING (tenant_id = CURRENT_TENANT_ID());
缓存隔离处理
Redis等缓存系统需为不同租户添加前缀,防止数据串扰。
// Redis缓存key处理示例
public String buildTenantKey(String rawKey) {
return "tenant:" + TenantContext.getCurrentTenant() + ":" + rawKey;
}
租户上下文管理
通过过滤器或拦截器解析请求头/URL参数获取租户标识,存入ThreadLocal。
// 租户上下文示例
public class TenantContext {
private static final ThreadLocal<String> currentTenant = new ThreadLocal<>();
public static void setCurrentTenant(String tenant) {
currentTenant.set(tenant);
}
public static String getCurrentTenant() {
return currentTenant.get();
}
}
分布式ID生成
采用雪花算法等分布式ID生成方案,确保跨租户ID唯一性。
// 雪花ID生成示例
public class SnowflakeIdGenerator {
private final long datacenterId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException("Clock moved backwards");
}
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - epoch) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| sequence;
}
}
1809

被折叠的 条评论
为什么被折叠?



