1. 程式人生 > >ShardedJedisPool的連線池引數如何設定

ShardedJedisPool的連線池引數如何設定

1.場景:

客戶端採用客戶端分片使用redis叢集(即每個redis之間無關聯,每個redis都是master角色)

1.1說明:

由於採用客戶端hash,加上資料不可能均勻,因此最慢的一臺redis就是天花板,所以下面都是按照一臺redis的量來預估,這樣是考慮最壞情況來進行容量評估;

2要面對的問題:

  1. 單臺客戶端(指連線redis的服務)最多用多少連線?
  2. 單臺redis例項要設定多少個maxclients?

3思路:

假設單臺redis例項設定maxclients = 20000,而且我們通過壓測發現單臺客戶端最多使用了500個連線,那麼可以推斷出
理論上,最多隻能部署40個客戶端;

4詳細分析

4.1 說明

針對每個例項建立一個池:maxIdle和maxTotal指的是針對一個redis例項的設定,意思就是如果連線兩個例項,那麼就真正的池的大小要增加一倍,如果三個,那麼就又增加一倍;

4.2連線池不夠用,又不能建立

Caused by: java.util.NoSuchElementException: Timeout waiting for idle object
此刻連線池用光了,並且blockWhenExhausted該值為true(該值預設為true),就需要等連線池有可用的,但是等待超時報該錯誤;

分兩種情況解決上面的問題:
1.調大等待時間(如果還是超時,就在上層限流)
2.增加連線池數目(千萬不能超過服務端的maxclients限制)

注意:
jedis的如下值
maxIdle:表示池中目前可用的(不代表真的可用);
maxTotal:表示池中能裝多少(超過這個池的大小,不會再建立了)
取決於服務端允許多少,如果服務端允許1個,jedis的這兩個值設定再大都是無用的;

復現方法:
1.客戶端多執行緒併發呼叫
2.maxIdle和maxTotal設定為2,服務端maxclients也設定為大於2的值即可();

4.3 連線池用完了,然後新建立的validate失敗

Caused by: java.util.NoSuchElementException: Unable to validate object
無論blockWhenExhausted是否為true,都說明連線池用光了,而且也建立了(新的物件並放入池中)。
另外,也啟用(activateObject)了該物件(不能啟用會報錯:Unable to activate object),但是該物件(網路連線)是不可用的(一種可能是服務端最多可連線5個,你這個是第6個連線);
說明:物件啟用和驗證可用是兩個性質的問題,啟用不代表真的可用;

因此,針對上面的情況(即不夠用採取建立但是此時可能不可用),一種折中的方法是:不建立(來避免這種意外的情況),但是不建立怎麼夠用,
那麼就把天花板(maxTotal)設定的小一點;如果此時,客戶端會等待超時,那麼就調大點等待超時時間;最好的做法是上層限流(把目前能處理的批次當做一個任務來對待,處理完成了,再接收一個新任務);

復現方法:
1.一臺客戶端多執行緒併發呼叫,把連線吃光,另外一臺客戶端就拿不到可用的;
2.maxIdle和maxTotal遠大於大於2的值,服務端maxclients也設定為2的值即可;

5 壓測示例

說明:
* A任務表示對一個裝置進行和redis互動操作(一個A任務包含5次redis操作);
* 如下是一個服務 + 一個redis例項

20個執行緒任務(每個任務分為10000個A任務) + (使用了)10個redis連線 = 1785(qps的值)
10000個執行緒任務(每個任務分為20個A任務) + (使用了)500個redis連線 = 2531(qps的值)
2000個執行緒任務(每個任務分為100個A任務) + (使用了)500個redis連線 = 2325(qps的值)
20000個執行緒任務(每個任務分為10個A任務) + (使用了)500個redis連線 = 2040(qps的值)
200000個執行緒任務(每個任務分為1個A任務) + (使用了)500個redis連線 = 2083(qps的值) 接近符合我們的業務場景(因為我們的業務場景是一個任務是2000個執行緒任務)

6 關鍵程式碼與分析

說明:要通過報錯資訊來分析和推匯出原因是什麼

    public T borrowObject(long borrowMaxWaitMillis) throws Exception {
        this.assertOpen();
        AbandonedConfig ac = this.abandonedConfig;
        if(ac != null && ac.getRemoveAbandonedOnBorrow() && this.getNumIdle() < 2 && this.getNumActive() > this.getMaxTotal() - 3) {
            this.removeAbandoned(ac);
        }

        PooledObject p = null;
        boolean blockWhenExhausted = this.getBlockWhenExhausted();
        long waitTime = 0L;

        while(p == null) {
            boolean create = false;
            if(blockWhenExhausted) {
                p = (PooledObject)this.idleObjects.pollFirst();
                if(p == null) {
                    create = true;
                    p = this.create();
                }

                if(p == null) {
                    if(borrowMaxWaitMillis < 0L) {
                        p = (PooledObject)this.idleObjects.takeFirst();
                    } else {
                        waitTime = System.currentTimeMillis();
                        p = (PooledObject)this.idleObjects.pollFirst(borrowMaxWaitMillis, TimeUnit.MILLISECONDS);
                        waitTime = System.currentTimeMillis() - waitTime;
                    }
                }


                if(p == null) {//此時表明已經超過maxTotal,不能建立;報錯:等空閒超時
                    throw new NoSuchElementException("Timeout waiting for idle object");
                }

                if(!p.allocate()) {
                    p = null;
                }
            } else {
                p = (PooledObject)this.idleObjects.pollFirst();
                if(p == null) {
                    create = true;
                    p = this.create();
                }

                if(p == null) {
                    throw new NoSuchElementException("Pool exhausted");
                }

                if(!p.allocate()) {
                    p = null;
                }
            }

            if(p != null) {
                try {
                    this.factory.activateObject(p);
                } catch (Exception var15) {
                    try {
                        this.destroy(p);
                    } catch (Exception var14) {
                        ;
                    }

                    p = null;
                    if(create) {
                        NoSuchElementException validationThrowable = new NoSuchElementException("Unable to activate object");
                        validationThrowable.initCause(var15);
                        throw validationThrowable;
                    }
                }

                if(p != null && this.getTestOnBorrow()) {
                    boolean validate = false;
                    Throwable validationThrowable1 = null;

                    try {
                        validate = this.factory.validateObject(p);
                    } catch (Throwable var13) {
                        PoolUtils.checkRethrow(var13);
                        validationThrowable1 = var13;
                    }

                    if(!validate) {
                        try {
                            this.destroy(p);
                            this.destroyedByBorrowValidationCount.incrementAndGet();
                        } catch (Exception var12) {
                            ;
                        }

                        p = null;
                        if(create) {
                            NoSuchElementException nsee = new NoSuchElementException("Unable to validate object");
                            nsee.initCause(validationThrowable1);
                            throw nsee;
                        }
                    }
                }
            }
        }

        this.updateStatsBorrow(p, waitTime);
        return p.getObject();
    }

7.效能追求

如果要追求效能,(生產環境)需要將testOnBorrow和testOnReturn設定為false(至於不能用的或者空閒的連線,可以用其他配置或者方式處理,因為連線不可用造成的異常也要捕獲進行相應處理),因為進行這兩個操作,效能低;

8.總結

涉及到網路連線的開發,都會有連線池;
在出現連線池連線不夠用,一般原因的原因可能有如下幾種:
1.客戶端執行緒長期佔用,不還回池中;
2.服務端處理效能低,一個連線被長期使用和佔用;
3.向池子拿的速度比還的快(如果在內網,網路操作還是非常快的,這種情況很少發生,公網需要特殊分析和對待)
在出現連線超時時,一般原因可能有如下幾種:
1.服務端的backlog的限制,這個時候要分析連線狀態(SYN_SENT、SYN_RCVD、主動斷開連線的可能的狀態(FIN_WAIT_1、FIN_WAIT_2、TIME_WAIT)、被動斷開連線的可能的狀態(CLOSED_WAIT、LAST_ACK))是否存在異常(正常情況下,這些狀態一般都是比較少的,或者通過netstat看不到的)。
2.客戶端的連線也是有限制,這個也需要考慮,方法同服務端檢查方法一樣;

3.網路情況很差;

9其他