基于spring-jdbc中JdbcTemplate实现查询高可用

2020-11-19 14:48:43 浏览数 (1)

在传统系统或网站架构中,一般都是使用单点,当然单点在应对并发访问量不是很大的场景下是能够应对自如的,并且单机不存在服务延迟,分布式事务等问题,部署也比较简单。

但是随着互联网的发展,传统的架构已经远远满足不了庞大的并发访问量,并且单机所带来的问题也日益显著,比如:1)系统挂掉所有用户都无法访问 2)当瞬时访问量比较大的

时候单机部署的站点,单纯的通过硬件的扩充已经解决不了性能瓶颈问题;现在比较流行的就是SOA或者微服务,归根结底其目的就是解决单机情况下的系统可用性,可靠性以及性能问题,这里简单描述一下从请求接受层到数据库层的通用架构方案:

1.反向代理层:比较常用的就是nginx做反向代理和负载均衡

2.站点层:tomcat做集群

3.服务层:各种rpc框架都提供了负载均衡(dubbo)

4.数据库层:大多使用DB cache方式,DB的话又分很多种主从复制方式(一主多从,双主以及其他数据库中间件实现的读写分离方案)

本篇文章对其他问题不做赘述,简单对读写分离中的多个从库实现负载均衡访问和提高查询高可用(基于JdbcTemplate实现),实现步骤如下

一、创建maven项目并添加最简依赖

1. 新建maven项目,项目结构如下图:

2. 添加依赖

<dependency>

<groupId>junit</groupId>

<artifactId>junit</artifactId>

<version>4.11</version>

<scope>test</scope>

</dependency>

<!-- mysql connector -->

<dependency>

<groupId>mysql</groupId>

<artifactId>mysql-connector-java</artifactId>

<version>5.1.41</version>

</dependency>

<!-- spring依赖 -->

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-jdbc</artifactId>

<version>4.3.7.RELEASE</version>

</dependency>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-context</artifactId>

<version>4.3.7.RELEASE</version>

</dependency>

<!-- druid jdbcpool -->

<dependency>

<groupId>com.alibaba</groupId>

<artifactId>druid</artifactId>

<version>1.1.2</version>

</dependency>

<!-- fastjson -->

<dependency>

<groupId>com.alibaba</groupId>

<artifactId>fastjson</artifactId>

<version>1.2.28</version>

</dependency>

<!-- log4j -->

<dependency>

<groupId>log4j</groupId>

<artifactId>log4j</artifactId>

<version>1.2.17</version>

</dependency>

<!-- commons -->

<dependency>

<groupId>org.apache.commons</groupId>

<artifactId>commons-lang3</artifactId>

<version>3.1</version>

</dependency>

二、添加配置文件
  1. 添加jdbc属性配置(jdbc.properties)

jdbc.trace=true

# 写库

jdbc.crmupdate.database=10.11.9.243:3306/test

jdbc.crmupdate.user=crm

jdbc.crmupdate.password=USszJ497whda

# 读库1

jdbc.crmquery1.database=10.11.9.243:3306/test

jdbc.crmquery1.user=crm

jdbc.crmquery1.password=USszJ497whda

# 读库2

jdbc.crmquery2.database=10.1.8.76:3306/test

jdbc.crmquery2.user=root

jdbc.crmquery2.password=root

2. 添加日志配置(log4j.properties)

### 设置###

log4j.rootLogger = info,stdout,D,E

### 输出信息到控制抬 ###

log4j.appender.stdout = org.apache.log4j.ConsoleAppender

log4j.appender.stdout.Target = System.out

log4j.appender.stdout.layout = org.apache.log4j.PatternLayout

log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n

### 输出DEBUG 级别以上的日志文件设置 ###

log4j.appender.D = org.apache.log4j.DailyRollingFileAppender

log4j.appender.D.File = logs/jdbcTemplateHA_debug.log

log4j.appender.D.Append = true

log4j.appender.D.Threshold = DEBUG

log4j.appender.D.layout = org.apache.log4j.PatternLayout

log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n

### 输出ERROR 级别以上的日志文件设置 ###

log4j.appender.E = org.apache.log4j.DailyRollingFileAppender

log4j.appender.E.File = logs/jdbcTemplateHA_error.log

log4j.appender.E.Append = true

log4j.appender.E.Threshold = ERROR

log4j.appender.E.layout = org.apache.log4j.PatternLayout

log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n

3. 添加spring数据源配置(spring-jdbc.xml)

<bean id="statFilter" class="com.alibaba.druid.filter.stat.StatFilter">

<property name="mergeSql" value="true" />

<property name="slowSqlMillis" value="3000" />

<property name="logSlowSql" value="true" />

</bean>

<!-- 使用druid连接池,并配置通用属性 -->

<bean id="parentDataSource" class="com.alibaba.druid.pool.DruidDataSource" abstract="true">

<property name="initialSize" value="5"/>

<property name="minIdle" value="5"/>

<property name="maxActive" value="100"/>

<property name="maxWait" value="30000"/>

<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" />

<property name="poolPreparedStatements" value="false" />

<property name="maxPoolPreparedStatementPerConnectionSize" value="20" />

<!-- 超过时间限制是否回收 -->

<property name="removeAbandoned" value="true"/>

<!-- 超时时间;单位为秒。180秒=3分钟 -->

<property name="removeAbandonedTimeout" value="180"/>

<!-- 关闭abanded连接时输出错误日志 -->

<property name="logAbandoned" value="false" />

<property name="proxyFilters">

<list>

<ref bean="statFilter"></ref>

</list>

</property>

</bean>

<!-- 配置主库(写库)数据源 -->

<bean id="crmUpdateDataSource" parent="parentDataSource">

<property name="url">

<value>jdbc:mysql://${jdbc.crmupdate.database}?useUnicode=true&amp;characterEncoding=gbk&amp;zeroDateTimeBehavior=convertToNull</value>

</property>

<property name="username" value="${jdbc.crmupdate.user}"/>

<property name="password" value="${jdbc.crmupdate.password}"/>

</bean>

<!-- 配置从库1数据源(读库) -->

<bean id="crmQuery1DataSource" parent="parentDataSource">

<property name="url">

<value>jdbc:mysql://${jdbc.crmquery1.database}?useUnicode=true&amp;characterEncoding=gbk&amp;zeroDateTimeBehavior=convertToNull</value>

</property>

<property name="username" value="${jdbc.crmquery1.user}"/>

<property name="password" value="${jdbc.crmquery1.password}"/>

</bean>

<!-- 配置从库2数据源(读库) -->

<bean id="crmQuery2DataSource" parent="parentDataSource">

<property name="url">

<value>jdbc:mysql://${jdbc.crmquery2.database}?useUnicode=true&amp;characterEncoding=gbk&amp;zeroDateTimeBehavior=convertToNull</value>

</property>

<property name="username" value="${jdbc.crmquery2.user}"/>

<property name="password" value="${jdbc.crmquery2.password}"/>

</bean>

<!-- 配置主库jdbcTemplate -->

<bean id="crmUpdate" class="org.springframework.jdbc.core.JdbcTemplate">

<property name="dataSource" ref="crmUpdateDataSource"/>

</bean>

<!-- 配置从库1jdbcTemplate -->

<bean id="crmQuery1" class="org.springframework.jdbc.core.JdbcTemplate">

<property name="dataSource" ref="crmQuery1DataSource"/>

</bean>

<!-- 配置从库2jdbcTemplate -->

<bean id="crmQuery2" class="org.springframework.jdbc.core.JdbcTemplate">

<property name="dataSource" ref="crmQuery2DataSource"/>

</bean>

三、编写实现查询HA的相关类

从图中可以看到,实现HA主要有上边标注的几个类,下边将逐个描述各个类的作用

1. BalanceNodeBean

该类是实现负载均衡的bean

2. NodeHealthValueBean

实现健康值检查的bean,

3. MyLoadBalance

根据可用JdbcTemplate列表实现负载均衡访问的工具类,代码如下:

/**

* 负载均衡器

* (1)支持各节点间的负载均衡

* (2)支持每个节点的权重设定

* (3)支持单个节点的动态故障隔离和故障恢复

* (4)支持每个节点的动态权重修正系数设定

*

* @author Typhoon

* @date 2017-09-17 18:39 Sunday

* @since V2.0

*/

public class MyLoadBalance {

/**

* 取得 将使用的 BalanceNode index

*

* @param serverList serverList

* @return 将使用的 BalanceNode index

*/

public static int getForwardServerIndex(

ArrayList<BalanceNodeBean> serverList) {

// check para

if(CollectionUtils.isEmpty(serverList)) {

return -1;

}

int len = serverList.size();

int result = -1;

if (len == 1) {

// (2)仅有一台server时

if (serverList.get(0).getDownFlag() == 1) {

// 无可用的server

result = -1;

} else {

// 仅这一台

result = 0;

}

return result;

}

// (3)有多台后端服务器

BalanceNodeBean bestBean = null;

BalanceNodeBean tempBean = null;

float currentWeight = 0;

// 所有服务器总权重

float totalEffectiveWeight = 0;

// 遍历所有服务器, 按照各台服务器的当前权值进行选择

for (int i = 0; i < len; i ) {

tempBean = serverList.get(i);

if (tempBean.getDownFlag() == 1) {

// Down掉的server,将被skip.

continue;

}

// 计算当前权值,包含权重修正系数

currentWeight = tempBean.getCurrentWeight() tempBean.getWeight()

* (1 tempBean.getCorrectionWeight());

tempBean.setCurrentWeight(currentWeight);

// 总的权重计算,包含权重修正系数

totalEffectiveWeight = tempBean.getWeight()

* (1 tempBean.getCorrectionWeight());

// 选择

if (bestBean == null

|| bestBean.getCurrentWeight() < tempBean

.getCurrentWeight()) {

// 选择当前权重最大的serverNode

bestBean = tempBean;

//

result = i;

}

}

if (bestBean == null) {

// 无可用的server

return -1;

}

if (bestBean != null) {

// 该服务器这次被选中,因此要降低权值,以便下次计算。

currentWeight = bestBean.getCurrentWeight() - totalEffectiveWeight;

bestBean.setCurrentWeight(currentWeight);

}

return result;

}

}

4. HABalanceSimpleJdbcOperations

和调用方直接打交道的类,实现了JdbcOperations接口,提供了和JdbcTemplate一样的功能(具体操作还是委托给具体的jdbcTemplate去完成),同时实现了PropertyChangeListener接口,对JdbcTemplate列表对应的健康值监听并完成健康值矫正操作.代码如下:

/**

* 高可用jdbcOperations

*

* @author Typhoon

* @date 2017-09-17 13:54 Sunday

* @since V2.0

*/

public class HABalanceSimpleJdbcOperations implements PropertyChangeListener, JdbcOperations {

/**

* LOG

*/

private static Logger log = Logger.getLogger(HABalanceSimpleJdbcOperations.class);

// private static org.slf4j.Logger log = LoggerFactory.getLogger(HABalanceSimpleJdbcOperations.class);

/**

* DB从库server列表

*/

private ArrayList<BalanceNodeBean> serverList = new ArrayList<BalanceNodeBean>();

/**

* DB从库server列表用的readWriteLock

*/

private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

/**

* 备机列表(查询库)

*/

protected List<JdbcOperations> crmB_N_JdbcList;

/**

* 主机ip(写库)

*/

protected JdbcOperations crmJdbc;

/**

* 构造器

*

* @param crmB_N_JdbcList

*/

public HABalanceSimpleJdbcOperations(

List<JdbcOperations> crmB_N_JdbcList,

JdbcOperations crmJdbc) {

this.crmB_N_JdbcList = crmB_N_JdbcList;

this.crmJdbc = crmJdbc;

// 生成相应的serverList

int len = this.crmB_N_JdbcList.size();

BalanceNodeBean nodeBean = null;

for (int i = 0; i < len; i ) {

nodeBean = new BalanceNodeBean();

// 初始状态为正常

nodeBean.setDownFlag(0);

nodeBean.setCorrectionWeight(0);

nodeBean.setCurrentWeight(0);

// add

this.serverList.add(nodeBean);

}

// Thread数

int threadNum = 1;

// 新建一个Threadpool

ExecutorService threadPool = Executors.newFixedThreadPool(threadNum);

HABalanceJdbcHealthCheckTask task = null;

for (int i = 0; i < threadNum; i ) {//使用threadNum个线程去监听jdbcTemplate列表的健康状态,如果出现健康值变动,触发监听器修改节点的健康状态

// 新作成一个Task

task = new HABalanceJdbcHealthCheckTask(this.crmB_N_JdbcList);

// add PropertyChangeListener

task.addPropertyChangeListener(this);

// 提交task到Threadpool

threadPool.execute(task);

}

}

/**

* healthyValue属性改变通知处理。 根据healthyValue修改节点的DownFlag和权重修正系数。

*/

public void propertyChange(PropertyChangeEvent event) {

if (event.getPropertyName().equalsIgnoreCase("healthyValue")) {

Object newBean = event.getNewValue();

if (newBean instanceof NodeHealthValueBean) {

NodeHealthValueBean valueBean = (NodeHealthValueBean) newBean;

int nodeIndex = valueBean.getNodeIndex();

//

int healthValue = valueBean.getHealthValue();

if (healthValue < 0) {

// 故障发生

this.setNodeDownFlag(nodeIndex, 1);

} else {

// 故障恢复

this.setNodeDownFlag(nodeIndex, 0);

}

// 权重修正系数

float correctionWeight = 0;

if (healthValue == 0) {

// 设置节点的权重修正系数为0

correctionWeight = 0;

} else if (healthValue == 1) {

// 设置节点的权重修正系数为 -0.1

correctionWeight = -0.1f;

} else if (healthValue == 2) {

// 设置节点的权重修正系数为 -0.3

correctionWeight = -0.3f;

} else if (healthValue == 3) {

// 设置节点的权重修正系数为 -0.5

correctionWeight = -0.5f;

} else if (healthValue == 4) {

// 设置节点的权重修正系数为 -1.0

correctionWeight = -1.0f;

}

// 设置节点的权重修正系数

this.setNodeCorrectionWeight(nodeIndex, correctionWeight);

// log

log.warn("nodeIndex=" nodeIndex ",healthValue="

healthValue ",correctionWeight=" correctionWeight);

}

}

}

/**

* 设置节点的downFlag

*

* @param nodeIndex

* @param downFlag

*/

public void setNodeDownFlag(int nodeIndex, int downFlag) {

// check para

if (nodeIndex < 0 || nodeIndex >= this.serverList.size()) {

return;

}

Lock writeLock = this.readWriteLock.writeLock();

//

writeLock.lock();

try {

BalanceNodeBean nodeBean = this.serverList.get(nodeIndex);

// set downFlag

nodeBean.setDownFlag(downFlag);

} finally {

writeLock.unlock();

}

}

/**

* 设置节点的权重修正系数

*

* @param nodeIndex

* @param downFlag

*/

public void setNodeCorrectionWeight(int nodeIndex, float correctionWeight) {

// check para

if (nodeIndex < 0 || nodeIndex >= this.serverList.size()) {

return;

}

Lock writeLock = this.readWriteLock.writeLock();

//

writeLock.lock();

try {

BalanceNodeBean nodeBean = this.serverList.get(nodeIndex);

// set correctionWeight

nodeBean.setCorrectionWeight(correctionWeight);

} finally {

writeLock.unlock();

}

}

/**

* 使用负载均衡器取得一个可用JdbcOperations。

*

* @return

*/

protected JdbcOperations getJdbcOperations() {

JdbcOperations result = null;

int resultIndex = -1;

Lock readLock = this.readWriteLock.readLock();

// lock

readLock.lock();

try {

// 取得一个server index

resultIndex = MyLoadBalance.getForwardServerIndex(this.serverList);

//log.warn("resultIndex=" resultIndex);

} finally {

// unlock

readLock.unlock();

}

log.info("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<获取的负载均衡jdbcOperations节点resultIndex:" resultIndex);

if (resultIndex >= 0) {

result = this.crmB_N_JdbcList.get(resultIndex);

}

return result;

}

@Override

public <T> List<T> query(String sql, RowMapper<T> rowMapper)

throws DataAccessException {

JdbcOperations jdbcOperation = getJdbcOperations();

//log.debug("当前jdbc信息:{}",ToStringBuilder.reflectionToString(jdbcOperation));

return jdbcOperation.query(sql, rowMapper);

}

@Override

public int update(String sql) throws DataAccessException {

return crmJdbc.update(sql);

}

}

由于实现了JdbcOperations接口,其定义的所有方法都要实现,此处忽略;另外可以观察到所有的读操作都是操作从库,写操作直接指定主库,从而实现了读写分离的操作.

5. HABalanceJdbcHealthCheckTask

该类实现了Runnable接口,可以独立运行,其作用是监听从库相关jdbcTemplate列表的健康值,如果有变化或者某个从库挂掉会通知其监听器修改jdbcTemplate节点的健康值,代码如下:

/**

* 高可用DB从库HealthCheck Task

*

* @author Typhoon

* @date 2017-09-17 14:04 Sunday

* @since V2.0

*/

public class HABalanceJdbcHealthCheckTask implements Runnable {

/**

* LOG

*/

private static Logger log = Logger.getLogger(HABalanceJdbcHealthCheckTask.class);

/**

* DB从库Jdbc列表

*/

private List<JdbcOperations> crmB_N_JdbcList;

/**

* Node健康值Bean列表

*/

private ArrayList<NodeHealthValueBean> healthyValueList = new ArrayList<NodeHealthValueBean>();

/**

* 运行中标志

*/

private volatile boolean runFlag = true;

/**

* 健康值 PropertyChangeSupport

*/

protected PropertyChangeSupport listeners = new PropertyChangeSupport(this);

public HABalanceJdbcHealthCheckTask(List<JdbcOperations> crmB_N_JdbcList) {

// DB从库Jdbc列表

this.crmB_N_JdbcList = crmB_N_JdbcList;

int len = this.crmB_N_JdbcList.size();

NodeHealthValueBean nodeBean = null;

for (int i = 0; i < len; i ) {

nodeBean = new NodeHealthValueBean();

nodeBean.setNodeIndex(i);

// 初始状态为正常

nodeBean.setHealthValue(0);

// add

this.healthyValueList.add(nodeBean);

}

}

/**

* 添加 健康值PropertyChangeListener

*

* @param listener

*/

public void addPropertyChangeListener(PropertyChangeListener listener) {

this.listeners.addPropertyChangeListener(listener);

}

/**

* 移除 健康值PropertyChangeListener

*

* @param listener

*/

public void removePropertyChangeListener(PropertyChangeListener listener) {

this.listeners.removePropertyChangeListener(listener);

}

/**

* 取得健康值

*

* @param jdbcOpers

* @return -1: 表示已经Down; 0-4级;

*/

private int getHealthyValue(JdbcOperations jdbcOpers) {

int result = 0;

try {

long beforeMS = System.currentTimeMillis();

// query 主要用来做DB存活性以及性能探测

jdbcOpers.queryForObject("select 1 from dual", Integer.class);

long afterMS = System.currentTimeMillis();

// diff

long diff = afterMS - beforeMS;

// log

// log.warn("diff=" diff);

if (diff >= 0 && diff <= 500) {// 小于500毫秒为0级

// 执行时长0-500ms内为正常

result = 0;

} else if (diff > 500 && diff <= 1000) {// 500毫秒到1秒之间是1级

// 执行时长500-1000ms内为降1级

result = 1;

} else if (diff > 1000 && diff <= 3000) {// 1秒到3秒之间是2级

result = 2;

} else if (diff > 3000 && diff <= 5000) {// 3秒到5秒之间是3级

result = 3;

} else if (diff > 5000) {// 大于5秒是4级

result = 4;

}

} catch (Exception e) {// 如果出现异常,认为DB挂掉

result = -1;

}

return result;

}

/**

* 检测所有Node的健康值(5秒钟检查一下 )

*/

public void run() {

while (this.runFlag) {

try {

int len = this.crmB_N_JdbcList.size();

JdbcOperations jdbcOpers = null;

NodeHealthValueBean nodeBean = null;

int healthyValue = -1;

for (int i = 0; i < len; i ) {

jdbcOpers = this.crmB_N_JdbcList.get(i);

//

healthyValue = getHealthyValue(jdbcOpers);// 获得jdbc节点健康值

nodeBean = this.healthyValueList.get(i);// 健康节点

if (nodeBean.getHealthValue() == healthyValue) {// 上一次节点健康值和当前健康值一致,不需要变动

continue;

}

//节点健康值有变化

NodeHealthValueBean oldValue = new NodeHealthValueBean();//节点旧健康值

oldValue.setNodeIndex(i);

oldValue.setHealthValue(nodeBean.getHealthValue());

NodeHealthValueBean newValue = new NodeHealthValueBean();//节点新健康值

newValue.setNodeIndex(i);

newValue.setHealthValue(healthyValue);

PropertyChangeEvent event = new PropertyChangeEvent(this, "healthyValue", oldValue, newValue);

// fire 健康值变化event

this.listeners.firePropertyChange(event);//触发监听器操作

// set new healthvalue

nodeBean.setHealthValue(healthyValue);//设置节点的新健康值

// set lastUpdate

nodeBean.setLastUpdate(new Date());

}

} catch (Exception e) {

log.warn(e.getMessage());

}

try {

// sleep 5 秒

Thread.sleep(5000);

} catch (Exception e) {

log.error("exception",e);

}

}

}

}

可以看到该类定义了监听管理器,再run方法中5秒执行一次jdbcTemplate节点健康性检查,如果发现当前健康值和节点之前健康值不一致,会触发监听器通知其修改节点健康值属性

四、添加HA相关配置

在spring-jdbc.xml中添加如下配置

<bean id="crmBalanceJdbc" class="com.typhoon.jdbcTemplateHA.dao.ha.HABalanceSimpleJdbcOperations">

<constructor-arg index="0">

<list>

<ref bean="crmQuery1"/>

<ref bean="crmQuery2"/>

</list>

</constructor-arg>

<constructor-arg index="1" ref="crmUpdate"/>

</bean>

该案例中我们忽略更新操作;在HABalanceSimpleJdbcOperations构造器中根据jdbcTemplate列表构造出对应的用于实现负载均衡的BalanceNodeBean列表(默认节点不是挂掉,当前权重0,矫正权重0),然后由一个线程池创建指定数量的线程是监视检查jdbcTemplate节点的健康情况,并且为每一个task添加监视器(实现任何一个监视线程监测到jdbcTemplate健康值有变化,就会通知监听器修改健康值),为了不增加过多类和逻辑,HABalanceSimpleJdbcOperations也是task的监听类(触发逻辑propertyChange)

五、测试

HA功能和配置都写好了,接着我们需要些代码来验证

  1. 两个从库中test库添加user表并插入两条记录

从库1:

从库2:

两台机器上的test库中都有user表,有两条相同的记录

2. 编写业务类

接口:

public interface UserService {

List<User> selectAll();

}

接口默认实现:

@Service("userService")

public class UserServiceImpl implements UserService {

@Resource

UserDao userDao;

@Override

public List<User> selectAll() {

return this.userDao.selectAll();

}

}

数据交互dao操作类:

/**

* 用户信息操作dao

*

* @author Typhoon

*

*/

@Repository("userDao")

public class UserDao {

@Resource

JdbcOperations crmBalanceJdbc;

public List<User> selectAll() {

String sql = " select * from user ";

return this.crmBalanceJdbc.query(sql, BeanPropertyRowMapper.newInstance(User.class));

}

}

可以看到UserDao中的JdbcTemplate已经不是之前用到的了,而是我们自己定义的类型HABalanceSimpleJdbcOperations,然后注入后就可以像其他JdbcTemplate或者NamedJdbcTemplate一样操作数据库了.

3. 编写单元测试类

使用main方法借助SpringContextUtils来实现单元测试,代码如下:

public class HAJdbcConsumer {

public static void main(String[] args) {

new ClassPathXmlApplicationContext("spring-root.xml").start();

UserService userService = SpringContextUtil.getBean("userService", UserService.class);

List<User> list = null;

for (int i = 0; i < 10; i ) {

list = userService.selectAll();

System.out.println(JSON.toJSONString(list));

}

}

}

如上述代码所示,我们获取userService后,执行了十次查询,然后我们运行程序得到如下结果:

细心的人发现了,我们确实查询到了我们想要的结果,但是完全看不出来十次查询分别是从哪个jdbcTemplate节点查询的,很好办,刚开始我们定义的日志就派上用场了,

我们查看获取jdbcTemplate节点的代码:

在根据权重规则获取到权重最高的jdbcTemplate节点的时候会计算处其所在列表的索引,我们在计算出所有后使用日志打印索引的值,也就是jdbcTemplate对应的节点索引值,如红色标注部分,接着我们再次运行单元测试,可以看到如下结果:

可以看到我们每次查询操作都打印出了获取到的jdbcTempalte索引值,这样我们就基于spring-jdbc中的jdbcTemplate简单实现了HA

总结

当然此例只是简单的实现了HA和负载均衡,具体的业务场景中可能需要更加强大完善和性能比较好的实现方式,目前市面上比较流行的有mycat,sharding-jdbc,atlas等一系列,各有千秋,不同公司要根据不同的业务场景进行技术选型或者自己团队实现.

PS:希望我的总结能够给你带来一些改变和提升,荣幸至极!

0 人点赞