最近寫個東西期間用到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直接自減一,如果非強制的並且連線池正常情況下,則把該連線重新放回連線池。
到這裡可以總結下了:
- ConnectionPool的nb_active欄位儲存是當前已經建立了多少連線,至於有多少在池裡是在zend_list的count中 ;
- 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 協議》,轉載必須註明作者和本文連結