1. <tr id="33chb"><label id="33chb"></label></tr>
  2. <pre id="33chb"></pre>
    當前位置:首頁 > 短網址資訊 > 正文內容

    從代碼層面優化系統性能應該怎么做?


    我們以前看到的很多架構變遷或者演進方面的文章大多都是針對架構方面的介紹,很少有針對代碼級別的性能優化介紹。本文將針對一些代碼細節方面的東西進行介紹,歡迎大家吐槽以及提建議。
    服務器環境
    • 服務器配置:4 核 CPU,8G 內存,共 4 臺

    • MQ:RabbitMQ

    • 數據庫:DB2

    • SOA 框架:公司內部封裝的 Dubbo

    • 緩存框架:Redis、Memcached

    • 統一配置管理系統:公司內部開發的系統

    問題描述
    1. 單臺 40TPS,加到 4 臺服務器能到 60TPS,擴展性幾乎沒有。

    2. 在實際生產環境中,經常出現數據庫死鎖導致整個服務中斷不可用。

    3. 數據庫事務亂用,導致事務占用時間太長。

    4. 在實際生產環境中,服務器經常出現內存溢出和 CPU 時間被占滿。

    5. 程序開發的過程中,考慮不全面,容錯很差,經常因為一個小 bug 而導致服務不可用。

    6. 程序中沒有打印關鍵日志,或者打印了日志,信息卻是無用信息沒有任何參考價值。

    7. 配置信息和變動不大的信息依然會從數據庫中頻繁讀取,導致數據庫 IO 很大。

    8. 項目拆分不徹底,一個 Tomcat 中會布署多個項目 WAR 包。

    9. 因為基礎平臺的 bug,或者功能缺陷導致程序可用性降低。

    10. 程序接口中沒有限流策略,導致很多 VIP 商戶直接拿我們的生產環境進行壓測,直接影響真正的服務可用性。

    11. 沒有故障降級策略,項目出了問題后解決的時間較長,或者直接粗暴的回滾項目,但是不一定能解決問題。

    12. 沒有合適的監控系統,不能準實時或者提前發現項目瓶頸。

    優化解決方案
    1、數據庫死鎖優化解決

    我們從第二條開始分析,先看一個基本例子展示數據庫死鎖的發生:

    注:在上述事例中,會話 B 會拋出死鎖異常,死鎖的原因就是 A 和 B 二個會話互相等待。

    分析:出現這種問題就是我們在項目中混雜了大量的事務 +for update 語句,針對數據庫鎖來說有下面三種基本鎖:

    • Record Lock:單個行記錄上的鎖

    • Gap Lock:間隙鎖,鎖定一個范圍,但不包含記錄本身

    • Next-Key Lock:Gap Lock + Record Lock,鎖定一個范圍,并且鎖定記錄本身

    當 for update 語句和 gap lock 和 next-key lock 鎖相混合使用,又沒有注意用法的時候,就非常容易出現死鎖的情況。

    那我們用大量的鎖的目的是什么,經過業務分析發現,其實就是為了防重,同一時刻有可能會有多筆支付單發到相應系統中,而防重措施是通過在某條記錄上加鎖的方式來進行。

    針對以上問題完全沒有必要使用悲觀鎖的方式來進行防重,不僅對數據庫本身造成極大的壓力,同時也會把對于項目擴展性來說也是很大的擴展瓶頸,我們采用了三種方法來解決以上問題:

    • 使用 Redis 來做分布式鎖,Redis 采用多個來進行分片,其中一個 Redis 掛了也沒關系,重新爭搶就可以了。

    • 使用主鍵防重方法,在方法的入口處使用防重表,能夠攔截所有重復的訂單,當重復插入時數據庫會報一個重復錯,程序直接返回。

    • 使用版本號的機制來防重。

    以上三種方式都必須要有過期時間,當鎖定某一資源超時的時候,能夠釋放資源讓競爭重新開始。

    2、數據庫事務占用時間過長

    偽代碼示例:

    項目中類似這樣的程序有很多,經常把類似 httpClient,或者有可能會造成長時間超時的操作混在事務代碼中,不僅會造成事務執行時間超長,而且也會嚴重降低并發能力。

    那么我們在用事務的時候,遵循的原則是快進快出,事務代碼要盡量小。針對以上偽代碼,我們要用 httpClient 這一行拆分出來,避免同事務性的代碼混在一起,這不是一個好習慣。

    3、CPU 時間被占滿分析

    下面以我之前分析的一個案例作為問題的起始點,首先看下面的圖:

    項目在壓測的過程中,CPU 一直居高不下,那么通過分析得出如下分析:

    數據庫連接池影響

    我們針對線上的環境進行模擬,盡量真實的在測試環境中再現,采用數據庫連接池為咱們默認的 C3P0。

    那么當壓測到二萬批,100 個用戶同時訪問的時候,并發量突然降為零!報錯如下:

    com.yeepay.g3.utils.common.exception.YeepayRuntimeException: Could not get JDBC Connection; nested exception is java.sql.SQLException: An attempt by a client to checkout a Connection has timed out.

    那么針對以上錯誤跟蹤 C3P0 源碼,以及在網上搜索資料發現 C3P0 在大并發下表現的性能不佳。

    線程池使用不當引起

    以上代碼的場景是每一次并發請求過來,都會創建一個線程,將 DUMP 日志導出進行分析發現,項目中啟動了一萬多個線程,而且每個線程都極為忙碌,徹底將資源耗盡。

    那么問題到底在哪里呢???就在這一行!

    private static final ExecutorService executorService = Executors.newCachedThreadPool();

    在并發的情況下,無限制的申請線程資源造成性能嚴重下降,在圖表中顯拋物線形狀的元兇就是它?。?!那么采用這種方式最大可以產生多少個線程呢??答案是:Integer 的最大值!看如下源碼:

    那么嘗試修改成如下代碼:

    private static final ExecutorService executorService = Executors.newFixedThreadPool(50);

    修改完成以后,并發量重新上升到 100 以上 TPS,但是當并發量非常大的時候,項目 GC(垃圾回收能力下降),分析原因還是因為 Executors.newFixedThreadPool(50) 這一行,雖然解決了產生無限線程的問題,但是當并發量非常大的時候,采用 newFixedThreadPool 這種方式,會造成大量對象堆積到隊列中無法及時消費,看源碼如下:

    可以看到采用的是無界隊列,也就是說隊列是可以無限的存放可執行的線程,造成大量對象無法釋放和回收。

    最終線程池技術方案

    方案一

    注:因為服務器的 CPU 只有 4 核,有的服務器甚至只有 2 核,所以在應用程序中大量使用線程的話,反而會造成性能影響,針對這樣的問題,我們將所有異步任務全部拆出應用項目,以任務的方式發送到專門的任務處理器處理,處理完成回調應用程序器。后端定時任務會定時掃描任務表,定時將超時未處理的異步任務再次發送到任務處理器進行處理。

    方案二

    使用 AKKA 技術框架,下面是我以前寫的一個簡單的壓測情況:

    http://www.jianshu.com/p/6d62256e3327

    4、日志打印問題

    先看下面這段日志打印程序:

    像這樣的代碼是嚴格不符合規范的,雖然每個公司都有自己的打印要求。

    首先日志的打印必須是以 logger.error 或者 logger.warn 的方式打印出來。

    日志打印格式:[系統來源] 錯誤描述 [關鍵信息],日志信息要能打印出能看懂的信息,有前因和后果。甚至有些方法的入參和出參也要考慮打印出來。

    在輸入錯誤信息的時候,Exception 不要以 e.getMessage 的方式打印出來。

    合理的日志格式是:

    我們在程序中大量的打印日志,雖然能夠打印很多有用信息幫助我們排查問題,但是更多是日志量太多不僅影響磁盤 IO,更多會造成線程阻塞對程序的性能造成較大影響。

    在使用 Log4j1.2.14 版本的時候,使用如下格式:

    %d %-5p %c:%L [%t] - %m%n

    那么在壓測的時候會出現下面大量的線程阻塞,如下圖:

    再看壓測圖如下:

    原因可以根據 log4j 源碼分析如下:

    注:Log4j 源碼里用了 synchronized 鎖,然后又通過打印堆棧來獲取行號,在高并發下可能就會出現上面的情況。

    于是修改 Log4j 配置文件為:

    %d %-5p %c [%t] - %m%n

    上面問題解決,線程阻塞的情況很少出現,極大的提高了程序的并發能力,如下圖所示:

    掃描二維碼推送至手機訪問。

    版權聲明:本文由短鏈接發布,如需轉載請注明出處。

    本文鏈接:http://www.virginiabusinesslawupdate.com/article_414.html

    分享給朋友:

    相關文章

    實戰Guzzle抓取

    雖然早就知道很多人用 Guzzle 爬數據,但是我卻從來沒有真正實踐過,因為在我的潛意識里,抓取是 Python 的地盤。不過前段時間,當我抓汽車之家數據的時候,好心人跟我提起 Goutte 搭配 G...

    HTTP的長連接和短連接

    HTTP的長連接和短連接

     本文總結&分享網絡編程中涉及的長連接、短連接概念。    關鍵字:Keep-Alive,并發連接數限制,TCP,HTTP一、什么是長連接     HTTP1.1...

    解析百雀羚的廣告為什么刷爆朋友圈

    雖然我們曉得這是一則廣告,但是我們還是想要看到最后一刻,這就是好的想法和創意帶給我們的吸收力。朋友圈被百雀羚的廣告刷屏,繼寶馬的H5廣告之后,這家降生于1931年的企業再次用一種十分新奇的方式火爆了整個朋友圈。雖然我們曉得這是一則廣告,但是...

    英國和中國的人工智能醫學應用已經改變未來

    英國和中國的人工智能醫學應用已經改變未來

    [ 短網址導讀 ] AI醫生只是人工智能應用于醫療保健領域的一個縮影。機器學習和大數據的快速商業化,已經助推人工智能來到醫療保健和生命健康領域的前沿位置,將注定會改變行業內疾病診斷和疾病治療的方式。圖片來自“123rf.com.c...

    短網址的今生前世

    短網址的今生前世

    引言  短網址司空見慣,比如說下面這些  http://u6.gg/baidu => http://www.baidu.com,  http://rrd.me/baidu => http:...

    FT12短網址對科大訊飛2017半年報及人工智能戰略的解讀

    FT12短網址對科大訊飛2017半年報及人工智能戰略的解讀

    [ FT12短網址資訊 ] 2017年8月10日,科大訊飛應邀在合肥舉行投資者交流會。董事長劉慶峰、高級副總裁兼董秘江濤、財務總監張少兵對公司半年報及人工智能戰略進行了詳細解讀,并現場回答了投資者及網友十分關注的問題。鄙人一直對科大訊飛這幾...

    發表評論

    訪客

    ◎歡迎參與討論,請在這里發表您的看法和觀點。
    一本色综合网久久
    1. <tr id="33chb"><label id="33chb"></label></tr>
    2. <pre id="33chb"></pre>