多租戶的四種常用方案!
前言
某中型電商平臺(tái)的報(bào)表系統(tǒng)曾在深夜突然崩潰,起因竟是運(yùn)營誤刪了共享表中的某租戶數(shù)據(jù)列。
運(yùn)維團(tuán)隊(duì)排查發(fā)現(xiàn),因?yàn)槿狈τ行ё鈶舾綦x,一條誤操作的ALTER TABLE語句導(dǎo)致全平臺(tái)數(shù)據(jù)混亂。
這讓我們警惕:選擇多租戶方案的每一步,都是安全與成本的權(quán)衡。
今天這篇文章就跟大家一起聊聊,多租戶的4種常用方案,希望對(duì)你會(huì)有所幫助。
一、字段隔離方案
低成本背后的高風(fēng)險(xiǎn)
字段隔離方案,是通過統(tǒng)一數(shù)據(jù)表+租戶ID過濾實(shí)現(xiàn)邏輯隔離。
如下圖所示:
圖片
初期開發(fā)成本極低,但將數(shù)據(jù)安全的壓力完全轉(zhuǎn)移到了代碼質(zhì)量控制上。
致命缺陷檢查清單:
- 任意一次DAO層查詢漏加tenant_id條件 → 數(shù)據(jù)跨租戶泄露
- 索引必須將tenant_id作為最左前綴 → 性能瓶頸風(fēng)險(xiǎn)
- 全表掃描類查詢(如報(bào)表統(tǒng)計(jì))無法避免跨租戶干擾
代碼防御示范
(1)MyBatis攔截器自動(dòng)注入租戶ID
@Intercepts({@Signature(type = Executor.class, method = "update")})
public class TenantInterceptor implements Interceptor {
public Object intercept(Invocation iv) throws SQLException {
MappedStatement ms = (MappedStatement) iv.getArgs()[0];
Object param = iv.getArgs()[1];
// 實(shí)體類自動(dòng)填充tenant_id
if (param instanceof BaseTenantEntity) {
Field tenantIdField = param.getClass().getDeclaredField("tenantId");
tenantIdField.setAccessible(true);
if (tenantIdField.get(param) == null) {
tenantIdField.set(param, TenantContext.get());
}
}
return iv.proceed();
}
}
(2)SQL防火墻:強(qiáng)制全表掃描必須聲明租戶范圍
/* 危險(xiǎn)操作(可能掃全表) */
SELECT * FROM orders WHERE status = 'PAID';
/* 安全寫法(強(qiáng)制tenant_id過濾) */
SELECT * FROM orders
WHERE tenant_id = 'tenant_01'
AND status = 'PAID'
/* 必須添加LIMIT防止全量拉取 */
LIMIT 1000;
適用場(chǎng)景建議
- 初期快速驗(yàn)證的MVP產(chǎn)品,用戶量比較少的業(yè)務(wù)系統(tǒng)。
- 對(duì)數(shù)據(jù)隔離要求較低的內(nèi)部管理系統(tǒng)。
二、Schema隔離
數(shù)據(jù)庫層的單元房
在同一個(gè)數(shù)據(jù)庫實(shí)例中為每個(gè)租戶獨(dú)立Schema,實(shí)現(xiàn)庫級(jí)別隔離。
如下圖所示:
圖片
各租戶表結(jié)構(gòu)相同但數(shù)據(jù)獨(dú)立,像小區(qū)里的不同住戶單元。
運(yùn)維警告清單:
- 百級(jí)Schema數(shù)量級(jí)后,備份與遷移成本陡增
- 跨Schema關(guān)聯(lián)查詢必須引入中間聚合層
- 數(shù)據(jù)庫連接池需按最大租戶數(shù)配置 → 連接風(fēng)暴風(fēng)險(xiǎn)
動(dòng)態(tài)路由代碼實(shí)現(xiàn)
(1)Spring動(dòng)態(tài)數(shù)據(jù)源配置
spring:
datasource:
dynamic:
primary: master
strict: true
datasource:
master:
url: jdbc:mysql://主庫地址
tenant_001:
url: jdbc:mysql://從庫地址?currentSchema=tenant_001
tenant_002:
url: jdbc:mysql://從庫地址?currentSchema=tenant_002
(2)AOP切面動(dòng)態(tài)切換Schema
@Aspect
@Component
public class SchemaAspect {
@Before("@annotation(requireTenant)")
public void switchSchema(JoinPoint joinPoint) {
HttpServletRequest request = getCurrentRequest();
String tenantId = request.getHeader("X-Tenant-ID");
// 驗(yàn)證租戶合法性
if (!tenantService.isValid(tenantId)) {
throw new IllegalTenantException("租戶身份異常!");
}
// 動(dòng)態(tài)切換數(shù)據(jù)源
DynamicDataSourceContextHolder.push(tenantId);
}
@After("@annotation(requireTenant)")
public void clearSchema() {
DynamicDataSourceContextHolder.clear();
}
}
適用場(chǎng)景建議
- 需要中等安全級(jí)別的行業(yè)(教育、零售)。
- 租戶數(shù)<50且數(shù)據(jù)規(guī)模可控的系統(tǒng)。
三、獨(dú)立數(shù)據(jù)庫
數(shù)據(jù)隔離的終極形態(tài)
每個(gè)租戶享有獨(dú)立數(shù)據(jù)庫實(shí)例。
如下圖所示:
圖片
從存儲(chǔ)到底層連接完全隔離。
安全性最高但成本呈線性增長(zhǎng)。
財(cái)務(wù)預(yù)警清單:
- 每個(gè)實(shí)例約增加¥3000/月(云RDS基礎(chǔ)配置)
- 跨租戶數(shù)據(jù)聚合需額外ETL系統(tǒng)支持
- DBA運(yùn)維成本隨租戶數(shù)量直線上升
數(shù)據(jù)源動(dòng)態(tài)路由核心代碼
(1)抽象路由控制器
public class TenantDataSourceRouter extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return TenantContextHolder.get();
}
@Override
protected DataSource determineTargetDataSource() {
String tenantId = (String) determineCurrentLookupKey();
DataSource ds = dataSourceMap.get(tenantId);
if (ds == null) {
ds = createNewDataSource(tenantId); // 動(dòng)態(tài)創(chuàng)建新租戶數(shù)據(jù)源
dataSourceMap.put(tenantId, ds);
}
return ds;
}
}
(2)多租戶事務(wù)同步器(關(guān)鍵!)
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager() {
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
TenantDataSourceRouter router = (TenantDataSourceRouter) getDataSource();
router.initTenantDataSource(TenantContextHolder.get()); // 確保事務(wù)綁定正確數(shù)據(jù)源
super.doBegin(transaction, definition);
}
};
}
適用場(chǎng)景建議
- 金融、醫(yī)療等強(qiáng)合規(guī)行業(yè)
- 付費(fèi)能力強(qiáng)且需要獨(dú)立資源池的KA客戶
四、混合架構(gòu)
沒有銀彈的平衡術(shù)
核心原則:按租戶等級(jí)提供不同隔離方案
在系統(tǒng)中創(chuàng)建租戶時(shí),根據(jù)租戶的實(shí)際情況,給它分配一個(gè)等級(jí)。
不同的等級(jí),使用不同的隔離方案。
如下圖所示:
租戶等級(jí) | 隔離方案 | 資源配置 |
S級(jí) | 獨(dú)立數(shù)據(jù)庫 | 獨(dú)占RDS實(shí)例+只讀副本 |
A級(jí) | Schema隔離 | 共享實(shí)例獨(dú)立Schema |
B級(jí) | 字段過濾 | 共享表 |
動(dòng)態(tài)策略選擇器
針對(duì)不同的租戶,我們可以使用策略模式,根據(jù)不同的等級(jí),選擇不同的數(shù)據(jù)庫訪問方式。
代碼如下:
public class IsolationStrategyFactory {
public IsolationStrategy getStrategy(String tenantId) {
TenantConfig config = configService.getConfig(tenantId);
switch(config.getLevel()) {
case VIP:
return new IndependentDBStrategy();
case STANDARD:
return new SchemaStrategy();
case BASIC:
default:
return new SharedTableStrategy();
}
}
// 示例策略接口
public interface IsolationStrategy {
DataSource getDataSource();
void executeQuery(String sql);
}
}
運(yùn)維避坑必讀
- 元數(shù)據(jù)管理:建立租戶-資源映射表,避免配置漂移
- 遷移工具鏈:開發(fā)自動(dòng)化升降級(jí)工具(如VIP客戶從共享表遷移到獨(dú)立庫)
- 監(jiān)控分層:不同方案的性能指標(biāo)需獨(dú)立采集分析
總結(jié)
這篇文章列舉了多租戶的4種常用方案。
沒有最完美的,只有最合適的。
多租戶設(shè)計(jì)的本質(zhì)是資源、安全、成本的黃金三角博弈。
與其追求理論完美,不如根據(jù)業(yè)務(wù)階段選擇最適方案。
畢竟能用可控成本解決問題的,才是真正的架構(gòu)智慧。
如果看了文章有些收獲,記得給我點(diǎn)贊喔,謝謝你的支持和鼓勵(lì)。