ToB企服应用市场:ToB评测及商务社交产业平台

标题: 聊聊Spring的主从数据库配置 [打印本页]

作者: 惊雷无声    时间: 2022-9-16 17:15
标题: 聊聊Spring的主从数据库配置
在应对日渐复杂的业务环境,单个数据库所能承载的压力已经远远不够。很多业务中诞生了主从数据库的架构模型,将数据读写进行分离,主库写,从库读,以提升服务的吞吐量。
在进行代码设计的时候,我们很自然会想到一个问题,一个业务操作,往往会包括读 和 写,例如在实现一个阅读点击量的简单需求的时候,是不是需要先查询一下原来有多少点击量Num,然后再给这个获取到的数据Num进行+1操作呢?
那么问题来了:
如果很多人同时点击,都在给这个Num进行+1,咱们姑且不论数据没办法实现真实稳定的问题。就单从主库和从库切换上来说,是不是已经会产生问题呢?
仔细分析一下,假如用户A此时在调用接口读取数据,而用户B在调用接口进行写。这个时候会有两种情况出现,数据库操作全局变量在经过A操作之后,变成了从库,那么这时候B写的时候就会写到从库里面了。第二种情况是,A还没有完成读取从库的行为,B将全局变量设置为主库,数据能够正常写入主库,但是问题也出现了,此时A读取数据的时候可能就是主库了,读写分离的意义也就荡然无存了。而线上实际情况,往往比这个复杂一点。在我经历的项目中,读写数据的行为稍有不当,可能带来的是项目的巨大损失。
回顾一下上篇文章:各扫门前雪的ThreadLocal
我提到过,ThreadLocal是线程安全的,借助ThreadLocal,我们可以比较好的规避上面的这个问题。聪明的你一定马上会想到,对了,把这个数据库切换的全局变量,变成一个线程的“本地”变量,不就安全多了吗?
 
下面我们来简单写个实现类
主从库切换类,基于ThreadLocal实现调用接口线程的安全性。
  1. public class DynamicDataSource extends AbstractRoutingDataSource {
  2.     private final static Logger LOGGER = LoggerFactory.getLogger(DynamicDataSource.class);
  3.     private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();<br>
  4.     @Override
  5.     protected Object determineCurrentLookupKey() {<br>
  6.         String dataSource = getDataSource();return dataSource;
  7.     }
  8.     /**
  9.      * 设置数据源
  10.      *
  11.      * @param dataSource
  12.      */
  13.     public static void setDataSource(String dataSource) {<br>
  14.         CONTEXT_HOLDER.set(dataSource);
  15.     }
  16.     /**
  17.      * 获取数据源
  18.      *
  19.      */
  20.     public static String getDataSource() {<br>
  21.         String dataSource = CONTEXT_HOLDER.get();<br>        // 如果没有指定数据源,使用默认数据源
  22.         if (null == dataSource) {
  23.             DynamicDataSource.setDataSource(DataSourceEnum.MASTER.getDefault());
  24.         }
  25.         return CONTEXT_HOLDER.get();
  26.     }
  27.     /**
  28.      * 清除数据源
  29.      */
  30.     public static void clearDataSource() {<br>
  31.         CONTEXT_HOLDER.remove();
  32.     }
  33. }
复制代码
 
>> 当需要进行数据库操作的时候,调用
  1. DynamicDataSource.setDataSource(DataSourceEnum.MASTER.getName());
复制代码
  1. 或者 DynamicDataSource.setDataSource(DataSourceEnum.SLAVE.getName());
复制代码
切换数据库;
>> 用完了,调用
  1. DynamicDataSource.clearDataSource();
复制代码
以便下一次继续使用
 
其他关联数据和配置:
主从库枚举:
  1. public enum DataSourceEnum {
  2.     // 主库
  3.     MASTER("masterDataSource", true),
  4.     // 从库
  5.     SLAVE("slaveDataSource", false),;
  6.     // 数据源名称
  7.     private String name;
  8.     // 是否是默认数据源
  9.     private boolean master;
  10.     DataSourceEnum(String name, boolean master) {
  11.         this.name = name;
  12.         this.master = master;
  13.     }
  14.     public String getName() {
  15.         return name;
  16.     }
  17.     public void setName(String name) {
  18.         this.name = name;
  19.     }
  20.     public boolean isMaster() {
  21.         return master;
  22.     }
  23.     public void setMaster(boolean master) {
  24.         this.master = master;
  25.     }
  26.     public String getDefault() {
  27.         String defaultDataSource = "";
  28.         for (DataSourceEnum dataSourceEnum : DataSourceEnum.values()) {
  29.             if (!"".equals(defaultDataSource)) {
  30.                 break;
  31.             }
  32.             if (dataSourceEnum.master) {
  33.                 defaultDataSource = dataSourceEnum.getName();
  34.             }
  35.         }
  36.         return defaultDataSource;
  37.     }
  38. }
复制代码
 
主从库的配置如下:
  1.    
  2.     <bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
  3.           destroy-method="close">
  4.         
  5.         <property name="driverClassName" value="${master.jdbc.driver}"/>
  6.         <property name="url" value="${master.jdbc.url}"/>
  7.         <property name="username" value="${master.jdbc.username}"/>
  8.         <property name="password" value="${master.jdbc.password}"/>
  9.         
  10.         <property name="initialSize" value="1"/>
  11.         <property name="minIdle" value="1"/>
  12.         <property name="maxActive" value="20"/>
  13.         
  14.         <property name="maxWait" value="60000"/>
  15.         
  16.         <property name="timeBetweenEvictionRunsMillis" value="60000"/>
  17.         
  18.         <property name="minEvictableIdleTimeMillis" value="300000"/>
  19.         
  20.         <property name="validationQuery" value="SELECT 1"/>
  21.         <property name="testWhileIdle" value="true"/>
  22.         <property name="testOnBorrow" value="false"/>
  23.         <property name="testOnReturn" value="false"/>
  24.         
  25.         <property name="filters" value="stat"/>
  26.     </bean>
  27.    
  28.     <bean id="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
  29.         
  30.         <property name="driverClassName" value="${slave.jdbc.driver}"/>
  31.         <property name="url" value="${slave.jdbc.url}"/>
  32.         <property name="username" value="${slave.jdbc.username}"/>
  33.         <property name="password" value="${slave.jdbc.password}"/>
  34.         
  35.         <property name="initialSize" value="1"/>
  36.         <property name="minIdle" value="1"/>
  37.         <property name="maxActive" value="20"/>
  38.         
  39.         <property name="maxWait" value="60000"/>
  40.         
  41.         <property name="timeBetweenEvictionRunsMillis" value="60000"/>
  42.         
  43.         <property name="minEvictableIdleTimeMillis" value="300000"/>
  44.         
  45.         <property name="validationQuery" value="SELECT 1"/>
  46.         <property name="testWhileIdle" value="true"/>
  47.         <property name="testOnBorrow" value="false"/>
  48.         <property name="testOnReturn" value="false"/>
  49.         
  50.         <property name="filters" value="stat"/>
  51.     </bean>
复制代码
 
好了 ,代码虽然看起来挺简单的,但也能说明ThreadLocal在实现读写分离时候的有它一席用武之地咯。实际应用场景,会有更复杂的处理。
备注:此案例借用了网上的一些资源,做了简化处理,只作为演示说明使用。
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4