最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

MySQL使用ReplicationConnection導(dǎo)致連接失效解決

 更新時間:2022年07月08日 14:06:44   作者:轉(zhuǎn)轉(zhuǎn)技術(shù)團隊  
這篇文章主要為大家介紹了MySQL使用ReplicationConnection導(dǎo)致連接失效問題分析解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

引言

MySQL數(shù)據(jù)庫讀寫分離,是提高服務(wù)質(zhì)量的常用手段之一,而對于技術(shù)方案,有很多成熟開源框架或方案,例如:sharding-jdbc、spring中的AbstractRoutingDatasource、MySQL-Router等,而mysql-jdbc中的ReplicationConnection亦可支持。

本文暫不對讀寫分離的技術(shù)選型做過多的分析,只是探索在使用druid作為數(shù)據(jù)源、結(jié)合ReplicationConnection做讀寫分離時,連接失效的原因,并找到一個簡單有效的解決方案。

問題背景

由于歷史原因,某幾個服務(wù)出現(xiàn)連接失效異常,關(guān)鍵報錯如下:

從日志不難看出,這是由于該連接長時間未和MySQL服務(wù)端交互,服務(wù)端已將連接關(guān)閉,典型的連接失效場景。

涉及的主要配置

jdbc配置

jdbc:mysql:replication://master_host:port,slave_host:port/database_name

druid配置

testWhileIdle=true(即,開啟了空閑連接檢查);

timeBetweenEvictionRunsMillis=6000L(即,對于獲取連接的場景,如果某連接空閑時間超過1分鐘,將會進行檢查,如果連接無效,將拋棄后重新獲取)。

附:DruidDataSource.getConnectionDirect中

處理邏輯如下:

if (testWhileIdle) {
    final DruidConnectionHolder holder = poolableConnection.holder;
    long currentTimeMillis             = System.currentTimeMillis();
    long lastActiveTimeMillis          = holder.lastActiveTimeMillis;
    long lastExecTimeMillis            = holder.lastExecTimeMillis;
    long lastKeepTimeMillis            = holder.lastKeepTimeMillis;
    if (checkExecuteTime
            && lastExecTimeMillis != lastActiveTimeMillis) {
        lastActiveTimeMillis = lastExecTimeMillis;
    }
    if (lastKeepTimeMillis > lastActiveTimeMillis) {
        lastActiveTimeMillis = lastKeepTimeMillis;
    }
    long idleMillis    = currentTimeMillis - lastActiveTimeMillis;
    long timeBetweenEvictionRunsMillis = this.timeBetweenEvictionRunsMillis;
    if (timeBetweenEvictionRunsMillis <= 0) {
        timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
    }
    if (idleMillis >= timeBetweenEvictionRunsMillis
            || idleMillis < 0 // unexcepted branch
            ) {
        boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
        if (!validate) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("skip not validate connection.");
            }
            discardConnection(poolableConnection.holder);
             continue;
        }
    }
}

mysql超時參數(shù)配置

wait_timeout=3600(3600秒,即:如果某連接超過一個小時和服務(wù)端沒有交互,該連接將會被服務(wù)端kill)。 顯而易見,基于如上配置,按照常規(guī)理解,不應(yīng)該出現(xiàn)“The last packet successfully received from server was xxx,xxx,xxx milliseconds ago”的問題。(當然,當時也排除了人工介入kill掉數(shù)據(jù)庫連接的可能)。

當“理所應(yīng)當”的經(jīng)驗解釋不了問題所在,往往需要跳出可能浮于表面經(jīng)驗束縛,來一次追根究底。那么,該問題的真正原因是什么呢?

本質(zhì)原因

當使用druid管理數(shù)據(jù)源,結(jié)合mysql-jdbc中原生的ReplicationConnection做讀寫分離時,ReplicationConnection代理對象中實際存在master和slaves兩套連接,druid在做連接檢測時候,只能檢測到其中的master連接,如果某個slave連接長時間未使用,會導(dǎo)致連接失效問題。

原因分析

mysql-jdbc中,數(shù)據(jù)庫驅(qū)動對連接的處理過程

結(jié)合com.mysql.jdbc.Driver源碼,不難看出mysql-jdbc中獲取連接的主體流程如下:

對于以“jdbc:mysql:replication://”開頭配置的jdbc-url,通過mysql-jdbc獲取到的連接,其實是一個ReplicationConnection的代理對象,默認情況下,“jdbc:mysql:replication://”后的第一個host和port對應(yīng)master連接,其后的host和port對應(yīng)slaves連接,而對于存在多個slave配置的場景,默認使用隨機策略進行負載均衡。

ReplicationConnection代理對象,使用JDK動態(tài)代理生成的,其中InvocationHandler的具體實現(xiàn),是ReplicationConnectionProxy,關(guān)鍵代碼如下:

public static ReplicationConnection createProxyInstance(List<String> masterHostList, Properties masterProperties, List<String> slaveHostList,
            Properties slaveProperties) throws SQLException {
      ReplicationConnectionProxy connProxy = new ReplicationConnectionProxy(masterHostList, masterProperties, slaveHostList, slaveProperties);
      return (ReplicationConnection) java.lang.reflect.Proxy.newProxyInstance(ReplicationConnection.class.getClassLoader(), INTERFACES_TO_PROXY, connProxy);
 }

ReplicationConnectionProxy的重要組成

關(guān)于數(shù)據(jù)庫連接代理,ReplicationConnectionProxy中的主要組成如下圖:

ReplicationConnectionProxy存在masterConnection和slavesConnection兩個實際連接對象,currentConnetion(當前連接)可以切換成mastetConnection或者slavesConnection,切換方式可以通過設(shè)置readOnly實現(xiàn)。

業(yè)務(wù)邏輯中,實現(xiàn)讀寫分離的核心也在于此,簡單來說:使用ReplicationConnection做讀寫分離時,只要做一個“設(shè)置connection的readOnly屬性的”aop即可。

基于ReplicationConnectionProxy,業(yè)務(wù)邏輯中獲取到的Connection代理對象,數(shù)據(jù)庫訪問時的主要邏輯是什么樣的呢?

ReplicationConnection代理對象處理過程

對于業(yè)務(wù)邏輯而言,獲取到的Connection實例,是ReplicationConnection代理對象,該代理對象通過ReplicationConnectionProxy和ReplicationMySQLConnection相互協(xié)同完成對數(shù)據(jù)庫訪問的處理,其中ReplicationConnectionProxy在實現(xiàn) InvocationHandler的同時,還充當對連接管理的角色,核心邏輯如下圖:

對于prepareStatement等常規(guī)邏輯,ConnectionMySQConnection獲取到當前連接進行處理(普通的讀寫分離的處理的重點正是在此);此時,重點提及pingInternal方法,其處理方式也是獲取當前連接,然后執(zhí)行pingInternal邏輯。

對于ping()這個特殊邏輯,圖中描述相對簡單,但主體含義不變,即:對master連接和sleves連接都要進行ping()的處理。

圖中,pingInternal流程和druid的MySQ連接檢查有關(guān),而ping的特殊處理,也正是解決問題的關(guān)鍵。

druid數(shù)據(jù)源對MySQ連接的檢查

druid中對MySQL連接檢查的默認實現(xiàn)類是MySqlValidConnectionChecker,其中核心邏輯如下:

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();
        }
        if (clazz.isAssignableFrom(conn.getClass())) {
            if (validationQueryTimeout <= 0) {
                validationQueryTimeout = DEFAULT_VALIDATION_QUERY_TIMEOUT;
            }
            try {
                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;
    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);
        }
        rs = stmt.executeQuery(query);
        return true;
    } finally {
        JdbcUtils.close(rs);
        JdbcUtils.close(stmt);
    }
}

對應(yīng)服務(wù)中使用的mysql-jdbc(5.1.45版),在未設(shè)置“druid.mysql.usePingMethod”系統(tǒng)屬性的情況下,默認usePingMethod為true,如下:

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);
    }
    configFromProperties(System.getProperties());
}
@Override
public void configFromProperties(Properties properties) {
    String property = properties.getProperty("druid.mysql.usePingMethod");
    if ("true".equals(property)) {
        setUsePingMethod(true);
    } else if ("false".equals(property)) {
        setUsePingMethod(false);
    }
}

同時,可以看出MySqlValidConnectionChecker中的ping方法使用的是MySQLConnection中的pingInternal方法,而該方法,結(jié)合上面對ReplicationConnection的分析,當調(diào)用pingInternal時,只是對當前連接進行檢驗。執(zhí)行檢驗連接的時機是通過DrduiDatasource獲取連接時,此時未設(shè)置readOnly屬性,檢查的連接,其實只是ReplicationConnectionProxy中的master連接。

此外,如果通過“druid.mysql.usePingMethod”屬性設(shè)置usePingMeghod為false,其實也會導(dǎo)致連接失效的問題,因為:當通過valideQuery(例如“select 1”)進行連接校驗時,會走到ReplicationConnection中的普通查詢邏輯,此時對應(yīng)的連接依然是master連接。

題外一問:ping方法為什么使用“pingInternal”,而不是常規(guī)的ping?

原因:pingInternal預(yù)留了超時時間等控制參數(shù)。

解決方式

調(diào)整依賴版本

服務(wù)中使用的mysql-jdbc版本為5.1.45,druid版本為1.1.20。經(jīng)過對其他高版本依賴的了解,依然存在該問題。

修改讀寫分離實現(xiàn)

修改的工作量主要在于數(shù)據(jù)源配置和aop調(diào)整,但需要一定的整體回歸驗證成本,鑒于涉及該問題的服務(wù)重要性一般,暫不做大調(diào)整。

拓展mysql-jdbc驅(qū)動

基于原有ReplicationConnection的功能,拓展pingInternal調(diào)整為普通的ping,集成原有Driver拓展新的Driver。方案可行,但修改成本不算小。

基于druid,拓展MySQL連接檢查

為簡單高效解決問題,選擇拓展MySqlValidConnectionChecker,并在druid數(shù)據(jù)源中加上對應(yīng)配置即可。拓展如下:

public class MySqlReplicationCompatibleValidConnectionChecker extends MySqlValidConnectionChecker {
    private static final Log LOG = LogFactory.getLog(MySqlValidConnectionChecker.class);
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    @Override
    public boolean isValidConnection(Connection conn, String validateQuery, int validationQueryTimeout) throws Exception {
        if (conn.isClosed()) {
            return false;
        }
        if (conn instanceof DruidPooledConnection) {
            conn = ((DruidPooledConnection) conn).getConnection();
        }
        if (conn instanceof ConnectionProxy) {
            conn = ((ConnectionProxy) conn).getRawObject();
        }
        if (conn instanceof ReplicationConnection) {
            try {
                ((ReplicationConnection) conn).ping();
                LOG.info("validate connection success: connection=" + conn.toString());
                return true;
            } catch (SQLException e) {
                LOG.error("validate connection error: connection=" + conn.toString(), e);
                throw e;
            }
        }
        return super.isValidConnection(conn, validateQuery, validationQueryTimeout);
    }
}

ReplicatoinConnection.ping()的實現(xiàn)邏輯中,會對所有master和slaves連接進行ping操作,最終每個ping操作都會調(diào)用到LoadBalancedConnectionProxy.doPing進行處理,而此處,可在數(shù)據(jù)庫配置url中設(shè)置loadBalancePingTimeout屬性設(shè)置超時時間。

以上就是MySQL使用ReplicationConnection導(dǎo)致連接失效解決的詳細內(nèi)容,更多關(guān)于MySQL Replication連接失效的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 解決MySQl查詢不區(qū)分大小寫的方法講解

    解決MySQl查詢不區(qū)分大小寫的方法講解

    今天小編就為大家分享一篇關(guān)于解決MySQl查詢不區(qū)分大小寫的方法講解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-04-04
  • MySQL排序與分頁講解

    MySQL排序與分頁講解

    這篇文章主要介紹了MySQL排序與分頁講解,使用 ORDER BY 對查詢到的數(shù)據(jù)進行排序操作,按照dept_id的降序排列,salary的升序排列相關(guān)展開文章,需要的小伙伴可以參考一下
    2022-01-01
  • MySQL數(shù)據(jù)庫常用命令小結(jié)

    MySQL數(shù)據(jù)庫常用命令小結(jié)

    這篇文章主要介紹了MySQL數(shù)據(jù)庫命令,主要包括對數(shù)據(jù)庫常用命令及數(shù)據(jù)庫中對表的命令,本文結(jié)合實例代碼給大家介紹的非常詳細,需要的朋友可以參考下
    2023-01-01
  • MySQL8.0窗口函數(shù)入門實踐及總結(jié)

    MySQL8.0窗口函數(shù)入門實踐及總結(jié)

    這篇文章主要給大家介紹了關(guān)于MySQL8.0窗口函數(shù)入門實踐及總結(jié)的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者使用MySQL8.0具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧
    2020-06-06
  • 淺談innodb_autoinc_lock_mode的表現(xiàn)形式和選值參考方法

    淺談innodb_autoinc_lock_mode的表現(xiàn)形式和選值參考方法

    下面小編就為大家?guī)硪黄獪\談innodb_autoinc_lock_mode的表現(xiàn)形式和選值參考方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-03-03
  • CentOS7下安裝MySQL5.7.39的詳細過程

    CentOS7下安裝MySQL5.7.39的詳細過程

    這篇文章主要介紹了CentOS7下安裝MySQL5.7.39的詳細過程,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-09-09
  • cmd命令提示符輸入:mysql?-u?root?-p報錯提示"mysql?不是內(nèi)部或外部命令,也不是可運行的程序"問題解決

    cmd命令提示符輸入:mysql?-u?root?-p報錯提示"mysql?不是內(nèi)部或外部命令,也不是

    這篇文章主要給大家介紹了關(guān)于cmd命令提示符輸入:mysql?-u?root?-p報錯提示"mysql?不是內(nèi)部或外部命令,也不是可運行的程序"問題的解決辦法,文中通過圖文介紹的非常詳細,需要的朋友可以參考下
    2023-12-12
  • mysql5.7.20免安裝版配置方法圖文教程

    mysql5.7.20免安裝版配置方法圖文教程

    這篇文章主要為大家詳細介紹了mysql5.7.20 免安裝版配置方法圖文教程,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-05-05
  • MySQL 8.0.41圖文詳細安裝教程 附安裝包

    MySQL 8.0.41圖文詳細安裝教程 附安裝包

    本文詳細介紹了MySQL 8.0.41的安裝步驟,包括下載安裝包、選擇安裝模式、設(shè)置數(shù)據(jù)庫密碼、配置系統(tǒng)環(huán)境變量以及驗證安裝結(jié)果,適合初學者和企業(yè)級應(yīng)用,感興趣的朋友一起看看吧
    2025-03-03
  • MySQL數(shù)據(jù)庫中如何查詢近一年的數(shù)據(jù)

    MySQL數(shù)據(jù)庫中如何查詢近一年的數(shù)據(jù)

    最近碰到一個需求是統(tǒng)計某張表的數(shù)據(jù),統(tǒng)計時間維度為近一年,下面這篇文章主要給大家介紹了關(guān)于MySQL數(shù)據(jù)庫中如何查詢近一年的數(shù)據(jù)的相關(guān)資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2024-07-07

最新評論

修水县| 汾西县| 清新县| 太保市| 金乡县| 宣恩县| 从江县| 紫云| 股票| 称多县| 乌鲁木齐市| 东台市| 进贤县| 呼伦贝尔市| 东辽县| 朔州市| 宜宾县| 呼玛县| 合阳县| 勃利县| 聂拉木县| 英德市| 武胜县| 闵行区| 九寨沟县| 姜堰市| 毕节市| 桦川县| 博白县| 布尔津县| 犍为县| 青河县| 高密市| 绥阳县| 吉林省| 蒙山县| 犍为县| 南木林县| 枣强县| 阿拉善盟| 屏南县|