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月前
|
Java 关系型数据库 数据库连接
实时计算 Flink版操作报错之在使用JDBC连接MySQL数据库时遇到报错,识别不到jdbc了,怎么解决
在使用实时计算Flink版过程中,可能会遇到各种错误,了解这些错误的原因及解决方法对于高效排错至关重要。针对具体问题,查看Flink的日志是关键,它们通常会提供更详细的错误信息和堆栈跟踪,有助于定位问题。此外,Flink社区文档和官方论坛也是寻求帮助的好去处。以下是一些常见的操作报错及其可能的原因与解决策略。
|
6月前
|
Oracle 关系型数据库 Java
实时计算 Flink版操作报错之报错:Caused by: oracle.jdbc.OracleDatabaseException: ORA-01291如何解决
在使用实时计算Flink版过程中,可能会遇到各种错误,了解这些错误的原因及解决方法对于高效排错至关重要。针对具体问题,查看Flink的日志是关键,它们通常会提供更详细的错误信息和堆栈跟踪,有助于定位问题。此外,Flink社区文档和官方论坛也是寻求帮助的好去处。以下是一些常见的操作报错及其可能的原因与解决策略。
|
3月前
|
关系型数据库 MySQL Java
【Azure 应用服务】App Service 无法连接到Azure MySQL服务,报错:com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure
【Azure 应用服务】App Service 无法连接到Azure MySQL服务,报错:com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure
170 0
|
5月前
|
关系型数据库 MySQL 分布式数据库
PolarDB操作报错合集之遇到报错“com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure”,该怎么办
在使用阿里云的PolarDB(包括PolarDB-X)时,用户可能会遇到各种操作报错。下面汇总了一些常见的报错情况及其可能的原因和解决办法:1.安装PolarDB-X报错、2.PolarDB安装后无法连接、3.PolarDB-X 使用rpm安装启动卡顿、4.PolarDB执行UPDATE/INSERT报错、5.DDL操作提示“Lock conflict”、6.数据集成时联通PolarDB报错、7.编译DN报错(RockyLinux)、8.CheckStorage报错(源数据库实例被删除)、9.嵌套事务错误(TDDL-4604)。
246 0
|
6月前
|
Java 数据库连接 数据库
实时计算 Flink版操作报错合集之flink jdbc写入数据时,长时间没写入后报错,是什么原因导致的
在使用实时计算Flink版过程中,可能会遇到各种错误,了解这些错误的原因及解决方法对于高效排错至关重要。针对具体问题,查看Flink的日志是关键,它们通常会提供更详细的错误信息和堆栈跟踪,有助于定位问题。此外,Flink社区文档和官方论坛也是寻求帮助的好去处。以下是一些常见的操作报错及其可能的原因与解决策略。
174 9
|
6月前
|
Java 关系型数据库 数据库连接
实时计算 Flink版操作报错之遇到错误org.apache.flink.table.api.ValidationException: Could not find any factory for identifier 'jdbc',该如何解决
在使用实时计算Flink版过程中,可能会遇到各种错误,了解这些错误的原因及解决方法对于高效排错至关重要。针对具体问题,查看Flink的日志是关键,它们通常会提供更详细的错误信息和堆栈跟踪,有助于定位问题。此外,Flink社区文档和官方论坛也是寻求帮助的好去处。以下是一些常见的操作报错及其可能的原因与解决策略。
|
5月前
|
缓存 监控 druid
对比各大数据库连接池技术-Jdbc-Dbcp-C3p0-Druid-Hikaricp
对比各大数据库连接池技术-Jdbc-Dbcp-C3p0-Druid-Hikaricp
68 0
|
6月前
|
SQL Java 数据库连接
JDBC Java标准库提供的一些api(类+方法) 统一各种数据库提供的api
JDBC Java标准库提供的一些api(类+方法) 统一各种数据库提供的api
48 0
|
6月前
|
Java 关系型数据库 MySQL
【JDBC编程】基于MySql的Java应用程序中访问数据库与交互数据的技术
【JDBC编程】基于MySql的Java应用程序中访问数据库与交互数据的技术
|
6月前
|
关系型数据库 MySQL Java
报错 create connection SQLException, url: jdbc:mysql://noreggie?serverTimezone=Asia/Shanghai&useUnicod
报错 create connection SQLException, url: jdbc:mysql://noreggie?serverTimezone=Asia/Shanghai&useUnicod