This commit is contained in:
2019-02-06 00:49:12 +03:00
commit 8dbb1bb605
4796 changed files with 506072 additions and 0 deletions

2
wcmtools/memcached/AUTHORS Executable file
View File

@@ -0,0 +1,2 @@
Anatoly Vorobey <mellon@pobox.com>
Brad Fitzpatrick <brad@danga.com>

37
wcmtools/memcached/BUILD Executable file
View File

@@ -0,0 +1,37 @@
Ideally, you want to make a static binary, otherwise the dynamic
linker pollutes your address space with shared libs right in the
middle. (NOTE: actually, this shouldn't matter so much anymore, now
that we only allocate huge, fixed-size slabs)
Make sure your libevent has epoll (Linux) or kqueue (BSD) support.
Using poll or select only is slow, and works for testing, but
shouldn't be used for high-traffic memcache installations.
To build libevent with epoll on Linux, you need two things. First,
you need /usr/include/sys/epoll.h . To get it, you can install the
userspace epoll library, epoll-lib. The link to the latest version
is buried inside
http://www.xmailserver.org/linux-patches/nio-improve.html ; currently
it's http://www.xmailserver.org/linux-patches/epoll-lib-0.9.tar.gz .
If you're having any trouble building/installing it, you can just copy
epoll.h from that tarball to /usr/include/sys as that's the only thing
from there that libevent really needs.
Secondly, you need to declare syscall numbers of epoll syscalls, so
libevent can use them. Put these declarations somewhere
inside <sys/epoll.h>:
#define __NR_epoll_create 254
#define __NR_epoll_ctl 255
#define __NR_epoll_wait 256
After this you should be able to build libevent with epoll support.
Once you build/install libevent, you don't need <sys/epoll.h> to
compile memcache or link it against libevent. Don't forget that for epoll
support to actually work at runtime you need to use a kernel with epoll
support patch applied, as explained in the README file.
BSD users are luckier, and will get kqueue support by default.

48
wcmtools/memcached/CONTRIBUTORS Executable file
View File

@@ -0,0 +1,48 @@
Brad Fitzpatrick <brad@danga.com>
-- design/protocol
-- Perl client
-- prototype Perl server
-- memory allocator design
-- small enhancements/changes to C server
-- website
Anatoly Vorobey <mellon@pobox.com>
-- C server
-- memory allocator design
-- revised setuid code
Evan Martin <martine@danga.com>
-- automake/autoconf support
-- Python client
-- portability work to build on OS X
Ryan <hotrodder@rocketmail.com>
-- PHP client
Jamie McCarthy <jamie@mccarthy.vg>
-- Perl client fixes: Makefile.PL, stats, doc updates
Lisa Marie Seelye <lisa@gentoo.org>
-- packaging for Gentoo Linux
-- initial setuid code
Sean Chittenden <seanc@FreeBSD.org>
-- packaging for FreeBSD
Stuart Herbert <stuart@gentoo.org>
-- fix for: memcached's php client can run in an infinite loop
http://bugs.gentoo.org/show_bug.cgi?id=25385
Brion Vibber <brion@pobox.com>
-- debugging abstraction in PHP client
-- debugging the failure of daemon mode on FreeBSD
Brad Whitaker <whitaker@danga.com>
-- compression support for the Perl API
Richard Russo <russor@msoe.edu>
-- Java API
Ryan T. Dean <rtdean@cytherianage.net>
-- Second PHP client with correct parsing (based on Perl client)
-- autoconf fixes for mallinfo.arena on BSD (don't just check malloc.h)

30
wcmtools/memcached/COPYING Executable file
View File

@@ -0,0 +1,30 @@
Copyright (c) 2003, Danga Interactive, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of the Danga Interactive nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

178
wcmtools/memcached/ChangeLog Executable file
View File

@@ -0,0 +1,178 @@
2005-05-25
* patch from Peter van Dijk <peter@nextgear.nl> to make
stderr unbuffered, for running under daemontools
2005-04-04
* patch from Don MacAskill <don@smugmug.com> 'flush_all' doesn't
seem to work properly. Basically, if you try to add a key which
is present, but expired, the store fails but the old key is no
longer expired.
* release 1.1.12
2005-01-14
* Date: Thu, 18 Nov 2004 15:25:59 -0600
From: David Phillips <electrum@gmail.com>
Here is a patch to configure.ac and Makefile.am to put the man page in
the correct location. Trying to install the man page from a
subdirectory results in the subdirectory being used in the install
path (it tries to install to doc/memcached.1). This is the correct
thing to do:
- create a Makefile.am in the doc directory that installs the man page
with man_MANS
- modify Makefile.am in the base directory to reference the doc
directory using SUBDIRS
- modify the AC_CONFIG_FILES macro in configure.ac to output the
Makefile in doc
2005-01-14
* pidfile saving support from Lisa Seelye <lisa@gentoo.org>, sent
Jan 13, 2005
2005-01-14
* don't delete libevent events that haven't been added (the deltimer)
patch from Ted Schundler <tschundler@gmail.com>
2004-12-10
* document -M and -r in manpage (Doug Porter <dsp@dsp.name>)
2004-07-22
* fix buffer overflow in items.c with 250 byte keys along with
other info on the same line going into a 256 byte char[].
thanks to Andrei Nigmatulin <anight@monamour.ru>
2004-06-15
* immediate deletes weren't being unlinked a few seconds,
preventing "add" commands to the same key in that time period.
thanks to Michael Alan Dorman <mdorman@debian.org> for the
bug report and demo script.
2004-04-30
* released 1.1.11
2004-04-24
* Avva: Add a new command line option: -r , to maximize core file
limit.
2004-03-31
* Avva: Use getrlimit and setrlimit to set limits for number of
simultaneously open file descriptors. Get the current limits and
try to raise them if they're not enough for the specified (or the
default) setting of max connections.
2004-02-24
* Adds a '-M' flag to turn off tossing items from the cache.
(Jason Titus <jtitus@postini.com>)
2004-02-19 (Evan)
* Install manpage on "make install", etc.
2003-12-30 (Brad)
* remove static build stuff. interferes with PAM setuid stuff
and was only included as a possible fix with the old memory
allocator. really shouldn't make a difference.
* add Jay Bonci's Debian scripts and manpage
* release version 1.1.10
2003-12-01 (Avva)
* New command: flush_all, causes all existing items to
be invalidated immediately (without deleting them from
memory, merely causing memcached to no longer return them).
2003-10-23
* Shift init code around to fix daemon mode on FreeBSD,
* and drop root only after creating the server socket (to
* allow the use of privileged ports)
* version 1.1.10pre
2003-10-09
* BSD compile fixes from Ryan T. Dean
* version 1.1.9
2003-09-29
* ignore SIGPIPE at start instead of crashing in rare cases it
comes up. no other code had to be modified, since everything
else is already dead-connection-aware. (avva)
2003-09-09 (Avva, Lisa Marie Seelye <lisa@gentoo.org>)
* setuid support
2003-09-05 (Avva)
* accept all new connections in the same event (so we work with ET epoll)
* mark all items as clsid=0 after slab page reassignment to please future
asserts (on the road to making slab page reassignment work fully)
2003-08-12 (Brad Fitzpatrick)
* use TCP_CORK on Linux or TCP_PUSH on BSD
* only use TCP_NODELAY when we don't have alternatives
2003-08-10
* disable Nagel's Algorithm (TCP_NODELAY) for better performance (avva)
2003-08-10
* support multiple levels of verbosity (-vv)
2003-08-10 (Evan Martin)
* Makefile.am: debug, optimization, and static flags are controlled
by the configure script.
* configure.ac:
- allow specifying libevent directory with --with-libevent=DIR
- check for malloc.h (unavailable on BSDs)
- check for socklen_t (unavailable on OSX)
* assoc.c, items.c, slabs.c: Remove some unused headers.
* memcached.c: allow for nonexistence of malloc.h; #define a POSIX
macro to import mlockall flags.
2003-07-29
* version 1.1.7
* big bug fix: item exptime 0 meant expire immediately, not never
* version 1.1.8
2003-07-22
* make 'delete' take second arg, of time to refuse new add/replace
* set/add/replace/delete can all take abs or delta time (delta can't
be larger than a month)
2003-07-21
* added doc/protocol.txt
2003-07-01
* report CPU usage in stats
2003-06-30
* version 1.1.6
* fix a number of obscure bugs
* more stats reporting
2003-06-10
* removing use of Judy; use a hash. (judy caused memory fragmentation)
* shrink some structures
* security improvements
* version 1.1.0
2003-06-18
* changing maxsize back to an unsigned int
2003-06-16
* adding PHP support
* added CONTRIBUTORS file
* version 1.0.4
2003-06-15
* forgot to distribute website/api (still learning auto*)
* version 1.0.3
2003-06-15
* update to version 1.0.2
* autoconf/automake fixes for older versions
* make stats report version number
* change license from GPL to BSD
Fri, 13 Jun 2003 10:05:51 -0700 Evan Martin <martine@danga.com>
* configure.ac, autogen.sh, Makefile.am: Use autotools.
* items.c, memcached.c: #include <time.h> for time(),
printf time_t as %lu (is this correct?),
minor warnings fixes.

30
wcmtools/memcached/LICENSE Executable file
View File

@@ -0,0 +1,30 @@
Copyright (c) 2003, Danga Interactive, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of the Danga Interactive nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

10
wcmtools/memcached/Makefile.am Executable file
View File

@@ -0,0 +1,10 @@
bin_PROGRAMS = memcached
memcached_SOURCES = memcached.c slabs.c items.c memcached.h assoc.c
SUBDIRS = doc
DIST_DIRS = scripts
EXTRA_DIST = doc scripts TODO
AM_CFLAGS=-DNDEBUG

1
wcmtools/memcached/NEWS Executable file
View File

@@ -0,0 +1 @@
http://www.danga.com/memcached/news.bml

22
wcmtools/memcached/README Executable file
View File

@@ -0,0 +1,22 @@
Dependencies:
-- libevent, http://www.monkey.org/~provos/libevent/ (libevent-dev)
If using Linux, you need a kernel with epoll. Sure, libevent will
work with normal select, but it sucks.
epoll isn't in Linux 2.4 yet, but there's a backport at:
http://www.xmailserver.org/linux-patches/nio-improve.html
You want the epoll-lt patch (level-triggered).
Also, be warned that the -k (mlockall) option to memcached might be
dangerous when using a large cache. Just make sure the memcached machines
don't swap. memcached does non-blocking network I/O, but not disk. (it
should never go to disk, or you've lost the whole point of it)
The memcached website is at:
http://www.danga.com/memcached/

8
wcmtools/memcached/TODO Executable file
View File

@@ -0,0 +1,8 @@
* slab class reassignment still buggy and can crash. once that's
stable, server should re-assign pages every 60 seconds or so
to keep all classes roughly equal. [Update: fixed now?, but
not heavily tested. Future: make slab classes, with per-class
cleaners functions.]
* calendar queue for early expirations of items, so they don't push
out other objects with infinite expirations.

View File

@@ -0,0 +1,56 @@
Version 0.9.1 - 12 Oct 2003
-- Altered the SockIO helper class, so it no longer allows accessing
the streams it contains directly, instead it has methods
with identical signatures to the methods that were called
on the streams... This makes the client code prettier.
-- Changed looped non blocking read to blocking read, for getting
items from the server. This probably reduces CPU usage in
cases where the retrieval would block, and cleans up the
code a bit. We're blocking on retrieval anyhow.
-- Made get() not call get_multi(), and added single socket
optimization. This parallels recent changes to the perl
client
-- Changed a few for loops to use iterators instead, since it's
probably marginally more efficient, and it's probably
better coding practice.
-- Actually spell checked. :)
Version 0.9.0 - 29 Sep 2003
-- Renumbered to reflect that it's not been realworld tested
-- changed package to danga.com.MemCached (thanks)
-- added dates to changelog
-- added .txt to text files
-- added to official memcached site :)
Version 1.0.0 - 28 Sep 2003
-- Adjusted namespacing for SockIO, it shouldn't have been public; is now package level.
As a bonus, this means I don't have to Javadoc it. :)
-- Finished adding complete Javadoc to MemCachedClient.
-- spellchecked
-- added a couple versions of function variations that I missed. for example, some that
didn't take an int directly as a hash value, and i missed a get_multi w/out hashes.
-- removed java.net.Socket reference from MemCachedClient, SockIO has a new constructor which
takes hostname and port number
-- switched to three part version number
Version 0.3 - 27 Sep 2003
-- Compression, for strings/stringified numbers, this is compatible w/ perl
Serialized objects are incompatible w/ perl for obvious reasons. :)
-- Added PORTABILITY file, to include information about using the client
with various JVM's
-- Updated string parsing to StreamTokenizer rather than regexp's in an
effort to get sablevm to like the client
Version 0.2 - 24 Sep 2003
-- Serialization works
-- Possible BUG: Only the lower byte of the characters of keys are sent
This is only a problem if the memcache server can handle
unicode keys. (I haven't checked)
-- Server Failures handled gracefully
-- Partial Javadoc
Version 0.1 - 23 Sep 2003
-- Initial Release
-- Storing and Retrieving numbers and strings works

View File

@@ -0,0 +1,18 @@
This file lists the portability status of this client. Please send me any
additional information.
Richard 'toast' Russo <russor@msoe.edu>
I have personally tested this on the following platforms, and it works to the
best of my knowledge:
Sun's JRE 1.4.2 on Linux/i86
kaffe 1.1.1 on Linux/i86
I have personally tested this on the following platforms, and it does not work:
sablevm 1.0.9: complains of todo in native_interface.c
gjc(jdk) 3.3.2 20030908 (Debian prerelease): strange compiler errors
gij(jre) 3.3.2 20030908 (Debian prerelease): does not get items from server properly

View File

@@ -0,0 +1,9 @@
This are the things left to do
Investigate threading issues (what needs to be synchronized)
Investigate 3rd party jvm incompatibility
Non deprecated stream input
Extensive testing

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,72 @@
/**
* MemCached Java client, utility class for Socket IO
* Copyright (c) 2003
* Richard 'toast' Russo <russor@msoe.edu>
* http://people.msoe.edu/~russor/memcached
*
*
* This module is Copyright (c) 2003 Richard Russo.
* All rights reserved.
* You may distribute under the terms of the GNU General Public License
* This is free software. IT COMES WITHOUT WARRANTY OF ANY KIND.
*
* @author Richard 'toast' Russo <russor@msoe.edu>
* @version 0.9.1
*/
package com.danga.MemCached;
import java.util.*;
import java.net.*;
import java.io.*;
class SockIO {
Socket sock;
DataInputStream in;
DataOutputStream out;
boolean closed = false;
public SockIO(String host, int port) throws IOException {
sock = new Socket(host,port);
in = new DataInputStream(sock.getInputStream());
out = new DataOutputStream(sock.getOutputStream());
}
public void close() {
closed = true;
try {
in.close();
out.close();
sock.close();
} catch (IOException e) {
}
}
public boolean isConnected() {
return (closed && sock.isConnected());
}
public void readFully(byte[] b) throws IOException {
in.readFully(b);
}
public String readLine() throws IOException {
return in.readLine();
}
public void writeBytes(String s) throws IOException {
out.writeBytes(s);
}
public void flush() throws IOException {
out.flush();
}
public void write(byte[] b) throws IOException {
out.write(b);
}
}

View File

@@ -0,0 +1,79 @@
#!/usr/bin/perl
#
# Simplify Brad's life. I'm sure there's a Java-specific way
# to do this (or there should be), but I don't have Java
# installed.
#
use strict;
use Getopt::Long;
my $opt_tar = 0;
my $opt_upload = 0;
exit 1 unless GetOptions("tar" => \$opt_tar,
"upload" => \$opt_upload);
# chdir to the directory the script's at, so future
# paths need only be relative
use FindBin qw($Bin);
chdir $Bin or die "Couldn't cd to $Bin\n";
die "Must use --tar or --upload\n" unless $opt_tar || $opt_upload;
# files to distribute
my @manifest = qw(
TODO.txt
PORTABILITY.txt
CHANGELOG.txt
memcachetest.java
com
com/danga
com/danga/MemCached
com/danga/MemCached/MemCachedClient.java
com/danga/MemCached/SockIO.java
);
# figure out the version number
open (F, "com/danga/MemCached/MemCachedClient.java") or die;
{ local $/ = undef; $_ = <F>; } # suck in the whole file
close F;
die "Can't find version number\n" unless
/\@version\s+(\d[^\'\"\s]+)/s;
my $ver = $1;
my $dir = "java-memcached-$ver";
my $dist = "$dir.tar.gz";
if ($opt_tar) {
# make a fresh directory
mkdir $dir or die "Couldn't make directory: $dir\n";
# copy files to fresh directory
foreach my $file (@manifest) {
if (-f $file) {
system("cp", $file, "$dir/$file")
and die "Error copying file $file\n";
} elsif (-d $file) {
mkdir "$dir/$file"
or die "Error creating directory $file\n";
}
}
# tar it up
system("tar", "zcf", $dist, $dir)
and die "Error running tar.\n";
# remove temp directory
system("rm", "-rf", $dir)
and die "Error cleaning up temp directory\n";
print "$dist created.\n";
}
if ($opt_upload) {
print "Uploading $dist...\n";
system("scp", $dist, 'bradfitz@danga.com:memd/dist/')
and die "Error uploading to memcached/dist\n";
}
print "Done.\n";

View File

@@ -0,0 +1,61 @@
/*
* memcachetest.java
*
* Created on September 23, 2003, 12:23 AM
*/
/**
*
* @author toast
*/
import com.danga.MemCached.*;
import java.util.ArrayList;
/** This is an example program using a MemCacheClient. */
public class memcachetest {
/** Creates a new instance of memcachetest */
public memcachetest() {
}
/** This runs through some simple tests of the MemCacheClient.
* @param args the command line arguments
*/
public static void main(String[] args) {
MemCachedClient mcc = new MemCachedClient();
String[] serverlist = { "localhost:12345"}; //, "localhost:12346"};
mcc.set_compress_enable(true);
mcc.set_compress_savings(0.0); // compress everthing
mcc.set_compress_threshold(0); // compress everthing
mcc.set_servers(serverlist);
//mcc.set_serial(true);
// Integer foo = new Integer(-2);
mcc.set("foo", "Your mother eats army boots, in the day time, with her friends. " +
"English text should be nice and compressible.");
Object tmp = mcc.get("foo");
System.out.println(tmp);
System.out.println("Sleeping ...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
while (mcc.get("foo") == null) {
System.out.print(".");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
System.out.println(mcc.get("foo"));
}
}

View File

@@ -0,0 +1,164 @@
2004-07-27
* release 1.14
2004-07-27
* kill buggy, slow ord() _hashfunc, replace with crc32.
this adds String::CRC32 as a dependency. thanks to
everybody's feedback on the mailing list.
2004-07-19
* don't use pos() because it doesn't seem to work in
taint mode. use $+[0] instead. (Dave Evans <..@djce.org.uk>)
2004-06-19
* support multiple paths to memcache nodes (Brad)
see 'set_pref_ip'
2004-05-30
* release version 1.13
2004-05-26 (Whitaker <whitaker@danga.com>)
* quiet warning
2004-05-25 (Whitaker <whitaker@danga.com>)
* get_multi shouldn't modify caller's @_
2004-05-18 (Michael <ged@danga.com>)
* namespace support
* use fields
2004-05-16 (Alexei Kozlov <kozlov@w-m.ru>)
* remove warnings with vec
2004-04-09 (brad)
* in perl 5.6, trap errors dethawing 5.8 storable objects
and instead treat it like a cache miss
2004-04-01
* use $! and not %! for perl 5.6 compat (Dave Evans <..@djce.org.uk>)
* don't mark whole IP dead anymore when a node is down (Jason Titus <jtitus@postini.com>)
* start version numbering (Jamie McCarthy <jamie@mccarthy.vg>)
2004-03-09 (Brad/Avva)
* _oneline can return more than one line (but always on a line break),
so caller must decide when it's really time to quit. had to modify
run_command to know that. (which is used by stats)
2004-03-05 (Dave Evans <..@djce.org.uk>)
* Here's a really trivial patch for the Perl binding,
Cache::Memcached. The bug is that the module assumes that the
currently select()ed filehandle is STDOUT, but this might not be
the case. So this patch ensures that the select()ed filehandle is
preserved, not forced to STDOUT.
2004-02-29 (Brad)
* add readonly option
2004-02-27 (Avva)
* Cleaner handling of the case when _oneline is called without a
line parameter (i.e. not to send anything, just read a line from
the socket). Make it depend on $line being defined only,
regardless of its content (thanks Brad!).
2004-02-25 (Avva)
* Asyncify all I/O, finally get rid of alarm() yuckiness, unify all
one-liner command/responses into a single internal API.
2004-02-17
* document in POD the delete method
2004-02-03
* fix bug with 2k read boundaries falling in the middle
of "VALUE ..." or "END" lines, thus halting future
parsing and responses. (eek!)
* version 1.0.12
2003-12-01
* merge stats/stats_reset patch from Jamie McCarthy
* trailing whitespace cleanup
2003-11-08
* work on Solaris/BSD where there's no MSG_NOSIGNAL.
the expense is extra syscalls to change the local
SIGPIPE handler all the time. in the future, it'd
be nice to have an option so Solaris/BSD callers
can say, "Hey, I've turned off SIGPIPE globally,
don't worry about it."
2003-10-26
* add a test file, so automated CPAN test hosts are happy
* check MSG_NOSIGNAL immediately on module load, not on use,
so Solaris dies early. (still on TODO to fix, but better
to fail loudly)
* version 1.0.11
2003-10-25
* version 1.0.10, rename to Cache::Memcached, upload to CPAN
2003-10-18
* implement read/write timeouts everywhere. Now the client shouldn't
hang if the server machine goes down unexpectedly. (avva)
2003-10-16
* use Storable::nfreeze instead of freeze, so hosts from different
architectures can all use the same data. (all must use Perl, though.
the different memcache APIs all store/pickle/serialize data differently)
Suggestion by Jason Titus <jtitus@postini.com>
2003-10-06
* fix _incrdecr to return complete number, not just first
digit (thanks to Ryan T. Dean)
* release version 1.0.9
2003-10-04
* document expiration times in POD (thanks to Tim Bunce
for noting the omission)
* release version 1.0.8
2003-10-03
* add connect timeout of 0.25s, for dead host detection.
We had 1 second a couple revs ago, but lost it when
ditching IO::Socket module. (avva)
2003-10-02
* fix _incrdecr with explicit-hashvalue keys (whitaker)
2003-10-01
* add run_command API call. TODO: document, and document
the $exptime on the setters
2003-09-30
* use send instead of print, so we can set MSG_NOSIGNAL
and not get SIGPIPES, which avoids 3 syscalls of localizing
$SIG{PIPE} and sends everything at once, instead of 4k
stdio chunks. in review: stdio buffered in, send unbuffered
out. TODO: setvbuf so reads are buffered at more than 4k.
2003-09-29
* yet faster parsing
* switch to stdio/perlio instead of raw io: more correct,
simpler parsing code.
2003-09-28
* prevent some warnings
* faster get() call that doesn't use get_multi()
* optimizations for single-server case
* use socket APIs directly, instead of uber-slow IO::* modules
* new faster _load_items parsing
2003-09-04
* emit debug when set/add/replace fails, in addition to succeed
Version 1.0.7
-- compression support (Brad Whitaker)
Version 1.0.6
-- incr/decr client support
-- make delete optionally take second argument (server now supports
a delay time on delete)
-- doc updates from Jamie McCarthy
-- better hashing after dead host detection: new requests go to different
remaining hosts, instead of all to the same one.
Version 1.0.2
-- initial release, about.

View File

@@ -0,0 +1,8 @@
ChangeLog
Memcached.pm
Makefile.PL
README
MANIFEST
TODO
t/use.t

View File

@@ -0,0 +1,13 @@
use ExtUtils::MakeMaker;
WriteMakefile( 'NAME' => 'Cache::Memcached',
'VERSION_FROM' => 'Memcached.pm',
'PREREQ_PM' => {
'Storable' => 0,
'Time::HiRes' => 0,
'String::CRC32' => 0,
},
($] >= 5.005 ?
(ABSTRACT_FROM => 'Memcached.pm',
AUTHOR => 'Brad Fitzpatrick <brad@danga.com>') : ()),
);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,14 @@
This is the Perl API for memcached, a distributed memory cache daemon.
See the documentation within the module for details on its use.
Information on the big picture is available at:
http://www.danga.com/memcached/
Feel free to join the mailing list and ask any questions.
--
Brad Fitzpatrick
brad@danga.com

View File

@@ -0,0 +1,3 @@
(currently empty)

View File

@@ -0,0 +1,7 @@
#!/usr/bin/env perl -w
use strict;
use Test;
BEGIN { plan tests => 1 }
use Cache::Memcached; ok(1);
exit;

View File

@@ -0,0 +1,45 @@
Release 1.0.10
--------------
* bug fix: changes hashing function to crc32, sprintf %u
* feature: optional compression
Release 1.0.9
-------------
* protocol parsing bug
Release 1.0.8
-------------
* whitespace/punctuation/wording cleanups
Release 1.0.7
-------------
* added 3 functions which handle error reporting
error() - returns error number of last error generated, else returns 0
error_string() - returns a string description of error number retuned
error_clear() - clears the last error number and error string
* removed call to preg_match() in _loaditems()
* only non-scalar values are serialize() before being
sent to the server
* added the optional timestamp argument for delete()
read Documentation file for details
* PHPDocs/PEAR style comments added
* abstract debugging (Brion Vibber <brion@pobox.com>)
Release 1.0.6
-------------
* removed all array_push() calls
* applied patch provided by Stuart Herbert<stuart@gentoo.org>
corrects possible endless loop. Available at
http://bugs.gentoo.org/show_bug.cgi?id=25385
* fixed problem with storing large binary files
* added more error checking, specifically on all socket functions
* added support for the INCR and DECR commands
which increment or decrement a value stored in MemCached
* Documentation removed from source and is now available
in the file Documentation
Release 1.0.4
-------------
* initial release, version numbers kept
in sync with MemCached version
* capable of storing any datatype in MemCached

View File

@@ -0,0 +1,258 @@
Ryan Gilfether <hotrodder@rocketmail.com>
http://www.gilfether.com
This module is Copyright (c) 2003 Ryan Gilfether.
All rights reserved.
You may distribute under the terms of the GNU General Public License
This is free software. IT COMES WITHOUT WARRANTY OF ANY KIND.
See the memcached website: http://www.danga.com/memcached/
// Takes one parameter, a array of options. The most important key is
// options["servers"], but that can also be set later with the set_servers()
// method. The servers must be an array of hosts, each of which is
// either a scalar of the form <10.0.0.10:11211> or an array of the
// former and an integer weight value. (the default weight if
// unspecified is 1.) It's recommended that weight values be kept as low
// as possible, as this module currently allocates memory for bucket
// distribution proportional to the total host weights.
// $options["debug"] turns the debugging on if set to true
MemCachedClient::MemCachedClient($options);
// sets up the list of servers and the ports to connect to
// takes an array of servers in the same format as in the constructor
MemCachedClient::set_servers($servers);
// Retrieves a key from the memcache. Returns the value (automatically
// unserialized, if necessary) or FALSE if it fails.
// The $key can optionally be an array, with the first element being the
// hash value, if you want to avoid making this module calculate a hash
// value. You may prefer, for example, to keep all of a given user's
// objects on the same memcache server, so you could use the user's
// unique id as the hash value.
// Possible errors set are:
// MC_ERR_GET
MemCachedClient::get($key);
// just like get(), but takes an array of keys, returns FALSE on error
// Possible errors set are:
// MC_ERR_NOT_ACTIVE
MemCachedClient::get_multi($keys)
// Unconditionally sets a key to a given value in the memcache. Returns true
// if it was stored successfully.
// The $key can optionally be an arrayref, with the first element being the
// hash value, as described above.
// returns TRUE on success else FALSE
// Possible errors set are:
// MC_ERR_NOT_ACTIVE
// MC_ERR_GET_SOCK
// MC_ERR_SOCKET_WRITE
// MC_ERR_SOCKET_READ
// MC_ERR_SET
MemCachedClient::set($key, $value, $exptime);
// Like set(), but only stores in memcache if the key doesn't already exist.
// returns TRUE on success else FALSE
// Possible errors set are:
// MC_ERR_NOT_ACTIVE
// MC_ERR_GET_SOCK
// MC_ERR_SOCKET_WRITE
// MC_ERR_SOCKET_READ
// MC_ERR_SET
MemCachedClient::add($key, $value, $exptime);
// Like set(), but only stores in memcache if the key already exists.
// returns TRUE on success else FALSE
// Possible errors set are:
// MC_ERR_NOT_ACTIVE
// MC_ERR_GET_SOCK
// MC_ERR_SOCKET_WRITE
// MC_ERR_SOCKET_READ
// MC_ERR_SET
MemCachedClient::replace($key, $value, $exptime);
// removes the key from the MemCache
// $time is the amount of time in seconds (or Unix time) until which
// the client wishes the server to refuse "add" and "replace" commands
// with this key. For this amount of item, the item is put into a
// delete queue, which means that it won't possible to retrieve it by
// the "get" command, but "add" and "replace" command with this key
// will also fail (the "set" command will succeed, however). After the
// time passes, the item is finally deleted from server memory.
// The parameter $time is optional, and, if absent, defaults to 0
// (which means that the item will be deleted immediately and further
// storage commands with this key will succeed).
// returns TRUE on success else returns FALSE
// Possible errors set are:
// MC_ERR_NOT_ACTIVE
// MC_ERR_GET_SOCK
// MC_ERR_SOCKET_WRITE
// MC_ERR_SOCKET_READ
// MC_ERR_DELETE
MemCachedClient::delete($key, $time = 0);
// Sends a command to the server to atomically increment the value for
// $key by $value, or by 1 if $value is undefined. Returns FALSE if $key
// doesn't exist on server, otherwise it returns the new value after
// incrementing. Value should be zero or greater. Overflow on server
// is not checked. Be aware of values approaching 2**32. See decr.
// Possible errors set are:
// MC_ERR_NOT_ACTIVE
// MC_ERR_GET_SOCK
// MC_ERR_SOCKET_WRITE
// MC_ERR_SOCKET_READ
// returns new value on success, else returns FALSE
// ONLY WORKS WITH NUMERIC VALUES
MemCachedClient::incr($key[, $value]);
// Like incr, but decrements. Unlike incr, underflow is checked and new
// values are capped at 0. If server value is 1, a decrement of 2
// returns 0, not -1.
// Possible errors set are:
// MC_ERR_NOT_ACTIVE
// MC_ERR_GET_SOCK
// MC_ERR_SOCKET_WRITE
// MC_ERR_SOCKET_READ
// returns new value on success, else returns FALSE
// ONLY WORKS WITH NUMERIC VALUES
MemCachedClient::decr($key[, $value]);
// disconnects from all servers
MemCachedClient::disconnect_all();
// if $do_debug is set to true, will print out
// debugging info, else debug is turned off
MemCachedClient::set_debug($do_debug);
// remove all cached hosts that are no longer good
MemCachedClient::forget_dead_hosts();
// When a function returns FALSE, an error code is set.
// This funtion will return the error code.
// See error_string()
// returns last error code set
MemCachedClient::error()
// Returns a string describing the error set in error()
// See error()
// returns a string describing the error code given
MemCachedClient::error_string()
// Resets the error number and error string
MemCachedClient::error_clear()
Error codes are as follows:
MC_ERR_NOT_ACTIVE // no active servers
MC_ERR_SOCKET_WRITE // socket_write() failed
MC_ERR_SOCKET_READ // socket_read() failed
MC_ERR_SOCKET_CONNECT // failed to connect to host
MC_ERR_DELETE // delete() did not recieve DELETED command
MC_ERR_HOST_FORMAT // sock_to_host() invalid host format
MC_ERR_HOST_DEAD // sock_to_host() host is dead
MC_ERR_GET_SOCK // get_sock() failed to find a valid socket
MC_ERR_SET // _set() failed to receive the STORED response
MC_ERR_LOADITEM_HEADER // _load_items failed to receive valid data header
MC_ERR_LOADITEM_END // _load_items failed to receive END response
MC_ERR_LOADITEM_BYTES // _load_items bytes read larger than bytes available
MC_ERR_GET // failed to get value associated with key
// Turns compression on or off; 0=off, 1=on
MemCacheClient::set_compression($setting)
EXAMPLE:
<?php
require("MemCachedClient.inc.php");
// set the servers, with the last one having an interger weight value of 3
$options["servers"] = array("10.0.0.15:11000","10.0.0.16:11001",array("10.0.0.17:11002", 3));
$options["debug"] = false;
$memc = new MemCachedClient($options);
/***********************
* STORE AN ARRAY
***********************/
$myarr = array("one","two", 3);
$memc->set("key_one", $myarr);
$val = $memc->get("key_one");
print $val[0]."\n"; // prints 'one'
print $val[1]."\n"; // prints 'two'
print $val[2]."\n"; // prints 3
print "\n";
/***********************
* STORE A CLASS
***********************/
class tester
{
var $one;
var $two;
var $three;
}
$t = new tester;
$t->one = "one";
$t->two = "two";
$t->three = 3;
$memc->set("key_two", $t);
$val = $memc->get("key_two");
print $val->one."\n";
print $val->two."\n";
print $val->three."\n";
print "\n";
/***********************
* STORE A STRING
***********************/
$memc->set("key_three", "my string");
$val = $memc->get("key_three");
print $val; // prints 'my string'
$memc->delete("key_one");
$memc->delete("key_two");
$memc->delete("key_three");
$memc->disconnect_all();
print "\n";
/***********************
* STORE A BINARY FILE
***********************/
// first read the file and save it in memcache
$fp = fopen( "./image.jpg", "rb" ) ;
if ( !$fp )
{
print "Could not open ./file.dat!\n" ;
exit ;
}
$data = fread( $fp, filesize( "./image.jpg" ) ) ;
fclose( $fp ) ;
print "Data length is " . strlen( $data ) . "\n" ;
$memc->set( "key", $data ) ;
// now open a file for writing and write the data
// retrieved from memcache
$fp = fopen("./test.jpg","wb");
$data = $memc->get( "key" ) ;
print "Data length is " . strlen( $data ) . "\n" ;
fwrite($fp,$data,strlen( $data ));
fclose($fp);
?>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,70 @@
#!/usr/bin/perl
#
# Simplify Brad's life. I'm sure there's a PHP-specific way
# to do this (or there should be), but I don't do the PHP,
# so this is my answer.
#
# Heavily documented for the PHP programmers who might be
# reading this.
#
use strict;
use Getopt::Long;
my $opt_tar = 0;
my $opt_upload = 0;
exit 1 unless GetOptions("tar" => \$opt_tar,
"upload" => \$opt_upload);
# chdir to the directory the script's at, so future
# paths need only be relative
use FindBin qw($Bin);
chdir $Bin or die "Couldn't cd to $Bin\n";
die "Must use --tar or --upload\n" unless $opt_tar || $opt_upload;
# files to distribute
my @manifest = qw(
ChangeLog
Documentation
MemCachedClient.inc.php
);
# figure out the version number
open (PHP, "MemCachedClient.inc.php") or die;
{ local $/ = undef; $_ = <PHP>; } # suck in the whole file
close PHP;
die "Can't find version number\n" unless
/MC_VERSION.+?(\d[^\'\"]+)/s;
my $ver = $1;
my $dir = "php-memcached-$ver";
my $dist = "$dir.tar.gz";
if ($opt_tar) {
# make a fresh directory
mkdir $dir or die "Couldn't make directory: $dir\n";
# copy files to fresh directory
foreach my $file (@manifest) {
system("cp", $file, "$dir/$file")
and die "Error copying file $file\n";
}
# tar it up
system("tar", "zcf", $dist, $dir)
and die "Error running tar.\n";
# remove temp directory
system("rm", "-rf", $dir)
and die "Error cleaning up temp directory\n";
print "$dist created.\n";
}
if ($opt_upload) {
print "Uploading $dist...\n";
system("scp", $dist, 'bradfitz@danga.com:memd/dist/')
and die "Error uploading to memcached/dist\n";
}
print "Done.\n";

View File

@@ -0,0 +1,16 @@
Thu, 10 Aug 2003 12:17:50 -0700 Evan Martin <martine@danga.com>
* Slightly more verbose self-test output.
* Fix mark_dead() to use proper classname.
* Make pooltest.py run from the test directory.
Thu, 07 Aug 2003 16:32:32 -0700 Evan Martin <martine@danga.com>
* Add incr, decr, and delete.
* Better Python (based on comments from Uriah Welcome).
* Docs, using epydoc.
Thu, 07 Aug 2003 14:20:27 -0700 Evan Martin <martine@danga.com>
* Initial prerelease.

View File

@@ -0,0 +1,3 @@
Note for Brad, on how to make new releases:
$ python setup.py sdist

View File

@@ -0,0 +1,2 @@
memcache.py
setup.py

View File

@@ -0,0 +1,10 @@
Metadata-Version: 1.0
Name: python-memcache
Version: 1.1
Summary: UNKNOWN
Home-page: http://www.danga.com/memcached/
Author: Evan Martin
Author-email: martine@danga.com
License: UNKNOWN
Description: UNKNOWN
Platform: UNKNOWN

View File

@@ -0,0 +1,585 @@
#!/usr/bin/env python
"""
client module for memcached (memory cache daemon)
Overview
========
See U{the MemCached homepage<http://www.danga.com/memcached>} for more about memcached.
Usage summary
=============
This should give you a feel for how this module operates::
import memcache
mc = memcache.Client(['127.0.0.1:11211'], debug=0)
mc.set("some_key", "Some value")
value = mc.get("some_key")
mc.set("another_key", 3)
mc.delete("another_key")
mc.set("key", "1") # note that the key used for incr/decr must be a string.
mc.incr("key")
mc.decr("key")
The standard way to use memcache with a database is like this::
key = derive_key(obj)
obj = mc.get(key)
if not obj:
obj = backend_api.get(...)
mc.set(obj)
# we now have obj, and future passes through this code
# will use the object from the cache.
Detailed Documentation
======================
More detailed documentation is available in the L{Client} class.
"""
import sys
import socket
import time
import types
try:
import cPickle as pickle
except ImportError:
import pickle
__author__ = "Evan Martin <martine@danga.com>"
__version__ = "1.2"
__copyright__ = "Copyright (C) 2003 Danga Interactive"
__license__ = "Python"
class _Error(Exception):
pass
class Client:
"""
Object representing a pool of memcache servers.
See L{memcache} for an overview.
In all cases where a key is used, the key can be either:
1. A simple hashable type (string, integer, etc.).
2. A tuple of C{(hashvalue, key)}. This is useful if you want to avoid
making this module calculate a hash value. You may prefer, for
example, to keep all of a given user's objects on the same memcache
server, so you could use the user's unique id as the hash value.
@group Setup: __init__, set_servers, forget_dead_hosts, disconnect_all, debuglog
@group Insertion: set, add, replace
@group Retrieval: get, get_multi
@group Integers: incr, decr
@group Removal: delete
@sort: __init__, set_servers, forget_dead_hosts, disconnect_all, debuglog,\
set, add, replace, get, get_multi, incr, decr, delete
"""
_FLAG_PICKLE = 1<<0
_FLAG_INTEGER = 1<<1
_FLAG_LONG = 1<<2
_SERVER_RETRIES = 10 # how many times to try finding a free server.
def __init__(self, servers, debug=0):
"""
Create a new Client object with the given list of servers.
@param servers: C{servers} is passed to L{set_servers}.
@param debug: whether to display error messages when a server can't be
contacted.
"""
self.set_servers(servers)
self.debug = debug
self.stats = {}
def set_servers(self, servers):
"""
Set the pool of servers used by this client.
@param servers: an array of servers.
Servers can be passed in two forms:
1. Strings of the form C{"host:port"}, which implies a default weight of 1.
2. Tuples of the form C{("host:port", weight)}, where C{weight} is
an integer weight value.
"""
self.servers = [_Host(s, self.debuglog) for s in servers]
self._init_buckets()
def debuglog(self, str):
if self.debug:
sys.stderr.write("MemCached: %s\n" % str)
def _statlog(self, func):
if not self.stats.has_key(func):
self.stats[func] = 1
else:
self.stats[func] += 1
def forget_dead_hosts(self):
"""
Reset every host in the pool to an "alive" state.
"""
for s in self.servers:
s.dead_until = 0
def _init_buckets(self):
self.buckets = []
for server in self.servers:
for i in range(server.weight):
self.buckets.append(server)
def _get_server(self, key):
if type(key) == types.TupleType:
serverhash = key[0]
key = key[1]
else:
serverhash = hash(key)
for i in range(Client._SERVER_RETRIES):
server = self.buckets[serverhash % len(self.buckets)]
if server.connect():
#print "(using server %s)" % server,
return server, key
serverhash = hash(str(serverhash) + str(i))
return None, None
def disconnect_all(self):
for s in self.servers:
s.close_socket()
def delete(self, key, time=0):
'''Deletes a key from the memcache.
@return: Nonzero on success.
@rtype: int
'''
server, key = self._get_server(key)
if not server:
return 0
self._statlog('delete')
if time != None:
cmd = "delete %s %d" % (key, time)
else:
cmd = "delete %s" % key
try:
server.send_cmd(cmd)
server.expect("DELETED")
except socket.error, msg:
server.mark_dead(msg[1])
return 0
return 1
def incr(self, key, delta=1):
"""
Sends a command to the server to atomically increment the value for C{key} by
C{delta}, or by 1 if C{delta} is unspecified. Returns None if C{key} doesn't
exist on server, otherwise it returns the new value after incrementing.
Note that the value for C{key} must already exist in the memcache, and it
must be the string representation of an integer.
>>> mc.set("counter", "20") # returns 1, indicating success
1
>>> mc.incr("counter")
21
>>> mc.incr("counter")
22
Overflow on server is not checked. Be aware of values approaching
2**32. See L{decr}.
@param delta: Integer amount to increment by (should be zero or greater).
@return: New value after incrementing.
@rtype: int
"""
return self._incrdecr("incr", key, delta)
def decr(self, key, delta=1):
"""
Like L{incr}, but decrements. Unlike L{incr}, underflow is checked and
new values are capped at 0. If server value is 1, a decrement of 2
returns 0, not -1.
@param delta: Integer amount to decrement by (should be zero or greater).
@return: New value after decrementing.
@rtype: int
"""
return self._incrdecr("decr", key, delta)
def _incrdecr(self, cmd, key, delta):
server, key = self._get_server(key)
if not server:
return 0
self._statlog(cmd)
cmd = "%s %s %d" % (cmd, key, delta)
try:
server.send_cmd(cmd)
line = server.readline()
return int(line)
except socket.error, msg:
server.mark_dead(msg[1])
return None
def add(self, key, val, time=0):
'''
Add new key with value.
Like L{set}, but only stores in memcache if the key doesn't already exist.
@return: Nonzero on success.
@rtype: int
'''
return self._set("add", key, val, time)
def replace(self, key, val, time=0):
'''Replace existing key with value.
Like L{set}, but only stores in memcache if the key already exists.
The opposite of L{add}.
@return: Nonzero on success.
@rtype: int
'''
return self._set("replace", key, val, time)
def set(self, key, val, time=0):
'''Unconditionally sets a key to a given value in the memcache.
The C{key} can optionally be an tuple, with the first element being the
hash value, if you want to avoid making this module calculate a hash value.
You may prefer, for example, to keep all of a given user's objects on the
same memcache server, so you could use the user's unique id as the hash
value.
@return: Nonzero on success.
@rtype: int
'''
return self._set("set", key, val, time)
def _set(self, cmd, key, val, time):
server, key = self._get_server(key)
if not server:
return 0
self._statlog(cmd)
flags = 0
if isinstance(val, types.StringTypes):
pass
elif isinstance(val, int):
flags |= Client._FLAG_INTEGER
val = "%d" % val
elif isinstance(val, long):
flags |= Client._FLAG_LONG
val = "%d" % val
else:
flags |= Client._FLAG_PICKLE
val = pickle.dumps(val, 2)
fullcmd = "%s %s %d %d %d\r\n%s" % (cmd, key, flags, time, len(val), val)
try:
server.send_cmd(fullcmd)
server.expect("STORED")
except socket.error, msg:
server.mark_dead(msg[1])
return 0
return 1
def get(self, key):
'''Retrieves a key from the memcache.
@return: The value or None.
'''
server, key = self._get_server(key)
if not server:
return None
self._statlog('get')
try:
server.send_cmd("get %s" % key)
rkey, flags, rlen, = self._expectvalue(server)
if not rkey:
return None
value = self._recv_value(server, flags, rlen)
server.expect("END")
except (_Error, socket.error), msg:
if type(msg) is types.TupleType:
msg = msg[1]
server.mark_dead(msg)
return None
return value
def get_multi(self, keys):
'''
Retrieves multiple keys from the memcache doing just one query.
>>> success = mc.set("foo", "bar")
>>> success = mc.set("baz", 42)
>>> mc.get_multi(["foo", "baz", "foobar"]) == {"foo": "bar", "baz": 42}
1
This method is recommended over regular L{get} as it lowers the number of
total packets flying around your network, reducing total latency, since
your app doesn't have to wait for each round-trip of L{get} before sending
the next one.
@param keys: An array of keys.
@return: A dictionary of key/value pairs that were available.
'''
self._statlog('get_multi')
server_keys = {}
# build up a list for each server of all the keys we want.
for key in keys:
server, key = self._get_server(key)
if not server:
continue
if not server_keys.has_key(server):
server_keys[server] = []
server_keys[server].append(key)
# send out all requests on each server before reading anything
dead_servers = []
for server in server_keys.keys():
try:
server.send_cmd("get %s" % " ".join(server_keys[server]))
except socket.error, msg:
server.mark_dead(msg[1])
dead_servers.append(server)
# if any servers died on the way, don't expect them to respond.
for server in dead_servers:
del server_keys[server]
retvals = {}
for server in server_keys.keys():
try:
line = server.readline()
while line and line != 'END':
rkey, flags, rlen = self._expectvalue(server, line)
val = self._recv_value(server, flags, rlen)
retvals[rkey] = val
line = server.readline()
except (_Error, socket.error), msg:
server.mark_dead(msg)
return retvals
def _expectvalue(self, server, line=None):
if not line:
line = server.readline()
if line[:5] == 'VALUE':
resp, rkey, flags, len = line.split()
flags = int(flags)
rlen = int(len)
return (rkey, flags, rlen)
else:
return (None, None, None)
def _recv_value(self, server, flags, rlen):
rlen += 2 # include \r\n
buf = server.recv(rlen)
if len(buf) != rlen:
raise _Error("received %d bytes when expecting %d" % (len(buf), rlen))
if len(buf) == rlen:
buf = buf[:-2] # strip \r\n
if flags == 0:
val = buf
elif flags & Client._FLAG_INTEGER:
val = int(buf)
elif flags & Client._FLAG_LONG:
val = long(buf)
elif flags & Client._FLAG_PICKLE:
val = pickle.loads(buf)
else:
self.debuglog("unknown flags on get: %x\n" % flags)
return val
class _Host:
_DEAD_RETRY = 30 # number of seconds before retrying a dead server.
def __init__(self, host, debugfunc=None):
if isinstance(host, types.TupleType):
host = host[0]
self.weight = host[1]
else:
self.weight = 1
if host.find(":") > 0:
self.ip, self.port = host.split(":")
self.port = int(self.port)
else:
self.ip, self.port = host, 11211
if not debugfunc:
debugfunc = lambda x: x
self.debuglog = debugfunc
self.deaduntil = 0
self.socket = None
def _check_dead(self):
if self.deaduntil and self.deaduntil > time.time():
return 1
self.deaduntil = 0
return 0
def connect(self):
if self._get_socket():
return 1
return 0
def mark_dead(self, reason):
print "MemCache: %s: %s. Marking dead." % (self, reason)
self.deaduntil = time.time() + _Host._DEAD_RETRY
self.close_socket()
def _get_socket(self):
if self._check_dead():
return None
if self.socket:
return self.socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Python 2.3-ism: s.settimeout(1)
try:
s.connect((self.ip, self.port))
except socket.error, msg:
self.mark_dead("connect: %s" % msg[1])
return None
self.socket = s
return s
def close_socket(self):
if self.socket:
self.socket.close()
self.socket = None
def send_cmd(self, cmd):
self.socket.sendall(cmd + "\r\n")
def readline(self):
newlines = 0
buf = ''
while newlines < 2:
char = self.socket.recv(1) # XXX does this buffer or is this slow?
if len(char) == 0:
# connection closed.
print "MemCache: Connection closed while reading from %s. Marking dead." % self
self.mark_dead
return buf
if char == '\r' and newlines == 0:
newlines = 1
elif char == '\n' and newlines == 1:
newlines = 2
else:
newlines = 0
buf = buf + char
return buf
def expect(self, text):
line = self.readline()
if line != text:
self.debuglog("while expecting '%s', got unexpected response '%s'" % (text, line))
return line
def recv(self, rlen):
buf = ''
while len(buf) < rlen:
buf = buf + self.socket.recv(rlen - len(buf))
return buf
def __str__(self):
d = ''
if self.deaduntil:
d = " (dead until %d)" % self.deaduntil
return "%s:%d%s" % (self.ip, self.port, d)
def _doctest():
import doctest, memcache
servers = ["127.0.0.1:11211"]
mc = Client(servers, debug=1)
globs = {"mc": mc}
return doctest.testmod(memcache, globs=globs)
if __name__ == "__main__":
print "Testing docstrings..."
_doctest()
print "Running tests:"
print
#servers = ["127.0.0.1:11211", "127.0.0.1:11212"]
servers = ["127.0.0.1:11211"]
mc = Client(servers, debug=1)
def to_s(val):
if not isinstance(val, types.StringTypes):
return "%s (%s)" % (val, type(val))
return "%s" % val
def test_setget(key, val):
print "Testing set/get {'%s': %s} ..." % (to_s(key), to_s(val)),
mc.set(key, val)
newval = mc.get(key)
if newval == val:
print "OK"
return 1
else:
print "FAIL"
return 0
class FooStruct:
def __init__(self):
self.bar = "baz"
def __str__(self):
return "A FooStruct"
def __eq__(self, other):
if isinstance(other, FooStruct):
return self.bar == other.bar
return 0
test_setget("a_string", "some random string")
test_setget("an_integer", 42)
if test_setget("long", long(1<<30)):
print "Testing delete ...",
if mc.delete("long"):
print "OK"
else:
print "FAIL"
print "Testing get_multi ...",
print mc.get_multi(["a_string", "an_integer"])
print "Testing get(unknown value) ...",
print to_s(mc.get("unknown_value"))
f = FooStruct()
test_setget("foostruct", f)
print "Testing incr ...",
x = mc.incr("an_integer", 1)
if x == 43:
print "OK"
else:
print "FAIL"
print "Testing decr ...",
x = mc.decr("an_integer", 1)
if x == 42:
print "OK"
else:
print "FAIL"
# vim: ts=4 sw=4 et :

View File

@@ -0,0 +1,12 @@
#!/usr/bin/env python
from distutils.core import setup
import memcache
setup(name="python-memcached",
version=memcache.__version__,
author="Evan Martin",
author_email="martine@danga.com",
url="http://www.danga.com/memcached/",
py_modules=["memcache"])

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env python
"""pooltest
Bring up two memcaches on :11211 and :11212. Try killing one or both.
If this code raises any exceptions, it's a bug."""
import sys
sys.path.append("..")
import memcache
import time
mc = memcache.Client(["127.0.0.1:11211", "127.0.0.1:11212"], debug=1)
def test_setget(key, val):
print "Testing set/get {'%s': %s} ..." % (key, val),
mc.set(key, val)
newval = mc.get(key)
if newval == val:
print "OK"
else:
print "FAIL"
i = 0
while 1:
test_setget("foo%d" % i, "bar%d" % i)
time.sleep(1)
i += 1

186
wcmtools/memcached/assoc.c Executable file
View File

@@ -0,0 +1,186 @@
/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
* Hash table
*
* The hash function used here is by Bob Jenkins, 1996:
* <http://burtleburtle.net/bob/hash/doobs.html>
* "By Bob Jenkins, 1996. bob_jenkins@burtleburtle.net.
* You may use this code any way you wish, private, educational,
* or commercial. It's free."
*
* The rest of the file is licensed under the BSD license. See LICENSE.
*
* $Id: assoc.c,v 1.6 2003/08/11 05:44:26 bradfitz Exp $
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/signal.h>
#include <sys/resource.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <errno.h>
#include <event.h>
#include <assert.h>
#include "memcached.h"
typedef unsigned long int ub4; /* unsigned 4-byte quantities */
typedef unsigned char ub1; /* unsigned 1-byte quantities */
/* hard-code one million buckets, for now (2**20 == 4MB hash) */
#define HASHPOWER 20
#define hashsize(n) ((ub4)1<<(n))
#define hashmask(n) (hashsize(n)-1)
#define mix(a,b,c) \
{ \
a -= b; a -= c; a ^= (c>>13); \
b -= c; b -= a; b ^= (a<<8); \
c -= a; c -= b; c ^= (b>>13); \
a -= b; a -= c; a ^= (c>>12); \
b -= c; b -= a; b ^= (a<<16); \
c -= a; c -= b; c ^= (b>>5); \
a -= b; a -= c; a ^= (c>>3); \
b -= c; b -= a; b ^= (a<<10); \
c -= a; c -= b; c ^= (b>>15); \
}
/*
--------------------------------------------------------------------
hash() -- hash a variable-length key into a 32-bit value
k : the key (the unaligned variable-length array of bytes)
len : the length of the key, counting by bytes
initval : can be any 4-byte value
Returns a 32-bit value. Every bit of the key affects every bit of
the return value. Every 1-bit and 2-bit delta achieves avalanche.
About 6*len+35 instructions.
The best hash table sizes are powers of 2. There is no need to do
mod a prime (mod is sooo slow!). If you need less than 32 bits,
use a bitmask. For example, if you need only 10 bits, do
h = (h & hashmask(10));
In which case, the hash table should have hashsize(10) elements.
If you are hashing n strings (ub1 **)k, do it like this:
for (i=0, h=0; i<n; ++i) h = hash( k[i], len[i], h);
By Bob Jenkins, 1996. bob_jenkins@burtleburtle.net. You may use this
code any way you wish, private, educational, or commercial. It's free.
See http://burtleburtle.net/bob/hash/evahash.html
Use for hash table lookup, or anything where one collision in 2^^32 is
acceptable. Do NOT use for cryptographic purposes.
--------------------------------------------------------------------
*/
ub4 hash( k, length, initval)
register ub1 *k; /* the key */
register ub4 length; /* the length of the key */
register ub4 initval; /* the previous hash, or an arbitrary value */
{
register ub4 a,b,c,len;
/* Set up the internal state */
len = length;
a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */
c = initval; /* the previous hash value */
/*---------------------------------------- handle most of the key */
while (len >= 12)
{
a += (k[0] +((ub4)k[1]<<8) +((ub4)k[2]<<16) +((ub4)k[3]<<24));
b += (k[4] +((ub4)k[5]<<8) +((ub4)k[6]<<16) +((ub4)k[7]<<24));
c += (k[8] +((ub4)k[9]<<8) +((ub4)k[10]<<16)+((ub4)k[11]<<24));
mix(a,b,c);
k += 12; len -= 12;
}
/*------------------------------------- handle the last 11 bytes */
c += length;
switch(len) /* all the case statements fall through */
{
case 11: c+=((ub4)k[10]<<24);
case 10: c+=((ub4)k[9]<<16);
case 9 : c+=((ub4)k[8]<<8);
/* the first byte of c is reserved for the length */
case 8 : b+=((ub4)k[7]<<24);
case 7 : b+=((ub4)k[6]<<16);
case 6 : b+=((ub4)k[5]<<8);
case 5 : b+=k[4];
case 4 : a+=((ub4)k[3]<<24);
case 3 : a+=((ub4)k[2]<<16);
case 2 : a+=((ub4)k[1]<<8);
case 1 : a+=k[0];
/* case 0: nothing left to add */
}
mix(a,b,c);
/*-------------------------------------------- report the result */
return c;
}
static item** hashtable = 0;
void assoc_init(void) {
unsigned int hash_size = hashsize(HASHPOWER) * sizeof(void*);
hashtable = malloc(hash_size);
if (! hashtable) {
fprintf(stderr, "Failed to init hashtable.\n");
exit(1);
}
memset(hashtable, 0, hash_size);
}
item *assoc_find(char *key) {
ub4 hv = hash(key, strlen(key), 0) & hashmask(HASHPOWER);
item *it = hashtable[hv];
while (it) {
if (strcmp(key, ITEM_key(it)) == 0)
return it;
it = it->h_next;
}
return 0;
}
/* returns the address of the item pointer before the key. if *item == 0,
the item wasn't found */
static item** _hashitem_before (char *key) {
ub4 hv = hash(key, strlen(key), 0) & hashmask(HASHPOWER);
item **pos = &hashtable[hv];
while (*pos && strcmp(key, ITEM_key(*pos))) {
pos = &(*pos)->h_next;
}
return pos;
}
/* Note: this isn't an assoc_update. The key must not already exist to call this */
int assoc_insert(char *key, item *it) {
ub4 hv = hash(key, strlen(key), 0) & hashmask(HASHPOWER);
it->h_next = hashtable[hv];
hashtable[hv] = it;
return 1;
}
void assoc_delete(char *key) {
item **before = _hashitem_before(key);
if (*before) {
item *nxt = (*before)->h_next;
(*before)->h_next = 0; /* probably pointless, but whatever. */
*before = nxt;
return;
}
/* Note: we never actually get here. the callers don't delete things
they can't find. */
assert(*before != 0);
}

22
wcmtools/memcached/autogen.sh Executable file
View File

@@ -0,0 +1,22 @@
#!/bin/sh
#
# This is hacky, because there are so many damn versions
# of autoconf/automake. It works with Debian woody, at least.
#
echo "aclocal..."
ACLOCAL=${ACLOCAL:-aclocal-1.7}
$ACLOCAL || aclocal-1.5 || aclocal || exit 1
echo "autoheader..."
AUTOHEADER=${AUTOHEADER:-autoheader}
$AUTOHEADER || exit 1
echo "automake..."
AUTOMAKE=${AUTOMAKE:-automake-1.7}
$AUTOMAKE --foreign --add-missing || automake --gnu --add-missing || exit 1
echo "autoconf..."
AUTOCONF=${AUTOCONF:-autoconf}
$AUTOCONF || exit 1

56
wcmtools/memcached/configure.ac Executable file
View File

@@ -0,0 +1,56 @@
AC_PREREQ(2.52)
AC_INIT(memcached, 1.1.11, brad@danga.com)
AC_CANONICAL_SYSTEM
AC_CONFIG_SRCDIR(memcached.c)
AM_INIT_AUTOMAKE(AC_PACKAGE_NAME, AC_PACKAGE_VERSION)
AM_CONFIG_HEADER(config.h)
AC_PROG_CC
AC_PROG_INSTALL
AC_ARG_WITH(libevent,
AC_HELP_STRING([--with-libevent=DIRECTORY],[base directory for libevent]))
if test "$with_libevent" != "no"; then
CFLAGS="$CFLAGS -I$with_libevent/include"
LDFLAGS="$LDFLAGS -L$with_libevent/lib"
fi
LIBEVENT_URL=http://www.monkey.org/~provos/libevent/
AC_CHECK_LIB(event, event_set, ,
[AC_MSG_ERROR(libevent is required. You can get it from $LIBEVENT_URL)])
AC_CHECK_HEADER(malloc.h, AC_DEFINE(HAVE_MALLOC_H,,[do we have malloc.h?]))
AC_CHECK_MEMBER([struct mallinfo.arena], [
AC_DEFINE(HAVE_STRUCT_MALLINFO,,[do we have stuct mallinfo?])
], ,[
# include <malloc.h>
]
)
dnl From licq: Copyright (c) 2000 Dirk Mueller
dnl Check if the type socklen_t is defined anywhere
AC_DEFUN(AC_C_SOCKLEN_T,
[AC_CACHE_CHECK(for socklen_t, ac_cv_c_socklen_t,
[
AC_TRY_COMPILE([
#include <sys/types.h>
#include <sys/socket.h>
],[
socklen_t foo;
],[
ac_cv_c_socklen_t=yes
],[
ac_cv_c_socklen_t=no
])
])
if test $ac_cv_c_socklen_t = no; then
AC_DEFINE(socklen_t, int, [define to int if socklen_t not available])
fi
])
AC_C_SOCKLEN_T
AC_CHECK_FUNCS(mlockall)
AC_CONFIG_FILES(Makefile doc/Makefile)
AC_OUTPUT

View File

@@ -0,0 +1,3 @@
man_MANS = memcached.1
EXTRA_DIST = *.txt

View File

@@ -0,0 +1,86 @@
.TH MEMCACHED 1 "April 11, 2005"
.SH NAME
memcached \- high-performance memory object caching system
.SH SYNOPSIS
.B memcached
.RI [ options ]
.br
.SH DESCRIPTION
This manual page documents briefly the
.B memcached
memory object caching daemon.
.PP
.B memcached
is a flexible memory object caching daemon designed to alleviate database load
in dynamic web applications by storing objects in memory. It's based on
libevent to scale to any size needed, and is specifically optimized to avoid
swapping and always use non-blocking I/O.
.br
.SH OPTIONS
These programs follow the usual GNU command line syntax. A summary of options
is included below.
.TP
.B \-l <ip_addr>
Listen on <ip_addr>; default to INDRR_ANY. This is an important option to
consider as there is no other way to secure the installation. Binding to an
internal or firewalled network interface is suggested.
.TP
.B \-d
Run memcached as a daemon.
.TP
.B \-u <username>
Assume the identity of <username> (only when run as root).
.TP
.B \-m <num>
Use <num> MB memory max to use for object storage; the default is 64 megabytes.
.TP
.B \-c <num>
Use <num> max simultaneous connections; the default is 1024.
.TP
.B \-k
Lock down all paged memory. This is a somewhat dangerous option with large
caches, so consult the README and memcached homepage for configuration
suggestions.
.TP
.B \-p <num>
Listen on port <num>, the default is port 11211.
.TP
.B \-M
Disable automatic removal of items from the cache when out of memory.
Additions will not be possible until adequate space is freed up.
.TP
.B \-r
Raise the core file size limit to the maximum allowable.
.TP
.B \-h
Show the version of memcached and a summary of options.
.TP
.B \-v
Be verbose during the event loop; print out errors and warnings.
.TP
.B \-vv
Be even more verbose; same as \-v but also print client commands and
responses.
.TP
.B \-i
Print memcached and libevent licenses.
.TP
.B \-P <filename>
Print pidfile to <filename>, only used under -d option.
.br
.SH LICENSE
The memcached daemon is copyright Danga Interactive and is distributed under
the BSD license. Note that daemon clients are licensed separately.
.br
.SH SEE ALSO
The README file that comes with memcached
.br
.B http://www.danga.com/memcached
.SH AUTHOR
The memcached daemon was written by Anatoly Vorobey
.B <mellon@pobox.com>
and Brad Fitzpatrick
.B <brad@danga.com>
and the rest of the crew of Danga Interactive
.B http://www.danga.com
.br

View File

@@ -0,0 +1,83 @@
Date: Fri, 5 Sep 2003 20:31:03 +0300
From: Anatoly Vorobey <mellon@pobox.com>
To: memcached@lists.danga.com
Subject: Re: Memory Management...
On Fri, Sep 05, 2003 at 12:07:48PM -0400, Kyle R. Burton wrote:
> prefixing keys with a container identifier). We have just begun to
> look at the implementation of the memory management sub-system with
> regards to it's allocation, de-allocation and compaction approaches.
> Is there any documentation or discussion of how this subsystem
> operates? (slabs.c?)
There's no documentation yet, and it's worth mentioning that this
subsystem is the most active area of memcached under development at the
moment (however, all the changes to it won't modify the way memcached
presents itself towards clients, they're primarily directed at making
memcached use memory more efficiently).
Here's a quick recap of what it does now and what is being worked
on.
The primary goal of the slabs subsystem in memcached was to eliminate
memory fragmentation issues totally by using fixed-size memory chunks
coming from a few predetermined size classes (early versions of
memcached relied on malloc()'s handling of fragmentation which proved
woefully inadequate for our purposes). For instance, suppose
we decide at the outset that the list of possible sizes is: 64 bytes,
128 bytes, 256 bytes, etc. - doubling all the way up to 1Mb. For each
size class in this list (each possible size) we maintain a list of free
chunks of this size. Whenever a request comes for a particular size,
it is rounded up to the closest size class and a free chunk is taken
from that size class. In the above example, if you request from the
slabs subsystem 100 bytes of memory, you'll actually get a chunk 128
bytes worth, from the 128-bytes size class. If there are no free chunks
of the needed size at the moment, there are two ways to get one: 1) free
an existing chunk in the same size class, using LRU queues to free the
least needed objects; 2) get more memory from the system, which we
currently always do in _slabs_ of 1Mb each; we malloc() a slab, divide
it to chunks of the needed size, and use them.
The tradeoff is between memory fragmentation and memory utilisation. In
the scheme we're now using, we have zero fragmentation, but a relatively
high percentage of memory is wasted. The most efficient way to reduce
the waste is to use a list of size classes that closely matches (if
that's at all possible) common sizes of objects that the clients
of this particular installation of memcached are likely to store.
For example, if your installation is going to store hundreds of thousands of objects of the size exactly 120 bytes, you'd be much better
off changing, in the "naive" list of sizes outlined above, the class
of 128 bytes to something a bit higher (because the overhead of
storing an item, while not large, will push those 120-bytes objects over
128 bytes of storage internally, and will require using 256 bytes for
each of them in the naive scheme, forcing you to waste almost 50% of
memory). Such tinkering with the list of size classes is not currently
possible with memcached, but enabling it is one of the immediate goals.
Ideally, the slabs subsystem would analyze at runtime the common sizes
of objects that are being requested, and would be able to modify the
list of sizes dynamically to improve memory utilisation. This is not
planned for the immediate future, however. What is planned is the
ability to reassign slabs to different classes. Here's what this means.
Currently, the total amount of memory allocated for each size class is
determined by how clients interact with memcached during the initial
phase of its execution, when it keeps malloc()'ing more slabs and
dividing them into chunks, until it hits the specified memory limit
(say, 2Gb, or whatever else was specified). Once it hits the limit, to
allocate a new chunk it'll always delete an existing chunk of the same
size (using LRU queues), and will never malloc() or free() any memory
from/to the system. So if, for example, during those initial few hours
of memcached's execution your clients mainly wanted to store very small
items, the bulk of memory allocated will be divided to small-sized
chunks, and the large size classes will get fewer memory, therefore the
life-cycle of large objects you'll store in memcached will henceforth
always be much shorter, with this instance of memcached (their LRU
queues will be shorter and they'll be pushed out much more often). In
general, if your system starts producing a different pattern of common
object sizes, the memcached servers will become less efficient, unless
you restart them. Slabs reassignment, which is the next feature being
worked on, will ensure the server's ability to reclaim a slab (1Mb of
memory) from one size class and put it into another class size, where
it's needed more.
--
avva

View File

@@ -0,0 +1,389 @@
Protocol
--------
Clients of memcached communicate with server through TCP
connections. A given running memcached server listens on some
(configurable) port; clients connect to that port, send commands to
the server, read responses, and eventually close the connection.
There is no need to send any command to end the session. A client may
just close the connection at any moment it no longer needs it. Note,
however, that clients are encouraged to cache their connections rather
than reopen them every time they need to store or retrieve data. This
is because memcached is especially designed to work very efficiently
with a very large number (many hundreds, more than a thousand if
necessary) of open connections. Caching connections will eliminate the
overhead associated with establishing a TCP connection (the overhead
of preparing for a new connection on the server side is insignificant
compared to this).
There are two kinds of data sent in the memcache protocol: text lines
and unstructured data. Text lines are used for commands from clients
and responses from servers. Unstructured data is sent when a client
wants to store or retrieve data. The server will transmit back
unstructured data in exactly the same way it received it, as a byte
stream. The server doesn't care about byte order issues in
unstructured data and isn't aware of them. There are no limitations on
characters that may appear in unstructured data; however, the reader
of such data (either a client or a server) will always know, from a
preceding text line, the exact length of the data block being
transmitted.
Text lines are always terminated by \r\n. Unstructured data is _also_
terminated by \r\n, even though \r, \n or any other 8-bit characters
may also appear inside the data. Therefore, when a client retrieves
data from a server, it must use the length of the data block (which it
will be provided with) to determine where the data block ends, and not
the fact that \r\n follows the end of the data block, even though it
does.
Keys
----
Data stored by memcached is identified with the help of a key. A key
is a text string which should uniquely identify the data for clients
that are interested in storing and retrieving it. Currently the
length limit of a key is set at 250 characters (of course, normally
clients wouldn't need to use such long keys); the key must not include
control characters or whitespace.
Commands
--------
There are three types of commands.
Storage commands (there are three: "set", "add" and "replace") ask the
server to store some data identified by a key. The client sends a
command line, and then a data block; after that the client expects one
line of response, which will indicate success or faulure.
Retrieval commands (there is only one: "get") ask the server to
retrieve data corresponding to a set of keys (one or more keys in one
request). The client sends a command line, which includes all the
requested keys; after that for each item the server finds it sends to
the client one response line with information about the item, and one
data block with the item's data; this continues until the server
finished with the "END" response line.
All other commands don't involve unstructured data. In all of them,
the client sends one command line, and expects (depending on the
command) either one line of response, or several lines of response
ending with "END" on the last line.
A command line always starts with the name of the command, followed by
parameters (if any) delimited by whitespace. Command names are
lower-case and are case-sensitive.
Expiration times
----------------
Some commands involve a client sending some kind of expiration time
(relative to an item or to an operation requested by the client) to
the server. In all such cases, the actual value sent may either be
Unix time (number of seconds since January 1, 1970, as a 32-bit
value), or a number of seconds starting from current time. In the
latter case, this number of seconds may not exceed 60*60*24*30 (number
of seconds in 30 days); if the number sent by a client is larger than
that, the server will consider it to be real Unix time value rather
than an offset from current time.
Error strings
-------------
Each command sent by a client may be answered with an error string
from the server. These error strings come in three types:
- "ERROR\r\n"
means the client sent a nonexistent command name.
- "CLIENT_ERROR <error>\r\n"
means some sort of client error in the input line, i.e. the input
doesn't conform to the protocol in some way. <error> is a
human-readable error string.
- "SERVER_ERROR <error>\r\n"
means some sort of server error prevents the server from carrying
out the command. <error> is a human-readable error string. In cases
of severe server errors, which make it impossible to continue
serving the client (this shouldn't normally happen), the server will
close the connection after sending the error line. This is the only
case in which the server closes a connection to a client.
In the descriptions of individual commands below, these error lines
are not again specifically mentioned, but clients must allow for their
possibility.
Storage commands
----------------
First, the client sends a command line which looks like this:
<command name> <key> <flags> <exptime> <bytes>\r\n
- <command name> is "set", "add" or "replace"
"set" means "store this data".
"add" means "store this data, but only if the server *doesn't* already
hold data for this key".
"replace" means "store this data, but only if the server *does*
already hold data for this key".
- <key> is the key under which the client asks to store the data
- <flags> is an arbitrary 16-bit unsigned integer (written out in
decimal) that the server stores along with the data and sends back
when the item is retrieved. Clients may use this as a bit field to
store data-specific information; this field is opaque to the server.
- <exptime> is expiration time. If it's 0, the item never expires
(although it may be deleted from the cache to make place for other
items). If it's non-zero (either Unix time or offset in seconds from
current time), it is guaranteed that clients will not be able to
retrieve this item after the expiration time arrives (measured by
server time).
- <bytes> is the number of bytes in the data block to follow, *not*
including the delimiting \r\n. <bytes> may be zero (in which case
it's followed by an empty data block).
After this line, the client sends the data block:
<data block>\r\n
- <data block> is a chunk of arbitrary 8-bit data of length <bytes>
from the previous line.
After sending the command line and the data blockm the client awaits
the reply, which may be:
- "STORED\r\n", to indicate success.
- "NOT_STORED\r\n" to indicate the data was not stored, but not
because of an error. This normally means that either that the
condition for an "add" or a "replace" command wasn't met, or that the
item is in a delete queue (see the "delete" command below).
Retrieval command:
------------------
The retrieval command looks like this:
get <key>*\r\n
- <key>* means one or more key strings separated by whitespace.
After this command, the client expects zero or more items, each of
which is received as a text line followed by a data block. After all
the items have been transmitted, the server sends the string
"END\r\n"
to indicate the end of response.
Each item sent by the server looks like this:
VALUE <key> <flags> <bytes>\r\n
<data block>\r\n
- <key> is the key for the item being sent
- <flags> is the flags value set by the storage command
- <bytes> is the length of the data block to follow, *not* including
its delimiting \r\n
- <data block> is the data for this item.
If some of the keys appearing in a retrieval request are not sent back
by the server in the item list this means that the server does not
hold items with such keys (because they were never stored, or stored
but deleted to make space for more items, or expired, or explicitly
deleted by a client).
Deletion
--------
The command "delete" allows for explicit deletion of items:
delete <key> <time>\r\n
- <key> is the key of the item the client wishes the server to delete
- <time> is the amount of time in seconds (or Unix time until which)
the client wishes the server to refuse "add" and "replace" commands
with this key. For this amount of item, the item is put into a
delete queue, which means that it won't possible to retrieve it by
the "get" command, but "add" and "replace" command with this key
will also fail (the "set" command will succeed, however). After the
time passes, the item is finally deleted from server memory.
The parameter <time> is optional, and, if absent, defaults to 0
(which means that the item will be deleted immediately and further
storage commands with this key will succeed).
The response line to this command can be one of:
- "DELETED\r\n" to indicate success
- "NOT_FOUND\r\n" to indicate that the item with this key was not
found.
See the "flush_all" command below for immediate invalidation
of all existing items.
Increment/Decrement
-------------------
Commands "incr" and "decr" are used to change data for some item
in-place, incrementing or decrementing it. The data for the item is
treated as decimal representation of a 32-bit unsigned integer. If the
current data value does not conform to such a representation, the
commands behave as if the value were 0. Also, the item must already
exist for incr/decr to work; these commands won't pretend that a
non-existent key exists with value 0; instead, they will fail.
The client sends the command line:
incr <key> <value>\r\n
or
decr <key> <value>\r\n
- <key> is the key of the item the client wishes to change
- <value> is the amount by which the client wants to increase/decrease
the item. It is a decimal representation of a 32-bit unsigned integer.
The response will be one of:
- "NOT_FOUND\r\n" to indicate the item with this value was not found
- <value>\r\n , where <value> is the new value of the item's data,
after the increment/decrement operation was carried out.
Note that underflow in the "decr" command is caught: if a client tries
to decrease the value below 0, the new value will be 0. Overflow in
the "incr" command is not checked.
Statistics
----------
The command "stats" is used to query the server about statistics it
maintains and other internal data. It has two forms. Without
arguments:
stats\r\n
it causes the server to output general-purpose statistics and
settings, documented below. In the other form it has some arguments:
stats <args>\r\n
Depending on <args>, various internal data is sent by the server. The
kinds of arguments and the data sent are not documented in this vesion
of the protocol, and are subject to change for the convenience of
memcache developers.
General-purpose statistics
--------------------------
Upon receiving the "stats" command without arguments, the server sents
a number of lines which look like this:
STAT <name> <value>\r\n
The server terminates this list with the line
END\r\n
In each line of statistics, <name> is the name of this statistic, and
<value> is the data. The following is the list of all names sent in
response to the "stats" command, together with the type of the value
sent for this name, and the meaning of the value.
In the type column below, "32u" means a 32-bit unsigned integer, "64u"
means a 64-bit unsigner integer. '32u:32u' means two 32-but unsigned
integers separated by a colon.
Name Type Meaning
----------------------------------
pid 32u Process id of this server process
uptime 32u Number of seconds this server has been running
time 32u current UNIX time according to the server
version string Version string of this server
rusage_user 32u:32u Accumulated user time for this process
(seconds:microseconds)
rusage_system 32u:32u Accumulated system time for this process
(seconds:microseconds)
curr_items 32u Current number of items stored by the server
total_items 32u Total number of items stored by this server
ever since it started
bytes 64u Current number of bytes used by this server
to store items
curr_connections 32u Number of open connections
total_connections 32u Total number of connections opened since
the server started running
connection_structures 32u Number of connection structures allocated
by the server
cmd_get 32u Cumulative number of retrieval requests
cmd_set 32u Cumulative number of storage requests
get_hits 32u Number of keys that have been requested and
found present
get_misses 32u Number of items that have been requested
and not found
bytes_read 64u Total number of bytes read by this server
from network
bytes_written 64u Total number of bytes sent by this server to
network
limit_maxbytes 32u Number of bytes this server is allowed to
use for storage.
Other commands
--------------
"flush_all" is a command with no arguments. It always succeeds,
and the server sends "OK\r\n" in response. Its effect is to immediately
invalidate all existing items: none of them will be returned in
response to a retrieval command (unless it's stored again under the
same key *after* flush_all has been executed). flush_all doesn't
actually free all the memory taken up by existing items; that will
happen gradually as new items are stored. The most precise definition
of what flush_all does is the following: it causes all items whose
update time is earlier than the time at which flush_all was executed
to be ignored for retrieval purposes.
"version" is a command with no arguments:
version\r\n
In response, the server sends
"VERSION <version>\r\n", where <version> is the version string for the
server.
"quit" is a command with no arguments:
quit\r\n
Upon receiving this command, the server closes the
connection. However, the client may also simply close the connection
when it no longer needs it, without issuing this command.

View File

@@ -0,0 +1,2 @@
By Evan Martin <martine@danga.com>

View File

@@ -0,0 +1,121 @@
#!/usr/bin/perl
# vim: set ts=4 sw=4 et :
use strict;
use Gtk2;
use Gtk2::Gdk::Keysyms;
use Cache::Memcached;
use Data::Dumper;
$Data::Dumper::Terse = 1;
my @cmds = ();
my $cmd_cur = -1;
my $mc = new Cache::Memcached {
'servers' => [ "127.0.0.1:11211" ]
};
Gtk2->init;
my $win = Gtk2::Window->new("toplevel");
$win->signal_connect(delete_event => sub { Gtk2->main_quit });
$win->set_border_width(10);
my $vb = Gtk2::VBox->new(0, 5);
my $display = Gtk2::TextView->new;
my $buffer = $display->get_buffer;
$display->set_editable(0);
my $scroll = Gtk2::ScrolledWindow->new;
$scroll->set_policy('automatic', 'automatic');
$scroll->set_shadow_type('in');
$scroll->add($display);
$vb->pack_start($scroll, 1, 1, 0);
$display->modify_font(Gtk2::Pango::FontDescription->from_string("monospace"));
$buffer->create_tag("command", "foreground", "blue");
$buffer->create_tag("data", "foreground", "black");
$buffer->create_tag("error", "foreground", "red");
my $entry = Gtk2::Entry->new();
$entry->signal_connect(key_press_event => \&entry_keypress);
$entry->signal_connect(activate => \&entry_activate);
$vb->pack_start($entry, 0, 0, 0);
$win->add($vb);
$win->set_title("MemCachedClient");
$win->set_default_size(400, 500);
$win->signal_connect(show => sub { $entry->grab_focus });
$win->show_all;
Gtk2->main;
sub display {
my ($level, $text) = @_;
$buffer->insert_with_tags_by_name($buffer->get_end_iter, "$text\n", $level);
}
sub run_command {
my ($text) = @_;
# if we're rerunning a history command, then
# we should pull it out of its old spot in the history.
splice(@cmds, $cmd_cur, 1)
if $cmd_cur >= 0 and $cmds[$cmd_cur] eq $text;
# and in any case, add this command to the history.
unshift(@cmds, $text);
$cmd_cur = -1;
display('command', $text);
if ($text =~ /^get\s+(\S+)$/i) {
my $str = $mc->get($1);
if (ref $str) {
$str = Dumper($str);
$str =~ s/^ //gm;
}
if ($str) {
display('data', $str);
} else {
display('error', "Not found.");
}
} elsif ($text =~ /^set\s+(\S+)\s+(.*)$/i) {
my ($key, $val) = ($1, $2);
if ($mc->set($key, $val)) {
display('data', "Ok.");
} else {
display('error', "Not found.");
}
} elsif ($text =~ /^delete\s+(\S+)$/i) {
$mc->delete($1);
display('data', "Ok.");
} else {
display('error', "Unknown command '$text'.");
}
}
sub entry_keypress {
my ($entry, $ev) = @_;
if ($ev->keyval == $Gtk2::Gdk::Keysyms{'Up'}) {
$cmd_cur++ if $cmd_cur < @cmds-1;
$entry->set_text($cmds[$cmd_cur]) if $cmds[$cmd_cur];
return 1;
} elsif ($ev->keyval == $Gtk2::Gdk::Keysyms{'Down'}) {
$cmd_cur-- if $cmd_cur >= 0;
if ($cmd_cur >= 0 and $cmds[$cmd_cur]) {
$entry->set_text($cmds[$cmd_cur]);
} else {
$entry->set_text('');
}
return 1;
}
return 0;
}
sub entry_activate {
my $text = $entry->get_text;
if ($text =~ /\w/) {
run_command($entry->get_text);
$entry->set_text("");
}
}

299
wcmtools/memcached/items.c Executable file
View File

@@ -0,0 +1,299 @@
/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/* $Id: items.c,v 1.23 2004/09/13 22:31:53 avva Exp $ */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/signal.h>
#include <sys/resource.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <errno.h>
#include <time.h>
#include <event.h>
#include <assert.h>
#include "memcached.h"
/*
* NOTE: we assume here for simplicity that slab ids are <=32. That's true in
* the powers-of-2 implementation, but if that changes this should be changed too
*/
#define LARGEST_ID 32
static item *heads[LARGEST_ID];
static item *tails[LARGEST_ID];
unsigned int sizes[LARGEST_ID];
void item_init(void) {
int i;
for(i=0; i<LARGEST_ID; i++) {
heads[i]=0;
tails[i]=0;
sizes[i]=0;
}
}
item *item_alloc(char *key, int flags, time_t exptime, int nbytes) {
int ntotal, len;
item *it;
unsigned int id;
len = strlen(key) + 1; if(len % 4) len += 4 - (len % 4);
ntotal = sizeof(item) + len + nbytes;
id = slabs_clsid(ntotal);
if (id == 0)
return 0;
it = slabs_alloc(ntotal);
if (it == 0) {
int tries = 50;
item *search;
/* If requested to not push old items out of cache when memory runs out,
* we're out of luck at this point...
*/
if (!settings.evict_to_free) return 0;
/*
* try to get one off the right LRU
* don't necessariuly unlink the tail because it may be locked: refcount>0
* search up from tail an item with refcount==0 and unlink it; give up after 50
* tries
*/
if (id > LARGEST_ID) return 0;
if (tails[id]==0) return 0;
for (search = tails[id]; tries>0 && search; tries--, search=search->prev) {
if (search->refcount==0) {
item_unlink(search);
break;
}
}
it = slabs_alloc(ntotal);
if (it==0) return 0;
}
assert(it->slabs_clsid == 0);
it->slabs_clsid = id;
assert(it != heads[it->slabs_clsid]);
it->next = it->prev = it->h_next = 0;
it->refcount = 0;
it->it_flags = 0;
it->nkey = len;
it->nbytes = nbytes;
strcpy(ITEM_key(it), key);
it->exptime = exptime;
it->flags = flags;
return it;
}
void item_free(item *it) {
unsigned int ntotal = ITEM_ntotal(it);
assert((it->it_flags & ITEM_LINKED) == 0);
assert(it != heads[it->slabs_clsid]);
assert(it != tails[it->slabs_clsid]);
assert(it->refcount == 0);
/* so slab size changer can tell later if item is already free or not */
it->slabs_clsid = 0;
it->it_flags |= ITEM_SLABBED;
slabs_free(it, ntotal);
}
void item_link_q(item *it) { /* item is the new head */
item **head, **tail;
assert(it->slabs_clsid <= LARGEST_ID);
assert((it->it_flags & ITEM_SLABBED) == 0);
head = &heads[it->slabs_clsid];
tail = &tails[it->slabs_clsid];
assert(it != *head);
assert((*head && *tail) || (*head == 0 && *tail == 0));
it->prev = 0;
it->next = *head;
if (it->next) it->next->prev = it;
*head = it;
if (*tail == 0) *tail = it;
sizes[it->slabs_clsid]++;
return;
}
void item_unlink_q(item *it) {
item **head, **tail;
assert(it->slabs_clsid <= LARGEST_ID);
head = &heads[it->slabs_clsid];
tail = &tails[it->slabs_clsid];
if (*head == it) {
assert(it->prev == 0);
*head = it->next;
}
if (*tail == it) {
assert(it->next == 0);
*tail = it->prev;
}
assert(it->next != it);
assert(it->prev != it);
if (it->next) it->next->prev = it->prev;
if (it->prev) it->prev->next = it->next;
sizes[it->slabs_clsid]--;
return;
}
int item_link(item *it) {
assert((it->it_flags & (ITEM_LINKED|ITEM_SLABBED)) == 0);
assert(it->nbytes < 1048576);
it->it_flags |= ITEM_LINKED;
it->time = time(0);
assoc_insert(ITEM_key(it), it);
stats.curr_bytes += ITEM_ntotal(it);
stats.curr_items += 1;
stats.total_items += 1;
item_link_q(it);
return 1;
}
void item_unlink(item *it) {
if (it->it_flags & ITEM_LINKED) {
it->it_flags &= ~ITEM_LINKED;
stats.curr_bytes -= ITEM_ntotal(it);
stats.curr_items -= 1;
assoc_delete(ITEM_key(it));
item_unlink_q(it);
}
if (it->refcount == 0) item_free(it);
}
void item_remove(item *it) {
assert((it->it_flags & ITEM_SLABBED) == 0);
if (it->refcount) it->refcount--;
assert((it->it_flags & ITEM_DELETED) == 0 || it->refcount);
if (it->refcount == 0 && (it->it_flags & ITEM_LINKED) == 0) {
item_free(it);
}
}
void item_update(item *it) {
assert((it->it_flags & ITEM_SLABBED) == 0);
item_unlink_q(it);
it->time = time(0);
item_link_q(it);
}
int item_replace(item *it, item *new_it) {
assert((it->it_flags & ITEM_SLABBED) == 0);
item_unlink(it);
return item_link(new_it);
}
char *item_cachedump(unsigned int slabs_clsid, unsigned int limit, unsigned int *bytes) {
int memlimit = 2*1024*1024;
char *buffer;
int bufcurr;
item *it;
int len;
int shown = 0;
char temp[512];
if (slabs_clsid > LARGEST_ID) return 0;
it = heads[slabs_clsid];
buffer = malloc(memlimit);
if (buffer == 0) return 0;
bufcurr = 0;
while (it && (!limit || shown < limit)) {
len = sprintf(temp, "ITEM %s [%u b; %lu s]\r\n", ITEM_key(it), it->nbytes - 2, it->time);
if (bufcurr + len + 6 > memlimit) /* 6 is END\r\n\0 */
break;
strcpy(buffer + bufcurr, temp);
bufcurr+=len;
shown++;
it = it->next;
}
strcpy(buffer+bufcurr, "END\r\n");
bufcurr+=5;
*bytes = bufcurr;
return buffer;
}
void item_stats(char *buffer, int buflen) {
int i;
char *bufcurr = buffer;
time_t now = time(0);
if (buflen < 4096) {
strcpy(buffer, "SERVER_ERROR out of memory");
return;
}
for (i=0; i<LARGEST_ID; i++) {
if (tails[i])
bufcurr += sprintf(bufcurr, "STAT items:%u:number %u\r\nSTAT items:%u:age %lu\r\n",
i, sizes[i], i, now - tails[i]->time);
}
strcpy(bufcurr, "END");
return;
}
/* dumps out a list of objects of each size, with granularity of 32 bytes */
char* item_stats_sizes(int *bytes) {
int num_buckets = 32768; /* max 1MB object, divided into 32 bytes size buckets */
unsigned int *histogram = (int*) malloc(num_buckets * sizeof(int));
char *buf = (char*) malloc(1024*1024*2*sizeof(char));
int i;
if (histogram == 0 || buf == 0) {
if (histogram) free(histogram);
if (buf) free(buf);
return 0;
}
/* build the histogram */
memset(histogram, 0, num_buckets * sizeof(int));
for (i=0; i<LARGEST_ID; i++) {
item *iter = heads[i];
while (iter) {
int ntotal = ITEM_ntotal(iter);
int bucket = ntotal / 32;
if (ntotal % 32) bucket++;
if (bucket < num_buckets) histogram[bucket]++;
iter = iter->next;
}
}
/* write the buffer */
*bytes = 0;
for (i=0; i<num_buckets; i++) {
if (histogram[i]) {
*bytes += sprintf(&buf[*bytes], "%u %u\r\n", i*32, histogram[i]);
}
}
*bytes += sprintf(&buf[*bytes], "END\r\n");
free(histogram);
return buf;
}

1554
wcmtools/memcached/memcached.c Executable file

File diff suppressed because it is too large Load Diff

212
wcmtools/memcached/memcached.h Executable file
View File

@@ -0,0 +1,212 @@
/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/* $Id: memcached.h,v 1.21 2004/02/24 23:42:02 bradfitz Exp $ */
#define DATA_BUFFER_SIZE 2048
#if defined(TCP_CORK) && !defined(TCP_NOPUSH)
#define TCP_NOPUSH TCP_CORK
#endif
struct stats {
unsigned int curr_items;
unsigned int total_items;
unsigned long long curr_bytes;
unsigned int curr_conns;
unsigned int total_conns;
unsigned int conn_structs;
unsigned int get_cmds;
unsigned int set_cmds;
unsigned int get_hits;
unsigned int get_misses;
time_t started; /* when the process was started */
unsigned long long bytes_read;
unsigned long long bytes_written;
};
struct settings {
unsigned int maxbytes;
int maxconns;
int port;
struct in_addr interface;
int verbose;
time_t oldest_live; /* ignore existing items older than this */
int evict_to_free;
};
extern struct stats stats;
extern struct settings settings;
#define ITEM_LINKED 1
#define ITEM_DELETED 2
/* temp */
#define ITEM_SLABBED 4
typedef struct _stritem {
struct _stritem *next;
struct _stritem *prev;
struct _stritem *h_next; /* hash chain next */
unsigned short refcount;
unsigned short flags;
int nbytes; /* size of data */
time_t time; /* least recent access */
time_t exptime; /* expire time */
unsigned char it_flags; /* ITEM_* above */
unsigned char slabs_clsid;
unsigned char nkey; /* key length, with terminating null and padding */
unsigned char dummy1;
void * end[0];
} item;
#define ITEM_key(item) ((char*)&((item)->end[0]))
/* warning: don't use these macros with a function, as it evals its arg twice */
#define ITEM_data(item) ((char*) &((item)->end[0]) + (item)->nkey)
#define ITEM_ntotal(item) (sizeof(struct _stritem) + (item)->nkey + (item)->nbytes)
enum conn_states {
conn_listening, /* the socket which listens for connections */
conn_read, /* reading in a command line */
conn_write, /* writing out a simple response */
conn_nread, /* reading in a fixed number of bytes */
conn_swallow, /* swallowing unnecessary bytes w/o storing */
conn_closing, /* closing this connection */
conn_mwrite /* writing out many items sequentially */
};
#define NREAD_ADD 1
#define NREAD_SET 2
#define NREAD_REPLACE 3
typedef struct {
int sfd;
int state;
struct event event;
short ev_flags;
short which; /* which events were just triggered */
char *rbuf;
int rsize;
int rbytes;
char *wbuf;
char *wcurr;
int wsize;
int wbytes;
int write_and_go; /* which state to go into after finishing current write */
void *write_and_free; /* free this memory after finishing writing */
char is_corked; /* boolean, connection is corked */
char *rcurr;
int rlbytes;
/* data for the nread state */
/*
* item is used to hold an item structure created after reading the command
* line of set/add/replace commands, but before we finished reading the actual
* data. The data is read into ITEM_data(item) to avoid extra copying.
*/
void *item; /* for commands set/add/replace */
int item_comm; /* which one is it: set/add/replace */
/* data for the swallow state */
int sbytes; /* how many bytes to swallow */
/* data for the mwrite state */
item **ilist; /* list of items to write out */
int isize;
item **icurr;
int ileft;
int ipart; /* 1 if we're writing a VALUE line, 2 if we're writing data */
char ibuf[300]; /* for VALUE lines */
char *iptr;
int ibytes;
} conn;
/* listening socket */
extern int l_socket;
/* temporary hack */
/* #define assert(x) if(!(x)) { printf("assert failure: %s\n", #x); pre_gdb(); }
void pre_gdb (); */
/*
* Functions
*/
/*
* given time value that's either unix time or delta from current unix time, return
* unix time. Use the fact that delta can't exceed one month (and real time value can't
* be that low).
*/
time_t realtime(time_t exptime);
/* slabs memory allocation */
/* Init the subsystem. The argument is the limit on no. of bytes to allocate, 0 if no limit */
void slabs_init(unsigned int limit);
/* Given object size, return id to use when allocating/freeing memory for object */
/* 0 means error: can't store such a large object */
unsigned int slabs_clsid(unsigned int size);
/* Allocate object of given length. 0 on error */
void *slabs_alloc(unsigned int size);
/* Free previously allocated object */
void slabs_free(void *ptr, unsigned int size);
/* Fill buffer with stats */
char* slabs_stats(int *buflen);
/* Request some slab be moved between classes
1 = success
0 = fail
-1 = tried. busy. send again shortly. */
int slabs_reassign(unsigned char srcid, unsigned char dstid);
/* event handling, network IO */
void event_handler(int fd, short which, void *arg);
conn *conn_new(int sfd, int init_state, int event_flags);
void conn_close(conn *c);
void conn_init(void);
void drive_machine(conn *c);
int new_socket(void);
int server_socket(int port);
int update_event(conn *c, int new_flags);
int try_read_command(conn *c);
int try_read_network(conn *c);
void complete_nread(conn *c);
void process_command(conn *c, char *command);
/* stats */
void stats_reset(void);
void stats_init(void);
/* defaults */
void settings_init(void);
/* associative array */
void assoc_init(void);
item *assoc_find(char *key);
int assoc_insert(char *key, item *item);
void assoc_delete(char *key);
void item_init(void);
item *item_alloc(char *key, int flags, time_t exptime, int nbytes);
void item_free(item *it);
int item_link(item *it); /* may fail if transgresses limits */
void item_unlink(item *it);
void item_remove(item *it);
void item_update(item *it); /* update LRU time to current and reposition */
int item_replace(item *it, item *new_it);
char *item_cachedump(unsigned int slabs_clsid, unsigned int limit, unsigned int *bytes);
char *item_stats_sizes(int *bytes);
void item_stats(char *buffer, int buflen);

View File

@@ -0,0 +1,59 @@
#! /bin/sh
#
# skeleton example file to build /etc/init.d/ scripts.
# This file should be used to construct scripts for /etc/init.d.
#
# Written by Miquel van Smoorenburg <miquels@cistron.nl>.
# Modified for Debian
# by Ian Murdock <imurdock@gnu.ai.mit.edu>.
#
# Version: @(#)skeleton 1.9 26-Feb-2001 miquels@cistron.nl
#
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/bin/memcached
DAEMONBOOTSTRAP=/usr/share/memcached/scripts/start-memcached
NAME=memcached
DESC=memcached
PIDFILE=/var/run/$NAME.pid
test -x $DAEMON || exit 0
test -x $DAEMONBOOTSTRAP || exit 0
set -e
case "$1" in
start)
echo -n "Starting $DESC: "
start-stop-daemon --start --quiet --exec $DAEMONBOOTSTRAP
echo "$NAME."
;;
stop)
echo -n "Stopping $DESC: "
start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE --exec $DAEMON
echo "$NAME."
rm -f $PIDFILE
;;
restart|force-reload)
#
# If the "reload" option is implemented, move the "force-reload"
# option to the "reload" entry above. If not, "force-reload" is
# just the same as "restart".
#
echo -n "Restarting $DESC: "
start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE
rm -f $PIDFILE
sleep 1
start-stop-daemon --start --quiet --exec $DAEMONBOOTSTRAP
echo "$NAME."
;;
*)
N=/etc/init.d/$NAME
# echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2
echo "Usage: $N {start|stop|restart|force-reload}" >&2
exit 1
;;
esac
exit 0

View File

@@ -0,0 +1,111 @@
#!/usr/bin/perl
#
# memcached-tool:
# stats/management tool for memcached.
#
# Author:
# Brad Fitzpatrick <brad@danga.com>
#
# License:
# public domain. I give up all rights to this
# tool. modify and copy at will.
#
use strict;
use IO::Socket::INET;
my $host = shift;
my $mode = shift || "display";
my ($from, $to);
if ($mode eq "display") {
undef $mode if @ARGV;
} elsif ($mode eq "move") {
$from = shift;
$to = shift;
undef $mode if $from < 6 || $from > 17;
undef $mode if $to < 6 || $to > 17;
print STDERR "ERROR: parameters out of range\n\n" unless $mode;
} else {
undef $mode;
}
undef $mode if @ARGV;
die
"Usage: memcached-tool <host[:port]> [mode]\n
memcached-tool 10.0.0.5:11211 display # shows slabs
memcached-tool 10.0.0.5:11211 # same. (default is display)
memcached-tool 10.0.0.5:11211 move 7 9 # takes 1MB slab from class #7
# to class #9.
You can only move slabs around once memory is totally allocated, and only
once the target class is full. (So you can't move from #6 to #9 and #7
to #9 at the same itme, since you'd have to wait for #9 to fill from
the first reassigned page)
" unless $host && $mode;
$host .= ":11211" unless $host =~ /:\d+/;
my $sock = IO::Socket::INET->new(PeerAddr => $host,
Proto => 'tcp');
die "Couldn't connect to $host\n" unless $sock;
if ($mode eq "move") {
my $tries = 0;
while (1) {
print $sock "slabs reassign $from $to\r\n";
my $res = <$sock>;
$res =~ s/\s+//;
if ($res eq "DONE") {
print "Success.\n";
exit 0;
} elsif ($res eq "CANT") {
print "Error: can't move from $from to $to. Destination not yet full? See usage docs.\n";
exit;
} elsif ($res eq "BUSY") {
if (++$tries == 3) {
print "Failed to move after 3 tries. Try again later.\n";
exit;
}
print "Page busy, retrying...\n";
sleep 1;
}
}
exit;
}
# display mode:
my %items; # class -> { number, age, chunk_size, chunks_per_page,
# total_pages, total_chunks, used_chunks,
# free_chunks, free_chunks_end }
print $sock "stats items\r\n";
while (<$sock>) {
last if /^END/;
if (/^STAT items:(\d+):(\w+) (\d+)/) {
$items{$1}{$2} = $3;
}
}
print $sock "stats slabs\r\n";
while (<$sock>) {
last if /^END/;
if (/^STAT (\d+):(\w+) (\d+)/) {
$items{$1}{$2} = $3;
}
}
print " # Item_Size Max_age 1MB_pages Full?\n";
foreach my $n (6..17) {
my $it = $items{$n};
my $size = $it->{chunk_size} < 1024 ? "$it->{chunk_size} B" :
sprintf("%d kB", $it->{chunk_size} / 1024);
my $full = $it->{free_chunks_end} == 0 ? "yes" : " no";
printf "%3d %6s%7d s %7d $full\n", $n, $size, $it->{age}, $it->{total_pages};
}

View File

@@ -0,0 +1,117 @@
#!/usr/bin/perl -w
# start-memcached
# 2003/2004 - Jay Bonci <jaybonci@debian.org>
# This script handles the parsing of the /etc/memcached.conf file
# and was originally created for the Debian distribution.
# Anyone may use this little script under the same terms as
# memcached itself.
use strict;
if($> != 0 and $< != 0)
{
print STDERR "Only root wants to run start-memcached.\n";
exit;
}
my $params; my $etchandle; my $etcfile = "/etc/memcached.conf";
# This script assumes that memcached is located at /usr/bin/memcached, and
# that the pidfile is writable at /var/run/memcached.pid
my $memcached = "/usr/bin/memcached";
my $pidfile = "/var/run/memcached.pid";
# If we don't get a valid logfile parameter in the /etc/memcached.conf file,
# we'll just throw away all of our in-daemon output. We need to re-tie it so
# that non-bash shells will not hang on logout. Thanks to Michael Renner for
# the tip
my $fd_reopened = "/dev/null";
sub handle_logfile
{
my ($logfile) = @_;
$fd_reopened = $logfile;
}
sub reopen_logfile
{
my ($logfile) = @_;
open *STDERR, ">>$logfile";
open *STDOUT, ">>$logfile";
open *STDIN, ">>/dev/null";
$fd_reopened = $logfile;
}
# This is set up in place here to support other non -[a-z] directives
my $conf_directives = {
"logfile" => \&handle_logfile,
};
if(open $etchandle, $etcfile)
{
foreach my $line (<$etchandle>)
{
$line ||= "";
$line =~ s/\#.*//g;
$line =~ s/\s+$//g;
$line =~ s/^\s+//g;
next unless $line;
next if $line =~ /^\-[dh]/;
if($line =~ /^[^\-]/)
{
my ($directive, $arg) = $line =~ /^(.*?)\s+(.*)/;
$conf_directives->{$directive}->($arg);
next;
}
push @$params, $line;
}
}else{
$params = [];
}
push @$params, "-u root" unless(grep "-u", @$params);
$params = join " ", @$params;
if(-e $pidfile)
{
open PIDHANDLE, "$pidfile";
my $localpid = <PIDHANDLE>;
close PIDHANDLE;
chomp $localpid;
if(-d "/proc/$localpid")
{
print STDERR "memcached is already running.\n";
exit;
}else{
`rm -f $localpid`;
}
}
my $pid = fork();
if($pid == 0)
{
reopen_logfile($fd_reopened);
exec "$memcached $params";
exit(0);
}else{
if(open PIDHANDLE,">$pidfile")
{
print PIDHANDLE $pid;
close PIDHANDLE;
}else{
print STDERR "Can't write pidfile to $pidfile.\n";
}
}

290
wcmtools/memcached/slabs.c Executable file
View File

@@ -0,0 +1,290 @@
/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
* Slabs memory allocation, based on powers-of-2
*
* $Id: slabs.c,v 1.15 2003/09/05 22:37:36 bradfitz Exp $
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/signal.h>
#include <sys/resource.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <errno.h>
#include <event.h>
#include <assert.h>
#include "memcached.h"
#define POWER_SMALLEST 3
#define POWER_LARGEST 20
#define POWER_BLOCK 1048576
/* powers-of-2 allocation structures */
typedef struct {
unsigned int size; /* sizes of items */
unsigned int perslab; /* how many items per slab */
void **slots; /* list of item ptrs */
unsigned int sl_total; /* size of previous array */
unsigned int sl_curr; /* first free slot */
void *end_page_ptr; /* pointer to next free item at end of page, or 0 */
unsigned int end_page_free; /* number of items remaining at end of last alloced page */
unsigned int slabs; /* how many slabs were allocated for this class */
void **slab_list; /* array of slab pointers */
unsigned int list_size; /* size of prev array */
unsigned int killing; /* index+1 of dying slab, or zero if none */
} slabclass_t;
static slabclass_t slabclass[POWER_LARGEST+1];
static unsigned int mem_limit = 0;
static unsigned int mem_malloced = 0;
unsigned int slabs_clsid(unsigned int size) {
int res = 1;
if(size==0)
return 0;
size--;
while(size >>= 1)
res++;
if (res < POWER_SMALLEST)
res = POWER_SMALLEST;
if (res > POWER_LARGEST)
res = 0;
return res;
}
void slabs_init(unsigned int limit) {
int i;
int size=1;
mem_limit = limit;
for(i=0; i<=POWER_LARGEST; i++, size*=2) {
slabclass[i].size = size;
slabclass[i].perslab = POWER_BLOCK / size;
slabclass[i].slots = 0;
slabclass[i].sl_curr = slabclass[i].sl_total = slabclass[i].slabs = 0;
slabclass[i].end_page_ptr = 0;
slabclass[i].end_page_free = 0;
slabclass[i].slab_list = 0;
slabclass[i].list_size = 0;
slabclass[i].killing = 0;
}
}
static int grow_slab_list (unsigned int id) {
slabclass_t *p = &slabclass[id];
if (p->slabs == p->list_size) {
unsigned int new_size = p->list_size ? p->list_size * 2 : 16;
void *new_list = realloc(p->slab_list, new_size*sizeof(void*));
if (new_list == 0) return 0;
p->list_size = new_size;
p->slab_list = new_list;
}
return 1;
}
int slabs_newslab(unsigned int id) {
slabclass_t *p = &slabclass[id];
int num = p->perslab;
int len = POWER_BLOCK;
char *ptr;
if (mem_limit && mem_malloced + len > mem_limit)
return 0;
if (! grow_slab_list(id)) return 0;
ptr = malloc(len);
if (ptr == 0) return 0;
memset(ptr, 0, len);
p->end_page_ptr = ptr;
p->end_page_free = num;
p->slab_list[p->slabs++] = ptr;
mem_malloced += len;
return 1;
}
void *slabs_alloc(unsigned int size) {
slabclass_t *p;
unsigned char id = slabs_clsid(size);
if (id < POWER_SMALLEST || id > POWER_LARGEST)
return 0;
p = &slabclass[id];
assert(p->sl_curr == 0 || ((item*)p->slots[p->sl_curr-1])->slabs_clsid == 0);
#ifdef USE_SYSTEM_MALLOC
if (mem_limit && mem_malloced + size > mem_limit)
return 0;
mem_malloced += size;
return malloc(size);
#endif
/* fail unless we have space at the end of a recently allocated page,
we have something on our freelist, or we could allocate a new page */
if (! (p->end_page_ptr || p->sl_curr || slabs_newslab(id)))
return 0;
/* return off our freelist, if we have one */
if (p->sl_curr)
return p->slots[--p->sl_curr];
/* if we recently allocated a whole page, return from that */
if (p->end_page_ptr) {
void *ptr = p->end_page_ptr;
if (--p->end_page_free) {
p->end_page_ptr += p->size;
} else {
p->end_page_ptr = 0;
}
return ptr;
}
return 0; /* shouldn't ever get here */
}
void slabs_free(void *ptr, unsigned int size) {
unsigned char id = slabs_clsid(size);
slabclass_t *p;
assert(((item *)ptr)->slabs_clsid==0);
assert(id >= POWER_SMALLEST && id <= POWER_LARGEST);
if (id < POWER_SMALLEST || id > POWER_LARGEST)
return;
p = &slabclass[id];
#ifdef USE_SYSTEM_MALLOC
mem_malloced -= size;
free(ptr);
return;
#endif
if (p->sl_curr == p->sl_total) { /* need more space on the free list */
int new_size = p->sl_total ? p->sl_total*2 : 16; /* 16 is arbitrary */
void **new_slots = realloc(p->slots, new_size*sizeof(void *));
if (new_slots == 0)
return;
p->slots = new_slots;
p->sl_total = new_size;
}
p->slots[p->sl_curr++] = ptr;
return;
}
char* slabs_stats(int *buflen) {
int i, total;
char *buf = (char*) malloc(8192);
char *bufcurr = buf;
*buflen = 0;
if (!buf) return 0;
total = 0;
for(i = POWER_SMALLEST; i <= POWER_LARGEST; i++) {
slabclass_t *p = &slabclass[i];
if (p->slabs) {
unsigned int perslab, slabs;
slabs = p->slabs;
perslab = p->perslab;
bufcurr += sprintf(bufcurr, "STAT %d:chunk_size %u\r\n", i, p->size);
bufcurr += sprintf(bufcurr, "STAT %d:chunks_per_page %u\r\n", i, perslab);
bufcurr += sprintf(bufcurr, "STAT %d:total_pages %u\r\n", i, slabs);
bufcurr += sprintf(bufcurr, "STAT %d:total_chunks %u\r\n", i, slabs*perslab);
bufcurr += sprintf(bufcurr, "STAT %d:used_chunks %u\r\n", i, slabs*perslab - p->sl_curr);
bufcurr += sprintf(bufcurr, "STAT %d:free_chunks %u\r\n", i, p->sl_curr);
bufcurr += sprintf(bufcurr, "STAT %d:free_chunks_end %u\r\n", i, p->end_page_free);
total++;
}
}
bufcurr += sprintf(bufcurr, "STAT active_slabs %d\r\nSTAT total_malloced %u\r\n", total, mem_malloced);
bufcurr += sprintf(bufcurr, "END\r\n");
*buflen = bufcurr - buf;
return buf;
}
/* 1 = success
0 = fail
-1 = tried. busy. send again shortly. */
int slabs_reassign(unsigned char srcid, unsigned char dstid) {
void *slab, *slab_end;
slabclass_t *p, *dp;
void *iter;
int was_busy = 0;
if (srcid < POWER_SMALLEST || srcid > POWER_LARGEST ||
dstid < POWER_SMALLEST || dstid > POWER_LARGEST)
return 0;
p = &slabclass[srcid];
dp = &slabclass[dstid];
/* fail if src still populating, or no slab to give up in src */
if (p->end_page_ptr || ! p->slabs)
return 0;
/* fail if dst is still growing or we can't make room to hold its new one */
if (dp->end_page_ptr || ! grow_slab_list(dstid))
return 0;
if (p->killing == 0) p->killing = 1;
slab = p->slab_list[p->killing-1];
slab_end = slab + POWER_BLOCK;
for (iter=slab; iter<slab_end; iter+=p->size) {
item *it = (item *) iter;
if (it->slabs_clsid) {
if (it->refcount) was_busy = 1;
item_unlink(it);
}
}
/* go through free list and discard items that are no longer part of this slab */
{
int fi;
for (fi=p->sl_curr-1; fi>=0; fi--) {
if (p->slots[fi] >= slab && p->slots[fi] < slab_end) {
p->sl_curr--;
if (p->sl_curr > fi) p->slots[fi] = p->slots[p->sl_curr];
}
}
}
if (was_busy) return -1;
/* if good, now move it to the dst slab class */
p->slab_list[p->killing-1] = p->slab_list[p->slabs-1];
p->slabs--;
p->killing = 0;
dp->slab_list[dp->slabs++] = slab;
dp->end_page_ptr = slab;
dp->end_page_free = dp->perslab;
/* this isn't too critical, but other parts of the code do asserts to
make sure this field is always 0. */
for (iter=slab; iter<slab_end; iter+=dp->size) {
((item *)iter)->slabs_clsid = 0;
}
return 1;
}

View File

@@ -0,0 +1,101 @@
#!/usr/bin/perl
#
use strict;
use lib '../api/perl';
use MemCachedClient;
use Time::HiRes qw(time);
unless (@ARGV == 2) {
die "Usage: stress-memcached.pl ip:port threads\n";
}
my $host = shift;
my $threads = shift;
my $memc = new MemCachedClient;
$memc->set_servers([$host]);
unless ($memc->set("foo", "bar") &&
$memc->get("foo") eq "bar") {
die "memcached not running at $host ?\n";
}
$memc->disconnect_all();
my $running = 0;
while (1) {
if ($running < $threads) {
my $cpid = fork();
if ($cpid) {
$running++;
#print "Launched $cpid. Running $running threads.\n";
} else {
stress();
exit 0;
}
} else {
wait();
$running--;
}
}
sub stress {
undef $memc;
$memc = new MemCachedClient;
$memc->set_servers([$host]);
my ($t1, $t2);
my $start = sub { $t1 = time(); };
my $stop = sub {
my $op = shift;
$t2 = time();
my $td = sprintf("%0.3f", $t2 - $t1);
if ($td > 0.25) { print "Took $td seconds for: $op\n"; }
};
my $max = rand(50);
my $sets = 0;
for (my $i = 0; $i < $max; $i++) {
my $key = key($i);
my $set = $memc->set($key, $key);
$sets++ if $set;
}
for (1..int(rand(500))) {
my $rand = int(rand($max));
my $key = key($rand);
my $meth = int(rand(3));
my $exp = int(rand(3));
undef $exp unless $exp;
$start->();
if ($meth == 0) {
$memc->add($key, $key, $exp);
$stop->("add");
} elsif ($meth == 1) {
$memc->delete($key);
$stop->("delete");
} else {
$memc->set($key, $key, $exp);
$stop->("set");
}
$rand = int(rand($max));
$key = key($rand);
$start->();
my $v = $memc->get($key);
$stop->("get");
if ($v && $v ne $key) { die "Bogus: $v for key $rand\n"; }
}
$start->();
my $multi = $memc->get_multi(map { key(int(rand($max))) } (1..$max));
$stop->("get_multi");
}
sub key {
my $n = shift;
$_ = sprintf("%04d", $n);
if ($n % 2) { $_ .= "a"x20; }
$_;
}

View File

@@ -0,0 +1,2 @@
LookRoot .
ForceScheme memcached

View File

@@ -0,0 +1,78 @@
<?page
wintitle=>client apis
body<=
<?h1 Perl API h1?>
<p> An object-oriented Perl module can be found on CPAN as <tt>Cache::Memcached</tt> or downloaded here.</p>
<ul>
<li><a href="dist/Cache-Memcached-1.14.tar.gz">Cache-Memcached-1.14.tar.gz</a>, GPL/Artistic.
[<a href="http://cvs.danga.com/browse.cgi/wcmtools/memcached/api/perl/ChangeLog?rev=HEAD&amp;content-type=text/plain">ChangeLog</a>]
</ul>
<p>The API takes advantage of the server's opaque flag support and
sets its "complex" flag whenever the object being stored or retrieved
isn't a plain scalar. In that case, the <tt>Storable</tt> module is
used to freeze and thaw the value automatically going in and out of
the memcached.</p>
<?h1 PHP API h1?>
<p>Several PHP APIs are available:</p>
<ul>
<li><a href="http://phpca.cytherianage.net/memcached/">http://phpca.cytherianage.net/memcached/</a></li>
<li><a href="http://pecl.php.net/package/memcache/">http://pecl.php.net/package/memcache/</a> -- implemented in C, but doesn't support multiple memcached servers</li>
<li>... (many others, send me links?)</li>
</ul>
<?h1 Python API h1?>
<p>An object-oriented Python module modelled after the Perl one is also
included in the distribution. It includes Pickle/cPickle support (like Perl's Storable).</p>
<ul>
<li><a href="dist/python-memcached-1.2.tar.gz">python-memcached-1.2.tar.gz</a>, Python license.
[<a href="http://cvs.danga.com/browse.cgi/wcmtools/memcached/api/python/ChangeLog?rev=HEAD&amp;content-type=text/plain">ChangeLog</a>]
</ul>
<p>The Python API, while tested, has not experienced real-world production use.
Also, its performance could be optimized (less syscalls parsing responses), if anybody
out there would care to improve it.</p>
<p>Please contact us to take over its development.</p>
<?h1 Ruby API h1?>
<p>A Ruby API is available from Michael Granger at:</p>
<ul>
<li><a href="http://www.deveiate.org/code/Ruby-MemCache.html">http://www.deveiate.org/code/Ruby-MemCache.html</a></li>
</ul>
<?h1 Java API h1?>
<p>A Java API is maintained by <a href="mailto:greg@meetup.com">Greg Whalin</a> from <a href="http://www.meetup.com/">Meetup.com</a>. You can find that library here:
<ul>
<li><a href="http://www.whalin.com/memcached/">http://www.whalin.com/memcached/</a> -- Java API for memcached
</ul>
<?h1 C API h1?>
<p>Multiple C libraries for memcached exist:</p>
<ul>
<li><a href="http://www.outoforder.cc/projects/libs/apr_memcache/">apr_memcache</a> by Paul Querna; Apache Software License version 2.0</li>
<li><a href="http://people.freebsd.org/~seanc/libmemcache/">libmemcache</a> by Sean Chittenden; BSD license.</li>
</ul>
<?h1 Protocol h1?> <p>To write a new client, check out the <a
href="http://cvs.danga.com/browse.cgi/wcmtools/memcached/doc/protocol.txt?rev=HEAD&amp;content-type=text/plain">protocol
docs</a>. Be aware that the most important part of the client is the
hashing across multiple servers, based on the key, or an optional
caller-provided hashing value. Feel free to join the mailing list (or
mail me directly) for help, inclusion in CVS, and/or a link to your
client from this site.</p>
<=body
page?>

View File

@@ -0,0 +1,18 @@
<?page
wintitle=>download
body<=
<?h1 Download Server h1?>
<p>The server is licensed under the BSD License. Read the <a href="http://cvs.danga.com/browse.cgi/wcmtools/memcached/ChangeLog?rev=HEAD&amp;content-type=text/plain">ChangeLog</a> for details on what's new.</p>
<ul>
<li><a href="dist/memcached-1.1.12.tar.gz">memcached-1.1.12.tar.gz</a> -- Apr 4, 2005
</ul>
<?h1 Download Client APIs h1?>
<p>The client APIs, licensed individually, are available for download on the <a href="apis.bml">Client APIs</a> page.</p>
<=body
page?>

View File

@@ -0,0 +1,116 @@
<?page
wintitle=>a distributed memory object caching system
body<=
<?h1 What is <?memd?>? h1?>
<p><?memd?> is a high-performance, distributed memory object caching system, generic in nature, but intended for use in speeding up dynamic web applications by alleviating database load.</p>
<p><a href="http://www.danga.com/">Danga Interactive</a> developed <?memd?> to enhance the speed of <a href="http://www.livejournal.com/">LiveJournal.com</a>, a site which was already doing 20 million+ dynamic page views per day for 1 million users with a bunch of webservers and a bunch of database servers. <?memd?> dropped the database load to almost nothing, yielding faster page load times for users, better resource utilization, and faster access to the databases on a memcache miss.</p>
<?h1 How it Works h1?>
<p>First, you start up the <?memd?> daemon on as many spare machines as you have. The daemon has no configuration file, just a few command line options, only 3 or 4 of which you'll likely use:
<pre class='example'># ./memcached -d -m 2048 -l 10.0.0.40 -p 11211</pre>
<p>This starts <?memd?> up as a daemon, using 2GB of memory, and listening on IP 10.0.0.40, port 11211. Because a 32-bit process can only address 4GB of virtual memory (usually significantly less, depending on your operating system), if you have a 32-bit server with 4-64GB of memory using PAE you can just run multiple processes on the machine, each using 2 or 3GB of memory.</p>
<?h1 Porting the Application h1?>
<p>Now, in your application, wherever you go to do a database query, first check the memcache. If the memcache returns an undefined object, then go to the database, get what you're looking for, and put it in the memcache:</p>
<pre class='example'>
<div class='exampletitle'>Perl Example (see <a href="apis.bml">APIs page</a>)</div>
sub get_foo_object {
my $foo_id = int(shift);
my $obj = $::MemCache->get("foo:$foo_id");
return $obj if $obj;
$obj = $::db->selectrow_hashref("SELECT .... FROM foo f, bar b ".
"WHERE ... AND f.fooid=$foo_id");
$::MemCache->set("foo:$foo_id", $obj);
return $obj;
}</pre>
<p>(If your internal API was already clean enough, you should only have to do this in a few spots. Start with the queries that kill your database the most, then move to doing as much as possible.)</p>
<p>You'll notice the data structure the server provides is just a dictionary. You assign values to keys, and you request values from keys.</p>
<p>Now, what actually happens is that the API hashes your key to a unique server. (You define all the available servers and their weightings when initializing the API) Alternatively, the APIs also let you provide your own hash value. A good hash value for user-related data is the user's ID number. Then, the API maps that hash value onto a server (modulus number of server buckets, one bucket for each server IP/port, but some can be weighted heigher if they have more memory available).</p>
<p>If a host goes down, the API re-maps that dead host's requests onto the servers that are available.</p>
<?h1 Shouldn't the database do this? h1?>
<p>Regardless of what database you use (MS-SQL, Oracle, Postgres, MysQL-InnoDB, etc..), there's a lot of overhead in implementing <a href="http://www.wikipedia.org/wiki/ACID">ACID</a> properties in a RDBMS, especially when disks are involved, which means queries are going to block. For databases that aren't ACID-compliant (like MySQL-MyISAM), that overhead doesn't exist, but reading threads block on the writing threads.</p>
<p><?memd?> never blocks. See the "Is memcached fast?" question below.</p>
<?h1 What about shared memory? h1?>
<p>The first thing people generally do is cache things within their
web processes. But this means your cache is duplicated multiple
times, once for each mod_perl/PHP/etc thread. This is a waste of
memory and you'll get low cache hit rates. If you're using a
multi-threaded language or a shared memory API (IPC::Shareable, etc),
you can have a global cache for all threads, but it's per-machine. It doesn't scale to multiple machines.
Once you have 20 webservers, those 20 independent caches start to look
just as silly as when you had 20 threads with their own caches on a
single box. (plus, shared memory is typically laden with limitations)</p>
<p>The <?memd?> server and clients work together to implement one
global cache across as many machines as you have. In fact, it's
recommended you run both web nodes (which are typically memory-lite
and CPU-hungry) and memcached processes (which are memory-hungry and
CPU-lite) on the same machines. This way you'll save network
ports.</p>
<?h1 What about MySQL 4.x query caching? h1?>
<p>MySQL query caching is less than ideal, for a number of reasons:</p>
<ul class='spaced'>
<li>MySQL's query cache destroys the entire cache for a given table whenever that table is changed. On a high-traffic site with updates happening many times per second, this makes the the cache practically worthless. In fact, it's often harmful to have it on, since there's a overhead to maintain the cache.</li>
<li>On 32-bit architectures, the entire server (including the query cache) is limited to a 4 GB virtual address space. <?memd?> lets you run as many processes as you want, so you have no limit on memory cache size.</li>
<li>MySQL has a query cache, not an object cache. If your objects require extra expensive construction after the data retrieval step, MySQL's query cache can't help you there.</li>
</ul>
<p>If the data you need to cache is small and you do infrequent updates, MySQL's query caching should work for you. If not, use <?memd?>.</p>
<?h1 What about database replication? h1?>
<p>You can spread your reads with replication, and that helps a lot,
but you can't spread writes (they have to process on all machines) and
they'll eventually consume all your resources. You'll find yourself
adding replicated slaves at an ever-increasing rate to make up for the
diminishing returns each addition slave provides.</p>
<p>The next logical step is to horizontally partition your dataset
onto different master/slave clusters so you can spread your writes,
and then teach your application to connect to the correct cluster
depending on the data it needs.</p>
<p>While this strategy works, and is recommended, more databases (each
with a bunch of disks) statistically leads to more frequent hardware
failures, which are annoying.</p>
<p>With <?memd?> you can reduce your database reads to a mere
fraction, leaving the databases to mainly do infrequent writes, and
end up getting much more bang for your buck, since your databases
won't be blocking themselves doing ACID bookkeeping or waiting on
writing threads.</p>
<?h1 Is <?memd?> fast? h1?> <p>Very fast. It uses <a
href="http://www.monkey.org/~provos/libevent/">libevent</a> to scale
to any number of open connections</a> (using <a
href="http://www.xmailserver.org/linux-patches/nio-improve.html">epoll</a>
on Linux, if available at runtime), uses non-blocking network I/O, refcounts internal objects
(so objects can be in multiple states to multiple clients), and uses
its own slab allocator and hash table so virtual memory never gets
externally fragmented and allocations are guaranteed O(1).</p>
<?h1 What about race conditions? h1?>
<p>You might wonder: <i>"What if the <tt>get_foo()</tt> function adds a stale version of the Foo object to the cache right as/after the user updates their Foo object via update_foo()?"</i></p>
<p>While the server and API only have one way to get data from the cache, there exists 3 ways to put data in:</p>
<ul class='spaced'>
<li><b>set</b> -- unconditionally sets a given key with a given value (<tt>update_foo()</tt> should use this)</li>
<li><b>add</b> -- adds to the cache, only if it doesn't already exist (<tt>get_foo()</tt> should use this)</li>
<li><b>replace</b> -- sets in the cache only if the key already exists (not as useful, only for completeness)</li>
</ul>
Additionally, all three support an expiration time.
<=body
page?>

View File

@@ -0,0 +1,59 @@
body {
font-family: sans-serif;
border: 5px solid #7070d0; margin: 0; padding: 30px;
background: white;
}
a { color: #5050d0; }
h1 {
color: #7070d0;
letter-spacing: 0.2em;
text-align: center;
width: auto;
font-size: 20pt;
margin-bottom: 0;
}
h2 {
font-size: 13pt;
font-style: italic;
margin: 1em 0 0.5em -0.7em;
border-bottom: 2px dotted blue;
background: #e0e0ff;
}
p {
margin-top: 0;
}
div.linkbar {
width: auto;
text-align: center;
}
div.pagetitle {
width: auto;
text-align: center;
font-weight: bold; font-size: 17pt;
margin-bottom: 10px;
margin-top: 10px;
}
pre.example {
margin-left: 40px;
background: #e0e0e0;
}
div.exampletitle {
font-family: sans-serif;
font-weight: bold;
background: #d0d0f0;
margin-bottom: 0;
}
ul.spaced li { margin-top: 0.7em; }
a.barlink:active { background: yellow; }
div.footer {
margin: 30px -30px -30px -30px;
padding: 5px;
font-size: smaller;
font-style: italic;
}
tt.mem { color: #000030; }

View File

@@ -0,0 +1,30 @@
page<=
{Fps}<html>
<head>
<title>memcached: %%wintitle%%</title>
<link rel='stylesheet' type='text/css' href='memcached.css' />
</head>
<body>
<h1>memcached</h1>
<div class='linkbar'>
<?link ./|About link?>
<?link news.bml|News link?>
<?link download.bml|Download link?>
<?link apis.bml|Client APIs link?>
<?link users.bml|Users link?>
<?link http://lists.danga.com/mailman/listinfo/memcached|Mailing List link?>
</div>
%%body%%
<div class='footer'>Brad Fitzpatrick &lt;brad@danga.com&gt;</div>
</body>
</html>
<=page
h1=>{D}<h2>%%data%%</h2>
memd=>{S}<tt class='mem'>memcached</tt>
link=>{RP}[<a href="%%data1%%" class='barlink'>%%data2%%</a>]

View File

@@ -0,0 +1,44 @@
<?page
wintitle=>news
body<=
<?h1 News h1?>
<p><b>2004-04-30:</b> Version 1.1.11 <a
href="download.bml">released</a>. New option (off by default) to not
act as a cache, but hold items forever until deleted (or until machine
goes down, rather). This release also installs the manual page, and
raises the per-process file descriptor limit to match the specified
max connections.
<p><b>2003-12-30:</b> Version 1.1.10 <a
href="download.bml">released</a>. Now includes a command to wipe
the entire cache.
<p><b>2003-10-09:</b> Version 1.1.9 <a
href="download.bml">released</a>, now with better network performance,
better portability, setuid support, and a fix for a rare crash
(SIGPIPE) if a client dies.
<p><b>2003-08-08:</b> We now have <a href="apis.bml">Python support</a>.</p>
<p><b>2003-07-29:</b> Version 1.1.8 <a href="download.bml">released</a>.
The client APIs are now distributed separately.</p>
<p><b>2003-06-30:</b> Version 1.1.6 <a
href="download.bml">released</a>. Many great changes.</p>
<p><b>2003-06-16:</b> <a href="apis.bml">PHP support</a> now included.</p>
<p><b>2003-06-15:</b> Autoconf support added in <a
href="download.bml">1.0.2 release</a>. Website text updated a bit.</p>
<p><b>2003-06-10:</b> Website not so bad now. Files licensed. Perl
API separated and documented. <a href="download.bml">1.0 release</a>.</p>
<p><b>2003-05-30:</b> Server's stable. Perl API's still LJ-specific
(just need to just change the package name, basically). Other APIs
should be easy to bang out. This website needs work.</p>
<=body
page?>

View File

@@ -0,0 +1,52 @@
<?page
wintitle=>users
body<=
<?h1 Who's using <?memd?>? h1?>
<p>This is an initial list of <?memd?> users that I've heard about. Please mail me if you're using it, optionally with a little description of how, and I'll add you to this page.</p>
<ul>
<li><b><a href="http://www.livejournal.com/">LiveJournal</a></b> -- fully dynamic blogging site with insane number of unnecessary features, doing over 20 million hits per day. We made <?memd?> for LiveJournal and we hardly ever hit the databases anymore. A few APIs in our codebase still unconditionally hit our databases, but they're being rewritten to be <?memd?>-aware. <?memd?> made a night-and-day difference in the speed of our site.</li>
<li><b><a href="http://www.slashdot.org/">Slashdot</a></b> -- I showed Jamie McCarthy <?memd?> at OSCON 2003 and how we use it on LiveJournal (including our <?memd?>-farm stats page) and he started frothing at the mouth and began implementing it that night in his hotel room. Now Slashdot uses it for caching comments in their rendered form, saving both DB load and web CPU load. They're reportedly working on using <?memd?> in more parts of their code.</li>
<li><b><a href="http://www.wikipedia.org/">WikiPedia</a></b> -- Brion Vibber added support to WikiPedia's MediaWiki backend. ( <a href="http://mail.wikipedia.org/pipermail/wikitech-l/2003-August/005514.html">original announcement</a>).</li>
<li><b><a href="http://vampirefreaks.com">VampireFreaks</a></b>:
<i>"Hey man. I just wanted to thank you for memcached, I just started
using it on http://vampirefreaks.com , a site which gets over a
million page hits a day and has been really slowing down the server.
I've already implemented memcached in a few key spots which determine
the number of users online as well as the number of current users, and
it seems to have helped a lot, I am sure I will be putting it into
more parts of the code as well. Feel free to put us on the memcached
users page if you like."</i></li>
<li><b><a href="http://sourceforge.net">SourceForge</a></b></li>
<li><b><a href="http://www.revelex.com/">Revelex</a></b>: <i>"... We have tried using MySQL, NFS-mounted flat-files and even NFS-mounted RAM drives to no avail. To date, only memcached has been able to keep up with our needs. ..."</i></li>
<li><b><a href="http://www.howardstern.com/">HowardStern.com</a></b>: <i>"We've been struggling to keep the hardware matched to the traffic
level and ever-growing database size. I've now implemented memcached
across major sections of the site and the vBulletin-based forum. We're
using three memcached servers to support the one large mySQL server.
The performance improvement has been tremendous and it allows me to
have an elegant memory caching solution always available instead of
my own cache on the webservers' filesystems, as I had been doing."</i></li>
</ul>
<?h1 Coming soon... h1?>
<p>These people are (or reportedly are, or were) working on memcache support to speed up their sites.</p>
<ul>
<li><b><a href="http://www.everything2.com/">Everything2</a></b> -- <a href="http://lists.danga.com/pipermail/memcached/2003-August/000044.html">adding support</a> for <?memd?> to the ecore nodecache over at <a href="http://www.everydevel.com">everydevel.com</a>.</li>
<li><b><a href="http://www.sourceforge.net/">SourceForge</a></b> -- adding support, which is why the Python API for <?memd?> was created.</li>
</ul>
<=body
page?>