当前位置: 首页 > 后端技术 > Java

停止搞乱MySQL驱动程序升级,.

时间:2023-04-01 17:49:20 Java

来源:https://blog.csdn.net/qq_4037...现象最近同事发现新服务用的驱动都是MySQL8.0主动将一些旧的MySQL驱动程序升级到8.0。但是升级后并发高的时候,查看监控管理,Druid连接池获取连接执行SQL的时间大多在200ms以上。本文详细分析了“破案”的全过程。系统压测发现有大量线程阻塞。threaddump信息如下:"http-nio-5366-exec-48"#210daemonprio=5os_prio=0tid=0x00000000023d0800nid=0x3be9waitingformonitorentry[0x00007fa4c1400000]java.lang.Thread.State:BLOCKED(在对象监视器上)在org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader.loadClass(TomcatEmbeddedWebappClassLoader.java:66)-等待在org.apache锁定<0x0000009java.60>(0x0000009java.60aaflang.Object)。com.alibaba.druid.util.Utils.loadClass(Utils.java:220)372)根据上面的信息,有一个线程BLOCKED,BLOCKED的位置是com.alibaba.druid.util.Utils.loadClass(Utils.java:220),所以重点查看这段代码终于找到了问题。根据分析publicclassMySqlUtils{publicstaticlonggetLastPacketReceivedTimeMs(Connectionconn)throwsSQLException{if(class_connectionImpl==null&&!class_connectionImpl_Error){try{class_connectionImpl=Utils.loadClass("com.mysql.jdbc.MySQLConnection");}catch(Throwableerror){class_connectionImpl_Error=true;}}if(class_connectionImpl==null){返回-1;}if(method_getIO==null&&!method_getIO_error){try{method_getIO=class_connectionImpl.getMethod("getIO");}catch(Throwableerror){method_getIO_error=true;}}if(method_getIO==null){返回-1;}if(class_MysqlIO==null&&!class_MysqlIO_Error){try{class_MysqlIO=Utils.loadClass("com.mysql.jdbc.MysqlIO");}catch(Throwableerror){class_MysqlIO_Error=true;}}if(class_MysqlIO==null){return-1;}if(method_getLastPacketReceivedTimeMs==null&&!method_getLastPacketReceivedTimeMs_error){try{方法方法=class_MysqlIO.getDeclaredMethod("getLastPacketReceivedTimeMs");method.setAccessible(true);method_getLastPacketReceivedTimeMs=方法;}catch(Throwableerror){method_getLastPacketReceivedTimeMs_error=true;}}if(method_getLastPacketReceivedTimeMs==null){return-1;}try{ObjectconnImpl=conn.unwrap(class_connectionImpl);if(connImpl==null){return-1;}Objectmysqlio=method_getIO.invoke(connImpl);Longms=(Long)遇见hod_getLastPacketReceivedTimeMs.invoke(mysqlio);返回ms.longValue();}catch(IllegalArgumentExceptione){thrownewSQLException("getLastPacketReceivedTimeMserror",e);}catch(IllegalAccessExceptione){thrownewSQLException("getLastMordPacketRe);}catch(InvocationTargetExceptione){thrownewSQLException("getLastPacketReceivedTimeMserror",e);}}MySqlUtils中的getLastPacketReceivedTimeMs()方法将加载com.mysql。jdbc.MySQLConnection类,但是MySQL驱动8.0中类名改为com.mysql.cj.jdbc.ConnectionImpl,所以MySQL驱动8.0无法加载com.mysql.jdbc.MySQLConnectiongetLastPacketReceivedTimeMs()方法实现,如果Utils.loadClass("com.mysql.jdbc.MySQLConnection")无法加载到类并抛出异常,变量class_connectionImpl_Error会被修改,下次调用不会再次加载publicclassUtils{publicstaticClassloadClass(StringclassName){Classclazz=null;if(className==null){returnnull;}try{returnClass.forName(className);}catch(ClassNotFoundExceptione){//跳过}ClassLoaderctxClassLoader=Thread.currentThread().getContextClassLoader();if(ctxClassLoader!=null){尝试{clazz=ctxClassLoader.loadClass(className);}catch(ClassNotFoundExceptione){//跳过}}returnclazz;但是在Utils的loadClass()方法中也捕获了ClassNotFoundException,导致加载类加载失败时loadClass()失败,不会抛出异常,导致每次getLastPacketReceivedTimeMs()方法被调用publicclassTomcatEmbeddedWebappClassLoaderextendsParallelWebappClassLoader{publicClassloadClass(Stringname,booleanresolve)throwsClassNotFoundException{synchronized(JreCompat.isGraalAvailable()?this:getClassLoadingLock(name)){Class结果=find(nameExist)=(结果!=空)?结果:doLoadClass(名称);if(result==null){thrownewClassNotFoundException(name);}returnresolveIfNecessary(result,resolve);这是因为TomcatEmbeddedWebappClassLoader加载类,会加一个synchronized锁,导致com.mysql.jdbc.MySQLConnection在每次调用getLastPacketReceivedTimeMs()方法时加载一次,但永远不会加载。类加载的时候会加同步锁,所以线程会出现Blocking,性能下降的现象。getLastPacketReceivedTimeMs()方法调用时间publicabstractclassDruidAbstractDataSourceextendsWrapperAdapterimplementsDruidAbstractDataSourceMBean,DataSource,DataSourceProxy,Serializable{protectedbooleantestConnectionInternal(DruidConnectionHolderholder,Connectionconn){StringsqlFile=JdbcSqlStat.getContextSqlFile();StringsqlName=JdbcSqlStat.getContextSqlName();if(sqlFile!=null){JdbcSqlStat.setContextSqlFile(null);}if(sqlName!=null){JdbcSqlStat.setContextSqlName(null);}try{if(validConnectionChecker!=null){booleanvalid=validConnectionChecker.isValidConnection(conn,validationQuery,validationQueryTimeout);longcurrentTimeMillis=System.currentTimeMillis();if(holder!=null){holder.lastValidTimeMillis=currentTimeMillis;holder.lastExecTimeMillis=currentTimeMillis;}if(valid&&isMySql){//未例外的分支longlastPacketReceivedTimeMs=MySqlUtils.getLastPacketReceivedTimeMs(conn);如果(lastPacketReceivedTimeMs>0){longmysqlIdleMillis=currentTimeMillis-lastPacketReceivedTimeMs;if(lastPacketReceivedTimeMs>0//&&mysqlIdleMillis>=timeBetweenEvictionRunsMillis){discardConnection(holder);StringerrorMsg="丢弃长时间未收到的连接。"+",jdbcUrl:"+jdbcUrl+",jdbcUrl:"+jdbcUrl+",lastPacketReceivedIdleMillis:"+mysqlIdleMillis;日志.error(errorMsg);关于变假;}}}if(valid&&onFatalError){lock.lock();尝试{如果(onFatalError){onFatalError=false;}}最后{lock.unlock();}}返回有效;}if(conn.isClosed()){返回false;}if(null==validationQuery){returntrue;}语句stmt=null;结果集rset=null;尝试{stmt=conn.createStatement();如果(getValidationQueryTimeout()>0){stmt.setQueryTimeout(validationQueryTimeout);}rset=stmt.executeQuery(validationQuery);如果(!rset.next()){返回假;}}最后{JdbcUtils.close(rset);JdbcUtils.close(stmt);}if(onFatalError){lock.lock();尝试{if(onFatalError){onFatalError=false;}}最后{lock.unlock();}}返回真;}catch(Throwableex){//跳过returnfalse;}finally{if(sqlFile!=null){JdbcSqlStat.setContextSqlFile(sqlFile);}if(sqlName!=null){JdbcSqlStat.setContextSqlName(sqlName);只有DruidAbstractDataSource的testConnectionInternal()方法会调用getLastPacketReceivedTimeMs()方法。testConnectionInternal()方法用于检测连接是否有效。这个方法可能会被调用,这取决于Druid是否检测连接是否有效。“连接是否有效”的参数:testOnBorrow:每次获取连接时执行validationQuery,检查连接是否有效(会影响性能)testOnReturn:每次返回连接时执行validationQuery,检查连接是否有效(会影响性能)testWhileIdle:申请连接时测试,如果idle时间大于timeBetweenEvictionRunsMillis,则执行validationQuery检查连接是否有效。在application中设置TestOnBorrow=true。每次获取连接时,synchronizedlock会被抢占,所以性能下降很明显,解决方案已经验证,使用Druid1.x版本这个bug会在<=1.1.22出现,解决方案升级到Druid1.x版本>=1.1.23或Druid1.2.x版本GitHub问题:https://github.com/alibaba/dr...近期热点文章推荐:1.1,000+Java面试题及答案(2021最新版)2.别被满满的if/else了,试试策略模式,真香!!3.操!Java中xx≠null的新语法是什么?4、SpringBoot2.5发布,深色模式太炸了!5.《Java开发手册(嵩山版)》最新发布,赶快下载吧!感觉不错,别忘了点赞+转发!