成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

記一次升級Oracle驅(qū)動(dòng)引發(fā)的死鎖

Caicloud / 3190人閱讀

摘要:問題描述近期項(xiàng)目需要從虛擬機(jī)環(huán)境遷移到容器環(huán)境,其中有一個(gè)項(xiàng)目在遷移到容器環(huán)境之后的兩天之內(nèi)出現(xiàn)了次死鎖的問題,部分關(guān)鍵日志如下日志還是挺明顯的,線程獲得了鎖,等待獲取而正好相反,從而導(dǎo)致死鎖問題分析以上的錯(cuò)誤

問題描述

近期項(xiàng)目需要從虛擬機(jī)環(huán)境遷移到容器環(huán)境,其中有一個(gè)項(xiàng)目在遷移到容器環(huán)境之后的兩天之內(nèi)出現(xiàn)了2次“死鎖(deadlock)”的問題,部分關(guān)鍵日志如下:

Found one Java-level deadlock:
=============================
"DefaultMessageListenerContainer-9":
  waiting to lock monitor 0x00007fde3400bf38 (object 0x00000000dda358d0, a oracle.jdbc.driver.T4CConnection),
  which is held by "DefaultMessageListenerContainer-7"
"DefaultMessageListenerContainer-7":
  waiting to lock monitor 0x00007fdea000b478 (object 0x00000000dda35578, a oracle.jdbc.driver.T4CConnection),
  which is held by "DefaultMessageListenerContainer-9"
Java stack information for the threads listed above:
===================================================
"DefaultMessageListenerContainer-9":
    at oracle.jdbc.oracore.OracleTypeADT.linearize(OracleTypeADT.java:1280)
    - waiting to lock <0x00000000dda358d0> (a oracle.jdbc.driver.T4CConnection)
    at oracle.sql.ArrayDescriptor.toBytes(ArrayDescriptor.java:653)
    at oracle.sql.ARRAY.toBytes(ARRAY.java:711)
    - locked <0x00000000dda35578> (a oracle.jdbc.driver.T4CConnection)
    at oracle.jdbc.driver.OraclePreparedStatement.setArrayCritical(OraclePreparedStatement.java:6049)
    at oracle.jdbc.driver.OraclePreparedStatement.setARRAYInternal(OraclePreparedStatement.java:6008)
    - locked <0x00000000dda35578> (a oracle.jdbc.driver.T4CConnection)
    at oracle.jdbc.driver.OraclePreparedStatement.setArrayInternal(OraclePreparedStatement.java:5963)
    at oracle.jdbc.driver.OracleCallableStatement.setArray(OracleCallableStatement.java:4833)
    at oracle.jdbc.driver.OraclePreparedStatementWrapper.setArray(OraclePreparedStatementWrapper.java:114)
"DefaultMessageListenerContainer-7":
    at oracle.jdbc.oracore.OracleTypeADT.linearize(OracleTypeADT.java:1280)
    - waiting to lock <0x00000000dda35578> (a oracle.jdbc.driver.T4CConnection)
    at oracle.sql.ArrayDescriptor.toBytes(ArrayDescriptor.java:653)
    at oracle.sql.ARRAY.toBytes(ARRAY.java:711)
    - locked <0x00000000dda358d0> (a oracle.jdbc.driver.T4CConnection)
    at oracle.jdbc.driver.OraclePreparedStatement.setArrayCritical(OraclePreparedStatement.java:6049)
    at oracle.jdbc.driver.OraclePreparedStatement.setARRAYInternal(OraclePreparedStatement.java:6008)
    - locked <0x00000000dda358d0> (a oracle.jdbc.driver.T4CConnection)
    at oracle.jdbc.driver.OraclePreparedStatement.setArrayInternal(OraclePreparedStatement.java:5963)
    at oracle.jdbc.driver.OracleCallableStatement.setArray(OracleCallableStatement.java:4833)
    at oracle.jdbc.driver.OraclePreparedStatementWrapper.setArray(OraclePreparedStatementWrapper.java:114)
    at

日志還是挺明顯的,線程DefaultMessageListenerContainer-9獲得了鎖0x00000000dda35578,等待獲取0x00000000dda358d0;而DefaultMessageListenerContainer-7正好相反,從而導(dǎo)致死鎖;

問題分析

以上的錯(cuò)誤日志和Oracle的驅(qū)動(dòng)類有關(guān),所以猜測是驅(qū)動(dòng)版本的問題,所以找相關(guān)人員分別拉取了虛擬機(jī)環(huán)境和容器環(huán)境的生產(chǎn)Oracle驅(qū)動(dòng)jar包,結(jié)果如下:

#虛擬機(jī)
[19:38:21 oracle@tomcat-384 lib]$ ls -l ojdbc-1.4.jar
-rw-r--r-- 1 oracle oinstall 1378346 Jul  3  2014 ojdbc-1.4.jar
 
#容器
[oracle@7f666c76b7-dx2gq lib]$ ls -l ojdbc6.jar
-rw-r--r-- 1 oracle oinstall 2739670 Aug 11  2015 ojdbc6.jar

兩個(gè)環(huán)境使用了不同的版本,容器使用了高版本(11.2.0.4.0),虛擬機(jī)使用的是低版本(10.1.0.5.0);Google查詢了和Oracle驅(qū)動(dòng)相關(guān)產(chǎn)生死鎖的問題,查到了Oracle官方有如下文檔:
Java-level deadlock with 11.2
提供給我們的方案是“Upgraded the Oracle JDBC driver from 10.2 to 11.2.”,正好和我們遇到的情況相反,我們是高版本有問題,低版本沒有問題,所以需要進(jìn)一步分析;

源碼分析

首先找到相關(guān)的邏輯代碼類,此處為了更好的看出問題,使用了如下的模擬類,大致如下:

//測試Dao,配置在spring下的單例
public class TestDaoImpl {

    //共享的兩個(gè)ArrayDescriptor
    private ArrayDescriptor param1Desc;
    private ArrayDescriptor param2Desc;

    private String param1;
    private String param2;
    private DataSource dataSource;

    public void callProc(Object param) {
        // 準(zhǔn)備的兩個(gè)ARRAY參數(shù)
        ARRAY param1Array = null;
        ARRAY param2Array = null;

        CallableStatement callable = null;
        Connection conn = null;

        try {
            // 從連接池獲取連接
            conn = DataSourceUtils.getConnection(dataSource);
            param1Array = wrapProcParameter1(param, conn);
            param2Array = wrapProcParameter2(param, conn);

            callable = conn.prepareCall("{ call testProc " + "(?,?,?)}");
            callable.setArray(1, param1Array);
            callable.setArray(2, param2Array);

            callable.execute();

        } catch (Exception e) {
            // 異常處理
        } finally {
            // 關(guān)閉處理
        }
    }

    private ARRAY wrapProcParameter1(Object param, Connection conn) throws SQLException {
        if (null == this.param1Desc) {
            this.param1Desc = new ArrayDescriptor(this.param1, conn);
        }
        //省略
        ARRAY array1 = new ARRAY(this.param1Desc, conn, param);
        return array1;
    }

    private ARRAY wrapProcParameter2(Object param, Connection conn) throws SQLException {
        if (null == this.param2Desc) {
            this.param2Desc = new ArrayDescriptor(this.param2, conn);
        }
        //省略
        ARRAY array2 = new ARRAY(this.param2Desc, conn, param);
        return array2;
    }
}

大致的邏輯是通過從連接池獲取的Connection創(chuàng)建了一個(gè)存儲過程,然后給存儲過程設(shè)置了兩個(gè)ARRAY參數(shù),在創(chuàng)建ARRAY時(shí)需要指定相應(yīng)的ArrayDescriptor,最后執(zhí)行存儲過程;
產(chǎn)生異常分別在兩次setArray的地方,線程1在setArray1的地方,線程2在setArray2的地方,所有以此為入口分別查看兩個(gè)驅(qū)動(dòng)版本相關(guān)類:OraclePreparedStatement,ARRAY,ArrayDescriptor以及OracleTypeADT;

驅(qū)動(dòng)11.2.0.4.0版本

首先查看OraclePreparedStatement中調(diào)用的setArray,最終會(huì)調(diào)用如下方法:

在方法setARRAYInternal中使用了connection作為了對象鎖,接下來OraclePreparedStatement會(huì)調(diào)用ARRAY,然后ARRAY調(diào)用ArrayDescriptor,最后ArrayDescriptor在調(diào)用OracleTypeADT,為了方便看出問題直接展示OracleTypeADT中使用鎖的地方:

同樣使用connection做為鎖對象,這樣就存在同時(shí)需要獲取兩把鎖了,而上面兩把鎖都是connection對象,應(yīng)該不會(huì)出現(xiàn)死鎖,但是深入發(fā)現(xiàn)其實(shí)OracleTypeADT中的connection對象是從ArrayDescriptor中獲取的,而ArrayDescriptor是一個(gè)共享的類變量,這樣在多線程環(huán)境下就會(huì)出現(xiàn)被賦值不同的connection,從而導(dǎo)致出現(xiàn)死鎖的問題;
大致流程如下:
1.首先線程1獲取conn1,然后線程2獲取conn2;
2.然后線程1創(chuàng)建Array1,同時(shí)對共享的ArrayDescriptor1設(shè)置connection=conn1;
3.線程1掛起,線程2創(chuàng)建Array1,同時(shí)對共享的ArrayDescriptor1設(shè)置connection=conn2,對共享的ArrayDescriptor2設(shè)置connection=conn2;
4.線程2繼續(xù)占用cpu,執(zhí)行setArray1,這時(shí)候都是Array1和ArrayDescriptor1中的鎖都是conn2,所以沒有問題,繼續(xù)執(zhí)行setArray2,在執(zhí)行完獲取第一把鎖conn2之后,線程2掛起;
5.線程1搶占cpu,對共享的ArrayDescriptor2設(shè)置connection=conn1,然后執(zhí)行setArray1;但此時(shí)Array1中的connection是conn1,而ArrayDescriptor1中的connection是conn2,所以出現(xiàn)線程1占用了conn1,等待conn2鎖;
6.此時(shí)線程2再次搶到cpu,但是在獲取第二把鎖時(shí),此時(shí)ArrayDescriptor2中的connection已經(jīng)被設(shè)置成了conn1,而conn1已經(jīng)被線程1占有,所以等待獲取conn1;
7.死鎖出現(xiàn)了線程1占有了conn1鎖,等待conn2鎖;線程2占有了conn2鎖,等待conn1鎖;從而導(dǎo)致死鎖發(fā)生;
從上面的分析可以看出主要原因是ArrayDescriptor被設(shè)置成了類變量,被多個(gè)線程所訪問,解決死鎖問題可以把ArrayDescriptor改成局部變量;但是如果僅是業(yè)務(wù)造成的問題,那應(yīng)該在驅(qū)動(dòng)ojdbc-1.4中存在同樣的死鎖問題,但是此項(xiàng)目在虛擬機(jī)環(huán)境中一直沒有出現(xiàn)過問題;繼續(xù)看ojdbc-1.4源碼;

驅(qū)動(dòng)10.1.0.5.0版本

同樣分析此驅(qū)動(dòng)版本中的相同類,同上首先查看OraclePreparedStatement中調(diào)用的setArray,最終會(huì)調(diào)用如下方法:

同樣使用了connection作為對象鎖,再看OracleTypeADT,相關(guān)代碼如下:

可以看到這里并沒有使用connection作為鎖,而是使用了內(nèi)置鎖,所以就不會(huì)出現(xiàn)死鎖問題;

問題總結(jié)

首先就是在遷移環(huán)境時(shí)一定要保證相關(guān)的依賴公共jar保證版本的一致,就算是低版本,高版本也不一樣保證向下兼容;其次也是最重要的寫業(yè)務(wù)邏輯時(shí)遇到公共變量時(shí)一定要謹(jǐn)慎,是否會(huì)出現(xiàn)多線程問題;

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/74280.html

相關(guān)文章

  • 一次 .NET 某電商無貨源后端服務(wù) 死鎖分析

    摘要:的情況下,必然就會(huì)死鎖,對吧,接下來怎么用驗(yàn)證呢切到號線程查看線程棧及棧對象。死鎖原因分析死鎖原因分析要想追究死鎖的原因,只能仔細(xì)推敲線程棧線程棧對象。在幾個(gè)痙攣過程中進(jìn)入了另外一個(gè)線程池的方法中,希望能得到該池中的鎖對象。一:背景1. 講故事這個(gè)月初,星球里的一位朋友找到我,說他的程序出現(xiàn)了死鎖,懷疑是自己的某些寫法導(dǎo)致mongodb出現(xiàn)了如此尷尬的情況,截圖如下:說實(shí)話,看過這么多dum...

    yimo 評論0 收藏0
  • 一次錯(cuò)誤卸載軟件包導(dǎo)致Linux系統(tǒng)崩潰修復(fù)解決過程

    摘要:于是檢查時(shí)發(fā)現(xiàn),拼寫錯(cuò)誤,應(yīng)為。第個(gè)問題,是真真切切錯(cuò)誤卸載重要軟件包,導(dǎo)致系統(tǒng)崩潰,修復(fù)系統(tǒng)的方法自然也就是利用原鏡像在下把該裝的都裝回去,前提是日志存在,萬幸沒有執(zhí)行過。 首先問題產(chǎn)生的緣由很簡單,是我一同事在安裝oracle一套軟件時(shí),按照要求需要binutils軟件包的32位版本,然而在Oracle Linux已經(jīng)裝有64位,按理說是可以安裝i686的,我猜應(yīng)該是32位的版本低...

    dreamGong 評論0 收藏0

發(fā)表評論

0條評論

最新活動(dòng)
閱讀需要支付1元查看
<