關(guān)于 HighAvailableDataSource
的介紹,我們可以看一下官方文檔相關(guān)的介紹,官方文檔主要介紹如下幾個(gè)作用:
- 節(jié)點(diǎn)路由 - 根據(jù)節(jié)點(diǎn)名稱指定路由,隨機(jī)路由,粘性隨機(jī)路由
- 節(jié)點(diǎn)配置 - 純手工配置節(jié)點(diǎn),根據(jù)配置文件生成節(jié)點(diǎn),根據(jù)ZooKeeper信息生成節(jié)點(diǎn)
- 節(jié)點(diǎn)健康檢查 - 基于ValidConnectionChecker的節(jié)點(diǎn)檢查機(jī)制扔涧,檢查間隔時(shí)間可根據(jù)運(yùn)行情況動(dòng)態(tài)調(diào)整。
我們接下來(lái)來(lái)測(cè)試一下這幾部分的內(nèi)容届谈。
路由節(jié)點(diǎn)
首先我們來(lái)測(cè)試路由節(jié)點(diǎn)功能枯夜,路由節(jié)點(diǎn)需要我們配置多個(gè)數(shù)據(jù)源,然后注入到 HighAvailableDataSource
中艰山,并最終生成 Datasource
湖雹。我們先來(lái)看一下配置類的信息 :
@org.springframework.context.annotation.Configuration
public class Configuration {
@Autowired
ApplicationContext applicationContext;
@Autowired
@Qualifier("dataSource1")
DataSource dataSource1;
@Autowired
@Qualifier("dataSource2")
DataSource dataSource2;
@Bean(initMethod = "init")
public DataSource dataSource1() {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUsername("root");
druidDataSource.setPassword("root");
druidDataSource.setUrl("jdbc:mysql://localhost:3306/information_schema?useSSL=false&useUnicode=true&characterEncoding=UTF-8");
return druidDataSource;
}
@Bean(initMethod = "init")
public DataSource dataSource2() {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUsername("root");
druidDataSource.setPassword("root");
druidDataSource.setUrl("jdbc:mysql://localhost:3306/information_schema?useSSL=false&useUnicode=true&characterEncoding=UTF-8");
return druidDataSource;
}
@Bean(initMethod = "init", destroyMethod = "destroy")
public DataSource dataSource() {
HighAvailableDataSource druidDataSource = new HighAvailableDataSource();
Map<String, DataSource> dataSourceMap = new HashMap<>();
dataSourceMap.put("dataSources1", dataSource1);
dataSourceMap.put("default", dataSource1);
dataSourceMap.put("dataSources2", dataSource2);
druidDataSource.setDataSourceMap(dataSourceMap);
druidDataSource.setSelector("byName");
return druidDataSource;
}
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws IOException {
SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
sqlSessionFactory.setDataSource(dataSource);
sqlSessionFactory.setMapperLocations(applicationContext.getResources("classpath:mapper/*.xml"));
return sqlSessionFactory;
}
}
如圖,我們首先要生成了兩個(gè) Datasource 曙搬, 最后注入到 HighAvailableDataSource
摔吏,有些同學(xué)會(huì)注意到,這里不是使用 ObjectProvider
來(lái)獲取多個(gè)數(shù)據(jù)源數(shù)據(jù)源纵装,也是經(jīng)過(guò)一位大佬的提醒征讲,我才意識(shí)到這個(gè)問(wèn)題,構(gòu)造注入的方式是沒(méi)法解決循環(huán)依賴的橡娄,而這里恰好有循環(huán)依賴的問(wèn)題诗箍。
回到正題,我們最后初始化完成后挽唉,日志會(huì)打印這兩個(gè)數(shù)據(jù)源初始化完成滤祖。接著我們就可以像使用同個(gè)數(shù)據(jù)源來(lái)使用他了。
根據(jù)ZooKeeper生成節(jié)點(diǎn)集合
在開始Demo前瓶籽,我們需要先在 zookeeper 注冊(cè)數(shù)據(jù)源相關(guān)的節(jié)點(diǎn)匠童,如下:
ZookeeperNodeRegister register = new ZookeeperNodeRegister();
register.setZkConnectString("127.0.0.1:2181");
register.setPath("/ha-druid-datasources");
register.init();
List<ZookeeperNodeInfo> payload = new ArrayList<ZookeeperNodeInfo>();
ZookeeperNodeInfo node = new ZookeeperNodeInfo();
node.setPrefix("ha");
node.setHost("127.0.0.1");
node.setPort(3306);
node.setDatabase("information_schema");
node.setUsername("root");
node.setPassword("root");
payload.add(node);
register.register("datasource1", payload);
ZookeeperNodeRegister register2 = new ZookeeperNodeRegister();
register2.setZkConnectString("127.0.0.1:2181");
register2.setPath("/ha-druid-datasources");
register2.init();
List<ZookeeperNodeInfo> payload2 = new ArrayList<ZookeeperNodeInfo>();
ZookeeperNodeInfo node2 = new ZookeeperNodeInfo();
node2.setPrefix("ha");
node2.setHost("127.0.0.1");
node2.setPort(3307);
node2.setDatabase("information_schema");
node2.setUsername("root");
node2.setPassword("root");
payload2.add(node2);
register2.register("datasource2", payload2);
Thread.sleep(1000 * 60 * 60);
我們注冊(cè)了兩個(gè)數(shù)據(jù)源信息,分別是本地的 3306 和 3307 端口塑顺,然后停止該線程一個(gè)小時(shí)汤求,我們需要注意的是假如線程掛了,那么注冊(cè)的臨時(shí)幾點(diǎn)也將消失,所以需要 sleep 首昔,接下來(lái)我們到zk 上看一下注冊(cè)節(jié)點(diǎn)的信息如下:
zk 準(zhǔn)備好后我們使用對(duì)數(shù)據(jù)源進(jìn)行配置赋朦,如下:
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws IOException {
SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
sqlSessionFactory.setDataSource(dataSource);
sqlSessionFactory.setMapperLocations(applicationContext.getResources("classpath:mapper/*.xml"));
return sqlSessionFactory;
}
@Bean(initMethod = "init", destroyMethod = "destroy")
public DataSource dataSource(ZookeeperNodeListener zkNodeListener) {
HighAvailableDataSource druidDataSource = new HighAvailableDataSource();
druidDataSource.setNodeListener(zkNodeListener);
druidDataSource.setSelector("random");
druidDataSource.setPoolPurgeIntervalSeconds(60);
druidDataSource.setAllowEmptyPoolWhenUpdate(false);
return druidDataSource;
}
@Bean
public ZookeeperNodeListener zkNodeListener() {
ZookeeperNodeListener zookeeperNodeListener = new ZookeeperNodeListener();
zookeeperNodeListener.setZkConnectString("localhost:2181");
zookeeperNodeListener.setPath("/ha-druid-datasources");
zookeeperNodeListener.setUrlTemplate("jdbc:mysql://${host}:${port}/${database}?useUnicode=true");
zookeeperNodeListener.setPrefix("ha");
return zookeeperNodeListener;
}
與之前使用兩個(gè) datasource
不同绎签,這里是直接注入了一個(gè)zookeeperNodeListener
,即不使用默認(rèn)的隨機(jī) selector
濒募。接著我們啟動(dòng)服務(wù)巧骚,可以看到以下信息赊颠,代表我們數(shù)據(jù)源初始化成功。
我們可以先測(cè)試一下當(dāng)前數(shù)據(jù)是否可用劈彪,然后試著斷開剛才
sleep
的線程竣蹦,可以看到日志打印出如下信息,刪除節(jié)點(diǎn)成功:還有關(guān)閉數(shù)據(jù)源的日志如下:
我們可以看到這里只刪除了
datasource1
, 我們重新進(jìn)行測(cè)試沧奴,還是能正常獲取到 datasource2
痘括。這里主要是我們?cè)O(shè)置了如下屬性,所以并不會(huì)刪除所有的節(jié)點(diǎn):
druidDataSource.setAllowEmptyPoolWhenUpdate(false);
還有最后一個(gè)部分就是 節(jié)點(diǎn)健康檢查 滔吠,這部分主要是一些配置相關(guān)的纲菌,就不進(jìn)行細(xì)講了,有興趣的同學(xué)可以自己配置試一下疮绷。