从AbstractRoutingDataSource说分库分表实现 路由规则

来源:互联网 发布:excel数据透视表素材 编辑:程序博客网 时间:2024/05/17 02:50

本文转自  http://wely.iteye.com/blog/2275725


很多人不知分库分表怎么实现,可能是把它想得复杂了。事实上,我们将复杂的事情分工后就简单了。如果仅仅是单库分表,那直接在代码中根据分表的维度得到表名后缀,如“0001”,然后比如在mybatis下,sql语句就可以这么写“select * from user_#tbIndex#”。程序中我们能够操作数据库中的表,是因为我们拿到了数据源DataSource,并由此getConnection(),因此对于分库分表,我们首先要实现的是动态数据源,我们根据路由规则确定要访问哪个数据源的哪个表。怎么实现数据源的切换呢?而且多个数据源的连接要怎么管理呢?

    Spring为我们提供了实现方案,核心类是AbstractRoutingDataSource,代码如下:

    

Java代码  收藏代码
  1. public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {  
  2.     private Map<Object, Object> targetDataSources;  
  3.     private Object defaultTargetDataSource;  
  4.     private boolean lenientFallback = true;  
  5.     private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();  
  6.     private Map<Object, DataSource> resolvedDataSources;  
  7.     private DataSource resolvedDefaultDataSource;  
  8.   
  9.     public AbstractRoutingDataSource() {  
  10.     }  
  11.   
  12.     public void setTargetDataSources(Map<Object, Object> targetDataSources) {  
  13.         this.targetDataSources = targetDataSources;  
  14.     }  
  15.   
  16.     public void setDefaultTargetDataSource(Object defaultTargetDataSource) {  
  17.         this.defaultTargetDataSource = defaultTargetDataSource;  
  18.     }  
  19.   
  20.     public void setLenientFallback(boolean lenientFallback) {  
  21.         this.lenientFallback = lenientFallback;  
  22.     }  
  23.   
  24.     public void setDataSourceLookup(DataSourceLookup dataSourceLookup) {  
  25.         this.dataSourceLookup = (DataSourceLookup)(dataSourceLookup != null?dataSourceLookup:new JndiDataSourceLookup());  
  26.     }  
  27.   
  28.     public void afterPropertiesSet() {  
  29.         if(this.targetDataSources == null) {  
  30.             throw new IllegalArgumentException("Property \'targetDataSources\' is required");  
  31.         } else {  
  32.             this.resolvedDataSources = new HashMap(this.targetDataSources.size());  
  33.             Iterator var2 = this.targetDataSources.entrySet().iterator();  
  34.   
  35.             while(var2.hasNext()) {  
  36.                 Entry entry = (Entry)var2.next();  
  37.                 Object lookupKey = this.resolveSpecifiedLookupKey(entry.getKey());  
  38.                 DataSource dataSource = this.resolveSpecifiedDataSource(entry.getValue());  
  39.                 this.resolvedDataSources.put(lookupKey, dataSource);  
  40.             }  
  41.   
  42.             if(this.defaultTargetDataSource != null) {  
  43.                 this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);  
  44.             }  
  45.   
  46.         }  
  47.     }  
  48.   
  49.     protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {  
  50.         if(dataSource instanceof DataSource) {  
  51.             return (DataSource)dataSource;  
  52.         } else if(dataSource instanceof String) {  
  53.             return this.dataSourceLookup.getDataSource((String)dataSource);  
  54.         } else {  
  55.             throw new IllegalArgumentException("Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);  
  56.         }  
  57.     }  
  58.   
  59.     public Connection getConnection() throws SQLException {  
  60.         return this.determineTargetDataSource().getConnection();  
  61.     }  
  62.   
  63.     public Connection getConnection(String username, String password) throws SQLException {  
  64.         return this.determineTargetDataSource().getConnection(username, password);  
  65.     }  
  66.   
  67.     protected DataSource determineTargetDataSource() {  
  68.         Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");  
  69.         Object lookupKey = this.determineCurrentLookupKey();  
  70.         DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);  
  71.         if(dataSource == null && (this.lenientFallback || lookupKey == null)) {  
  72.             dataSource = this.resolvedDefaultDataSource;  
  73.         }  
  74.   
  75.         if(dataSource == null) {  
  76.             throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");  
  77.         } else {  
  78.             return dataSource;  
  79.         }  
  80.     }  
  81.   
  82.     protected Object resolveSpecifiedLookupKey(Object lookupKey) {  
  83.         return lookupKey;  
  84.     }  
  85.   
  86.     protected abstract Object determineCurrentLookupKey();  
  87. }  

     

    AbstractRoutingDataSource实现了AbstractDataSource,该抽象类又继承了javax.sql.DataSource接口。我们常用的org.apache.commons.dbcp.BasicDataSource就是实现了这个接口,该接口的核心方法是getConnection(),AbstractRoutingDataSource实现该方法如下:

 

Java代码  收藏代码
  1. public Connection getConnection() throws SQLException {  
  2.     return this.determineTargetDataSource().getConnection();  
  3. }  

 

显然我们要关注选择目标数据源的方法,该方法中两个重要的地方是determineCurrentLookupKey()方法和属性resolvedDataSources。determineCurrentLookupKey()是个抽象方法,需要我们自己去实现,返回的是当前要操作的数据源的标识。resolvedDataSources和resolvedDefaultDataSource是在bean实例化后的操作得到的,即afterPropertiesSet()。下面给出bean的配置:

 

Xml代码  收藏代码
  1. <bean id="dynamicDataSource" class="org.javared.wely.dao.db.DynamicDataSource">    
  2.    <property name="targetDataSources">       
  3.       <map key-type="java.lang.String">       
  4.          <entry key="db1" value-ref="dataSource1"/>       
  5.          <entry key="db2" value-ref="dataSource2"/>       
  6.       </map>       
  7.    </property>       
  8.    <property name="defaultTargetDataSource" ref="dataSource"/>      
  9. </bean>  

 

 DynamicDataSource需实现determineCurrentLookupKey()方法,代码如下:

 

Java代码  收藏代码
  1. public class DynamicDataSource extends AbstractRoutingDataSource {  
  2.     public DynamicDataSource() {  
  3.     }  
  4.   
  5.     protected Object determineCurrentLookupKey() {  
  6.         return DbContextHolder.getDbKey(); // ThreadLocal  
  7.     }  
  8. }  

 

显然,现在我们的重点是路由规则实现了,即根据某个或几个字段维度找到对应的DB和table,并把dbKey和tbIndex保存于当前线程中。

 

Xml代码  收藏代码
  1.   <bean id="dbRouter" class="org.javared.wely.dao.db.DBRouterImpl">  
  2.     <property name="dbRules">  
  3.         <list>  
  4.             <ref bean="dbRule1" />  
  5.         </list>  
  6.     </property>  
  7. </bean>  
  8.   
  9. <bean id="dbRule1" class="org.javared.wely.db.DbRule">  
  10.                <!-- 维度字段计算得到的long值范围 -->  
  11.     <property name="routeFieldStart" value="0"></property>  
  12.     <property name="routeFieldEnd" value="9200000000000000000"></property>  
  13.                <!-- db个数 -->  
  14.     <property name="dbNumber" value="2"></property>  
  15.                <!-- 路由规则,分表,分库,既分库又分表 -->  
  16.     <property name="routeType" value="2"></property>  
  17.                <!-- 每个库里分表个数 -->  
  18.     <property name="tableNumber" value="2"></property>  
  19.     <property name="dbKeys">  
  20.         <list>  
  21.             <value>db1</value>  
  22.             <value>db2</value>  
  23.         </list>  
  24.     </property>  
  25. </bean>  

 

Java代码  收藏代码
  1. public String route(String fieldId) {  
  2.     if(StringUtils.isEmpty(fieldId)) {  
  3.         throw new IllegalArgumentException("dbsCount and tablesCount must be both positive!");  
  4.     } else {  
  5.             // base64编码得到的字符串取hashcode  
  6.         int routeFieldInt = RouteUtils.getResourceCode(fieldId);   
  7.         String dbKey = getDbKey(this.dbRules, routeFieldInt);  
  8.         return dbKey;  
  9.     }  
  10. }  
  11.   
  12. public static String getDbKey(List<DbRule> rules, int routeFieldInt) {  
  13.         Object dbRule = null;  
  14.         if(rules != null && rules.size() > 0) {  
  15.             String dbKey = null;  
  16.             Iterator<DbRule> iter = rules.iterator();  
  17.             while(iter.hasNext()) {  
  18.                     DbRule item = iter.next();  
  19.                     if(item.getDbKeys() != null && item.getDbNumber() != 0) {  
  20.                         long dbIndex = 0L;  
  21.                         long tbIndex = 0L;  
  22.                         long mode = (long)item.getDbNumber();  
  23.                         String tableIndex;  
  24.                         if(item.getRouteType() == 2 && item.getTableNumber() != 0) {  
  25.                            // 分库又分表  
  26.                             mode = (long)(item.getDbNumber() * item.getTableNumber());  
  27.                             dbIndex = (long)routeFieldInt % mode / (long)item.getTableNumber();  
  28.                             tbIndex = (long)(routeFieldInt % item.getTableNumber());  
  29.                             tableIndex = getFormateTableIndex(item.getTableIndexStyle(), tbIndex);  
  30.                             DbContextHolder.setTableIndex(tableIndex);  
  31.                         } else if(item.getRouteType() == 0) { // 只分库  
  32.                             mode = (long)item.getDbNumber();  
  33.                             dbIndex = (long)routeFieldInt % mode;  
  34.                         } else if(item.getRouteType() == 1) { // 只分表  
  35.                             tbIndex = (long)(routeFieldInt % item.getTableNumber());  
  36.                             tableIndex = getFormateTableIndex(item.getTableIndexStyle(), tbIndex);  
  37.                             DbContextHolder.setTableIndex(tableIndex);  
  38.                         }  
  39.   
  40.                         dbKey = (String)item.getDbKeys().get(Long.valueOf(dbIndex).intValue());  
  41.                         log.info("resource:{}------->dbkey:{},tableIndex:{},"new Object[]{Integer.valueOf(routeFieldInt), dbKey, Long.valueOf(tbIndex)});  
  42.                         DbContextHolder.setDbKey(dbKey);  
  43.                     }  
  44.                     break;  
  45.             }  
  46.   
  47.             return dbKey;  
  48.         } else {  
  49.             throw new IllegalArgumentException("dbsCount and tablesCount must be both positive!");  
  50.         }  
  51.     }  

 

Java代码  收藏代码
  1. public class RouteUtils {  
  2.     private static final Logger log = LoggerFactory.getLogger(RouteUtils.class);  
  3.     private static final String encode = "utf-8";  
  4.     private static final int resourceMax = 10000;  
  5.   
  6.     public RouteUtils() {  
  7.     }  
  8.   
  9.     public static int getHashCodeBase64(String routeValue) {  
  10.         int hashCode = 0;  
  11.   
  12.         try {  
  13.             String e = Base64Binrary.encodeBase64Binrary(routeValue.getBytes("utf-8"));  
  14.             hashCode = Math.abs(e.hashCode());  
  15.         } catch (Exception var3) {  
  16.             log.error("hashCode 失败", var3);  
  17.         }  
  18.   
  19.         return hashCode;  
  20.     }  
  21.   
  22.     public static int getResourceCode(String routeValue) {  
  23.         int hashCode = getHashCodeBase64(routeValue);  
  24.         int resourceCode = hashCode % 10000;  
  25.         return resourceCode;  
  26.     }  
  27.   
  28.     public static void main(String[] args) {  
  29.         String payid = "140331160123935469773";  
  30.         String resource = payid.substring(payid.length() - 4);  
  31.         int routeFieldInt = Integer.valueOf(resource).intValue();  
  32.         short mode = 1200;  
  33.         int dbIndex = routeFieldInt % mode / 200;  
  34.         int tbIndex = routeFieldInt % 200;  
  35.         System.out.println(dbIndex + "-->" + tbIndex);  
  36.     }  
  37. }  

 

    应用时,先执行dbRouter.route(field),这时dynamicDataSource.getConnection()得到的就是当前线程需要对应的数据源连接,DbContextHolder.getTableIndex()得到的是当前线程需要对应的表名后缀。

 

 最后,对于dbRouter.route(field)和DbContextHolder.getTableIndex(),我们可以用注解的方式来处理,这样程序员只需在代码中加入注解即可。下面给出一种解决方案:

Java代码  收藏代码
  1. @Retention(RetentionPolicy.RUNTIME)  
  2. @Target({ElementType.METHOD})  
  3. public @interface DoRoute {  
  4.     String routeField() default "userId";  
  5.   
  6.     String tableStyle() default "_0000";  
  7. }  
  8.   
  9. @Aspect  
  10. @Component  
  11. public class DBRouterInterceptor {  
  12.     private static final Logger log = LoggerFactory.getLogger(DBRouterInterceptor.class);  
  13.     private DBRouter dBRouter;  
  14.   
  15.     public DBRouterInterceptor() {  
  16.     }  
  17.   
  18.     @Pointcut("@annotation( com.jd.jr.baitiao.dbrouter.annotation.DoRoute)")  
  19.     public void aopPoint() {  
  20.     }  
  21.   
  22.     @Before("aopPoint()")  
  23.     public Object doRoute(JoinPoint jp) throws Throwable {  
  24.         long t1 = System.currentTimeMillis();  
  25.         boolean result = true;  
  26.         Method method = this.getMethod(jp);  
  27.         DoRoute doRoute = (DoRoute)method.getAnnotation(DoRoute.class);  
  28.         String routeField = doRoute.routeField();  
  29.         Object[] args = jp.getArgs();  
  30.         if(args != null && args.length > 0) {  
  31.             for(int i = 0; i < args.length; ++i) {  
  32.                 long t2 = System.currentTimeMillis();  
  33.                 String routeFieldValue = BeanUtils.getProperty(args[i], routeField);  
  34.                 if(StringUtils.isNotEmpty(routeFieldValue)) {  
  35.                     if("userId".equals(routeField)) {  
  36.                         this.dBRouter.doRouteByResource("" + RouteUtils.getResourceCode(routeFieldValue));  
  37.                     } else {  
  38.                         String resource = routeFieldValue.substring(routeFieldValue.length() - 4);  
  39.                         this.dBRouter.doRouteByResource(resource);  
  40.                     }  
  41.                     break;  
  42.                 }  
  43.             }  
  44.         }  
  45.   
  46.         log.info("doRouteTime{}" + (System.currentTimeMillis() - t1));  
  47.         return Boolean.valueOf(result);  
  48.     }  
  49.   
  50.     private Method getMethod(JoinPoint jp) throws NoSuchMethodException {  
  51.         Signature sig = jp.getSignature();  
  52.         MethodSignature msig = (MethodSignature)sig;  
  53.         return this.getClass(jp).getMethod(msig.getName(), msig.getParameterTypes());  
  54.     }  
  55.   
  56.     private Class<? extends Object> getClass(JoinPoint jp) throws NoSuchMethodException {  
  57.         return jp.getTarget().getClass();  
  58.     }  
  59.   
  60.     public DBRouter getdBRouter() {  
  61.         return this.dBRouter;  
  62.     }  
  63.   
  64.     public void setdBRouter(DBRouter dBRouter) {  
  65.         this.dBRouter = dBRouter;  
  66.     }  
  67. }  

 上面定义了一个切面,需要在spring配置文件中加上<aop:aspectj-autoproxy />,这样spring会发现切面并织入到匹配的目标bean中。

 

附:生产环境配置参数参考

 

Xml代码  收藏代码
  1. sqlMapConfig配置  
  2. <settings cacheModelsEnabled="false" enhancementEnabled="true"  
  3.         lazyLoadingEnabled="false" errorTracingEnabled="true" maxRequests="200"  
  4.         maxSessions="60" maxTransactions="20" useStatementNamespaces="true"  
  5.         defaultStatementTimeout="2" />  
 

 

 

<bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource">   <property name="driverClassName" value="${db.jdbc.driverClassName}" />   <property name="url" value="${db1.jdbc.url}" />   <property name="username" value="${db1.jdbc.username}" />   <property name="password" value="${db1.jdbc.password}" />   <property name="maxActive" value="20" />   <property name="maxIdle" value="3" />   <property name="maxWait" value="15000" />   <property name="timeBetweenEvictionRunsMillis" value="60000" />   <property name="minEvictableIdleTimeMillis" value="180000" /></bean>

 

<!-- MQ发消息线程池 --><bean id="taskMqExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor" >   <!-- 核心线程数  --><property name="corePoolSize" value="10" />   <!-- 最大线程数 --><property name="maxPoolSize" value="200" />   <!-- 队列最大长度 --><property name="queueCapacity" value="500" />   <!-- 线程池维护线程所允许的空闲时间 --><property name="keepAliveSeconds" value="5" />   <!-- 线程池对拒绝任务(无线程可用)的处理策略 --><property name="rejectedExecutionHandler">      <bean class="java.util.concurrent.ThreadPoolExecutor$DiscardPolicy" />   </property></bean>


0 1
原创粉丝点击