diff options
author | michael-grunder <michael.grunder@gmail.com> | 2019-03-19 21:30:14 +0300 |
---|---|---|
committer | michael-grunder <michael.grunder@gmail.com> | 2019-03-19 21:30:14 +0300 |
commit | 2761c607e2a273283e2aca32933f929ab067ef46 (patch) | |
tree | 4bc4a26dc7387a4882d824e06471d3b518bc6a12 | |
parent | 8e2ff3fc8eedc42e58e323059470b6466b642c54 (diff) | |
parent | 0f38afa2e2ed56ae3cbdfaf0f6a245cdb9a8eff5 (diff) |
Merge remote-tracking branch 'cthulhu/issue.1448-require_php7' into issue.1448-require_php7
-rw-r--r-- | .travis.yml | 3 | ||||
-rw-r--r-- | cluster_library.c | 186 | ||||
-rw-r--r-- | cluster_library.h | 59 | ||||
-rw-r--r-- | common.h | 1 | ||||
-rw-r--r-- | redis.c | 14 | ||||
-rw-r--r-- | redis_array.c | 18 | ||||
-rw-r--r-- | redis_array.h | 2 | ||||
-rw-r--r-- | redis_array_impl.c | 46 | ||||
-rw-r--r-- | redis_array_impl.h | 3 | ||||
-rw-r--r-- | redis_cluster.c | 130 |
10 files changed, 397 insertions, 65 deletions
diff --git a/.travis.yml b/.travis.yml index 4829561f..5815e0f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,8 +26,7 @@ addons: packages: clang before_install: - phpize - - pecl install igbinary - - ./configure --enable-redis-igbinary --enable-redis-lzf CFLAGS=-Wall + - pecl install igbinary-2.0.8 && ./configure --enable-redis-igbinary --enable-redis-lzf || ./configure --enable-redis-lzf install: make install before_script: - gem install redis diff --git a/cluster_library.c b/cluster_library.c index 43e0c1cf..cc1ef16e 100644 --- a/cluster_library.c +++ b/cluster_library.c @@ -651,6 +651,9 @@ cluster_node_create(redisCluster *c, char *host, size_t host_len, node->slave = slave; node->slaves = NULL; + /* Initialize our list of slot ranges */ + zend_llist_init(&node->slots, sizeof(redisSlotRange), NULL, 0); + // Attach socket node->sock = redis_sock_create(host, host_len, port, c->timeout, c->read_timeout, c->persistent, NULL, 0); @@ -690,10 +693,11 @@ cluster_node_add_slave(redisClusterNode *master, redisClusterNode *slave) /* Use the output of CLUSTER SLOTS to map our nodes */ static int cluster_map_slots(redisCluster *c, clusterReply *r) { + redisClusterNode *pnode, *master, *slave; + redisSlotRange range; int i,j, hlen, klen; short low, high; clusterReply *r2, *r3; - redisClusterNode *pnode, *master, *slave; unsigned short port; char *host, key[1024]; @@ -746,6 +750,10 @@ static int cluster_map_slots(redisCluster *c, clusterReply *r) { for (j = low; j<= high; j++) { c->master[j] = master; } + + /* Append to our list of slot ranges */ + range.low = low; range.high = high; + zend_llist_add_element(&master->slots, &range); } // Success @@ -758,7 +766,10 @@ PHP_REDIS_API void cluster_free_node(redisClusterNode *node) { zend_hash_destroy(node->slaves); efree(node->slaves); } + + zend_llist_destroy(&node->slots); redis_free_socket(node->sock); + efree(node); } @@ -802,6 +813,23 @@ static void ht_free_node(zval *data) { cluster_free_node(node); } +/* zend_llist of slot ranges -> persistent array */ +static redisSlotRange *slot_range_list_clone(zend_llist *src, size_t *count) { + redisSlotRange *dst, *range; + size_t i = 0; + + *count = zend_llist_count(src); + dst = pemalloc(*count * sizeof(*dst), 1); + + range = zend_llist_get_first(src); + while (range) { + memcpy(&dst[i++], range, sizeof(*range)); + range = zend_llist_get_next(src); + } + + return dst; +} + /* Construct a redisCluster object */ PHP_REDIS_API redisCluster *cluster_create(double timeout, double read_timeout, int failover, int persistent) @@ -860,10 +888,49 @@ cluster_free(redisCluster *c, int free_ctx TSRMLS_DC) /* Free any error we've got */ if (c->err) zend_string_release(c->err); + /* Invalidate our cache if we were redirected during operation */ + if (c->cache_key) { + if (c->redirections) { + zend_hash_del(&EG(persistent_list), c->cache_key); + } + zend_string_release(c->cache_key); + } + /* Free structure itself */ if (free_ctx) efree(c); } +/* Create a cluster slot cache structure */ +PHP_REDIS_API +redisCachedCluster *cluster_cache_create(zend_string *hash, HashTable *nodes) { + redisCachedCluster *cc; + redisCachedMaster *cm; + redisClusterNode *node; + + cc = pecalloc(1, sizeof(*cc), 1); + cc->hash = zend_string_dup(hash, 1); + + /* Copy nodes */ + cc->master = pecalloc(zend_hash_num_elements(nodes), sizeof(*cc->master), 1); + ZEND_HASH_FOREACH_PTR(nodes, node) { + /* Skip slaves */ + if (node->slave) continue; + + cm = &cc->master[cc->count]; + + /* Duplicate host/port and clone slot ranges */ + cm->host.addr = zend_string_dup(node->sock->host, 1); + cm->host.port = node->sock->port; + + /* Copy over slot ranges */ + cm->slot = slot_range_list_clone(&node->slots, &cm->slots); + + cc->count++; + } ZEND_HASH_FOREACH_END(); + + return cc; +} + /* Takes our input hash table and returns a straigt C array with elements, * which have been randomized. The return value needs to be freed. */ static zval **cluster_shuffle_seeds(HashTable *seeds, int *len) { @@ -892,6 +959,110 @@ static zval **cluster_shuffle_seeds(HashTable *seeds, int *len) { return z_seeds; } +static void cluster_free_cached_master(redisCachedMaster *cm) { + size_t i; + + /* Free each slave entry */ + for (i = 0; i < cm->slaves; i++) { + zend_string_release(cm->slave[i].addr); + } + + /* Free other elements */ + zend_string_release(cm->host.addr); + pefree(cm->slave, 1); + pefree(cm->slot, 1); +} + +static redisClusterNode* +cached_master_clone(redisCluster *c, redisCachedMaster *cm) { + redisClusterNode *node; + size_t i; + + node = cluster_node_create(c, ZSTR_VAL(cm->host.addr), ZSTR_LEN(cm->host.addr), + cm->host.port, cm->slot[0].low, 0); + + /* Now copy in our slot ranges */ + for (i = 0; i < cm->slots; i++) { + zend_llist_add_element(&node->slots, &cm->slot[i]); + } + + return node; +} + +/* Destroy a persistent cached cluster */ +PHP_REDIS_API void cluster_cache_free(redisCachedCluster *rcc) { + size_t i; + + /* Free masters */ + for (i = 0; i < rcc->count; i++) { + cluster_free_cached_master(&rcc->master[i]); + } + + /* Free hash key */ + zend_string_release(rcc->hash); + pefree(rcc->master, 1); + pefree(rcc, 1); +} + +/* Initialize cluster from cached slots */ +PHP_REDIS_API +void cluster_init_cache(redisCluster *c, redisCachedCluster *cc) { + RedisSock *sock; + redisClusterNode *mnode, *slave; + redisCachedMaster *cm; + char key[HOST_NAME_MAX]; + size_t keylen, i, j, s; + int *map; + + /* Randomize seeds */ + map = emalloc(sizeof(*map) * cc->count); + for (i = 0; i < cc->count; i++) map[i] = i; + fyshuffle(map, cc->count); + + /* Iterate over masters */ + for (i = 0; i < cc->count; i++) { + /* Attach cache key */ + c->cache_key = cc->hash; + + /* Grab the next master */ + cm = &cc->master[map[i]]; + + /* Hash our host and port */ + keylen = snprintf(key, sizeof(key), "%s:%u", ZSTR_VAL(cm->host.addr), + cm->host.port); + + /* Create socket */ + sock = redis_sock_create(ZSTR_VAL(cm->host.addr), ZSTR_LEN(cm->host.addr), cm->host.port, + c->timeout, c->read_timeout, c->persistent, + NULL, 0); + + /* Add to seed nodes */ + zend_hash_str_update_ptr(c->seeds, key, keylen, sock); + + /* Create master node */ + mnode = cached_master_clone(c, cm); + + /* Add our master */ + zend_hash_str_update_ptr(c->nodes, key, keylen, mnode); + + /* Attach any slaves */ + for (s = 0; s < cm->slaves; s++) { + zend_string *host = cm->slave[s].addr; + slave = cluster_node_create(c, ZSTR_VAL(host), ZSTR_LEN(host), cm->slave[s].port, 0, 1); + cluster_node_add_slave(mnode, slave); + } + + /* Hook up direct slot access */ + for (j = 0; j < cm->slots; j++) { + for (s = cm->slot[j].low; s <= cm->slot[j].high; s++) { + c->master[s] = mnode; + } + } + } + + efree(map); +} + /* Initialize seeds */ PHP_REDIS_API int cluster_init_seeds(redisCluster *cluster, HashTable *ht_seeds) { @@ -908,6 +1079,7 @@ cluster_init_seeds(redisCluster *cluster, HashTable *ht_seeds) { if ((z_seed = z_seeds[i]) == NULL) continue; ZVAL_DEREF(z_seed); + /* Has to be a string */ if (Z_TYPE_P(z_seed) != IS_STRING) continue; @@ -940,7 +1112,7 @@ cluster_init_seeds(redisCluster *cluster, HashTable *ht_seeds) { efree(z_seeds); // Success if at least one seed seems valid - return zend_hash_num_elements(cluster->seeds) > 0 ? 0 : -1; + return zend_hash_num_elements(cluster->seeds) > 0 ? SUCCESS : FAILURE; } /* Initial mapping of our cluster keyspace */ @@ -978,7 +1150,7 @@ PHP_REDIS_API int cluster_map_keyspace(redisCluster *c TSRMLS_DC) { return -1; } - return 0; + return SUCCESS; } /* Parse the MOVED OR ASK redirection payload when we get such a response @@ -1046,8 +1218,10 @@ static int cluster_check_response(redisCluster *c, REDIS_REPLY_TYPE *reply_type } // Check for MOVED or ASK redirection - if ((moved = IS_MOVED(inbuf)) || IS_ASK(inbuf)) { - // Set our redirection information + if ((moved = IS_MOVED(inbuf)) || IS_ASK(inbuf)) { // Set our redirection information + /* We'll want to invalidate slot cache if we're using one */ + c->redirections++; + if (cluster_set_redirection(c,inbuf,moved) < 0) { return -1; } @@ -1374,7 +1548,7 @@ PHP_REDIS_API int cluster_send_slot(redisCluster *c, short slot, char *cmd, /* Send a command to given slot in our cluster. If we get a MOVED or ASK error * we attempt to send the command to the node as directed. */ PHP_REDIS_API short cluster_send_command(redisCluster *c, short slot, const char *cmd, - int cmd_len TSRMLS_DC) + int cmd_len TSRMLS_DC) { int resp, timedout = 0; long msstart; diff --git a/cluster_library.h b/cluster_library.h index 62f7f9da..45223308 100644 --- a/cluster_library.h +++ b/cluster_library.h @@ -143,22 +143,43 @@ typedef enum CLUSTER_REDIR_TYPE { /* MULTI BULK response callback typedef */ typedef int (*mbulk_cb)(RedisSock*,zval*,long long, void* TSRMLS_DC); -/* Specific destructor to free a cluster object */ -// void redis_destructor_redis_cluster(zend_resource *rsrc TSRMLS_DC); +/* A list of covered slot ranges */ +typedef struct redisSlotRange { + unsigned short low; + unsigned short high; +} redisSlotRange; + +/* Simple host/port information for our cache */ +typedef struct redisCachedHost { + zend_string *addr; + unsigned short port; +} redisCachedHost; + +/* Storage for a cached master node */ +typedef struct redisCachedMaster { + redisCachedHost host; + + redisSlotRange *slot; /* Slots and count */ + size_t slots; + + redisCachedHost *slave; /* Slaves and their count */ + size_t slaves; +} redisCachedMaster; + +typedef struct redisCachedCluster { + // int rsrc_id; /* Zend resource ID */ + zend_string *hash; /* What we're cached by */ + redisCachedMaster *master; /* Array of masters */ + size_t count; /* Number of masters */ +} redisCachedCluster; /* A Redis Cluster master node */ typedef struct redisClusterNode { - /* Our Redis socket in question */ - RedisSock *sock; - - /* A slot where one of these lives */ - short slot; - - /* Is this a slave node */ - unsigned short slave; - - /* A HashTable containing any slaves */ - HashTable *slaves; + RedisSock *sock; /* Our Redis socket in question */ + short slot; /* One slot we believe this node serves */ + zend_llist slots; /* List of all slots we believe this node serves */ + unsigned short slave; /* Are we a slave */ + HashTable *slaves; /* Hash table of slaves */ } redisClusterNode; /* Forward declarations */ @@ -208,6 +229,11 @@ typedef struct redisCluster { /* Flag for when we get a CLUSTERDOWN error */ short clusterdown; + /* Key to our persistent list cache and number of redirections we've + * received since construction */ + zend_string *cache_key; + uint64_t redirections; + /* The last ERROR we encountered */ zend_string *err; @@ -362,6 +388,13 @@ PHP_REDIS_API int cluster_init_seeds(redisCluster *c, HashTable *ht_seeds); PHP_REDIS_API int cluster_map_keyspace(redisCluster *c TSRMLS_DC); PHP_REDIS_API void cluster_free_node(redisClusterNode *node); +/* Functions for interacting with cached slots maps */ +PHP_REDIS_API redisCachedCluster *cluster_cache_create(zend_string *hash, HashTable *nodes); +PHP_REDIS_API void cluster_cache_free(redisCachedCluster *rcc); +PHP_REDIS_API void cluster_init_cache(redisCluster *c, redisCachedCluster *rcc); + +/* Functions to facilitate cluster slot caching */ + PHP_REDIS_API char **cluster_sock_read_multibulk_reply(RedisSock *redis_sock, int *len TSRMLS_DC); @@ -6,6 +6,7 @@ #define PHPREDIS_NOTUSED(v) ((void)v) +#include "zend_llist.h" #include <ext/standard/php_var.h> #include <ext/standard/php_math.h> #include <zend_smart_str.h> @@ -50,12 +50,15 @@ extern zend_class_entry *redis_cluster_exception_ce; zend_class_entry *redis_ce; zend_class_entry *redis_exception_ce; +extern int le_cluster_slot_cache; + extern zend_function_entry redis_array_functions[]; extern zend_function_entry redis_cluster_functions[]; PHP_INI_BEGIN() /* redis arrays */ PHP_INI_ENTRY("redis.arrays.algorithm", "", PHP_INI_ALL, NULL) + PHP_INI_ENTRY("redis.arrays.auth", "", PHP_INI_ALL, NULL) PHP_INI_ENTRY("redis.arrays.autorehash", "0", PHP_INI_ALL, NULL) PHP_INI_ENTRY("redis.arrays.connecttimeout", "0", PHP_INI_ALL, NULL) PHP_INI_ENTRY("redis.arrays.distributor", "", PHP_INI_ALL, NULL) @@ -71,6 +74,7 @@ PHP_INI_BEGIN() PHP_INI_ENTRY("redis.arrays.consistent", "0", PHP_INI_ALL, NULL) /* redis cluster */ + PHP_INI_ENTRY("redis.clusters.cache_slots", "0", PHP_INI_ALL, NULL) PHP_INI_ENTRY("redis.clusters.auth", "", PHP_INI_ALL, NULL) PHP_INI_ENTRY("redis.clusters.persistent", "0", PHP_INI_ALL, NULL) PHP_INI_ENTRY("redis.clusters.read_timeout", "0", PHP_INI_ALL, NULL) @@ -555,6 +559,12 @@ free_reply_callbacks(RedisSock *redis_sock) redis_sock->current = NULL; } +/* Passthru for destroying cluster cache */ +static void cluster_cache_dtor(zend_resource *rsrc) { + redisCachedCluster *rcc = (redisCachedCluster*)rsrc->ptr; + cluster_cache_free(rcc); +} + void free_redis_object(zend_object *object) { @@ -737,6 +747,10 @@ PHP_MINIT_FUNCTION(redis) redis_cluster_ce = zend_register_internal_class(&redis_cluster_class_entry TSRMLS_CC); redis_cluster_ce->create_object = create_cluster_context; + /* Register our cluster cache list item */ + le_cluster_slot_cache = zend_register_list_destructors_ex(NULL, cluster_cache_dtor, + "Redis cluster slot cache", + module_number); /* Base Exception class */ #if HAVE_SPL diff --git a/redis_array.c b/redis_array.c index c332f6fc..67e117dd 100644 --- a/redis_array.c +++ b/redis_array.c @@ -160,7 +160,7 @@ redis_array_free(RedisArray *ra) zval_dtor(&ra->z_dist); /* Hashing algorithm */ - zval_dtor(&ra->z_algo); + if (ra->algorithm) zend_string_release(ra->algorithm); /* Delete pur commands */ zend_hash_destroy(ra->pure_cmds); @@ -239,13 +239,14 @@ ra_call_user_function(HashTable *function_table, zval *object, zval *function_na Public constructor */ PHP_METHOD(RedisArray, __construct) { - zval *z0, z_fun, z_dist, z_algo, *zpData, *z_opts = NULL; + zval *z0, z_fun, z_dist, *zpData, *z_opts = NULL; RedisArray *ra = NULL; zend_bool b_index = 0, b_autorehash = 0, b_pconnect = 0, consistent = 0; HashTable *hPrev = NULL, *hOpts = NULL; long l_retry_interval = 0; zend_bool b_lazy_connect = 0; double d_connect_timeout = 0, read_timeout = 0.0; + zend_string *algorithm = NULL, *auth = NULL; redis_array_object *obj; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|a", &z0, &z_opts) == FAILURE) { @@ -254,7 +255,6 @@ PHP_METHOD(RedisArray, __construct) ZVAL_NULL(&z_fun); ZVAL_NULL(&z_dist); - ZVAL_NULL(&z_algo); /* extract options */ if(z_opts) { hOpts = Z_ARRVAL_P(z_opts); @@ -279,7 +279,7 @@ PHP_METHOD(RedisArray, __construct) /* extract function name. */ if ((zpData = zend_hash_str_find(hOpts, "algorithm", sizeof("algorithm") - 1)) != NULL && Z_TYPE_P(zpData) == IS_STRING) { - ZVAL_ZVAL(&z_algo, zpData, 1, 0); + algorithm = zval_get_string(zpData); } /* extract index option. */ @@ -337,6 +337,11 @@ PHP_METHOD(RedisArray, __construct) if ((zpData = zend_hash_str_find(hOpts, "consistent", sizeof("consistent") - 1)) != NULL) { consistent = zval_is_true(zpData); } + + /* auth */ + if ((zpData = zend_hash_str_find(hOpts, "auth", sizeof("auth") - 1)) != NULL) { + auth = zval_get_string(zpData); + } } /* extract either name of list of hosts from z0 */ @@ -346,13 +351,14 @@ PHP_METHOD(RedisArray, __construct) break; case IS_ARRAY: - ra = ra_make_array(Z_ARRVAL_P(z0), &z_fun, &z_dist, &z_algo, hPrev, b_index, b_pconnect, l_retry_interval, b_lazy_connect, d_connect_timeout, read_timeout, consistent TSRMLS_CC); + ra = ra_make_array(Z_ARRVAL_P(z0), &z_fun, &z_dist, hPrev, b_index, b_pconnect, l_retry_interval, b_lazy_connect, d_connect_timeout, read_timeout, consistent, algorithm, auth TSRMLS_CC); break; default: WRONG_PARAM_COUNT; } - zval_dtor(&z_algo); + if (algorithm) zend_string_release(algorithm); + if (auth) zend_string_release(auth); zval_dtor(&z_dist); zval_dtor(&z_fun); diff --git a/redis_array.h b/redis_array.h index 55538860..54218412 100644 --- a/redis_array.h +++ b/redis_array.h @@ -58,7 +58,7 @@ typedef struct RedisArray_ { zend_bool pconnect; /* should we use pconnect */ zval z_fun; /* key extractor, callable */ zval z_dist; /* key distributor, callable */ - zval z_algo; /* key hashing algorithm name */ + zend_string *algorithm; /* key hashing algorithm name */ HashTable *pure_cmds; /* hash table */ double connect_timeout; /* socket connect timeout */ double read_timeout; /* socket read timeout */ diff --git a/redis_array_impl.c b/redis_array_impl.c index c879d094..153368d5 100644 --- a/redis_array_impl.c +++ b/redis_array_impl.c @@ -32,8 +32,8 @@ extern zend_class_entry *redis_ce; -RedisArray* -ra_load_hosts(RedisArray *ra, HashTable *hosts, long retry_interval, zend_bool b_lazy_connect TSRMLS_DC) +static RedisArray * +ra_load_hosts(RedisArray *ra, HashTable *hosts, zend_string *auth, long retry_interval, zend_bool b_lazy_connect TSRMLS_DC) { int i = 0, host_len; char *host, *p; @@ -74,10 +74,13 @@ ra_load_hosts(RedisArray *ra, HashTable *hosts, long retry_interval, zend_bool b /* create socket */ redis->sock = redis_sock_create(host, host_len, port, ra->connect_timeout, ra->read_timeout, ra->pconnect, NULL, retry_interval); + /* copy password is specified */ + if (auth) redis->sock->auth = zend_string_copy(auth); + if (!b_lazy_connect) { /* connect */ - if (redis_sock_server_open(redis->sock TSRMLS_CC) < 0) { + if (redis_sock_server_open(redis->sock TSRMLS_CC) < 0 || (auth && redis_sock_auth(redis->sock TSRMLS_CC) < 0)) { zval_dtor(&z_cons); ra->count = ++i; return NULL; @@ -161,7 +164,7 @@ ra_find_name(const char *name) { /* laod array from INI settings */ RedisArray *ra_load_array(const char *name TSRMLS_DC) { - zval *z_data, z_fun, z_dist, z_algo; + zval *z_data, z_fun, z_dist; zval z_params_hosts; zval z_params_prev; zval z_params_funs; @@ -175,8 +178,10 @@ RedisArray *ra_load_array(const char *name TSRMLS_DC) { zval z_params_read_timeout; zval z_params_lazy_connect; zval z_params_consistent; + zval z_params_auth; RedisArray *ra = NULL; + zend_string *algorithm = NULL, *auth = NULL; zend_bool b_index = 0, b_autorehash = 0, b_pconnect = 0, consistent = 0; long l_retry_interval = 0; zend_bool b_lazy_connect = 0; @@ -232,9 +237,8 @@ RedisArray *ra_load_array(const char *name TSRMLS_DC) { if ((iptr = INI_STR("redis.arrays.algorithm")) != NULL) { sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_algo TSRMLS_CC); } - ZVAL_NULL(&z_algo); if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_algo), name, name_len)) != NULL) { - ZVAL_ZVAL(&z_algo, z_data, 1, 0); + algorithm = zval_get_string(z_data); } /* find index option */ @@ -335,15 +339,26 @@ RedisArray *ra_load_array(const char *name TSRMLS_DC) { } } + /* find auth option */ + array_init(&z_params_auth); + if ((iptr = INI_STR("redis.arrays.auth")) != NULL) { + sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_auth TSRMLS_CC); + } + if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_auth), name, name_len)) != NULL) { + auth = zval_get_string(z_data); + } /* create RedisArray object */ - ra = ra_make_array(hHosts, &z_fun, &z_dist, &z_algo, hPrev, b_index, b_pconnect, l_retry_interval, b_lazy_connect, d_connect_timeout, read_timeout, consistent TSRMLS_CC); + ra = ra_make_array(hHosts, &z_fun, &z_dist, hPrev, b_index, b_pconnect, l_retry_interval, b_lazy_connect, d_connect_timeout, read_timeout, consistent, algorithm, auth TSRMLS_CC); if (ra) { ra->auto_rehash = b_autorehash; if(ra->prev) ra->prev->auto_rehash = b_autorehash; } /* cleanup */ + if (algorithm) zend_string_release(algorithm); + if (auth) zend_string_release(auth); + zval_dtor(&z_params_hosts); zval_dtor(&z_params_prev); zval_dtor(&z_params_funs); @@ -357,7 +372,7 @@ RedisArray *ra_load_array(const char *name TSRMLS_DC) { zval_dtor(&z_params_read_timeout); zval_dtor(&z_params_lazy_connect); zval_dtor(&z_params_consistent); - zval_dtor(&z_algo); + zval_dtor(&z_params_auth); zval_dtor(&z_dist); zval_dtor(&z_fun); @@ -405,8 +420,8 @@ ra_make_continuum(zend_string **hosts, int nb_hosts) } RedisArray * -ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, zval *z_algo, HashTable *hosts_prev, zend_bool b_index, zend_bool b_pconnect, long retry_interval, zend_bool b_lazy_connect, double connect_timeout, double read_timeout, zend_bool consistent TSRMLS_DC) { - +ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev, zend_bool b_index, zend_bool b_pconnect, long retry_interval, zend_bool b_lazy_connect, double connect_timeout, double read_timeout, zend_bool consistent, zend_string *algorithm, zend_string *auth TSRMLS_DC) +{ int i, count; RedisArray *ra; @@ -415,7 +430,7 @@ ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, zval *z_algo, HashTab /* create object */ ra = emalloc(sizeof(RedisArray)); ra->hosts = ecalloc(count, sizeof(*ra->hosts)); - ra->redis = ecalloc(count, sizeof(zval)); + ra->redis = ecalloc(count, sizeof(*ra->redis)); ra->count = 0; ra->z_multi_exec = NULL; ra->index = b_index; @@ -424,8 +439,9 @@ ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, zval *z_algo, HashTab ra->connect_timeout = connect_timeout; ra->read_timeout = read_timeout; ra->continuum = NULL; + ra->algorithm = NULL; - if (ra_load_hosts(ra, hosts, retry_interval, b_lazy_connect TSRMLS_CC) == NULL || !ra->count) { + if (ra_load_hosts(ra, hosts, auth, retry_interval, b_lazy_connect TSRMLS_CC) == NULL || !ra->count) { for (i = 0; i < ra->count; ++i) { zval_dtor(&ra->redis[i]); zend_string_release(ra->hosts[i]); @@ -435,7 +451,7 @@ ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, zval *z_algo, HashTab efree(ra); return NULL; } - ra->prev = hosts_prev ? ra_make_array(hosts_prev, z_fun, z_dist, z_algo, NULL, b_index, b_pconnect, retry_interval, b_lazy_connect, connect_timeout, read_timeout, consistent TSRMLS_CC) : NULL; + ra->prev = hosts_prev ? ra_make_array(hosts_prev, z_fun, z_dist, NULL, b_index, b_pconnect, retry_interval, b_lazy_connect, connect_timeout, read_timeout, consistent, algorithm, auth TSRMLS_CC) : NULL; /* init array data structures */ ra_init_function_table(ra); @@ -443,7 +459,7 @@ ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, zval *z_algo, HashTab /* Set hash function and distribtor if provided */ ZVAL_ZVAL(&ra->z_fun, z_fun, 1, 0); ZVAL_ZVAL(&ra->z_dist, z_dist, 1, 0); - ZVAL_ZVAL(&ra->z_algo, z_algo, 1, 0); + if (algorithm) ra->algorithm = zend_string_copy(algorithm); /* init continuum */ if (consistent) { @@ -537,7 +553,7 @@ ra_find_node(RedisArray *ra, const char *key, int key_len, int *out_pos TSRMLS_D const php_hash_ops *ops; /* hash */ - if (Z_TYPE(ra->z_algo) == IS_STRING && (ops = php_hash_fetch_ops(Z_STRVAL(ra->z_algo), Z_STRLEN(ra->z_algo))) != NULL) { + if (ra->algorithm && (ops = php_hash_fetch_ops(ZSTR_VAL(ra->algorithm), ZSTR_LEN(ra->algorithm))) != NULL) { void *ctx = emalloc(ops->context_size); unsigned char *digest = emalloc(ops->digest_size); diff --git a/redis_array_impl.h b/redis_array_impl.h index 43705ee5..877b22f8 100644 --- a/redis_array_impl.h +++ b/redis_array_impl.h @@ -9,9 +9,8 @@ #include "redis_array.h" -RedisArray *ra_load_hosts(RedisArray *ra, HashTable *hosts, long retry_interval, zend_bool b_lazy_connect TSRMLS_DC); RedisArray *ra_load_array(const char *name TSRMLS_DC); -RedisArray *ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, zval *z_algo, HashTable *hosts_prev, zend_bool b_index, zend_bool b_pconnect, long retry_interval, zend_bool b_lazy_connect, double connect_timeout, double read_timeout, zend_bool consistent TSRMLS_DC); +RedisArray *ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev, zend_bool b_index, zend_bool b_pconnect, long retry_interval, zend_bool b_lazy_connect, double connect_timeout, double read_timeout, zend_bool consistent, zend_string *algorithm, zend_string *auth TSRMLS_DC); zval *ra_find_node_by_name(RedisArray *ra, const char *host, int host_len TSRMLS_DC); zval *ra_find_node(RedisArray *ra, const char *key, int key_len, int *out_pos TSRMLS_DC); void ra_init_function_table(RedisArray *ra); diff --git a/redis_cluster.c b/redis_cluster.c index 4de9d6c3..2688c178 100644 --- a/redis_cluster.c +++ b/redis_cluster.c @@ -33,6 +33,7 @@ #include <SAPI.h> zend_class_entry *redis_cluster_ce; +int le_cluster_slot_cache; /* Exception handler */ zend_class_entry *redis_cluster_exception_ce; @@ -350,49 +351,137 @@ void free_cluster_context(zend_object *object) { zend_object_std_dtor(&cluster->std TSRMLS_CC); } -/* Attempt to connect to a Redis cluster provided seeds and timeout options */ -static void redis_cluster_init(redisCluster *c, HashTable *ht_seeds, double timeout, - double read_timeout, int persistent, char *auth, - size_t auth_len TSRMLS_DC) -{ - // Validate timeout +/* Turn a seed array into a zend_string we can use to look up a slot cache */ +static zend_string *cluster_hash_seeds(HashTable *ht) { + smart_str hash = {0}; + zend_string *zstr; + zval *z_seed; + + ZEND_HASH_FOREACH_VAL(ht, z_seed) { + zstr = zval_get_string(z_seed); + smart_str_appendc(&hash, '['); + smart_str_appendl(&hash, ZSTR_VAL(zstr), ZSTR_LEN(zstr)); + smart_str_appendc(&hash, ']'); + zend_string_release(zstr); + } ZEND_HASH_FOREACH_END(); + + /* Not strictly needed but null terminate anyway */ + smart_str_0(&hash); + + /* smart_str is a zend_string internally */ + return hash.s; +} + +#define CACHING_ENABLED() (INI_INT("redis.clusters.cache_slots") == 1) +static redisCachedCluster *cluster_cache_load(HashTable *ht_seeds TSRMLS_DC) { + zend_resource *le; + zend_string *h; + + /* Short circuit if we're not caching slots or if our seeds don't have any + * elements, since it doesn't make sense to cache an empty string */ + if (!CACHING_ENABLED() || zend_hash_num_elements(ht_seeds) == 0) + return NULL; + + /* Look for cached slot information */ + h = cluster_hash_seeds(ht_seeds); + le = zend_hash_str_find_ptr(&EG(persistent_list), ZSTR_VAL(h), ZSTR_LEN(h)); + zend_string_release(h); + + if (le != NULL) { + /* Sanity check on our list type */ + if (le->type != le_cluster_slot_cache) { + php_error_docref(0 TSRMLS_CC, E_WARNING, "Invalid slot cache resource"); + return NULL; + } + + /* Success, return the cached entry */ + return le->ptr; + } + + /* Not found */ + return NULL; +} + +/* Cache a cluster's slot information in persistent_list if it's enabled */ +static int cluster_cache_store(HashTable *ht_seeds, HashTable *nodes TSRMLS_DC) { + redisCachedCluster *cc; + zend_string *hash; + + /* Short circuit if caching is disabled or there aren't any seeds */ + if (!CACHING_ENABLED() || zend_hash_num_elements(ht_seeds) == 0) + return !CACHING_ENABLED() ? SUCCESS : FAILURE; + + /* Construct our cache */ + hash = cluster_hash_seeds(ht_seeds); + cc = cluster_cache_create(hash, nodes); + zend_string_release(hash); + + /* Set up our resource */ +#if PHP_VERSION_ID < 70300 + zend_resource le; + le.type = le_cluster_slot_cache; + le.ptr = cc; + + zend_hash_update_mem(&EG(persistent_list), cc->hash, (void*)&le, sizeof(zend_resource)); +#else + zend_register_persistent_resource_ex(cc->hash, cc, le_cluster_slot_cache); +#endif + + return SUCCESS; +} + +/* Validate redis cluster construction arguments */ +static int +cluster_validate_args(double timeout, double read_timeout, HashTable *seeds) { if (timeout < 0L || timeout > INT_MAX) { CLUSTER_THROW_EXCEPTION("Invalid timeout", 0); + return FAILURE; } - - // Validate our read timeout if (read_timeout < 0L || read_timeout > INT_MAX) { CLUSTER_THROW_EXCEPTION("Invalid read timeout", 0); + return FAILURE; } - /* Make sure there are some seeds */ - if (zend_hash_num_elements(ht_seeds) == 0) { + if (zend_hash_num_elements(seeds) == 0) { CLUSTER_THROW_EXCEPTION("Must pass seeds", 0); + return FAILURE; } + return SUCCESS; +} + +/* Attempt to connect to a Redis cluster provided seeds and timeout options */ +static void redis_cluster_init(redisCluster *c, HashTable *ht_seeds, double timeout, + double read_timeout, int persistent, char *auth, + size_t auth_len TSRMLS_DC) +{ + redisCachedCluster *cc; + + cluster_validate_args(timeout, read_timeout, ht_seeds); + if (auth && auth_len > 0) { c->auth = zend_string_init(auth, auth_len, 0); } - /* Set our timeout and read_timeout which we'll pass through to the - * socket type operations */ c->timeout = timeout; c->read_timeout = read_timeout; - - /* Set our option to use or not use persistent connections */ c->persistent = persistent; /* Calculate the number of miliseconds we will wait when bouncing around, * (e.g. a node goes down), which is not the same as a standard timeout. */ c->waitms = (long)(timeout * 1000); - // Initialize our RedisSock "seed" objects - cluster_init_seeds(c, ht_seeds); - - // Create and map our key space - cluster_map_keyspace(c TSRMLS_CC); + /* Attempt to load from cache */ + if ((cc = cluster_cache_load(ht_seeds TSRMLS_CC))) { + cluster_init_cache(c, cc); + } else if (cluster_init_seeds(c, ht_seeds) == SUCCESS && + cluster_map_keyspace(c TSRMLS_CC) == SUCCESS) + { + cluster_cache_store(ht_seeds, c->nodes TSRMLS_CC); + } } + /* Attempt to load a named cluster configured in php.ini */ void redis_cluster_load(redisCluster *c, char *name, int name_len TSRMLS_DC) { zval z_seeds, z_timeout, z_read_timeout, z_persistent, z_auth, *z_value; @@ -911,7 +1000,7 @@ static void cluster_generic_delete(INTERNAL_FUNCTION_PARAMETERS, char *kw, int kw_len) { zval *z_ret = emalloc(sizeof(*z_ret)); - + // Initialize a LONG value to zero for our return ZVAL_LONG(z_ret, 0); @@ -3098,3 +3187,4 @@ PHP_METHOD(RedisCluster, command) { } /* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4: */ + |