Druid - 路由多数据源

前言:公司搞活动,主数据库压力太大,决定将压力太大的接口迁移到从库查询

前提:不能对原有业务代码进行改动

多数据源路由器优缺点如下:

优点:

1、在不改变原来逻辑的基础上进行数据源路由,侵入性弱,改动小

缺点:

1、多数据源的数据同步可能会存在一定的延时,请做好评估工作


1.定于数据源枚举类

package com.xxx.xxx.xxx;

/**
 * 多数据源枚举类
 * ========================
 * @author linyantao
 * ========================
 */
public enum DataSources {

	/**
	 * 主库
	 */
	MASTER, 
	
	/**
	 * 从库
	 */
	SLAVE
}

2.通过TheadLocal来保存每条线程的数据源变量

package com.xxx.xxx.xxx;

/**
 * 数据源管理类
 * ========================
 * @author linyantao
 * ========================
 */
public class DataSourceTypeManager {

	/**
	 * 数据源线程池
	 */
	private static final ThreadLocal<DataSources> dataSourceTypes = new ThreadLocal<DataSources>() {
		@Override
		protected DataSources initialValue() {
			
			/**
			 * 默认取主库
			 */
			return DataSources.MASTER;
		}
	};

	/**
	 * 获取当前线程数据源
	 * @return
	 */
	public static DataSources get() {
		
		return dataSourceTypes.get();
	}

	/**
	 * 设置当前线程数据源
	 * @param dataSourceType
	 */
	public static void set(DataSources dataSourceType) {
		
		dataSourceTypes.set(dataSourceType);
	}

	/**
	 * 重置当前线程数据源
	 */
	public static void reset() {
		
		dataSourceTypes.set(DataSources.MASTER);
	}
	
	/**
	 * 删除当前线程数据源变量
	 */
	public static void remove() {
		
		dataSourceTypes.remove();
	}
}

3.定义 ThreadLocalRountingDataSource,继承AbstractRoutingDataSource

package com.xxx.xxx.xxx;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 路由数据源
 * ========================
 * @author linyantao
 * ========================
 */
public class ThreadLocalRountingDataSource extends AbstractRoutingDataSource{

	@Override
	protected Object determineCurrentLookupKey() {
		
		return DataSourceTypeManager.get();
	}

}


4.ThreadLocalRountingDataSource 设置多数据源映射

<!--主库连接-->  
    <bean id="dataSourceMaster" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">   
       
        <property name="driverClassName" value="${jdbc.driverClassName}" />
        <property name="url" value="${jdbc.master.url}" />  
        <property name="username" value="${jdbc.master.username}"/>  
        <property name="password" value="${jdbc.master.password}"/>  
        <!-- 配置初始化大小、最小、最大 -->  
        <property name="initialSize" value="${druid.initialSize}"/>  
        <property name="maxActive" value="${druid.maxActive}"/>  
        <property name="minIdle" value="${druid.minIdle}"/>  
        
        <!-- 配置获取连接等待超时的时间 -->  
        <property name="maxWait" value="${druid.maxWait}"/>  
        <!-- 解密密码必须要配置的项 -->
        <property name="filters" value="${druid.filters}" />
		<property name="connectionProperties" value="${druid.connectionProperties}" />
		
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->  
        <property name="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}"/>  
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->  
        <property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}"/>  
         
        <property name="validationQuery" value="${druid.validationQuery}"/>  
        <property name="testWhileIdle" value="${druid.testWhileIdle}"/>  
        <property name="testOnBorrow" value="${druid.testOnBorrow}"/>  
        <property name="testOnReturn" value="${druid.testOnReturn}"/>  
        
        <!-- 打开PSCache,并且指定每个连接上PSCache的大小   -->
        <property name="poolPreparedStatements" value="${druid.poolPreparedStatements}"/>  
        <property name="maxOpenPreparedStatements" value="${druid.maxOpenPreparedStatements}"/>  
          
    </bean>  
    
    <!--从库连接-->  
    <bean id="dataSourceSlave" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">   
       
        <property name="driverClassName" value="${jdbc.driverClassName}" />
        <property name="url" value="${jdbc.slave.url}" />  
        <property name="username" value="${jdbc.slave.username}"/>  
        <property name="password" value="${jdbc.slave.password}"/>  
        <!-- 配置初始化大小、最小、最大 -->  
        <property name="initialSize" value="${druid.initialSize}"/>  
        <property name="maxActive" value="${druid.maxActive}"/>  
        <property name="minIdle" value="${druid.minIdle}"/>  
        
        <!-- 配置获取连接等待超时的时间 -->  
        <property name="maxWait" value="${druid.maxWait}"/>  
        <!-- 解密密码必须要配置的项 -->
        <property name="filters" value="${druid.filters}" />
		<property name="connectionProperties" value="${druid.connectionProperties}" />
		
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->  
        <property name="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}"/>  
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->  
        <property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}"/>  
         
        <property name="validationQuery" value="${druid.validationQuery}"/>  
        <property name="testWhileIdle" value="${druid.testWhileIdle}"/>  
        <property name="testOnBorrow" value="${druid.testOnBorrow}"/>  
        <property name="testOnReturn" value="${druid.testOnReturn}"/>  
        
        <!-- 打开PSCache,并且指定每个连接上PSCache的大小   -->
        <property name="poolPreparedStatements" value="${druid.poolPreparedStatements}"/>  
        <property name="maxOpenPreparedStatements" value="${druid.maxOpenPreparedStatements}"/>  
          
    </bean> 
    
    <bean id="dataSource" class="com.xxx.xxx.xxx.ThreadLocalRountingDataSource">
        <property name="defaultTargetDataSource" ref="dataSourceMaster" />
        <property name="targetDataSources">
            <map key-type="com.xxx.xxx.xxx.DataSources">
                <entry key="MASTER" value-ref="dataSourceMaster"/>
                <entry key="SLAVE" value-ref="dataSourceSlave"/>
                <!-- 这里还可以加多个dataSource -->
            </map>
        </property>
    </bean> 

5.jdbc.properties 配置

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.master.url=jdbc:mysql://xxx.xx.xx.xx:3306/xxx?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
jdbc.master.username=xxx
jdbc.master.password=O+mjFGxvSCYBHUJYrapflD7RhWzdCpgJ97XA8cNUC2AjKSZCZCqj/CiM+ZoDx4rG7dk4HJDxMw6G874+e4MJRw==

jdbc.slave.url=jdbc:mysql://xxx.xx.xx.xx:3306/xxx?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
jdbc.slave.username=xxx
jdbc.slave.password=O+mjFGxvSCYBHUJYrapflD7RhWzdCpgJ97XA8cNUC2AjKSZCZCqj/CiM+ZoDx4rG7dk4HJDxMw6G874+e4MJRw==

#\u914d\u7f6e\u521d\u59cb\u5316\u5927\u5c0f
druid.initialSize=2
#\u914d\u7f6e\u521d\u59cb\u5316\u6700\u5927
druid.minIdle=2
#\u914d\u7f6e\u521d\u59cb\u5316\u6700\u5c0f
druid.maxActive=30
#\u914d\u7f6e\u83b7\u53d6\u8fde\u63a5\u7b49\u5f85\u8d85\u65f6\u7684\u65f6\u95f4
druid.maxWait=60000

druid.testWhileIdle=false
#\u89e3\u5bc6\u5bc6\u7801\u5fc5\u987b\u8981\u914d\u7f6e\u7684\u9879
druid.filters=config
druid.connectionProperties=config.decrypt=true
#\u914d\u7f6e\u95f4\u9694\u591a\u4e45\u624d\u8fdb\u884c\u4e00\u6b21\u68c0\u6d4b,\u68c0\u6d4b\u9700\u8981\u5173\u95ed\u7684\u7a7a\u95f2\u8fde\u63a5,\u5355\u4f4d\u662f\u6beb\u79d2
druid.timeBetweenEvictionRunsMillis=60000
#\u914d\u7f6e\u4e00\u4e2a\u8fde\u63a5\u5728\u6c60\u4e2d\u6700\u5c0f\u751f\u5b58\u7684\u65f6\u95f4,\u5355\u4f4d\u662f\u6beb\u79d2
druid.minEvictableIdleTimeMillis=300000

druid.validationQuery=SELECT 'x'
druid.testWhileIdle=true
druid.testOnBorrow=false
druid.testOnReturn=false

druid.poolPreparedStatements=true
druid.maxOpenPreparedStatements=20

6.切换数据源

默认线程使用的主库,如果要切换到从库,请使用如下语句:

DataSourceTypeManager.set(DataSources.SLAVE);
注:我在做压测的时候有一个现象,当线程池的线程全部打满的时候,后面的请求再进去,会复用前面线程所创建的变量,线程没有进行销毁,这是个很奇怪的现象,我至今没找到完美的解决办法。

我目前采用的方案是在切换从库的时候,加上 try catch finally,在finally这块,加上以下语句:

DataSourceTypeManager.remove();
在使用完当前线程切换的数据源之后,对线程变量进行清空。

这种方式是可以实现,但是我还是觉得不够完美,希望能完美解决的人可以给小弟指点一二,在此谢过。

7.使用SpringBoot当做容器启动的朋友,如果只配置如上这些启动的时候会报错:

报错信息大概意思是:当前有两个数据源,不晓得使用哪一个。

这是由于SpringBoot 跟 Spring jdbc的JAR包冲突导致的,要解决这个问题,在主库上面加上一个属性

<bean id="dataSourceMaster" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close" primary="true">  






  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值