論代碼級(jí)性能優(yōu)化變遷之路(二)

本文是“論代碼級(jí)性能優(yōu)化變遷之路一”(http://www.reibang.com/p/c4a748002e66) 的第二篇。

在上一篇我們主要介紹了所遇到問題的五點(diǎn)厘灼,那么今天接下來討論剩下的問題,我們先再回顧一下之前討論的問題:

1脸候、單臺(tái)40TPS婿脸,加到4臺(tái)服務(wù)器能到60TPS,擴(kuò)展性幾乎沒有烛占。
2、在實(shí)際生產(chǎn)環(huán)境中,經(jīng)常出現(xiàn)數(shù)據(jù)庫死鎖導(dǎo)致整個(gè)服務(wù)中斷不可用忆家。
3犹菇、數(shù)據(jù)庫事務(wù)亂用,導(dǎo)致事務(wù)占用時(shí)間太長芽卿。
4揭芍、在實(shí)際生產(chǎn)環(huán)境中,服務(wù)器經(jīng)常出現(xiàn)內(nèi)存溢出和CPU時(shí)間被占滿卸例。
5称杨、程序開發(fā)的過程中,考慮不全面筷转,容錯(cuò)很差姑原,經(jīng)常因?yàn)橐粋€(gè)小bug而導(dǎo)致服務(wù)不可用。
6呜舒、程序中沒有打印關(guān)鍵日志锭汛,或者打印了日志,信息卻是無用信息沒有任何參考價(jià)值袭蝗。
7店乐、配置信息和變動(dòng)不大的信息依然會(huì)從數(shù)據(jù)庫中頻繁讀取,導(dǎo)致數(shù)據(jù)庫IO很大呻袭。
8、項(xiàng)目拆分不徹底腺兴,一個(gè)tomcat中會(huì)布署多個(gè)項(xiàng)目WAR包左电。
9、因?yàn)榛A(chǔ)平臺(tái)的bug页响,或者功能缺陷導(dǎo)致程序可用性降低篓足。
10、程序接口中沒有限流策略闰蚕,導(dǎo)致很多vip商戶直接拿我們的生產(chǎn)環(huán)境進(jìn)行壓測(cè)栈拖,直接影響真正的服務(wù)可用性。
11没陡、沒有故障降級(jí)策略涩哟,項(xiàng)目出了問題后解決的時(shí)間較長,或者直接粗暴的回滾項(xiàng)目盼玄,但是不一定能解決問題贴彼。
12、沒有合適的監(jiān)控系統(tǒng)埃儿,不能準(zhǔn)實(shí)時(shí)或者提前發(fā)現(xiàn)項(xiàng)目瓶頸器仗。

四、優(yōu)化解決方案

5、緩存優(yōu)化方案
針對(duì)配置信息和變動(dòng)不大的信息可以放到緩存中精钮,提高并發(fā)能力也能夠降低IO緩存威鹿,具體緩存優(yōu)化策略可以參考我之前寫的:
http://www.reibang.com/p/d96906140199

6、程序容錯(cuò)優(yōu)化方案
在這一塊我要先舉一個(gè)程序的例子說明一下什么才是容錯(cuò)轨香,先看程序:

//Service層:
public void insertOrderInfo(OrderInfo orderInfo) {
        try {
            OrderDao.insertOrderInfo(orderInfo);
        } catch (Exception e) {
            logger.error("訂單信息插入數(shù)據(jù)庫失敗! orderId:"+orderInfo.getOrderId(), e);
        }
    }

//DAO層
 public void insertOrderInfo(OrderInfo orderInfo) {
        try {
            this.sqlMapClient.insert("Order.insertOrderInfo", orderInfo)
        } catch (Exception e) {}
    }

注:
那么如果service層的方法調(diào)用dao層的方法忽你,一旦數(shù)據(jù)插入失敗,那么這種異常處理的方式是容錯(cuò)嗎弹沽?
把異常給吃掉了檀夹,在service層調(diào)用的時(shí)候,雖然沒有打印報(bào)錯(cuò)信息策橘,但是這能是容錯(cuò)嗎炸渡?
所謂容錯(cuò)是指在故障存在的情況下計(jì)算機(jī)系統(tǒng)不失效,仍然能夠正常工作的特性丽已。

我們拿使用緩存來作為一個(gè)案例講解蚌堵,先看一個(gè)圖:

Paste_Image.png

這是一個(gè)最簡(jiǎn)單的圖,應(yīng)用服務(wù)定期從redis中獲取配置信息沛婴,可能會(huì)有朋友認(rèn)為這樣已經(jīng)很穩(wěn)定了吼畏,但是如果Redis出現(xiàn)問題呢?可能會(huì)有朋友說嘁灯,Redis會(huì)是集群泻蚊,分片或者主從,確保不會(huì)出現(xiàn)問題丑婿。其實(shí)我是這樣的認(rèn)為的性雄,雖然應(yīng)用服務(wù)程序盡量的保持輕量級(jí)是不錯(cuò)的,但是不能因此而把希望全部寄托在中間組件上面羹奉,換句話說秒旋,如果此時(shí)的Redis是單點(diǎn),那么后果會(huì)是什么樣的诀拭,那么隨著大量的并發(fā)請(qǐng)求到來的時(shí)候迁筛,程序中會(huì)報(bào)大量的錯(cuò)誤,同時(shí)正常的流程也不能進(jìn)行下去了業(yè)務(wù)也可能由此而中斷耕挨。

那么在此種場(chǎng)景下我的解決方案是细卧,要把緩存的使用分級(jí)別,有的緩存同步要求時(shí)效性非常高筒占,比如支付限額配置酒甸,在后臺(tái)修改完成以后前臺(tái)立刻就能夠獲得感知,并且能夠成功切換赋铝,這種情況只能實(shí)時(shí)的從Redis中獲取最新數(shù)據(jù)插勤,但是每次獲取完最新的數(shù)據(jù)后都可以同步更新本地緩存,當(dāng)單點(diǎn)的Redis掛掉后,應(yīng)用程序至少還能從本地讀取信息而不至于服務(wù)瞬間掛掉农尖。有的緩存對(duì)時(shí)效性要求不高析恋,允許有一定延遲,那么在這種情況下我采用的方案是盛卡,利用本地緩存和遠(yuǎn)程緩存相結(jié)合的方式助隧,如下圖所示:
方案一:

Paste_Image.png

這種方式通過應(yīng)用服務(wù)器的Ehcache定時(shí)輪詢Redis緩存服務(wù)器更同步更新本地緩存,缺點(diǎn)是因?yàn)槊颗_(tái)服務(wù)器定時(shí)Ehcache的時(shí)間不一樣滑沧,那么不同服務(wù)器刷新最新緩存的時(shí)間也不一樣并村,會(huì)產(chǎn)生數(shù)據(jù)不一致問題,對(duì)一致性要求不高可以使用滓技。

方案二:

Paste_Image.png

通過引入了MQ隊(duì)列哩牍,使每臺(tái)應(yīng)用服務(wù)器的Ehcache同步偵聽MQ消息,這樣在一定程度上可以達(dá)到準(zhǔn)同步更新數(shù)據(jù)令漂,通過MQ推送或者拉取的方式膝昆,但是因?yàn)椴煌?wù)器之間的網(wǎng)絡(luò)速度的原因,所以也不能完全達(dá)到強(qiáng)一致性叠必〖苑酰基于此原理使用Zookeeper等分布式協(xié)調(diào)通知組件也是如此。

7纬朝、部分項(xiàng)目拆分不徹底

  • 拆分前

    Paste_Image.png

    注:
    一個(gè)Tomcat中布署多個(gè)應(yīng)用war包收叶,彼此之間互相牽制在并發(fā)量非常大的情況下性能降低非常明顯。

  • 拆分后

    Paste_Image.png

    注:
    拆分前的這種情況其實(shí)還是挺普遍共苛,之前我一直認(rèn)為項(xiàng)目中不會(huì)存在這種情況但是事實(shí)上還是存在了判没。解決的方法很簡(jiǎn)單,每一個(gè)應(yīng)用war只布在一個(gè)tomcat中俄讹,這樣應(yīng)用程序之間就不會(huì)存在資源和連接數(shù)的競(jìng)爭(zhēng)情況,性能和并發(fā)能力提交較為明顯绕德。

8患膛、因基礎(chǔ)平臺(tái)組件功能不完善導(dǎo)致性能下降
先看一段代碼:

public void purchase(PurchaseParam purchaseParam, long timeoutSencond) {
        Future<String> future = threadPool.submit(new TestRunnable(purchaseParam, testService));
        logger.info("超時(shí)時(shí)間="+timeoutSencond);
        if(timeoutSencond > 0){
            try {
                future.get(timeoutSencond, TimeUnit.SECONDS);
                logger.info("超時(shí)返回,超時(shí)時(shí)間="+timeoutSencond);
            } catch (InterruptedException e) {
                logger.info("",e);
            } catch (ExecutionException e) {
                logger.info("",e);
            } catch (TimeoutException e) {
                logger.info("",e);
            }
        }
    }

注:
首先我們先不說這段代碼的格式如何如何耻蛇,先看功能實(shí)現(xiàn)踪蹬,使用Future來做超時(shí)控制,這是為何呢臣咖?原因其實(shí)是在我們調(diào)用的Dubbo接口上面跃捣,因?yàn)槭荄ubbo已經(jīng)經(jīng)過二次封裝,結(jié)果把自帶的timeout給淹沫了夺蛇,程序員只能通過這種方式來控制超時(shí)疚漆,可以看到這種用法非常差勁,對(duì)程序性能造成一定的影響。

9娶聘、如何快速定位程序性能瓶頸

我相信在定位程序性能問題的時(shí)候闻镶,大家有很多種辦法,比如用jdk自帶的命令丸升,如Jcmd铆农,Jstack,jmap狡耻,jhat墩剖,jstat,iostat夷狰,vmstat等等命令岭皂,還可以用VisualVM,MAT孵淘,JRockit等可視化工具蒲障,我今天想說的是利用一個(gè)最簡(jiǎn)單的命令就能夠定位到哪段程序可能存在性能問題,請(qǐng)看下面介紹:

一般我們會(huì)通過top命令查看各個(gè)進(jìn)程的cpu和內(nèi)存占用情況瘫证,獲得到了我們的進(jìn)程id揉阎,然后我們將會(huì)通過pstack命令查看里邊的各個(gè)線程id以及對(duì)應(yīng)的線程現(xiàn)在正在做什么事情,分析多組數(shù)據(jù)就可以獲得哪些線程里有慢操作影響了服務(wù)器的性能背捌,從而得到解決方案毙籽。示例如下:

輸入命令:pstack 30222

顯示如下:
Thread 9 (Thread 0x7f729adc1700 (LWP 30251)):
#0  0x00007f72a429b720 in sem_wait () from /lib64/libpthread.so.0
#1  0x0000000000ac5eb6 in Semaphore::down() ()
#2  0x0000000000ac5cac in Queue::get() ()
#3  0x00000000009a583f in DBManager::processUpdate(Queue*) ()
#4  0x00000000009a4bfb in dbUpdateThread(void*) ()
#5  0x00007f72a4295851 in start_thread () from /lib64/libpthread.so.0
#6  0x00007f72a459267d in clone () from /lib64/libc.so.6
Thread 1 (Thread 0x7f72a60ae7e0 (LWP 30222)):
#0  0x00007f72a4584c95 in _xstat () from /lib64/libc.so.6
#1  0x00007f72a45483e0 in __tzfile_read () from /lib64/libc.so.6
#2  0x00007f72a4547864 in tzset_internal () from /lib64/libc.so.6
#3  0x00007f72a4547b20 in tzset () from /lib64/libc.so.6
#4  0x00007f72a4546699 in timelocal () from /lib64/libc.so.6
#5  0x0000000000b0b08d in Achieve::GetRemainTime(AchieveTemplate*) ()
#6  0x0000000000b115ca in Achieve::update() ()
#7  0x0000000000a197ce in Player::update() ()
#8  0x0000000000b1b272 in PlayerMng::Tick() ()
#9  0x0000000000a73105 in GameServer::FrameTick(unsigned int) ()
#10 0x0000000000a6ff80 in GameServer::run() ()
#11 0x0000000000a773a1 in main ()

輸入命令:ps  -eLo pid,lwp,pcpu | grep 30222
顯示如下:

30222 30222 31.4
30222 30251  0.0
30222 30252  0.0
30222 30253  0.0

由此可以判斷出來在LWP 30222這個(gè)線程產(chǎn)生了性能問題,執(zhí)行時(shí)間長達(dá)31.4毫秒的時(shí)間毡庆,再觀察無非就是下面的幾個(gè)語句出現(xiàn)的問題坑赡,只需要簡(jiǎn)單排查就知道了問題瓶頸。


Paste_Image.png

10么抗、關(guān)于索引的優(yōu)化

  • 組合索引的原則是偏左原則毅否,所以在使用的時(shí)候需要多加注意

  • 索引的數(shù)量不需要過多的添加,在添加的時(shí)候要考慮聚集索引和輔助索引蝇刀,這二者的性能是有區(qū)別的

  • 索引不會(huì)包含有NULL值的列
    只要列中包含有NULL值都將不會(huì)被包含在索引中螟加,復(fù)合索引中只要有一列含有NULL值,那么這一列對(duì)于此復(fù)合索引就是無效的吞琐。所以我們?cè)跀?shù)據(jù)庫設(shè)計(jì)時(shí)不要讓字段的默認(rèn)值為NULL捆探。

  • MySQL索引排序
    MySQL查詢只使用一個(gè)索引,因此如果where子句中已經(jīng)使用了索引的話站粟,那么order by中的列是不會(huì)使用索引的黍图。因此數(shù)據(jù)庫默認(rèn)排序可以符合要求的情況下不要使用排序操作;盡量不要包含多個(gè)列的排序奴烙,如果需要最好給這些列創(chuàng)建復(fù)合索引助被。

  • 使用索引的注意事項(xiàng)
    以下操作符可以應(yīng)用索引:
    大于等于
    Between
    IN
    LIKE 不以%開頭

以下操作符不能應(yīng)用索引:
NOT IN
LIKE %_開頭

  • 索引技巧
    同樣是1234567890剖张,數(shù)值類型存儲(chǔ)遠(yuǎn)比字符串節(jié)約存儲(chǔ)空間。
    節(jié)約存儲(chǔ)就是節(jié)約IO恰起,減少IO就是提升性能
    通常對(duì)數(shù)字的索引和檢索要比對(duì)字符串的索引和檢索效率更高修械。

** 11、使用Redis需要注意的一些點(diǎn)**

  • 在增加key的時(shí)候盡量設(shè)置過期時(shí)間检盼,不然Redis Server的內(nèi)存使用會(huì)達(dá)到
    系統(tǒng)物理內(nèi)存的最大值肯污,導(dǎo)致Redis使用VM降低系統(tǒng)性能

  • Redis Key設(shè)計(jì)時(shí)應(yīng)該盡可能短,Value盡量不要使用復(fù)雜對(duì)象。

  • 將對(duì)象轉(zhuǎn)換成JSON對(duì)象(利用現(xiàn)成的JSON庫)后存入Redis吨枉,

  • 將對(duì)象轉(zhuǎn)換成Google開源二進(jìn)制協(xié)議對(duì)象(Google Protobuf蹦渣,和JSON數(shù)據(jù)
    格式類似,但是因?yàn)槭嵌M(jìn)制表現(xiàn)貌亭,所以性能效率以及空間占用都比JSON要屑砦ā;
    缺點(diǎn)是Protobuf的學(xué)習(xí)曲線比JSON大得多)

  • Redis使用完以后一定要釋放連接圃庭,如下圖示例:

Paste_Image.png

不管是返回到連接池中還是直接釋放掉锄奢,總之就是要將連接還回去。

** 12剧腻、關(guān)于長耗時(shí)方法的拆分**
我們拆分長耗時(shí)方法的一般技巧是:

  • 尋找業(yè)務(wù)的冗余點(diǎn)拘央,代碼中有很多重復(fù)性的代碼,可以適當(dāng)簡(jiǎn)化书在。
  • 檢查庫表索引是否合理加入灰伟。
  • 利用單元測(cè)試或者壓力測(cè)試長耗時(shí)的操作進(jìn)行算法級(jí)別優(yōu)化,比如從庫中大批量讀取數(shù)據(jù)儒旬,或者長時(shí)間循環(huán)操作栏账,或者死循環(huán)操作等等。
  • 尋找業(yè)務(wù)的拆分點(diǎn)栈源,根據(jù)業(yè)務(wù)需求拆分同步操作為異步挡爵,比如可以使用消息隊(duì)列或者多線程異步化。

經(jīng)過以上幾個(gè)分析后如果方法執(zhí)行時(shí)間仍然非常的長甚垦,這樣可能就是業(yè)務(wù)方面的需求使然茶鹃,如下圖:


Paste_Image.png

那么我們是否可以考慮將一個(gè)長耗時(shí)方法進(jìn)行拆分,拆分為多個(gè)短耗時(shí)方法由發(fā)起端分別調(diào)用制轰,這樣在高并發(fā)的情況下不會(huì)造成某一個(gè)方法的長時(shí)間阻塞前计,在一定程度上能夠提高并發(fā)能力胞谭,如下圖:

Paste_Image.png

在接下來的第三篇文章中我們就介紹系統(tǒng)的降級(jí)垃杖,限流,還有監(jiān)控的一些方案丈屹。謝謝大家

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末调俘,一起剝皮案震驚了整個(gè)濱河市伶棒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌彩库,老刑警劉巖肤无,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異骇钦,居然都是意外死亡宛渐,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門眯搭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來窥翩,“玉大人,你說我怎么就攤上這事鳞仙】芪茫” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵棍好,是天一觀的道長仗岸。 經(jīng)常有香客問我,道長借笙,這世上最難降的妖魔是什么扒怖? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮提澎,結(jié)果婚禮上姚垃,老公的妹妹穿的比我還像新娘。我一直安慰自己盼忌,他們只是感情好积糯,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著谦纱,像睡著了一般看成。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上跨嘉,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天川慌,我揣著相機(jī)與錄音,去河邊找鬼祠乃。 笑死梦重,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的亮瓷。 我是一名探鬼主播琴拧,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼嘱支!你這毒婦竟也來了蚓胸?” 一聲冷哼從身側(cè)響起挣饥,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎沛膳,沒想到半個(gè)月后扔枫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锹安,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年短荐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叹哭。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡搓侄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出话速,到底是詐尸還是另有隱情讶踪,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布泊交,位于F島的核電站乳讥,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏廓俭。R本人自食惡果不足惜云石,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望研乒。 院中可真熱鬧汹忠,春花似錦、人聲如沸雹熬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽竿报。三九已至铅乡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間烈菌,已是汗流浹背阵幸。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留芽世,地道東北人挚赊。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像济瓢,于是被迫代替她去往敵國和親荠割。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容