从源码中分析关于phpredis中的连接池可持有数目
最近写个东西期间用到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 协议》,转载必须注明作者和本文链接
太硬核啦