源碼面前,了無(wú)秘密
著名IT作家、譯者侯捷老師以前在其著作中有句話(huà),就是我們今天文章的標(biāo)題「源碼面前,了無(wú)秘密」??梢哉f(shuō)相當(dāng)精煉。高度概括了從源碼中我們可以收獲的內(nèi)容。在源碼中,無(wú)論是應(yīng)用的調(diào)用邏輯,關(guān)系,各種設(shè)計(jì)都一目了然。
為什么會(huì)突然想到這樣一個(gè)題目呢?
是因?yàn)樽罱粋€(gè)項(xiàng)目上線,其中有幾個(gè)功能模塊使用了 Redis 進(jìn)行數(shù)據(jù)的緩存,包括權(quán)限信息也在其中。所以每個(gè)請(qǐng)求都會(huì)通過(guò) Redis 進(jìn)行鑒權(quán)。這塊功能是我指導(dǎo)的一個(gè)小同學(xué)負(fù)責(zé)開(kāi)發(fā)的。上線幾天后出現(xiàn)了這樣的問(wèn)題:
問(wèn)題現(xiàn)象是線上出現(xiàn)了大量的錯(cuò)誤
- Cannot get Jedis connection; nested exception
- is redis.clients.jedis.exceptions.JedisException:
- Could not get a resource from the pool
項(xiàng)目約等于小流量,上線時(shí) Redis 的***連接數(shù)開(kāi)到了1000,用戶(hù)數(shù)遠(yuǎn)小于***連接數(shù),Redis 使用的是公司的集群,穩(wěn)定性也有保障,理論上是不該出現(xiàn)這種取不到資源的情況的。
小同學(xué)自行 Google了好久沒(méi)找到問(wèn)題具體原因。我提供的幾個(gè)思路也沒(méi)本質(zhì)解決問(wèn)題。不過(guò)好在調(diào)整配置可以在線下穩(wěn)定復(fù)現(xiàn)。所以我在小同學(xué)的電腦上開(kāi)始debug,跟源碼。
和結(jié)對(duì)編程類(lèi)似,我一邊Debug,我一邊給小同學(xué)講 Redis 這個(gè)連接池的實(shí)現(xiàn)思路,目前懷疑的問(wèn)題等等。小同學(xué)說(shuō)自己剛才也在試著跟源碼,但跟了兩層之后,感覺(jué)調(diào)用越來(lái)越深,有點(diǎn)暈,就放棄了。
然后我繼續(xù)Debug和講原理,跟到資源使用完畢,在 finally 進(jìn)行釋放回收的地方,發(fā)現(xiàn)有處代碼在判斷當(dāng)前的 dataSource是否為空,從而執(zhí)行兩種不同的操作。 如果不為空,則會(huì)將資源放回pool中,便于下次繼續(xù)使用,為空則真正的進(jìn)行close的操作,直接將Socket關(guān)閉了。而這兩個(gè)if/else的邏輯是封裝在一個(gè)方法中,不跟進(jìn)來(lái)不會(huì)發(fā)現(xiàn)區(qū)別對(duì)待。
而且我們當(dāng)前在用連接池,預(yù)期是按連接池的思路,使用完畢的需要釋放回池中繼續(xù)下次的使用,當(dāng)前這個(gè)現(xiàn)象就比較怪異了。
繼續(xù)向前,發(fā)現(xiàn)整體雖然Connection 使用的是JedisConnection,但在我們出問(wèn)題的這里,返回的是個(gè)其子類(lèi)JedisWrapper,這個(gè)是部門(mén)同學(xué)開(kāi)發(fā)的一個(gè)SpringBoot 的 starter,重寫(xiě)了一部分邏輯,將連接對(duì)象作為原始對(duì)象持有,我們并沒(méi)有用到其中的特性。上面判斷dataSource是否為空本來(lái)是要判斷JedisConnection里該屬性是否為空,在Wrapper之后,是保存了origin的對(duì)象,返回的是個(gè)new Wrapper,這樣dataSource并沒(méi)有初始化,就出現(xiàn)了前面dataSource為空的問(wèn)題。
跟到這里,發(fā)現(xiàn)是maven 的依賴(lài)不同,切換回標(biāo)準(zhǔn)的 SpringBoot starter,問(wèn)題解決了。
小同學(xué)感嘆道,「這不是造孽么,如果不是你幫我,不知道從哪下手分析了?!?/p>
我則鼓勵(lì)小同學(xué)沒(méi)有思路的時(shí)候,可以從源碼里找找,跟代碼的過(guò)程,也是學(xué)習(xí)的過(guò)程,比如這次就可以在跟的時(shí)候了解連接池具體是怎樣實(shí)現(xiàn)的。任何問(wèn)題在源碼中都無(wú)處遁形,況且這些源碼也可能有Bug呢。
回過(guò)頭來(lái),我們不難發(fā)現(xiàn),可能不是所有的問(wèn)題都需要跟源碼,但閱讀源碼,帶著問(wèn)題 Debug,本身就像你放大招一樣,雖然費(fèi)時(shí)費(fèi)力,但收獲也會(huì)很多,一招制敵。這也是開(kāi)源帶來(lái)的好處,可能通過(guò)源碼來(lái)分析作者的思路,在問(wèn)題產(chǎn)生時(shí)可以逐行分析,了解細(xì)枝末節(jié),這些都是C/C++里dll文件所不具備的優(yōu)勢(shì)。:-)
對(duì)于新同學(xué),可能對(duì)于源碼閱讀有畏難情緒,感覺(jué)這一層層的嵌套,調(diào)用有點(diǎn)找不著頭緒。但這些都是表象,你試著跟幾次就輕車(chē)熟路了。
老司機(jī)為什么稱(chēng)為老司機(jī),一是車(chē)技好,二是熟悉車(chē)況。這些也都是練出來(lái)的。
所以無(wú)論是做為解決問(wèn)題的***手段,還是學(xué)習(xí)設(shè)計(jì)與思想的寶庫(kù),源碼都是不二之選,有時(shí)間的時(shí)候,可以多看看,不久之后,你也會(huì)是老司機(jī)。
***,總結(jié)下一般出現(xiàn)問(wèn)題時(shí),跟源碼的思路
- 如果有異常,可以利用IDE的異常斷點(diǎn),這樣異常產(chǎn)生時(shí),一個(gè)鮮活的異常棧就獲取到了,完整的調(diào)用鏈就在你眼前。
- 逐級(jí)深入,先分塊找到懷疑的目標(biāo)方法,結(jié)合前一步,跟進(jìn)
- 具體方法時(shí),不要根據(jù)表現(xiàn)的方法名,參數(shù)來(lái)猜方法的意圖,必要時(shí)進(jìn)去看一眼,萬(wàn)一有隱藏邏輯呢
- 同樣的類(lèi),看看包名、類(lèi)名,是否是加載的類(lèi)不對(duì)導(dǎo)致
【本文為51CTO專(zhuān)欄作者“侯樹(shù)成”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)通過(guò)作者微信公眾號(hào)『Tomcat那些事兒』獲取授權(quán)】