2424#include "SAPI.h"
2525#include "ext/standard/url.h"
2626#include "ext/standard/crc32.h"
27+ #include "ext/standard/md5.h"
2728
2829#define PHPREDIS_INDEX_NAME "__phpredis_array_index__"
2930
@@ -173,9 +174,10 @@ RedisArray *ra_load_array(const char *name TSRMLS_DC) {
173174 zval z_params_connect_timeout ;
174175 zval z_params_read_timeout ;
175176 zval z_params_lazy_connect ;
177+ zval z_params_consistent ;
176178 RedisArray * ra = NULL ;
177179
178- zend_bool b_index = 0 , b_autorehash = 0 , b_pconnect = 0 ;
180+ zend_bool b_index = 0 , b_autorehash = 0 , b_pconnect = 0 , consistent = 0 ;
179181 long l_retry_interval = 0 ;
180182 zend_bool b_lazy_connect = 0 ;
181183 double d_connect_timeout = 0 , read_timeout = 0.0 ;
@@ -312,9 +314,20 @@ RedisArray *ra_load_array(const char *name TSRMLS_DC) {
312314 }
313315 }
314316
317+ /* find consistent option */
318+ array_init (& z_params_consistent );
319+ if ((iptr = INI_STR ("redis.arrays.consistent" )) != NULL ) {
320+ sapi_module .treat_data (PARSE_STRING , estrdup (iptr ), & z_params_consistent TSRMLS_CC );
321+ }
322+ if ((z_data = zend_hash_str_find (Z_ARRVAL (z_params_consistent ), name , name_len )) != NULL ) {
323+ if (Z_TYPE_P (z_data ) == IS_STRING && strncmp (Z_STRVAL_P (z_data ), "1" , 1 ) == 0 ) {
324+ consistent = 1 ;
325+ }
326+ }
327+
315328
316329 /* create RedisArray object */
317- 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 TSRMLS_CC );
330+ 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 TSRMLS_CC );
318331 if (ra ) {
319332 ra -> auto_rehash = b_autorehash ;
320333 if (ra -> prev ) ra -> prev -> auto_rehash = b_autorehash ;
@@ -332,14 +345,55 @@ RedisArray *ra_load_array(const char *name TSRMLS_DC) {
332345 zval_dtor (& z_params_connect_timeout );
333346 zval_dtor (& z_params_read_timeout );
334347 zval_dtor (& z_params_lazy_connect );
348+ zval_dtor (& z_params_consistent );
335349 zval_dtor (& z_dist );
336350 zval_dtor (& z_fun );
337351
338352 return ra ;
339353}
340354
355+ static int
356+ ra_points_cmp (const void * v1 , const void * v2 )
357+ {
358+ const ContinuumPoint * p1 = v1 , * p2 = v2 ;
359+
360+ return p1 -> value < p2 -> value ? - 1 : p1 -> value > p2 -> value ;
361+ }
362+
363+ static Continuum *
364+ ra_make_continuum (zend_string * * hosts , int nb_hosts )
365+ {
366+ int i , j , k , len , idx = 0 ;
367+ char host [HOST_NAME_MAX ];
368+ unsigned char digest [16 ];
369+ PHP_MD5_CTX ctx ;
370+ Continuum * c ;
371+
372+ c = ecalloc (1 , sizeof (* c ));
373+ c -> nb_points = nb_hosts * 160 ; /* 40 hashes, 4 numbers per hash = 160 points per server */
374+ c -> points = ecalloc (c -> nb_points , sizeof (* c -> points ));
375+
376+ for (i = 0 ; i < nb_hosts ; ++ i ) {
377+ for (j = 0 ; j < 40 ; ++ j ) {
378+ len = snprintf (host , sizeof (host ), "%.*s-%u" , ZSTR_LEN (hosts [i ]), ZSTR_VAL (hosts [i ]), j );
379+ PHP_MD5Init (& ctx );
380+ PHP_MD5Update (& ctx , host , len );
381+ PHP_MD5Final (digest , & ctx );
382+ for (k = 0 ; k < 4 ; ++ k ) {
383+ c -> points [idx ].index = i ;
384+ c -> points [idx ++ ].value = (digest [3 + k * 4 ] << 24 )
385+ | (digest [2 + k * 4 ] << 16 )
386+ | (digest [1 + k * 4 ] << 8 )
387+ | (digest [k * 4 ]);
388+ }
389+ }
390+ }
391+ qsort (c -> points , c -> nb_points , sizeof (* c -> points ), ra_points_cmp );
392+ return c ;
393+ }
394+
341395RedisArray *
342- 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 TSRMLS_DC ) {
396+ 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 TSRMLS_DC ) {
343397
344398 int i , count ;
345399 RedisArray * ra ;
@@ -357,6 +411,7 @@ ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev
357411 ra -> pconnect = b_pconnect ;
358412 ra -> connect_timeout = connect_timeout ;
359413 ra -> read_timeout = read_timeout ;
414+ ra -> continuum = NULL ;
360415
361416 if (ra_load_hosts (ra , hosts , retry_interval , b_lazy_connect TSRMLS_CC ) == NULL || !ra -> count ) {
362417 for (i = 0 ; i < ra -> count ; ++ i ) {
@@ -368,7 +423,7 @@ ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev
368423 efree (ra );
369424 return NULL ;
370425 }
371- 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 TSRMLS_CC ) : NULL ;
426+ 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 TSRMLS_CC ) : NULL ;
372427
373428 /* init array data structures */
374429 ra_init_function_table (ra );
@@ -377,6 +432,11 @@ ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev
377432 ZVAL_ZVAL (& ra -> z_fun , z_fun , 1 , 0 );
378433 ZVAL_ZVAL (& ra -> z_dist , z_dist , 1 , 0 );
379434
435+ /* init continuum */
436+ if (consistent ) {
437+ ra -> continuum = ra_make_continuum (ra -> hosts , ra -> count );
438+ }
439+
380440 return ra ;
381441}
382442
@@ -480,7 +540,23 @@ ra_find_node(RedisArray *ra, const char *key, int key_len, int *out_pos TSRMLS_D
480540 }
481541
482542 /* get position on ring */
483- pos = (int )((ret ^ 0xffffffff ) * ra -> count / 0xffffffff );
543+ if (ra -> continuum ) {
544+ int left = 0 , right = ra -> continuum -> nb_points ;
545+ while (left < right ) {
546+ i = (int )((left + right ) / 2 );
547+ if (ra -> continuum -> points [i ].value < ret ) {
548+ left = i + 1 ;
549+ } else {
550+ right = i ;
551+ }
552+ }
553+ if (right == ra -> continuum -> nb_points ) {
554+ right = 0 ;
555+ }
556+ pos = ra -> continuum -> points [right ].index ;
557+ } else {
558+ pos = (int )((ret ^ 0xffffffff ) * ra -> count / 0xffffffff );
559+ }
484560 } else {
485561 pos = ra_call_distributor (ra , key , key_len TSRMLS_CC );
486562 if (pos < 0 || pos >= ra -> count ) {
0 commit comments