從原始碼中分析關於phpredis中的連線池可持有數目

Limpid發表於2020-10-26

最近寫個東西期間用到redis擴充時,看到文件裡提到有連線池到方式,所以本想看看別人用時,基本沒看到如何設定連線池到數量的地方,無奈之下看看phpredis手冊也只有如何連線,沒有相關介紹如何控制連線池中數量,最後辦法,查原始碼
在phpredis的library.c中有這樣一個方法

static ConnectionPool *
redis_sock_get_connection_pool(RedisSock *redis_sock)
{
    ConnectionPool *pool;
    zend_resource *le;
    zend_string *persistent_id;

    /* Generate our unique pool id depending on configuration */
    persistent_id = redis_pool_spprintf(redis_sock, INI_STR("redis.pconnect.pool_pattern"));
//查詢連線池
    /* Return early if we can find the pool */
    if ((le = zend_hash_find_ptr(&EG(persistent_list), persistent_id))) {
        zend_string_release(persistent_id);
        return le->ptr;
    }
//建立連線池
    /* Create the pool and store it in our persistent list */
    pool = pecalloc(1, sizeof(*pool), 1);
//初始化連線池
    zend_llist_init(&pool->list, sizeof(php_stream *), NULL, 1);
    redis_register_persistent_resource(persistent_id, pool, le_redis_pconnect);

    zend_string_release(persistent_id);
    return pool;
}

在建立連線池階段首先宣告瞭一塊大小為ConnectionPool這個結構體的記憶體塊,但這裡並沒有連線池數量進行設定,因為ConnectionPool指記錄了連線池的指標,具體看下面程式碼

typedef struct {
    zend_llist list;
    int nb_active;
} ConnectionPool;

下面來自PHP7.2的原始碼

typedef struct _zend_llist_element {
    struct _zend_llist_element *next;
    struct _zend_llist_element *prev;
    char data[1]; /* Needs to always be last in the struct */
} zend_llist_element;

typedef struct _zend_llist {
    zend_llist_element *head;
    zend_llist_element *tail;
    size_t count;
    size_t size;
    llist_dtor_func_t dtor;
    unsigned char persistent;
    zend_llist_element *traverse_ptr;
} zend_llist;

從上面可以看出zend_llist是一個雙向連結串列的結構體,而ConnectionPool是一個包含雙向連結串列和活躍計數的結構體。

下面回到第一段程式碼的初始化連線池階段,這裡 zend_llist_init(&pool->list, sizeof(php_stream *), NULL, 1);方法對ConnectionPool中的zend_llist開始初始化,也就是開始給連線池填充,而sizeof(php_stream *)是zend_llist->size,不是數量具體如下

void zend_llist_init(zend_llist *l, size_t size, llist_dtor_func_t dtor, unsigned char persistent)
{
    l->head  = NULL;
    l->tail  = NULL;
    l->count = 0;
    l->size  = size;
    l->dtor  = dtor;
    l->persistent = persistent;
}

再回到第一段程式碼中redis_register_persistent_resource(persistent_id, pool, le_redis_pconnect);這個是註冊持久資源的。
到此為止只是搭了一個連線池的框子並沒有往池子裡放入物件,那麼只能查誰呼叫了redis_sock_get_connection_pool()這個方法,透過查詢發現

/**
 * redis_sock_connect 
 *redis建立連線
 */
PHP_REDIS_API int redis_sock_connect(RedisSock *redis_sock)

這個方法裡中有呼叫該方法下面只擷取相關部分:

if (redis_sock->persistent) {
        if (INI_INT("redis.pconnect.pooling_enabled")) {
            p = redis_sock_get_connection_pool(redis_sock);
            if (zend_llist_count(&p->list) > 0) {
                redis_sock->stream = *(php_stream **)zend_llist_get_last(&p->list);
                zend_llist_remove_tail(&p->list);

                if (redis_sock_check_liveness(redis_sock) == SUCCESS) {
                    return SUCCESS;
                }
                p->nb_active--;
            }

            int limit = INI_INT("redis.pconnect.connection_limit");
            if (limit > 0 && p->nb_active >= limit) {
                redis_sock_set_err(redis_sock, "Connection limit reached", sizeof("Connection limit reached") - 1);
                return FAILURE;
            }

            gettimeofday(&tv, NULL);
            persistent_id = strpprintf(0, "phpredis_%ld%ld", tv.tv_sec, tv.tv_usec);
        } else {
           //省略
    }
//省略redis建立連線部分
    ......
//省略redis建立連線部分
if (!redis_sock->stream) {
    if (estr) {
        redis_sock_set_err(redis_sock, ZSTR_VAL(estr), ZSTR_LEN(estr));
        zend_string_release(estr);
    }
    return FAILURE;
}

if (p) p->nb_active++;

看著有點亂,其實這塊原理就是,當從想池中獲取一個連結時,首先判斷池子裡是否有,如果有並且驗證沒有失活就直接返回成功,如果驗證活性失敗,就nb_active–。
下面判斷是根據redis.pconnect.connection_limit設定的值和nb_active判斷是否達到自定義上限。
在未設定limit和未到上限情況下,由於上面未獲得連結,所以接下來還要新建立一個redis連線,在連線建立成功時,有連線池情況下nb_active++。
這裡會發現只是對nb_active++但並沒有往池子裡放物件,再往下找:

/**
 * redis_sock_disconnect */PHP_REDIS_API int
redis_sock_disconnect(RedisSock *redis_sock, int force)
{
    if (redis_sock == NULL) {
        return FAILURE;
    } else if (redis_sock->stream) {
        if (redis_sock->persistent) {
            ConnectionPool *p = NULL;
            if (INI_INT("redis.pconnect.pooling_enabled")) {
                p = redis_sock_get_connection_pool(redis_sock);
            }
            if (force) {
                php_stream_pclose(redis_sock->stream);
                if (p) p->nb_active--;
            } else if (p) {
                zend_llist_prepend_element(&p->list, &redis_sock->stream);
            }
        } else {
            php_stream_close(redis_sock->stream);
        }
        redis_sock->stream = NULL;
    }
    redis_sock->mode = ATOMIC;
    redis_sock->status = REDIS_SOCK_STATUS_DISCONNECTED;
    redis_sock->watching = 0;

    return SUCCESS;
}

連線斷開時,這裡有兩種情況對於客戶端斷開的連線如果強制關閉的,那麼連線池nb_active直接自減一,如果非強制的並且連線池正常情況下,則把該連線重新放回連線池。
到這裡可以總結下了:

  1. ConnectionPool的nb_active欄位儲存是當前已經建立了多少連線,至於有多少在池裡是在zend_list的count中 ;
  2. phpredis的連線池中的數量由兩方面決定:一個redis.pconnect.connection_limit這個引數,雖然文件裡沒有可以設定該值的提示,但它是可以設定的,預設值0是不自定義限制;另一個決定面是由當前系統可以建立的redis的連線數目確定的。(簡單說就是在不使用連線池時你能建立多少連線,在用池子時每建立一個,正常回收時它就放入池子,當然這裡涉及到池子中存在的連線失活的)

到這裡本該結尾了,只有在查詢中好奇php_stream到底是什麼結構,而它的大小是多少,查了許久也沒看到看到一個說法,只能再看php原始碼:

struct _php_stream  {
    php_stream_ops *ops;
    void *abstract;            /* convenience pointer for abstraction */

    php_stream_filter_chain readfilters, writefilters;

    php_stream_wrapper *wrapper; /* which wrapper was used to open the stream */
    void *wrapperthis;        /* convenience pointer for a instance of a wrapper */
    zval wrapperdata;        /* fgetwrapperdata retrieves this */

    uint8_t is_persistent:1;
    uint8_t in_free:2;            /* to prevent recursion during free */
    uint8_t eof:1;
    uint8_t __exposed:1;    /* non-zero if exposed as a zval somewhere */

    /* so we know how to clean it up correctly.  This should be set to
     * PHP_STREAM_FCLOSE_XXX as appropriate */
    uint8_t fclose_stdiocast:2;

    uint8_t fgetss_state;        /* for fgetss to handle multiline tags */

    char mode[16];            /* "rwb" etc. ala stdio */

    uint32_t flags;    /* PHP_STREAM_FLAG_XXX */

    zend_resource *res;        /* used for auto-cleanup */
    FILE *stdiocast;    /* cache this, otherwise we might leak! */
    char *orig_path;

    zend_resource *ctx;

    /* buffer */
    zend_off_t position; /* of underlying stream */
    unsigned char *readbuf;
    size_t readbuflen;
    zend_off_t readpos;
    zend_off_t writepos;

    /* how much data to read when filling buffer */
    size_t chunk_size;

#if ZEND_DEBUG
    const char *open_filename;
    uint32_t open_lineno;
#endif

    struct _php_stream *enclosing_stream; /* this is a private stream owned by enclosing_stream */
}; /* php_stream */

可以大概瞭解到這個結構應該是儲存流的上下文等相關資訊的,而phpredis中的這個php_stream應該是開啟redis連線時的stream。
到這裡結束。。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章