主頁 > 後端開發 > Mybatis的parameterType造成執行緒阻塞問題分析

Mybatis的parameterType造成執行緒阻塞問題分析

2023-06-09 07:48:41 後端開發

一、前言

最近在新發布某個專案上線時,每次重啟都會收到機器的 CPU 使用率告警,查看對應監控,持續時長達 5 分鐘,對于服務重啟有很大風險,而該專案有非常多 Consumer 消費,服務啟動后會有大量執行緒去拉取訊息處理邏輯,通過多次 Jstack 輸出執行緒快照發現有很多 BLOCKED 狀態執行緒,此文主要記錄分析 BLOCKED 原因,

二、分析程序

2.1、初步分析

"consumer_order_status_jmq1714_1684822992337" #3125 daemon prio=5 os_prio=0 tid=0x00007fd9eca34000 nid=0x1ca4f waiting for monitor entry [0x00007fd1f33b5000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at java.util.concurrent.ConcurrentHashMap.putVal(ConcurrentHashMap.java:1027)
    - waiting to lock <0x000000056e822bc8> (a java.util.concurrent.ConcurrentHashMap$Node)
    at java.util.concurrent.ConcurrentHashMap.put(ConcurrentHashMap.java:1006)
    at org.apache.ibatis.type.TypeHandlerRegistry.getJdbcHandlerMap(TypeHandlerRegistry.java:234)
    at org.apache.ibatis.type.TypeHandlerRegistry.getTypeHandler(TypeHandlerRegistry.java:200)
    at org.apache.ibatis.type.TypeHandlerRegistry.getTypeHandler(TypeHandlerRegistry.java:191)
    at org.apache.ibatis.mapping.ParameterMapping$Builder.resolveTypeHandler(ParameterMapping.java:128)
    at org.apache.ibatis.mapping.ParameterMapping$Builder.build(ParameterMapping.java:103)
    at org.apache.ibatis.builder.SqlSourceBuilder$ParameterMappingTokenHandler.buildParameterMapping(SqlSourceBuilder.java:123)
    at org.apache.ibatis.builder.SqlSourceBuilder$ParameterMappingTokenHandler.handleToken(SqlSourceBuilder.java:67)
    at org.apache.ibatis.parsing.GenericTokenParser.parse(GenericTokenParser.java:78)
    at org.apache.ibatis.builder.SqlSourceBuilder.parse(SqlSourceBuilder.java:45)
    at org.apache.ibatis.scripting.xmltags.DynamicSqlSource.getBoundSql(DynamicSqlSource.java:44)
    at org.apache.ibatis.mapping.MappedStatement.getBoundSql(MappedStatement.java:292)
    at com.github.pagehelper.PageInterceptor.intercept(PageInterceptor.java:83)
	at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61)
	at com.sun.proxy.$Proxy232.query(Unknown Source)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:148)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:141)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:77)
	at sun.reflect.GeneratedMethodAccessor160.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:433)
	at com.sun.proxy.$Proxy124.selectOne(Unknown Source)
	at org.mybatis.spring.SqlSessionTemplate.selectOne(SqlSessionTemplate.java:166)
	at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:82)
	at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:59)
        ......

通過對服務連續間隔 1 分鐘使用 Jstack 抓取執行緒快照,發現存在部分執行緒是 BLOCKED 狀態,通過堆疊可以看出,當前執行緒阻塞在 ConcurrentHashMap.putVal,而 putVal 方法內部使用了 synchronized 導致當前執行緒被 BLOCKED,而上一級是 Mybaits 的TypeHandlerRegistry,TypeHandlerRegistry 的作用是記錄 Java 型別與 JDBC 型別的相互映射關系,例如 java.lang.String 可以映射 JdbcType.CHAR、JdbcType.VARCHAR 等,更上一級是 Mybaits 的 ParameterMapping,而 ParameterMapping 的作用是記錄請求引數的資訊,包括 Java 型別、JDBC 型別,以及兩種型別轉換的操作類 TypeHandler,通過以上資訊可以初步定位為在并發情況下 Mybaits 決議某些引數導致大量執行緒被阻塞,還需繼續往下分析,

我們可以先回想下 Mybatis 啟動加載時的大致流程,查看下流程中哪些地方會操作 TypeHandler,會使用 ConcurrentHashMap.putVal 進行快取操作?

在 Mybatis 啟動流程中,大致分為以下幾步:

1、XMLConfigBuilder#parseConfiguration() 讀取本地XML檔案

2、XMLMapperBuilder#configurationElement() 決議XML檔案中的 select|insert|update|delete 標簽

3、XMLMapperBuilder#parseStatementNode() 開始決議單條 SQL,包括請求引數、回傳引數、替換占位符等

4、SqlSourceBuilder 組合單條 SQL 的基本資訊

5、SqlSourceBuilder#buildParameterMapping() 決議請求引數

6、ParameterMapping#getJdbcHandlerMap() 決議 Java 與 JDBC 型別,并把映射結果放入快取

而在第 6 步時候(圖中標色),會去獲取 Java 物件型別與 JDBC 型別的映射關系,并把已經處理過的映射關系 TypeHandler 存入本地快取中,但是堆疊資訊顯示,還是觸發了 TypeHandler 入快取的操作,也就是某個 paramType 并沒有命中快取,而是在 SQL 查詢的時候實時決議 paramType,在高并發情況下造成了執行緒阻塞情況,下面繼續分析下 sql xml 的配置:

<select id="listxxxByMap" parameterType="java.util.Map" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List"/>
        from xxxxx
        where business_id = #{businessId,jdbcType=VARCHAR}
        and template_id = #{templateId,jdbcType=INTEGER}
    </select>

代碼請求:

Map<String, Object> params = new HashMap<>();
params.put("businessId", "11111");
params.put("templateId", "11111");
List<TrackingInfo> result = trackingInfoMapper.listxxxByMap(params);

初步看沒發現問題,但是我們在入 TypeHandler 快取時 debug 下,分析下哪種型別在快取中缺失?

從 debug 資訊中可以看出,TypeHandler 快取中存在的是 interface java.util.Map,而 SQL 執行時傳入的是 class java.util.HashMap,導致并沒有命中快取,那我們修改下 xml 檔案為 parameterType="java.util.HashMap" 是不是就解決了?

很遺憾,部署后仍然存在問題,

2.2、進一步分析

為了進一步分析,引入了對照組,而對照組的 paramType 為具體 JavaBean,

<select id="listResultMap" parameterType="com.jdwl.xxx.domain.TrackingInfo" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List"/>
        from xxxx
        where business_id = #{businessId,jdbcType=VARCHAR}
        and template_id = #{templateId,jdbcType=INTEGER}
    </select>

對照組代碼請求

TrackingInfo record = new TrackingInfo();
record.setBusinessId("11111");
record.setTemplateId(11111);
List<TrackingInfo> result = trackingInfoMapper.listResultMap(record);

在裝載引數的 Handler 類 org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters 處進行 debug 分析,

2.2.1、對照組為 listResultMap(paramType=JavaBean)

兩個引數的決議型別分別為 StringTypeHandler(紅框中灰色的字)與 IntegerTypeHandler(紅框中灰色的字),已經是 Mybatis 提供的 TypeHandler,并沒有再進行型別的二次決議,說明 JavaBean 中的 businessId、templateId 欄位已經在啟動時候被預決議了,

2.2.2、實驗組為listxxxByMap(paramType=Map)

兩個引數的決議都是 UnknownTypeHandler(紅框中灰色的字),而在 UnknownTypeHandler 中會再次呼叫 resolveTypeHandler() 方法,對引數進行型別的二次決議,可以理解為 Map 里的屬性不是固定型別,只能在執行 SQL 時候再決議一次,

最后修改為 paramType=JavaBean 部署測驗環境再抓包,并未發現 TypeHandlerRegistry 相關的執行緒阻塞,

三、引申思考

既然 paramType 傳值會出現阻塞問題,那 resultType 與 resultMap 是不是有相同問題呢?繼續分為兩個實驗組:

1、對照組(resultMap=BaseResultMap)

<resultMap id="BaseResultMap" type="com.jdwl.tracking.domain.TrackingInfo">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="template_id" property="templateId" jdbcType="INTEGER"/>
        <result column="business_id" property="businessId" jdbcType="VARCHAR"/>
        <result column="is_delete" property="isDelete" jdbcType="TINYINT"/>
        <result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
        <result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
        <result column="ts" property="ts" jdbcType="TIMESTAMP"/>
    </resultMap>

<select id="listResultMap" parameterType="com.jdwl.tracking.domain.TrackingInfo" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List"/>
        from tracking_info
        where business_id = #{businessId,jdbcType=VARCHAR}
        and template_id = #{templateId,jdbcType=INTEGER}
    </select>

對照組代碼請求:

TrackingInfo record = new TrackingInfo();
record.setBusinessId("11111");
record.setTemplateId(11111);
List<TrackingInfo> result1 = trackingInfoMapper.listResultMap(record);

2、實驗組(resultType=JavaBean)

<select id="listResultType" parameterType="com.jdwl.tracking.domain.TrackingInfo" resultType="com.jdwl.tracking.domain.TrackingInfo">
        select
        <include refid="Base_Column_List"/>
        from tracking_info
        where business_id = #{businessId,jdbcType=VARCHAR}
        and template_id = #{templateId,jdbcType=INTEGER}
    </select>

實驗組代碼請求:

TrackingInfo record = new TrackingInfo();
record.setBusinessId("11111");
record.setTemplateId(11111);
List<TrackingInfo> result2 = trackingInfoMapper.listResultType(record);

在對回傳結果 Handler 處理類 org.apache.ibatis.executor.resultset.DefaultResultSetHandler#createAutomaticMappings() 進行 debug 分析,

1、對照組(resultMap=BaseResultMap)

List unmappedColumnNames 長度為 0,表示所有欄位都命中了 標簽配置,符合預期,

2、實驗組(resultType=JavaBean)

List unmappedColumnNames 長度為 11,表示所有欄位都在 標簽配置中未找到,這是因為 SQL 執行后的 resultMap 對應的 id 并不等于標簽的 id,所以這些欄位被標識為未決議,又會執行 TypeHandlerRegistry 的型別映射邏輯,引發并發時執行緒阻塞問題,

四、總結

1、在使用 paramType 時,xml 配置的型別需要與 Java 代碼中傳入的一致,使用 Mybatis 預加載時的型別快取,

2、在使用 paramType 時,避免使用 java.util.HashMap 型別,避免 SQL 執行時決議 TypeHandler,

3、在接受回傳值時,使用 resultMap,提前映射回傳值,減少 TypeHandler 決議,

五、后續

在 Mybatis 社區已經優化了 TypeHandler 入快取的邏輯,可以解決重復計算 TypeHandler 問題,一定程度上緩解以上問題,但是 Mybatis 修復最低版本為 3.5.8,依賴 spring5.x,而我們專案使用的 Mybatis3.4.4,spring4.x,直接升級會存在一定風險,所以在不升級情況下,按照總結規范使用也可以降低阻塞風險,

TypeHandler 相關issue:https://github.com/mybatis/mybatis-3/pull/2300/commits/8690d60cad1f397102859104fee1f6e6056a0593

作者:京東物流 鐘凱

來源:京東云開發者社區

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/554670.html

標籤:其他

上一篇:【python基礎】回圈陳述句-continue關鍵字

下一篇:返回列表

標籤雲
其他(160612) Python(38218) JavaScript(25485) Java(18210) C(15237) 區塊鏈(8270) C#(7972) AI(7469) 爪哇(7425) MySQL(7238) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5873) 数组(5741) R(5409) Linux(5347) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4588) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2435) ASP.NET(2404) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1984) 功能(1967) HtmlCss(1956) Web開發(1951) C++(1933) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1880) .NETCore(1863) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Mybatis的parameterType造成執行緒阻塞問題分析

    最近在新發布某個專案上線時,每次重啟都會收到機器的 CPU 使用率告警,查看對應監控,持續時長達 5 分鐘,對于服務重啟有很大風險。而該專案有非常多 Consumer 消費,服務啟動后會有大量執行緒去拉取訊息處理邏輯,通過多次 Jstack 輸出執行緒快照發現有很多 BLOCKED 狀態執行緒,此文主要記... ......

    uj5u.com 2023-06-09 07:48:41 more
  • 【python基礎】回圈陳述句-continue關鍵字

    # 1.continue關鍵字 continue關鍵字的作用是:用來告訴 Python 跳過當前回圈代碼塊中的剩余陳述句,然后繼續進行下一輪回圈。 其在while回圈和for回圈中的作用示意圖如下 ![image](https://img2023.cnblogs.com/blog/3179433/20 ......

    uj5u.com 2023-06-09 07:43:23 more
  • static

    | static基本知識 | header | | | | | | | 類名.靜態成員變數(推薦) 同一個類中靜態成員變數的訪問可以省略類名。 1.靜態成員變數(有static修飾,屬于類、加載一次,可以被共享訪問),訪問格式 類名.靜態成員變數(推薦) 物件.靜態成員變數(不推薦)。 2.實體成員 ......

    uj5u.com 2023-06-09 07:21:44 more
  • java~如何使用無符號整型

    在 Java 中,沒有直接支持無符號整數的資料型別。Java 的基本資料型別(如 int、long、short、byte)都是帶符號的,即它們可以表示正數和負數。 > .net中每種整型都有對應的無符號型別,它不會把取值范圍分成正負兩個區間,只在正整數范圍內取值 然而,你可以使用 Java 中的較大 ......

    uj5u.com 2023-06-08 11:55:03 more
  • 【技識訓累】Python中的NumPy庫【一】

    博客推行版本更新,成果積累制度,已經寫過的博客還會再次更新,不斷地琢磨,高質量高數量都是要追求的,工匠精神是學習必不可少的精神。因此,大家有何建議歡迎在評論區踴躍發言,你們的支持是我最大的動力,你們敢投,我就敢肝 ......

    uj5u.com 2023-06-08 11:54:25 more
  • 【python基礎】回圈陳述句-break關鍵字

    # 1.break關鍵字 break關鍵字,其作用是在回圈中的代碼塊遇到此關鍵字,立刻跳出整個回圈,執行回圈外的下一條陳述句。 其在while和for回圈中的作用示意圖如下: ![image](https://img2023.cnblogs.com/blog/3179433/202306/317943 ......

    uj5u.com 2023-06-08 11:26:29 more
  • 【技識訓累】Python中的NumPy庫【一】

    博客推行版本更新,成果積累制度,已經寫過的博客還會再次更新,不斷地琢磨,高質量高數量都是要追求的,工匠精神是學習必不可少的精神。因此,大家有何建議歡迎在評論區踴躍發言,你們的支持是我最大的動力,你們敢投,我就敢肝 ......

    uj5u.com 2023-06-08 11:21:06 more
  • 【python基礎】回圈陳述句-for回圈

    # 1.初始for回圈 for回圈可以遍歷任何可迭代物件,如一個串列或者一個字串。這里可迭代物件的概念我們后期介紹,先知道這個名詞就好了。 其語法格式之一: ![image](https://img2023.cnblogs.com/blog/3179433/202306/3179433-20230 ......

    uj5u.com 2023-06-08 11:07:05 more
  • 48基于java的學生課程成績系統設計與實作

    基于java的學生課程成績管理系統設計與實作,可適用于學生學生課程管理系統,學生成績管理系統,教務課程管理系統,教務系統,成績系統,課程系統,校園管理系統,校園課程管理系統,大學校園課程管理系統等等。 ......

    uj5u.com 2023-06-08 10:46:10 more
  • 【python爬蟲實戰】用python爬取愛奇藝電視劇十大榜單的全部資料

    [toc] # 一、爬取目標 本次爬取的目標是,愛奇藝電視劇類目下的10個榜單:[電視劇風云榜-愛奇藝風云榜](https://www.iqiyi.com/ranks1/2/0) ?![愛奇藝頁面](https://img2023.cnblogs.com/blog/2864563/202306/28 ......

    uj5u.com 2023-06-08 08:09:24 more