百木园-与人分享,
就是让自己快乐。

聊聊Spring的主从数据库配置

在应对日渐复杂的业务环境,单个数据库所能承载的压力已经远远不够。很多业务中诞生了主从数据库的架构模型,将数据读写进行分离,主库写,从库读,以提升服务的吞吐量。

在进行代码设计的时候,我们很自然会想到一个问题,一个业务操作,往往会包括读 和 写,例如在实现一个阅读点击量的简单需求的时候,是不是需要先查询一下原来有多少点击量Num,然后再给这个获取到的数据Num进行+1操作呢?

那么问题来了:

如果很多人同时点击,都在给这个Num进行+1,咱们姑且不论数据没办法实现真实稳定的问题。就单从主库和从库切换上来说,是不是已经会产生问题呢?

仔细分析一下,假如用户A此时在调用接口读取数据,而用户B在调用接口进行写。这个时候会有两种情况出现,数据库操作全局变量在经过A操作之后,变成了从库,那么这时候B写的时候就会写到从库里面了。第二种情况是,A还没有完成读取从库的行为,B将全局变量设置为主库,数据能够正常写入主库,但是问题也出现了,此时A读取数据的时候可能就是主库了,读写分离的意义也就荡然无存了。而线上实际情况,往往比这个复杂一点。在我经历的项目中,读写数据的行为稍有不当,可能带来的是项目的巨大损失。

回顾一下上篇文章:各扫门前雪的ThreadLocal

我提到过,ThreadLocal是线程安全的,借助ThreadLocal,我们可以比较好的规避上面的这个问题。聪明的你一定马上会想到,对了,把这个数据库切换的全局变量,变成一个线程的“本地”变量,不就安全多了吗?

 

下面我们来简单写个实现类

主从库切换类,基于ThreadLocal实现调用接口线程的安全性。

public class DynamicDataSource extends AbstractRoutingDataSource {

    private final static Logger LOGGER = LoggerFactory.getLogger(DynamicDataSource.class);
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
@Override
protected Object determineCurrentLookupKey() {
String dataSource
= getDataSource();return dataSource; } /** * 设置数据源 * * @param dataSource */ public static void setDataSource(String dataSource) {
CONTEXT_HOLDER.set(dataSource); } /** * 获取数据源 * */ public static String getDataSource() {
String dataSource
= CONTEXT_HOLDER.get();
// 如果没有指定数据源,使用默认数据源 if (null == dataSource) { DynamicDataSource.setDataSource(DataSourceEnum.MASTER.getDefault()); } return CONTEXT_HOLDER.get(); } /** * 清除数据源 */ public static void clearDataSource() {
CONTEXT_HOLDER.remove(); } }

 

>> 当需要进行数据库操作的时候,调用

DynamicDataSource.setDataSource(DataSourceEnum.MASTER.getName());
或者 DynamicDataSource.setDataSource(DataSourceEnum.SLAVE.getName());

切换数据库;

>> 用完了,调用

DynamicDataSource.clearDataSource();

以便下一次继续使用

 

其他关联数据和配置:

主从库枚举:

public enum DataSourceEnum {

    // 主库
    MASTER(\"masterDataSource\", true),
    // 从库
    SLAVE(\"slaveDataSource\", false),;

    // 数据源名称
    private String name;
    // 是否是默认数据源
    private boolean master;

    DataSourceEnum(String name, boolean master) {
        this.name = name;
        this.master = master;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isMaster() {
        return master;
    }

    public void setMaster(boolean master) {
        this.master = master;
    }

    public String getDefault() {
        String defaultDataSource = \"\";
        for (DataSourceEnum dataSourceEnum : DataSourceEnum.values()) {
            if (!\"\".equals(defaultDataSource)) {
                break;
            }
            if (dataSourceEnum.master) {
                defaultDataSource = dataSourceEnum.getName();
            }
        }
        return defaultDataSource;
    }

}

 

主从库的配置如下:

    <!-- 主库数据源 -->
    <bean id=\"masterDataSource\" class=\"com.alibaba.druid.pool.DruidDataSource\" init-method=\"init\"
          destroy-method=\"close\">
        <!-- 基本属性 url、user、password -->
        <property name=\"driverClassName\" value=\"${master.jdbc.driver}\"/>
        <property name=\"url\" value=\"${master.jdbc.url}\"/>
        <property name=\"username\" value=\"${master.jdbc.username}\"/>
        <property name=\"password\" value=\"${master.jdbc.password}\"/>
        <!-- 配置初始化大小、最小、最大 -->
        <property name=\"initialSize\" value=\"1\"/>
        <property name=\"minIdle\" value=\"1\"/>
        <property name=\"maxActive\" value=\"20\"/>
        <!-- 配置获取连接等待超时的时间 -->
        <property name=\"maxWait\" value=\"60000\"/>
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name=\"timeBetweenEvictionRunsMillis\" value=\"60000\"/>
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name=\"minEvictableIdleTimeMillis\" value=\"300000\"/>
        <!-- 校验语句 -->
        <property name=\"validationQuery\" value=\"SELECT 1\"/>
        <property name=\"testWhileIdle\" value=\"true\"/>
        <property name=\"testOnBorrow\" value=\"false\"/>
        <property name=\"testOnReturn\" value=\"false\"/>
        <!-- 配置监控统计拦截的filters -->
        <property name=\"filters\" value=\"stat\"/>
    </bean>

    <!-- 从库数据源 -->
    <bean id=\"slaveDataSource\" class=\"com.alibaba.druid.pool.DruidDataSource\" init-method=\"init\" destroy-method=\"close\">
        <!-- 基本属性 url、user、password -->
        <property name=\"driverClassName\" value=\"${slave.jdbc.driver}\"/>
        <property name=\"url\" value=\"${slave.jdbc.url}\"/>
        <property name=\"username\" value=\"${slave.jdbc.username}\"/>
        <property name=\"password\" value=\"${slave.jdbc.password}\"/>
        <!-- 配置初始化大小、最小、最大 -->
        <property name=\"initialSize\" value=\"1\"/>
        <property name=\"minIdle\" value=\"1\"/>
        <property name=\"maxActive\" value=\"20\"/>
        <!-- 配置获取连接等待超时的时间 -->
        <property name=\"maxWait\" value=\"60000\"/>
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name=\"timeBetweenEvictionRunsMillis\" value=\"60000\"/>
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name=\"minEvictableIdleTimeMillis\" value=\"300000\"/>
        <!-- 校验语句 -->
        <property name=\"validationQuery\" value=\"SELECT 1\"/>
        <property name=\"testWhileIdle\" value=\"true\"/>
        <property name=\"testOnBorrow\" value=\"false\"/>
        <property name=\"testOnReturn\" value=\"false\"/>
        <!-- 配置监控统计拦截的filters -->
        <property name=\"filters\" value=\"stat\"/>
    </bean>

 

好了 ,代码虽然看起来挺简单的,但也能说明ThreadLocal在实现读写分离时候的有它一席用武之地咯。实际应用场景,会有更复杂的处理。

备注:此案例借用了网上的一些资源,做了简化处理,只作为演示说明使用。

 


来源:https://www.cnblogs.com/thomson-fred/p/15972398.html
本站部分图文来源于网络,如有侵权请联系删除。

未经允许不得转载:百木园 » 聊聊Spring的主从数据库配置

相关推荐

  • 暂无文章