从源码中分析关于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直接自减一,如果非强制的并且连接池正常情况下,则把该连接重新放回连接池。
到这里可以总结下了:

  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 协议》,转载必须注明作者和本文链接
讨论数量: 1

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!