黄a在线观看-黄a在线-黄a大片-黄色片在线看-黄色毛片免费-黄色大片网站

您的位置:首頁技術文章
文章詳情頁

詳解MySQL連接掛死的原因

瀏覽:68日期:2023-10-02 18:51:07
目錄一、背景架構問題現象二、分析過程連接池陷入焦灼撥開云霧見光明三、解決方案四、小結一、背景

近期由測試反饋的問題有點多,其中關于系統可靠性測試提出的問題令人感到頭疼,一來這類問題有時候屬于“偶發”現象,難以在環境上快速復現;二來則是可靠性問題的定位鏈條有時候變得很長,極端情況下可能要從 A 服務追蹤到 Z 服務,或者是從應用代碼追溯到硬件層面。

本次分享的是一次關于 MySQL 高可用問題的定位過程,其中曲折頗多但問題本身卻比較有些代表性,遂將其記錄以供參考。

架構

首先,本系統以 MySQL 作為主要的數據存儲部件。整一個是典型的微服務架構(SpringBoot + SpringCloud),持久層則采用了如下幾個組件:

mybatis,實現 SQL <-> Method 的映射

hikaricp,實現數據庫連接池

mariadb-java-client,實現 JDBC 驅動

在 MySQL 服務端部分,后端采用了雙主架構,前端以 keepalived 結合浮動IP(VIP)做一層高可用。如下:

詳解MySQL連接掛死的原因

說明

MySQL 部署兩臺實例,設定為互為主備的關系。 為每臺 MySQL 實例部署一個 keepalived 進程,由 keepalived 提供 VIP 高可用的故障切換。實際上,keepalived 和 MySQL 都實現了容器化,而 VIP 端口則映射到 VM 上的 nodePort 服務端口上。 業務服務一律使用 VIP 進行數據庫訪問。

Keepalived 是基于 VRRP 協議實現了路由層轉換的,在同一時刻,VIP 只會指向其中的一個虛擬機(master)。當主節點發生故障時,其他的 keepalived 會檢測到問題并重新選舉出新的 master,此后 VIP 將切換到另一個可用的 MySQL 實例節點上。這樣一來,MySQL 數據庫就擁有了基礎的高可用能力。

另外一點,Keepalived 還會對 MySQL 實例進行定時的健康檢查,一旦發現 MySQL 實例不可用會將自身進程殺死,進而再觸發 VIP 的切換動作。

問題現象

本次的測試用例也是基于虛擬機故障的場景來設計的:

持續以較小的壓力向業務服務發起訪問,隨后將其中一臺 MySQL 的容器實例(master)重啟。按照原有的評估,業務可能會產生很小的抖動,但其中斷時間應該保持在秒級。

然而經過多次的測試后發現,在重啟 MySQL 主節點容器之后,有一定的概率會出現業務卻再也無法訪問的情況!

二、分析過程

在發生問題之后,開發同學的第一反應是 MySQL 的高可用機制出了問題。由于此前曾經出現過由于 keepalived 配置不當導致 VIP 未能及時切換的問題,因此對其已經有所戒備。

先是經過一通的排查,然后并沒有找到 keepalived 任何配置上的毛病。

然后在沒有辦法的情況下,重新測試了幾次,問題又復現了。

緊接著,我們提出了幾個疑點:

1.Keepalived 會根據 MySQL 實例的可達性進行判斷,會不會是健康檢查出了問題?

但在本次測試場景中,MySQL 容器銷毀會導致 keepalived 的端口探測產生失敗,這同樣會導致 keepalived 失效。如果 keepalived 也發生了中止,那么 VIP 應該能自動發生搶占。而通過對比兩臺虛擬機節點的信息后,發現 VIP 的確發生了切換。

2. 業務進程所在的容器是否發生了網絡不可達的問題?

嘗試進入容器,對當前發生切換后的浮動IP、端口執行 telnet 測試,發現仍然能訪問成功。

連接池

在排查前面兩個疑點之后,我們只能將目光轉向了業務服務的DB客戶端上。

從日志上看,在產生故障的時刻,業務側的確出現了一些異常,如下:

Unable to acquire JDBC Connection [n/a]

java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30000ms.

    at com.zaxxer.hikari.pool.HikariPool.createTimeoutException(HikariPool.java:669) ~[HikariCP-2.7.9.jar!/:?]

    at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:183) ~[HikariCP-2.7.9.jar!/:?] 

    ...

這里提示的是業務操作獲取連接超時了(超過了30秒)。那么,會不會是連接數不夠用呢?

業務接入采用的是 hikariCP 連接池,這也是市面上流行度很高的一款組件了。

我們隨即檢查了當前的連接池配置,如下:

//最小空閑連接數spring.datasource.hikari.minimum-idle=10//連接池最大大小spring.datasource.hikari.maximum-pool-size=50//連接最大空閑時長spring.datasource.hikari.idle-timeout=60000//連接生命時長spring.datasource.hikari.max-lifetime=1800000//獲取連接的超時時長spring.datasource.hikari.connection-timeout=30000

其中 注意到 hikari 連接池配置了 minimum-idle = 10,也就是說,就算在沒有任何業務的情況下,連接池應該保證有 10 個連接。更何況當前的業務訪問量極低,不應該存在連接數不夠使用的情況。

除此之外,另外一種可能性則可能是出現了“僵尸連接”,也就是說在重啟的過程中,連接池一直沒有釋放這些不可用的連接,最終造成沒有可用連接的結果。

開發同學對'僵尸鏈接'的說法深信不疑,傾向性的認為這很可能是來自于 HikariCP 組件的某個 BUG…

于是開始走讀 HikariCP 的源碼,發現應用層向連接池請求連接的一處代碼如下:

public class HikariPool{ //獲取連接對象入口 public Connection getConnection(final long hardTimeout) throws SQLException { suspendResumeLock.acquire(); final long startTime = currentTime(); try { //使用預設的30s 超時時間 long timeout = hardTimeout; do { //進入循環,在指定時間內獲取可用連接 //從 connectionBag 中獲取連接 PoolEntry poolEntry = connectionBag.borrow(timeout, MILLISECONDS); if (poolEntry == null) { break; // We timed out... break and throw exception } final long now = currentTime(); //連接對象被標記清除或不滿足存活條件時,關閉該連接 if (poolEntry.isMarkedEvicted() || (elapsedMillis(poolEntry.lastAccessed, now) > aliveBypassWindowMs && !isConnectionAlive(poolEntry.connection))) { closeConnection(poolEntry, poolEntry.isMarkedEvicted() ? EVICTED_CONNECTION_MESSAGE : DEAD_CONNECTION_MESSAGE); timeout = hardTimeout - elapsedMillis(startTime); } //成功獲得連接對象 else { metricsTracker.recordBorrowStats(poolEntry, startTime); return poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now); } } while (timeout > 0L); //超時了,拋出異常 metricsTracker.recordBorrowTimeoutStats(startTime); throw createTimeoutException(startTime); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new SQLException(poolName + ' - Interrupted during connection acquisition', e); } finally { suspendResumeLock.release(); } }}

getConnection() 方法展示了獲取連接的整個流程,其中 connectionBag 是用于存放連接對象的容器對象。如果從 connectionBag 獲得的連接不再滿足存活條件,那么會將其手動關閉,代碼如下:

void closeConnection(final PoolEntry poolEntry, final String closureReason) { //移除連接對象 if (connectionBag.remove(poolEntry)) { final Connection connection = poolEntry.close(); //異步關閉連接 closeConnectionExecutor.execute(() -> { quietlyCloseConnection(connection, closureReason); //由于可用連接變少,將觸發填充連接池的任務 if (poolState == POOL_NORMAL) { fillPool(); } }); } }

注意到,只有當連接滿足下面條件中的其中一個時,會被執行 close。

isMarkedEvicted() 的返回結果是 true,即標記為清除,如果連接存活時間超出最大生存時間(maxLifeTime),或者距離上一次使用超過了idleTimeout,會被定時任務標記為清除狀態,清除狀態的連接在獲取的時候才真正 close。 500ms 內沒有被使用,且連接已經不再存活,即 isConnectionAlive() 返回 false

由于我們把 idleTimeout 和 maxLifeTime 都設置得非常大,因此需重點檢查 isConnectionAlive 方法中的判斷,如下:

public class PoolBase{ //判斷連接是否存活 boolean isConnectionAlive(final Connection connection) { try { try { //設置 JDBC 連接的執行超時 setNetworkTimeout(connection, validationTimeout); final int validationSeconds = (int) Math.max(1000L, validationTimeout) / 1000; //如果沒有設置 TestQuery,使用 JDBC4 的校驗接口 if (isUseJdbc4Validation) { return connection.isValid(validationSeconds); } //使用 TestQuery(如 select 1)語句對連接進行探測 try (Statement statement = connection.createStatement()) { if (isNetworkTimeoutSupported != TRUE) { setQueryTimeout(statement, validationSeconds); } statement.execute(config.getConnectionTestQuery()); } } finally { setNetworkTimeout(connection, networkTimeout); if (isIsolateInternalQueries && !isAutoCommit) { connection.rollback(); } } return true; } catch (Exception e) { //發生異常時,將失敗信息記錄到上下文 lastConnectionFailure.set(e); logger.warn('{} - Failed to validate connection {} ({}). Possibly consider using a shorter maxLifetime value.', poolName, connection, e.getMessage()); return false; } }}

我們看到,在PoolBase.isConnectionAlive 方法中對連接執行了一系列的探測,如果發生異常還會將異常信息記錄到當前的線程上下文中。隨后,在 HikariPool 拋出異常時會將最后一次檢測失敗的異常也一同收集,如下:

private SQLException createTimeoutException(long startTime){ logPoolState('Timeout failure '); metricsTracker.recordConnectionTimeout(); String sqlState = null; //獲取最后一次連接失敗的異常 final Throwable originalException = getLastConnectionFailure(); if (originalException instanceof SQLException) { sqlState = ((SQLException) originalException).getSQLState(); } //拋出異常 final SQLException connectionException = new SQLTransientConnectionException(poolName + ' - Connection is not available, request timed out after ' + elapsedMillis(startTime) + 'ms.', sqlState, originalException); if (originalException instanceof SQLException) { connectionException.setNextException((SQLException) originalException); } return connectionException;}

這里的異常消息和我們在業務服務中看到的異常日志基本上是吻合的,即除了超時產生的 “Connection is not available, request timed out after xxxms” 消息之外,日志中還伴隨輸出了校驗失敗的信息:

Caused by: java.sql.SQLException: Connection.setNetworkTimeout cannot be called on a closed connection

    at org.mariadb.jdbc.internal.util.exceptions.ExceptionMapper.getSqlException(ExceptionMapper.java:211) ~[mariadb-java-client-2.2.6.jar!/:?]

    at org.mariadb.jdbc.MariaDbConnection.setNetworkTimeout(MariaDbConnection.java:1632) ~[mariadb-java-client-2.2.6.jar!/:?]

    at com.zaxxer.hikari.pool.PoolBase.setNetworkTimeout(PoolBase.java:541) ~[HikariCP-2.7.9.jar!/:?]

    at com.zaxxer.hikari.pool.PoolBase.isConnectionAlive(PoolBase.java:162) ~[HikariCP-2.7.9.jar!/:?]

    at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:172) ~[HikariCP-2.7.9.jar!/:?]

    at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:148) ~[HikariCP-2.7.9.jar!/:?]

    at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:128) ~[HikariCP-2.7.9.jar!/:?]

到這里,我們已經將應用獲得連接的代碼大致梳理了一遍,整個過程如下圖所示:

詳解MySQL連接掛死的原因

從執行邏輯上看,連接池的處理并沒有問題,相反其在許多細節上都考慮到位了。在對非存活連接執行 close 時,同樣調用了 removeFromBag 動作將其從連接池中移除,因此也不應該存在僵尸連接對象的問題。

那么,我們之前的推測應該就是錯誤的!

陷入焦灼

在代碼分析之余,開發同學也注意到當前使用的 hikariCP 版本為 3.4.5,而環境上出問題的業務服務卻是 2.7.9 版本,這仿佛預示著什么… 讓我們再次假設 hikariCP 2.7.9 版本存在某種未知的 BUG,導致了問題的產生。

為了進一步分析連接池對于服務端故障的行為處理,我們嘗試在本地機器上進行模擬,這一次使用了 hikariCP 2.7.9 版本進行測試,并同時將 hikariCP 的日志級別設置為 DEBUG。

模擬場景中,會由 由本地應用程序連接本機的 MySQL 數據庫進行操作,步驟如下:

1. 初始化數據源,此時連接池 min-idle 設置為 10;

2. 每隔50ms 執行一次SQL操作,查詢當前的元數據表;

3. 將 MySQL 服務停止一段時間,觀察業務表現;

4. 將 MySQL 服務重新啟動,觀察業務表現。

最終產生的日志如下:

//初始化過程,建立10個連接

DEBUG -HikariPool.logPoolState - Pool stats (total=1, active=1, idle=0, waiting=0)

DEBUG -HikariPool$PoolEntryCreator.call- Added connection MariaDbConnection@71ab7c09

DEBUG -HikariPool$PoolEntryCreator.call- Added connection MariaDbConnection@7f6c9c4c

DEBUG -HikariPool$PoolEntryCreator.call- Added connection MariaDbConnection@7b531779

...

DEBUG -HikariPool.logPoolState- After adding stats (total=10, active=1, idle=9, waiting=0)

//執行業務操作,成功

execute statement: true

test time -------1

execute statement: true

test time -------2

...

//停止MySQL

...

//檢測到無效連接

WARN  -PoolBase.isConnectionAlive - Failed to validate connection MariaDbConnection@9225652 ((conn=38652) 

Connection.setNetworkTimeout cannot be called on a closed connection). Possibly consider using a shorter maxLifetime value.

WARN  -PoolBase.isConnectionAlive - Failed to validate connection MariaDbConnection@71ab7c09 ((conn=38653) 

Connection.setNetworkTimeout cannot be called on a closed connection). Possibly consider using a shorter maxLifetime value.

//釋放連接

DEBUG -PoolBase.quietlyCloseConnection(PoolBase.java:134) - Closing connection MariaDbConnection@9225652: (connection is dead) 

DEBUG -PoolBase.quietlyCloseConnection(PoolBase.java:134) - Closing connection MariaDbConnection@71ab7c09: (connection is dead)

//嘗試創建連接失敗

DEBUG -HikariPool.createPoolEntry - Cannot acquire connection from data source

java.sql.SQLNonTransientConnectionException: Could not connect to address=(host=localhost)(port=3306)(type=master) : 

Socket fail to connect to host:localhost, port:3306. Connection refused: connect

Caused by: java.sql.SQLNonTransientConnectionException: Socket fail to connect to host:localhost, port:3306. Connection refused: connect

    at internal.util.exceptions.ExceptionFactory.createException(ExceptionFactory.java:73) ~[mariadb-java-client-2.6.0.jar:?]

    ...

//持續失敗.. 直到MySQL重啟

//重啟后,自動創建連接成功

DEBUG -HikariPool$PoolEntryCreator.call -Added connection MariaDbConnection@42c5503e

DEBUG -HikariPool$PoolEntryCreator.call -Added connection MariaDbConnection@695a7435

//連接池狀態,重新建立10個連接

DEBUG -HikariPool.logPoolState(HikariPool.java:421) -After adding stats (total=10, active=1, idle=9, waiting=0)

//執行業務操作,成功(已經自愈)

execute statement: true

從日志上看,hikariCP 還是能成功檢測到壞死的連接并將其踢出連接池,一旦 MySQL 重新啟動,業務操作又能自動恢復成功了。根據這個結果,基于 hikariCP 版本問題的設想也再次落空,研發同學再次陷入焦灼。

撥開云霧見光明

多方面求證無果之后,我們最終嘗試在業務服務所在的容器內進行抓包,看是否能發現一些蛛絲馬跡。

進入故障容器,執行tcpdump -i eth0 tcp port 30052進行抓包,然后對業務接口發起訪問。

此時令人詭異的事情發生了,沒有任何網絡包產生!而業務日志在 30s 之后也出現了獲取連接失敗的異常。

我們通過 netstat 命令檢查網絡連接,發現只有一個 ESTABLISHED 狀態的 TCP 連接。

詳解MySQL連接掛死的原因

也就是說,當前業務實例和 MySQL 服務端是存在一個建好的連接的,但為什么業務還是報出可用連接呢?

推測可能原因有二:

該連接被某個業務(如定時器)一直占用。 該連接實際上還沒有辦法使用,可能處于某種僵死的狀態。

對于原因一,很快就可以被推翻,一來當前服務并沒有什么定時器任務,二來就算該連接被占用,按照連接池的原理,只要沒有達到上限,新的業務請求應該會促使連接池進行新連接的建立,那么無論是從 netstat 命令檢查還是 tcpdump 的結果來看,不應該一直是只有一個連接的狀況。

那么,情況二的可能性就很大了。帶著這個思路,繼續分析 Java 進程的線程棧。

執行 kill -3 pid 將線程棧輸出后分析,果不其然,在當前 thread stack 中發現了如下的條目:

'HikariPool-1 connection adder' #121 daemon prio=5 os_prio=0 tid=0x00007f1300021800 nid=0xad runnable [0x00007f12d82e5000]

   java.lang.Thread.State: RUNNABLE

    at java.net.SocketInputStream.socketRead0(Native Method)

    at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)

    at java.net.SocketInputStream.read(SocketInputStream.java:171)

    at java.net.SocketInputStream.read(SocketInputStream.java:141)

    at java.io.FilterInputStream.read(FilterInputStream.java:133)

    at org.mariadb.jdbc.internal.io.input.ReadAheadBufferedStream.fillBuffer(ReadAheadBufferedStream.java:129)

    at org.mariadb.jdbc.internal.io.input.ReadAheadBufferedStream.read(ReadAheadBufferedStream.java:102)

    - locked <0x00000000d7f5b480> (a org.mariadb.jdbc.internal.io.input.ReadAheadBufferedStream)

    at org.mariadb.jdbc.internal.io.input.StandardPacketInputStream.getPacketArray(StandardPacketInputStream.java:241)

    at org.mariadb.jdbc.internal.io.input.StandardPacketInputStream.getPacket(StandardPacketInputStream.java:212)

    at org.mariadb.jdbc.internal.com.read.ReadInitialHandShakePacket.<init>(ReadInitialHandShakePacket.java:90)

    at org.mariadb.jdbc.internal.protocol.AbstractConnectProtocol.createConnection(AbstractConnectProtocol.java:480)

    at org.mariadb.jdbc.internal.protocol.AbstractConnectProtocol.connectWithoutProxy(AbstractConnectProtocol.java:1236)

    at org.mariadb.jdbc.internal.util.Utils.retrieveProxy(Utils.java:610)

    at org.mariadb.jdbc.MariaDbConnection.newConnection(MariaDbConnection.java:142)

    at org.mariadb.jdbc.Driver.connect(Driver.java:86)

    at com.zaxxer.hikari.util.DriverDataSource.getConnection(DriverDataSource.java:138)

    at com.zaxxer.hikari.pool.PoolBase.newConnection(PoolBase.java:358)

    at com.zaxxer.hikari.pool.PoolBase.newPoolEntry(PoolBase.java:206)

    at com.zaxxer.hikari.pool.HikariPool.createPoolEntry(HikariPool.java:477)

這里顯示HikariPool-1 connection adder這個線程一直處于 socketRead 的可執行狀態。從命名上看該線程應該是 HikariCP 連接池用于建立連接的任務線程,socket 讀操作則來自于 MariaDbConnection.newConnection() 這個方法,即 mariadb-java-client 驅動層建立 MySQL 連接的一個操作,其中 ReadInitialHandShakePacket 初始化則屬于 MySQL 建鏈協議中的一個環節。

簡而言之,上面的線程剛好處于建鏈的一個過程態,關于 mariadb 驅動和 MySQL 建鏈的過程大致如下:

詳解MySQL連接掛死的原因

MySQL 建鏈首先是建立 TCP 連接(三次握手),客戶端會讀取 MySQL 協議的一個初始化握手消息包,內部包含 MySQL 版本號,鑒權算法等等信息,之后再進入身份鑒權的環節。

這里的問題就在于 ReadInitialHandShakePacket 初始化(讀取握手消息包)一直處于 socket read 的一個狀態。

如果此時 MySQL 遠端主機故障了,那么該操作就會一直卡住。而此時的連接雖然已經建立(處于 ESTABLISHED 狀態),但卻一直沒能完成協議握手和后面的身份鑒權流程,即該連接只能算一個半成品(無法進入 hikariCP 連接池的列表中)。從故障服務的 DEBUG 日志也可以看到,連接池持續是沒有可用連接的,如下:

DEBUG HikariPool.logPoolState --> Before cleanup stats (total=0, active=0, idle=0, waiting=3)

另一個需要解釋的問題則是,這樣一個 socket read 操作的阻塞是否就造成了整個連接池的阻塞呢?

經過代碼走讀,我們再次梳理了 hikariCP 建立連接的一個流程,其中涉及到幾個模塊:

HikariPool,連接池實例,由該對象連接的獲取、釋放以及連接的維護。 ConnectionBag,連接對象容器,存放當前的連接對象列表,用于提供可用連接。 AddConnectionExecutor,添加連接的執行器,命名如 “HikariPool-1 connection adder”,是一個單線程的線程池。 PoolEntryCreator,添加連接的任務,實現創建連接的具體邏輯。 HouseKeeper,內部定時器,用于實現連接的超時淘汰、連接池的補充等工作。

HouseKeeper 在連接池初始化后的 100ms 觸發執行,其調用 fillPool() 方法完成連接池的填充,例如 min-idle 是10,那么初始化就會創建10個連接。ConnectionBag 維護了當前連接對象的列表,該模塊還維護了請求連接者(waiters)的一個計數器,用于評估當前連接數的需求。

其中,borrow 方法的邏輯如下:

public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException { // 嘗試從 thread-local 中獲取 final List<Object> list = threadList.get(); for (int i = list.size() - 1; i >= 0; i--) { ... } // 計算當前等待請求的任務 final int waiting = waiters.incrementAndGet(); try { for (T bagEntry : sharedList) { if (bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) { //如果獲得了可用連接,會觸發填充任務 if (waiting > 1) { listener.addBagItem(waiting - 1); } return bagEntry; } } //沒有可用連接,先觸發填充任務 listener.addBagItem(waiting); //在指定時間內等待可用連接進入 timeout = timeUnit.toNanos(timeout); do { final long start = currentTime(); final T bagEntry = handoffQueue.poll(timeout, NANOSECONDS); if (bagEntry == null || bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) { return bagEntry; } timeout -= elapsedNanos(start); } while (timeout > 10_000); return null; } finally { waiters.decrementAndGet(); } }

注意到,無論是有沒有可用連接,該方法都會觸發一個 listener.addBagItem() 方法,HikariPool 對該接口的實現如下:

public void addBagItem(final int waiting) { final boolean shouldAdd = waiting - addConnectionQueueReadOnlyView.size() >= 0; // Yes, >= is intentional. if (shouldAdd) { //調用 AddConnectionExecutor 提交創建連接的任務 addConnectionExecutor.submit(poolEntryCreator); } else { logger.debug('{} - Add connection elided, waiting {}, queue {}', poolName, waiting, addConnectionQueueReadOnlyView.size()); } }PoolEntryCreator 則實現了創建連接的具體邏輯,如下:public class PoolEntryCreator{ @Override public Boolean call() { long sleepBackoff = 250L; //判斷是否需要建立連接 while (poolState == POOL_NORMAL && shouldCreateAnotherConnection()) { //創建 MySQL 連接 final PoolEntry poolEntry = createPoolEntry(); if (poolEntry != null) { //建立連接成功,直接返回。 connectionBag.add(poolEntry); logger.debug('{} - Added connection {}', poolName, poolEntry.connection); if (loggingPrefix != null) { logPoolState(loggingPrefix); } return Boolean.TRUE; } ... } // Pool is suspended or shutdown or at max size return Boolean.FALSE; }}

由此可見,AddConnectionExecutor 采用了單線程的設計,當產生新連接需求時,會異步觸發 PoolEntryCreator 任務進行補充。其中 PoolEntryCreator. createPoolEntry() 會完成 MySQL 驅動連接建立的所有事情,而我們的情況則恰恰是MySQL 建鏈過程產生了永久性阻塞。因此無論后面怎么獲取連接,新來的建鏈任務都會一直排隊等待,這便導致了業務上一直沒有連接可用。

下面這個圖說明了 hikariCP 的建鏈過程:

詳解MySQL連接掛死的原因

好了,讓我們在回顧一下前面關于可靠性測試的場景:

首先,MySQL 主實例發生故障,而緊接著 hikariCP 則檢測到了壞的連接(connection is dead)并將其釋放,在釋放關閉連接的同時又發現連接數需要補充,進而立即觸發了新的建鏈請求。而問題就剛好出在這一次建鏈請求上,TCP 握手的部分是成功了(客戶端和 MySQL VM 上 nodePort 完成連接),但在接下來由于當前的 MySQL 容器已經停止(此時 VIP 也切換到了另一臺 MySQL 實例上),因此客戶端再也無法獲得原 MySQL 實例的握手包響應(該握手屬于MySQL應用層的協議),此時便陷入了長時間的阻塞式 socketRead 操作。而建鏈請求任務恰恰好采用了單線程運作,進一步則導致了所有業務的阻塞。

三、解決方案

在了解了事情的來龍去脈之后,我們主要考慮從兩方面進行優化:

優化一,增加 HirakiPool 中 AddConnectionExecutor 線程的數量,這樣即使第一個線程出現掛死,還有其他的線程能參與建鏈任務的分配。 優化二,出問題的 socketRead 是一種同步阻塞式的調用,可通過 SO_TIMEOUT 來避免長時間掛死。

對于優化點一,我們一致認為用處并不大,如果連接出現了掛死那么相當于線程資源已經泄露,對服務后續的穩定運行十分不利,而且 hikariCP 在這里也已經將其寫死了。因此關鍵的方案還是避免阻塞式的調用。

查閱了 mariadb-java-client 官方文檔后,發現可以在 JDBC URL 中指定網絡IO 的超時參數,如下:

詳解MySQL連接掛死的原因

具體參考:https://mariadb.com/kb/en/about-mariadb-connector-j/

如描述所說的,socketTimeout 可以設置 socket 的 SO_TIMEOUT 屬性,從而達到控制超時時間的目的。默認是 0,即不超時。

我們在 MySQL JDBC URL 中加入了相關的參數,如下:

spring.datasource.url=jdbc:mysql://10.0.71.13:33052/appdb?socketTimeout=60000&connectTimeout=30000&serverTimezone=UTC

此后對 MySQL 可靠性場景進行多次驗證,發現連接掛死的現象已經不再出現,此時問題得到解決。

四、小結

本次分享了一次關于 MySQL 連接掛死問題排查的心路歷程,由于環境搭建的工作量巨大,而且該問題復現存在偶然性,整個分析過程還是有些坎坷的(其中也踩了坑)。的確,我們很容易被一些表面的現象所迷惑,而覺得問題很難解決時,更容易帶著偏向性思維去處理問題。例如本例中曾一致認為連接池出現了問題,但實際上卻是由于 MySQL JDBC 驅動(mariadb driver)的一個不嚴謹的配置所導致。

從原則上講,應該避免一切可能導致資源掛死的行為。如果我們能在前期對代碼及相關配置做好充分的排查工作,相信 996 就會離我們越來越遠。

以上就是詳解MySQL連接掛死的原因的詳細內容,更多關于MySQL連接掛死的原因的資料請關注好吧啦網其它相關文章!

標簽: MySQL 數據庫
相關文章:
主站蜘蛛池模板: 日韩中文字幕精品视频 | 亚洲美女黄色片 | 日韩视频一区二区三区 | 人人妻人人澡人人爽欧美一区九九 | 亚洲精品视频网 | 色一涩| 极品少妇被猛得白浆直流草莓视频 | 久久99国产亚洲高清观看首页 | 北条麻妃二三区 | 国产精品186在线观看在线播放 | 欧美性www | 久久综合久 | 亚洲国产精品福利片在线观看 | 国内精品久久久久久久久久久久 | 丁香桃色午夜亚洲一区二区三区 | 久久亚洲区 | 精久国产一区二区三区四区 | 国产爽爽久久影院潘金莲 | 日韩 亚洲 中文 图片 小说 | 女女女bbbbbb毛片在线法国 | www国产精品视频 | 农村乡下女人毛片 | 天干天干天啪啪夜爽爽av小说 | 这里精品 | 国产综合av | 97综合| 岛国av免费观看 | 中文一区在线观看 | 国产精品又黄又爽又色无遮挡 | 成人深夜免费视频 | 亚洲精品99久久久久中文字幕 | 大桥未久在线视频 | 蜜桃视频成人 | 夫妇交换性三中文字幕 | 激情深爱五月 | 空姐毛片 | 亚洲日韩在线中文字幕综合 | 国产成人福利 | 色婷婷久久综合 | 日韩av毛片| 色偷偷av一区二区三区 | 国产盗摄夫妻原创视频在线观看 | 色视频在线播放 | 日批在线观看 | av中文字幕网 | 性久久久久久久久久 | 一区二区亚洲视频 | 久久无码专区国产精品 | 国产精品黄在线观看免费软件 | 中文国产成人精品久久不卡 | 91灌醉下药在线观看播放 | 国产精品综合网 | 高清欧美性猛交xxxx黑人猛交 | 日韩激情视频网站 | 又色又爽又黄高潮的免费视频 | 国产精品9999久久久久仙踪林 | 欧美黄色大片免费看 | 日本中文在线视频 | 亚洲乱码av中文一区二区 | 国产午夜亚洲精品羞羞网站 | 亚洲日本三级 | 2019亚洲日韩新视频 | 九九九九热精品免费视频点播观看 | 亚洲乱码国产乱码精品精软件 | 亚洲一区精品二人人爽久久 | 女性女同性aⅴ免费观女性恋 | 欧美成在线观看 | 日韩a无v码在线播放免费 | 欧美三级午夜理伦三级 | 久草视频资源 | 麻豆国产va免费精品高清在线 | 国产精品久久久久久久久免费桃花 | 中国真实的国产乱xxxx | 新版天堂资源中文8在线 | 91成人在线免费观看 | 日本一本在线观看 | 日本性网站 | 国产精品久久久久9999小说 | 91美女图片黄在线观看 | 人人爽人人爱 | 国产亚洲精品久久网站 | 岛国片人妻三上悠亚 | 亚洲一区在线免费观看 | 免费在线看黄视频 | 日韩av在线一区 | 欧美日韩在线高清 | 五月激情六月丁香激情天堂 | 人妻av综合天堂一区 | 内射干少妇亚洲69xxx | 亚洲女子a中天字幕 | 99re热这里只有精品视频 | 五月天福利视频 | 亚洲另类在线观看 | 日本福利一区二区 | 免费线上av | 精品动漫3d一区二区三区免费版 | 久久这里只有精品23 | jvid视频 | 深夜视频一区二区 | 深夜在线播放 | 男女无遮挡做爰猛烈视频 | 中文字幕人成人乱码亚洲影视的特点 | 叶子楣裸乳照无奶罩视频 | 国产精品人八做人人女人a级刘 | 日韩av无码精品一二三区 | 全球av集中精品导航福利 | 欧美精品极品 | 亚洲人成在线播放 | 在线a毛片 | 亚洲一级影片 | 久久九九日本韩国精品 | 台湾佬中文娱乐22vvvv | 特一级黄色 | 被c到高潮疯狂喷水国产 | 欧美日韩中文字幕在线 | 久操福利 | 免费人成在线观看网站 | 奇米影视777在线观看 | 人妻激情偷乱视频一区二区三区 | 欧美专区亚洲专区 | 久久综合给合久久狠狠狠97色 | 日韩精品一区二区在线播放 | 99自拍| 国产91丝袜在线播放0 | 国产三区精品 | 欧美激情视频在线播放 | 日本久久爱| 8090毛片| 久久网页| 天天操操操操 | 谁有免费黄色网址 | 国产精品福利一区二区 | 日本欧美久久久久免费播放网 | 亚洲欧洲日韩在线 | 黄网在线免费观看 | av在线免费播放网址 | 亚洲成a人蜜臀av在线播放 | 欧美日韩不卡视频 | 一本色道久久综合亚洲精品高清 | 成人美女黄网站色大色费全看在线观看 | 亚洲黄色片子 | 日本高清一区免费中文视频 | 果冻传媒一区 | 播放灌醉水嫩大学生国内精品 | 夜夜高潮夜夜爽高清完整版1 | 韩日在线| 五月婷婷亚洲综合 | 亚洲视频在线观看视频 | 欧美精品乱码99久久蜜桃 | 涩涩视频免费看 | 午夜涩涩 | 欧美性猛交xxxx免费视频软件 | 国产成人精品亚洲日本在线观看 | 黑人vs日本人ⅹxxxhd | 日本在线免费观看 | 国产成人一区二区三区在线播放 | 91久久国产最好的精华液 | 国产男女爽爽爽免费视频 | 中文字幕在线视频观看 | a毛片在线 | 国产免费一区二区三区在线能观看 | 国产激情久久 | 黄色片在线看 | 免费无码成人av在线播放不卡 | yy6080久久伦理一区二区 | 99亚洲精品在线 | 男人天堂欧美 | 爆乳熟妇一区二区三区霸乳 | 亚洲欧美小视频 | 中文字幕在线观看二区 | 成人午夜天 | 午夜丰满少妇高清毛片1000部 | 奴色虐av一区二区三区 | 性色蜜桃臀x66av | 欧州一区二区三区 | 一本久久a精品一合区久久久 | 国产精品午夜影院 | 日韩超碰人人爽人人做人人添 | 亚洲欧美激情网站 | 国产免费一级视频 | 国产日产欧产精品精品 | 国产深夜视频在线观看 | 情趣内衣a∨片在线观看 | xxx69美国| 天天操夜夜拍 | 欧美无砖专区免费 | 伊人成人在线 | 亚洲第一二三四区 | 国产91精品ai换脸 | 亚洲痴女 | 在线精品亚洲 | 欧美第一视频 | 一本之道色综合网站 | 午夜高潮视频 | 国产精品入口66mio | 老子影院午夜伦手机不四虎卡 | 东北女人啪啪ⅹxx对白 | 欧美成人一区二区三区片免费 | 国产爽爽久久影院hd | 亚洲人成网站免费播放 | 全黄一级毛片 | 色婷婷亚洲六月婷婷中文字幕 | 免费裸体无遮挡黄网站免费看 | 国产成人久久777777 | 久久国产36精品色熟妇 | 四虎影视免费观看 | 无码人妻毛片丰满熟妇区毛片 | 欧美午夜精品久久久久久人妖 | 少妇人妻挤奶水中文视频毛片 | 成年免费视频黄网站在线观看 | 欧美变态绿帽cuckold | 少妇看片 | www狠狠操| 欧美国产精品一二三 | 亚洲精品久久久久玩吗 | 少妇一级片| 中文乱码人妻系列一区二区 | 国产瑟瑟视频 | 希岛爱理和黑人中文字幕系列 | 一个色的综合 | 欧美xxxx做受欧美1314 | 国产亚洲真人做受在线观看 | 精彩动漫 - 91爱爱 | 少妇爆乳无码专区 | 精品日韩一区 | 国产三级全黄 | 好紧好爽再进去一点在线视频 | 日韩一卡2卡3卡4卡2021免费观看国色天香 | 中文字幕视频 | 中国东北少妇bbb真爽 | 欧美日韩激情 | 午夜精品久久久 | 裸体女人a级一片 | 国产女同疯狂互摸系列3 | av解说在线 | 一级片视频免费观看 | 国产高潮流白浆喷水视频 | www毛片| 视频免费精品 | 天天爽天天爽天天爽 | 国产无套粉嫩白浆内谢软件 | 亚洲性色av私人影院无码 | 肉色超薄丝袜脚交91 | 日韩精品无码一区二区三区 | 所有明星裸露影片合集在线播放 | 日本少妇色 | 亚洲日韩av片在线观看 | 尤物99国产成人精品视频 | 一级片久久久久 | 在线成人欧美 | 美女av网| 婷婷在线观看视频 | 伊人春色网站 | 69精品在线 | 日产精品入口 | 国产欧美熟妇另类久久久 | 色偷偷噜噜噜亚洲男人 | 色黄视频| 久久久午夜视频 | 亚洲一区二区三区无码国产 | a国产精品| 欧美大成色www永久网站婷 | 樱花草国产18久久久久 | 一本一道久久a久久综合蜜桃 | 精品国产片一区二区三区 | 十八女人国产毛毛片视频 | 成人xxxxx | 疯狂做爰的爽文多肉小说王爷 | 中文字幕+乱码+中文 | 婷婷激情亚洲 | 欧美一区二区三区久久 | 国产真实乱对白精彩 | 久久大片| 免费观看全黄做爰大片国产 | 日本久久久久久久久久久 | 五月天青青草 | 小箩莉末发育娇小性色xxxx | 日本青草视频 | 免费在线网站 | 精品中出 | 中文字幕精品亚洲一区 | 欧美 国产 综合 | 国产精品免费久久 | 日本三级久久久 | 国产综合99 | 欧美成aⅴ人高清免费 | 女人与黑拘的毛片 | 亚洲六月丁香色婷婷综合久久 | 天天干伊人 | 日本特黄特黄刺激大片 | 亚洲色大成网站www永久一区 | 中文字幕国产一区二区 | www.色就是色.com | www浪潮avcom| 野战视频aaaaa免费观看 | 日本一区二区三区视频在线观看 | 91丨porny丨在线 | 大地av| 国产精品久久久久久久久久免费看 | 亚洲v欧美v另类v综合v日韩v | 成年人网站免费在线观看 | 国产99对白在线播放 | 亚洲日韩视频免费观看 | 日韩久久无码免费毛片软件 | 深夜福利网 | 日韩亚洲制服丝袜中文字幕 | 奴性白洁会所调教 | 国产久久精品 | 国产又粗又猛又爽又黄视频 | 成人在线国产视频 | 爱丝aiss无内高清丝袜视频 | 免费精品在线观看 | 天天做天天爱夜夜爽少妇 | 韩国性经典xxxxhd | 天堂网www在线资源中文 | 少妇淫片 | 日欧一片内射va在线影院 | 乱码精品国产成人观看免费 | 97人人模人人爽人人喊网 | 日韩欧美精品在线 | 椎名由奈中文字幕 | 小草社区视频在线观看 | 长河落日电视连续剧免费观看01 | 美女啪啪无遮挡 | 阿v天堂在线观看 | 丁香社区五月天 | 在线观看黄色的网站 | 国产免费一区二区三区最新6 | 亚洲性喷水 | 成人免费一级伦理片在线播放 | 亚洲精品中文字幕久久久久下载 | 一本一本久久a久久精品综合麻豆 | 色 综合 欧美 亚洲 国产 | 337p日本欧洲亚洲大胆色噜噜 | 无码国产成人午夜电影在线观看 | 337p人体粉嫩胞高清视频 | 特黄aaaaaaaaa毛片免费视频 | 国产精品日韩精品欧美精品 | 伊人自拍| 国产手机在线αⅴ片无码观看 | 亚洲成人经典 | 成人免费公开视频 | 欧美天堂一区二区三区 | 99精品国自产在线 | 一级国产航空美女毛片内谢 | 亚洲免费国产 | 国产一区免费在线 | 欧美成人黑人xx视频免费观看 | 欧美日韩精品人妻狠狠躁免费视频 | 国产精品第5页 | 成人欧美一区二区三区 | 天堂av手机在线观看 | 香港三级日本三级a视频 | 捆绑凌虐一区二区三区 | 91一区视频| 伊人五月综合 | 在线观看黄色毛片 | 九九在线视频 | 波霸ol色综合久久 | 亚洲天堂精品在线观看 | 国产va免费精品观看精品 | 高中生自慰www网站 日本护士毛茸茸高潮 | 久久精品无码一区二区日韩av | 日本女人一级片 | 国产精品久久久久久久久久久久久久久久久久 | 国产精品无套粉嫩白浆在线 | 久久精品国产亚洲夜色av网站 | 区二区欧美性插b在线视频网站 | 欧美一级二级片 | a男人的天堂久久a毛片 | 四色永久访问网站 | 亚洲国产精品91 | 日本娇小侵犯hd | 国产乱色精品成人免费视频 | 美女又爽又黄又免费 | 国产91精清纯白嫩高中在线观看 | 亚洲精品自在在线观看 | 法国伦理少妇愉情 | 美女视频黄8视频大全 | 国产又粗又黄的视频 | 丰满爆乳在线播放 | 国产免费网站在线观看 | 日日干日日操 | 男女啪啪无遮挡免费网站 | 久久精品国产精品亚洲毛片 | 日本韩国在线 | 亚洲第一天堂影院 | 国产麻豆视频 | 国产网站一区二区 | 中文字幕黄色片 | 搡老岳熟女国产熟妇 | 国产无套粉嫩白浆内精品 | 少妇性荡欲视频 | 久操香蕉| 色窝窝无码一区二区三区 | 日日噜噜噜夜夜爽爽狠狠视频97 | 国产女主播视频一区二区三区 | 日本肥老妇色xxxxx日本老妇 | 欧美精品乱码久久久久久按摩 | 男人用嘴添女人私密视频 | wwwwww国产| 中文字幕乱视频 | 免费观看黄色av | www.日本黄| 亚洲高清久久 | 91桃色网站 | 亚洲日本va午夜中文字幕一区 | 国产欧美一区二区精品性色 | 国产精品黑色丝袜久久 | 久久综合99re88久久爱 | 蜜桃视频中文字幕 | 日本一区二区不卡在线 | 激情久久一区 | 女色综合 | 欧美绝顶高潮抽搐喷水合集 | 乱人伦中文字幕 | 99色在线| 青青青手机在线视频 | 日产精品高潮呻吟av久久 | 亚洲r成人av久久人人爽澳门赌 | 四虎国产精品一区二区 | 少妇挑战三个黑人惨叫4p国语 | 日本大bbb裸体欣赏 日本大尺度吃奶呻吟视频 日本大尺度吃奶做爰过程 日本大尺度吃奶做爰久久久绯色 | 少妇精品久久久一区二区三区 | 久久久精品久久日韩一区综合 | 男人午夜av | 国产精品免费一区二区 | 日本中文字幕不卡 | 18禁无遮挡羞羞污污污污免费 | 亚洲精品成人无码中文毛片 | 91精品国产99久久久久久红楼 | 日韩国产高清一区二区 | 四虎影视www在线播放 | 国产午夜精品一区二区三区四区 | 另类天堂网不卡另类系列 | 日韩美女亚洲99久久二区 | 日本一区二区网站 | 欧美激情图 | 国产二区三区视频 | 国产免费网址 | 国产成人三级在线观看视频 | 欧美成人精品第一区 | 人人射av | 国产性一乱一性一伧一色 | 久久网伊人| 国内乱子对白免费在限 | 婷婷情更久日本久久久片 | 最爽无遮挡行房视频 | 黄在线观看 | 92精品国产成人观看免费 | 欧美精品一区二区三区在线四季 | 无码任你躁久久久久久老妇 | 无码中文字幕在线播放2 | 天天爱天天做天天爽 | 日韩一级片网址 | 欧美性xxxxx极品少妇 | 欧美福利在线视频 | 一本久久综合亚洲鲁鲁五月天 | 国产精品9999久久久久仙踪林 | 国产精品久久久久久久久久免费 | 亚洲国产婷婷香蕉久久久久久99 | 污污网站在线观看免费 | 新疆少妇xxxx做受 | 亚洲精华国产精华精华液网站 | 国语对白新婚少妇在线观看 | 国产精品成人久久电影 | 国产精品vr专区 | 上司人妻互换hd无码 | 日日免费视频 | 少妇厨房愉情理伦bd在线观看 | 亚洲区欧美 | 丰满少妇弄高潮了www | 国产特黄aaa大片免费观看 | 成av人片在线观看www | 性生av免费播放 | 国产美女牲交视频 | 国产免费午夜福利757 | 国产一区二区三区高清在线观看 | 福利免费在线观看 | 91久久国产精品 | 成人啪啪18免费网站 | 色播网址 | 国产精品乱码一区二区三区视频 | 亚洲女同女同女同女同女同69 | 成人一级网站 | 97成人精品区在线播放 | 91丨九色丨黑人外教 | 欧美视频一区二区三区四区在线观看 | 欧美日韩一卡2卡三卡4卡 乱码欧美孕交 | 国产嫩草一区二区三区在线观看 | 无毛av | 国产高潮好爽受不了了夜夜做 | 午夜福利影院私人爽爽 | 理论视频在线观看 | jizz18欧美18| xxhd麻豆xxhd激情视频 | 国模欢欢炮交啪啪150 | 91精品国产成人www | 免费嗨片首页中文字幕 | 国产精品午夜小视频观看 | 做爰xxxⅹ高潮69网站 | 日本黄网在线观看 | 性欧美色图 | 91们嫩草伦理 | 日日鲁鲁鲁夜夜爽爽狠狠 | 在线网站免费观看入口 | 91成人精品一区在线播放69 | 免费在线观看黄 | 青青操在线 | 日韩av不卡在线 | 亚洲欧美在线人成最新 | 99在线免费 | 亚洲精品中文字幕无码蜜桃 | yy111122少妇光屁股影院 | 91国产视频在线 | a级一级黄色片 | 一本色道久久88综合亚洲精品ⅰ | 亚洲精品乱码久久久久久蜜桃麻豆 | 亚洲日韩精品a∨片无码加勒比 | 成人学院中文字幕 | 福利免费观看 | 久久99精品国产麻豆 | 成人做爰69片免费 | 中文字字幕在线中文 | 亚洲 欧美 国产 制服 动漫 | 色婷婷av一区 | 尤物yw193无码点击进入 | 人与动物黄色大片 | 全球成人中文在线 | 日本理论片a级奶大 | 亚洲色图一区二区 | 91精品久久久久 | 国产精品爱久久久久久久 | 大人和孩做爰aⅴ18 大人和孩做爰av | 性欧美video另类hdbbw | 国产黄色片av| 我要看一级黄色毛片 | av在线亚洲男人的天堂 | 亚洲免费综合色在线视频 | 欧美精品videosbestsex日本 | 91中文字幕永久在线 | 欧美极品在线观看 | 久久乐国产精品亚洲综合 | 最近中文字幕mv | 黄网站永久免费 | 成人免费看吃奶视频网站 | 日本性高潮视频 | 七七色影院 | 涩涩久久 | 97免费观看视频 | 午夜精品久久久久久99热 | 国产毛片农村妇女系列bd | 偷看做性肉体探欲k8 | 少妇饥渴偷公乱第75章 | 男ji大巴进入女人的视频 | 美丽姑娘国语版在线播放 | 337p亚洲欧洲色噜噜噜 | y111111少妇蜜桃视频 | 国产精品黄色片 | 无码人妻丰满熟妇区五十路百度 | 无码天堂va亚洲va在线va | 91精品国产99久久久 | 91久久婷婷 | 久久综合伊人77777 | 亚洲乱码国产乱码精品精的特点 | 国产日韩欧美一区二区宅男 | 日日操影院 | 日韩免费在线观看视频 | 日批免费看 | 国产精品成人3p一区二区三区 | 97色在线观看免费视频 | 欧美乱三级 | 国产精品无码一区二区三区 | 亚洲熟女www一区二区三区 | 岛国av毛片| 天天狠天天操 | 我要看一级黄色 | 91麻豆精品国产91久久久点播时间 | 欧美在线视频观看 | 黄色1级视频| 美女张开腿黄网站免费下载 | 深夜福利免费观看 | 日韩精品一区二区三区在线播放 | 国产第4页| 久久爽久久爽久久av东京爽 | 国产在线 | 中文 | xfplay2023成人资源站 | 亚洲国产精品成人综合色在线婷婷 | 国产全肉乱妇杂乱视频 | 欧美体内she精高潮 欧美体内谢she精2性欧美 | 国产亚洲精品久久网站 | 99久久国产露脸国语对白 | 欧美精品成人影院 | 亚洲天堂男人影院 | 69xxx免费视频 | 可乐操亚洲| 青青青草国产 | 麻豆成人网| 少妇色视频 | 91av资源在线 | 一区二区三区中文字幕在线观看 | 免费观看黄色一级视频 | 欧美精品一区二区视频在线观看 | 日本一区中文字幕 | xxx18hd国语对白 | 日韩综合无码一区二区 | 亚洲国产另类久久久精品黑人 | 在线亚洲不卡 | 成人在线免费视频观看 | 午夜影院色| 亚洲天堂网在线播放 | 国产69精品久久99卡顿的解决方法 | 精品精品国产毛片在线看 | 国产主播喷水 | 九九在线观看免费高清版 |