Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9f0d7bc

Browse files
WIP: Reimplementation of cluster slot caching
RedisCluster currently has a high construction overhead because every request has to issue a CLUSTER SLOTS command to map the keyspace. The issue is especially evident when a request only does a few commands.
1 parent f992864 commit 9f0d7bc

6 files changed

Lines changed: 362 additions & 43 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ mkinstalldirs
1616
run-tests.php
1717
idea/*
1818
.cquery
19+
tags

cluster_library.c

Lines changed: 173 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,9 @@ cluster_node_create(redisCluster *c, char *host, size_t host_len,
651651
node->slave = slave;
652652
node->slaves = NULL;
653653

654+
/* Initialize our list of slot ranges */
655+
zend_llist_init(&node->slots, sizeof(redisSlotRange), NULL, 0);
656+
654657
// Attach socket
655658
node->sock = redis_sock_create(host, host_len, port, c->timeout,
656659
c->read_timeout, c->persistent, NULL, 0);
@@ -690,10 +693,11 @@ cluster_node_add_slave(redisClusterNode *master, redisClusterNode *slave)
690693

691694
/* Use the output of CLUSTER SLOTS to map our nodes */
692695
static int cluster_map_slots(redisCluster *c, clusterReply *r) {
696+
redisClusterNode *pnode, *master, *slave;
697+
redisSlotRange range;
693698
int i,j, hlen, klen;
694699
short low, high;
695700
clusterReply *r2, *r3;
696-
redisClusterNode *pnode, *master, *slave;
697701
unsigned short port;
698702
char *host, key[1024];
699703

@@ -746,6 +750,10 @@ static int cluster_map_slots(redisCluster *c, clusterReply *r) {
746750
for (j = low; j<= high; j++) {
747751
c->master[j] = master;
748752
}
753+
754+
/* Append to our list of slot ranges */
755+
range.low = low; range.high = high;
756+
zend_llist_add_element(&master->slots, &range);
749757
}
750758

751759
// Success
@@ -758,7 +766,10 @@ PHP_REDIS_API void cluster_free_node(redisClusterNode *node) {
758766
zend_hash_destroy(node->slaves);
759767
efree(node->slaves);
760768
}
769+
770+
zend_llist_destroy(&node->slots);
761771
redis_free_socket(node->sock);
772+
762773
efree(node);
763774
}
764775

@@ -802,6 +813,23 @@ static void ht_free_node(zval *data) {
802813
cluster_free_node(node);
803814
}
804815

816+
/* zend_llist of slot ranges -> persistent array */
817+
static redisSlotRange *slot_range_list_clone(zend_llist *src, size_t *count) {
818+
redisSlotRange *dst, *range;
819+
size_t i = 0;
820+
821+
*count = zend_llist_count(src);
822+
dst = pemalloc(*count * sizeof(*dst), 1);
823+
824+
range = zend_llist_get_first(src);
825+
while (range) {
826+
memcpy(&dst[i++], range, sizeof(*range));
827+
range = zend_llist_get_next(src);
828+
}
829+
830+
return dst;
831+
}
832+
805833
/* Construct a redisCluster object */
806834
PHP_REDIS_API redisCluster *cluster_create(double timeout, double read_timeout,
807835
int failover, int persistent)
@@ -860,10 +888,49 @@ cluster_free(redisCluster *c, int free_ctx TSRMLS_DC)
860888
/* Free any error we've got */
861889
if (c->err) zend_string_release(c->err);
862890

891+
/* Invalidate our cache if we were redirected during operation */
892+
if (c->cache_key) {
893+
if (c->redirections) {
894+
zend_hash_del(&EG(persistent_list), c->cache_key);
895+
}
896+
zend_string_release(c->cache_key);
897+
}
898+
863899
/* Free structure itself */
864900
if (free_ctx) efree(c);
865901
}
866902

903+
/* Create a cluster slot cache structure */
904+
PHP_REDIS_API
905+
redisCachedCluster *cluster_cache_create(zend_string *hash, HashTable *nodes) {
906+
redisCachedCluster *cc;
907+
redisCachedMaster *cm;
908+
redisClusterNode *node;
909+
910+
cc = pecalloc(1, sizeof(*cc), 1);
911+
cc->hash = zend_string_dup(hash, 1);
912+
913+
/* Copy nodes */
914+
cc->master = pecalloc(zend_hash_num_elements(nodes), sizeof(*cc->master), 1);
915+
ZEND_HASH_FOREACH_PTR(nodes, node) {
916+
/* Skip slaves */
917+
if (node->slave) continue;
918+
919+
cm = &cc->master[cc->count];
920+
921+
/* Duplicate host/port and clone slot ranges */
922+
cm->host.addr = zend_string_dup(node->sock->host, 1);
923+
cm->host.port = node->sock->port;
924+
925+
/* Copy over slot ranges */
926+
cm->slot = slot_range_list_clone(&node->slots, &cm->slots);
927+
928+
cc->count++;
929+
} ZEND_HASH_FOREACH_END();
930+
931+
return cc;
932+
}
933+
867934
/* Takes our input hash table and returns a straigt C array with elements,
868935
* which have been randomized. The return value needs to be freed. */
869936
static zval **cluster_shuffle_seeds(HashTable *seeds, int *len) {
@@ -892,6 +959,107 @@ static zval **cluster_shuffle_seeds(HashTable *seeds, int *len) {
892959
return z_seeds;
893960
}
894961

962+
static void cluster_free_cached_master(redisCachedMaster *cm) {
963+
size_t i;
964+
965+
/* Free each slave entry */
966+
for (i = 0; i < cm->slaves; i++) {
967+
zend_string_release(cm->slave[i].addr);
968+
}
969+
970+
/* Free other elements */
971+
zend_string_release(cm->host.addr);
972+
pefree(cm->slave, 1);
973+
pefree(cm->slot, 1);
974+
}
975+
976+
static redisClusterNode*
977+
cached_master_clone(redisCluster *c, redisCachedMaster *cm) {
978+
redisClusterNode *node;
979+
size_t i;
980+
981+
node = cluster_node_create(c, ZSTR_VAL(cm->host.addr), ZSTR_LEN(cm->host.addr),
982+
cm->host.port, cm->slot[0].low, 0);
983+
984+
/* Now copy in our slot ranges */
985+
for (i = 0; i < cm->slots; i++) {
986+
zend_llist_add_element(&node->slots, &cm->slot[i]);
987+
}
988+
989+
return node;
990+
}
991+
992+
/* Destroy a persistent cached cluster */
993+
PHP_REDIS_API void cluster_cache_free(redisCachedCluster *rcc) {
994+
size_t i;
995+
996+
/* Free masters */
997+
for (i = 0; i < rcc->count; i++) {
998+
cluster_free_cached_master(&rcc->master[i]);
999+
}
1000+
1001+
/* Free hash key */
1002+
zend_string_release(rcc->hash);
1003+
pefree(rcc->master, 1);
1004+
pefree(rcc, 1);
1005+
}
1006+
1007+
/* Initialize cluster from cached slots */
1008+
PHP_REDIS_API
1009+
void cluster_init_cache(redisCluster *c, redisCachedCluster *cc) {
1010+
RedisSock *sock;
1011+
redisClusterNode *mnode, *slave;
1012+
redisCachedMaster *cm;
1013+
char key[HOST_NAME_MAX];
1014+
size_t keylen, i, j, s;
1015+
int *map;
1016+
1017+
/* Randomize seeds */
1018+
map = emalloc(sizeof(*map) * cc->count);
1019+
for (i = 0; i < cc->count; i++) map[i] = i;
1020+
fyshuffle(map, cc->count);
1021+
1022+
/* Iterate over masters */
1023+
for (i = 0; i < cc->count; i++) {
1024+
/* Grab the next master */
1025+
cm = &cc->master[map[i]];
1026+
1027+
/* Hash our host and port */
1028+
keylen = snprintf(key, sizeof(key), "%s:%u", ZSTR_VAL(cm->host.addr),
1029+
cm->host.port);
1030+
1031+
/* Create socket */
1032+
sock = redis_sock_create(ZSTR_VAL(cm->host.addr), ZSTR_LEN(cm->host.addr), cm->host.port,
1033+
c->timeout, c->read_timeout, c->persistent,
1034+
NULL, 0);
1035+
1036+
/* Add to seed nodes */
1037+
zend_hash_str_update_ptr(c->seeds, key, keylen, sock);
1038+
1039+
/* Create master node */
1040+
mnode = cached_master_clone(c, cm);
1041+
1042+
/* Add our master */
1043+
zend_hash_str_update_ptr(c->nodes, key, keylen, mnode);
1044+
1045+
/* Attach any slaves */
1046+
for (s = 0; s < cm->slaves; s++) {
1047+
zend_string *host = cm->slave[s].addr;
1048+
slave = cluster_node_create(c, ZSTR_VAL(host), ZSTR_LEN(host), cm->slave[s].port, 0, 1);
1049+
cluster_node_add_slave(mnode, slave);
1050+
}
1051+
1052+
/* Hook up direct slot access */
1053+
for (j = 0; j < cm->slots; j++) {
1054+
for (s = cm->slot[j].low; s <= cm->slot[j].high; s++) {
1055+
c->master[s] = mnode;
1056+
}
1057+
}
1058+
}
1059+
1060+
efree(map);
1061+
}
1062+
8951063
/* Initialize seeds */
8961064
PHP_REDIS_API int
8971065
cluster_init_seeds(redisCluster *cluster, HashTable *ht_seeds) {
@@ -908,6 +1076,7 @@ cluster_init_seeds(redisCluster *cluster, HashTable *ht_seeds) {
9081076
if ((z_seed = z_seeds[i]) == NULL) continue;
9091077

9101078
ZVAL_DEREF(z_seed);
1079+
9111080
/* Has to be a string */
9121081
if (Z_TYPE_P(z_seed) != IS_STRING) continue;
9131082

@@ -940,7 +1109,7 @@ cluster_init_seeds(redisCluster *cluster, HashTable *ht_seeds) {
9401109
efree(z_seeds);
9411110

9421111
// Success if at least one seed seems valid
943-
return zend_hash_num_elements(cluster->seeds) > 0 ? 0 : -1;
1112+
return zend_hash_num_elements(cluster->seeds) > 0 ? SUCCESS : FAILURE;
9441113
}
9451114

9461115
/* Initial mapping of our cluster keyspace */
@@ -977,10 +1146,10 @@ PHP_REDIS_API int cluster_map_keyspace(redisCluster *c TSRMLS_DC) {
9771146
zend_throw_exception(redis_cluster_exception_ce,
9781147
"Couldn't map cluster keyspace using any provided seed", 0
9791148
TSRMLS_CC);
980-
return -1;
1149+
return FAILURE;
9811150
}
9821151

983-
return 0;
1152+
return SUCCESS;
9841153
}
9851154

9861155
/* Parse the MOVED OR ASK redirection payload when we get such a response

cluster_library.h

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -143,22 +143,43 @@ typedef enum CLUSTER_REDIR_TYPE {
143143
/* MULTI BULK response callback typedef */
144144
typedef int (*mbulk_cb)(RedisSock*,zval*,long long, void* TSRMLS_DC);
145145

146-
/* Specific destructor to free a cluster object */
147-
// void redis_destructor_redis_cluster(zend_resource *rsrc TSRMLS_DC);
146+
/* A list of covered slot ranges */
147+
typedef struct redisSlotRange {
148+
unsigned short low;
149+
unsigned short high;
150+
} redisSlotRange;
151+
152+
/* Simple host/port information for our cache */
153+
typedef struct redisCachedHost {
154+
zend_string *addr;
155+
unsigned short port;
156+
} redisCachedHost;
157+
158+
/* Storage for a cached master node */
159+
typedef struct redisCachedMaster {
160+
redisCachedHost host;
161+
162+
redisSlotRange *slot; /* Slots and count */
163+
size_t slots;
164+
165+
redisCachedHost *slave; /* Slaves and their count */
166+
size_t slaves;
167+
} redisCachedMaster;
168+
169+
typedef struct redisCachedCluster {
170+
// int rsrc_id; /* Zend resource ID */
171+
zend_string *hash; /* What we're cached by */
172+
redisCachedMaster *master; /* Array of masters */
173+
size_t count; /* Number of masters */
174+
} redisCachedCluster;
148175

149176
/* A Redis Cluster master node */
150177
typedef struct redisClusterNode {
151-
/* Our Redis socket in question */
152-
RedisSock *sock;
153-
154-
/* A slot where one of these lives */
155-
short slot;
156-
157-
/* Is this a slave node */
158-
unsigned short slave;
159-
160-
/* A HashTable containing any slaves */
161-
HashTable *slaves;
178+
RedisSock *sock; /* Our Redis socket in question */
179+
short slot; /* One slot we believe this node serves */
180+
zend_llist slots; /* List of all slots we believe this node serves */
181+
unsigned short slave; /* Are we a slave */
182+
HashTable *slaves; /* Hash table of slaves */
162183
} redisClusterNode;
163184

164185
/* Forward declarations */
@@ -208,6 +229,11 @@ typedef struct redisCluster {
208229
/* Flag for when we get a CLUSTERDOWN error */
209230
short clusterdown;
210231

232+
/* Key to our persistent list cache and number of redirections we've
233+
* received since construction */
234+
zend_string *cache_key;
235+
uint64_t redirections;
236+
211237
/* The last ERROR we encountered */
212238
zend_string *err;
213239

@@ -362,6 +388,13 @@ PHP_REDIS_API int cluster_init_seeds(redisCluster *c, HashTable *ht_seeds);
362388
PHP_REDIS_API int cluster_map_keyspace(redisCluster *c TSRMLS_DC);
363389
PHP_REDIS_API void cluster_free_node(redisClusterNode *node);
364390

391+
/* Functions for interacting with cached slots maps */
392+
PHP_REDIS_API redisCachedCluster *cluster_cache_create(zend_string *hash, HashTable *nodes);
393+
PHP_REDIS_API void cluster_cache_free(redisCachedCluster *rcc);
394+
PHP_REDIS_API void cluster_init_cache(redisCluster *c, redisCachedCluster *rcc);
395+
396+
/* Functions to facilitate cluster slot caching */
397+
365398
PHP_REDIS_API char **cluster_sock_read_multibulk_reply(RedisSock *redis_sock,
366399
int *len TSRMLS_DC);
367400

common.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#define PHPREDIS_NOTUSED(v) ((void)v)
88

9+
#include "zend_llist.h"
910
#include <ext/standard/php_var.h>
1011
#include <ext/standard/php_math.h>
1112
#include <zend_smart_str.h>

redis.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ extern zend_class_entry *redis_cluster_exception_ce;
5050
zend_class_entry *redis_ce;
5151
zend_class_entry *redis_exception_ce;
5252

53+
extern int le_cluster_slot_cache;
54+
5355
extern zend_function_entry redis_array_functions[];
5456
extern zend_function_entry redis_cluster_functions[];
5557

@@ -71,6 +73,7 @@ PHP_INI_BEGIN()
7173
PHP_INI_ENTRY("redis.arrays.consistent", "0", PHP_INI_ALL, NULL)
7274

7375
/* redis cluster */
76+
PHP_INI_ENTRY("redis.clusters.cache_slots", "0", PHP_INI_ALL, NULL)
7477
PHP_INI_ENTRY("redis.clusters.auth", "", PHP_INI_ALL, NULL)
7578
PHP_INI_ENTRY("redis.clusters.persistent", "0", PHP_INI_ALL, NULL)
7679
PHP_INI_ENTRY("redis.clusters.read_timeout", "0", PHP_INI_ALL, NULL)
@@ -549,6 +552,12 @@ free_reply_callbacks(RedisSock *redis_sock)
549552
redis_sock->current = NULL;
550553
}
551554

555+
/* Passthru for destroying cluster cache */
556+
static void cluster_cache_dtor(zend_resource *rsrc) {
557+
redisCachedCluster *rcc = (redisCachedCluster*)rsrc->ptr;
558+
cluster_cache_free(rcc);
559+
}
560+
552561
void
553562
free_redis_object(zend_object *object)
554563
{
@@ -731,6 +740,10 @@ PHP_MINIT_FUNCTION(redis)
731740
redis_cluster_ce = zend_register_internal_class(&redis_cluster_class_entry TSRMLS_CC);
732741
redis_cluster_ce->create_object = create_cluster_context;
733742

743+
/* Register our cluster cache list item */
744+
le_cluster_slot_cache = zend_register_list_destructors_ex(NULL, cluster_cache_dtor,
745+
"Redis cluster slot cache",
746+
module_number);
734747

735748
/* Base Exception class */
736749
#if HAVE_SPL

0 commit comments

Comments
 (0)