File Coverage

blib/lib/RedisDB/Cluster.pm
Criterion Covered Total %
statement 29 247 11.7
branch 4 144 2.7
condition 0 21 0.0
subroutine 8 22 36.3
pod 10 10 100.0
total 51 444 11.4


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