Skip to content

Commit 2f69b07

Browse files
Initial commit of RedisCluster class and library
This is an initial commmit which adds a RedisCluster class as well as the framework around which we'll be building proper cluster support. The first commit just contains the code to set up and use our new RedisCluster class as well as parsing logic to handline CLUSTER NODES such that we can map the keyspace. Next up, command processing and then pipelining in a sane way.
1 parent 37af5ed commit 2f69b07

9 files changed

+1018
-7
lines changed

cluster_library.c

+506
Large diffs are not rendered by default.

cluster_library.h

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#ifndef _PHPREDIS_CLUSTER_LIBRARY_H
2+
#define _PHPREDIS_CLUSTER_LIBRARY_H
3+
4+
#include "common.h"
5+
6+
#ifdef ZTS
7+
#include "TSRM.h"
8+
#endif
9+
10+
/* Redis cluster hash slots and N-1 which we'll use to find it */
11+
#define REDIS_CLUSTER_SLOTS 16384
12+
#define REDIS_CLUSTER_MOD (REDIS_CLUSTER_SLOTS-1)
13+
14+
/* Nodes we expect for slave or master */
15+
#define CLUSTER_NODES_MASTER_ELE 9
16+
#define CLUSTER_NODES_SLAVE_ELE 8
17+
18+
/* Length of a cluster name */
19+
#define CLUSTER_NAME_LEN 40
20+
21+
/* The parts for our cluster nodes command */
22+
#define CLUSTER_NODES_HASH 0
23+
#define CLUSTER_NODES_HOST_PORT 1
24+
#define CLUSTER_NODES_TYPE 2
25+
#define CLUSTER_NODES_MASTER_HASH 3
26+
#define CLUSTER_NODES_PING 4
27+
#define CLUSTER_NODES_PONG 5
28+
#define CLUSTER_NODES_EPOCH 6
29+
#define CLUSTER_NODES_CONNECTED 7
30+
#define CLUSTER_SLOTS 8
31+
32+
/* Specific destructor to free a cluster object */
33+
// void redis_destructor_redis_cluster(zend_rsrc_list_entry *rsrc TSRMLS_DC);
34+
35+
/* Bits related to CLUSTER NODES output */
36+
typedef struct clusterNodeInfo {
37+
char *name, *master_name;
38+
39+
short seed;
40+
41+
char *host;
42+
int host_len;
43+
44+
unsigned short port;
45+
46+
unsigned short start_slot;
47+
unsigned short end_slot;
48+
} clusterNodeInfo;
49+
50+
/* A Redis Cluster master node */
51+
typedef struct redisClusterNode {
52+
/* Our cluster ID and master ID */
53+
char *name;
54+
char *master_name;
55+
56+
/* Our Redis socket in question */
57+
RedisSock *sock;
58+
59+
/* Our start and end slots that we serve */
60+
unsigned short start_slot;
61+
unsigned short end_slot;
62+
63+
/* A HashTable containing any slaves */
64+
HashTable *slaves;
65+
} redisClusterNode;
66+
67+
/* RedisCluster implementation structure */
68+
typedef struct redisCluster {
69+
/* Object reference for Zend */
70+
zend_object std;
71+
72+
/* Timeout and read timeout */
73+
double timeout;
74+
double read_timeout;
75+
76+
/* Hash table of seed host/ports */
77+
HashTable *seeds;
78+
79+
/* RedisCluster masters, by direct slot */
80+
redisClusterNode *master[REDIS_CLUSTER_SLOTS];
81+
82+
/* All RedisCluster objects we've created/are connected to */
83+
HashTable *nodes;
84+
} redisCluster;
85+
86+
/* Hash a key to it's slot, using the Redis Cluster hash algorithm */
87+
unsigned short cluster_hash_key(const char *key, int len);
88+
89+
PHPAPI int cluster_init_seeds(redisCluster *cluster, HashTable *ht_seeds);
90+
PHPAPI int cluster_map_keyspace(redisCluster *cluster TSRMLS_DC);
91+
PHPAPI void cluster_free_node(redisClusterNode *node);
92+
93+
PHPAPI char **cluster_sock_read_multibulk_reply(RedisSock *redis_sock, int *len TSRMLS_DC);
94+
95+
PHPAPI int cluster_node_add_slave(redisCluster *cluster, redisClusterNode *master, clusterNodeInfo *slave TSRMLS_DC);
96+
97+
#endif

common.h

+20
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ typedef enum _PUBSUB_TYPE {
5353
PUBSUB_NUMPAT
5454
} PUBSUB_TYPE;
5555

56+
/* Cluster redirection type */
57+
typedef enum _MOVED_TYPE {
58+
MOVED_NONE,
59+
MOVED_MOVED,
60+
MOVED_ASK
61+
} MOVED_TYPE;
62+
5663
/* options */
5764
#define REDIS_OPT_SERIALIZER 1
5865
#define REDIS_OPT_PREFIX 2
@@ -177,6 +184,13 @@ else if(redis_sock->mode == MULTI) { \
177184

178185
#define REDIS_PROCESS_RESPONSE(function) REDIS_PROCESS_RESPONSE_CLOSURE(function, NULL)
179186

187+
/* Clear redirection info */
188+
#define REDIS_MOVED_CLEAR(redis_sock) \
189+
redis_sock->redir_slot = 0; \
190+
redis_sock->redir_port = 0; \
191+
redis_sock->redir_type = MOVED_NONE; \
192+
193+
180194
/* Extended SET argument detection */
181195
#define IS_EX_ARG(a) ((a[0]=='e' || a[0]=='E') && (a[1]=='x' || a[1]=='X') && a[2]=='\0')
182196
#define IS_PX_ARG(a) ((a[0]=='p' || a[0]=='P') && (a[1]=='x' || a[1]=='X') && a[2]=='\0')
@@ -239,6 +253,12 @@ typedef struct {
239253
zend_bool lazy_connect;
240254

241255
int scan;
256+
257+
/* Cluster node redirection */
258+
MOVED_TYPE redir_type;
259+
char *redir_host;
260+
unsigned short redir_port;
261+
unsigned short redir_slot;
242262
} RedisSock;
243263
/* }}} */
244264

config.m4

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,5 +99,5 @@ dnl Check for igbinary
9999
dnl
100100
dnl PHP_SUBST(REDIS_SHARED_LIBADD)
101101

102-
PHP_NEW_EXTENSION(redis, redis.c library.c redis_session.c redis_array.c redis_array_impl.c, $ext_shared)
102+
PHP_NEW_EXTENSION(redis, redis.c library.c redis_session.c redis_array.c redis_array_impl.c redis_cluster.c cluster_library.c, $ext_shared)
103103
fi

crc16.h

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2001-2010 Georges Menie (www.menie.org)
3+
* Copyright 2010 Salvatore Sanfilippo (adapted to Redis coding style)
4+
* All rights reserved.
5+
* Redistribution and use in source and binary forms, with or without
6+
* modification, are permitted provided that the following conditions are met:
7+
*
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above copyright
11+
* notice, this list of conditions and the following disclaimer in the
12+
* documentation and/or other materials provided with the distribution.
13+
* * Neither the name of the University of California, Berkeley nor the
14+
* names of its contributors may be used to endorse or promote products
15+
* derived from this software without specific prior written permission.
16+
*
17+
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
18+
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19+
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20+
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
21+
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22+
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23+
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24+
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26+
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27+
*/
28+
29+
/* CRC16 implementation according to CCITT standards.
30+
*
31+
* Note by @antirez: this is actually the XMODEM CRC 16 algorithm, using the
32+
* following parameters:
33+
*
34+
* Name : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN"
35+
* Width : 16 bit
36+
* Poly : 1021 (That is actually x^16 + x^12 + x^5 + 1)
37+
* Initialization : 0000
38+
* Reflect Input byte : False
39+
* Reflect Output CRC : False
40+
* Xor constant to output CRC : 0000
41+
* Output for "123456789" : 31C3
42+
*/
43+
44+
#include <stdint.h>
45+
46+
static const uint16_t crc16tab[256]= {
47+
0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
48+
0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
49+
0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6,
50+
0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de,
51+
0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485,
52+
0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d,
53+
0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4,
54+
0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc,
55+
0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823,
56+
0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b,
57+
0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12,
58+
0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a,
59+
0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41,
60+
0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49,
61+
0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70,
62+
0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78,
63+
0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f,
64+
0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067,
65+
0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e,
66+
0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256,
67+
0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d,
68+
0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405,
69+
0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c,
70+
0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634,
71+
0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab,
72+
0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3,
73+
0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a,
74+
0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92,
75+
0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9,
76+
0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1,
77+
0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8,
78+
0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0
79+
};
80+
81+
static uint16_t crc16(const char *buf, int len) {
82+
int counter;
83+
uint16_t crc = 0;
84+
for (counter = 0; counter < len; counter++)
85+
crc = (crc<<8) ^ crc16tab[((crc>>8) ^ *buf++)&0x00FF];
86+
return crc;
87+
}

library.c

+68-3
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,58 @@ PHP_REDIS_API char *redis_sock_read_bulk_reply(RedisSock *redis_sock, int bytes
320320
return reply;
321321
}
322322

323+
/**
324+
* Parse MOVED or ASK redirection (Redis Cluster)
325+
* We should get slot host:port
326+
*/
327+
PHPAPI
328+
int redis_sock_redir(RedisSock *redis_sock, const char *msg, int len,
329+
MOVED_TYPE type TSRMLS_DC)
330+
{
331+
char buf[24], *p1, *p2;
332+
333+
// Find the space and ':' seperating host/port and do a sanity check on the
334+
// lengths we get back.
335+
if(!(p1 = strchr(msg, ' ')) || !(p2 = strchr(p1,':')) || (len-(p2-msg)>6))
336+
{
337+
zend_throw_exception(redis_exception_ce,
338+
"Error parsing MOVED/ASK redirection", 0 TSRMLS_CC);
339+
return -1;
340+
}
341+
342+
// Free previously stored redirection host
343+
if(redis_sock->redir_host) efree(redis_sock->redir_host);
344+
345+
// Copy and convert slot
346+
strncpy(buf, msg, p1-msg);
347+
buf[p1-msg]='\0';
348+
redis_sock->redir_slot = (unsigned short)atoi(buf);
349+
350+
// Make a copy of our host
351+
redis_sock->redir_host = estrndup(p1+1, p2-p1-1);
352+
353+
// Copy and convert port
354+
strncpy(buf, p2+1, len-(p2-msg));
355+
buf[len-(p2-msg)+1]='\0';
356+
redis_sock->redir_port = (unsigned short)atoi(buf);
357+
358+
// Success
359+
return 0;
360+
}
361+
362+
/**
363+
* Clear redirection information
364+
*/
365+
PHPAPI void redis_sock_redir_clear(RedisSock *redis_sock)
366+
{
367+
if(redis_sock->redir_host) {
368+
efree(redis_sock->redir_host);
369+
redis_sock->redir_host = NULL;
370+
}
371+
redis_sock->redir_slot = 0;
372+
redis_sock->redir_port = 0;
373+
}
374+
323375
/**
324376
* redis_sock_read
325377
*/
@@ -343,15 +395,25 @@ PHP_REDIS_API char *redis_sock_read(RedisSock *redis_sock, int *buf_len TSRMLS_D
343395
return NULL;
344396
}
345397

398+
// Clear any previous MOVED or ASK redirection
399+
REDIS_MOVED_CLEAR(redis_sock);
400+
346401
switch(inbuf[0]) {
347402
case '-':
348403
/* Set the last error */
349404
err_len = strlen(inbuf+1) - 2;
350405
redis_sock_set_err(redis_sock, inbuf+1, err_len);
351406

352-
/* Filter our ERROR through the few that should actually throw */
353-
redis_error_throw(inbuf + 1, err_len TSRMLS_CC);
354-
407+
/* Handle stale data or MOVED/ASK redirection */
408+
if(memcmp(inbuf + 1, "-ERR SYNC ", 10) == 0) {
409+
zend_throw_exception(redis_exception_ce, "SYNC with master in progress", 0 TSRMLS_CC);
410+
} else if(memcmp(inbuf, "-MOVED ", sizeof("-MOVED ")-1)==0) {
411+
redis_sock_redir(redis_sock,inbuf+sizeof("-MOVED "),
412+
err_len-sizeof("-MOVED ")-1, MOVED_MOVED TSRMLS_CC);
413+
} else if(memcmp(inbuf, "-ASK ", sizeof("-ASK ")-1)==0) {
414+
redis_sock_redir(redis_sock,inbuf+sizeof("-ASK "),
415+
err_len-sizeof("-ASK ")-1, MOVED_ASK TSRMLS_CC);
416+
}
355417
return NULL;
356418
case '$':
357419
*buf_len = atoi(inbuf + 1);
@@ -1821,6 +1883,9 @@ PHP_REDIS_API void redis_free_socket(RedisSock *redis_sock)
18211883
if(redis_sock->persistent_id) {
18221884
efree(redis_sock->persistent_id);
18231885
}
1886+
if(redis_sock->redir_host) {
1887+
efree(redis_sock->redir_host);
1888+
}
18241889
efree(redis_sock->host);
18251890
efree(redis_sock);
18261891
}

0 commit comments

Comments
 (0)