提交 14e58c1b 编写于 作者: 小傅哥's avatar 小傅哥

小傅哥,数据库路由组件设计

上级 ef37762d
...@@ -61,7 +61,12 @@ ...@@ -61,7 +61,12 @@
<dependency> <dependency>
<groupId>commons-beanutils</groupId> <groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId> <artifactId>commons-beanutils</artifactId>
<version>1.8.3</version> <version>1.9.4</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
......
package cn.bugstack.middleware.db.router;
/**
* 博客:https://bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获!
* 公众号:bugstack虫洞栈
* Create by 小傅哥(fustack)
*/
public class DBContextHolder {
private static final ThreadLocal<String> dbKey = new ThreadLocal<String>();
private static final ThreadLocal<String> tbKey = new ThreadLocal<String>();
public static void setDBKey(String dbKeyIdx){
dbKey.set(dbKeyIdx);
}
public static String getDBKey(){
return dbKey.get();
}
public static void setTBKey(String tbKeyIdx){
tbKey.set(tbKeyIdx);
}
public static String getTBKey(){
return tbKey.get();
}
public static void clearDBKey(){
dbKey.remove();
}
public static void clearTBKey(){
tbKey.remove();
}
}
package cn.bugstack.middleware.db.router;
/**
* 博客:https://bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获!
* 公众号:bugstack虫洞栈
* Create by 小傅哥(fustack)
*/
public class DBRouterBase {
private String tbIdx;
public String getTbIdx() {
return DBContextHolder.getTBKey();
}
}
package cn.bugstack.middleware.db.router;
/**
* 博客:https://bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获!
* 公众号:bugstack虫洞栈
* Create by 小傅哥(fustack)
*/
public class DBRouterConfig {
private int dbCount; //分库数
private int tbCount; //分表数
public DBRouterConfig() {
}
public DBRouterConfig(int dbCount, int tbCount) {
this.dbCount = dbCount;
this.tbCount = tbCount;
}
public int getDbCount() {
return dbCount;
}
public void setDbCount(int dbCount) {
this.dbCount = dbCount;
}
public int getTbCount() {
return tbCount;
}
public void setTbCount(int tbCount) {
this.tbCount = tbCount;
}
}
package cn.bugstack.middleware.db.router;
import cn.bugstack.middleware.db.router.annotation.DBRouter;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 博客:https://bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获!
* 公众号:bugstack虫洞栈
* Create by 小傅哥(fustack)
*/
@Aspect
@Component("db-router-point")
public class DBRouterJoinPoint {
private Logger logger = LoggerFactory.getLogger(DBRouterJoinPoint.class);
@Autowired
private DBRouterConfig dbRouterConfig;
@Pointcut("@annotation(cn.bugstack.middleware.db.router.annotation.DBRouter)")
public void aopPoint() {
}
@Around("aopPoint()")
public Object doRouter(ProceedingJoinPoint jp) throws Throwable {
Method method = getMethod(jp);
DBRouter dbRouter = method.getAnnotation(DBRouter.class);
// String dbKey = dbRouter.key();
String dbKey = "userId";
if (StringUtils.isBlank(dbKey)) throw new RuntimeException("annotation DBRouter key is null!");
// 计算路由
String dbKeyAttr = getAttrValue(dbKey, jp.getArgs());
int size = dbRouterConfig.getDbCount() * dbRouterConfig.getTbCount();
// 扰动函数
int idx = (size - 1) & (dbKeyAttr.hashCode() ^ (dbKeyAttr.hashCode() >>> 16));
// 库表索引
int dbIdx = idx / dbRouterConfig.getTbCount() + 1;
int tbIdx = idx - dbRouterConfig.getTbCount() * (dbIdx - 1);
// 设置到ThreadLocal
DBContextHolder.setDBKey(String.format("%02d", dbIdx));
DBContextHolder.setTBKey(String.format("%02d", tbIdx));
logger.info("数据库路由 method:{} dbIdx:{} tbIdx:{}", method.getName(), dbIdx, tbIdx);
try {
return jp.proceed();
} finally {
DBContextHolder.clearDBKey();
DBContextHolder.clearTBKey();
}
}
private Method getMethod(JoinPoint jp) throws NoSuchMethodException {
Signature sig = jp.getSignature();
MethodSignature methodSignature = (MethodSignature) sig;
return getClass(jp).getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
}
private Class<? extends Object> getClass(JoinPoint jp) throws NoSuchMethodException {
return jp.getTarget().getClass();
}
public String getAttrValue(String attr, Object[] args) {
String filedValue = null;
for (Object arg : args) {
try {
if (StringUtils.isBlank(filedValue)) {
filedValue = BeanUtils.getProperty(arg, attr);
} else {
break;
}
} catch (Exception e) {
logger.error("获取路由属性值失败 attr:{}", attr, e);
}
}
return filedValue;
}
}
package cn.bugstack.middleware.db.router.annotation;
import java.lang.annotation.*;
/**
* 博客:https://bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获!
* 公众号:bugstack虫洞栈
* Create by 小傅哥(fustack)
*/
//@Retention(RetentionPolicy.RUNTIME)
//@Target({ElementType.TYPE, ElementType.METHOD})
public @interface DBRouter {
String key() default "";
}
package cn.bugstack.middleware.db.router.config; package cn.bugstack.middleware.db.router.config;
import cn.bugstack.middleware.db.router.DBRouterConfig;
import cn.bugstack.middleware.db.router.dynamic.DynamicDataSource; import cn.bugstack.middleware.db.router.dynamic.DynamicDataSource;
import cn.bugstack.middleware.db.router.util.PropertyUtil; import cn.bugstack.middleware.db.router.util.PropertyUtil;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
...@@ -24,27 +25,41 @@ import java.util.Map; ...@@ -24,27 +25,41 @@ import java.util.Map;
@Configuration @Configuration
public class DataSourceAutoConfig implements EnvironmentAware { public class DataSourceAutoConfig implements EnvironmentAware {
private Map<String, Map<String, Object>> dataSourceMap = new HashMap<>();
private int dbCount; //分库数
private int tbCount; //分表数
@Bean @Bean
public DataSource dataSource() { public DBRouterConfig dbRouterConfig() {
return new DBRouterConfig(dbCount, tbCount);
}
@Bean
public DataSource dataSource() {
// 创建数据源
Map<Object, Object> targetDataSources = new HashMap<>(); Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("db01", new DriverManagerDataSource("jdbc:mysql://127.0.0.1:3306/bugstack?useUnicode=true", "root", "1234")); for (String dbInfo : dataSourceMap.keySet()) {
Map<String, Object> objMap = dataSourceMap.get(dbInfo);
targetDataSources.put(dbInfo, new DriverManagerDataSource(objMap.get("url").toString(), objMap.get("username").toString(), objMap.get("password").toString()));
}
// 设置数据源
DynamicDataSource dynamicDataSource = new DynamicDataSource(); DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setTargetDataSources(targetDataSources); dynamicDataSource.setTargetDataSources(targetDataSources);
return dynamicDataSource; return dynamicDataSource;
} }
@Override @Override
public void setEnvironment(Environment environment) { public void setEnvironment(Environment environment) {
String prefix = "router.jdbc.datasource."; String prefix = "router.jdbc.datasource.";
String dataSources = environment.getProperty(prefix + "list");
assert dataSources != null; dbCount = Integer.valueOf(environment.getProperty(prefix + "dbCount"));
for (String each : dataSources.split(",")) { tbCount = Integer.valueOf(environment.getProperty(prefix + "tbCount"));
Map<String, Object> dataSourceProps = PropertyUtil.handle(environment, prefix + each, Map.class);
String dataSources = environment.getProperty(prefix + "list");
for (String dbInfo : dataSources.split(",")) {
Map<String, Object> dataSourceProps = PropertyUtil.handle(environment, prefix + dbInfo, Map.class);
dataSourceMap.put(dbInfo, dataSourceProps);
} }
} }
......
package cn.bugstack.middleware.db.router.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 博客:https://bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获!
* 公众号:bugstack虫洞栈
* Create by 小傅哥(fustack)
*/
public class DataSourceProperties {
public static final String PREFIX = "spring.datasource";
private String username;
private String password;
private String url;
private String driverClassName;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
}
package cn.bugstack.middleware.db.router.dynamic; package cn.bugstack.middleware.db.router.dynamic;
import cn.bugstack.middleware.db.router.DBContextHolder;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/** /**
...@@ -11,7 +12,7 @@ public class DynamicDataSource extends AbstractRoutingDataSource { ...@@ -11,7 +12,7 @@ public class DynamicDataSource extends AbstractRoutingDataSource {
@Override @Override
protected Object determineCurrentLookupKey() { protected Object determineCurrentLookupKey() {
return "db01"; return "db" + DBContextHolder.getDBKey();
} }
} }
package cn.bugstack.middleware.test; package cn.bugstack.middleware.test;
import org.junit.Test;
/** /**
* 博客:https://bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获! * 博客:https://bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获!
* 公众号:bugstack虫洞栈 * 公众号:bugstack虫洞栈
...@@ -11,4 +13,29 @@ public class ApiTest { ...@@ -11,4 +13,29 @@ public class ApiTest {
System.out.println("Hi"); System.out.println("Hi");
} }
@Test
public void test_db_hash() {
String key = "小傅哥";
int dbCount = 2, tbCount = 32;
int size = dbCount * tbCount;
// 散列
int idx = (size - 1) & (key.hashCode() ^ (key.hashCode() >>> 16));
System.out.println(idx);
int dbIdx = idx / tbCount + 1;
int tbIdx = idx - tbCount * (dbIdx - 1);
System.out.println(dbIdx);
System.out.println(tbIdx);
}
@Test
public void test_str_format(){
System.out.println(String.format("db%02d", 1));
System.out.println(String.format("_%02d", 25));
}
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册