Sharding-JDBC强制走主库,一不留神就报错了

简介: 从错误日志我们可以看出是使用到Sharding-JDBC的相关代码出问题了。而具体出错的业务代码也很容易定位到(这里没有把所有日志贴出来,后面会贴出简化处理过的问题代码)。

今天项目中突然告警报错,打出了多条相似的错误日志。查看了下,具体报错内容如下:


19.png


HintManagerHolder has previous value, please clear first.


从错误日志我们可以看出是使用到Sharding-JDBC的相关代码出问题了。而具体出错的业务代码也很容易定位到(这里没有把所有日志贴出来,后面会贴出简化处理过的问题代码)。


不过,分析和解决问题之前,先介绍下背景吧:强哥项目中用Sharding-JDBC主要是为了读写分离。而发生问题的代码,又是因为项目中用到的表和其他人的业务表在一个数据库中。之前有遇到其他业务瞬时数据库写入量过大导致数据库整体主从延迟过久,进而造成我们这边的业务写主库读从库无法读取到数据而失败报错的情况。


但是别人的业务一时半会又没法处理,所以只能我们自己项目上将涉及到比较容易受影响的业务代码进行强制读走主库。


而这次的问题就是因为修改的同学代码没写好出了问题,具体的代码如下:


public void executeTask(String jobId) {  //强制查询主库,避免从库延迟导致查不到刚刚插入的任务  ReportRequestJob reportRequestJob;  if (status != 1) {      //这里也没有报错      try (HintManager hintManager = HintManager.getInstance()) {          hintManager.setMasterRouteOnly();          updateJobStatus(reportRequestJob);      }  }  //下面代码没有报错  try (HintManager hintManager = HintManager.getInstance()) {      hintManager.setMasterRouteOnly();      reportRequestJob = reportRequestJobRepository.findOne(jobId);  }   //下面代码没有报错  try (HintManager hintManager = HintManager.getInstance()) {      hintManager.setMasterRouteOnly();      reportRequestJob = reportRequestJobRepository.findOne(jobId);  }}


updateJobStatus()代码如下:


@Overridepublic void updateJobStatus(ReportRequestJob reportRequestJob) {    reportRequestJob.setJobStatus(2);    //这里报错了    try (HintManager hintManager = HintManager.getInstance()) {        hintManager.setMasterRouteOnly();        reportRequestJobRepository.save(reportRequestJob);    }}


不知道小伙伴们从上面的代码中有没有看出问题,不过不管有没有用过Sharding-JDBC,这样的代码显然不是非常友好。我们看到每次需要对数据库进行走主库操作时,都需要使用HintManager.setMasterRouteOnly()进行手动强制路由,代码重复且容易出错。


那么这次报错的原因又是因为什么呢?我们从最开始的报错信息其实可以看出来:


18.png


截图的最后一行告诉我们错误是HintManager.getInstance()导致的。而且是updateJobStatus方法里的HintManager.getInstance()报的错(这里强哥没有把全部错误信息截图进去,小伙伴知道就好)。


那么我们先来看看HintManager.getInstance()方法内部实现:


public static HintManager getInstance() {    HintManager result = new HintManager();    HintManagerHolder.setHintManager(result);    return result;}


没看出什么,那就到 HintManagerHolder.setHintManager(result);里看看:


public static void setHintManager(final HintManager hintManager) {    Preconditions.checkState(null == HINT_MANAGER_HOLDER.get(), "HintManagerHolder has previous value, please clear first.");    HINT_MANAGER_HOLDER.set(hintManager);}


我们可以看到,错误信息就是在这个方法里打印出来的。可是为什么第一次获取HintManager时不会报错呢?


这是因为,第一次获取getInstance()时,HINT_MANAGER_HOLDER为空,断言处自然就不会报错。然后走下面代码


HINT_MANAGER_HOLDER.set(hintManager);


HINT_MANAGER_HOLDER被设置了内容。


然而,一个关键的一点,我们看到:executeTask方法中在获取到HintManager后并没有直接操作数据库,而是跳到了updateJobStatus方法。而在updateJobStatus方法中,又再次调用HintManager.getInstance()获取HintManager。而这第二次的调用就是问题的所在。


聪明的小伙伴应该看出来,第二次再调用,HINT_MANAGER_HOLDER已经不是空的了,那么setHintManager方法里的这句断言不成立,就会导致报错。


Preconditions.checkState(null == HINT_MANAGER_HOLDER.get(), "HintManagerHolder has previous value, please clear first.");


至此问题找到了,解决办法相对来说就很简单,直接删除外层获取HintManager.getInstance()的方法,使用内层一次切换就行,或者在每次要切主库前如错误提示所言,加一个clear处理,这样不管使用多少次都不会报错:


//强制读主库if (!HintManagerHolder.isMasterRouteOnly()) {   HintManagerHolder.clear();   HintManager hintManager = HintManager.getInstance();   hintManager.setMasterRouteOnly();}


不过,既然解决办法出来了。强哥还想问一个问题:当executeTask方法里的status是1时,为什么没走if,之后连续调用了两次HintManager.getInstance()却都不会报错呢?


限于篇幅,强哥就不多卖关子了,原因就是和Java的try-with-resources有关了。


我们看HintManager的源码


public final class HintManager implements AutoCloseable


可以看到HintManager实现了AutoCloseable 接口,而我们获取HintManager是使用的try-with-resources语法。也就是说try结束会自动调用close方法。HintManagerclose方法如下:


@Overridepublic void close() {    HintManagerHolder.clear();}


clear方法如下:


public static void clear() {    HINT_MANAGER_HOLDER.remove();}


所以在try块结束后,就直接自动把 HINT_MANAGER_HOLDER的内容清空了,下次获取就不会报错了。而如果我们在同一个try中,多次获取HintManager而没有手动clear就会报错。


至此,所有相关的问题就都说清楚啦~


好了,今天就到这了,这个问题也是因为在写代码的过程中,逻辑比较复杂,一没注意就容易掉到坑里。之后小伙伴们有用到Sharding-JDBC强制切主库也要多加注意哦~

相关文章
|
6月前
|
SQL Java 数据库连接
JSP商品进出库管理系统myeclipse开发sql数据库bs框架java编程jdbc
JSP 商品进出库管理系统是一套完善的web设计系统,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,开发环境为TOMCAT7.0,Myeclipse8.5开发,数据库为SQLSERVER2008,使用java语言开发,系统主要采用B/S模式开发。
49 0
|
6月前
|
SQL 存储 Java
原生Jdbc获取库、表、字段
原生Jdbc获取库、表、字段
73 0
|
2月前
|
SQL 消息中间件 Java
Flink报错问题之jdbc表报错如何解决
Apache Flink是由Apache软件基金会开发的开源流处理框架,其核心是用Java和Scala编写的分布式流数据流引擎。本合集提供有关Apache Flink相关技术、使用技巧和最佳实践的资源。
|
2月前
|
关系型数据库 MySQL Java
Flink cdc报错问题之使用jdbc connector报错如何解决
Flink CDC报错指的是使用Apache Flink的Change Data Capture(CDC)组件时遇到的错误和异常;本合集将汇总Flink CDC常见的报错情况,并提供相应的诊断和解决方法,帮助用户快速恢复数据处理任务的正常运行。
|
4月前
|
Java 数据库连接 数据库
Flink全托管,holo 库同步到另一个库,报错failed to get user from ak 亲,请问是哪种权限缺失?Flink 配置中使用的是holo. jdbc 的user和password 。
Flink全托管,holo 库同步到另一个库,报错failed to get user from ak 亲,请问是哪种权限缺失?Flink 配置中使用的是holo. jdbc 的user和password 。
37 1
|
11月前
|
SQL 存储 Oracle
Spring JDBC-使用Spring JDBC访问数据库
Spring JDBC-使用Spring JDBC访问数据库
109 0
|
前端开发 JavaScript Java
|
SQL 前端开发 druid
|
Java 关系型数据库 MySQL
jdbc连接数据库报错Connection连接错误
错误代码示例: @Test public void test1() throws ClassNotFoundException, SQLException { //1:注册驱动 Class.forName("com.mysql.jdbc.Driver"); //2:获取链接 String url = "jdbc:mysql://localhost:3308/spj"; String user = "root"; .
199 0
jdbc连接数据库报错Connection连接错误
|
Java 关系型数据库 MySQL
JDBC连接MySQL数据库,访问数据库信息完成登录功能(完整代码)
JDBC连接MySQL数据库,访问数据库信息完成登录功能
JDBC连接MySQL数据库,访问数据库信息完成登录功能(完整代码)