這一篇對(duì)項(xiàng)目的優(yōu)化實(shí)戰(zhàn),能讓你面試加分30%!

但如果你想了解Java大數(shù)據(jù)平臺(tái)開發(fā)、項(xiàng)目系統(tǒng)的優(yōu)化實(shí)戰(zhàn)。請(qǐng)繼續(xù)向下閱讀。

項(xiàng)目背景

該項(xiàng)目是銀行自用項(xiàng)目,是多租戶的數(shù)據(jù)查詢平臺(tái)??赡芎芏嗳藢?duì)這個(gè)概念不是很清楚,別急,容我做個(gè)簡單的介紹,就明白這個(gè)系統(tǒng)是干嘛的了。

項(xiàng)目簡介

首先,整個(gè)系統(tǒng)是基于Dubbo的分布式系統(tǒng)架構(gòu),數(shù)據(jù)存儲(chǔ)統(tǒng)一存儲(chǔ)在數(shù)據(jù)倉庫。數(shù)據(jù)倉庫提供多種存儲(chǔ)方式,包括MySQL、HDFS、HBSE、Hive、Impala、Spark、ElasticSearch等等。而如果讓業(yè)務(wù)方去做數(shù)據(jù)存取操作,顯然是非常麻煩的。所以在業(yè)務(wù)系統(tǒng)與數(shù)據(jù)倉庫之間再搭建了一個(gè)數(shù)據(jù)查詢系統(tǒng)——這就是本篇文章的主角。

系統(tǒng)架構(gòu)

image

項(xiàng)目源碼

這里會(huì)給大家展示項(xiàng)目的部分源碼,當(dāng)然,所展示的源碼都是功能性的而非項(xiàng)目業(yè)務(wù)相關(guān)(即任何項(xiàng)目都可以有這些代碼),大家可以先找找茬。


image
image
image
image

通過4張圖,大家應(yīng)該對(duì)該系統(tǒng)之前的編碼水平有了大致的了解。下面我將一一解鎖每張?jiān)创a圖的故事。

源碼1:

源碼1是我在做功能調(diào)試的時(shí)候發(fā)現(xiàn)的一個(gè)BUG,邏輯非常簡單,就是比對(duì)兩個(gè)id是否相等。但為什么這就產(chǎn)生BUG了呢?
很簡單,就是包裝類的緩存!
Integer和Long類型會(huì)有1個(gè)byte的緩存,即 -128 ~ 127,當(dāng)比較數(shù)的返回在此之間時(shí),因?yàn)槎际鞘褂玫木彺妗r?yàn)證代碼如下:

package demo;

public class IntegerCacheDemo {

    public static void main(String[] args) {
        compare(1,1);
        compare(127,127);
        compare(128,128);

        compareWithEquals(1,1);
        compareWithEquals(127,127);
        compareWithEquals(128,128);
    }

    /**
     * 錯(cuò)誤的包裝類比較
     * @param a
     * @param b
     */
    public static void compare(Integer a, Integer b){
        System.out.println(a == b ? a + " == " + b:a + " != " + b);
    }
    /**
     * 正確的的包裝類比較
     * @param a
     * @param b
     */
    public static void compareWithEquals(Integer a, Integer b){
        System.out.println(a.equals(b) ? a + " == " + b:a + " != " + b);
    }
}

測試結(jié)果:


image

測試的結(jié)果印證了前面的說法。

眾所周知, == 比較是直接比較的地址,而由于緩存的原因,包裝類緩存所指向的都是同一個(gè)對(duì)象,所有 == 判斷返回true,而當(dāng)超出了緩存的返回,包裝類的對(duì)象都是新創(chuàng)建的地址,使用 == 判斷會(huì)返回false,而equals判斷使用的是重寫的equals方法,Integer的equals方法如下:

    public boolean equals(Object obj) {
        //判斷類型是否相同
        if (obj instanceof Integer) {
            //如果相同則判斷值是否相同,this.value存儲(chǔ)的是int類型值, == 與   
            //Integer比較,會(huì)觸發(fā)自動(dòng)拆箱, 即等價(jià)于  int == int 判斷
            return this.value == (Integer)obj;
        } else {
            return false;
        }
    }

再來看IntegerCache的源碼吧。


image

注意看全紅部分,相信大家都明白了吧。 其他的包裝類如Long、Short、Byte等都有對(duì)應(yīng)的緩存,而且都是一個(gè)byte的取值范圍。

源碼2:

請(qǐng)注意,源碼2是在上線第二天就引起了線上事故。
業(yè)務(wù)描述

業(yè)務(wù)方通過查詢接口調(diào)用查詢平臺(tái),查詢平臺(tái)通過Zookepper訪問到Hbase獲取數(shù)據(jù)并返回。

問題排查

通過錯(cuò)誤日志,可以查到當(dāng)時(shí)有很多請(qǐng)求查詢失敗,并且偶爾會(huì)有一個(gè)查詢成功,且失敗數(shù)量是成線性增長的趨勢。當(dāng)時(shí)我就根據(jù)經(jīng)驗(yàn)判斷是連接出了問題。
果然,通過查看zookeeper日志,發(fā)現(xiàn)確實(shí)報(bào)連接數(shù)超過最大限制,但業(yè)務(wù)方反饋業(yè)務(wù)才上線,使用人數(shù)也就10來人。那么可以判斷,代碼存在BUG。

問題解決

首先,修改代碼上線是需要經(jīng)過一個(gè)流程的,不適合短時(shí)間解決。 而我們zookepper的最大連接數(shù)配置的是100,我們先將最大連接數(shù)調(diào)整到600,然后查找代碼BUG修復(fù)。

通過走查代碼,發(fā)現(xiàn)代碼中有一個(gè)非常低級(jí)且致命的低級(jí)錯(cuò)誤(大家有沒有發(fā)現(xiàn)呢?),就是圖2的try-cache中的代碼,調(diào)用了createConnection(conf)方法兩次,其中一個(gè)連接返回給調(diào)用者,而另外一個(gè)連接創(chuàng)建后則沒有返回。返回的連接會(huì)在使用后正確關(guān)閉,而沒有返回的連接由于永遠(yuǎn)不可能會(huì)有調(diào)用者,也就不可能手動(dòng)釋放,而只能等待超時(shí)自動(dòng)釋放,超時(shí)時(shí)間在代碼中也看到了-30000ms。這就解釋了為什么并發(fā)不高的情況下,連接首先掛掉了。去掉此段代碼即可。

4.優(yōu)化升級(jí)

請(qǐng)注意看上一段加粗的文字,我為什么加粗呢? 肯定是另有乾坤啊,哈哈哈哈~~~!
圖2中有3個(gè)代碼片段, 可以看到,整個(gè)操作流程是 : 獲取配置 -> 創(chuàng)建Util對(duì)象 -> 創(chuàng)建連接 -> 查詢 -> 關(guān)閉連接。

OMG!OMG!OMG! 不得不驚嘆在21世紀(jì)的20年代, 居然還能看到這樣的代碼。OK,兩個(gè)問題,其一,整個(gè)流程少了個(gè)連接池吧? 其二,util對(duì)象居然是要new出來使用。 不使用連接池的弊端無需多說,太浪費(fèi)資源了。我們可以看看在單機(jī)并發(fā)下TCP連接數(shù)。


image

看到那個(gè)頂上去的尖了嗎。 并發(fā)也不是特別高, 20線程 * 200 次循環(huán)。 可以想象,如果在生產(chǎn)環(huán)境,并發(fā)量如果稍微上去一點(diǎn),這機(jī)器是最先扛不住的。

ok,繼續(xù)整。優(yōu)化思路:

  1. 配置和工具類分離,創(chuàng)建配置對(duì)象,而不創(chuàng)建工具對(duì)象
  2. 使用連接池管理連接,這一點(diǎn)比較好辦,Hbase的Java客戶端提供了連接池。

經(jīng)過優(yōu)化,TCP連接基本比較穩(wěn)定,優(yōu)化代碼我這里就不貼了,代碼還是不少,重要的是思路而不是代碼。

源碼3 + 源碼4:

源碼3比較簡單,就是最基本的JDBC獲取連接操作,同樣的問題,整個(gè)操作都是創(chuàng)建連接、查詢、關(guān)閉連接,而沒有使用到連接池。但這一點(diǎn)和Hbase操作又有所不同,Hbase的數(shù)據(jù)源在系統(tǒng)中只有一個(gè),而JDBC的數(shù)據(jù)源就非常多了,包括MySQL、Hive、Impala都是使用JDBC來連接的,而每一個(gè)數(shù)據(jù)庫就是一個(gè)數(shù)據(jù)源。這樣我們系統(tǒng)中就會(huì)有非常多的數(shù)據(jù)源,而不是單一的數(shù)據(jù)資源管理。

而源碼4我認(rèn)為是比較好的代碼,通過令牌池的機(jī)制限制了單臺(tái)服務(wù)器的最大數(shù)據(jù)庫連接數(shù)量,這種思想在高并發(fā)中也可以使用。 相對(duì)于限流的一種機(jī)制,他最大限度的保證了服務(wù)器的穩(wěn)定性,不像源碼2那樣直接導(dǎo)致服務(wù)不可用。

這里的優(yōu)化思路就是在一個(gè)數(shù)據(jù)源對(duì)應(yīng)一個(gè)連接池,而多個(gè)數(shù)據(jù)源則對(duì)應(yīng)多個(gè)連接池,然后對(duì)多個(gè)連接池進(jìn)行緩存,當(dāng)請(qǐng)求訪問的時(shí)候,首先根據(jù)請(qǐng)求查找到對(duì)應(yīng)的連接池,然后再從連接池獲取一個(gè)連接返回。這樣就解決了頻繁創(chuàng)建連接的問題。這種方式暫時(shí)提升了系統(tǒng)的并發(fā)度,但這種方式對(duì)服務(wù)器的本地資源占用比較多,還有其他的解決方案,比如開源的中間件MyCat等。

如果你看到了這里,證明你太有耐心了 哈哈~?。?/p>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容