首页 > 数据库 >聊聊业务项目如何主动感知mysql是否存活

聊聊业务项目如何主动感知mysql是否存活

时间:2023-01-03 09:55:49浏览次数:65  
标签:druid 存活 validationQueryTimeout boolean 聊聊 mysql dbCheckProperies public conn

前言

先前写过一篇文章聊聊如何利用redis实现多级缓存同步,里面讲到业务部门因数据库宕机,有技术提出当数据库宕机,切换到redis,今天我们就来聊聊如何触发这个切换动作?

1、方案一:利用异常机制

伪代码如下:


首先这个方案是不可行的,因为每次请求,还是先走到数据库逻辑,然后等抛出异常,这个时间会挺长的,业务上是无法接受的

2、方案二:主动进行mysql探活

实现思路: 可以利用数据库连接池检测有效连接的思路

实现方案

1、方式一:利用druid连接池的ValidConnectionChecker进行扩展

核心逻辑如下

@Slf4j
public class MysqlConnectionCheck extends MySqlValidConnectionChecker {

    private ApplicationContext applicationContext;

    public MysqlConnectionCheck(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }


    @Override
    public boolean isValidConnection(Connection conn, String validateQuery, int validationQueryTimeout) throws Exception {
      return checkMySQLCommunications(conn,validateQuery,validationQueryTimeout);

    }

    private boolean checkMySQLCommunications (Connection conn, String validateQuery, int validationQueryTimeout) {
        boolean validConnection = false;
        try {
            validConnection = super.isValidConnection(conn, validateQuery, validationQueryTimeout);
        } catch (Exception e) {
        }
        if(validConnection){
            boolean b = MySQLCommunicationsHolder.isMySQLCommunicationsException.compareAndSet(true, false);
            if(b){
                CommunicationsHealthEvent event = CommunicationsHealthEvent.builder().conn(conn).build();
                applicationContext.publishEvent(event);
            }
        }


        return validConnection;
    }
}

在yml配置我们自定义的检测器

spring:
    datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: ${DRIVER_CALSS_NAME:com.mysql.cj.jdbc.Driver}
        url: ${DATASOURCE_URL:jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai}
        username: ${DATASOURCE_USERNAME:root}
        password: ${DATASOURCE_PWD:123456}
        druid:
            # 指定连接的有效检查类
            valid-connection-checker-class-name: com.github.lybgeek.db.check.test.user.check.MysqlConnectionCheck 

这个方案也是不大行的,后面翻了一下druid源码。当数据库抛出不可恢复的异常时,比如网络抖动,异常断开,druid会触发exceptionSorter,抛弃连接。而CreateConnectionThread会检测是否需要创建连接,如果不需要,他就会进行等待。当连接不够时,会调用

com.alibaba.druid.pool.DruidAbstractDataSource#createPhysicalConnection()

​进行创建,同时isValidConnection也是在这个方法里面进行连接验证,但这边就有问题,就是当数据库宕机了,就创建不了连接,因此就进入异常流程,isValidConnection是没法执行的

2、方式二:参考druid的检测连接逻辑,额外编写定时器触发检测逻辑

核心代码块:

public class ValidConnectionCheckerAdapter implements ValidConnectionChecker {

    private DbCheckProperies dbCheckProperies;

    public ValidConnectionCheckerAdapter(DbCheckProperies dbCheckProperies) {
        this.dbCheckProperies = dbCheckProperies;
    }

    @Override
    public boolean isValidConnection(Connection conn, String query, int validationQueryTimeout) throws SQLException {
        boolean valid = checkConnection(conn, query, validationQueryTimeout);

        // unexcepted branch
        if (valid && isMysql()) {
            long lastPacketReceivedTimeMs = MySqlUtils.getLastPacketReceivedTimeMs(conn);
            if (lastPacketReceivedTimeMs > 0) {
                long mysqlIdleMillis = System.currentTimeMillis() - lastPacketReceivedTimeMs;
                if (lastPacketReceivedTimeMs > 0
                        && mysqlIdleMillis >= dbCheckProperies.getTimeBetweenEvictionRunsMillis()) {
                    return false;
                }
            }
        }
        return valid;

    }


    private boolean checkConnection(Connection conn, String query, int validationQueryTimeout) throws SQLException {
        if (query == null || query.length() == 0) {
            return true;
        }

        if(conn == null){
            return false;
        }

        Statement stmt = null;
        ResultSet rs = null;
        try {
            stmt = conn.createStatement();
            if (validationQueryTimeout > 0) {
                stmt.setQueryTimeout(validationQueryTimeout);
            }
            rs = stmt.executeQuery(query);
            return true;
        } finally {
            JdbcUtils.close(rs);
            JdbcUtils.close(stmt);
        }
    }



 @Override
    public void afterPropertiesSet() throws Exception {
        executorService.scheduleWithFixedDelay(new DbCheckTask(),0,dbCheckProperies.getTimeBetweenEvictionRunsMillis(), TimeUnit.MILLISECONDS);
    }
    

    private class DbCheckTask implements Runnable{

        @Override
        public void run() {
            SQLException sqlException = null;
            Connection conn = dbConnManger.getConn();
            try {
                boolean validConnection = validConnectionChecker.isValidConnection(conn, dbCheckProperies.getValidationQuery(), dbCheckProperies.getValidationQueryTimeout());
                if(validConnection){
                    boolean b = MySQLCommunicationsHolder.isMySQLCommunicationsException.compareAndSet(true, false);
                    if(b){
                        CommunicationsHealthEvent event = CommunicationsHealthEvent.builder().conn(conn).build();
                        applicationContext.publishEvent(event);
                    }
                }else{
                   sqlException = new SQLException("connection is invalid","10040");
                }
            } catch (SQLException e) {
                 log.error("{}",e);
                 sqlException = e;
                 dbConnManger.closeConnection();
                 conn = null;
            }

            if(sqlException != null){
                MySQLCommunicationsHolder.isMySQLCommunicationsException.compareAndSet(false, true);
                CommunicationsUnHealthEvent event = CommunicationsUnHealthEvent.builder().sqlException(sqlException).build();
                applicationContext.publishEvent(event);
            }



        }
    } 

总结

其实mysql的探活实现方式有很多种,本文的实现检测逻辑是直接套用druid的检测连接逻辑,之前对druid的使用,基本上就是停留在配置上,没过多关注。

为了写这篇文章,特地翻了一下druid的源码,主要是因为之前以为扩展ValidConnectionChecker就行了,后面发现行不通。就看了一下源码,发现druid的设计思路挺好的,有些实现思想是我们在日常开发中,可以借鉴使用的。还有druid里面有些跟数据库相关的util,也是可以直接拿过来用的。

demo链接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-db-check

标签:druid,存活,validationQueryTimeout,boolean,聊聊,mysql,dbCheckProperies,public,conn
From: https://www.cnblogs.com/linyb-geek/p/16725749.html

相关文章

  • MySQL——事务
     1、隔离级别SQL标准的事务隔离级别包括:读未提交(readuncommitted)、读提交(readcommitted)、可重复读(repeatableread)和串行化(serializable)。读未提交是指,一个事务还......
  • 1.2复习了一下MySQL的索引
    今天又复习了一下索引,做一个总结吧主要就是复习了索引分类概念,索引使用,SQL优化等三个方面1.主键索引,唯一索引,常规索引;对于唯一索引,同一个表中可以创建多个,用作创建唯一......
  • cpp lmysqlcppconn insert multiple rows into mysql table via groups and remainder
    //model/util.h#pragmaonce#ifndef__util_h__#define__util_h__#include<chrono>#include<ctime>#include<fstream>#include<iomanip>#include<iostream>......
  • MySQL中的sql优化
    目标:掌握SQL调优的原则掌握SQL调优的基本逻辑掌握优秀SQL的编写方案掌握何为慢SQL以及检测方案SQL优化原则1、减少数据量(表中数据太多可以分表,例如......
  • MySQL中的索引应用
    1.什么是索引(Index)?官方的定义是索引是一种数据结构,从生活的纬度上讲,假如将一本书比作成一张表,这本书的目录就是表中的索引(Index)。2.索引的优势和劣势?优势:数据量比较大......
  • 关于MySql链接url参数的设置 专题
    报错:EstablishingSSLconnectionwithoutserver'sidentityverificationisnotrecommended.AccordingtoMySQL5.5.45+,5.6.26+and5.7.6+requirementsSSLconnec......
  • SQL注入之MYSQL注入
    前言:MYSQL注入中首先要明确当前注入点权限,高权限注入时有更多的攻击手法,有的能直接进行geshell操作。其中也会遇到很多阻碍,相关的防御反感也要明确。高权限注入及低权限......
  • MySql学习笔记--基础篇02
    约束外键--父表和子表,如果要删除父表的记录时,会判断子表是否存在关联关系,如果存在不予删除  多表关系一对多,在此表中建立外键关联主表的主键多对多,建立第三张中......
  • (转)主从复制报错Fatal error:The slave I/O thread stops because master and slave ha
    最后发现/var/lib/mysql/auto.cnf还有一个auto.cnf文件,我们直接执行rm-rf/var/lib/mysql/auto.cnf命令删除主机和从机中的这个auto.cnf文件就可以,当然也可以选择修改里面......
  • MySQL-MVCC
    首先MVCC不是MySQL才有的概念,它是一种思想,一种方法论。MVCC全称:Multi-VersionConcurrencyControl多版本并发控制最早于1978年,论文『NamingandSynchronizationina......