天天看點

如何快速實作一個連接配接池?

文章首發于公衆号「架構師指南」及個人部落格 ​​shuyi.tech​​,歡迎關注通路。

如何快速實作一個連接配接池?

在實際工作中,我們經常會用到各種連接配接池,例如:連接配接 FTP 伺服器的連接配接數有限,需要建立一個連接配接池;連接配接資料庫的連接配接數有限,需要建立一個連接配接池。那我們如何去快速實作一個連接配接池呢?

無論是 FTP 連接配接池,還是資料庫連接配接池,我們會發現它們都有相同的地方,它們都需要:生命周期管理、連接配接建立管理等等。如果我們從零開始去實作這些功能,那我們要耗費的時間就很長了!那有沒有一個通用的庫可以快速實作一個線程池呢?

得益于 Java 完善的生态,前人們針對這種需要開發了一個通用庫:Apache Commons Pool(下文簡稱 ACP)。本質上來說,ACP 庫提供的是管理對象池的通用能力,當然也可以用來管理連接配接池了!

ACP 庫提供了一整套用于實作對象池化的 API,以及若幹種各具特色的對象池實作。目前最常用的版本是 2.0 版本,相對于 1.x 版本而言,并不是簡單更新。2.0 版本是對象池實作的完全重寫,顯著的提升了性能和可伸縮性,并且包含可靠的執行個體跟蹤和池監控。

Apache Commons Pool 的官網位址為:​​Pool – Overview​​,想翻找相關文檔資料,到這裡去是最權威、最全面的。

要使用 ACP 實作一個線程池,首先需要先引入 ACP 的依賴包,這裡以 Maven 為例。

要使用 ACP 實作一個對象池,大緻可以分為三個步驟:

建立對象工廠:告訴 ACP 如何建立你要的對象。

建立對象池:告訴 ACP 你想建立一個怎樣的對象池。

使用對象池:ACP 告訴你如何使用你的對象。

對象工廠告訴 ACP,它應該如何去建立、激活、鈍化、銷毀你的對象。建立對象工廠非常簡單,隻需要實作 ACP 的 PooledObjectFactory 接口即可。PooledObjectFactory 接口的定義如下:

但更多情況下,我們會繼承 BasePooledObjectFactory 類來實作對象工廠。因為 BasePooledObjectFactory 類是 PooledObjectFactory 的基礎實作類,使用它可以幫我們省了很多麻煩。通過繼承這個抽象類,我們隻需要實作兩個方法:create() 和 wrap() 方法。

create() 方法定義你的對象初始化過程,最後将初始化完成的對象傳回。例如你想定義一個 SFTP 的連接配接,那麼你首先需要定義一個 JSch 對象,之後設定賬号密碼,之後連接配接伺服器,最後傳回一個 ChannelSftp 對象。

wrap() 方法定義你要傳回的對象,對于一個 SFTP 的連接配接池來說,其實就是一個 ChannelSftp 對象。一般情況下可以使用類 DefaultPooledObject 替代,參考實作如下:

建立好對象工廠之後,ACP 已經知道你需要的對象如何建立了。那麼接下來,你需要根據你的實際需要,去建立一個對象池。在 ACP 中,我們通過 GenericObjectPool 以及 GenericObjectPoolConfig 來建立一個對象池。

在上面建立 SFTP 連接配接池的代碼中,我們配置了一些線程池的參數以及設定了抛棄政策。抛棄政策是非常重要的,如果沒有設定抛棄政策,那麼會拿到失效的連接配接進而導緻擷取檔案失敗。抛棄政策是通過 ​<code>​poolConfig.setEvictionPolicyClassName​</code>​ 來設定的,我們這裡設定的是 ​<code>​SftpEvictionPolicy​</code>​ 類,其代碼内容如下:

看到這裡,建立線程池的代碼就結束了,SftpConnectPool 檔案的全部内容如下:

為了友善使用,我還增加了 borrowObject 和 returnObject 方法,但這兩個并不是必須的。在這兩個方法中,我們分别調用了 GenericObjectPool 類的 borrowObject 方法和 returnObject 方法。這正是 ACP 提供的、使用線程池對象的方法,先借一個對象,之後歸還對象。

注:其實在這一步,已經包含了對象池的使用了。但實際使用的時候,我們經常是将對象池的聲明與使用放在同一個類中,是以為了講解友善,這裡沒有分開。是以下文的使用對象池,本質上是對對象池做進一步封裝。

到這裡我們的 SFTP 對象池就已經建立完畢了,是不是非常簡單呢!但在實際的工作中,我們通常會在這基礎上,做一些封裝。對于我們這次的 SFTP 連接配接池來說,我們會對外直接提供下載下傳檔案的服務,将 SFTP 對象池進一步封裝起來,不需要關心怎麼擷取檔案。

最後我們寫一個測試用例來試一試,是否能正常下載下傳檔案。

沒有意外的話,你會看到一條綠線,檔案已經被成功下載下傳了!

本文針對 Apache Commons Pool 庫最常用的對象池功能做了示範。看完這篇文章,我們知道建立一個線程池需要三個步驟,分别是:

建立對象池:告訴 ACP 你想建立一個怎樣的對象池、設定驅逐政策。

本文相關代碼存放在部落客 Github 項目:java-code-chip 中,可以點選位址擷取:​​java-code-chip/src/main/java/tech/shuyi/javacodechip/acp at master · chenyurong/java-code-chip​​

ACP 庫能夠讓讀者朋友們快速地建立一個對象池,更加專注于業務内容。但事實上,ACP 提供的内容遠不止如此,它還有更多更進階的功能。

例如當我們連接配接的 SFTP 伺服器有多個時,我們需要通過不同位址來獲得不同的連接配接對象。此時最笨的辦法是每個不同的位址,都複制多一份代碼,然後通過不同類的不同方法來實作。但這樣的情況工作量相當可觀,并且也會有很多重複代碼。這種時候就可以使用BaseKeyedPooledObjectFactory 來替代 BasePooledObjectFactory,進而實作通過 key 來實作不同位址的連接配接對象管理。

更多關于 ACP 的内容,感興趣的同學可以自行探索,這裡就不深入講解了。

謝謝大家的閱讀。如果文章對你有幫助,點個「點贊」 ,或者分享到朋友圈 吧。

​​Apache Commons 系列簡介 之 Pool-阿裡雲開發者社群​​

​​Apache Common Pool2 對象池應用淺析 - 知乎​​

​​Pool – Project Information​​