root/lang/perl/Cache-Memcached-libmemcached/trunk/lib/Cache/Memcached/libmemcached.pm

Revision 31596, 18.8 kB (checked in by daisuke, 4 years ago)

version++

  • Property svn:keywords set to Id
Line 
1# $Id$
2#
3# Copyright (c) 2008 Daisuke Maki <daisuke@endeworks.jp>
4# All rights reserved.
5
6package Cache::Memcached::libmemcached;
7use strict;
8use warnings;
9use base qw(Memcached::libmemcached);
10use Carp qw(croak);
11use Scalar::Util qw(weaken);
12use Storable ();
13
14our $VERSION = '0.02009';
15
16use constant HAVE_ZLIB    => eval { require Compress::Zlib } && !$@;
17use constant F_STORABLE   => 1;
18use constant F_COMPRESS   => 2;
19use constant OPTIMIZE     => $ENV{PERL_LIBMEMCACHED_OPTIMIZE} ? 1 : 0;
20
21BEGIN
22{
23    # Make sure to load bytes.pm if HAVE_ZLIB is enabled
24    if (HAVE_ZLIB) {
25        require bytes;
26    }
27
28    # accessors
29    foreach my $field qw(compress_enable compress_threshold compress_savings) {
30        eval sprintf(<<"        EOSUB", $field, $field, $field, $field);
31            sub set_%s { \$_[0]->{%s} = \$_[1] }
32            sub get_%s { \$_[0]->{%s} }
33        EOSUB
34        die if $@;
35    }
36
37    if (OPTIMIZE) {
38        # If the optimize flag is enabled, we do not support master key
39        # generation, cause we really care about the speed.
40        foreach my $method qw(get set add replace prepend append cas delete) {
41            eval <<"            EOSUB";
42                sub $method {
43                    my \$self = shift;
44                    my \$key  = shift;
45                    if (\$self->{namespace}) {
46                        \$key = "\$self->{namespace}\$key";
47                    }
48                    \$self->SUPER::memcached_${method}(\$key, \@_)
49                }
50            EOSUB
51            die if $@;
52        }
53    } else {
54        # Regular case.
55        # Mental note. We only do this cause while we're faster than
56        # Cache::Memcached::Fast, *even* when the above optimization isn't
57        # toggled.
58        foreach my $method qw(get set add replace prepend append cas delete) {
59            eval <<"            EOSUB";
60                sub $method {
61                    my \$self = shift;
62                    my \$key  = shift;
63                    my \$master_key;
64                    if (ref \$key eq 'ARRAY') {
65                        (\$master_key, \$key) = @\$key;
66                    }
67
68                    if (\$self->{namespace}) {
69                        \$key = "\$self->{namespace}\$key";
70                    }
71                    if (\$master_key) {
72                        \$self->SUPER::memcached_${method}_by_key(\$master_key, \$key, \@_);
73                    } else {
74                        \$self->SUPER::memcached_${method}(\$key, \@_);
75                    }
76                }
77            EOSUB
78            die if $@;
79        }
80    }
81}
82
83sub import
84{
85    my $class = shift;
86    Memcached::libmemcached->export_to_level(1, undef, @_) ;
87}
88
89sub new
90{
91    my $class = shift;
92    my $args  = shift || {};
93
94    $args->{servers} || die "No servers specified";
95
96    my $self = $class->SUPER::new();
97
98    $self->{compress_threshold} = $args->{compress_threshold};
99    $self->{compress_savingsS}   = $args->{compress_savings} || 0.20;
100    $self->{compress_enable}    =
101        exists $args->{compress_enable} ? $args->{compress_enable} : 1;
102
103    # servers
104    $self->set_servers($args->{servers});
105
106    # Set compression/serialization callbacks
107    $self->set_callback_coderefs(
108        # Closures so we have reference to $self
109        $self->_mk_callbacks()
110    );
111
112    # behavior options
113    $self->set_no_block( $args->{no_block} ) if exists $args->{no_block};
114    $self->set_hashing_algorithm( $args->{hashing_algorithm} ) if exists $args->{hashing_algorithm};
115    $self->set_distribution_method( $args->{distribution_method} ) if exists $args->{distribution_method};
116
117    $self->{namespace} = $args->{namespace} || '';
118
119    return $self;
120}
121
122sub set_servers
123{
124    my $self = shift;
125    my $servers = shift || [];
126    foreach my $server (@$servers) {
127        $self->server_add($server);
128    }
129}
130
131sub server_add
132{
133    my $self = shift;
134    my $server = shift;
135
136    # check for existance of :
137    if ($server =~ /^([^:]+):([^:]+)$/) {
138        my ($hostname, $port) = ($1, $2);
139        $self->memcached_server_add($hostname, $port );
140    } else {
141        $self->memcached_server_add_unix_socket( $server );
142    }
143}
144
145sub _mk_callbacks
146{
147    my $self = shift;
148
149    weaken($self);
150    my $inflate = sub {
151        my ($key, $flags) = @_;
152        if ($flags & F_COMPRESS) {
153            if (! HAVE_ZLIB) {
154                croak("Data for $key is compressed, but we have no Compress::Zlib");
155            }
156            $_ = Compress::Zlib::memGunzip($_);
157        }
158
159        if ($flags & F_STORABLE) {
160            $_ = Storable::thaw($_);
161        }
162        return ();
163    };
164
165    my $deflate = sub {
166        # Check if we have a complex structure
167        if (ref $_) {
168            $_ = Storable::nfreeze($_);
169            $_[1] |= F_STORABLE;
170        }
171
172        # Check if we need compression
173        if (HAVE_ZLIB && $self->{compress_enable} && $self->{compress_threshold}) {
174            # Find the byte length
175            my $length = bytes::length($_);
176            if ($length > $self->{compress_threshold}) {
177                my $tmp = Compress::Zlib::memGzip($_);
178                if (1 - bytes::length($tmp) / $length < $self->{compress_savingsS}) {
179                    $_ = $tmp;
180                    $_[1] |= F_COMPRESS;
181                }
182            }
183        }
184        return ();
185    };
186    return ($deflate, $inflate);
187}
188
189sub incr
190{
191    my $self = shift;
192    my $key  = shift;
193    my $offset = shift || 1;
194    if ($self->{namespace}) {
195        $key = "$self->{namespace}$key";
196    }
197    my $val = 0;
198    $self->memcached_increment($key, $offset, $val);
199    return $val;
200}
201
202sub decr
203{
204    my $self = shift;
205    my $key  = shift;
206    my $offset = shift || 1;
207    if ($self->{namespace}) {
208        $key = "$self->{namespace}$key";
209    }
210    my $val = 0;
211    $self->memcached_decrement($key, $offset, $val);
212    return $val;
213}
214
215sub get_multi {
216    my $self = shift;
217
218    my $namespace = $self->{namespace};
219    my @keys = $namespace ? map { "$namespace$_" } @_ : @_;
220    my $hash = $self->SUPER::get_multi(@keys);
221    return $namespace ? +{ map { ($_ => $hash->{"$namespace$_"}) } @_ } : $hash;
222}
223
224sub flush_all
225{
226    $_[0]->memcached_flush(0);
227}
228
229*remove = \&delete;
230
231sub disconnect_all
232{
233    $_[0]->memcached_quit();
234}
235
236sub stats
237{
238    my %h;
239    my %misc_keys = map { ($_ => 1) }
240      qw/ bytes bytes_read bytes_written
241          cmd_get cmd_set connection_structures curr_items
242          get_hits get_misses
243          total_connections total_items
244        /;
245    my $code = sub {
246        my($key, $value, $hostport, $type) = @_;
247
248        # XXX - This is hardcoded in the callback cause r139 in perl-memcached
249        # removed the magic of "misc"
250        $type ||= 'misc';
251        $h{hosts}{$hostport}{$type}{$key} = $value;
252        if ($type eq 'misc') {
253            if ($misc_keys{$key}) {
254                $h{total}{$key} ||= 0;
255                $h{total}{$key} += $value;
256            }
257        } elsif ($type eq 'malloc') {
258            $h{total}{"malloc_$key"} ||= 0;
259            $h{total}{"malloc_$key"} += $value;
260        }
261        return ();
262    };
263    $_[0]->walk_stats($_, $code) for ('', qw(malloc sizes self));
264    return \%h;
265}
266
267BEGIN
268{
269    my @boolean_behavior = qw( no_block );
270    my %behavior = (
271        distribution_method => 'distribution',
272        hashing_algorithm   => 'hash'
273    );
274
275    foreach my $name (@boolean_behavior) {
276        my $code = sprintf(<<'        EOSUB', $name, uc $name, $name, uc $name);
277            sub is_%s {
278                $_[0]->memcached_behavior_get( Memcached::libmemcached::MEMCACHED_BEHAVIOR_%s() );
279            }
280
281            sub set_%s {
282                $_[0]->memcached_behavior_set( Memcached::libmemcached::MEMCACHED_BEHAVIOR_%s(), $_[1] );
283            }
284        EOSUB
285        eval $code;
286        die if $@;
287    }
288
289    while (my($method, $field) = each %behavior) {
290        my $code = sprintf(<<'        EOSUB', $method, uc $field, $method, uc $field);
291            sub get_%s {
292                $_[0]->memcached_behavior_get( Memcached::libmemcached::MEMCACHED_BEHAVIOR_%s() );
293            }
294
295            sub set_%s {
296                $_[0]->memcached_behavior_set( Memcached::libmemcached::MEMCACHED_BEHAVIOR_%s(), $_[1]);
297            }
298        EOSUB
299        eval $code;
300        die if $@;
301    }
302
303}
304
3051;
306
307__END__
308
309=head1 NAME
310
311Cache::Memcached::libmemcached - Perl Interface to libmemcached
312
313=head1 SYNOPSIS
314
315  use Cache::Memcached::libmemcached;
316  my $memd = Cache::Memcached::libmemcached->new({
317    servers => [ "10.0.0.15:11211", "10.0.0.15:11212", "/var/sock/memcached" ],
318    compress_threshold => 10_000
319  });
320
321  $memd->set("my_key", "Some value");
322  $memd->set("object_key", { 'complex' => [ "object", 2, 4 ]});
323
324  $val = $memd->get("my_key");
325  $val = $memd->get("object_key");
326  if ($val) { print $val->{complex}->[2] }
327
328  $memd->incr("key");
329  $memd->decr("key");
330  $memd->incr("key", 2);
331
332  $memd->delete("key");
333  $memd->remove("key"); # Alias to delete
334
335  my $hashref = $memd->get_multi(@keys);
336
337  # Constants - explicitly by name or by tags
338  #    see Memcached::libmemcached::constants for a list
339  use Cache::Memcached::libmemcached qw(MEMCACHED_DISTRIBUTION_CONSISTENT);
340  use Cache::Memcached::libmemcached qw(
341    :defines
342    :memcached_allocated
343    :memcached_behavior
344    :memcached_callback
345    :memcached_connection
346    :memcached_hash
347    :memcached_return
348    :memcached_server_distribution
349  );
350
351  # Extra constructor options that are not in Cache::Memcached
352  # See Memcached::libmemcached::constants for a list of available options
353  my $memd = Cache::Memcached::libmemcached->new({
354    ...,
355    no_block            => $boolean,
356    distribution_method => $distribution_method,
357    hashing_algorithm   => $hashing_algorithm,
358  });
359
360=head1 DESCRIPTION
361
362This is the Cache::Memcached compatible interface to libmemcached,
363a C library to interface with memcached.
364
365Cache::Memcached::libmemcached is built on top of Memcached::libmemcached.
366While Memcached::libmemcached aims to port libmemcached API to perl,
367Cache::Memcached::libmemcached attempts to be API compatible with
368Cache::Memcached, so it can be used as a drop-in replacement.
369
370Note that as of version 0.02000, Cache::Memcached::libmemcached I<inherits>
371from Memcached::libmemcached. While you are free to use the
372Memcached::libmemcached specific methods directly on the object, you should
373use them with care, as it will mean that your code is no longer compatible
374with the Cache::Memcached API therefore losing some of th portability in
375case you want to replace it with some other package.
376
377=head1 FOR Cache::Memcached::LibMemcached USERS
378
379Cache::Memcached::libmemcached is a rewrite of Cache::Memcached::LibMemcached,
380using Memcached::libmemcached instead of straight XS as its backend.
381
382Therefore you might notice some differences. Here are the ones we are
383aware of:
384
385=over 4
386
387=item cas() is not implemented
388
389This was sort of implemented in a previous life, but since
390Memcached::libmemcached is still undecided how to handle it, we don't
391support it either.
392
393=item performance is probably a bit different
394
395To be honest, we haven't ran benchmarks comparing the two (yet). In general,
396you might see a decrease in performance here and there because we've
397essentially added another call stack (instead of going straight from perl to
398XS, we are now going from perl to perl to XS). But on the other hand,
399Memcached::libmemcached is in the hands of XS gurus like Time Bunce, so
400you are probably sparing yourself some accidental hooplas that occasional
401C programmers like me might introduce.
402
403=back
404
405=head1 Cache::Memcached COMPATIBLE METHODS
406
407Except for the minor incompatiblities, below methods are generally compatible
408with Cache::Memcached.
409
410=head2 new
411
412Takes on parameter, a hashref of options.
413
414=head2 set_servers
415
416  $memd->set_servers( [ qw(serv1:port1 serv2:port2 ...) ]);
417
418Sets the server list.
419
420=head2 get
421
422  my $val = $memd->get($key);
423
424Retrieves a key from the memcached. Returns the value (automatically thawed
425with Storable, if necessary) or undef.
426
427Currently the arrayref form of $key is NOT supported. Perhaps in the future.
428
429=head2 get_multi
430
431  my $hashref = $memd->get_multi(@keys);
432
433Retrieves multiple keys from the memcache doing just one query.
434Returns a hashref of key/value pairs that were available.
435
436=head2 set
437
438  $memd->set($key, $value[, $expires]);
439
440Unconditionally sets a key to a given value in the memcache. Returns true if
441it was stored successfully.
442
443Currently the arrayref form of $key is NOT supported. Perhaps in the future.
444
445=head2 add
446
447  $memd->add($key, $value[, $expires]);
448
449Like set(), but only stores in memcache if they key doesn't already exist.
450
451=head2 replace
452
453  $memd->replace($key, $value[, $expires]);
454
455Like set(), but only stores in memcache if they key already exist.
456
457=head2 append
458
459  $memd->append($key, $value);
460
461Appends $value to whatever value associated with $key. Only available for
462memcached > 1.2.4
463
464=head2 prepend
465
466  $memd->prepend($key, $value);
467
468Prepends $value to whatever value associated with $key. Only available for
469memcached > 1.2.4
470
471=head2 incr
472
473=head2 decr
474
475  my $newval = $memd->incr($key);
476  my $newval = $memd->decr($key);
477  my $newval = $memd->incr($key, $offset);
478  my $newval = $memd->decr($key, $offset);
479
480Atomically increments or decrements the specified the integer value specified
481by $key. Returns undef if the key doesn't exist on the server.
482
483=head2 delete
484
485=head2 remove
486
487  $memd->delete($key);
488
489Deletes a key.
490
491XXX - The behavior when second argument is specified may differ from
492Cache::Memcached -- this hasn't been very well tested. Patches welcome!
493
494=head2 flush_all
495
496  $memd->fush_all;
497
498Runs the memcached "flush_all" command on all configured hosts, emptying all
499their caches.
500
501=head2 set_compress_threshold
502
503  $memd->set_compress_threshold($threshold);
504
505Set the compress threshold.
506
507=head2 enable_compress
508
509  $memd->enable_compress($bool);
510
511This is actually an alias to set_compress_enable(). The original version
512from Cache::Memcached is, despite its naming, a setter as well.
513
514=head2 stats
515
516  my $h = $memd->stats();
517
518This method is still half-baked. It gives you some stats. If the values are
519wrong, well, reports, or better yet, patches welcome.
520
521=head2 disconnect_all
522
523Disconnects from servers
524
525=head2 cas
526
527  $memd->cas($key, $cas, $value[, $exptime]);
528
529XXX - This method is still broken.
530
531Sets if $cas matches the value on the server.
532
533=head2 gets
534
535=head2 get_cas
536
537  my $cas = $memd->gets($key);
538  my $cas = $memd->get_cas($key);
539
540Get the CAS value for $key
541
542=head2 get_cas_multi
543
544  my $h = $memd->get_cas_multi(@keys)
545
546Gets CAS values for multiple keys
547
548=head1 Cache::Memcached::libmemcached SPECIFIC METHODS
549
550These methods are libmemcached-specific.
551
552=head2 server_add
553
554Adds a memcached server.
555
556=head2 server_add_unix_socket
557
558Adds a memcached server, connecting via unix socket.
559
560=head2 server_list_free
561
562Frees the memcached server list.
563
564=head1 UTILITY METHODS
565
566WARNING: Please do not consider the existance for these methods to be final.
567They may be renamed or may entirely disappear from future releases.
568
569=head2 get_compress_threshold
570
571Return the current value of compress_threshold
572
573=head2 set_compress_enable
574
575Set the value of compress_enable
576
577=head2 get_compress_enable
578
579Return the current value of compress_enable
580
581=head2 set_compress_savings
582
583Set the value of compress_savings
584
585=head2 get_compress_savings
586
587Return the current value of compress_savings
588
589=head1 BEHAVIOR CUSTOMIZATION
590
591Certain libmemcached behaviors can be configured with the following
592methods.
593
594(NOTE: This API is not fixed yet)
595
596=head2 behavior_set
597
598=head2 behavior_get
599
600If you want to customize something that we don't have a wrapper for,
601you can directly use these method.
602
603=head2 set_no_block
604
605  Cache::Memcached::libmemcached->new({
606    ...
607    no_block => 1
608  });
609  # or
610  $memd->set_no_block( 1 );
611
612Set to use blocking/non-blocking I/O. When this is in effect, get() becomes
613flaky, so don't attempt to call it. This has the most effect for set()
614operations, because libmemcached stops waiting for server response after
615writing to the socket (set() will also always return success)
616
617Please consult the man page for C<memcached_behavior_set()> for details
618before setting.
619
620=head2 is_no_block
621
622Get the current value of no_block behavior.
623
624=head2 set_distribution_method
625
626  $memd->set_distribution_method( MEMCACHED_DISTRIBUTION_CONSISTENT );
627
628Set the distribution behavior.
629
630=head2 get_distribution_method
631
632Get the distribution behavior.
633
634=head2 set_hashing_algorithm
635
636  $memd->set_hashing_algorithm( MEMCACHED_HASH_KETAMA );
637
638Set the hashing algorithm used.
639
640=head2 get_hashing_algorithm
641
642Get the hashing algorithm used.
643
644=head2 set_support_cas
645
646  $memd->set_support_cas($boolean);
647  # or
648  $memd = Cache::Memcached::libmemcached->new( {
649    ...
650    support_cas => 1
651  } );
652
653Enable/disable CAS support.
654
655=head1 OPTIMIZE FLAG
656
657There's an EXPERIMENTAL optimization available for some corner cases, where
658if you know before hand that you won't be using some features, you can
659disable them all together for some performance boost. To enable this mode,
660set an environment variable named PERL_LIBMEMCACHED_OPTIMIZE to a true value
661
662=head2 NO MASTER KEY SUPPORT
663
664If you are 100% sure that you won't be using the master key support, where
665you provide an arrayref as the key, you get about 4~5% performance boost.
666
667=head1 VARIOUS MEMCACHED MODULES
668
669Below are the various memcached modules available on CPAN.
670
671Please check tool/benchmark.pl for a live comparison of these modules.
672(except for Cache::Memcached::XS, which I wasn't able to compile under my
673main dev environment)
674
675=head2 Cache::Memcached
676
677This is the "main" module. It's mostly written in Perl.
678
679=head2 Cache::Memcached::libmemcached
680
681Cache::Memcached::libmemcached, which is the module for which your reading
682the document of, is a perl binding for libmemcached (http://tangent.org/552/libmemcached.html). Not to be confused with libmemcache (see below).
683
684=head2 Cache::Memcached::Fast
685
686Cache::Memcached::Fast is a memcached client written in XS from scratch.
687As of this writing benchmarks shows that Cache::Memcached::Fast is faster on
688get_multi(), and Cache::Memcached::libmemcached is faster on regular get()/set()
689
690=head2 Memcached::libmemcached
691
692Memcached::libmemcached is a straight binding to libmemcached, and is also
693the parent class of this module.
694
695It has most of the libmemcached API. If you don't care about a drop-in
696replacement for Cache::Memcached, and want to benefit from low level API that
697libmemcached offers, this is the way to go.
698
699=head2 Cache::Memcached::XS
700
701Cache::Memcached::XS is a binding for libmemcache (http://people.freebsd.org/~seanc/libmemcache/).
702The main memcached site at http://danga.com/memcached/apis.bml seems to
703indicate that the underlying libmemcache is no longer in active development.
704
705=head1 CAVEATS
706
707Unless you know what you're getting yourself into, don't try to subclass this
708module just yet. Internal structures may change without notice.
709
710=head1 AUTHOR
711
712Copyright (c) 2008 Daisuke Maki E<lt>daisuke@endeworks.jpE<gt>
713
714=head1 LICENSE
715
716This program is free software; you can redistribute it and/or modify it
717under the same terms as Perl itself.
718
719See http://www.perl.com/perl/misc/Artistic.html
720
721=cut
Note: See TracBrowser for help on using the browser.