做地推的網(wǎng)站關(guān)鍵詞排名怎么做上首頁(yè)
開(kāi)篇語(yǔ)錄:以架構(gòu)師的能力標(biāo)準(zhǔn)去分析每個(gè)問(wèn)題,過(guò)后由表及里分析問(wèn)題的本質(zhì),復(fù)盤(pán)總結(jié)經(jīng)驗(yàn),并把總結(jié)內(nèi)容記錄下來(lái)。當(dāng)你解決各種各樣的問(wèn)題,也就積累了豐富的解決問(wèn)題的經(jīng)驗(yàn),解決問(wèn)題的能力也將自然得到極大的提升。
勵(lì)志做架構(gòu)師的擼碼人,認(rèn)知很重要,可以訂閱:架構(gòu)設(shè)計(jì)專欄
????? 擼碼人平時(shí)大多數(shù)時(shí)間都在擼碼或者擼碼的路上,很少關(guān)注框架的一些底層原理,當(dāng)出現(xiàn)問(wèn)題時(shí)沒(méi)能力第一時(shí)間解決問(wèn)題,出現(xiàn)問(wèn)題后不去層層剖析問(wèn)題產(chǎn)生的原因,后續(xù)也就可能無(wú)法避免或者繞開(kāi)同類的問(wèn)題。因此不要單純做Ctrl+c和Ctrl+V,而是一邊仰望星空(目標(biāo)規(guī)劃),一邊腳踏實(shí)地去分析每個(gè)問(wèn)題。
?
一、背景
?????? 我們最近在使用mybatis執(zhí)行批量數(shù)據(jù)插入,數(shù)據(jù)插入非常慢:每批次5000條數(shù)據(jù)大概耗時(shí)在3~4分鐘左右。架構(gòu)師的職責(zé)之一是疑難技術(shù)點(diǎn)攻關(guān):要主動(dòng)積極解決系統(tǒng)出現(xiàn)的問(wèn)題,過(guò)后由表及里分析問(wèn)題的本質(zhì),復(fù)盤(pán)總結(jié)經(jīng)驗(yàn),并把總結(jié)內(nèi)容記錄下來(lái)分享給團(tuán)隊(duì),確保后續(xù)如何智慧地繞開(kāi)同類問(wèn)題。
以下是排查問(wèn)題的過(guò)程和思路:
二、定位問(wèn)題
1、全面打印日志:
日志是排查問(wèn)題的第一手資料。
logback設(shè)置mybatis相關(guān)日志打印:
<logger name="org.mybatis.spring" level="DEBUG"/> <logger name="org.apache.ibatis" level="DEBUG"/> <!-- <logger name="java.sql.PreparedStatement" level="DEBUG"/>--> <!-- <logger name="java.sql.Statement" level="DEBUG"/>--> <!-- <logger name="java.sql.Connection" level="DEBUG"/>--> <!-- <logger name="java.sql.ResultSet" level="DEBUG"/>-->
2、初步定位問(wèn)題
發(fā)現(xiàn)Creating a new SqlSession到查詢語(yǔ)句耗時(shí)特別長(zhǎng):
?????
3、初步排查問(wèn)題:
有幾個(gè)可能的原因?qū)е聞?chuàng)建新的SqlSession到查詢語(yǔ)句耗時(shí)特別長(zhǎng):
1. 數(shù)據(jù)庫(kù)連接池問(wèn)題:如果連接池中沒(méi)有空余的連接,則創(chuàng)建新的SqlSession時(shí)需要等待連接釋放。可以通過(guò)增加連接池大小或者優(yōu)化查詢語(yǔ)句等方式來(lái)緩解該問(wèn)題。
排除連接池問(wèn)題:我們連接池比較大,通過(guò)mysql show processlist查看幾乎沒(méi)有慢查詢的連接。
2. 網(wǎng)絡(luò)延遲問(wèn)題:如果數(shù)據(jù)庫(kù)服務(wù)器和應(yīng)用服務(wù)器之間的網(wǎng)絡(luò)延遲較大,則創(chuàng)建新的SqlSession時(shí)會(huì)受到影響??梢酝ㄟ^(guò)優(yōu)化網(wǎng)絡(luò)配置或者將數(shù)據(jù)庫(kù)服務(wù)器和應(yīng)用服務(wù)器放在同一臺(tái)機(jī)器上等方式來(lái)緩解該問(wèn)題。
排除網(wǎng)絡(luò)問(wèn)題:ping mysql地址,耗時(shí)都在0.5ms左右。
3. 查詢語(yǔ)句過(guò)于復(fù)雜:如果查詢語(yǔ)句過(guò)于復(fù)雜,則會(huì)導(dǎo)致查詢時(shí)間較長(zhǎng)??梢酝ㄟ^(guò)優(yōu)化查詢語(yǔ)句或者增加索引等方式來(lái)緩解該問(wèn)題。
排除復(fù)雜sql問(wèn)題:簡(jiǎn)單insert 語(yǔ)句,通過(guò)mysql show processlist查看沒(méi)有慢查詢的連接。
<insert id="batchInsertDuplicate">INSERT INTO b_table_#{day} (<include refid="selectAllColumn"/>) VALUES<foreach collection="list" item="item" index="index" separator=",">(#{item.id ,jdbcType=VARCHAR },#{item.sn ,jdbcType=VARCHAR },#{item.ip ,jdbcType=VARCHAR },.....#{item.createTime ,jdbcType=TIMESTAMP },#{item.updateTime ,jdbcType=TIMESTAMP })</foreach>ON DUPLICATE KEY UPDATE`ip`=VALUES(`ip`),`updateTime`=VALUES(`updateTime`)
</insert>
4. 數(shù)據(jù)庫(kù)服務(wù)器性能問(wèn)題:如果數(shù)據(jù)庫(kù)服務(wù)器性能較低,則創(chuàng)建新的SqlSession時(shí)會(huì)受到影響??梢酝ㄟ^(guò)優(yōu)化數(shù)據(jù)庫(kù)服務(wù)器配置或者升級(jí)硬件等方式來(lái)緩解該問(wèn)題。
排除數(shù)據(jù)庫(kù)服務(wù)器性能問(wèn)題:mysql是8核16G,通過(guò)mysql show processlist查看沒(méi)有慢查詢的連接。
5. 應(yīng)用服務(wù)器性能問(wèn)題:如果應(yīng)用服務(wù)器性能較低,則創(chuàng)建新的SqlSession時(shí)會(huì)受到影響??梢酝ㄟ^(guò)優(yōu)化應(yīng)用服務(wù)器配置或者升級(jí)硬件等方式來(lái)緩解該問(wèn)題。
暫時(shí)無(wú)法確定:top查看cpu占用比較高90%,可能原因是mybatis框架處理sql語(yǔ)句引起cpu飆高。
三、定位cpu飆高耗時(shí)的方法
1、優(yōu)化代碼:
?5000條改為500條批量插入,查看每個(gè)線程的耗時(shí)依然很高,說(shuō)明是mybatis框架處理sql語(yǔ)句耗cpu。
top -H -p? 18912
2、使用arthas定位:
curl -O https://arthas.aliyun.com/arthas-boot.jar java -jar arthas-boot.jar pid?
使用dashboard可以查看每個(gè)線程耗cpu情況,和top -H 差不多:
3、trace方法耗時(shí):
trace com.xxxx.class xxxmethod
逐步跟進(jìn)去:
最后定位方法
?org.apache.ibatis.parsing.GenericTokenParser:parse耗時(shí):
查看 org.apache.ibatis.parsing.GenericTokenParser:parse的源碼:
public String parse(String text) {StringBuilder builder = new StringBuilder();if (text != null) {String after = text;int start = text.indexOf(this.openToken);for(int end = text.indexOf(this.closeToken); start > -1; end = after.indexOf(this.closeToken)) {String before;if (end <= start) {if (end <= -1) {break;}before = after.substring(0, end);builder.append(before);builder.append(this.closeToken);after = after.substring(end + this.closeToken.length());} else {before = after.substring(0, start);String content = after.substring(start + this.openToken.length(), end);String substitution;if (start > 0 && text.charAt(start - 1) == '\\') {before = before.substring(0, before.length() - 1);substitution = this.openToken + content + this.closeToken;} else {substitution = this.handler.handleToken(content);}builder.append(before);builder.append(substitution);after = after.substring(end + this.closeToken.length());}start = after.indexOf(this.openToken);}builder.append(after);}return builder.toString();}
?主要作用是對(duì) SQL 進(jìn)行解析,對(duì)轉(zhuǎn)義字符進(jìn)行特殊處理(刪除反斜杠)并處理相關(guān)的參數(shù)(${}),如sql需要解析的標(biāo)志${name} 替換為實(shí)際的文本。我們可以使用一個(gè)例子說(shuō)明:
final Map<String,Object> mapper = new HashMap<String, Object>();
mapper.put("name", "張三");
mapper.put("pwd", "123456");//先初始化一個(gè)handler
TokenHandler handler = new TokenHandler() {@Overridepublic String handleToken(String content) {System.out.println(content);return (String) mapper.get(content);}};
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
System.out.println("************" + parser.parse("用戶:${name},你的密碼是:${pwd}"));
通過(guò)源碼發(fā)現(xiàn),如果mapper定義的字段很多,for遍歷條數(shù)比較大(下面紅色部分):
<insert id="batchInsertDuplicate">
??????? INSERT INTO b_table_#{day} (
??????? <include refid="selectAllColumn"/>
??????? ) VALUES
??????? <foreach collection="list" item="item" index="index" separator=",">
??????????? (
??????????? #{item.id ,jdbcType=VARCHAR },
??????????? #{item.sn ,jdbcType=VARCHAR },
??????????? #{item.ip ,jdbcType=VARCHAR },
??????????? .....
??????????? #{item.createTime ,jdbcType=TIMESTAMP },
??????????? #{item.updateTime ,jdbcType=TIMESTAMP }
??????????? )
??????? </foreach>
??????? ON DUPLICATE KEY UPDATE
??????? `ip`=VALUES(`ip`),
??????? `updateTime`=VALUES(`updateTime`)
</insert>
需要解析耗時(shí)較久,由于都是字符串遍歷,特別耗CPU,因此可以看到cpu飆升很高。
四、解決
解決:直接執(zhí)行原聲sql。
不使用mapper方式拼接sql,而是手動(dòng)拼接好sql,使用JdbcTemplate執(zhí)行原聲sql。