line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Cache::Elasticache::Memcache; |
2
|
|
|
|
|
|
|
|
3
|
3
|
|
|
3
|
|
170879
|
use strict; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
81
|
|
4
|
3
|
|
|
3
|
|
15
|
use warnings; |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
105
|
|
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
=pod |
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
=begin html |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
=end html |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
=head1 NAME |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
Cache::Elasticache::Memcache - A wrapper for L with support for AWS's auto reconfiguration mechanism |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
=head1 SYNOPSIS |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
use Cache::Elasticache::Memcache; |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
my $memd = new Cache::Elasticache::Memcache->new({ |
25
|
|
|
|
|
|
|
config_endpoint => 'foo.bar', |
26
|
|
|
|
|
|
|
update_period => 180, |
27
|
|
|
|
|
|
|
# All other options are passed on to Cache::Memcached::Fast |
28
|
|
|
|
|
|
|
... |
29
|
|
|
|
|
|
|
}); |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
# Will update the server list from the configuration endpoint |
32
|
|
|
|
|
|
|
$memd->updateServers(); |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
# Will update the serverlist from the configuration endpoint if the time since |
35
|
|
|
|
|
|
|
# the last time the server list was checked is greater than the update period |
36
|
|
|
|
|
|
|
# specified when the $memd object was created. |
37
|
|
|
|
|
|
|
$memd->checkServers(); |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
# Class method to retrieve a server list from a configuration endpoint. |
40
|
|
|
|
|
|
|
Cache::Elasticache::Memcache->getServersFromEndpoint('foo.bar'); |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
# All other supported methods are handled by Cache::Memcached::Fast |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
# N.B. This library is currently under development |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
=head1 DESCRIPTION |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
A wrapper for L with support for AWS's auto reconfiguration mechanism. It makes use of an AWS elasticache memcached cluster's configuration endpoint to disctover the memcache servers in the cluster and periodically check the current server list to adapt to a changing cluster. |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
=head1 UNDER DEVELOPMENT DISCALIMER |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
N.B. This module is still under development. It should work, but things may change under the hood. I plan to imporove the resiliance with better timeout handling of communication when updating the server list. Also I'm investigating switching to Dist::Milla. I'm open to suggestions, ideas and pull requests. |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
=cut |
55
|
|
|
|
|
|
|
|
56
|
3
|
|
|
3
|
|
14
|
use Carp; |
|
3
|
|
|
|
|
9
|
|
|
3
|
|
|
|
|
162
|
|
57
|
3
|
|
|
3
|
|
3307
|
use IO::Socket::IP; |
|
3
|
|
|
|
|
130475
|
|
|
3
|
|
|
|
|
17
|
|
58
|
3
|
|
|
3
|
|
5986
|
use Cache::Memcached::Fast; |
|
3
|
|
|
|
|
21624
|
|
|
3
|
|
|
|
|
1029
|
|
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
our $VERSION = '0.0.3'; |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
=pod |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
=head1 CONSTRUCTOR |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
Cache::Elasticache::Memcache->new({ |
67
|
|
|
|
|
|
|
config_endpoint => 'foo.bar', |
68
|
|
|
|
|
|
|
update_period => 180, |
69
|
|
|
|
|
|
|
... |
70
|
|
|
|
|
|
|
}) |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
=head2 Constructor parameters |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
=over |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
=item config_endpoint |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
AWS elasticache memcached cluster config endpoint location |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
=item update_period |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
The minimum period (in seconds) to wait between updating the server list. Defaults to 180 seconds |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
=back |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
=cut |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
sub new { |
89
|
9
|
|
|
9
|
0
|
103864
|
my $class = shift; |
90
|
9
|
|
|
|
|
19
|
my ($conf) = @_; |
91
|
9
|
|
|
|
|
27
|
my $self = bless {}, $class; |
92
|
|
|
|
|
|
|
|
93
|
9
|
50
|
|
|
|
49
|
my $args = (@_ == 1) ? shift : { @_ }; # hashref-ify args |
94
|
|
|
|
|
|
|
|
95
|
9
|
100
|
|
|
|
55
|
croak "config_endpoint must be speccified" if (!defined $args->{'config_endpoint'}); |
96
|
8
|
100
|
|
|
|
53
|
croak "servers is not a valid constructors parameter" if (defined $args->{'servers'}); |
97
|
|
|
|
|
|
|
|
98
|
7
|
|
|
|
|
15
|
$self->{'config_endpoint'} = delete @{$args}{'config_endpoint'}; |
|
7
|
|
|
|
|
40
|
|
99
|
|
|
|
|
|
|
|
100
|
7
|
50
|
|
|
|
57
|
$args->{servers} = $class->getServersFromEndpoint($self->{'config_endpoint'}) if(defined $self->{'config_endpoint'}); |
101
|
7
|
|
|
|
|
38
|
$self->{_last_update} = time; |
102
|
|
|
|
|
|
|
|
103
|
7
|
100
|
|
|
|
38
|
$self->{update_period} = exists $args->{update_period} ? $args->{update_period} : 180; |
104
|
|
|
|
|
|
|
|
105
|
7
|
|
|
|
|
18
|
$self->{'_args'} = $args; |
106
|
7
|
|
|
|
|
36
|
$self->{_memd} = Cache::Memcached::Fast->new($args); |
107
|
7
|
|
|
|
|
142
|
$self->{servers} = $args->{servers}; |
108
|
|
|
|
|
|
|
|
109
|
7
|
|
|
|
|
40
|
return $self; |
110
|
|
|
|
|
|
|
} |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
=pod |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
=head1 METHODS |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
=over |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
=item Supported Cache::Memcached::Fast methods |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
These methods can be called on a Cache::Elasticache::Memcache object. The object will call checkServers, then the call will be passed on to the appropriate L code. Please see the L documentation for further details regarding these methods. |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
$memd->enable_compress($enable) |
123
|
|
|
|
|
|
|
$memd->namespace($string) |
124
|
|
|
|
|
|
|
$memd->set($key, $value) |
125
|
|
|
|
|
|
|
$memd->set_multi([$key, $value],[$key, $value, $expiration_time]) |
126
|
|
|
|
|
|
|
$memd->cas($key, $cas, $value) |
127
|
|
|
|
|
|
|
$memd->cas_multi([$key, $cas, $value],[$key, $cas, $value]) |
128
|
|
|
|
|
|
|
$memd->add($key, $value) |
129
|
|
|
|
|
|
|
$memd->add_multi([$key, $value],[$key, $value]) |
130
|
|
|
|
|
|
|
$memd->replace($key, $value) |
131
|
|
|
|
|
|
|
$memd->replace_multi([$key, $value],[$key, $value]) |
132
|
|
|
|
|
|
|
$memd->append($key, $value) |
133
|
|
|
|
|
|
|
$memd->append_multi([$key, $value],[$key, $value]) |
134
|
|
|
|
|
|
|
$memd->prepend($key, $value) |
135
|
|
|
|
|
|
|
$memd->prepend_multi([$key, $value],[$key, $value]) |
136
|
|
|
|
|
|
|
$memd->get($key) |
137
|
|
|
|
|
|
|
$memd->get_multi(@keys) |
138
|
|
|
|
|
|
|
$memd->gets($key) |
139
|
|
|
|
|
|
|
$memd->gets_multi(@keys) |
140
|
|
|
|
|
|
|
$memd->incr($key) |
141
|
|
|
|
|
|
|
$memd->incr_multi(@keys) |
142
|
|
|
|
|
|
|
$memd->decr($key) |
143
|
|
|
|
|
|
|
$memd->decr_multi(@keys) |
144
|
|
|
|
|
|
|
$memd->delete($key) |
145
|
|
|
|
|
|
|
$memd->delete_multi(@keys) |
146
|
|
|
|
|
|
|
$memd->touch($key, $expiration_time) |
147
|
|
|
|
|
|
|
$memd->touch_multi([$key],[$key, $expiration_time]) |
148
|
|
|
|
|
|
|
$memd->flush_all($delay) |
149
|
|
|
|
|
|
|
$memd->nowait_push() |
150
|
|
|
|
|
|
|
$memd->server_versions() |
151
|
|
|
|
|
|
|
$memd->disconnect_all() |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
=cut |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
my @methods = qw( |
156
|
|
|
|
|
|
|
enable_compress |
157
|
|
|
|
|
|
|
namespace |
158
|
|
|
|
|
|
|
set |
159
|
|
|
|
|
|
|
set_multi |
160
|
|
|
|
|
|
|
cas |
161
|
|
|
|
|
|
|
cas_multi |
162
|
|
|
|
|
|
|
add |
163
|
|
|
|
|
|
|
add_multi |
164
|
|
|
|
|
|
|
replace |
165
|
|
|
|
|
|
|
replace_multi |
166
|
|
|
|
|
|
|
append |
167
|
|
|
|
|
|
|
append_multi |
168
|
|
|
|
|
|
|
prepend |
169
|
|
|
|
|
|
|
prepend_multi |
170
|
|
|
|
|
|
|
get |
171
|
|
|
|
|
|
|
get_multi |
172
|
|
|
|
|
|
|
gets |
173
|
|
|
|
|
|
|
gets_multi |
174
|
|
|
|
|
|
|
incr |
175
|
|
|
|
|
|
|
incr_multi |
176
|
|
|
|
|
|
|
decr |
177
|
|
|
|
|
|
|
decr_multi |
178
|
|
|
|
|
|
|
delete |
179
|
|
|
|
|
|
|
delete_multi |
180
|
|
|
|
|
|
|
touch |
181
|
|
|
|
|
|
|
touch_multi |
182
|
|
|
|
|
|
|
flush_all |
183
|
|
|
|
|
|
|
nowait_push |
184
|
|
|
|
|
|
|
server_versions |
185
|
|
|
|
|
|
|
disconnect_all |
186
|
|
|
|
|
|
|
); |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
foreach my $method (@methods) { |
189
|
|
|
|
|
|
|
my $method_name = "Cache::Elasticache::Memcache::$method"; |
190
|
3
|
|
|
3
|
|
26
|
no strict 'refs'; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
2336
|
|
191
|
|
|
|
|
|
|
*$method_name = sub { |
192
|
30
|
|
|
30
|
|
85316
|
my $self = shift; |
193
|
30
|
|
|
|
|
88
|
$self->checkServers; |
194
|
30
|
|
|
|
|
375
|
return $self->{'_memd'}->$method(@_); |
195
|
|
|
|
|
|
|
}; |
196
|
|
|
|
|
|
|
} |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
=pod |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
=item checkServers |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
my $memd = Cache::Elasticache::Memcache->new({ |
203
|
|
|
|
|
|
|
config_endpoint => 'foo.bar' |
204
|
|
|
|
|
|
|
}) |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
... |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
$memd->checkServers(); |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
Trigger the the server list to be updated if the time passed since the server list was last updated is greater than the update period (default 180 seconds). |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
=cut |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
sub checkServers { |
215
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
216
|
0
|
0
|
0
|
|
|
0
|
if ( defined $self->{'config_endpoint'} && (time - $self->{_last_update}) > $self->{update_period} ) { |
217
|
0
|
|
|
|
|
0
|
$self->updateServers(); |
218
|
|
|
|
|
|
|
} |
219
|
|
|
|
|
|
|
} |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
=pod |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
=item updateServers |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
my $memd = Cache::Elasticache::Memcache->new({ |
226
|
|
|
|
|
|
|
config_endpoint => 'foo.bar' |
227
|
|
|
|
|
|
|
}) |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
... |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
$memd->updateServers(); |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
This method will update the server list regardles of how much time has passed since the server list was last checked. |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
=cut |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
sub updateServers { |
238
|
4
|
|
|
4
|
1
|
6019479
|
my $self = shift; |
239
|
|
|
|
|
|
|
|
240
|
4
|
|
|
|
|
31
|
my $servers = $self->getServersFromEndpoint($self->{'config_endpoint'}); |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
## Cache::Memcached::Fast does not support updating the server list after creation |
243
|
|
|
|
|
|
|
## Therefore we must create a new object. |
244
|
|
|
|
|
|
|
|
245
|
4
|
100
|
|
|
|
20
|
if ( $self->_hasServerListChanged($servers) ) { |
246
|
2
|
|
|
|
|
6
|
$self->{_args}->{servers} = $servers; |
247
|
2
|
|
|
|
|
14
|
$self->{_memd} = Cache::Memcached::Fast->new($self->{'_args'}); |
248
|
|
|
|
|
|
|
} |
249
|
|
|
|
|
|
|
|
250
|
4
|
|
|
|
|
58
|
$self->{servers} = $servers; |
251
|
4
|
|
|
|
|
22
|
$self->{_last_update} = time; |
252
|
|
|
|
|
|
|
} |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
sub _hasServerListChanged { |
255
|
4
|
|
|
4
|
|
10
|
my $self = shift; |
256
|
4
|
|
|
|
|
9
|
my $servers = shift; |
257
|
|
|
|
|
|
|
|
258
|
4
|
100
|
|
|
|
10
|
return 1 unless(scalar(@$servers) == scalar(@{$self->{'servers'}})); |
|
4
|
|
|
|
|
30
|
|
259
|
|
|
|
|
|
|
|
260
|
2
|
|
|
|
|
8
|
foreach my $server (@$servers) { |
261
|
6
|
50
|
|
|
|
14
|
return 1 unless ( grep { $server eq $_ } @{$self->{'servers'}} ); |
|
18
|
|
|
|
|
74
|
|
|
6
|
|
|
|
|
21
|
|
262
|
|
|
|
|
|
|
} |
263
|
|
|
|
|
|
|
|
264
|
2
|
|
|
|
|
12
|
return 0; |
265
|
|
|
|
|
|
|
} |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
=pod |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
=back |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
=head1 CLASS METHODS |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
=over |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
=item getServersFromEndpoint |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
Cache::Elasticache::Memcache->getServersFromEndpoint('foo.bar'); |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
This class method will retrieve the server list for a given configuration endpoint. |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
=cut |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
sub getServersFromEndpoint { |
284
|
13
|
|
|
13
|
1
|
17222
|
my $class = shift; |
285
|
13
|
|
|
|
|
26
|
my $config_endpoint = shift; |
286
|
|
|
|
|
|
|
# TODO: Use IO::Socket::Timeout to handle timing outsocke reads sensibly |
287
|
|
|
|
|
|
|
# TODO: make use of "connect_timeout" (default 0.5s) and "io_timeout" (default 0.2s) constructor parameters |
288
|
|
|
|
|
|
|
# my $args = shift; |
289
|
|
|
|
|
|
|
# $connect_timeout = exists $args->{connect_timeout} ? $args->{connect_timeout} : $class::default_connect_timeout; |
290
|
|
|
|
|
|
|
# $io_timeout = exists $args->{io_timeout} ? $args->{io_timeout} : $class::default_io_timeout; |
291
|
13
|
|
|
|
|
74
|
my $socket = IO::Socket::IP->new(PeerAddr => $config_endpoint, Timeout => 10, Proto => 'tcp'); |
292
|
13
|
50
|
|
|
|
571
|
croak "Unable to connect to server: ".$config_endpoint." - $!" unless $socket; |
293
|
|
|
|
|
|
|
|
294
|
13
|
|
|
|
|
169
|
$socket->autoflush(1); |
295
|
13
|
|
|
|
|
1134
|
$socket->send("config get cluster\r\n"); |
296
|
13
|
|
|
|
|
862
|
my $data = ""; |
297
|
13
|
|
|
|
|
24
|
my $count = 0; |
298
|
13
|
|
|
|
|
51
|
until ($data =~ m/END/) { |
299
|
183
|
|
|
|
|
1107
|
my $line = $socket->getline(); |
300
|
183
|
100
|
|
|
|
11070
|
if (defined $line) { |
301
|
128
|
|
|
|
|
278
|
$data .= $line; |
302
|
|
|
|
|
|
|
} |
303
|
183
|
|
|
|
|
302
|
$count++; |
304
|
183
|
100
|
|
|
|
802
|
last if ( $count == 30 ); |
305
|
|
|
|
|
|
|
} |
306
|
13
|
|
|
|
|
80
|
$socket->close(); |
307
|
13
|
|
|
|
|
740
|
return $class->_parseConfigResponse($data); |
308
|
|
|
|
|
|
|
} |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
sub _parseConfigResponse { |
311
|
13
|
|
|
13
|
|
44
|
my $class = shift; |
312
|
13
|
|
|
|
|
29
|
my $data = shift; |
313
|
13
|
50
|
33
|
|
|
101
|
return [] unless (defined $data && $data ne ''); |
314
|
13
|
|
|
|
|
119
|
my @response_lines = split(/[\r\n]+/,$data); |
315
|
13
|
|
|
|
|
32
|
my @servers = (); |
316
|
13
|
|
|
|
|
28
|
my $node_regex = '([-.a-zA-Z0-9]+)\|(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\|(\d+)'; |
317
|
13
|
|
|
|
|
26
|
foreach my $line (@response_lines) { |
318
|
56
|
100
|
|
|
|
302
|
if ($line =~ m/$node_regex/) { |
319
|
13
|
|
|
|
|
52
|
foreach my $node (split(' ', $line)) { |
320
|
33
|
|
|
|
|
136
|
my ($host, $ip, $port) = split('\|',$node); |
321
|
33
|
|
|
|
|
164
|
push(@servers,$ip.':'.$port); |
322
|
|
|
|
|
|
|
} |
323
|
|
|
|
|
|
|
} |
324
|
|
|
|
|
|
|
} |
325
|
13
|
|
|
|
|
64
|
return \@servers; |
326
|
|
|
|
|
|
|
} |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
1; |
329
|
|
|
|
|
|
|
__END__ |