作者:刘开洋

爱可生交付服务团队北京 DBA,对数据库及周边技术有浓重的学习趣味,喜爱看书,谋求技术。

本文起源:原创投稿

*爱可生开源社区出品,原创内容未经受权不得随便应用,转载请分割小编并注明起源。


最近有钻研到 Druid 的问题,在进行探活检测时无奈失常输入 select x,解决问题后就跑来跟大家分享下 Druid 探活机制的实现。

一、Druid 是什么?Druid 对连贯的探活又是怎么实现的呢?

Druid 是阿里巴巴开源的一款 JDBC 组件,是一款数据库连接池,对MySQL的适配性和性能很弱小,包含监控数据库拜访性能、 数据库明码加密、SQL执行日志、以及拓展监控的实现等等,利用到MySQL还是很香的。

通过对官网源码(详见参考)进行一些简略剖析理解到,应用 Druid 在对连贯进行探活时,波及到以下两个参数的调整:

| 参数 | 阐明 |
| :--------- | :--: |
|druid.validationQuery = select 1 | 用来检测连贯是否无效的sql,要求是一个查问语句,罕用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 |
| druid.mysql.usePingMethod = false | 敞开 mysql com_ping 探活机制,需启用 validationQuery = select x,开启 validationQuery 探活机制 |

对其余参数的阐明参考配置属性列表:https://github.com/alibaba/dr...

在源码中失去的信息是 Druid 顺次初始化加载 initValidConnectionChecker(); 和 validationQueryCheck(); 在 ValidConnectionChecker 中 默认 com_ping 是开启的,就选用了 com_ping 作为默认探活,上面咱们来别离观测一下 com_ping 和 validationquery 的输入。

咱们接着往下读看看这两个参数的性能具体是怎么实现的。

二、验证

在测试中来敲定这两种探活机制参数的作用吧。这里应用的版本是 Druid 1.2.5

1、com_ping形式须要通过抓包形式验证

通过 tcpdump 抓包失去在 Druid 连贯中网络包的传输,之后应用 wireshark 进行剖析查看 Druid 发送到 MySQL 的 Request 包。

在MySQL Protocol 的 Request Command Ping 中失去 Ping (14)。

2、validationquery 形式通过应用 MySQL general log 来验证

public MySqlValidConnectionChecker(){    try {        clazz = Utils.loadClass("com.mysql.jdbc.MySQLConnection");        if (clazz == null) {            clazz = Utils.loadClass("com.mysql.cj.jdbc.ConnectionImpl");        }           if (clazz != null) {            ping = clazz.getMethod("pingInternal", boolean.class, int.class);        }           if (ping != null) {            usePingMethod = true;        }    } catch (Exception e) {        LOG.warn("Cannot resolve com.mysql.jdbc.Connection.ping method.  Will use 'SELECT 1' instead.", e);    }    //留神这里是从零碎变量中获取的 System.getProperties()。    configFromProperties(System.getProperties());}   @Overridepublic void configFromProperties(Properties properties) {   //从零碎变量中获取的,所以应该是在我的项目的启动脚本中增加 usePingMethod=false   String property = properties.getProperty("druid.mysql.usePingMethod");   if ("true".equals(property)) {       setUsePingMethod(true);   } else if ("false".equals(property)) {       setUsePingMethod(false);   }}    public boolean isValidConnection(Connection conn, String validateQuery, int validationQueryTimeout) throws Exception {  if (conn.isClosed()) {      return false;  }     if (usePingMethod) {      if (conn instanceof DruidPooledConnection) {          conn = ((DruidPooledConnection) conn).getConnection();      }         if (conn instanceof ConnectionProxy) {          conn = ((ConnectionProxy) conn).getRawObject();      }          // 以后的 conn 是否是 com.mysql.jdbc.MySQLConnection(or com.mysql.cj.jdbc.ConnectionImpl)      if (clazz.isAssignableFrom(conn.getClass())) {          if (validationQueryTimeout <= 0) {              validationQueryTimeout = DEFAULT_VALIDATION_QUERY_TIMEOUT;          }             try {              // 应用反射调用MySQLConnection.pingInternal 办法,查看连贯有效性,并且会刷新连贯的闲暇工夫,如果失败则会抛出异样,下层捕捉              ping.invoke(conn, true, validationQueryTimeout * 1000);          } catch (InvocationTargetException e) {              Throwable cause = e.getCause();              if (cause instanceof SQLException) {                  throw (SQLException) cause;              }              throw e;          }          return true;      }  }     String query = validateQuery;  // 当usePingMethod=false 或者 conn 不是 com.mysql.jdbc.MySQLConnection (or com.mysql.cj.jdbc.ConnectionImpl)会执行一下办法  if (validateQuery == null || validateQuery.isEmpty()) {      query = DEFAULT_VALIDATION_QUERY;  }     Statement stmt = null;  ResultSet rs = null;  try {      stmt = conn.createStatement();      if (validationQueryTimeout > 0) {          stmt.setQueryTimeout(validationQueryTimeout);      }      // 执行 select x 的query ,并且会刷新连贯的闲暇工夫      //  如果失败则会抛出异样,下层捕捉      rs = stmt.executeQuery(query);      return true;  } finally {      JdbcUtils.close(rs);      JdbcUtils.close(stmt);  }}

druid.validationQuery = SELECT 1 启用无奈间接应用validationquery,须要通过配置敞开com_ping(druid.mysql.usePingMethod = false)来实现。这个参数能够间接加在配置文件中,然而应用须要留神一点,如果配置敞开com_ping也无奈应用validationquery进行探活查问,则可能是程序自身的问题。

程序代码可能存在参数值只拉取 configFromPropety 的参数信息导致(druid.mysql.usePingMethod = false)参数生效,以下是我程序修改后的连贯示意图:

// 原程序public DruidDriverTest() {   logger = Logger.getLogger("druid_driver_test");   this.dataSource = new DruidDataSource();    // Druid 配置文件地址.   this.configPath = "./config.properties";··· #############################################// 批改后public DruidDriverTest() {    logger = Logger.getLogger("druid_driver_test");     // Druid 配置文件地址.    this.configPath = "config.properties";     try (BufferedReader bufferedReader = new BufferedReader(new FileReader(configPath))) {            // 将配置文件读入到 system.config 中            System.getProperties().load(bufferedReader);    } catch (IOException e) {            e.printStackTrace();            return;    }···

原程序中:Druid 默认从 config 配置文件 中拉配置参数信息到 DruidDataSource 中,而 usePingMethod 参数须要应用 MySqlValidConnectionChecker 插件加载读取到 DruidDataSource 中,然而config没有加载到System. getProperties()中,因而 Druid 不能辨认 config 配置文件 中的 usePingMethod 参数。 Druid 加载 DruidDataSource 中的配置信息进行一系列行为。

批改后:建设 config配置文件加载到 system 变量中的连贯,再应用 MySqlValidConnectionChecker 插件 加载到 DruidDataSource 中。

[root@yang-02 druid_demo-master]# mvn exec:java -Dexec.mainClass="test.App"[INFO] Scanning for projects...                                                                [INFO] ------------------------------------------------------------------------[INFO] Building druid-demo 1.0-SNAPSHOT[INFO] ------------------------------------------------------------------------[INFO] --- exec-maven-plugin:3.0.0:java (default-cli) @ druid-demo ---[2021-04-28 17:23:06] [SEVERE] minEvictableIdleTimeMillis should be greater than 30000[2021-04-28 17:23:06] [SEVERE] keepAliveBetweenTimeMillis should be greater than 30000[2021-04-28 17:23:06] [INFO] start test[2021-04-28 17:23:06] [INFO] ------------------ status --------------------[2021-04-28 17:23:06] [INFO] initial size: 3[2021-04-28 17:23:06] [INFO] min idle: 2[2021-04-28 17:23:06] [INFO] max active: 20[2021-04-28 17:23:06] [INFO] current active: 0[2021-04-28 17:23:06] [INFO] max wait: 6000[2021-04-28 17:23:06] [INFO] time between eviction runs millis: 2000[2021-04-28 17:23:06] [INFO] validation query: SELECT 1[2021-04-28 17:23:06] [INFO] keepAlive: true[2021-04-28 17:23:06] [INFO] testWhileIdle: false[2021-04-28 17:23:06] [INFO] testOnBorrow: false[2021-04-28 17:23:06] [INFO] testOnReturn: false[2021-04-28 17:23:06] [INFO] keepAliveBetweenTimeMillis: 4000[2021-04-28 17:23:06] [INFO] MinEvictableIdleTimeMillis: 2000[2021-04-28 17:23:06] [INFO] MaxEvictableIdleTimeMillis: 25200000[2021-04-28 17:23:06] [INFO] RemoveAbandoned: false[2021-04-28 17:23:06] [INFO] RemoveAbandonedTimeoutMillis: 300000[2021-04-28 17:23:06] [INFO] RemoveAbandonedTimeout: 300[2021-04-28 17:23:06] [INFO] LogAbandoned: false   // 通过开启MySQL general log 观测Druid下发查问的命令输入// mysql general log output2021-04-28T17:23:01.435944+08:00     7048 Connect   root@127.0.0.1 on druid_demo using TCP/IP2021-04-28T17:23:01.441663+08:00     7048 Query /* mysql-connector-java-5.1.40 ( Revision: 402933ef52cad9aa82624e80acbea46e3a701ce6 )*/SELECT  @@session.auto_increment_increment AS auto_increment_increment, @@character_set_client AS character_set_client,@@character_set_connection AS character_set_connection, @@character_set_results AS character_set_results,@@character_set_server AS character_set_server,@@init_connect AS init_connect, @@interactive_timeout AS interactive_timeout,@@license AS license, @@lower_case_table_names AS lower_case_table_names, @@max_allowed_packet AS max_allowed_packet,@@net_buffer_length AS net_buffer_length, @@net_write_timeout AS net_write_timeout, @@query_cache_size AS query_cache_size,@@query_cache_type AS query_cache_type, @@sql_mode AS sql_mode, @@system_time_zone AS system_time_zone, @@time_zone AS time_zone,@@tx_isolation AS tx_isolation, @@wait_timeout AS wait_timeout2021-04-28T17:23:01.467362+08:00     7048 Query SHOW WARNINGS2021-04-28T17:23:01.469893+08:00     7048 Query SET NAMES utf8mb42021-04-28T17:23:01.470325+08:00     7048 Query SET character_set_results = NULL2021-04-28T17:23:01.470681+08:00     7048 Query SET autocommit=12021-04-28T17:23:01.580189+08:00     7048 Query SELECT 12021-04-28T17:23:01.584444+08:00     7048 Query select @@session.tx_read_only2021-04-28T17:23:01.584964+08:00     7048 Query SELECT @@session.tx_isolation······2021-04-28T17:23:10.621839+08:00     7052 Quit2021-04-28T17:23:12.623470+08:00     7051 Query SELECT 12021-04-28T17:23:12.624380+08:00     7053 Query SELECT 12021-04-28T17:23:14.625555+08:00     7053 Query SELECT 12021-04-28T17:23:14.626719+08:00     7051 Query SELECT 12021-04-28T17:23:16.627945+08:00     7051 Query SELECT 12021-04-28T17:23:16.628719+08:00     7053 Query SELECT 12021-04-28T17:23:18.629940+08:00     7053 Query SELECT 12021-04-28T17:23:18.630674+08:00     7051 Query SELECT 1

如果翻阅文章的老师们有对 Druid 探活或其余参数的钻研欢送后盾留言分割小编,程度无限,敬请您的赐教。

参考

https://github.com/alibaba/dr...

https://github.com/alibaba/dr...

鸣谢:

爱可生CTO-黄炎学生 以及 爱可生研发-孙健学生,感激两位老师对 Druid 测试提供的帮忙。