SpringBoot使用Druid实现数据库连接主备切换,非主从,当主连接不能使用时,自动切换到备用连接

目前项目有一个需求,项目为了安全性,使用了两台数据库服务器,同时,每台服务器上都有A\B两个网卡,A\B网之前不互通,虽然采购了双机热备软件,但是只能实现将两台服务器虚拟化成一台服务器,但是虚拟的这一个服务器还是有用两个不同网段的IP
而为了数据更加安全,就想让软件能自动切换数据库连接,即A网交换机或者网络断掉之后,能通过B网访问数据库
在网上找了很久,都只有druid主从多数据源的解决办法,且主数据源必须启动,无法解决我的需求
后来,有一个思路是监听druid的事件,但是也没找到大牛提供思路,没办法,只有自己看源码
终于在今天找到了解决办法。


先贴一个我们的网络示意图
A网 和 B网是分开的,不能相互通信

A网
B网
A网
B网
A网
b网
database1
database2
双机虚拟
应用服务器

开始,我模拟了启动完成后,关闭数据库服务,再通过页面访问数据的形式来模式数据库连接异常

在这里插入图片描述
我们可以看到,后台报了一个create connection SQLException,跟踪源码,我进入了com.alibaba.druid.pool.DruidDataSource$CreateConnectionThread.run
方法里面
在这里插入图片描述
原来每一次都是要新建
原来每一次创建连接的时候,都是将我们默认注入的url取出来使用
那么思路来了,如果当主数据库连接不成功后,我们将这个url更换为备机的应该是不是就能实现了
但是,bean创建后,属性值要怎么更改,我们怎么能判断是否需要更换(是否故障)

接着看源码
回到最先外层报错的位置
在这里插入图片描述
当创建连接成功时,可连接失败后都会调用setFailContinuous()
在这里插入图片描述
他会把一个属性值更改,我们只需要判断这个属性值就知道是不是异常
当正常时,failCotinuous是false
既然时属性,那么继承DruidDataSource 注入自己重写的DataSouce是不是能够生效,就能取到这一个属性

直接贴继承的DataSouce类

package com.example.demo.configuration.druid;

import com.alibaba.druid.pool.DruidDataSource;
import lombok.extern.slf4j.Slf4j;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
 * 自定义DataSource
 * com.example.demo.configuration.druid
 * CustomizeDruidDataSource
 *
 * @author LiuJingPing
 * @date 2019/8/1 10:50
 */
@Slf4j
public class CustomizeDruidDataSource extends DruidDataSource {
    private boolean lastInited;


    private String[] urls;

    public String[] getUrls() {
        return urls;
    }

    public void setUrls(String[] urls) {
        this.urls = urls;
    }

    @Override
    public void init() throws SQLException {
        lastInited = inited;
        super.init();
        if (!lastInited && inited) {
            new Thread(new ValidateUrlTask()).start();
        }
    }

    class ValidateUrlTask implements Runnable {

        @Override
        public void run() {
            while (true) {
                // 如果这个数据源被关闭了,就结束这个定时任务
                if (isClosed()) {
                    break;
                }
                //如果这个数据源已经被初始化了,同时连接异常才进行处理
                if (isInited() && isFailContinuous()) {
                    for (String thisUrl : urls) {
                        Connection connection = null;
                        try {
                            connection = DriverManager.getConnection(thisUrl, username, password);
                            jdbcUrl = thisUrl;
                        } catch (Exception ignored) {
                        } finally {
                            try {
                                connection.close();
                            } catch (Exception ignored) {
                            }
                        }
                    }
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ignored) {
                }
            }
        }
    }
}


实现思路
查源码看到,每一次获取连接时,都会调用init()
且第一个实例化时,也会进入init()
所以判断,只有init()之前inted属性为false 之后为true,那么就表示这个DataSource是被初始化了
这个时候就开启定时任务,定时通过isFailContinuous()判断链接时候异常,当异常时,取出我们所有Url去进行链接,当找到正常的可链接Url时,直接将jdbcUrl更新
那么druid在创建链接时,就会使用这个新的地址去进行链接的创建

最后一部,只要将原来的DruidDataSouce更改为我自己的CustomizeDruidDataSource 就可以了

因为我时采用的Spring boot ,所以之前是自动装配bean
我重新增加配置类

package com.example.demo.configuration.druid;

import com.alibaba.druid.pool.DruidDataSource;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.sql.SQLException;

/**
 * com.example.demo.configuration.druid
 * DruidConfig
 *
 * @author LiuJingPing
 * @date 2019/8/1 10:57
 */
@Configuration
@Data
public class DruidConfig {
    @Value("${spring.datasource.url}")
    private String url;
    @Value("${spring.datasource.username}")
    private String username;
    @Value("${spring.datasource.password}")
    private String password;
    @Value("${spring.datasource.driver-class-name}")
    private String driverClassName;
    @Value("${spring.datasource.urls}")
    private String[] urls;

    @Value("${spring.datasource.druid.filters}")
    private String filters;
    @Value("${spring.datasource.druid.max-active}")
    private int maxActive;
    @Value("${spring.datasource.druid.initial-size}")
    private int initialSize;
    @Value("${spring.datasource.druid.min-idle}")
    private int minIdle;
    @Value("${spring.datasource.druid.validation-query}")
    private String validationQuery;
    @Value("${spring.datasource.druid.test-while-idle}")
    private boolean testWhileIdle;
    @Value("${spring.datasource.druid.max-wait}")
    private int maxWait;
    @Value("${spring.datasource.druid.time-between-eviction-runs-millis}")
    private int timeBetweenEvictionRunsMillis;

    @Bean
    public DruidDataSource druidDataSource() throws SQLException {
        CustomizeDruidDataSource dataSource = new CustomizeDruidDataSource();
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrls(urls);
        dataSource.setFilters(filters);
        dataSource.setMaxActive(maxActive);
        dataSource.setInitialSize(initialSize);
        dataSource.setMinIdle(minIdle);
        dataSource.setValidationQuery(validationQuery);
        dataSource.setTestWhileIdle(testWhileIdle);
        dataSource.setMaxWait(maxWait);
        dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        return dataSource;
    }
}

注意urls这个属性,这个属性是我自己在配置文件中增加的

spring:
  datasource:
    type: com.example.demo.configuration.druid.CustomizeDruidDataSource
    url: jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=true&serverTimezone=GMT%2B8
    urls: jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=true&serverTimezone=GMT%2B8 , jdbc:mysql://218.89.187.94:9606/test?characterEncoding=utf8&useSSL=true&serverTimezone=GMT%2B8
 

第一个url是默认的url,就是程序启动时进行链接的Url
urls里面我配置了两个地址,第一个和url相同,第二个就是我们备用的链接地址

注意:因为我虽然时两个网段,但是实际上链接的是同一个数据库,账号密码是一样的,表结构也完全相同,而且两台数据库服务器也是挂载同一块磁盘阵列,所以数据也是完全相同的


写在最后

当客户给我提这个需求的时候,最开始是完全没有思路了,后面有了思路也没有解决方式,本来都要放弃了,说看下源码试一下,结果就实现了
在这里我想说,如果你有一点思路的时候,网络上又没有解决方式,不妨自己仔细看一下源码,去追踪一些主要的方法,你自己就会慢慢想通的

还有-----
这种实现虽然今天测试通过了,还不确定实际使用中会不会有什么问题
当然,如果你有更好的实现方式,不妨留言交流

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值