需求是什么
這里不再介紹具體的業(yè)務(wù)。簡(jiǎn)而言之,有兩個(gè)接口(查詢、確認(rèn))對(duì)前端頁(yè)面提供服務(wù)。
查詢接口返回的數(shù)據(jù)依賴于本地?cái)?shù)據(jù)與外部接口計(jì)算后的結(jié)果,也就是頁(yè)面展示的是數(shù)據(jù)快照。確認(rèn)接口是按照頁(yè)面的展示結(jié)果請(qǐng)求外部接口。
考慮到用戶打開(kāi)展示頁(yè)面時(shí)的數(shù)據(jù)與提交操作可能間隔很久,實(shí)際請(qǐng)求時(shí)結(jié)果已發(fā)生變化,而這種操作會(huì)影響業(yè)務(wù)結(jié)果。因此在提交時(shí)會(huì)進(jìn)行一次 check,如果發(fā)現(xiàn)數(shù)據(jù)發(fā)生變化需要提示頁(yè)面進(jìn)行刷新。
為了方便大家理解,我簡(jiǎn)單的畫(huà)了個(gè)圖,畢竟上面太啰嗦了。
查詢接口:

確認(rèn)接口:

雖然這個(gè)圖有點(diǎn)草率,但是相信看到這里的小伙伴(默認(rèn)都是聰明的)都對(duì)需求了然于胸了。
我怎么搞得
掰扯了半天,我們的主角 MD5 還沒(méi)有出場(chǎng),別著急,馬上就來(lái)了。
你也可以想想,這個(gè)場(chǎng)景和 MD5 是怎么扯上關(guān)系的呢?
可以看出,這里需要前端將查詢接口的返回值重新組裝作為確認(rèn)接口的入?yún)?。而后端需要再次走?shù)據(jù)聚合的邏輯與前端傳過(guò)來(lái)的業(yè)務(wù)值進(jìn)行比較,如果不匹配則提示頁(yè)面需要刷新。
一切看起來(lái)都順理成章,那么小編遇到了什么問(wèn)題呢?
簡(jiǎn)單來(lái)說(shuō)有兩點(diǎn):
- 前端同學(xué)表示值不好傳,因?yàn)檫@個(gè)頁(yè)面比較復(fù)雜,具體原因小編也沒(méi)深究,可能是被糊弄了。
- 后端同學(xué)(也就是小編)發(fā)現(xiàn),這樣查詢接口和確認(rèn)接口耦合很?chē)?yán)重,如果確認(rèn)接口需要新的入?yún)ⅲ敲淳托枰膭?dòng)查詢接口。隨著查詢接口邏輯越來(lái)越復(fù)雜,確認(rèn)接口的一個(gè)入?yún)⒕托枰粚右粚拥膫鬟^(guò)來(lái)。很不友好。
呵呵,機(jī)智的小編?kù)`機(jī)一動(dòng),便想到了了MD5,看看百度百科怎么說(shuō):
MD5 信息摘要算法(英語(yǔ):MD5 Message-Digest Algorithm),一種被廣泛使用的密碼散列函數(shù),可以產(chǎn)生出一個(gè) 128 位(16 字節(jié))的散列值(hash value),用于確保信息傳輸完整一致。
一圖勝千言:

在工程,它差不多就是這么用。
String md5= Md5Utils.get(String source);
可能有聰明的小伙伴會(huì)說(shuō)了,這是散列函數(shù)存在哈希碰撞,不同的字符串也有可能生成相同的哈希值。
是的沒(méi)錯(cuò),但是在小編的業(yè)務(wù)場(chǎng)景中,這種出現(xiàn)的概率微乎其微,忽略不計(jì),解釋權(quán)歸小編所有。
那么具體怎么做的呢,還是看圖說(shuō)話.
改造后的查詢接口:

改造后的確認(rèn)接口:

我們需要對(duì)查詢接口返回的業(yè)務(wù)集關(guān)鍵屬性進(jìn)行組合哈希,這樣可以生成數(shù)據(jù)快照值。確認(rèn)接口無(wú)需再傳入業(yè)務(wù)集合,只需要傳入數(shù)據(jù)快照值,后端進(jìn)行對(duì)比即可知道是否發(fā)生變更。
一切都是那么的美好,接下來(lái)就到了動(dòng)人心魄的編碼環(huán)節(jié)。話不多說(shuō),小編的項(xiàng)目中引入了hutool包,什么你不知道糊涂包?

真不錯(cuò),果然是效率擔(dān)當(dāng),一行代碼就搞定了。
/**
*生成數(shù)據(jù)哈希
*/
privateStringgenerateSnapShotHash(AcceptListQueryWrapResultDTOwrapResultDTO){
StringBuilderbuilder=newStringBuilder();
for(AcceptListQueryResultDTOitem:wrapResultDTO.getAllList()){
builder.append(item.getQuotationId()).append(item.getOperateType()).append(item.getPriceTypeCN());
}
returnMD5.create().digestHex16(builder.toString());
}
請(qǐng)各位看官記住這行代碼:
MD5.create().digestHex16(builder.toString());
畢竟它就是糊弄你點(diǎn)進(jìn)來(lái)的罪魁禍?zhǔn)住?/p>
出了什么事
當(dāng)小編開(kāi)發(fā)完以后,開(kāi)心的部署在了測(cè)試環(huán)境。和前端聯(lián)調(diào)的時(shí)候,發(fā)現(xiàn)第一次請(qǐng)求總是超時(shí) ???
一想可能是mock平臺(tái)的問(wèn)題,畢竟三方的查詢接口還沒(méi)開(kāi)發(fā)完成,就不以為然。請(qǐng)注意,只是第一次超時(shí)。同樣的請(qǐng)求參數(shù)第二次光速返回。呵呵,你說(shuō)不是環(huán)境的問(wèn)題,小編自己都不大信呢。
友方的接口開(kāi)發(fā)完了,小編期待的換上了對(duì)方的接口。結(jié)果現(xiàn)實(shí)給了小編一記左勾拳,還是第一次超時(shí)。這不科學(xué)?于是小編對(duì)自身產(chǎn)生了懷疑?難道不是環(huán)境的問(wèn)題?
于是連忙在本地測(cè)試了一下,居然是光速返回。作為自信的人一定不是代碼的問(wèn)題,那么這個(gè)鍋往哪里甩呢?又臭又硬的小編狠狠的思考了一分鐘,又將鍋甩給了業(yè)務(wù)網(wǎng)關(guān)(統(tǒng)一接收HTTP請(qǐng)求)肯定是它的毛病,畢竟測(cè)試環(huán)境的網(wǎng)關(guān)出問(wèn)題很常見(jiàn)。
于是開(kāi)開(kāi)心心的準(zhǔn)備上預(yù)發(fā)了。上了預(yù)發(fā)絕對(duì)沒(méi)問(wèn)題?。。⌒【幮攀牡┑┑膶?duì)QA說(shuō)道。
上帝為你關(guān)上一扇門(mén)的同時(shí)也會(huì)為你關(guān)上一扇窗,預(yù)發(fā)環(huán)境第一次還是超時(shí)?。?!小編覺(jué)得很慚愧對(duì)不起一起上線的小伙伴,畢竟大家都準(zhǔn)備十點(diǎn)下機(jī)了。
小編陷入了沉思中。。。
怎么修好的
排查了預(yù)發(fā)環(huán)境的接口,友方的杰夫接口TP99只有幾毫秒,網(wǎng)關(guān)也沒(méi)有問(wèn)題,也許是數(shù)據(jù)庫(kù)的原因,排查發(fā)現(xiàn)也沒(méi)有問(wèn)題。頓時(shí),小編又迷茫了。
山重水復(fù)疑無(wú)路柳暗花明又一村,機(jī)智的小編想到了國(guó)內(nèi)知名廠商開(kāi)源的一款java診斷工具Arthas,利用它可以查看方法詳細(xì)耗時(shí)。點(diǎn)我查看 主動(dòng)打開(kāi)另一扇窗。
當(dāng)你遇到以下類似問(wèn)題而束手無(wú)策時(shí),Arthas可以幫助你解決:
- 這個(gè)類從哪個(gè) jar 包加載的?為什么會(huì)報(bào)各種類相關(guān)的 Exception?
- 我改的代碼為什么沒(méi)有執(zhí)行到?難道是我沒(méi) commit?分支搞錯(cuò)了?
- 遇到問(wèn)題無(wú)法在線上 debug,難道只能通過(guò)加日志再重新發(fā)布嗎?
- 線上遇到某個(gè)用戶的數(shù)據(jù)處理有問(wèn)題,但線上同樣無(wú)法 debug,線下無(wú)法重現(xiàn)!
- 是否有一個(gè)全局視角來(lái)查看系統(tǒng)的運(yùn)行狀況?
- 有什么辦法可以監(jiān)控到 JVM 的實(shí)時(shí)運(yùn)行狀態(tài)?
- 怎么快速定位應(yīng)用的熱點(diǎn),生成火焰圖?
- 怎樣直接從 JVM 內(nèi)查找某個(gè)類的實(shí)例?
由于預(yù)發(fā)環(huán)境還是比較麻煩,于是小編在測(cè)試環(huán)境準(zhǔn)備好了arthas環(huán)境。
下面簡(jiǎn)單介紹下使用步驟:
- 下載全量包 arthas-bin.zip
- 解壓
- chmod -777 arthas-boot.jar
- 啟動(dòng) sudo -u admin -EH java -jar /home/export/App/arthas-boot.jar
當(dāng)看到圖標(biāo)出現(xiàn)時(shí),即啟動(dòng)成功。具體使用方法可以查看官網(wǎng),此處不再贅述。
我們使用trace命令查看方法耗時(shí),同時(shí)在頁(yè)面請(qǐng)求該查詢接口。
trace --skipJDKMethod false com.jd.universal.inquiry.service.protocol.jsf.AcceptListWebErpServiceImpl queryList

可以看到這行生成數(shù)據(jù)快照的方法,耗時(shí)占整個(gè)接口的99.57%,緊接著我們繼續(xù)監(jiān)控generateSnapShotHash方法:
trace --skipJDKMethod false com.jd.universal.inquiry.service.protocol.jsf.AcceptListWebErpServiceImpl generateSnapShotHash

可以看到方法的耗時(shí)都集中在
[99.99% 36562.318173ms ] cn.hutool.crypto.digest.MD5:create() #103
接著再次頁(yè)面點(diǎn)擊請(qǐng)求操作,出現(xiàn)以下情況:

可以看到后面多次請(qǐng)求 cn.hutool.crypto.digest.MD5:create()方法耗時(shí)僅不到一毫秒。和我們之前遇到的狀況一致。此時(shí)已確定是這行MD5導(dǎo)致的第一次加載很慢。
雖然原因找到了,但是還是得看下為什么這行代碼只有在第一次時(shí)這么慢,于是我們進(jìn)入該方法看看它到底搞什么幺蛾子。
可以看到初始化方法如下:

由于現(xiàn)象是程序第一次運(yùn)行很慢,后續(xù)很快,根據(jù)小編多年的寫(xiě)/修BUG經(jīng)驗(yàn)懷疑是這段初始化中存在靜態(tài)加載。

MessageDigest是JDK自帶的類,為應(yīng)用程序提供摘要算法的,這里我們關(guān)注點(diǎn)就落在了上面的一行。我們點(diǎn)進(jìn)去看一下:
果然我們看到了他在嘗試加載BouncyCastle庫(kù),我們來(lái)看一下這個(gè)庫(kù)的介紹:
BouncyCastle(輕量級(jí)密碼術(shù)包)是一種用于 Java 平臺(tái)的開(kāi)放源碼的輕量級(jí)密碼術(shù)包;Bouncycstle 包含了大量的密碼算法,其支持橢圓曲線密碼算法,并提供 JCE 1.2.1 的實(shí)現(xiàn)。

所以問(wèn)題的答案就呼之欲出了,隨著源碼的深入,我們看到:
privatevoidsetup()
{
loadAlgorithms(DIGEST_PACKAGE,DIGESTS);
loadAlgorithms(SYMMETRIC_PACKAGE,SYMMETRIC_GENERIC);
loadAlgorithms(SYMMETRIC_PACKAGE,SYMMETRIC_MACS);
loadAlgorithms(SYMMETRIC_PACKAGE,SYMMETRIC_CIPHERS);
loadAlgorithms(ASYMMETRIC_PACKAGE,ASYMMETRIC_GENERIC);
loadAlgorithms(ASYMMETRIC_PACKAGE,ASYMMETRIC_CIPHERS);
loadAlgorithms(KEYSTORE_PACKAGE,KEYSTORES);
loadAlgorithms(SECURE_RANDOM_PACKAGE,SECURE_RANDOMS);
loadPQCKeys();//sowecanhandlecertificatescontainingthem.
//省略。。。
}
正是由于這些算法實(shí)現(xiàn)的加載,導(dǎo)致MD5.create()第一次調(diào)用時(shí)耗時(shí)超過(guò)數(shù)十秒。
好了,既然找到了問(wèn)題。那么改動(dòng)起來(lái)就很簡(jiǎn)單了,小編嘗試尋找了糊涂包中提供的方法,發(fā)現(xiàn)并沒(méi)有入?yún)⒖梢躁P(guān)閉該三方加密包的初始化。于是換用了Google提供的MD5的實(shí)現(xiàn)。重新打包,部署,一次成功,完美。
后語(yǔ)
QA同學(xué)在測(cè)試環(huán)境測(cè)出了這個(gè)問(wèn)題,而自信的本人不屑一顧,堅(jiān)持自己愚昧的觀點(diǎn),先認(rèn)為是Mock的問(wèn)題,接著又說(shuō)是網(wǎng)關(guān)的問(wèn)題。由于我的盲目自信,導(dǎo)致上線到很晚,表示非常的慚愧??偨Y(jié)失敗的原因:

- 合理評(píng)估使用第三方包
- 測(cè)試環(huán)境遇到的問(wèn)題盡力去追,不要盲目下結(jié)論
- 要聽(tīng)QA的話
-
數(shù)據(jù)
+關(guān)注
關(guān)注
8文章
7252瀏覽量
91730 -
md5
+關(guān)注
關(guān)注
0文章
30瀏覽量
21083 -
Check
+關(guān)注
關(guān)注
0文章
4瀏覽量
7731
原文標(biāo)題:震驚,一行MD5居然讓小伙伴都回不了家!??!
文章出處:【微信號(hào):magedu-Linux,微信公眾號(hào):馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
植物小伙伴
【云智易申請(qǐng)】辦公桌上的智能小伙伴
【MiCOKit申請(qǐng)】辦公桌上的智能小伙伴
MD5計(jì)算hex文件的過(guò)程是什么
md5算法原理與實(shí)現(xiàn)

評(píng)論