多租户适配
很多产品只有专属化版本,需要从产品底层进行尽量少的改造,满足上云之后多租户的数据、缓存、定时任务等隔离
多租户适配条目
条目名称 | 适配方案 |
---|---|
持久层适配 | 支持schema和字段隔离两种方案 |
quartz定时任务 | 上下文无法获取租户信息,通过JobGroup识别 |
reids缓存 | 缓存key体现租户id即可 |
websocket场景 | 从cookie获取、前端调用diwork的api获取租户信息塞到cookie,后端websocket握手后从cookie获取 |
1. 持久层适配
考虑到本身业务的实际情况,要求数据源同时支持schema隔离和字段隔离,持久层的多租户适配业务代码需要零感知、无侵入,适配实现过程如下:
STEP-1. 表结构改造,追加租户字段、有预置脚本的表,需要跟租户字段建立联合主键;
STEP-2. 引入动态数据源,动态数据源查询租户信息,切换schema实现租户按schema隔离;
STEP-3. 改造dao,采用cglib加入Interceptor,在dao层方法的执> 行前加入拦截;
STEP-4. 用jsqlParser编写sql解析类,第3步拦截到的sql追加租户ID的条件;
动态数据源关键代码
获取租户信息中的schema信息,根据schema信息切换,租户信息通过rest接口获取,考虑了到性能已加ThreadLocal和redis两重缓存
protected Connection changeCatalog(Connection con) throws SQLException {
String tenantId = InvocationInfoProxy.getTenantid();
if (StringUtils.isBlank(tenantId)) {
tenantId = "tenant";
}
String catalog = this.getCatalog(tenantId);
if (StringUtils.isNotBlank(catalog)) {
try {
con.setCatalog(catalog);
} catch (SQLException e) {
logger.error("Error occurred when setting catalog for connection, Tenant ID is {}", tenantId);
con.close();
throw e;
}
} else {
// logger.error("Switching catalog failed, check tenant ID -> {}!", tenantId);
String defaultCatalog = PropertyUtil.getPropertyByKey("jdbc.catalog");
if (StringUtils.isNotBlank(defaultCatalog) && !defaultCatalog.equals(con.getCatalog())) {
con.setCatalog(defaultCatalog);
logger.info("reset catalog for connection success!");
}
}
return con;
}