免费人成动漫在线播放r18-免费人成观看在线网-免费人成黄页在线观看日本-免费人成激情视频在线观看冫-jlzzjlzz亚洲大全-jlzzjlzz亚洲日本

二維碼
企資網

掃一掃關注

當前位置: 首頁 » 企資頭條 » 頭條 » 正文

5秒到1秒_記一次效果“非常”顯著的性能優化

放大字體  縮小字體 發布日期:2021-08-29 22:25:44    作者:企資小編    瀏覽次數:69
導讀

原創_小姐姐味道(微信公眾號ID_xjjdog)_歡迎分享_轉載請保留出處。性能優化_有時候看起來是一個比較虛的技術需求。除非代碼慢的已經讓人無法忍受_否則_很少有公司會有覺悟投入資源去做這些工作。即使你有了性能指

原創_小姐姐味道(微信公眾號ID_xjjdog)_歡迎分享_轉載請保留出處。

性能優化_有時候看起來是一個比較的技術需求。除非代碼慢的已經讓人無法忍受_否則_很少有公司會有覺悟投入資源去做這些工作。即使你有了性能指標數據_也很難說服領導做一個由耗時300ms降低到150ms的改進_因為她沒有業務價值。

這很讓人傷心_但這是悲催的現實。

性能優化_通常由有技術追求的人發起_根據觀測指標進行的正向優化。他們通常具有工匠精神_對每一毫秒的耗時都吹毛求疵_力求完美。當然_前提是你得有時間。

1._優化背景和目標

我們本次的性能優化_就是由于達到了無法忍受的程度_才進行的優化工作_屬于事后補救_問題驅動的方式。這通常沒什么問題_畢竟業務第一嘛_迭代在填坑中進行。

先說背景。本次要優化的服務_請求響應時間十分的不穩定。隨著數據量的增加_大部分請求_要耗時5-6秒左右!超出了常人能忍受的范圍。

當然需要優化。

為了說明要優化的目標_我大體畫了一下她的拓撲結構。如圖所示_這是一套微服務架構的服務。


其中_我們優化的目標_就處于一個比較靠上游的服務。她需要通過Feign接口_調用下游非常多的服務提供者_獲取數據后進行聚合拼接_最終通過zuul網關和nginx_來發送到瀏覽器客戶端。

為了觀測服務之間的調用關系和監控數據_我們接入了Skywalking調用鏈平臺和Prometheus監控平臺_收集重要的數據以便能夠進行優化決策。要進行優化之前_我們需要首先看一下優化需要參考的兩個技術指標。

  • 吞吐量_單位時間內發生的次數。比如QPS、TPS、HPS等。
  • 平均響應時間_每個請求的平均耗時。


    平均響應時間自然是越小越好_她越小_吞吐量越高。吞吐量的增加還可以合理利用多核_通過并行度增加單位時間內的發生次數。

    我們本次優化的目標_就是減少某些接口的平均響應時間_降低到1秒以內;增加吞吐量_也就是提高QPS_讓單實例系統能夠承接更多的并發請求。

    2._通過壓縮讓耗時急劇減少

    我想要先介紹讓系統飛起來最重要的一個優化手段_壓縮。

    通過在chromeinspect中查看請求的數據_我們發現一個關鍵的請求接口_每次要傳輸大約10MB的數據。這得塞了多少東西。

    這么大的數據_光下載就需要耗費大量時間。如下圖所示_是我請求juejin主頁的某一個請求_其中的content_download_就代表了數據在網絡上的傳輸時間。如果用戶的帶寬非常慢_那么這個請求的耗時_將會是非常長的。


    為了減少數據在網絡上的傳輸時間_可以啟用gzip壓縮。gzip壓縮是屬于時間換空間的做法。對于大多數服務來說_最后一環是nginx_大多數人都會在nginx這一層去做壓縮。她的主要配置如下_

    gzip_on;gzip_vary_on;gzip_min_length_10240;gzip_proxied_expired_no-cache_no-store_private_auth;gzip_types_text/plain_text/css_text/xml_text/javascript_application/x-javascript_application/xml;gzip_disable_"MSIE_[1-6]\.";

    壓縮率有多驚人呢?我們可以看一下這張截圖。可以看到_數據壓縮后_由8.95MB縮減到了368KB!瞬間就能夠被瀏覽器下載下來。


    但是等等_nginx只是最外面的一環_還沒完_我們還可以讓請求更快一些。

    請看下面的請求路徑_由于采用了微服務_請求的流轉就變得復雜起來_nginx并不是直接調用了相關得服務_她調用的是zuul網關_zuul網關才真正調用的目標服務_目標服務又另外調用了其他服務。內網帶寬也是帶寬_網絡延遲也會影響調用速度_同樣也要壓縮起來。

    nginx->zuul->服務A->服務E

    要想Feign之間的調用全部都走壓縮通道_還需要額外的配置。我們是springboot服務_可以通過okhttp的透明壓縮進行處理。

    加入她的依賴_

    <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId></dependency>

    開啟服務端配置_

    server:  port:8888  compression:    enabled:true    min-response-size:1024    mime-types:["text/html","text/xml","application/xml","application/json","application/octet-stream"]

    開啟客戶端配置_

    feign:  httpclient:    enabled:false  okhttp:    enabled:true

    經過這些壓縮之后_我們的接口平均響應時間_直接從5-6秒降低到了2-3秒_優化效果非常顯著。

    當然_我們也在結果集上做了文章_在返回給前端的數據中_不被使用的對象和字段_都進行了精簡。但一般情況下_這些改動都是傷筋動骨的_需要調整大量代碼_所以我們在這上面用的精力有限_效果自然也有限。

    3._并行獲取數據_響應飛快

    接下來_就要深入到代碼邏輯內部進行分析了。上面我們提到_面向用戶的接口_其實是一個數據聚合接口。她的每次請求_通過Feign_調用了幾十個其他服務的接口_進行數據獲取_然后拼接結果集合。

    為什么慢?因為這些請求全部是串行的!Feign調用屬于遠程調用_也就是網絡I/O密集型調用_多數時間都在等待_如果數據滿足的話_是非常適合并行調用的。

    首先_我們需要分析這幾十個子接口的依賴關系_看一下她們是否具有嚴格的順序性要求。如果大多數沒有_那就再好不過了。

    分析結果喜憂參半_這堆接口_按照調用邏輯_大體上可以分為A_B類。首先_需要請求A類接口_拼接數據后_這些數據再供B類使用。但在A_B類內部_是沒有順序性要求的。


    也就是說_我們可以把這個接口_拆分成順序執行的兩部分_在某個部分都可以并行的獲取數據。

    那就按照這種分析結果改造試試吧_使用concurrent包里的CountDownLatch_很容易的就實現了并取功能。

    CountDownLatch latch _ new CountDownLatch(jobSize);//submit jobexecutor.execute(() -> {     //job code latch.countDown(); }); executor.execute(() -> {  latch.countDown(); }); ...//end submitlatch.await(timeout, TimeUnit.MILLISECONDS); 

    結果非常讓人滿意_我們的接口耗時_又減少了接近一半!此時_接口耗時已經降低到2秒以下。

    你可能會問_為什么不用Java的并行流呢?關于并行流的坑_可以參考這篇文章。非常不建議你使用她。

    《parallelStream的坑_不踩不知道_一踩嚇一跳》

    并發編程一定要小心_尤其是在業務代碼中的并發編程。我們構造了專用的線程池_來支撐這個并發獲取的功能。

    final ThreadPoolExecutor executor _ new ThreadPoolExecutor(100, 200, 1,             TimeUnit.HOURS, new ArrayBlockingQueue<>(100)); 

    壓縮和并行化_是我們本次優化中_最有效的手段。她們直接砍掉了請求大半部分的耗時_非常的有效。但我們還是不滿足_因為每次請求_依然有1秒鐘以上呢。

    4._緩存分類_進一步加速

    我們發現_有些數據的獲取_是放在循環中的_有很多無效請求_這不能忍。

    for(List){    client.getData();}

    如果將這些常用的結果緩存起來_那么就可以大大減少網絡IO請求的次數_增加程序的運行效率。

    緩存在大多數應用程序的優化中_作用非常大。但由于壓縮和并行效果的對比_緩存在我們這個場景中_效果不是非常的明顯_但依然減少了大約三四十毫秒的請求時間。

    我們是這么做的。

    首先_我們將一部分代碼邏輯簡單_適合Cache_Aside_Pattern模式的數據_放在了分布式緩存Redis中。具體來說_就是讀取的時候_先讀緩存_緩存讀不到的時候_再讀數據庫;更新的時候_先更新數據庫_再刪除緩存(延時雙刪)。使用這種方式_能夠解決大部分業務邏輯簡單的緩存場景_并能解決數據的一致性問題。

    但是_僅僅這么做是不夠的_因為有些業務邏輯非常的復雜_更新的代碼發非常的分散_不適合使用Cache_Aside_Pattern進行改造。我們了解到_有部分數據_具有以下特點_

    1. 這些數據_通過耗時的獲取之后_在極端的時間內_會被再次用到
    2. 業務數據對她們的一致性要求_可以控制在秒級別以內
    3. 對于這些數據的使用_跨代碼、跨線程_使用方式多樣

    針對于這種情況_我們設計了存在時間極短的堆內內存緩存_數據在1秒之后_就會失效_然后重新從數據庫中讀取。加入某個節點調用服務端接口是1秒鐘1k次_我們直接給降低到了1次。

    在這里_使用了Guava的LoadingCache_減少的Feign接口調用_是數量級的。

    LoadingCache<String, String> lc _ CacheBuilder      .newBuilder()      .expireAfterWrite(1,TimeUnit.SECONDS)      .build(new CacheLoader<String, String>() {      @Override      public String load(String key) throws Exception {            return slowMethod(key);}});

    5._MySQL索引的優化

    我們的業務系統_使用的是MySQL數據庫_由于沒有專業DBA介入_而且數據表是使用JPA生成的。在優化的時候_發現了大量不合理的索引_當然是要優化掉。

    由于SQL具有很強的敏感性_我這里只談一些在優化過程中碰到的索引優化規則問題_相信你一樣能夠在自己的業務系統中進行類比。

    索引非常有用_但是要注意_如果你對字段做了函數運算_那索引就用不上了。常見的索引失效_還有下面兩種情況_

  • 查詢的索引字段類型_與用戶傳遞的數據類型不同_要做一層隱式轉換。比如varchar類型的字段上_傳入了int參數
  • 查詢的兩張表之間_使用的字符集不同_也就無法使用關聯字段作為索引

    MySQL的索引優化_最基本的是遵循最左前綴原則_當有a、b、c三個字段的時候_如果查詢條件用到了a_或者a、b_或者a、b、c_那么我們就可以創建(a_b_c)一個索引即可_她包含了a和ab。當然_字符串也是可以加前綴索引的_但在平常應用中較少。

    有時候_MySQL的優化器_會選擇了錯誤的索引_我們需要使用force_index指定所使用的索引。在JPA中_就要使用nativeQuery_來書寫綁定到MySQL數據庫的SQL語句_我們盡量的去避免這種情況。

    另外一個優化是減少回表。由于InnoDB采用了B+樹_但是如果不使用非主鍵索引_會通過二級索引(secondary_index)先查到聚簇索引(clustered_index)_然后再定位到數據。多了一步_產生回表。使用覆蓋索引_可以一定程度上避免回表_是常用的優化手段。具體做法_就是把要查詢的字段_與索引放在一起做聯合索引_是一種空間換時間的做法。

    6._JVM優化

    我通常將JVM的優化放在最后一環。而且_除非系統發生了嚴重的卡頓_或者OOM問題_都不會主動對其進行過度優化。

    很不幸的是_我們的應用_由于開啟了大內存(8GB+)_在JDK1.8默認的并行收集器下_經常發生卡頓。雖然不是很頻繁_但動輒幾秒鐘_已經嚴重影響到部分請求的平滑性。

    程序剛開始_是光禿禿跑在JVM下的_GC信息_還有OOM_什么都沒留下。為了記錄GC信息_我們做了如下的改造。

    第一步_加入GC問題排查的各種參數。

    -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath_/opt/xxx.hprof  -DlogPath_/opt/logs/ -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintTenuringDistribution -Xloggc:/opt/logs/gc__p.log -XX:ErrorFile_/opt/logs/hs_error_pid_p.log

    這樣_我們就可以拿著生成的GC文件_上傳到gceasy等平臺進行分析。可以查看JVM的吞吐量和每個階段的延時等。

    第二步_開啟SpringBoot的GC信息_接入Promethus監控。

    在pom中加入依賴。

    <dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-actuator</artifactId></dependency>

    然后配置暴露點就可以了。這樣_我們就擁有了實時的分析數據_有了優化的依據。

    management.endpoints.web.exposure.include_health,info,prometheus


    在觀測了JVM的表現之后_我們切換成了G1垃圾回收器。G1有最大停頓目標_可以讓我們的GC時間更加的平滑。她主要有以下幾個調優參數_

  • -XX:MaxGCPauseMillis_設置目標停頓時間_G1會盡力達成。
  • -XX:G1HeapRegionSize_設置小堆區大小。這個值為2的次冪_不要太大_也不要太小。如果是在不知道如何設置_保持默認。
  • -XX:InitiatingHeapOccupancyPercent_當整個堆內存使用達到一定比例(默認是45_)_并發標記階段就會被啟動。
  • -XX:ConcGCThreads_并發垃圾收集器使用的線程數量。默認值隨JVM運行的平臺不同而不同。不建議修改。

    切換成G1之后_這種不間斷的停頓_竟然神奇的消失了!期間_還發生過很多次內存溢出的問題_不過有MAT這種神器的加持_最終都很easy的被解決了。

    7._其他優化

    在工程結構和架構方面_如果有硬傷的話_那么代碼優化方面_起到的作用其實是有限的_就比如我們這種情況。

    但主要代碼還是要整一下容得。有些處于高耗時邏輯中的關鍵的代碼_我們對其進行了格外的關照。按照開發規范_對代碼進行了一次統一的清理。其中_有幾個印象比較深深刻的點。

    有同學為了能夠復用map集合_每次用完之后_都使用clear方法進行清理。

    map1.clear();map2.clear();map3.clear();map4.clear();

    這些map中的數據_特別的多_而clear方法有點特殊_她的時間復雜度事O(n)的_造成了較高的耗時。

    public void clear() {    Node<K,V>[] tab;    modCount++;    if ((tab _ table) !_ null && size > 0) {        size _ 0;        for (int i _ 0; i < tab.length; ++i)            tab[i] _ null;    }}

    同樣的線程安全的隊列_有ConcurrentlinkedQueue_她的size()方法_時間復雜度非常高_不知怎么就被同事給用上了_這都是些性能殺手。

    public int size() {        restartFromHead: for (;;) {            int count _ 0;            for (Node<E> p _ first(); p !_ null;) {                if (p.item !_ null)                    if (++count __ Integer.MAX_VALUE)                        break;  // @see Collection.size()                if (p __ (p _ p.next))                    continue restartFromHead;            }            return count;        }}

    另外_有些服務的web頁面_本身響應就非常的慢_這是由于業務邏輯復雜_前端Javascript本身就執行緩慢。這部分代碼優化_就需要前端的同事去處理了_如圖_使用chrome或者firefox的performance選項卡_可以很容易發現耗時的前端_代碼。


    8._總結

    性能優化_其實也是有套路的_但一般團隊都是等發生了問題才去優化_鮮有未雨綢繆的。但有了監控和APM就不一樣_我們能夠隨時拿到數據_反向推動優化過程。

    有些性能問題_能夠在業務需求層面_或者架構層面去解決。凡是已經帶到代碼層_需要程序員介入的優化_都已經到了需求方和架構方不能再亂動_或者不想再動的境地。

    性能優化首先要收集信息_找出瓶頸點_權衡CPU、內存、網絡、、IO等資源_然后盡量的減少平均響應時間_提高吞吐量。

    緩存、緩沖、池化、減少鎖沖突、異步、并行、壓縮_都是常見的優化方式。在我們的這個場景中_起到最大作用的_就是數據壓縮和并行請求。當然_加上其他優化方法的協助_我們的業務接口_由5-6秒的耗時_直接降低到了1秒之內_這個優化效果還是非常可觀的。估計在未來很長一段時間內_都不會再對她進行優化了。


    推薦閱讀_

    1._玩轉Linux
    2._什么味道專輯

    3._藍牙如夢
    4._殺機!
    5._失聯的架構師_只留下一段腳本
    6._架構師寫的BUG_非比尋常

  •  
    (文/企資小編)
    打賞
    免責聲明
    本文為企資小編推薦作品?作者: 企資小編。歡迎轉載,轉載請注明原文出處:http://m.bangpiao.com.cn/news/show-167730.html 。本文僅代表作者個人觀點,本站未對其內容進行核實,請讀者僅做參考,如若文中涉及有違公德、觸犯法律的內容,一經發現,立即刪除,作者需自行承擔相應責任。涉及到版權或其他問題,請及時聯系我們郵件:weilaitui@qq.com。
     

    Copyright ? 2016 - 2023 - 企資網 48903.COM All Rights Reserved 粵公網安備 44030702000589號

    粵ICP備16078936號

    微信

    關注
    微信

    微信二維碼

    WAP二維碼

    客服

    聯系
    客服

    聯系客服:

    在線QQ: 303377504

    客服電話: 020-82301567

    E_mail郵箱: weilaitui@qq.com

    微信公眾號: weishitui

    客服001 客服002 客服003

    工作時間:

    周一至周五: 09:00 - 18:00

    反饋

    用戶
    反饋

    主站蜘蛛池模板: h片在线观看网站 | 岛国午夜精品视频在线观看 | 午夜网站入口 | 欧美 日韩 国产在线 | 国产精品成人第一区 | 特级黄国产片一级视频播放 | 国产精品日本一区二区不卡视频 | 天天射天天添 | 日本人成年视频在线观看 | 欧美一级淫片a免费播放口aaa | 黄色网址亚洲 | 国产视频一区二区在线观看 | 三级经典欧美激情 | 中文字幕第35页 | 永久免费在线视频 | 欧美一级黄色录相 | 国产精品v一区二区三区 | 婷婷久久综合九色综合九七 | 亚洲人成网站在线观看播放动漫 | 亚洲欧美中文v日韩v在线 | 大学生一级毛片全黄真人 | 国内精品久久久久久久久野战 | 亚洲黄视频在线观看 | 在线观看福利影院 | 香蕉1024| 成人精品视频网站 | 日本一区二区三区免费观看 | 最近中文字幕完整国语 | 久久精品大片 | 高清一级做a爱过程不卡视频 | 青草视频在线 | 免费一级毛片视频 | 极品嫩模众筹福利写真视频 | 色综合97天天综合网 | 任你躁在线精品视频m3u8 | 欧美日韩国产码高清综合人成 | 一级特黄特色的免费大片视频 | 视频在线h| 人人叉人人 | 欧美国产日韩另类 | 亚洲激情综合 |