溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

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

發(fā)布時間:2021-11-30 09:47:58 來源:億速云 閱讀:179 作者:柒染 欄目:數(shù)據(jù)庫

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

 一:初步排查

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

和數(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.

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

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

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

所以當(dāng)時我們的解決辦法是調(diào)整數(shù)據(jù)庫連接池大小,開始初步認(rèn)為是,高峰時期,我們設(shè)置的連接池數(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ù)濫用的后果

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

在這里我簡單說下構(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ù)據(jù)庫連接的泄露,大多數(shù)情況應(yīng)該是服務(wù)和數(shù)據(jù)庫連接的過程中發(fā)生了阻塞,導(dǎo)致連接泄露。但是現(xiàn)在來看,很容易能發(fā)現(xiàn)問題所在,看當(dāng)時的代碼:

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

這就是當(dāng)時的代碼入口,當(dāng)然代碼處沒有這么簡單??梢钥吹轿以诜椒ㄈ肟诰图由狭薚ransactional注解,這里的意思其實就是發(fā)生錯誤,拋出異常時,數(shù)據(jù)庫回滾。

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

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

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

@Transactional(rollbackFor = Exception.class)

足以。

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

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

當(dāng)然如果步驟3操作數(shù)據(jù)庫是多表操作,具有強(qiáng)相關(guān)性,數(shù)據(jù)一致,我們可以這樣做。將和步驟3無關(guān)的步驟分開,變成兩個方法,那么在1,2處發(fā)生阻塞也不會影響到數(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ù)據(jù)連接池泄露的原因后,我第一步就是去掉了事務(wù),然后加上了一些日志,這時我已經(jīng)能確定代碼在jenkinsclient處出現(xiàn)了問題,但是仍然不確定問題出在了哪,我只能加上一些日志,同時通過監(jiān)控繼續(xù)觀察。

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

jenkinsClient.startBuild(jobName, params);

隨后我觀察了項目監(jiān)控。觀察線程情況,發(fā)現(xiàn)大量http-nio的線程阻塞了,而這個線程和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行

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

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

我們再分析下源碼,看看什么情況下會導(dǎo)致線程跑到該處:

/** * 獲取http連接,從名稱也能看出該方法會造成阻塞 */  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();         }     }

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

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

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

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

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

解決

1.jenkinsClient分配超時時間

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

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

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

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

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

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

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

//獲取對應(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);  //獲取對應(yīng)job某次build的詳情 3  client.get(url, BuildWithDetails.class);

bug問題1:為什么全量更新job和增量更新job使用的是同一個JenkinsClient,但是全量更新仍然正常獲取值,而增量更新job狀態(tài)的線程確出現(xiàn)阻塞超時(超時是因為前面我設(shè)置了timeout,使得請求不會一直阻塞下去)。

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

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

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

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

if (pool.getAllocatedCount() < maxPerRoute) {                          }

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

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

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

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

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

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

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

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

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

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

所以這個bug也能很好的解釋了,對于httpclient來說,每個router默認(rèn)可以最多兩個連接。雖然是同一個調(diào)用api采用的是同一個jenkinsClient,但是卻維護(hù)了兩個router,一個是從配置中獲取,一個是jenkins返回的,這個是配置不一致導(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());         //每個路由最多有10個連接(默認(rèn)2個)         httpClientBuilder.setMaxConnPerRoute(10);         //設(shè)置連接池最大連接數(shù)         httpClientBuilder.setMaxConnTotal(20);         return httpClientBuilder;     }

給JenkinsClient添加健康檢查,并手動更新不能用的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ù)利用問題

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

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

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

  • 無法配置

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

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

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

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

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

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

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

  • 使用完的Client放回池中

  • 回收長期不用和不可用的Client

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

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

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

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

  • destroyObject 方法可以銷毀不可用或者過期的對象

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

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

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

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

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

五:反思

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

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

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

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

by陳朗:

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

  • 對于連接、鎖等這些可能會阻塞的場景,都需要給出超時設(shè)置

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

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

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

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI