File Coverage

blib/lib/RedisDB/Cluster.pm
Criterion Covered Total %
statement 29 248 11.6
branch 4 144 2.7
condition 0 21 0.0
subroutine 8 23 34.7
pod 10 10 100.0
total 51 446 11.4


line stmt bran cond sub pod time code
1             package RedisDB::Cluster;
2              
3 2     2   99218 use strict;
  2         11  
  2         49  
4 2     2   9 use warnings;
  2         3  
  2         77  
5             our $VERSION = "2.55";
6             $VERSION = eval $VERSION;
7              
8 2     2   8 use Carp;
  2         3  
  2         209  
9 2     2   483 use RedisDB;
  2         3  
  2         60  
10 2     2   522 use Time::HiRes qw(usleep);
  2         1425  
  2         13  
11              
12             our $DEBUG = 0;
13              
14             # use util/generate_key_positions.pl to generate this
15             # command / first key position
16             my %key_pos = (
17             append => 1,
18             bitcount => 1,
19             bitop => 2,
20             bitpos => 1,
21             blpop => 1,
22             brpop => 1,
23             brpoplpush => 1,
24             decr => 1,
25             decrby => 1,
26             del => 1,
27             dump => 1,
28             exists => 1,
29             expire => 1,
30             expireat => 1,
31             get => 1,
32             getbit => 1,
33             getrange => 1,
34             getset => 1,
35             hdel => 1,
36             hexists => 1,
37             hget => 1,
38             hgetall => 1,
39             hincrby => 1,
40             hincrbyfloat => 1,
41             hkeys => 1,
42             hlen => 1,
43             hmget => 1,
44             hmset => 1,
45             hscan => 1,
46             hset => 1,
47             hsetnx => 1,
48             hvals => 1,
49             incr => 1,
50             incrby => 1,
51             incrbyfloat => 1,
52             lindex => 1,
53             linsert => 1,
54             llen => 1,
55             lpop => 1,
56             lpush => 1,
57             lpushx => 1,
58             lrange => 1,
59             lrem => 1,
60             lset => 1,
61             ltrim => 1,
62             mget => 1,
63             move => 1,
64             mset => 1,
65             msetnx => 1,
66             object => 2,
67             persist => 1,
68             pexpire => 1,
69             pexpireat => 1,
70             pfadd => 1,
71             pfcount => 1,
72             pfmerge => 1,
73             psetex => 1,
74             pttl => 1,
75             rename => 1,
76             renamenx => 1,
77             restore => 1,
78             'restore-asking' => 1,
79             rpop => 1,
80             rpoplpush => 1,
81             rpush => 1,
82             rpushx => 1,
83             sadd => 1,
84             scard => 1,
85             sdiff => 1,
86             sdiffstore => 1,
87             set => 1,
88             setbit => 1,
89             setex => 1,
90             setnx => 1,
91             setrange => 1,
92             sinter => 1,
93             sinterstore => 1,
94             sismember => 1,
95             smembers => 1,
96             smove => 1,
97             sort => 1,
98             spop => 1,
99             srandmember => 1,
100             srem => 1,
101             sscan => 1,
102             strlen => 1,
103             substr => 1,
104             sunion => 1,
105             sunionstore => 1,
106             ttl => 1,
107             type => 1,
108             watch => 1,
109             zadd => 1,
110             zcard => 1,
111             zcount => 1,
112             zincrby => 1,
113             zlexcount => 1,
114             zrange => 1,
115             zrangebylex => 1,
116             zrangebyscore => 1,
117             zrank => 1,
118             zrem => 1,
119             zremrangebylex => 1,
120             zremrangebyrank => 1,
121             zremrangebyscore => 1,
122             zrevrange => 1,
123             zrevrangebylex => 1,
124             zrevrangebyscore => 1,
125             zrevrank => 1,
126             zscan => 1,
127             zscore => 1,
128             );
129              
130             =head1 NAME
131              
132             RedisDB::Cluster - client for redis cluster
133              
134             =head1 SYNOPSIS
135              
136             my $cluster = RedisDB::Cluster->new( startup_nodes => \@nodes );
137             $cluster->set( 'foo', 'bar' );
138             my $res = $cluster->get('foo');
139              
140             =head1 DESCRIPTION
141              
142             This module allows you to access redis cluster.
143              
144             =head1 METHODS
145              
146             =cut
147              
148             =head2 $self->new(startup_nodes => \@nodes)
149              
150             create a new connection to cluster. Startup nodes should contain array of
151             hashes that contains addresses of some nodes in the cluster. Each hash should
152             contain 'host' and 'port' elements. Constructor will try to connect to nodes
153             from the list and from the first node to which it will be able to connect it
154             will retrieve information about all cluster nodes and slots mappings.
155              
156             =cut
157              
158             sub new {
159 0     0 1 0 my ( $class, %params ) = @_;
160              
161             my $self = {
162             _slots => [],
163             _connections => {},
164             _nodes => $params{startup_nodes},
165 0         0 };
166 0 0       0 $self->{no_slots_initialization} = 1 if $params{no_slots_initialization};
167              
168 0         0 bless $self, $class;
169 0         0 $self->_initialize_slots;
170              
171 0         0 return $self;
172             }
173              
174             sub _initialize_slots {
175 0     0   0 my $self = shift;
176              
177 0 0       0 return if $self->{no_slots_initialization};
178 0 0 0     0 unless ( $self->{_nodes} and @{ $self->{_nodes} } ) {
  0         0  
179 0         0 confess "list of cluster nodes is empty";
180             }
181              
182 0         0 my %new_nodes;
183             my $new_nodes;
184 0         0 for my $node ( @{ $self->{_nodes} } ) {
  0         0  
185 0         0 my $redis = _connect_to_node( $self, $node );
186 0 0       0 next unless $redis;
187              
188 0         0 my $nodes = $redis->cluster_nodes;
189 0 0       0 next if ref $nodes =~ /^RedisDB::Error/;
190 0         0 $new_nodes = $nodes;
191 0         0 for (@$nodes) {
192 0         0 $new_nodes{"$_->{host}:$_->{port}"}++;
193             }
194              
195 0         0 my $slots = $redis->cluster('SLOTS');
196 0 0       0 confess "got an error trying retrieve a list of cluster slots: $slots"
197             if ref $slots =~ /^RedisDB::Error/;
198 0         0 for (@$slots) {
199 0         0 my ( $ip, $port ) = @{ $_->[2] };
  0         0  
200 0         0 my $node_key = "$ip:$port";
201 0         0 for ( $_->[0] .. $_->[1] ) {
202 0         0 $self->{_slots}[$_] = $node_key;
203             }
204             }
205 0         0 last;
206             }
207              
208 0 0 0     0 unless ( $new_nodes and @$new_nodes ) {
209 0         0 confess "couldn't get list of cluster nodes";
210             }
211 0         0 $self->{_nodes} = $new_nodes;
212              
213             # close connections to nodes that are not in cluster
214 0         0 for ( keys %{ $self->{_connections} } ) {
  0         0  
215 0 0       0 delete $self->{_connections}{$_} unless $new_nodes{$_};
216             }
217              
218 0         0 return;
219             }
220              
221             =head2 $self->execute($command, @args)
222              
223             sends command to redis and returns the reply. It determines the cluster node to
224             send command to from the first key in I<@args>, sending commands that does not
225             include key as an argument is not supported. If I<@args> contains several keys,
226             all of them should belong to the same slot, otherwise redis-server will return
227             an error if some of the keys are stored on a different node.
228              
229             Module also defines wrapper methods with names matching corresponding redis
230             commands, so you can use
231              
232             $cluster->set( "foo", "bar" );
233             $cluster->inc("baz");
234              
235             instead of
236              
237             $cluster->execute( "set", "foo", "bar" );
238             $cluster->execute( "inc", "baz" );
239              
240             =cut
241              
242             sub execute {
243 0     0 1 0 my $self = shift;
244 0         0 my @args = @_;
245              
246 0         0 my $command = lc $args[0];
247 0 0       0 confess "Command $command does not have key" unless $key_pos{$command};
248 0         0 my $key = $args[ $key_pos{$command} ];
249 0 0       0 confess "Key is not specified in: ", join " ", @args unless length $key;
250              
251 0 0       0 if ( $self->{_refresh_slots} ) {
252 0         0 $self->_initialize_slots;
253             }
254 0         0 my $slot = key_slot($key);
255 0   0     0 my $node_key = $self->{_slots}[$slot]
256             || "$self->{_nodes}[0]{host}:$self->{_nodes}[0]{port}";
257 0         0 my $asking;
258             my $last_connection;
259              
260 0         0 my $attempts = 10;
261 0         0 while ( $attempts-- ) {
262 0         0 my $redis = $self->{_connections}{$node_key};
263 0 0       0 unless ($redis) {
264 0         0 my ( $host, $port ) = split /:([^:]+)$/, $node_key;
265 0         0 $redis = _connect_to_node(
266             $self,
267             {
268             host => $host,
269             port => $port
270             }
271             );
272             }
273              
274 0         0 my $res;
275 0 0       0 if ($redis) {
276 0 0       0 $redis->asking(RedisDB::IGNORE_REPLY) if $asking;
277 0         0 $asking = 0;
278 0         0 $res = $redis->execute(@args);
279             }
280             else {
281 0         0 $res = RedisDB::Error::DISCONNECTED->new(
282             "Couldn't connect to redis server at $node_key");
283             }
284              
285 0 0       0 if ( ref $res eq 'RedisDB::Error::MOVED' ) {
    0          
    0          
286 0 0       0 if ( $res->{slot} ne $slot ) {
287 0         0 confess
288             "Incorrectly computed slot for key '$key', ours $slot, theirs $res->{slot}";
289             }
290 0 0       0 warn "slot $slot moved to $res->{host}:$res->{port}" if $DEBUG;
291 0         0 $node_key = $self->{_slots}[$slot] = "$res->{host}:$res->{port}";
292 0         0 $self->{_refresh_slots} = 1;
293 0         0 next;
294             }
295             elsif ( ref $res eq 'RedisDB::Error::ASK' ) {
296 0 0       0 warn "asking $res->{host}:$res->{port} about slot $slot" if $DEBUG;
297 0         0 $node_key = "$res->{host}:$res->{port}";
298 0         0 $asking = 1;
299 0         0 next;
300             }
301             elsif ( ref $res eq 'RedisDB::Error::DISCONNECTED' ) {
302 0 0       0 warn "$res" if $DEBUG;
303 0         0 delete $self->{_connections}{$node_key};
304 0         0 usleep 100_000;
305 0 0 0     0 if ( $last_connection and $last_connection eq $node_key ) {
306              
307             # if we couldn't reconnect to host, then refresh slots table
308 0 0       0 warn "refreshing slots table" if $DEBUG;
309 0         0 $self->_initialize_slots;
310              
311             # if it's still the same host, then just return the error
312 0 0       0 return $res if $self->{_slots}[$slot] eq $node_key;
313 0 0       0 warn "got a new host for the slot" if $DEBUG;
314             }
315             else {
316 0 0       0 warn "trying to reconnect" if $DEBUG;
317 0         0 $last_connection = $node_key;
318             }
319 0         0 next;
320             }
321 0         0 return $res;
322             }
323              
324 0         0 return RedisDB::Error::DISCONNECTED->new(
325             "Couldn't send command after 10 attempts");
326             }
327              
328             for my $command (keys %key_pos) {
329 2     2   2609 no strict 'refs';
  2         4  
  2         4465  
330 0     0   0 *{ __PACKAGE__ . "::$command" } = sub { execute(shift, $command, @_) };
        0      
331             }
332              
333             =head2 $self->random_connection
334              
335             return RedisDB object that is connected to some node of the cluster. Note, that
336             in most cases this method will return the same connection every time.
337              
338             =cut
339              
340             sub random_connection {
341 0     0 1 0 my $self = shift;
342 0         0 my ($connection) = values %{ $self->{_connections} };
  0         0  
343 0 0       0 unless ($connection) {
344 0         0 for ( @{ $self->{_nodes} } ) {
  0         0  
345 0         0 $connection = _connect_to_node( $self, $_ );
346 0 0       0 last if $connection;
347             }
348             }
349 0         0 return $connection;
350             }
351              
352             =head2 $self->node_for_slot($slot, %params)
353              
354             return L object connected to cluster node that is master node for the
355             given slot. I<%params> are passed to RedisDB constructor as is. This method is
356             using information about mappings between slots and nodes that is cached by
357             RedisDB::Cluster object, if there were changes in cluster configuration since
358             the last time that information has been obtained, then the method will return
359             RedisDB object connected to a wrong server, you can detect that situation by
360             checking results returned by server, it should return MOVED or ASK error if you
361             accessing the wrong server or slot is being migrated. Each time you call this
362             method a new RedisDB object is returned and consequently a new connection is
363             being established, so it is not something very fast.
364              
365             =cut
366              
367             sub node_for_slot {
368 0     0 1 0 my ( $self, $slot, %params ) = @_;
369              
370 0 0       0 if ( $self->{_refresh_slots} ) {
371 0         0 $self->_initialize_slots;
372             }
373 0         0 $DB::single = 1;
374 0 0       0 my $node_key = $self->{_slots}[$slot]
375             or confess "Don't know master node for slot $slot";
376 0         0 my ( $host, $port ) = split /:([^:]+)$/, $node_key;
377 0         0 return RedisDB->new(
378             %params,
379             host => $host,
380             port => $port
381             );
382             }
383              
384             =head2 $self->node_for_key($key, %params)
385              
386             same as I but accepts key instead of slot number as the first
387             argument. Internally just calculates the slot number and then invokes
388             node_for_slot method.
389              
390             =cut
391              
392             sub node_for_key {
393 0     0 1 0 my ($self, $key, %params) = @_;
394              
395 0         0 return $self->node_for_slot(key_slot($key), %params);
396             }
397              
398             =head1 CLUSTER MANAGEMENT METHODS
399              
400             The following methods can be used for cluster management -- to add or remove a
401             node, or migrate slot from one node to another.
402              
403             =cut
404              
405             =head2 $self->add_new_node($address[, $master_id])
406              
407             attach node with the specified I<$address> to the cluster. If I<$master_id> is
408             specified, the new node is configured as a replica of the master with the
409             specified ID, otherwise it will be a master node itself. Address should be
410             specified as a hash containing I and I elements.
411              
412             =cut
413              
414             sub add_new_node {
415 0     0 1 0 my ( $self, $addr, $master_id ) = @_;
416 0         0 $addr = _ensure_hash_address($addr);
417              
418 0         0 my $redis = _connect_to_node( $self, $addr );
419 0         0 my $ok;
420 0         0 for my $node ( @{ $self->{_nodes} } ) {
  0         0  
421             $redis->cluster( 'MEET', $node->{host}, $node->{port},
422 0 0 0 0   0 sub { $ok++ if not ref $_[1] and $_[1] eq 'OK'; warn $_[1] if ref $_[1]; }
  0 0       0  
423 0         0 );
424             }
425 0         0 $redis->mainloop;
426 0 0       0 croak "failed to attach node to cluster" unless $ok;
427              
428 0 0       0 if ($master_id) {
429 0         0 my $attempt = 0;
430 0         0 my $nodes = $redis->cluster_nodes;
431 0         0 while ( not grep { $_->{node_id} eq $master_id } @$nodes ) {
  0         0  
432 0 0       0 croak "failed to start replication from $master_id - node is not present"
433             if $attempt++ >= 10;
434 0         0 usleep 100_000 * $attempt;
435 0         0 $nodes = $redis->cluster_nodes;
436             }
437 0         0 my $res = $redis->cluster( 'REPLICATE', $master_id );
438 0 0       0 croak $res if ref $res =~ /^RedisDB::Error/;
439             }
440              
441 0         0 return 'OK';
442             }
443              
444             =head2 $self->migrate_slot($slod, $destination_node)
445              
446             migrates specified slot to the given I<$destination_node> from the current node
447             responsible for this slot. Destinations node should be specified as a hash
448             containing I and I elements. For details check "Cluster live
449             reconfiguration" section in the L
450             Specification|http://redis.io/topics/cluster-spec>.
451              
452             =cut
453              
454             sub migrate_slot {
455 0     0 1 0 my ( $self, $slot, $dst ) = @_;
456              
457             # make sure we have up to date information about slots mapping
458 0         0 $self->_initialize_slots;
459 0         0 my $src_key = $self->{_slots}[$slot];
460 0 0       0 confess "mapping for slot $slot is not defined" unless $src_key;
461              
462             # destination node should be part of the cluster
463 0 0       0 $dst = $self->_get_node_info($dst)
464             or confess "destination node is seems not a part of the cluster";
465 0         0 my $dst_key = "$dst->{host}:$dst->{port}";
466 0 0       0 warn "migrating slot $slot from $src_key to $dst_key" if $DEBUG;
467              
468             # if slot is already on destination node, just return
469 0 0       0 return if $src_key eq $dst_key;
470 0         0 my $src = $self->_get_node_info($src_key);
471              
472 0 0       0 my $dst_redis = _connect_to_node( $self, $dst )
473             or confess "couldn't connect to destination node";
474 0 0       0 my $src_redis = _connect_to_node( $self, $src )
475             or confess "couldn't connect to source node";
476              
477             # set importing/migrating state for the slot
478             my $res =
479 0         0 $dst_redis->cluster( 'setslot', $slot, 'importing', $src->{node_id} );
480 0 0       0 confess "$res" unless "$res" eq 'OK';
481             $res =
482 0         0 $src_redis->cluster( 'setslot', $slot, 'migrating', $dst->{node_id} );
483 0 0       0 confess "$res" unless "$res" eq 'OK';
484 0 0       0 warn "set slots on dst/src nodes to importing/migrating state" if $DEBUG;
485              
486             # migrate all keys from src to dst
487 0         0 my $migrated = 0;
488 0         0 while (1) {
489 0         0 my $keys = $src_redis->cluster( 'getkeysinslot', $slot, 1000 );
490 0 0       0 confess "Migration failed: $keys" if ref $keys =~ /^RedisDB::Error/;
491 0 0       0 last unless @$keys;
492 0         0 for (@$keys) {
493 0         0 $res = $src_redis->migrate( $dst->{host}, $dst->{port}, $_, 0, 60 );
494 0 0       0 confess "Migration failed: $res" unless "$res" eq 'OK';
495 0         0 $migrated++;
496             }
497             }
498 0 0       0 warn "migrated $migrated keys from the slot" if $DEBUG;
499              
500 0         0 $res = $dst_redis->cluster( 'setslot', $slot, 'node', $dst->{node_id} );
501 0 0       0 confess "$res" unless "$res" eq 'OK';
502 0         0 $res = $src_redis->cluster( 'setslot', $slot, 'node', $src->{node_id} );
503 0 0       0 confess "$res" unless "$res" eq 'OK';
504 0 0       0 warn "migration is finished" if $DEBUG;
505              
506 0         0 return 1;
507             }
508              
509             =head2 $self->remove_node($node)
510              
511             removes node from the cluster. If the node is a slave, it simply shuts the node
512             down and sends CLUSTER FORGET command to all other cluster nodes. If the node
513             is a master node, the method first migrates all slots from it to other nodes.
514              
515             =cut
516              
517             sub remove_node {
518 0     0 1 0 my ( $self, $node ) = @_;
519              
520 0         0 $self->_initialize_slots;
521 0         0 $node = $self->_get_node_info($node);
522 0         0 my $node_key = "$node->{host}:$node->{port}";
523 0 0       0 if ( $node->{flags}{master} ) {
524 0         0 my @masters;
525             my @slaves;
526 0         0 for ( @{ $self->{_nodes} } ) {
  0         0  
527 0 0       0 if ( $_->{flags}{slave} ) {
528 0 0       0 push @slaves, $_ if $_->{master_id} eq $node->{node_id};
529 0         0 next;
530             }
531 0 0       0 next if $_->{node_id} eq $node->{node_id};
532 0         0 push @masters, $_;
533             }
534 0         0 my @slots;
535             my %slots_at;
536 0         0 for my $i ( 0 .. 16383 ) {
537 0 0       0 push @slots, $i if $self->{_slots}[$i] eq $node_key;
538 0         0 $slots_at{ $self->{_slots}[$i] }++;
539             }
540 0 0       0 if ($DEBUG) {
541 0         0 warn "Node to remove is a master with "
542             . scalar(@slaves)
543             . "\nIt holds "
544             . scalar(@slots)
545             . " slots."
546             . "\nThere are "
547             . scalar(@masters)
548             . " other masters in cluster\n";
549             }
550 0         0 my $slots_per_master = int( 16384 / @masters + 1 );
551 0         0 my $slaves_per_master = int( @slaves / @masters + 1 );
552 0         0 for my $master (@masters) {
553 0         0 my $key = "$master->{host}:$master->{port}";
554 0         0 for ( $slots_at{$key} + 1 .. $slots_per_master ) {
555 0         0 my $slot = shift @slots;
556 0 0       0 last unless defined $slot;
557 0         0 $self->migrate_slot( $slot, $master );
558             }
559 0         0 for ( 1 .. $slaves_per_master ) {
560 0 0       0 my $slave = shift @slaves or last;
561 0 0       0 my $redis = $self->_connect_to_node($slave) or next;
562 0         0 my $res = $redis->cluster( 'replicate', $master->{node_id} );
563 0 0       0 warn "Failed to reconfigure slave $slave->{host}:$slave->{port}"
564             . " to replicate from $master->{node_id}: $res"
565             if ref $res =~ /^RedisDB::Error/;
566             }
567             }
568             }
569              
570 0         0 my $redis = delete $self->{_connections}{$node_key};
571 0         0 $redis->shutdown;
572 0         0 my @nodes;
573 0         0 for ( @{ $self->{_nodes} } ) {
  0         0  
574 0 0       0 next if $_->{node_id} eq $node->{node_id};
575 0         0 push @nodes, $_;
576 0 0       0 my $redis = $self->_connect_to_node($_) or next;
577 0         0 my $res = $redis->cluster( 'forget', $node->{node_id} );
578 0 0       0 warn "$_->{host}:$_->{port} could not forget the node: $res"
579             if $res =~ /^RedisDB::Error/;
580             }
581 0         0 $self->{_nodes} = \@nodes;
582              
583 0         0 return 1;
584             }
585              
586             sub _get_node_info {
587 0     0   0 my ( $self, $node ) = @_;
588 0         0 $node = _ensure_hash_address($node);
589 0         0 for ( @{ $self->{_nodes} } ) {
  0         0  
590 0 0 0     0 return $_ if $node->{host} eq $_->{host} and $node->{port} eq $_->{port};
591             }
592 0         0 return;
593             }
594              
595             sub _ensure_hash_address {
596 0     0   0 my $addr = shift;
597 0 0       0 unless ( ref $addr eq 'HASH' ) {
598 0         0 my ( $host, $port ) = split /:([^:]+)$/, $addr;
599 0 0 0     0 croak "invalid address spec: $addr" unless $host and $port;
600 0         0 $addr = {
601             host => $host,
602             port => $port
603             };
604             }
605 0         0 return $addr;
606             }
607              
608             sub _connect_to_node {
609 0     0   0 my ( $self, $node ) = @_;
610 0         0 my $host_key = "$node->{host}:$node->{port}";
611 0 0       0 unless ( $self->{_connections}{$host_key} ) {
612             my $redis = RedisDB->new(
613             host => $node->{host},
614             port => $node->{port},
615 0         0 raise_error => 0,
616             );
617 0 0       0 $self->{_connections}{$host_key} = $redis->{_socket} ? $redis : undef;
618             }
619 0         0 return $self->{_connections}{$host_key};
620             }
621              
622             =head1 SERVICE FUNCTIONS
623              
624             =cut
625              
626             my @crc16tab = (
627             0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
628             0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
629             0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
630             0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
631             0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
632             0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
633             0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
634             0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
635             0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
636             0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
637             0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
638             0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
639             0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
640             0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
641             0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
642             0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
643             0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
644             0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
645             0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
646             0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
647             0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
648             0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
649             0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
650             0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
651             0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
652             0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
653             0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
654             0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
655             0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
656             0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
657             0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
658             0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0,
659             );
660              
661             =head2 crc16($buf)
662              
663             compute crc16 for the specified buffer as defined in redis cluster
664             specification
665              
666             =cut
667              
668             sub crc16 {
669 7     7 1 4002 my $buf = shift;
670 7 100       16 if ( utf8::is_utf8($buf) ) {
671 1         9 die "Can't compute crc16 for string with wide characters.\n"
672             . "You should encode strings you pass to redis as bytes";
673             }
674 6         8 my $crc = 0;
675 6         16 for ( split //, $buf ) {
676 40         61 $crc =
677             ( $crc << 8 & 0xFF00 ) ^ $crc16tab[ ( ( $crc >> 8 ) ^ ord ) & 0x00FF ];
678             }
679 6         23 return $crc;
680             }
681              
682             =head2 key_slot($key)
683              
684             return slot number for the given I<$key>
685              
686             =cut
687              
688             sub key_slot {
689 4     4 1 2379 my $key = shift;
690              
691 4 100       16 if ( $key =~ /\{([^}]+)\}/ ) {
692 2         5 $key = $1;
693             }
694              
695 4         8 return crc16($key) & 16383;
696             }
697              
698             1;
699              
700             __END__