File Coverage

blib/lib/Hash/ConsistentHash.pm
Criterion Covered Total %
statement 9 63 14.2
branch 0 14 0.0
condition 0 8 0.0
subroutine 3 7 42.8
pod 3 3 100.0
total 15 95 15.7


line stmt bran cond sub pod time code
1             package Hash::ConsistentHash;
2              
3 1     1   24435 use v5.10;
  1         4  
  1         45  
4 1     1   6 use strict;
  1         2  
  1         34  
5 1     1   6 use warnings;
  1         6  
  1         727  
6              
7             =head1 NAME
8              
9             Hash::ConsistentHash - Constant hash algorithm
10              
11             =head1 VERSION
12              
13             Version 0.05
14              
15             =cut
16              
17             our $VERSION = '0.07';
18              
19              
20             =head1 SYNOPSIS
21              
22             use Hash::ConsistentHash;
23             use String::CRC32;
24              
25             my $chash = Hash::ConsistentHash->new(
26             buckets => [qw(10.0.0.1 10.0.0.2 10.0.0.3 10.0.0.4)],
27             hash_func => \&crc32
28             );
29             # get just one bucket
30             my $server = $chash->get_bucket('foo');
31              
32             # or get a serie of non-repeating buckets through iterator
33             my $next = $chash->lookup('bar');
34             $server = $next->(); # get bucket
35             # do stuff with $server
36             $server = $next->(); # get another bucket
37             ...
38              
39             =head1 DESCRIPTION
40              
41             Hash::ConsistentHash algorithm distributes keys over fixed number of buckets.
42             Constant hash distribution means that if we add a bucket to a hash with N
43             buckets filled with M keys we have to reassign only M/(N+1) keys to new
44             buckets.
45              
46             What puts apart this module from all similar modules available is that you
47             could ask for non-repeatable series of buckets. Using this property you
48             could implement not only consistent distribution but also redundancy - one
49             key to be directed to more than one bucket.
50              
51             =head1 METHODS
52              
53             =head2 new
54              
55             Creates ConsistentHash object. It accept following params:
56              
57             =over
58              
59             =item hash_func
60              
61             Hash function to be used on keys and buckets
62              
63             =item buckets
64              
65             Arrayref or Hashref. If buckets are given as arrayref they will have
66             same weight. If given as hashref, every bucket could have differend
67             weight.
68              
69             Examples:
70              
71             # All buckets have same weight so they will hold equal amount of keys
72             my $chash = Hash::ConsistentHash->new(
73             buckets => [qw(A B C)],
74             hash_func=>\&crc32 );
75              
76             # Bucket "B" will hold twice the amount of keys of bucket A or C
77             my $chash = Cash::ConsistentHash->new(
78             buckets => {A=>1, B=>2, C=>1},
79             hash_func=>\&crc32 );
80              
81              
82             =back
83              
84             =cut
85              
86             sub new {
87 0     0 1   my $self = shift;
88 0   0       my $class = ref($self)||$self;
89 0           $self = bless {bukets=>0}, $class;
90              
91 0           my %params = @_;
92 0 0         die "You showld specify hash_func coderef"
93             unless ref($params{hash_func}) eq 'CODE';
94              
95 0           $self->{hash_func} = $params{hash_func};
96 0   0       $self->{mask} = $params{mask} // 0xFF;
97              
98 0           my (@dest,$weight);
99 0 0         if (ref $params{buckets} eq 'ARRAY'){
    0          
100 0           @dest = @{$params{buckets}};
  0            
101 0           $weight= { map {$_ => 1 } @dest };
  0            
102             }elsif(ref $params{buckets} eq 'HASH'){
103 0           @dest = keys %{$params{buckets}};
  0            
104 0           $weight= $params{buckets};
105             }
106 0 0         return unless @dest;
107 0           $self->{buckets} = scalar(@dest);
108 0           my $total_weight = 0;
109 0           for my $bucket (@dest){
110 0           $total_weight += $weight->{$bucket};
111             }
112 0           my $buckets_per_waight = int($self->{mask}/$total_weight);
113 0           while( $buckets_per_waight < 5 ){
114 0           $self->{mask} |= $self->{mask} << 1;
115 0           $buckets_per_waight = int($self->{mask}/$total_weight);
116             }
117 0           for my $bucket (@dest){
118 0           srand($self->{hash_func}->($bucket));
119 0           my $bucks = $buckets_per_waight * $weight->{$bucket};
120 0           while ($bucks > 0) {
121 0           my $n = int(rand($self->{mask}));
122 0 0         next if defined $self->{ring}->[$n];
123 0           $self->{ring}->[$n] = $bucket;
124 0           $bucks--;
125             }
126             }
127 0           for my $n (0..$self->{mask}){
128 0   0       $self->{ring}->[$n] //= shift @dest;
129             }
130 0           return $self;
131             }
132              
133              
134             =head2 lookup
135              
136             Lookup a key in the hash. Accept one param - the key. Returns an iterator
137             over the hash buckets.
138              
139             Example:
140              
141             my $chash = Hash::ConsistentHash->new(
142             buckets => [qw(A B C)],
143             hash_func=>\&crc32 );
144              
145             my $next = $chash->lookup('foo');
146             my $bucket = $next->(); # B
147             $bucket = $next->(); # A
148             $bucket = $next->(); # C, hash is exhausted
149             $bucket = $next->(); # A
150             ...
151              
152             Returned buckets will not repeat until all buckets are exhausted.
153              
154             =cut
155              
156             sub lookup {
157 0     0 1   my ($self,$key) = @_;
158 0           my $idx = $self->{hash_func}->($key) & $self->{mask};
159 0           my $ring= $self->{ring};
160 0           my %seen;
161 0           my $returned = 0;
162             return sub {
163             # start from the beggining if we have already returned all buckets
164 0 0   0     if ($returned >= $self->{buckets}){
165 0           $returned = 0;
166 0           %seen = ();
167             }
168 0           while($seen{$ring->[$idx]}){
169 0           $idx++;
170 0 0         $idx = 0 if $idx > $self->{mask};
171             }
172 0           $seen{$ring->[$idx]}=1;
173 0           $returned ++;
174 0           return $ring->[$idx];
175             }
176 0           }
177              
178             =head2 get_bucket
179              
180             Lookup a key in the hash. Accept one param - the key. Returns a bucket.
181              
182             Example:
183              
184             my $chash = Hash::ConsistentHash->new(
185             buckets => [qw(A B C)],
186             hash_func=>\&crc32 );
187              
188             my $bucket = $chash->get_bucket('foo');
189              
190             =cut
191              
192             sub get_bucket {
193 0     0 1   my ($self,$key) = @_;
194 0           my $idx = $self->{hash_func}->($key) & $self->{mask};
195 0           return $self->{ring}->[$idx];
196             }
197              
198             =head1 SEE ALSO
199              
200             L, L
201              
202             =head1 AUTHOR
203              
204             Luben Karavelov, C<< >>
205              
206             =head1 BUGS
207              
208             Please report any bugs or feature requests to C, or through
209             the web interface at L. I will be notified, and then you'll
210             automatically be notified of progress on your bug as I make changes.
211              
212              
213             =head1 SUPPORT
214              
215             You can find documentation for this module with the perldoc command.
216              
217             perldoc Hash::ConsistentHash
218              
219              
220             You can also look for information at:
221              
222             =over 4
223              
224             =item * GIT repository with the latest stuff
225              
226             L
227              
228             L
229              
230             =item * RT: CPAN's request tracker (report bugs here)
231              
232             L
233              
234             =item * AnnoCPAN: Annotated CPAN documentation
235              
236             L
237              
238             =item * CPAN Ratings
239              
240             L
241              
242             =item * Search CPAN
243              
244             L
245              
246             =back
247              
248             =head1 LICENSE AND COPYRIGHT
249              
250             Copyright 2011 Luben Karavelov.
251              
252             This program is free software; you can redistribute it and/or modify it
253             under the terms of either: the GNU General Public License as published
254             by the Free Software Foundation; or the Artistic License.
255              
256             See http://dev.perl.org/licenses/ for more information.
257              
258              
259             =cut
260              
261             1; # End of Hash::ConsistentHash