1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        SpringBoot 性能這樣優(yōu)化,同事直呼哇塞!

        共 9265字,需瀏覽 19分鐘

         ·

        2022-03-06 04:42


        SpringBoot已經(jīng)成為Java屆的No.1框架,每天都在蹂躪著數(shù)百萬的程序員們。當服務的壓力上升,對SpringBoot服務的優(yōu)化就會被提上議程。

        本文將詳細講解SpringBoot服務優(yōu)化的一般思路,并附上若干篇輔助文章作為開胃菜。

        本文較長,最適合收藏之。

        1.有監(jiān)控才有方向

        在開始對SpringBoot服務進行性能優(yōu)化之前,我們需要做一些準備,把SpringBoot服務的一些數(shù)據(jù)暴露出來。

        比如,你的服務用到了緩存,就需要把緩存命中率這些數(shù)據(jù)進行收集;用到了數(shù)據(jù)庫連接池,就需要把連接池的參數(shù)給暴露出來。

        我們這里采用的監(jiān)控工具是Prometheus,它是一個是時序數(shù)據(jù)庫,能夠存儲我們的指標。SpringBoot可以非常方便的接入到Prometheus中。

        創(chuàng)建一個SpringBoot項目后,首先,加入maven依賴。

        <dependency>
        ?????<groupId>org.springframework.bootgroupId>
        ?????<artifactId>spring-boot-starter-actuatorartifactId>
        ?dependency>
        ?<dependency>
        ?????<groupId>io.micrometergroupId>
        ?????<artifactId>micrometer-registry-prometheusartifactId>
        ?dependency>
        ?<dependency>
        ?????<groupId>io.micrometergroupId>
        ?????<artifactId>micrometer-coreartifactId>
        ?dependency>

        然后,我們需要在application.properties配置文件中,開放相關(guān)的監(jiān)控接口。

        management.endpoint.metrics.enabled=true
        management.endpoints.web.exposure.include=*
        management.endpoint.prometheus.enabled=true
        management.metrics.export.prometheus.enabled=true

        啟動之后,我們就可以通過訪問 http://localhost:8080/actuator/prometheus 來獲取監(jiān)控數(shù)據(jù)。

        想要監(jiān)控業(yè)務數(shù)據(jù)也是比較簡單的。你只需要注入一個MeterRegistry實例即可。下面是一段示例代碼:

        @Autowired
        MeterRegistry?registry;

        @GetMapping("/test")
        @ResponseBody
        public?String?test()?{
        ????registry.counter("test",
        ????????????"from",?"127.0.0.1",
        ????????????"method",?"test"
        ????).increment();

        ????return?"ok";
        }

        從監(jiān)控連接中,我們可以找到剛剛添加的監(jiān)控信息。

        test_total{from="127.0.0.1",method="test",}?5.0

        這里簡單介紹一下流行的Prometheus監(jiān)控體系,Prometheus使用的方式獲取監(jiān)控數(shù)據(jù),這個暴露數(shù)據(jù)的過程可以交給功能更加齊全的telegraf組件。

        如圖,我們通常使用Grafana進行監(jiān)控數(shù)據(jù)的展示,使用AlertManager組件進行提前預警。這一部分的搭建工作不是我們的重點,感興趣的同學可自行研究。下圖便是一張典型的監(jiān)控圖,可以看到Redis的緩存命中率等情況。

        2.Java生成火焰圖

        火焰圖是用來分析程序運行瓶頸的工具。在縱向,表示的是調(diào)用棧的深度;橫向表明的是消耗的時間。所以格子的寬度越大,越說明它可能是一個瓶頸。

        火焰圖也可以用來分析Java應用??梢詮膅ithub上下載async-profiler的壓縮包 進行相關(guān)操作。

        比如,我們把它解壓到/root/目錄。然后以javaagent的方式來啟動Java應用。命令行如下:

        java?-agentpath:/root/build/libasyncProfiler.so=start,svg,file=profile.svg?-jar?spring-petclinic-2.3.1.BUILD-SNAPSHOT.jar

        運行一段時間后,停止進程,可以看到在當前目錄下,生成了profile.svg文件,這個文件是可以用瀏覽器打開的,一層層向下瀏覽,即可找到需要優(yōu)化的目標。

        3.Skywalking

        對于一個web服務來說,最緩慢的地方就在于數(shù)據(jù)庫操作。所以,使用本地緩存和分布式緩存優(yōu)化,能夠獲得最大的性能提升。

        對于如何定位到復雜分布式環(huán)境中的問題,我這里想要分享另外一個工具:Skywalking

        Skywalking是使用探針技術(shù)(JavaAgent)來實現(xiàn)的。通過在Java的啟動參數(shù)中,加入javaagent的Jar包,即可將性能數(shù)據(jù)和調(diào)用鏈數(shù)據(jù)封裝、發(fā)送到Skywalking的服務器。

        下載相應的安裝包(如果使用ES存儲,需要下載專用的安裝包),配置好存儲之后,即可一鍵啟動。

        將agent的壓縮包,解壓到相應的目錄。

        tar?xvf?skywalking-agent.tar.gz??-C?/opt/

        在業(yè)務啟動參數(shù)中加入agent的包。比如,原來的啟動命令是:

        java??-jar?/opt/test-service/spring-boot-demo.jar??--spring.profiles.active=dev

        改造后的啟動命令是:

        java?-javaagent:/opt/skywalking-agent/skywalking-agent.jar?-Dskywalking.agent.service_name=the-demo-name??-jar?/opt/test-service/spring-boot-demo.ja??--spring.profiles.active=dev

        訪問一些服務的鏈接,打開Skywalking的UI,即可看到下圖的界面。我們可以從圖中找到響應比較慢QPS又比較高的的接口,進行專項優(yōu)化。

        15723404104715

        4.優(yōu)化思路

        對一個普通的Web服務來說,我們來看一下,要訪問到具體的數(shù)據(jù),都要經(jīng)歷哪些主要的環(huán)節(jié)。

        如下圖,在瀏覽器中輸入相應的域名,需要通過DNS解析到具體的IP地址上。為了保證高可用,我們的服務一般都會部署多份,然后使用Nginx做反向代理和負載均衡。

        Nginx根據(jù)資源的特性,會承擔一部分動靜分離的功能。其中,動態(tài)功能部分,會進入我們的SpringBoot服務。

        SpringBoot默認使用內(nèi)嵌的tomcat作為Web容器,使用典型的MVC模式,最終訪問到我們的數(shù)據(jù)。

        5.HTTP優(yōu)化

        下面我們舉例來看一下,哪些動作能夠加快網(wǎng)頁的獲取。為了描述方便,我們僅討論HTTP1.1協(xié)議的。

        1.使用CDN加速文件獲取

        比較大的文件,盡量使用CDN(Content Delivery Network)分發(fā)。甚至是一些常用的前端腳本、樣式、圖片等,都可以放到CDN上。CDN通常能夠加快這些文件的獲取,網(wǎng)頁加載也更加迅速。

        2.合理設置Cache-Control值

        瀏覽器會判斷HTTP頭Cache-Control的內(nèi)容,用來決定是否使用瀏覽器緩存,這在管理一些靜態(tài)文件的時候,非常有用。相同作用的頭信息還有Expires。Cache-Control表示多久之后過期,Expires則表示什么時候過期。

        這個參數(shù)可以在Nginx的配置文件中進行設置。

        location ~* ^.+\.(ico|gif|jpg|jpeg|png)$ { 
        # 緩存1年
        add_header Cache-Control: no-cache, max-age=31536000;
        }

        3.減少單頁面請求域名的數(shù)量

        減少每個頁面請求的域名數(shù)量,盡量保證在4個之內(nèi)。這是因為,瀏覽器每次訪問后端的資源,都需要先查詢一次DNS,然后找到DNS對應的IP地址,再進行真正的調(diào)用。

        DNS有多層緩存,比如瀏覽器會緩存一份、本地主機會緩存、ISP服務商緩存等。從DNS到IP地址的轉(zhuǎn)變,通常會花費20-120ms的時間。減少域名的數(shù)量,可加快資源的獲取。

        4.開啟gzip

        開啟gzip,可以先把內(nèi)容壓縮后,瀏覽器再進行解壓。由于減少了傳輸?shù)拇笮?,會減少帶寬的使用,提高傳輸效率。

        在nginx中可以很容易的開啟。配置如下:

        gzip on;
        gzip_min_length 1k;
        gzip_buffers 4 16k;
        gzip_comp_level 6;
        gzip_http_version 1.1;
        gzip_types text/plain application/javascript text/css;

        5.對資源進行壓縮

        對JavaScript和CSS,甚至是HTML進行壓縮。道理類似,現(xiàn)在流行的前后端分離模式,一般都是對這些資源進行壓縮的。

        6.使用keepalive

        由于連接的創(chuàng)建和關(guān)閉,都需要耗費資源。用戶訪問我們的服務后,后續(xù)也會有更多的互動,所以保持長連接可以顯著減少網(wǎng)絡交互,提高性能。

        nginx默認開啟了對客戶端的keep avlide支持。你可以通過下面兩個參數(shù)來調(diào)整它的行為。

        http {
        keepalive_timeout 120s 120s;
        keepalive_requests 10000;
        }

        nginx與后端upstream的長連接,需要手工開啟,參考配置如下:

        location ~ /{ 
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        }

        6.Tomcat優(yōu)化

        Tomcat本身的優(yōu)化,也是非常重要的一環(huán)??梢灾苯訁⒖枷旅娴奈恼隆?/p>

        搞定tomcat重要參數(shù)調(diào)優(yōu)!

        7.自定義Web容器

        如果你的項目并發(fā)量比較高,想要修改最大線程數(shù)、最大連接數(shù)等配置信息,可以通過自定義Web容器的方式,代碼如下所示。

        @SpringBootApplication(proxyBeanMethods?=?false)
        public?class?App?implements?WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>?{
        ?public?static?void?main(String[]?args)?{
        ??SpringApplication.run(PetClinicApplication.class,?args);
        ?}
        ?@Override
        ?public?void?customize(ConfigurableServletWebServerFactory?factory)?{
        ??TomcatServletWebServerFactory?f?=?(TomcatServletWebServerFactory)?factory;
        ????????f.setProtocol("org.apache.coyote.http11.Http11Nio2Protocol");

        ??f.addConnectorCustomizers(c?->?{
        ???Http11NioProtocol?protocol?=?(Http11NioProtocol)?c.getProtocolHandler();
        ???protocol.setMaxConnections(200);
        ???protocol.setMaxThreads(200);
        ???protocol.setSelectorTimeout(3000);
        ???protocol.setSessionTimeout(3000);
        ???protocol.setConnectionTimeout(3000);
        ??});
        ?}
        }

        注意上面的代碼,我們設置了它的協(xié)議為org.apache.coyote.http11.Http11Nio2Protocol,意思就是開啟了Nio2。這個參數(shù)在Tomcat8.0之后才有,開啟之后會增加一部分性能。對比如下:

        默認。

        [root@localhost?wrk2-master]#?./wrk?-t2?-c100?-d30s?-R2000?http://172.16.1.57:8080/owners?lastName=
        Running?30s?test?@?http://172.16.1.57:8080/owners?lastName=
        ??2?threads?and?100?connections
        ??Thread?calibration:?mean?lat.:?4588.131ms,?rate?sampling?interval:?16277ms
        ??Thread?calibration:?mean?lat.:?4647.927ms,?rate?sampling?interval:?16285ms
        ??Thread?Stats???Avg??????Stdev?????Max???+/-?Stdev
        ????Latency????16.49s?????4.98s???27.34s????63.90%
        ????Req/Sec???106.50??????1.50???108.00????100.00%
        ??6471?requests?in?30.03s,?39.31MB?read
        ??Socket?errors:?connect?0,?read?0,?write?0,?timeout?60
        Requests/sec:????215.51
        Transfer/sec:??????1.31MB

        Nio2。

        [root@localhost?wrk2-master]#?./wrk?-t2?-c100?-d30s?-R2000?http://172.16.1.57:8080/owners?lastName=
        Running?30s?test?@?http://172.16.1.57:8080/owners?lastName=
        ??2?threads?and?100?connections
        ??Thread?calibration:?mean?lat.:?4358.805ms,?rate?sampling?interval:?15835ms
        ??Thread?calibration:?mean?lat.:?4622.087ms,?rate?sampling?interval:?16293ms
        ??Thread?Stats???Avg??????Stdev?????Max???+/-?Stdev
        ????Latency????17.47s?????4.98s???26.90s????57.69%
        ????Req/Sec???125.50??????2.50???128.00????100.00%
        ??7469?requests?in?30.04s,?45.38MB?read
        ??Socket?errors:?connect?0,?read?0,?write?0,?timeout?4
        Requests/sec:????248.64
        Transfer/sec:??????1.51MB

        你甚至可以將tomcat替換成undertow。undertow也是一個Web容器,更加輕量級一些,占用的內(nèi)容更少,啟動的守護進程也更少,更改方式如下:

        <dependency>
        ??????<groupId>org.springframework.bootgroupId>
        ??????<artifactId>spring-boot-starter-webartifactId>
        ??????<exclusions>
        ????????<exclusion>
        ??????????<groupId>org.springframework.bootgroupId>
        ??????????<artifactId>spring-boot-starter-tomcatartifactId>
        ????????exclusion>
        ??????exclusions>
        ????dependency>
        ????<dependency>
        ??????<groupId>org.springframework.bootgroupId>
        ??????<artifactId>spring-boot-starter-undertowartifactId>
        ????dependency>

        8.各個層次的優(yōu)化方向

        Controller層

        controller層用于接收前端的查詢參數(shù),然后構(gòu)造查詢結(jié)果?,F(xiàn)在很多項目都采用前后端分離的架構(gòu),所以controller層的方法,一般會使用@ResponseBody注解,把查詢的結(jié)果,解析成JSON數(shù)據(jù)返回(兼顧效率和可讀性)。

        由于controller只是充當了一個類似功能組合和路由的角色,所以這部分對性能的影響就主要體現(xiàn)在數(shù)據(jù)集的大小上。如果結(jié)果集合非常大,JSON解析組件就要花費較多的時間進行解析。

        大結(jié)果集不僅會影響解析時間,還會造成內(nèi)存浪費。假如結(jié)果集在解析成JSON之前,占用的內(nèi)存是10MB,那么在解析過程中,有可能會使用20M或者更多的內(nèi)存去做這個工作。我見過很多案例,由于返回對象的嵌套層次太深、引用了不該引用的對象(比如非常大的byte[]對象),造成了內(nèi)存使用的飆升。

        所以,對于一般的服務,保持結(jié)果集的精簡,是非常有必要的,這也是DTO(data transfer object)存在的必要。如果你的項目,返回的結(jié)果結(jié)構(gòu)比較復雜,對結(jié)果集進行一次轉(zhuǎn)換是非常有必要的。

        另外,可以使用異步Servlet對Controller層進行優(yōu)化。它的原理如下:Servlet 接收到請求之后,將請求轉(zhuǎn)交給一個異步線程來執(zhí)行業(yè)務處理,線程本身返回至容器,異步線程處理完業(yè)務以后,可以直接生成響應數(shù)據(jù),或者將請求繼續(xù)轉(zhuǎn)發(fā)給其它 Servlet。

        Service層

        service層用于處理具體的業(yè)務,大部分功能需求都是在這里完成的。service層一般是使用單例模式(prototype),很少會保存狀態(tài),而且可以被controller復用。

        service層的代碼組織,對代碼的可讀性、性能影響都比較大。我們常說的設計模式,大多數(shù)都是針對于service層來說的。

        這里要著重提到的一點,就是分布式事務。

        如上圖,四個操作分散在三個不同的資源中。要想達到一致性,需要三個不同的資源進行統(tǒng)一協(xié)調(diào)。它們底層的協(xié)議,以及實現(xiàn)方式,都是不一樣的。那就無法通過Spring提供的Transaction注解來解決,需要借助外部的組件來完成。

        很多人都體驗過,加入了一些保證一致性的代碼,一壓測,性能掉的驚掉下巴。分布式事務是性能殺手,因為它要使用額外的步驟去保證一致性,常用的方法有:兩階段提交方案、TCC、本地消息表、MQ事務消息、分布式事務中間件等。

        如上圖,分布式事務要在改造成本、性能、實效等方面進行綜合考慮。有一個介于分布式事務和非事務之間的名詞,叫做柔性事務。柔性事務的理念是將業(yè)務邏輯和互斥操作,從資源層上移至業(yè)務層面。

        關(guān)于傳統(tǒng)事務和柔性事務,我們來簡單比較一下。

        ACID

        關(guān)系數(shù)據(jù)庫, 最大的特點就是事務處理, 即滿足ACID。

        • 原子性(Atomicity):事務中的操作要么都做,要么都不做。
        • 一致性(Consistency):系統(tǒng)必須始終處在強一致狀態(tài)下。
        • 隔離性(Isolation):一個事務的執(zhí)行不能被其他事務所干擾。
        • 持續(xù)性(Durability):一個已提交的事務對數(shù)據(jù)庫中數(shù)據(jù)的改變是永久性的。

        BASE

        BASE方法通過犧牲一致性和孤立性來提高可用性和系統(tǒng)性能。

        BASE為Basically Available, Soft-state, Eventually consistent三者的縮寫,其中BASE分別代表:

        • 基本可用(Basically Available):系統(tǒng)能夠基本運行、一直提供服務。
        • 軟狀態(tài)(Soft-state):系統(tǒng)不要求一直保持強一致狀態(tài)。
        • 最終一致性(Eventual consistency):系統(tǒng)需要在某一時刻后達到一致性要求。

        互聯(lián)網(wǎng)業(yè)務,推薦使用補償事務,完成最終一致性。比如,通過一系列的定時任務,完成對數(shù)據(jù)的修復。具體可以參照下面的文章。

        常用的 分布式事務 都有哪些?我該用哪個?

        Dao層

        經(jīng)過合理的數(shù)據(jù)緩存,我們都會盡量避免請求穿透到Dao層。除非你對ORM本身提供的緩存特性特別的熟悉,否則,都推薦你使用更加通用的方式去緩存數(shù)據(jù)。

        Dao層,主要在于對ORM框架的使用上。比如,在JPA中,如果加了一對多或者多對多的映射關(guān)系,而又沒有開啟懶加載,級聯(lián)查詢的時候就容易造成深層次的檢索,造成了內(nèi)存開銷大、執(zhí)行緩慢的后果。

        在一些數(shù)據(jù)量比較大的業(yè)務中,多采用分庫分表的方式。在這些分庫分表組件中,很多簡單的查詢語句,都會被重新解析后分散到各個節(jié)點進行運算,最后進行結(jié)果合并。

        舉個例子,select count(*) from a這句簡單的count語句,就可能將請求路由到十幾張表中去運算,最后在協(xié)調(diào)節(jié)點進行統(tǒng)計,執(zhí)行效率是可想而知的。目前,分庫分表中間件,比較有代表性的是驅(qū)動層的ShardingJdbc和代理層的MyCat,它們都有這樣的問題。這些組件提供給使用者的視圖是一致的,但我們在編碼的時候,一定要注意這些區(qū)別。

        End

        下面我們來總結(jié)一下。

        我們簡單看了一下SpringBoot常見的優(yōu)化思路。我們介紹了三個新的性能分析工具。一個是監(jiān)控系統(tǒng)Prometheus,可以看到一些具體的指標大??;一個是火焰圖,可以看到具體的代碼熱點;一個是Skywalking,可以分析分布式環(huán)境中的調(diào)用鏈。在對性能有疑惑的時候,我們都會采用類似于神農(nóng)氏嘗百草的方式,綜合各種測評工具的結(jié)果進行分析。

        SpringBoot自身的Web容器是Tomcat,那我們就可以通過對Tomcat的調(diào)優(yōu)來獲取性能提升。當然,對于服務上層的負載均衡Nginx,我們也提供了一系列的優(yōu)化思路。

        最后,我們看了在經(jīng)典的MVC架構(gòu)下,Controller、Service、Dao的一些優(yōu)化方向,并著重看了Service層的分布式事務問題。

        SpringBoot作為一個廣泛應用的服務框架,在性能優(yōu)化方面已經(jīng)做了很多工作,選用了很多高速組件。比如,數(shù)據(jù)庫連接池默認使用hikaricp,Redis緩存框架默認使用lettuce,本地緩存提供caffeine等。對于一個普通的于數(shù)據(jù)庫交互的Web服務來說,緩存是最主要的優(yōu)化手。

        完!

        程序汪資料鏈接

        程序汪接的7個私活都在這里,經(jīng)驗整理

        Java項目分享 最新整理全集,找項目不累啦 06版

        堪稱神級的Spring Boot手冊,從基礎(chǔ)入門到實戰(zhàn)進階

        臥槽!字節(jié)跳動《算法中文手冊》火了,完整版 PDF 開放下載!

        臥槽!阿里大佬總結(jié)的《圖解Java》火了,完整版PDF開放下載!

        字節(jié)跳動總結(jié)的設計模式 PDF 火了,完整版開放下載!


        歡迎添加程序汪個人微信 itwang009? 進粉絲群或圍觀朋友圈


        瀏覽 43
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            国产精品一哟哟哟 | 人妻大香蕉 | 西欧黄色片 | 二次元全身裸露无遮挡 | 免费黄色一级电影 | 俄罗斯色片 | 黑丝操逼| 亚洲AV秘 无码一区小凑四叶 | 美女航空一级毛片高清 | 无码熟妇人妻无码AV在线天堂 |