久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

數(shù)據(jù)庫連接池泄露后的思考是怎樣的

共計(jì) 13089 個(gè)字符,預(yù)計(jì)需要花費(fèi) 33 分鐘才能閱讀完成。

數(shù)據(jù)庫連接池泄露后的思考是怎樣的,很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面丸趣 TV 小編將為大家詳細(xì)講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。

  一:初步排查

早上作為能效平臺(tái)系統(tǒng)的使用高峰期,系統(tǒng)負(fù)載通常比其它時(shí)間段更大一些,某個(gè)時(shí)間段會(huì)有大量用戶登錄。當(dāng)天系統(tǒng)開始有用戶報(bào)障,發(fā)布系統(tǒng)線上無法構(gòu)建發(fā)布,然后后續(xù)有用戶不能登錄系統(tǒng),系統(tǒng)發(fā)生假死,當(dāng)然系統(tǒng)不是真的宕機(jī),而是所有和數(shù)據(jù)庫有關(guān)的連接都被阻塞,隨后查看日志發(fā)現(xiàn)有大量報(bào)錯(cuò)。

和數(shù)據(jù)庫連接池相關(guān):

Caused by: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30002ms.

可以看出上面的報(bào)錯(cuò)和數(shù)據(jù)庫連接有關(guān),大量超時(shí)。通過對(duì)線上 debug 日志的分析,也驗(yàn)證了數(shù)據(jù)庫連接池被大量消耗。

[DEBUG] c.z.h.p.HikariPool: HikariPool-1 - Timeout failure stats (total=20, active=20, idle=0, waiting=13)

這是開始大量報(bào)錯(cuò)前的日志。我們可以看到此時(shí) HikariPool 連接池已經(jīng)無法獲取連接了,active=20 表示被獲取正在被使用的數(shù)據(jù)庫連接。waiting 表示當(dāng)前正在排隊(duì)獲取連接的請(qǐng)求數(shù)量。可以看出,已經(jīng)有相當(dāng)多的請(qǐng)求處于掛起狀態(tài)。

所以當(dāng)時(shí)我們的解決辦法是調(diào)整數(shù)據(jù)庫連接池大小,開始初步認(rèn)為是,高峰時(shí)期,我們?cè)O(shè)置的連接池?cái)?shù)量大小,不足以支撐早高峰的連接數(shù)量導(dǎo)致的。

jdbc.connection.timeout=30000 jdbc.max.lifetime=1800000 jdbc.maximum.poolsize=200 jdbc.minimum.idle=10 jdbc.idle.timeout=60000 jdbc.readonly=false

我們將將數(shù)據(jù)庫連接池的數(shù)量調(diào)整到了 200。

二:事務(wù)

2.1 事務(wù)濫用的后果

及時(shí)將配置調(diào)整成了 200,服務(wù)重啟也恢復(fù)了正常,但是我仍然認(rèn)為系統(tǒng)存在連接泄露的風(fēng)險(xiǎn),我試圖從日志表現(xiàn)出的行為里尋找蛛絲馬跡。我在訪問日志看到每次在系統(tǒng)崩潰前,其實(shí)都有人在做構(gòu)建,而且構(gòu)建經(jīng)常點(diǎn)擊沒反應(yīng),我當(dāng)時(shí)添加的構(gòu)建 debug 日志也顯示了這一點(diǎn)。我開始懷疑是構(gòu)建造成的連接泄露。

在這里我簡(jiǎn)單說下構(gòu)建代碼處的邏輯

用戶觸發(fā)構(gòu)建

將 job 加入增量 job 緩存,用于更新 job 狀態(tài)

jenkinsClient 調(diào)用 jenkins 的 api,開始構(gòu)建

將構(gòu)建信息寫入數(shù)據(jù)庫(jobname,version)

我開始觀察自己寫的代碼,可是看了多遍,我也發(fā)現(xiàn)不了這段代碼和數(shù)據(jù)庫連接有啥關(guān)系,大多數(shù)人包括當(dāng)時(shí)自己來說,數(shù)據(jù)庫連接的泄露,大多數(shù)情況應(yīng)該是服務(wù)和數(shù)據(jù)庫連接的過程中發(fā)生了阻塞,導(dǎo)致連接泄露。但是現(xiàn)在來看,很容易能發(fā)現(xiàn)問題所在,看當(dāng)時(shí)的代碼:

@Transactional(rollbackFor = Exception.class) public void build(BuildHistoryReq buildHistoryReq) { //1. 封裝操作  //2. 調(diào)用 jenkins Api //3. 數(shù)據(jù)庫更新寫入  }

這就是當(dāng)時(shí)的代碼入口,當(dāng)然代碼處沒有這么簡(jiǎn)單。可以看到我在方法入口就加上了 Transactional 注解,這里的意思其實(shí)就是發(fā)生錯(cuò)誤,拋出異常時(shí),數(shù)據(jù)庫回滾。

問題就出現(xiàn)在了這里,當(dāng)有用戶點(diǎn)擊構(gòu)建時(shí),請(qǐng)求剛進(jìn)入 build 方法時(shí),就會(huì)從數(shù)據(jù)庫連接獲取一個(gè)連接。可是此時(shí),程序并沒有和數(shù)據(jù)庫相關(guān)的操作,如果此時(shí)代碼在步驟 1 或者 2 處出現(xiàn) io 或者網(wǎng)絡(luò)阻塞,就會(huì)導(dǎo)致,事務(wù)無法提交,連接也就會(huì)一直被該請(qǐng)求占用。而再大的連接池也會(huì)被耗費(fèi)殆盡。從而造成系統(tǒng)崩潰。

2.2 事務(wù)注解的正確用法

通常情況下作為非業(yè)務(wù)部門,沒有涉及到核心的業(yè)務(wù),像支付,訂單,庫存相關(guān)的操作時(shí),事務(wù)在可讀層面并沒有特別高的要求。通常也只涉及到,多表操作同時(shí)更新時(shí),保證數(shù)據(jù)一致性,要么同時(shí)成功要么同時(shí)失敗。而使用

@Transactional(rollbackFor = Exception.class)

足以。

而上述代碼該如何改進(jìn)呢??

首先分析有沒有需要使用事務(wù)的必要。在步驟 3 中,數(shù)據(jù)操作,看代碼后發(fā)現(xiàn)只有對(duì)一張表的操作,同時(shí)和其它操作沒有相關(guān)性。而且本身屬于最后一個(gè)步驟。所以在此代碼中完全沒有必要使用,刪除注解即可。

當(dāng)然如果步驟 3 操作數(shù)據(jù)庫是多表操作,具有強(qiáng)相關(guān)性,數(shù)據(jù)一致,我們可以這樣做。將和步驟 3 無關(guān)的步驟分開, 變成兩個(gè)方法,那么在 1,2 處發(fā)生阻塞也不會(huì)影響到數(shù)據(jù)庫連接。

public void build(BuildHistoryReq buildHistoryReq) { //1. 封裝操作  //2. 調(diào)用 jenkins Api update**(XX); } @Transactional(rollbackFor = Exception.class) public void update**(XX xx) { //3. 數(shù)據(jù)庫更新寫入  }

這里需要注意,注解事務(wù)的用法,方法必須是公開調(diào)用的。

三:HttpClient  4.x 連接池

當(dāng)時(shí)找到數(shù)據(jù)連接池泄露的原因后,我第一步就是去掉了事務(wù),然后加上了一些日志,這時(shí)我已經(jīng)能確定代碼在 jenkinsclient 處出現(xiàn)了問題,但是仍然不確定問題出在了哪,我只能加上一些日志,同時(shí)通過監(jiān)控繼續(xù)觀察。

果然在 hotfix 的第二天還是出現(xiàn)了我預(yù)料中的事情,構(gòu)建發(fā)布仍然有問題,當(dāng)然此時(shí)其它功能是不受影響了。我觀察日志發(fā)現(xiàn)構(gòu)建開始并在該處阻塞

jenkinsClient.startBuild(jobName, params);

隨后我觀察了項(xiàng)目監(jiān)控。觀察線程情況,發(fā)現(xiàn)大量 http-nio 的線程阻塞了,而這個(gè)線程和 httpclient 相關(guān)。

java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for  0x00000007067027e8  (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039) at org.apache.http.pool.AbstractConnPool.getPoolEntryBlocking(AbstractConnPool.java:379) at org.apache.http.pool.AbstractConnPool.access$200(AbstractConnPool.java:69) at org.apache.http.pool.AbstractConnPool$2.get(AbstractConnPool.java:245) - locked  0x00000007824713a0  (a org.apache.http.pool.AbstractConnPool$2) at org.apache.http.pool.AbstractConnPool$2.get(AbstractConnPool.java:193)

隨后我跟進(jìn)源碼查看了 AbstractConnPool 類的 379 行

可以看到線程走到 379 行執(zhí)行了 this.condition.await()后進(jìn)入無限期的等待,所以此時(shí)如果沒有線程執(zhí)行 this.condition.signal()就會(huì)導(dǎo)致該線程一直處于 waiting 狀態(tài),而前端也會(huì)遲遲收不到相應(yīng),導(dǎo)致請(qǐng)求 timeout。

我們?cè)俜治鱿略创a,看看什么情況下會(huì)導(dǎo)致線程跑到該處:

/** *  獲取 http 連接,從名稱也能看出該方法會(huì)造成阻塞  */ private E getPoolEntryBlocking( final T route, final Object state, final long timeout, final TimeUnit timeUnit, final Future E  future) throws IOException, InterruptedException, TimeoutException { Date deadline = null; if (timeout   0) { deadline = new Date (System.currentTimeMillis() + timeUnit.toMillis(timeout)); } this.lock.lock(); try { final RouteSpecificPool T, C, E  pool = getPool(route); E entry; for (;;) { Asserts.check(!this.isShutDown,  Connection pool shut down  for (;;) { entry = pool.getFree(state); if (entry == null) { break; } if (entry.isExpired(System.currentTimeMillis())) { entry.close(); } if (entry.isClosed()) { this.available.remove(entry); pool.free(entry, false); } else { break; } } if (entry != null) { this.available.remove(entry); this.leased.add(entry); onReuse(entry); return entry; } // New connection is needed final int maxPerRoute = getMax(route); // Shrink the pool prior to allocating a new connection final int excess = Math.max(0, pool.getAllocatedCount() + 1 - maxPerRoute); if (excess   0) { for (int i = 0; i   excess; i++) { final E lastUsed = pool.getLastUsed(); if (lastUsed == null) { break; } lastUsed.close(); this.available.remove(lastUsed); pool.remove(lastUsed); } } if (pool.getAllocatedCount()   maxPerRoute) { final int totalUsed = this.leased.size(); final int freeCapacity = Math.max(this.maxTotal - totalUsed, 0); if (freeCapacity   0) { final int totalAvailable = this.available.size(); if (totalAvailable   freeCapacity - 1) { if (!this.available.isEmpty()) { final E lastUsed = this.available.removeLast(); lastUsed.close(); final RouteSpecificPool T, C, E  otherpool = getPool(lastUsed.getRoute()); otherpool.remove(lastUsed); } } final C conn = this.connFactory.create(route); entry = pool.add(conn); this.leased.add(entry); return entry; } } boolean success = false; try { if (future.isCancelled()) { throw new InterruptedException( Operation interrupted  } pool.queue(future); this.pending.add(future); if (deadline != null) { success = this.condition.awaitUntil(deadline); } else { this.condition.await(); success = true; } if (future.isCancelled()) { throw new InterruptedException( Operation interrupted  } } finally { // In case of  success , we were woken up by the // connection pool and should now have a connection // waiting for us, or else we re shutting down. // Just continue in the loop, both cases are checked. pool.unqueue(future); this.pending.remove(future); } // check for spurious wakeup vs. timeout if (!success   (deadline != null   deadline.getTime()  = System.currentTimeMillis())) { break; } } throw new TimeoutException(Timeout waiting for connection  } finally { this.lock.unlock(); } }

從源碼我們可以看出有幾處必要條件才會(huì)導(dǎo)致線程會(huì)無限期等待:

timeout=0 也就是說沒有給默認(rèn)值,導(dǎo)致:deadline = null

pool.getAllocatedCount() maxPerRoute 判斷是否已經(jīng)到達(dá)了該路由 (host 地址) 的最大連接數(shù)。

其實(shí)整體邏輯就是,從池里獲取連接,如果有就直接返回,沒有,判斷當(dāng)前請(qǐng)求出去的路由有沒有到達(dá)該路由的最大值,如果達(dá)到了,就進(jìn)行等待。如果 timeout 為 0 就會(huì)進(jìn)行無限期等待。

而這些值我本身也沒有做任何設(shè)置,我當(dāng)時(shí)的第一想法就是,給 http 請(qǐng)求設(shè)置超時(shí)時(shí)間。也就是給每個(gè) client 設(shè)置必要的參數(shù)

解決

1.jenkinsClient 分配超時(shí)時(shí)間

public HttpClientBuilder clientBuilder() { HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); RequestConfig.Builder builder = RequestConfig.custom(); // 該參數(shù)對(duì)應(yīng) AbstractConnecPool getPoolEntryBlocking 方法的 timeout builder.setConnectionRequestTimeout(5 * 1000); // 數(shù)據(jù)傳輸?shù)某瑫r(shí)時(shí)間  builder.setSocketTimeout(20 * 1000); // 該參數(shù)為,服務(wù)和 jenkins 連接的時(shí)間(通常連接的時(shí)間都很短,可以設(shè)置小點(diǎn)) builder.setConnectTimeout(5 * 1000); httpClientBuilder.setDefaultRequestConfig(builder.build()); return httpClientBuilder; }

2. 構(gòu)建 JenkinsClient 和更新使用的 JenkinsClient 分離

其實(shí)我已經(jīng)嘗試用池化的思想來解決該問題了。

詭異 bug(同一個(gè) JenkinsClient,調(diào)用不同的 api,有的 api 會(huì)阻塞,有的調(diào)用仍然正常)

但 hotfix 的第二天,又出現(xiàn)了一個(gè)詭異的 bug:

構(gòu)建可以,但是無法同步 job 的狀態(tài)。這里出現(xiàn)這個(gè)問題的原因在于我將構(gòu)建和更新兩個(gè)過程使用的 jenkinsClient 分離成兩個(gè),所以這個(gè)過程相互獨(dú)立,互不影響,所以,更新的 client 出了問題但是構(gòu)建的 client 仍然能正常使用。

但是更新過程的 JenkinsClient 出現(xiàn)的問題讓我百思不得其解。我們先看看更新狀態(tài)過程會(huì)使用到的 api(接口)

// 獲取對(duì)應(yīng)的 job 1 JobWithDetails job = client.get(UrlUtils.toJobBaseUrl(folder, jobName), JobWithDetails.class); // 獲取 job 構(gòu)建的 pipeline 流水  2 client.get(/job/  + EncodingUtils.encode(jobName) +  /  + version +  /wfapi/describe , PipelineStep.class); // 獲取對(duì)應(yīng) job 某次 build 的詳情  3 client.get(url, BuildWithDetails.class);

bug 問題 1:為什么全量更新 job 和增量更新 job 使用的是同一個(gè) JenkinsClient,但是全量更新仍然正常獲取值,而增量更新 job 狀態(tài)的線程確出現(xiàn)阻塞超時(shí)(超時(shí)是因?yàn)榍懊嫖以O(shè)置了 timeout,使得請(qǐng)求不會(huì)一直阻塞下去)。

要回答這個(gè)問題,就要回到線程的相關(guān)問題了,

this.condition.wait()會(huì)導(dǎo)致當(dāng)前線程阻塞,并不會(huì)影響到另外線程。而更新使用了兩個(gè)線程。所以這個(gè)問題也比較好回答。

bug 問題 2:為什么同一個(gè)線程 (增量更新 job 線程) 調(diào)用不同 api,有的成功,而有的會(huì)阻塞:

解決這個(gè)問題,我們還是得回到 AbstractConnPool 中的方法 getPoolEntryBlocking()來看:

if (pool.getAllocatedCount()   maxPerRoute) { }

當(dāng)前請(qǐng)求的路由如果已經(jīng)達(dá)到最大值了就會(huì)阻塞等待。那么同一個(gè) jenkinsclient,按理來說不可能會(huì)出現(xiàn)不同的路由。所以同一個(gè) client 要么都能訪問,要么都會(huì)阻塞,怎么會(huì)出現(xiàn)有的能訪問有的會(huì)阻塞。為了尋求問題的答案,我翻閱了 JenkinsClient 的源碼,結(jié)合日志,發(fā)現(xiàn)服務(wù)每次阻塞的方法是:

不管多少次,每次都會(huì)完美的在該地方阻塞:對(duì)應(yīng)上面的 api 3:

// 獲取對(duì)應(yīng) job 某次 build 的詳情  3 client.get(url, BuildWithDetails.class);

這個(gè) url 和其它兩個(gè) api 拿到的路由都有區(qū)別:可以跟隨我一起看源碼:

public class Build extends BaseModel { private int number; private int queueId; private String url; }

我們可以看到 url 是屬于 Build 的屬性,并非 client 我們?cè)O(shè)置的值,當(dāng)然有人會(huì)覺得該值可能是通過將配置的 url 設(shè)置過來的。我們可以接著看, 哪些方法可能會(huì)給 build 設(shè)置 url,三個(gè)構(gòu)造函數(shù),一個(gè) set 方法都可以,如果我們繼續(xù)只看源碼仍然很難找到問題所在,所以這時(shí)候我開始啟動(dòng)服務(wù) debug;

發(fā)現(xiàn)了問題在哪:

可以看出調(diào)用 jenkins 的這個(gè) api 出現(xiàn)了兩個(gè) router,也可以看出這個(gè) url 是 jenkins 返回的,查閱資料可以看到,jenkins 系統(tǒng)設(shè)置時(shí)可以設(shè)置這個(gè) url。

所以這個(gè) bug 也能很好的解釋了,對(duì)于 httpclient 來說,每個(gè) router 默認(rèn)可以最多兩個(gè)連接。雖然是同一個(gè)調(diào)用 api 采用的是同一個(gè) jenkinsClient,但是卻維護(hù)了兩個(gè) router,一個(gè)是從配置中獲取,一個(gè)是 jenkins 返回的,這個(gè)是配置不一致導(dǎo)致的。

JenkinsClient 分配連接數(shù):

public HttpClientBuilder clientBuilder() { HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); RequestConfig.Builder builder = RequestConfig.custom(); builder.setConnectionRequestTimeout(5 * 1000); builder.setSocketTimeout(20 * 1000); builder.setConnectTimeout(5 * 1000); httpClientBuilder.setDefaultRequestConfig(builder.build()); // 每個(gè)路由最多有 10 個(gè)連接(默認(rèn) 2 個(gè)) httpClientBuilder.setMaxConnPerRoute(10); // 設(shè)置連接池最大連接數(shù)  httpClientBuilder.setMaxConnTotal(20); return httpClientBuilder; }

給 JenkinsClient 添加健康檢查,并手動(dòng)更新不能用的 Client

@Slf4j public class JenkinsClientManager implements Runnable { private volatile boolean flag = true; private final JenkinsClientProvider jenkinsClientProvider; public JenkinsClientManager(JenkinsClientProvider jenkinsClientProvider) { this.jenkinsClientProvider = jenkinsClientProvider; } @Override public void run() { while (flag) { try { checkJenkinsHealth(); // 每 30 秒檢查一次  Thread.sleep(30_000); } catch (Exception e) { log.warn( check health error:{} , e.getMessage()); } } } public void checkJenkinsHealth() { log.debug( check jenkins client health start  // 獲取 client 是否可用  available = isAvailable(..) if (!available || !queryAvailable) { // 更新 client jenkinsClientProvider.retrieveJenkinsClient(); } } private boolean isAvailable(Set Map.Entry String, JenkinsClient  entries) { boolean available = true; for (Map.Entry String, JenkinsClient  entry : entries) { boolean running = entry.getValue().isRunning(); if (!running) { log.debug( jenkins running error  available = false; } } return available; } @PostConstruct public void start() { TaskSchedulerConfig.getExecutor().execute(this); } }

四:JenkinsClient 連接池

采用池化技術(shù)解決 client 高可用和重復(fù)利用問題

雖然我手動(dòng)寫了一個(gè) JenkinsClientManager 每 30 秒來維護(hù)一次 client,但是這種手工的方式并不好:

每 30 秒維護(hù)一次,若是在期間發(fā)生問題,那么只能干等

無法動(dòng)態(tài)的根據(jù)系統(tǒng)需要,動(dòng)態(tài)構(gòu)建新的 client,也就是無法滿足高并發(fā)下的使用問題

無法配置

目前我們都知道各種池化技術(shù): 線程池、數(shù)據(jù)庫連接池、redis 連接池。

筆者在實(shí)現(xiàn) jenkinsClient  pool 之前,參考了線程池、數(shù)據(jù)庫連接池的實(shí)現(xiàn)、發(fā)現(xiàn)其底層實(shí)現(xiàn)較為復(fù)雜、redis 的連接池技術(shù)相對(duì)來說容易看懂和學(xué)習(xí)、所以采用了和 jedis 一樣的實(shí)現(xiàn)方式來實(shí)現(xiàn) JenkinsClient 的連接池

這是 jedis 的類結(jié)構(gòu)目錄,其實(shí)重點(diǎn)在我標(biāo)記的這 5 個(gè)類。

jedis 本身也是采用的 commons-pool2 提供的池技術(shù)實(shí)現(xiàn)的,接下來我會(huì)簡(jiǎn)單介紹一下該工具提供的池化技術(shù)。

JenkinsClient 連接池應(yīng)該要具備哪些功能??

動(dòng)態(tài)創(chuàng)建 JenkinsClient

使用完的 Client 放回池中

回收長(zhǎng)期不用和不可用的 Client

能夠根據(jù)需要配置一定數(shù)量的 Client

對(duì)于提到的這些功能,我將通過 commons-pool2 包來實(shí)現(xiàn)

PooledObjectFactory:該接口管理著 bean 的生命周期(An interface defining life-cycle methods  for instances to be served by an)

makeObject 方法創(chuàng)建一個(gè)可以入池的實(shí)例,也就是我們需要用的 Client 由該方法創(chuàng)建

destroyObject 方法可以銷毀不可用或者過期的對(duì)象

validateObject   方法對(duì)實(shí)例進(jìn)行驗(yàn)證,在每次創(chuàng)建完實(shí)例后,都會(huì)調(diào)用該方法,同時(shí)也會(huì)以一定的頻率進(jìn)行健康檢查(頻率 timeBetweenEvictionRunsMillis)

GenericObjectPool:實(shí)例都會(huì)放入該池中進(jìn)行管理:

// 所有的可用連接  private final Map IdentityWrapper T , PooledObject T  allObjects = new ConcurrentHashMap (); // 空閑的可用連接  private final LinkedBlockingDeque PooledObject T  idleObjects; // 獲取可用連接  T borrowObject() throws Exception, NoSuchElementException, IllegalStateException; // 資源釋放(將連接放回連接池) void returnObject(T obj) throws Exception;

配置(BaseObjectPoolConfig,但是我們繼承 GenericObjectPoolConfig,該類給出了大量的默認(rèn)值)

鏈接池中最大連接數(shù), 默認(rèn)為 8  maxTotal #鏈接池中最大空閑的連接數(shù), 默認(rèn)也為 8  maxIdle #連接池中最少空閑的連接數(shù), 默認(rèn)為 0  minIdle #連接空閑的最小時(shí)間,達(dá)到此值后空閑連接將可能會(huì)被移除。默認(rèn)為 1000L*60L*30L minEvictableIdleTimeMillis #連接空閑的最小時(shí)間,達(dá)到此值后空閑鏈接將會(huì)被移除,且保留 minIdle 個(gè)空閑連接數(shù)。默認(rèn)為 -1 softMinEvictableIdleTimeMillis #當(dāng)連接池資源耗盡時(shí),等待時(shí)間,超出則拋異常,默認(rèn)為 - 1 即永不超時(shí)  maxWaitMillis #當(dāng)這個(gè)值為 true 的時(shí)候,maxWaitMillis 參數(shù)才能生效。為 false 的時(shí)候,當(dāng)連接池沒資源,則立馬拋異常。默認(rèn)為 true blockWhenExhausted #空閑鏈接檢測(cè)線程檢測(cè)的周期,毫秒數(shù)。如果為負(fù)值,表示不運(yùn)行檢測(cè)線程。默認(rèn)為 -1. timeBetweenEvictionRunsMillis #在每次空閑連接回收器線程 (如果有) 運(yùn)行時(shí)檢查的連接數(shù)量,默認(rèn)為 3  numTestsPerEvictionRun #默認(rèn) false,create 的時(shí)候檢測(cè)是有有效,如果無效則從連接池中移除,并嘗試獲取繼續(xù)獲取  testOnCreate #默認(rèn) false,borrow 的時(shí)候檢測(cè)是有有效,如果無效則從連接池中移除,并嘗試獲取繼續(xù)獲取  testOnBorrow #默認(rèn) false,return 的時(shí)候檢測(cè)是有有效,如果無效則從連接池中移除,并嘗試獲取繼續(xù)獲取  testOnReturn # 默認(rèn) false,在 evictor 線程里頭,當(dāng) evictionPolicy.evict 方法返回 false 時(shí),而且 testWhileIdle 為 true 的時(shí)候則檢測(cè)是否有效,如果無效則移除  testWhileIdle

了解了這些我們對(duì)于需要開發(fā)的連接池就很輕松了:

實(shí)現(xiàn) PooledObjectFactory(JenkinsFactory)該工廠類就是負(fù)責(zé) JenkinsClient 的生命周期

自定義連接池 Pool,通過組合的方式引入框架的連接池 GenericObjectPool,當(dāng)然我們也可以用繼承的方式來實(shí)現(xiàn)(組合優(yōu)先于繼承)

五:反思

連接池寫完,目前也只是在測(cè)試環(huán)境運(yùn)行,還在觀察階段

有個(gè)特別的問題也需要指出來,該問題是筆者在開發(fā)時(shí)沒有注意的問題,也是此次線上產(chǎn)生問題的原因

筆者將原來更新頻率從 15s 調(diào)整到了 10s,問題就暴露出來了,對(duì)于 1 個(gè) job,可能會(huì)拉出上百個(gè) build,每次會(huì)調(diào)用 3 個(gè) api 接口,如果每次有十個(gè) job,每次更新會(huì)在 10 秒內(nèi)完成,隨著 job 增加,和構(gòu)建歷史增加(雖然有設(shè)置保留多少版本,但是 api 還是會(huì)拉出很奇怪的歷史 build),會(huì)超量發(fā)出大量 http 請(qǐng)求。所以我在代碼層面也做了改動(dòng),每次只更新每個(gè) job 的前 5 個(gè)最新的 build,這樣下來,請(qǐng)求量會(huì)降低很多

List Build  buildList = builds.stream().sorted(Comparator.comparing(Build::getNumber).reversed()).limit(5).collect(toList());

by 陳朗:

整體來講,還是筆者技術(shù)有限,解決問題時(shí)繞了很多彎,花了大量時(shí)間研究源碼。我也總結(jié)了以下幾點(diǎn)

對(duì)于連接、鎖等這些可能會(huì)阻塞的場(chǎng)景,都需要給出超時(shí)設(shè)置

資源消耗型,需要有池化的思想,提高資源利用率,保證系統(tǒng)穩(wěn)定

基礎(chǔ)很重要,需要持續(xù)不斷的學(xué)習(xí),這樣解決問題才能深入底層,找出問題所在,而不是浮于表面

看完上述內(nèi)容是否對(duì)您有幫助呢?如果還想對(duì)相關(guān)知識(shí)有進(jìn)一步的了解或閱讀更多相關(guān)文章,請(qǐng)關(guān)注丸趣 TV 行業(yè)資訊頻道,感謝您對(duì)丸趣 TV 的支持。

正文完
 
丸趣
版權(quán)聲明:本站原創(chuàng)文章,由 丸趣 2023-07-18發(fā)表,共計(jì)13089字。
轉(zhuǎn)載說明:除特殊說明外本站除技術(shù)相關(guān)以外文章皆由網(wǎng)絡(luò)搜集發(fā)布,轉(zhuǎn)載請(qǐng)注明出處。
評(píng)論(沒有評(píng)論)
主站蜘蛛池模板: 上犹县| 陕西省| 普定县| 余干县| 峨山| 山东省| 古田县| 永州市| 通山县| 淮滨县| 施甸县| 桃源县| 开平市| 闵行区| 都兰县| 怀来县| 枣阳市| 石家庄市| 怀化市| 德昌县| 柳林县| 鄂托克旗| 鲁甸县| 曲沃县| 乌什县| 溆浦县| 白朗县| 高平市| 石台县| 威信县| 嘉兴市| 新宁县| 鹤峰县| 玉林市| 永济市| 乐亭县| 平凉市| 共和县| 噶尔县| 颍上县| 晋州市|