sharding-jdbc使用总结
2021-05-28 本文已影响0人
雨夜都行
sharding-jdbc
由于生产或者QA环境下的数据库
是按主从进行部署,在业务上默认读操作会使用从库查询来实现与主库的读写分离,提高性能。但是不可避免的是 主从延迟
的存在,此时就需要我们切换到主库进行查询操作,来保证业务的正常执行。在现有技术栈背景下,是通过使用sharding-jdbc的主库路由切换到主库上的。主库路由在使用上需要注意下面几个点:
在进行数据库路由的时候会使用到HintManager.getInstance() ,它会将HintManager实例放入ThreadLocal中,该ThreadLocal清除的方式有两种:
- 调用HintManager.close()手动清除
- 由sharding-jdbc自动支持,当数据库连接被归还到连接池后自动清除
自动清除的方式又可以分为两种:
- 有事务,当前事务执行结束之后,会释放连接,同时清除该ThreadLocal
- 没有事务,spring-data-jpa的方法 findone等,或者native sql 执行完成之后也会释放连接,同时清除ThreadLocal
如果不清除该ThreadLocal会导致什么后果?
- 当下次请求到同一线程的时候,调用HintManager.getInstance()发现ThreadLocal中已经有值了,抛出异常
等待GC清除ThreadLocal,由于ThreadLocal中在存储结构实际上是一个weakReference,gc发生会清除弱引用,但是实际上存储的实例还在,与此同时这就导致了造成了内存泄漏(实际上不会有这种情况,因为HintManager使用的ThreadLocal是静态成员变量)
需要避免下面这种错误写法:
void test() {
HintManager hintManager = HintManager.getInstance();
hintManager.setMasterRouteOnly();
// 1.做了一些其它非数据库的操作,可能会出现异常(比如查询redis, 数据校验等)
pupuRedissonHash.getMulti();
// 2.查询数据库
repository.findOne();
}
- 上面这段代码如果在1中出现了异常,会导致ThreadLocal没有被清除,再下次请求用到了同一个线程(比如dubbo线程)就出现异常
- 上面这段代码如果能正常的执行到2的话,一旦2执行结束后就会清除ThreadLocal,保证了下次请求使用到相同线程的正常执行
在讨论过了ThreadLocal清除方式的情况下,可以发现下述代码产生的两种不同情况了:
// 没有事务先主库路由,再多次查询
void testNoTransaction() {
HintManger hintManager = HintManger.getInstance();
hintManager.setMasterRouteOnly();
repository.findOne(); // 1. 查询的是主库,同时清除ThreadLocal
repository.findOne(); // 2. 查询的是从库,因为ThreadLocal被清除了
}
// 有事务先主库路由,再多次查询
@Transactional
void testHasTransaction() {
HintManger hintManager = HintManger.getInstance();
hintManager.setMasterRouteOnly();
repository.findOne(); // 1. 查询的是主库,事务还没结束,不清除ThreadLocal
repository.findOne(); // 2. 查询的是主库,因为ThreadLocal还没被清除
} // 事务结束,归还数据库连接,清除ThreadLocal
在讨论完了自动清除的场景,下述代码表述了手动清除ThreadLocal:
// 使用try finally进行清除
void testManualClearByTryFinally(DTO dto) {
HintManger hintManager = HintManger.getInstance();
hintManager.setMasterRouteOnly();
try {
checkDTO(dto);
redissonHash.getMulti();
repository.findOne();
} finally {
hintManager.close();
}
}
// 使用try-with-resource进行清除
void testManualClearByTryWithResource(DTO dto) {
try (HintManager ignored = routeToMaster()) {
checkDTO(dto);
redissonHash.getMulti();
repository.findOne();
}
}
HintManager routeToMaster() {
HintManger hintManager = HintManger.getInstance();
hintManager.setMasterRouteOnly();
return hintManager;
}
建议:在使用
HintManager.getInstance()
后,不确定在此之后执行的代码是否会出现异常的情况下,使用try finally
或者try-with-resource
手动清除ThreadLocal