<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          SpringBoot 官方強烈推薦,連接池,太快了!

          共 9187字,需瀏覽 19分鐘

           ·

          2023-01-07 11:39

          大家好,我是寶哥

          大家好,現(xiàn)在介紹一款非常強大,高效,并且號稱“史上最快連接池”。由此可見他是有多受人喜歡,并且在SpringBoot2.0之后,采用的默認數(shù)據(jù)庫連接池就是Hikari

          我們知道的連接池有C3P0,DBCP,它們都比較成熟穩(wěn)定,但性能不是十分好。所以有了BoneCP這個連接池,它是一個高速、免費、開源的JAVA連接池,它的性能幾乎是C3P0、DBCP的25倍,十分強悍

          在我們平常的編碼中,通常會將一些對象保存起來,這主要考慮的是對象的創(chuàng)建成本。

          比如像線程資源、數(shù)據(jù)庫連接資源或者 TCP 連接等,這類對象的初始化通常要花費比較長的時間,如果頻繁地申請和銷毀,就會耗費大量的系統(tǒng)資源,造成不必要的性能損失。

          并且這些對象都有一個顯著的特征,就是通過輕量級的重置工作,可以循環(huán)、重復(fù)地使用。

          這個時候,我們就可以使用一個虛擬的池子,將這些資源保存起來,當使用的時候,我們就從池子里快速獲取一個即可。

          在Java 中,池化技術(shù)應(yīng)用非常廣泛,常見的就有數(shù)據(jù)庫連接池、線程池等,本文主講連接池,線程池我們將在后續(xù)的博客中進行介紹。

          公用池化包 Commons Pool 2

          我們首先來看一下 Java 中公用的池化包 Commons Pool 2,來了解一下對象池的一般結(jié)構(gòu)。

          根據(jù)我們的業(yè)務(wù)需求,使用這套 API 能夠很容易實現(xiàn)對象的池化管理。

                <!--?https://mvnrepository.com/artifact/org.apache.commons/commons-pool2?-->
          <dependency>
          ????<groupId>org.apache.commons</groupId>
          ????<artifactId>commons-pool2</artifactId>
          ????<version>2.11.1</version>
          </dependency>

          GenericObjectPool 是對象池的核心類,通過傳入一個對象池的配置和一個對象的工廠,即可快速創(chuàng)建對象池。

                
                  public?GenericObjectPool(?
          ????????????final?PooledObjectFactory<T>?factory,?
          ????????????final?GenericObjectPoolConfig<T>?config)

          案例

          Redis 的常用客戶端 Jedis,就是使用 Commons Pool 管理連接池的,可以說是一個最佳實踐。下圖是 Jedis 使用工廠創(chuàng)建對象的主要代碼塊。

          對象工廠類最主要的方法就是makeObject,它的返回值是 PooledObject 類型,可以將對象使用 new DefaultPooledObject<>(obj) 進行簡單包裝返回。

          redis.clients.jedis.JedisFactory,使用工廠創(chuàng)建對象。

                @Override
          public?PooledObject<Jedis>?makeObject()?throws?Exception?{
          ??Jedis?jedis?=?null;
          ??try?{
          ????jedis?=?new?Jedis(jedisSocketFactory,?clientConfig);
          ????//主要的耗時操作
          ????jedis.connect();
          ????//返回包裝對象
          ????return?new?DefaultPooledObject<>(jedis);
          ??}?catch?(JedisException?je)?{
          ????if?(jedis?!=?null)?{
          ??????try?{
          ????????jedis.quit();
          ??????}?catch?(RuntimeException?e)?{
          ????????logger.warn("Error?while?QUIT",?e);
          ??????}
          ??????try?{
          ????????jedis.close();
          ??????}?catch?(RuntimeException?e)?{
          ????????logger.warn("Error?while?close",?e);
          ??????}
          ????}
          ????throw?je;
          ??}
          }

          我們再來介紹一下對象的生成過程,如下圖,對象在進行獲取時,將首先嘗試從對象池里拿出一個,如果對象池中沒有空閑的對象,就使用工廠類提供的方法,生成一個新的。

                public?T?borrowObject(final?Duration?borrowMaxWaitDuration)?throws?Exception?{
          ????//此處省略若干行
          ????while?(p?==?null)?{
          ????????create?=?false;
          ????????//首先嘗試從池子中獲取。
          ????????p?=?idleObjects.pollFirst();
          ????????//?池子里獲取不到,才調(diào)用工廠內(nèi)生成新實例
          ????????if?(p?==?null)?{
          ????????????p?=?create();
          ????????????if?(p?!=?null)?{
          ????????????????create?=?true;
          ????????????}
          ????????}
          ????????//此處省略若干行
          ????}
          ????//此處省略若干行
          }

          那對象是存在什么地方的呢?這個存儲的職責,就是由一個叫作 LinkedBlockingDeque 的結(jié)構(gòu)來承擔的,它是一個雙向的隊列。

          接下來看一下 GenericObjectPoolConfig 的主要屬性:

                //?GenericObjectPoolConfig本身的屬性
          private?int?maxTotal?=?DEFAULT_MAX_TOTAL;
          private?int?maxIdle?=?DEFAULT_MAX_IDLE;
          private?int?minIdle?=?DEFAULT_MIN_IDLE;
          //?其父類BaseObjectPoolConfig的屬性
          private?boolean?lifo?=?DEFAULT_LIFO;
          private?boolean?fairness?=?DEFAULT_FAIRNESS;
          private?long?maxWaitMillis?=?DEFAULT_MAX_WAIT_MILLIS;
          private?long?minEvictableIdleTimeMillis?=?DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
          private?long?evictorShutdownTimeoutMillis?=?DEFAULT_EVICTOR_SHUTDOWN_TIMEOUT_MILLIS;
          private?long?softMinEvictableIdleTimeMillis?=?DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
          private?int?numTestsPerEvictionRun?=?DEFAULT_NUM_TESTS_PER_EVICTION_RUN;
          private?EvictionPolicy<T>?evictionPolicy?=?null;?
          //?Only?2.6.0?applications?set?this?
          private?String?evictionPolicyClassName?=?DEFAULT_EVICTION_POLICY_CLASS_NAME;
          private?boolean?testOnCreate?=?DEFAULT_TEST_ON_CREATE;
          private?boolean?testOnBorrow?=?DEFAULT_TEST_ON_BORROW;
          private?boolean?testOnReturn?=?DEFAULT_TEST_ON_RETURN;
          private?boolean?testWhileIdle?=?DEFAULT_TEST_WHILE_IDLE;
          private?long?timeBetweenEvictionRunsMillis?=?DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
          private?boolean?blockWhenExhausted?=?DEFAULT_BLOCK_WHEN_EXHAUSTED;

          參數(shù)很多,要想了解參數(shù)的意義,我們首先來看一下一個池化對象在整個池子中的生命周期。

          如下圖所示,池子的操作主要有兩個:一個是業(yè)務(wù)線程,一個是檢測線程。7e4e053d1eee6442146bdb91003d9359.webp

          對象池在進行初始化時,要指定三個主要的參數(shù):

          • maxTotal 對象池中管理的對象上限
          • maxIdle 最大空閑數(shù)
          • minIdle 最小空閑數(shù)

          其中maxTotal 和業(yè)務(wù)線程有關(guān),當業(yè)務(wù)線程想要獲取對象時,會首先檢測是否有空閑的對象。

          如果有,則返回一個;否則進入創(chuàng)建邏輯。此時,如果池中個數(shù)已經(jīng)達到了最大值,就會創(chuàng)建失敗,返回空對象。

          對象在獲取的時候,有一個非常重要的參數(shù),那就是最大等待時間(maxWaitMillis),這個參數(shù)對應(yīng)用方的性能影響是比較大的。該參數(shù)默認為 -1,表示永不超時,直到有對象空閑。

          如下圖,如果對象創(chuàng)建非常緩慢或者使用非常繁忙,業(yè)務(wù)線程會持續(xù)阻塞 (blockWhenExhausted 默認為 true),進而導(dǎo)致正常服務(wù)也不能運行。

          面試題

          一般面試官會問:你會把超時參數(shù)設(shè)置成多大呢?我一般都會把最大等待時間,設(shè)置成接口可以忍受的最大延遲。

          比如,一個正常服務(wù)響應(yīng)時間 10ms 左右,達到 1 秒鐘就會感覺到卡頓,那么這個參數(shù)設(shè)置成 500~1000ms 都是可以的。

          超時之后,會拋出 NoSuchElementException 異常,請求會快速失敗,不會影響其他業(yè)務(wù)線程,這種 Fail Fast 的思想,在互聯(lián)網(wǎng)應(yīng)用非常廣泛。

          帶有evcit 字樣的參數(shù),主要是處理對象逐出的。池化對象除了初始化和銷毀的時候比較昂貴,在運行時也會占用系統(tǒng)資源。

          比如,連接池會占用多條連接,線程池會增加調(diào)度開銷等。業(yè)務(wù)在突發(fā)流量下,會申請到超出正常情況的對象資源,放在池子中。等這些對象不再被使用,我們就需要把它清理掉。

          超出minEvictableIdleTimeMillis 參數(shù)指定值的對象,就會被強制回收掉,這個值默認是 30 分鐘;softMinEvictableIdleTimeMillis 參數(shù)類似,但它只有在當前對象數(shù)量大于 minIdle 的時候才會執(zhí)行移除,所以前者的動作要更暴力一些。

          還有4 個 test 參數(shù):testOnCreate、testOnBorrow、testOnReturn、testWhileIdle,分別指定了在創(chuàng)建、獲取、歸還、空閑檢測的時候,是否對池化對象進行有效性檢測。

          開啟這些檢測,能保證資源的有效性,但它會耗費性能,所以默認為 false。

          生產(chǎn)環(huán)境上,建議只將 testWhileIdle 設(shè)置為 true,并通過調(diào)整空閑檢測時間間隔(timeBetweenEvictionRunsMillis),比如 1 分鐘,來保證資源的可用性,同時也保證效率。

          JMH 測試

          使用連接池和不使用連接池,它們之間的性能差距到底有多大呢?

          下面是一個簡單的 JMH 測試例子(見倉庫),進行一個簡單的 set 操作,為 redis 的 key 設(shè)置一個隨機值。

                @Fork(2)?
          @State(Scope.Benchmark)?
          @Warmup(iterations?=?5,?time?=?1)?
          @Measurement(iterations?=?5,?time?=?1)?
          @BenchmarkMode(Mode.Throughput)?
          public?class?JedisPoolVSJedisBenchmark?{?
          ???JedisPool?pool?=?new?JedisPool("localhost",?6379);?

          ???@Benchmark?
          ???public?void?testPool()?{?
          ???????Jedis?jedis?=?pool.getResource();?
          ???????jedis.set("a",?UUID.randomUUID().toString());?
          ???????jedis.close();?
          ???}?

          ???@Benchmark?
          ???public?void?testJedis()?{?
          ???????Jedis?jedis?=?new?Jedis("localhost",?6379);?
          ???????jedis.set("a",?UUID.randomUUID().toString());?
          ???????jedis.close();?
          ???}?
          ???//此處省略若干行
          }

          將測試結(jié)果使用 meta-chart 作圖,展示結(jié)果如下圖所示,可以看到使用了連接池的方式,它的吞吐量是未使用連接池方式的 5 倍!28b0f27fe36773fe55953d2af9740f70.webp

          數(shù)據(jù)庫連接池 HikariCP

          HikariCP 源于日語“光る”,光的意思,寓意軟件工作速度和光速一樣快,它是 SpringBoot 中默認的數(shù)據(jù)庫連接池。

          數(shù)據(jù)庫是我們工作中經(jīng)常使用到的組件,針對數(shù)據(jù)庫設(shè)計的客戶端連接池是非常多的,它的設(shè)計原理與我們在本文開頭提到的基本一致,可以有效地減少數(shù)據(jù)庫連接創(chuàng)建、銷毀的資源消耗。

          同是連接池,它們的性能也是有差別的,下圖是 HikariCP 官方的一張測試圖,可以看到它優(yōu)異的性能,官方的 JMH 測試代碼見 Github。

          9addae7809c30da90e6b4b9eb547aec2.webp

          一般面試題是這么問的:HikariCP 為什么快呢?

          主要有三個方面:

          • 它使用 FastList 替代 ArrayList,通過初始化的默認值,減少了越界檢查的操作
          • 優(yōu)化并精簡了字節(jié)碼,通過使用 Javassist,減少了動態(tài)代理的性能損耗,比如使用 invokestatic 指令代替 invokevirtual 指令
          • 實現(xiàn)了無鎖的 ConcurrentBag,減少了并發(fā)場景下的鎖競爭

          HikariCP 對性能的一些優(yōu)化操作,是非常值得我們借鑒的,在之后的博客中,我們將詳細分析幾個優(yōu)化場景。

          數(shù)據(jù)庫連接池同樣面臨一個最大值(maximumPoolSize)和最小值(minimumIdle)的問題。這里同樣有一個非常高頻的面試題:你平常會把連接池設(shè)置成多大呢?

          很多同學認為,連接池的大小設(shè)置得越大越好,有的同學甚至把這個值設(shè)置成 1000 以上,這是一種誤解。

          根據(jù)經(jīng)驗,數(shù)據(jù)庫連接,只需要 20~50 個就夠用了。具體的大小,要根據(jù)業(yè)務(wù)屬性進行調(diào)整,但大得離譜肯定是不合適的。

          HikariCP 官方是不推薦設(shè)置 minimumIdle 這個值的,它將被默認設(shè)置成和 maximumPoolSize 一樣的大小。如果你的數(shù)據(jù)庫Server端連接資源空閑較大,不妨也可以去掉連接池的動態(tài)調(diào)整功能。

          另外,根據(jù)數(shù)據(jù)庫查詢和事務(wù)類型,一個應(yīng)用中是可以配置多個數(shù)據(jù)庫連接池的,這個優(yōu)化技巧很少有人知道,在此簡要描述一下。

          業(yè)務(wù)類型通常有兩種:一種需要快速的響應(yīng)時間,把數(shù)據(jù)盡快返回給用戶;另外一種是可以在后臺慢慢執(zhí)行,耗時比較長,對時效性要求不高。

          如果這兩種業(yè)務(wù)類型,共用一個數(shù)據(jù)庫連接池,就容易發(fā)生資源爭搶,進而影響接口響應(yīng)速度。

          雖然微服務(wù)能夠解決這種情況,但大多數(shù)服務(wù)是沒有這種條件的,這時就可以對連接池進行拆分。

          如圖,在同一個業(yè)務(wù)中,根據(jù)業(yè)務(wù)的屬性,我們分了兩個連接池,就是來處理這種情況的。c72e2026df54d0df7c50a3e9552c83be.webp

          HikariCP 還提到了另外一個知識點,在 JDBC4 的協(xié)議中,通過 Connection.isValid() 就可以檢測連接的有效性。

          這樣,我們就不用設(shè)置一大堆的 test 參數(shù)了,HikariCP 也沒有提供這樣的參數(shù)。

          結(jié)果緩存池

          到了這里你可能會發(fā)現(xiàn)池(Pool)與緩存(Cache)有許多相似之處。

          它們之間的一個共同點,就是將對象加工后,存儲在相對高速的區(qū)域。我習慣性將緩存看作是數(shù)據(jù)對象,而把池中的對象看作是執(zhí)行對象。緩存中的數(shù)據(jù)有一個命中率問題,而池中的對象一般都是對等的。

          考慮下面一個場景,jsp 提供了網(wǎng)頁的動態(tài)功能,它可以在執(zhí)行后,編譯成 class 文件,加快執(zhí)行速度;再或者,一些媒體平臺,會將熱門文章,定時轉(zhuǎn)化成靜態(tài)的 html 頁面,僅靠 nginx 的負載均衡即可應(yīng)對高并發(fā)請求(動靜分離)。

          這些時候,你很難說清楚,這是針對緩存的優(yōu)化,還是針對對象進行了池化,它們在本質(zhì)上只是保存了某個執(zhí)行步驟的結(jié)果,使得下次訪問時不需要從頭再來。

          我通常把這種技術(shù)叫作結(jié)果緩存池(Result Cache Pool),屬于多種優(yōu)化手段的綜合。

          小結(jié)

          下面我來簡單總結(jié)一下本文的內(nèi)容重點:我們從 Java 中最通用的公用池化包 Commons Pool 2 說起,介紹了它的一些實現(xiàn)細節(jié),并對一些重要參數(shù)的應(yīng)用做了講解。

          Jedis 就是在 Commons Pool 2 的基礎(chǔ)上封裝的,通過 JMH 測試,我們發(fā)現(xiàn)對象池化之后,有了接近 5 倍的性能提升。

          接下來介紹了數(shù)據(jù)庫連接池中速度很快的 HikariCP ,它在池化技術(shù)之上,又通過編碼技巧進行了進一步的性能提升,HikariCP 是我重點研究的類庫之一,我也建議你加入自己的任務(wù)清單中。

          總體來說,當你遇到下面的場景,就可以考慮使用池化來增加系統(tǒng)性能:

          • 對象的創(chuàng)建或者銷毀,需要耗費較多的系統(tǒng)資源
          • 對象的創(chuàng)建或者銷毀,耗時長,需要繁雜的操作和較長時間的等待
          • 對象創(chuàng)建后,通過一些狀態(tài)重置,可被反復(fù)使用

          將對象池化之后,只是開啟了第一步優(yōu)化。要想達到最優(yōu)性能,就不得不調(diào)整池的一些關(guān)鍵參數(shù),合理的池大小加上合理的超時時間,就可以讓池發(fā)揮更大的價值。和緩存的命中率類似,對池的監(jiān)控也是非常重要的。

          如下圖,可以看到數(shù)據(jù)庫連接池連接數(shù)長時間保持在高位不釋放,同時等待的線程數(shù)急劇增加,這就能幫我們快速定位到數(shù)據(jù)庫的事務(wù)問題。931b982903851cd7269d395a8911905b.webp

          平常的編碼中,有很多類似的場景。比如 Http 連接池,Okhttp 和 Httpclient 就都提供了連接池的概念,你可以類比著去分析一下,關(guān)注點也是在連接大小和超時時間上。

          在底層的中間件,比如 RPC,也通常使用連接池技術(shù)加速資源獲取,比如 Dubbo 連接池、 Feign 切換成 httppclient 的實現(xiàn)等技術(shù)。

          你會發(fā)現(xiàn),在不同資源層面的池化設(shè)計也是類似的。比如線程池,通過隊列對任務(wù)進行了二層緩沖,提供了多樣的拒絕策略等,線程池我們將在后續(xù)的文章中進行介紹。

          線程池的這些特性,你同樣可以借鑒到連接池技術(shù)中,用來緩解請求溢出,創(chuàng)建一些溢出策略。

          現(xiàn)實情況中,我們也會這么做。那么具體怎么做?有哪些做法?這部分內(nèi)容就留給大家思考了。

          來源:網(wǎng)絡(luò)


                  

          往期推薦:

          4.2k Star,這是我見過的最炫酷的開源監(jiān)控系統(tǒng),逼格很高!!!

          這是我見過最強橫的后臺管理系統(tǒng) !!

          Spring Boot 實現(xiàn)萬能文件在線預(yù)覽,已開源,真香!!

          放假了,在家寫一個微信小程序(前端+Java后端)

          微服務(wù)網(wǎng)關(guān)鑒權(quán):gateway使用、網(wǎng)關(guān)限流使用、用戶密碼加密、JWT鑒權(quán)

          瀏覽 51
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天堂aaa| 九热国产视频 | 丁香综合色 | 女学生中国一级毛片 | 国产美女被操 |