十億級(jí)流量下晒喷,我與Redis時(shí)延小突刺的戰(zhàn)斗史

一孝偎、背景

某一日收到上游調(diào)用方的反饋,提供的某一個(gè)Dubbo接口凉敲,每天在固定的時(shí)間點(diǎn)被短時(shí)間熔斷衣盾,拋出的異常信息為提供方dubbo線程池被耗盡。當(dāng)前dubbo接口日請(qǐng)求量18億次爷抓,報(bào)錯(cuò)請(qǐng)求94W/天势决,至此開始了優(yōu)化之旅。

二蓝撇、快速應(yīng)急

2.1 快速定位

首先進(jìn)行常規(guī)的系統(tǒng)信息監(jiān)控(機(jī)器果复、JVM內(nèi)存、GC渤昌、線程)据悔,發(fā)現(xiàn)雖稍有突刺,但都在合理范圍內(nèi)耘沼,且跟報(bào)錯(cuò)時(shí)間點(diǎn)對(duì)不上,先暫時(shí)忽略朱盐。

其次進(jìn)行流量分析群嗤,發(fā)現(xiàn)每天固定時(shí)間點(diǎn)會(huì)有流量突增的情況,流量突增的點(diǎn)跟報(bào)錯(cuò)的時(shí)間點(diǎn)也吻合兵琳,初步判斷為短時(shí)大流量導(dǎo)致狂秘。

流量趨勢(shì)

被降級(jí)量

接口99線

三、尋找性能瓶頸點(diǎn)

3.1 接口流程分析

3.1.1 流程圖

3.1.2 流程分析

收到請(qǐng)求后調(diào)用下游接口躯肌,使用hystrix熔斷器者春,熔斷時(shí)間為500MS;

根據(jù)下游接口返回的數(shù)據(jù)清女,進(jìn)行詳情數(shù)據(jù)的封裝钱烟,第一步先到本地緩存中獲取,如果本地緩存沒(méi)有嫡丙,則從Redis進(jìn)行回源拴袭,Redis中無(wú)則直接返回,異步線程從數(shù)據(jù)庫(kù)進(jìn)行回源曙博。

如果第一步調(diào)用下游接口異常拥刻,則進(jìn)行數(shù)據(jù)兜底,兜底流程為先到本地緩存中獲取父泳,如果本地緩存沒(méi)有般哼,則從Redis進(jìn)行回源吴汪,Redis中無(wú)則直接返回,異步線程從數(shù)據(jù)庫(kù)進(jìn)行回源蒸眠。

3.2 性能瓶頸點(diǎn)排查

3.2.1 下游接口服務(wù)耗時(shí)比較長(zhǎng)

調(diào)用鏈顯示漾橙,雖然下游接口的P99線在峰值流量時(shí)存在突刺,超出1S黔宛,但因?yàn)槿蹟喑瑫r(shí)的設(shè)置(熔斷時(shí)間500MS近刘,coreSize&masSize=50,下游接口平均耗時(shí)10MS以下)臀晃,判斷下游接口不是問(wèn)題的關(guān)鍵點(diǎn)觉渴,為進(jìn)一步排除干擾,在下游服務(wù)存在突刺時(shí)能快速失敗徽惋,調(diào)整熔斷時(shí)間為100MS案淋,dubbo超時(shí)時(shí)間100MS。

3.2.2 獲取詳情本地緩存無(wú)數(shù)據(jù)险绘,Redis回源

借助調(diào)用鏈平臺(tái)踢京,第一步分析Redis請(qǐng)求流量,以此來(lái)判斷本地緩存的命中率宦棺,發(fā)現(xiàn)Redis的流量是接口流量的2倍瓣距,從設(shè)計(jì)上來(lái)說(shuō)不應(yīng)該出現(xiàn)這個(gè)現(xiàn)象。開始代碼Review代咸,發(fā)現(xiàn)在有一處邏輯出現(xiàn)了問(wèn)題蹈丸。

沒(méi)有從本地緩存讀取,而是直接從Redis中獲取了數(shù)據(jù)呐芥,Redis最大響應(yīng)時(shí)間也確實(shí)發(fā)現(xiàn)了不合理的突刺逻杖,繼續(xù)分析發(fā)現(xiàn)Redis響應(yīng)時(shí)間和Dubbo99線突刺情況基本一致,感覺此時(shí)已經(jīng)找到了問(wèn)題的原因思瘟,心中暗喜荸百。

Redis請(qǐng)求流量

重試

服務(wù)接口請(qǐng)求流量

Dubbo99線

Redis最大響應(yīng)時(shí)間

3.2.3 獲取兜底數(shù)據(jù)本地緩存無(wú)數(shù)據(jù),Redis回源

正常

3.2.4 記錄請(qǐng)求結(jié)果入Redis

因?yàn)楫?dāng)前Redis做了資源隔離滨攻,且未在DB后臺(tái)查詢到慢日志够话,此時(shí)分析導(dǎo)致Redis變慢的原因有很多,不過(guò)其他的都被主觀忽略了铡买,注意力都在請(qǐng)求Redis流量翻倍的問(wèn)題上了更鲁,故優(yōu)先解決3.2.2中的問(wèn)題。

四奇钞、解決方案

4.1 3.3.2中定位的問(wèn)題上線

上線前Redis請(qǐng)求量

上線后Redis請(qǐng)求量

上線后Redis流量翻倍問(wèn)題得到解決澡为,Redis最大響應(yīng)時(shí)間突刺有所緩解,但依舊沒(méi)能徹底解決景埃,說(shuō)明大流量查詢不是最根本的原因媒至。

redis最大響應(yīng)時(shí)間(上線前)

redis最大響應(yīng)時(shí)間(上線后)

4.2 Redis擴(kuò)容

在Redis異常流量問(wèn)題解決后顶别,問(wèn)題并未得到徹底解決,此時(shí)能做的就是靜下心來(lái)拒啰,仔細(xì)去梳理導(dǎo)致Redis慢的原因驯绎,思路主要從以下三個(gè)方面:

  • 出現(xiàn)了慢查詢

  • Redis服務(wù)出現(xiàn)性能瓶頸

  • 客戶端配置不合理

基于以上思路,一個(gè)個(gè)的進(jìn)行排查谋旦;查詢Redis慢查詢?nèi)罩臼JВ窗l(fā)現(xiàn)慢查詢。

借用調(diào)用鏈平臺(tái)詳細(xì)分析慢的Redis命令册着,沒(méi)有了大流量導(dǎo)致的慢查詢的干擾拴孤,問(wèn)題定位流程很快,大量的耗時(shí)請(qǐng)求在setex方法上甲捏,偶爾出現(xiàn)查詢的慢請(qǐng)求也都是在setex方法之后演熟,根據(jù)Redis單線程的特性判斷setex是Redis99線突刺的元兇。找到具體語(yǔ)句司顿,定位到具體業(yè)務(wù)后芒粹,首先申請(qǐng)擴(kuò)容Redis,由6個(gè)master擴(kuò)到8個(gè)master大溜。

Redis擴(kuò)容前

Redis擴(kuò)容后

從結(jié)果上看化漆,擴(kuò)容基本上沒(méi)有效果,說(shuō)明redis服務(wù)本身不是性能瓶頸點(diǎn)钦奋,此時(shí)剩下的一個(gè)就是客戶端相關(guān)配置了获三。

4.3 客戶端參數(shù)優(yōu)化

4.3.1 連接池優(yōu)化

Redis擴(kuò)容沒(méi)有效果,針對(duì)客戶端可能出現(xiàn)的問(wèn)題锨苏,此時(shí)懷疑的點(diǎn)有兩個(gè)方向。

第一個(gè)是客戶端在處理Redis集群模式時(shí)棺聊,對(duì)連接的管理上存在BUG伞租,第二個(gè)是連接池參數(shù)設(shè)置不合理,此時(shí)源碼分析和連接池參數(shù)調(diào)整同步進(jìn)行限佩。

4.3.1.1 判斷客戶端連接管理上是否有BUG

在分析完葵诈,客戶端處理連接池的源碼后,沒(méi)有問(wèn)題祟同,跟預(yù)想一致作喘,按照槽位緩存連接池,第一個(gè)假設(shè)被排除晕城,源碼如下泞坦。

<pre class="public-DraftStyleDefault-pre" data-offset-key="42knj-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="ftb92" data-offset-key="42knj-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

1、setEx
public String setex(final byte[] key, final int seconds, final byte[] value) {
return new JedisClusterCommand<String>(connectionHandler, maxAttempts) {
@Override
public String execute(Jedis connection) {
return connection.setex(key, seconds, value);
}
}.runBinary(key);
}

2砖顷、runBinary
public T runBinary(byte[] key) {
if (key == null) {
throw new JedisClusterException("No way to dispatch this command to Redis Cluster.");
}

return runWithRetries(key, this.maxAttempts, false, false);

}
3贰锁、runWithRetries
private T runWithRetries(byte[] key, int attempts, boolean tryRandomNode, boolean asking) {
if (attempts <= 0) {
throw new JedisClusterMaxRedirectionsException("Too many Cluster redirections?");
}

Jedis connection = null;
try {

  if (asking) {
    // TODO: Pipeline asking with the original command to make it
    // faster....
    connection = askConnection.get();
    connection.asking();

    // if asking success, reset asking flag
    asking = false;
  } else {
    if (tryRandomNode) {
      connection = connectionHandler.getConnection();
    } else {
      connection = connectionHandler.getConnectionFromSlot(JedisClusterCRC16.getSlot(key));
    }
  }

  return execute(connection);

}

4赃梧、getConnectionFromSlot
public Jedis getConnectionFromSlot(int slot) {
JedisPool connectionPool = cache.getSlotPool(slot);
if (connectionPool != null) {
// It can't guaranteed to get valid connection because of node
// assignment
return connectionPool.getResource();
} else {
renewSlotCache(); //It's abnormal situation for cluster mode, that we have just nothing for slot, try to rediscover state
connectionPool = cache.getSlotPool(slot);
if (connectionPool != null) {
return connectionPool.getResource();
} else {
//no choice, fallback to new connection to random node
return getConnection();
}
}
}

</pre>

</pre>

4.3.1.2 分析連接池參數(shù)

通過(guò)跟中間件團(tuán)隊(duì)溝通,以及參考commons-pool2官方文檔修改如下豌熄;


參數(shù)調(diào)整后授嘀,1S以上的請(qǐng)求量得到減少,但還是存在锣险,上游反饋降級(jí)量由每天90萬(wàn)左右降到每天6W個(gè)(關(guān)于maxWaitMillis設(shè)置為200MS后為什么還會(huì)有超過(guò)200MS的請(qǐng)求蹄皱,下文有解釋)。

參數(shù)優(yōu)化后Reds最大響應(yīng)時(shí)間

參數(shù)優(yōu)化后接口報(bào)錯(cuò)量

4.3.2 持續(xù)優(yōu)化

優(yōu)化不能停止芯肤,如何把Redis的所有寫入請(qǐng)求降低到200MS以內(nèi)巷折,此時(shí)的優(yōu)化思路還是調(diào)整客戶端配置參數(shù),分析Jedis獲取連接相關(guān)源碼纷妆;

Jedis獲取連接源碼

<pre class="public-DraftStyleDefault-pre" data-offset-key="9b7rj-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="ftb92" data-offset-key="9b7rj-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

final AbandonedConfig ac = this.abandonedConfig;
if (ac != null && ac.getRemoveAbandonedOnBorrow() &&
(getNumIdle() < 2) &&
(getNumActive() > getMaxTotal() - 3) ) {
removeAbandoned(ac);
}

PooledObject<T> p = null;

// Get local copy of current config so it is consistent for entire
// method execution
final boolean blockWhenExhausted = getBlockWhenExhausted();

boolean create;
final long waitTime = System.currentTimeMillis();

while (p == null) {
create = false;
p = idleObjects.pollFirst();
if (p == null) {
p = create();
if (p != null) {
create = true;
}
}
if (blockWhenExhausted) {
if (p == null) {
if (borrowMaxWaitMillis < 0) {
p = idleObjects.takeFirst();
} else {
p = idleObjects.pollFirst(borrowMaxWaitMillis,
TimeUnit.MILLISECONDS);
}
}
if (p == null) {
throw new NoSuchElementException(
"Timeout waiting for idle object");
}
} else {
if (p == null) {
throw new NoSuchElementException("Pool exhausted");
}
}
if (!p.allocate()) {
p = null;
}

if (p != null) {
    try {
        factory.activateObject(p);
    } catch (final Exception e) {
        try {
            destroy(p);
        } catch (final Exception e1) {
            // Ignore - activation failure is more important
        }
        p = null;
        if (create) {
            final NoSuchElementException nsee = new NoSuchElementException(
                    "Unable to activate object");
            nsee.initCause(e);
            throw nsee;
        }
    }
    if (p != null && (getTestOnBorrow() || create && getTestOnCreate())) {
        boolean validate = false;
        Throwable validationThrowable = null;
        try {
            validate = factory.validateObject(p);
        } catch (final Throwable t) {
            PoolUtils.checkRethrow(t);
            validationThrowable = t;
        }
        if (!validate) {
            try {
                destroy(p);
                destroyedByBorrowValidationCount.incrementAndGet();
            } catch (final Exception e) {
                // Ignore - validation failure is more important
            }
            p = null;
            if (create) {
                final NoSuchElementException nsee = new NoSuchElementException(
                        "Unable to validate object");
                nsee.initCause(validationThrowable);
                throw nsee;
            }
        }
    }
}

}

updateStatsBorrow(p, System.currentTimeMillis() - waitTime);

return p.getObject();

</pre>

</pre>

獲取連接的大致流程如下:

是否有空閑連接盔几,有空閑連接就直接返回,沒(méi)有就創(chuàng)建掩幢;

創(chuàng)建時(shí)如果超出最大連接數(shù)逊拍,則判斷是否有其他線程在創(chuàng)建連接,如果沒(méi)則直接返回际邻,如果有則等待maxWaitMis時(shí)間(其他線程可能創(chuàng)建失斝旧ァ),如果未超出最大連接世曾,則執(zhí)行創(chuàng)建連接操作(此時(shí)獲取連接等待時(shí)間可能會(huì)大于maxWaitMs)缨恒。

如果創(chuàng)建不成功,則判斷是否是阻塞獲取連接轮听,如果不是則直接拋出異常骗露,連接池不夠用,如果是則判斷maxWaitMillis是否小于0血巍,如果小于0則阻塞等待萧锉,如果大于0則阻塞等待maxWaitMillis。

后續(xù)就是根據(jù)參數(shù)來(lái)判斷是否需要做連接check等述寡。

根據(jù)以上流程分析柿隙,maxWaitMills目前設(shè)置的為200,以上流程加起來(lái)最大阻塞時(shí)間為400MS鲫凶,大部分情況為200MS禀崖,不應(yīng)該出現(xiàn)超出400MS的突刺。

此時(shí)問(wèn)題可能出現(xiàn)在創(chuàng)建連接上螟炫,因?yàn)閯?chuàng)建連接比較耗時(shí)波附,且創(chuàng)建時(shí)間不定,重點(diǎn)分析是否有這個(gè)場(chǎng)景,通過(guò)DB后臺(tái)監(jiān)控Redis連接情況叶雹。

DB后臺(tái)監(jiān)控Redis服務(wù)連接

分析上圖發(fā)現(xiàn)财饥,確實(shí)在幾個(gè)時(shí)間點(diǎn)(9:00,12:00,19:00...)折晦,redis連接數(shù)存在上漲情況钥星,跟Redis突刺時(shí)間基本吻合。感覺(之前的各種嘗試后满着,已經(jīng)不敢用確定了)問(wèn)題到此定位清晰(在突增流量過(guò)來(lái)時(shí)谦炒,連接池可用連接滿足不了需求,會(huì)創(chuàng)建連接风喇,造成請(qǐng)求等待)宁改。

此時(shí)的想法是在服務(wù)啟動(dòng)時(shí)就進(jìn)行連接池的創(chuàng)建,盡量減少新連接的創(chuàng)建魂莫,修改連接池參數(shù)vivo.cache.depend.common.poolConfig.minIdle还蹲,結(jié)果竟然無(wú)效?耙考?谜喊?

啥都不說(shuō)了,開始擼源碼倦始,jedis底層使用的是commons-poll2來(lái)管理連接的斗遏,查看項(xiàng)目中使用的commons-pool2-2.6.2.jar部分源碼;

CommonPool2源碼

<pre class="public-DraftStyleDefault-pre" data-offset-key="c2oqp-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="ftb92" data-offset-key="c2oqp-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

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

super(config, ONAME_BASE, config.getJmxNamePrefix());

if (factory == null) {
    jmxUnregister(); // tidy up
    throw new IllegalArgumentException("factory may not be null");
}
this.factory = factory;

idleObjects = new LinkedBlockingDeque<>(config.getFairness());

setConfig(config);

}

</pre>

</pre>

竟然發(fā)現(xiàn)沒(méi)有初始化連接的地方鞋邑,開始咨詢中間件團(tuán)隊(duì)诵次,中間件團(tuán)隊(duì)給出的源碼(commons-pool2-2.4.2.jar)如下,方法執(zhí)行后多了一次startEvictor方法的調(diào)用枚碗?

<pre class="public-DraftStyleDefault-pre" data-offset-key="f6uc7-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="ftb92" data-offset-key="f6uc7-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

1逾一、初始化連接池
public GenericObjectPool(PooledObjectFactory<T> factory,
GenericObjectPoolConfig config) {
super(config, ONAME_BASE, config.getJmxNamePrefix());
if (factory == null) {
jmxUnregister(); // tidy up
throw new IllegalArgumentException("factory may not be null");
}
this.factory = factory;
idleObjects = new LinkedBlockingDeque<PooledObject<T>>(config.getFairness());
setConfig(config);
startEvictor(getTimeBetweenEvictionRunsMillis());
}

</pre>

</pre>

為啥不一樣?肮雨?嬉荆?開始檢查Jar包,版本不一樣酷含,中間件給出的版本是在V2.4.2,項(xiàng)目實(shí)際使用的是V2.6.2汪茧,分析startEvictor有一步邏輯正是處理連接池預(yù)熱邏輯椅亚。

Jedis連接池預(yù)熱

<pre class="public-DraftStyleDefault-pre" data-offset-key="89itr-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="ftb92" data-offset-key="89itr-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

1、final void startEvictor(long delay) {
synchronized (evictionLock) {
if (null != evictor) {
EvictionTimer.cancel(evictor);
evictor = null;
evictionIterator = null;
}
if (delay > 0) {
evictor = new Evictor();
EvictionTimer.schedule(evictor, delay, delay);
}
}
}
2舱污、class Evictor extends TimerTask {
/**
* Run pool maintenance. Evict objects qualifying for eviction and then
* ensure that the minimum number of idle instances are available.
* Since the Timer that invokes Evictors is shared for all Pools but
* pools may exist in different class loaders, the Evictor ensures that
* any actions taken are under the class loader of the factory
* associated with the pool.
*/
@Override
public void run() {
ClassLoader savedClassLoader =
Thread.currentThread().getContextClassLoader();
try {
if (factoryClassLoader != null) {
// Set the class loader for the factory
ClassLoader cl = factoryClassLoader.get();
if (cl == null) {
// The pool has been dereferenced and the class loader
// GC'd. Cancel this timer so the pool can be GC'd as
// well.
cancel();
return;
}
Thread.currentThread().setContextClassLoader(cl);
}

            // Evict from the pool
            try {
                evict();
            } catch(Exception e) {
                swallowException(e);
            } catch(OutOfMemoryError oome) {
                // Log problem but give evictor thread a chance to continue
                // in case error is recoverable
                oome.printStackTrace(System.err);
            }
            // Re-create idle instances.
            try {
                ensureMinIdle();
            } catch (Exception e) {
                swallowException(e);
            }
        } finally {
            // Restore the previous CCL
            Thread.currentThread().setContextClassLoader(savedClassLoader);
        }
    }
}

3呀舔、 void ensureMinIdle() throws Exception {
ensureIdle(getMinIdle(), true);
}
4、 private void ensureIdle(int idleCount, boolean always) throws Exception {
if (idleCount < 1 || isClosed() || (!always && !idleObjects.hasTakeWaiters())) {
return;
}

    while (idleObjects.size() < idleCount) {
        PooledObject<T> p = create();
        if (p == null) {
            // Can't create objects, no reason to think another call to
            // create will work. Give up.
            break;
        }
        if (getLifo()) {
            idleObjects.addFirst(p);
        } else {
            idleObjects.addLast(p);
        }
    }
    if (isClosed()) {
        // Pool closed while object was being added to idle objects.
        // Make sure the returned object is destroyed rather than left
        // in the idle object pool (which would effectively be a leak)
        clear();
    }
}

</pre>

</pre>

修改Jar版本,配置中心增加vivo.cache.depend.common.poolConfig.timeBetweenEvictionRunsMillis(檢查一次連接池中空閑的連接媚赖,把空閑時(shí)間超過(guò)minEvictableIdleTimeMillis毫秒的連接斷開,直到連接池中的連接數(shù)到minIdle為止)霜瘪。

vivo.cache.depend.common.poolConfig.minEvictableIdleTimeMillis(連接池中連接可空閑的時(shí)間,毫秒)兩個(gè)參數(shù),重啟服務(wù)后惧磺,連接池正常預(yù)熱颖对,最終從Redis層面上解決問(wèn)題。

優(yōu)化結(jié)果如下磨隘,性能問(wèn)題基本得到解決缤底;

Redis響應(yīng)時(shí)間(優(yōu)化前)

Redis響應(yīng)時(shí)間(優(yōu)化后)

接口99線(優(yōu)化前)

接口99線(優(yōu)化后)

五、總結(jié)

出現(xiàn)線上問(wèn)題時(shí)番捂,首先要考慮的還是快速恢復(fù)線上業(yè)務(wù)个唧,將業(yè)務(wù)的影響度降到最低,所以針對(duì)線上的業(yè)務(wù)设预,要提前做好限流徙歼、熔斷、降級(jí)等策略鳖枕,在線上出現(xiàn)問(wèn)題時(shí)能快速找到恢復(fù)方案魄梯。對(duì)公司各監(jiān)控平臺(tái)的熟練使用程度,決定了定位問(wèn)題的速度耕魄,每個(gè)開發(fā)都要把熟練使用監(jiān)控平臺(tái)(機(jī)器画恰、服務(wù)、接口吸奴、DB等)作為一個(gè)基本能力允扇。

Redis出現(xiàn)響應(yīng)慢時(shí),可以優(yōu)先從Redis集群服務(wù)端(機(jī)器負(fù)載则奥、服務(wù)是否有慢查詢)考润、業(yè)務(wù)代碼(是否有BUG)、客戶端(連接池配置是否合理)三個(gè)方面去排查读处,基本上能排查出大部分Redis慢響應(yīng)問(wèn)題糊治。

Redis連接池在系統(tǒng)冷啟動(dòng)時(shí),對(duì)連接池的預(yù)熱罚舱,不同commons-pool2的版本井辜,冷啟動(dòng)的策略也不同,但都需要配置minEvictableIdleTimeMillis參數(shù)才會(huì)生效管闷,可以看下common-pool2官方文檔粥脚,對(duì)常用參數(shù)都做到心中有數(shù),在問(wèn)題出現(xiàn)時(shí)能快速定位包个。

連接池默認(rèn)參數(shù)在解決大流量的業(yè)務(wù)上稍顯乏力刷允,需要針對(duì)大流量場(chǎng)景進(jìn)行調(diào)優(yōu)處理,如果業(yè)務(wù)上流量不是很大直接使用默認(rèn)參數(shù)即可。

具體問(wèn)題要具體分析树灶,不能解決問(wèn)題的時(shí)候要變通思路纤怒,通過(guò)各種方法去嘗試解決問(wèn)題。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末天通,一起剝皮案震驚了整個(gè)濱河市泊窘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌土砂,老刑警劉巖州既,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異萝映,居然都是意外死亡吴叶,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門序臂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蚌卤,“玉大人,你說(shuō)我怎么就攤上這事奥秆⊙放恚” “怎么了?”我有些...
    開封第一講書人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵构订,是天一觀的道長(zhǎng)侮叮。 經(jīng)常有香客問(wèn)我,道長(zhǎng)悼瘾,這世上最難降的妖魔是什么囊榜? 我笑而不...
    開封第一講書人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮亥宿,結(jié)果婚禮上卸勺,老公的妹妹穿的比我還像新娘。我一直安慰自己烫扼,他們只是感情好曙求,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著映企,像睡著了一般悟狱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上堰氓,一...
    開封第一講書人閱讀 51,488評(píng)論 1 302
  • 那天挤渐,我揣著相機(jī)與錄音,去河邊找鬼豆赏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的掷邦。 我是一名探鬼主播白胀,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼抚岗!你這毒婦竟也來(lái)了或杠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤宣蔚,失蹤者是張志新(化名)和其女友劉穎向抢,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胚委,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡挟鸠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了亩冬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片艘希。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖硅急,靈堂內(nèi)的尸體忽然破棺而出覆享,到底是詐尸還是另有隱情,我是刑警寧澤营袜,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布撒顿,位于F島的核電站,受9級(jí)特大地震影響荚板,放射性物質(zhì)發(fā)生泄漏凤壁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一啸驯、第九天 我趴在偏房一處隱蔽的房頂上張望客扎。 院中可真熱鬧,春花似錦罚斗、人聲如沸徙鱼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)袱吆。三九已至,卻和暖如春距淫,著一層夾襖步出監(jiān)牢的瞬間绞绒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工榕暇, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蓬衡,地道東北人喻杈。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像狰晚,于是被迫代替她去往敵國(guó)和親筒饰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 本文導(dǎo)讀: [1] 疫情當(dāng)前 [2] 應(yīng)用異常監(jiān)控 [3] Redis客戶端異常分析 [4] Redis客戶端問(wèn)題...
    東升的思考閱讀 710評(píng)論 0 4
  • 我們知道分布式鎖的特性是排他谬晕、避免死鎖、高可用携取。分布式鎖的實(shí)現(xiàn)可以通過(guò)數(shù)據(jù)庫(kù)的樂(lè)觀鎖(通過(guò)版本號(hào))或者悲觀鎖(通過(guò)...
    Java大生閱讀 407評(píng)論 0 0
  • 九攒钳、應(yīng)用級(jí)緩存 A.緩存簡(jiǎn)介 1.先從緩存中讀取數(shù)據(jù),如果沒(méi)有歹茶,再?gòu)穆僭O(shè)備上讀取實(shí)際數(shù)據(jù)并同步到緩存 2.經(jīng)常讀...
    ZyBlog閱讀 4,448評(píng)論 0 14
  • 表情是什么夕玩,我認(rèn)為表情就是表現(xiàn)出來(lái)的情緒。表情可以傳達(dá)很多信息惊豺。高興了當(dāng)然就笑了燎孟,難過(guò)就哭了。兩者是相互影響密不可...
    Persistenc_6aea閱讀 125,036評(píng)論 2 7
  • 16宿命:用概率思維提高你的勝算 以前的我是風(fēng)險(xiǎn)厭惡者尸昧,不喜歡去冒險(xiǎn)揩页,但是人生放棄了冒險(xiǎn),也就放棄了無(wú)數(shù)的可能烹俗。 ...
    yichen大刀閱讀 6,050評(píng)論 0 4