提交 f78fd11b 编写于 作者: A antirez

New file dump format, perl client library added

上级 7b45bfb2
......@@ -7,3 +7,6 @@
- maxclients directive
- check 'server.dirty' everywere
- replication automated tests
- a command, or an external tool, to perform the MD5SUM of the whole dataset, so that if the dataset between two servers is identical, so will be the MD5SUM
* Include Lua and Perl bindings
......@@ -9,9 +9,9 @@ code or recent bugfixes read more.
How to get the lastest versions of client libraries source code
---------------------------------------------------------------
Note that while the PHP and Python versions are the most uptodate available
libraries, the Ruby and Erlang libraries have their own sites so you may want
to grab this libraries from their main sites:
Note that while the pure PHP, Tcl and Python libraries are the most uptodate
available libraries, all the other libraries have their own repositories where
it's possible to grab the most recent version:
Ruby lib source code:
http://github.com/ezmobius/redis-rb/tree/master
......@@ -19,10 +19,11 @@ http://github.com/ezmobius/redis-rb/tree/master
Erlang lib source code:
http://bitbucket.org/adroll/erldis/
For the languages with development code in the Redis SVN, check this urls for unstable versions of the libs:
Perl lib source code:
(web) http://svn.rot13.org/index.cgi/Redis
(svn) svn://svn.rot13.org/Redis/
Python lib source code:
http://code.google.com/p/redis/source/browse/#svn/trunk/client-libraries/python
Redis-php PHP C module:
http://code.google.com/p/phpredis/
PHP lib source code:
http://code.google.com/p/redis/source/browse/#svn/trunk/client-libraries/php
For all the rest check the Redis tarball or Git repository.
Revision history for Redis
0.01 Sun Mar 22 19:02:17 CET 2009
First version, tracking git://github.com/antirez/redis
0.08 Tue Mar 24 22:38:59 CET 2009
This version supports new protocol introduced in beta 8
Version bump to be in-sync with Redis version
Changes
MANIFEST
Makefile.PL
README
lib/Redis.pm
t/00-load.t
t/pod-coverage.t
t/pod.t
use strict;
use warnings;
use ExtUtils::MakeMaker;
WriteMakefile(
NAME => 'Redis',
AUTHOR => 'Dobrica Pavlinusic <dpavlin@rot13.org>',
VERSION_FROM => 'lib/Redis.pm',
ABSTRACT_FROM => 'lib/Redis.pm',
PL_FILES => {},
PREREQ_PM => {
'Test::More' => 0,
'IO::Socket::INET' => 0,
'Data::Dump' => 0,
'Carp' => 0,
},
dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', },
clean => { FILES => 'Redis-*' },
);
Redis
Perl binding for Redis database which is in-memory hash store with
support for scalars, arrays and sets and disk persistence.
INSTALLATION
To install this module, run the following commands:
perl Makefile.PL
make
make test
make install
SUPPORT AND DOCUMENTATION
After installing, you can find documentation for this module with the
perldoc command.
perldoc Redis
You can also look for information at:
RT, CPAN's request tracker
http://rt.cpan.org/NoAuth/Bugs.html?Dist=Redis
AnnoCPAN, Annotated CPAN documentation
http://annocpan.org/dist/Redis
CPAN Ratings
http://cpanratings.perl.org/d/Redis
Search CPAN
http://search.cpan.org/dist/Redis
COPYRIGHT AND LICENCE
Copyright (C) 2009 Dobrica Pavlinusic
This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.
package Redis;
use warnings;
use strict;
use IO::Socket::INET;
use Data::Dump qw/dump/;
use Carp qw/confess/;
=head1 NAME
Redis - perl binding for Redis database
=cut
our $VERSION = '0.08';
=head1 DESCRIPTION
Pure perl bindings for L<http://code.google.com/p/redis/>
This version support git version 0.08 of Redis available at
L<git://github.com/antirez/redis>
This documentation
lists commands which are exercised in test suite, but
additinal commands will work correctly since protocol
specifies enough information to support almost all commands
with same peace of code with a little help of C<AUTOLOAD>.
=head1 FUNCTIONS
=head2 new
my $r = Redis->new;
=cut
our $debug = $ENV{REDIS} || 0;
our $sock;
my $server = '127.0.0.1:6379';
sub new {
my $class = shift;
my $self = {};
bless($self, $class);
warn "# opening socket to $server";
$sock ||= IO::Socket::INET->new(
PeerAddr => $server,
Proto => 'tcp',
) || die $!;
$self;
}
my $bulk_command = {
set => 1, setnx => 1,
rpush => 1, lpush => 1,
lset => 1, lrem => 1,
sadd => 1, srem => 1,
sismember => 1,
echo => 1,
};
# we don't want DESTROY to fallback into AUTOLOAD
sub DESTROY {}
our $AUTOLOAD;
sub AUTOLOAD {
my $self = shift;
my $command = $AUTOLOAD;
$command =~ s/.*://;
warn "## $command ",dump(@_) if $debug;
my $send;
if ( defined $bulk_command->{$command} ) {
my $value = pop;
$value = '' if ! defined $value;
$send
= uc($command)
. ' '
. join(' ', @_)
. ' '
. length( $value )
. "\r\n$value\r\n"
;
} else {
$send
= uc($command)
. ' '
. join(' ', @_)
. "\r\n"
;
}
warn ">> $send" if $debug;
print $sock $send;
if ( $command eq 'quit' ) {
close( $sock ) || die "can't close socket: $!";
return 1;
}
my $result = <$sock> || die "can't read socket: $!";
warn "<< $result" if $debug;
my $type = substr($result,0,1);
$result = substr($result,1,-2);
if ( $command eq 'info' ) {
my $hash;
foreach my $l ( split(/\r\n/, __sock_read_bulk($result) ) ) {
my ($n,$v) = split(/:/, $l, 2);
$hash->{$n} = $v;
}
return $hash;
} elsif ( $command eq 'keys' ) {
my $keys = __sock_read_bulk($result);
return split(/\s/, $keys) if $keys;
return;
}
if ( $type eq '-' ) {
confess $result;
} elsif ( $type eq '+' ) {
return $result;
} elsif ( $type eq '$' ) {
return __sock_read_bulk($result);
} elsif ( $type eq '*' ) {
return __sock_read_multi_bulk($result);
} elsif ( $type eq ':' ) {
return $result; # FIXME check if int?
} else {
confess "unknown type: $type", __sock_read_line();
}
}
sub __sock_read_bulk {
my $len = shift;
return undef if $len < 0;
my $v;
if ( $len > 0 ) {
read($sock, $v, $len) || die $!;
warn "<< ",dump($v),$/ if $debug;
}
my $crlf;
read($sock, $crlf, 2); # skip cr/lf
return $v;
}
sub __sock_read_multi_bulk {
my $size = shift;
return undef if $size < 0;
$size--;
my @list = ( 0 .. $size );
foreach ( 0 .. $size ) {
$list[ $_ ] = __sock_read_bulk( substr(<$sock>,1,-2) );
}
warn "## list = ", dump( @list ) if $debug;
return @list;
}
1;
__END__
=head1 Connection Handling
=head2 quit
$r->quit;
=head2 ping
$r->ping || die "no server?";
=head1 Commands operating on string values
=head2 set
$r->set( foo => 'bar' );
$r->setnx( foo => 42 );
=head2 get
my $value = $r->get( 'foo' );
=head2 mget
my @values = $r->mget( 'foo', 'bar', 'baz' );
=head2 incr
$r->incr('counter');
$r->incrby('tripplets', 3);
=head2 decr
$r->decr('counter');
$r->decrby('tripplets', 3);
=head2 exists
$r->exists( 'key' ) && print "got key!";
=head2 del
$r->del( 'key' ) || warn "key doesn't exist";
=head2 type
$r->type( 'key' ); # = string
=head1 Commands operating on the key space
=head2 keys
my @keys = $r->keys( '*glob_pattern*' );
=head2 randomkey
my $key = $r->randomkey;
=head2 rename
my $ok = $r->rename( 'old-key', 'new-key', $new );
=head2 dbsize
my $nr_keys = $r->dbsize;
=head1 Commands operating on lists
See also L<Redis::List> for tie interface.
=head2 rpush
$r->rpush( $key, $value );
=head2 lpush
$r->lpush( $key, $value );
=head2 llen
$r->llen( $key );
=head2 lrange
my @list = $r->lrange( $key, $start, $end );
=head2 ltrim
my $ok = $r->ltrim( $key, $start, $end );
=head2 lindex
$r->lindex( $key, $index );
=head2 lset
$r->lset( $key, $index, $value );
=head2 lrem
my $modified_count = $r->lrem( $key, $count, $value );
=head2 lpop
my $value = $r->lpop( $key );
=head2 rpop
my $value = $r->rpop( $key );
=head1 Commands operating on sets
=head2 sadd
$r->sadd( $key, $member );
=head2 srem
$r->srem( $key, $member );
=head2 scard
my $elements = $r->scard( $key );
=head2 sismember
$r->sismember( $key, $member );
=head2 sinter
$r->sinter( $key1, $key2, ... );
=head2 sinterstore
my $ok = $r->sinterstore( $dstkey, $key1, $key2, ... );
=head1 Multiple databases handling commands
=head2 select
$r->select( $dbindex ); # 0 for new clients
=head2 move
$r->move( $key, $dbindex );
=head2 flushdb
$r->flushdb;
=head2 flushall
$r->flushall;
=head1 Sorting
=head2 sort
$r->sort("key BY pattern LIMIT start end GET pattern ASC|DESC ALPHA');
=head1 Persistence control commands
=head2 save
$r->save;
=head2 bgsave
$r->bgsave;
=head2 lastsave
$r->lastsave;
=head2 shutdown
$r->shutdown;
=head1 Remote server control commands
=head2 info
my $info_hash = $r->info;
=head1 AUTHOR
Dobrica Pavlinusic, C<< <dpavlin at rot13.org> >>
=head1 BUGS
Please report any bugs or feature requests to C<bug-redis at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Redis>. I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.
=head1 SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Redis
perldoc Redis::List
perldoc Redis::Hash
You can also look for information at:
=over 4
=item * RT: CPAN's request tracker
L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Redis>
=item * AnnoCPAN: Annotated CPAN documentation
L<http://annocpan.org/dist/Redis>
=item * CPAN Ratings
L<http://cpanratings.perl.org/d/Redis>
=item * Search CPAN
L<http://search.cpan.org/dist/Redis>
=back
=head1 ACKNOWLEDGEMENTS
=head1 COPYRIGHT & LICENSE
Copyright 2009 Dobrica Pavlinusic, all rights reserved.
This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.
=cut
1; # End of Redis
package Redis::Hash;
use strict;
use warnings;
use Tie::Hash;
use base qw/Redis Tie::StdHash/;
use Data::Dump qw/dump/;
=head1 NAME
Redis::Hash - tie perl hashes into Redis
=head1 SYNOPSYS
tie %name, 'Redis::Hash', 'prefix';
=cut
# mandatory methods
sub TIEHASH {
my ($class,$name) = @_;
my $self = Redis->new;
$name .= ':' if $name;
$self->{name} = $name || '';
bless $self => $class;
}
sub STORE {
my ($self,$key,$value) = @_;
$self->set( $self->{name} . $key, $value );
}
sub FETCH {
my ($self,$key) = @_;
$self->get( $self->{name} . $key );
}
sub FIRSTKEY {
my $self = shift;
$self->{keys} = [ $self->keys( $self->{name} . '*' ) ];
$self->NEXTKEY;
}
sub NEXTKEY {
my $self = shift;
my $key = shift @{ $self->{keys} } || return;
my $name = $self->{name};
$key =~ s{^$name}{} || warn "can't strip $name from $key";
return $key;
}
sub EXISTS {
my ($self,$key) = @_;
$self->exists( $self->{name} . $key );
}
sub DELETE {
my ($self,$key) = @_;
$self->del( $self->{name} . $key );
}
sub CLEAR {
my ($self) = @_;
$self->del( $_ ) foreach ( $self->keys( $self->{name} . '*' ) );
$self->{keys} = [];
}
1;
package Redis::List;
use strict;
use warnings;
use base qw/Redis Tie::Array/;
=head1 NAME
Redis::List - tie perl arrays into Redis lists
=head1 SYNOPSYS
tie @a, 'Redis::List', 'name';
=cut
# mandatory methods
sub TIEARRAY {
my ($class,$name) = @_;
my $self = $class->new;
$self->{name} = $name;
bless $self => $class;
}
sub FETCH {
my ($self,$index) = @_;
$self->lindex( $self->{name}, $index );
}
sub FETCHSIZE {
my ($self) = @_;
$self->llen( $self->{name} );
}
sub STORE {
my ($self,$index,$value) = @_;
$self->lset( $self->{name}, $index, $value );
}
sub STORESIZE {
my ($self,$count) = @_;
$self->ltrim( $self->{name}, 0, $count );
# if $count > $self->FETCHSIZE;
}
sub CLEAR {
my ($self) = @_;
$self->del( $self->{name} );
}
sub PUSH {
my $self = shift;
$self->rpush( $self->{name}, $_ ) foreach @_;
}
sub SHIFT {
my $self = shift;
$self->lpop( $self->{name} );
}
sub UNSHIFT {
my $self = shift;
$self->lpush( $self->{name}, $_ ) foreach @_;
}
sub SPLICE {
my $self = shift;
my $offset = shift;
my $length = shift;
$self->lrange( $self->{name}, $offset, $length );
# FIXME rest of @_ ?
}
sub EXTEND {
my ($self,$count) = @_;
$self->rpush( $self->{name}, '' ) foreach ( $self->FETCHSIZE .. ( $count - 1 ) );
}
sub DESTROY {
my $self = shift;
$self->quit;
}
1;
#!/usr/bin/perl
use warnings;
use strict;
use Benchmark qw/:all/;
use lib 'lib';
use Redis;
my $r = Redis->new;
my $i = 0;
timethese( 100000, {
'00_ping' => sub { $r->ping },
'10_set' => sub { $r->set( 'foo', $i++ ) },
'11_set_r' => sub { $r->set( 'bench-' . rand(), rand() ) },
'20_get' => sub { $r->get( 'foo' ) },
'21_get_r' => sub { $r->get( 'bench-' . rand() ) },
'30_incr' => sub { $r->incr( 'counter' ) },
'30_incr_r' => sub { $r->incr( 'bench-' . rand() ) },
'40_lpush' => sub { $r->lpush( 'mylist', 'bar' ) },
'40_lpush' => sub { $r->lpush( 'mylist', 'bar' ) },
'50_lpop' => sub { $r->lpop( 'mylist' ) },
});
#!perl -T
use Test::More tests => 1;
BEGIN {
use_ok( 'Redis' );
}
diag( "Testing Redis $Redis::VERSION, Perl $], $^X" );
#!/usr/bin/perl
use warnings;
use strict;
use Test::More tests => 106;
use Data::Dump qw/dump/;
use lib 'lib';
BEGIN {
use_ok( 'Redis' );
}
ok( my $o = Redis->new(), 'new' );
ok( $o->ping, 'ping' );
diag "Commands operating on string values";
ok( $o->set( foo => 'bar' ), 'set foo => bar' );
ok( ! $o->setnx( foo => 'bar' ), 'setnx foo => bar fails' );
cmp_ok( $o->get( 'foo' ), 'eq', 'bar', 'get foo = bar' );
ok( $o->set( foo => 'baz' ), 'set foo => baz' );
cmp_ok( $o->get( 'foo' ), 'eq', 'baz', 'get foo = baz' );
ok( $o->set( 'test-undef' => 42 ), 'set test-undef' );
ok( $o->set( 'test-undef' => undef ), 'set undef' );
ok( ! defined $o->get( 'test-undef' ), 'get undef' );
ok( $o->exists( 'test-undef' ), 'exists undef' );
$o->del('non-existant');
ok( ! $o->exists( 'non-existant' ), 'exists non-existant' );
ok( ! $o->get( 'non-existant' ), 'get non-existant' );
ok( $o->set('key-next' => 0), 'key-next = 0' );
my $key_next = 3;
ok( $o->set('key-left' => $key_next), 'key-left' );
is_deeply( [ $o->mget( 'foo', 'key-next', 'key-left' ) ], [ 'baz', 0, 3 ], 'mget' );
my @keys;
foreach my $id ( 0 .. $key_next ) {
my $key = 'key-' . $id;
push @keys, $key;
ok( $o->set( $key => $id ), "set $key" );
ok( $o->exists( $key ), "exists $key" );
cmp_ok( $o->get( $key ), 'eq', $id, "get $key" );
cmp_ok( $o->incr( 'key-next' ), '==', $id + 1, 'incr' );
cmp_ok( $o->decr( 'key-left' ), '==', $key_next - $id - 1, 'decr' );
}
cmp_ok( $o->get( 'key-next' ), '==', $key_next + 1, 'key-next' );
ok( $o->set('test-incrby', 0), 'test-incrby' );
ok( $o->set('test-decrby', 0), 'test-decry' );
foreach ( 1 .. 3 ) {
cmp_ok( $o->incrby('test-incrby', 3), '==', $_ * 3, 'incrby 3' );
cmp_ok( $o->decrby('test-decrby', 7), '==', -( $_ * 7 ), 'decrby 7' );
}
ok( $o->del( $_ ), "del $_" ) foreach map { "key-$_" } ( 'next', 'left' );
ok( ! $o->del('non-existing' ), 'del non-existing' );
cmp_ok( $o->type('foo'), 'eq', 'string', 'type' );
cmp_ok( $o->keys('key-*'), '==', $key_next + 1, 'key-*' );
is_deeply( [ $o->keys('key-*') ], [ @keys ], 'keys' );
ok( my $key = $o->randomkey, 'randomkey' );
ok( $o->rename( 'test-incrby', 'test-renamed' ), 'rename' );
ok( $o->exists( 'test-renamed' ), 'exists test-renamed' );
eval { $o->rename( 'test-decrby', 'test-renamed', 1 ) };
ok( $@, 'rename to existing key' );
ok( my $nr_keys = $o->dbsize, 'dbsize' );
diag "Commands operating on lists";
my $list = 'test-list';
$o->del($list) && diag "cleanup $list from last run";
ok( $o->rpush( $list => "r$_" ), 'rpush' ) foreach ( 1 .. 3 );
ok( $o->lpush( $list => "l$_" ), 'lpush' ) foreach ( 1 .. 2 );
cmp_ok( $o->type($list), 'eq', 'list', 'type' );
cmp_ok( $o->llen($list), '==', 5, 'llen' );
is_deeply( [ $o->lrange( $list, 0, 1 ) ], [ 'l2', 'l1' ], 'lrange' );
ok( $o->ltrim( $list, 1, 2 ), 'ltrim' );
cmp_ok( $o->llen($list), '==', 2, 'llen after ltrim' );
cmp_ok( $o->lindex( $list, 0 ), 'eq', 'l1', 'lindex' );
cmp_ok( $o->lindex( $list, 1 ), 'eq', 'r1', 'lindex' );
ok( $o->lset( $list, 0, 'foo' ), 'lset' );
cmp_ok( $o->lindex( $list, 0 ), 'eq', 'foo', 'verified' );
ok( $o->lrem( $list, 1, 'foo' ), 'lrem' );
cmp_ok( $o->llen( $list ), '==', 1, 'llen after lrem' );
cmp_ok( $o->lpop( $list ), 'eq', 'r1', 'lpop' );
ok( ! $o->rpop( $list ), 'rpop' );
diag "Commands operating on sets";
my $set = 'test-set';
$o->del($set);
ok( $o->sadd( $set, 'foo' ), 'sadd' );
ok( ! $o->sadd( $set, 'foo' ), 'sadd' );
cmp_ok( $o->scard( $set ), '==', 1, 'scard' );
ok( $o->sismember( $set, 'foo' ), 'sismember' );
cmp_ok( $o->type( $set ), 'eq', 'set', 'type is set' );
ok( $o->srem( $set, 'foo' ), 'srem' );
ok( ! $o->srem( $set, 'foo' ), 'srem again' );
cmp_ok( $o->scard( $set ), '==', 0, 'scard' );
$o->sadd( 'test-set1', $_ ) foreach ( 'foo', 'bar', 'baz' );
$o->sadd( 'test-set2', $_ ) foreach ( 'foo', 'baz', 'xxx' );
my $inter = [ 'baz', 'foo' ];
is_deeply( [ $o->sinter( 'test-set1', 'test-set2' ) ], $inter, 'siter' );
ok( $o->sinterstore( 'test-set-inter', 'test-set1', 'test-set2' ), 'sinterstore' );
cmp_ok( $o->scard( 'test-set-inter' ), '==', $#$inter + 1, 'cardinality of intersection' );
diag "Multiple databases handling commands";
ok( $o->select( 1 ), 'select' );
ok( $o->select( 0 ), 'select' );
ok( $o->move( 'foo', 1 ), 'move' );
ok( ! $o->exists( 'foo' ), 'gone' );
ok( $o->select( 1 ), 'select' );
ok( $o->exists( 'foo' ), 'exists' );
ok( $o->flushdb, 'flushdb' );
cmp_ok( $o->dbsize, '==', 0, 'empty' );
diag "Sorting";
ok( $o->lpush( 'test-sort', $_ ), "put $_" ) foreach ( 1 .. 4 );
cmp_ok( $o->llen( 'test-sort' ), '==', 4, 'llen' );
is_deeply( [ $o->sort( 'test-sort' ) ], [ 1,2,3,4 ], 'sort' );
is_deeply( [ $o->sort( 'test-sort DESC' ) ], [ 4,3,2,1 ], 'sort DESC' );
diag "Persistence control commands";
ok( $o->save, 'save' );
ok( $o->bgsave, 'bgsave' );
ok( $o->lastsave, 'lastsave' );
#ok( $o->shutdown, 'shutdown' );
diag "shutdown not tested";
diag "Remote server control commands";
ok( my $info = $o->info, 'info' );
diag dump( $info );
diag "Connection handling";
ok( $o->quit, 'quit' );
#!/usr/bin/perl
use warnings;
use strict;
use Test::More tests => 8;
use lib 'lib';
use Data::Dump qw/dump/;
BEGIN {
use_ok( 'Redis::List' );
}
my @a;
ok( my $o = tie( @a, 'Redis::List', 'test-redis-list' ), 'tie' );
isa_ok( $o, 'Redis::List' );
$o->CLEAR;
ok( ! @a, 'empty list' );
ok( @a = ( 'foo', 'bar', 'baz' ), '=' );
is_deeply( [ @a ], [ 'foo', 'bar', 'baz' ] );
ok( push( @a, 'push' ), 'push' );
is_deeply( [ @a ], [ 'foo', 'bar', 'baz', 'push' ] );
#diag dump( @a );
#!/usr/bin/perl
use warnings;
use strict;
use Test::More tests => 7;
use lib 'lib';
use Data::Dump qw/dump/;
BEGIN {
use_ok( 'Redis::Hash' );
}
ok( my $o = tie( my %h, 'Redis::Hash', 'test-redis-hash' ), 'tie' );
isa_ok( $o, 'Redis::Hash' );
$o->CLEAR();
ok( ! keys %h, 'empty' );
ok( %h = ( 'foo' => 42, 'bar' => 1, 'baz' => 99 ), '=' );
is_deeply( [ sort keys %h ], [ 'bar', 'baz', 'foo' ], 'keys' );
is_deeply( \%h, { bar => 1, baz => 99, foo => 42, }, 'structure' );
#diag dump( \%h );
use strict;
use warnings;
use Test::More;
# Ensure a recent version of Test::Pod::Coverage
my $min_tpc = 1.08;
eval "use Test::Pod::Coverage $min_tpc";
plan skip_all => "Test::Pod::Coverage $min_tpc required for testing POD coverage"
if $@;
# Test::Pod::Coverage doesn't require a minimum Pod::Coverage version,
# but older versions don't recognize some common documentation styles
my $min_pc = 0.18;
eval "use Pod::Coverage $min_pc";
plan skip_all => "Pod::Coverage $min_pc required for testing POD coverage"
if $@;
all_pod_coverage_ok();
#!perl -T
use strict;
use warnings;
use Test::More;
# Ensure a recent version of Test::Pod
my $min_tp = 1.22;
eval "use Test::Pod $min_tp";
plan skip_all => "Test::Pod $min_tp required for testing POD" if $@;
all_pod_files_ok();
......@@ -46,6 +46,7 @@
#include <fcntl.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <limits.h>
#include "ae.h" /* Event driven programming library */
#include "sds.h" /* Dynamic safe strings */
......@@ -82,9 +83,28 @@
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_HASH 3
/* Object types only used for dumping to disk */
#define REDIS_SELECTDB 254
#define REDIS_EOF 255
/* Defines related to the dump file format. To store 32 bits lengths for short
* keys requires a lot of space, so we check the most significant 2 bits of
* the first byte to interpreter the length:
*
* 00|000000 => if the two MSB are 00 the len is the 6 bits of this byte
* 01|000000 00000000 => 01, the len is 14 byes, 6 bits + 8 bits of next byte
* 10|000000 [32 bit integer] => if it's 01, a full 32 bit len will follow
* 11|000000 [64 bit integer] => if it's 11, a full 64 bit len will follow
*
* 64 bit lengths are not used currently. Lenghts up to 63 are stored using
* a single byte, most DB keys, and may values, will fit inside. */
#define REDIS_RDB_6BITLEN 0
#define REDIS_RDB_14BITLEN 1
#define REDIS_RDB_32BITLEN 2
#define REDIS_RDB_64BITLEN 3
#define REDIS_RDB_LENERR UINT_MAX
/* Client flags */
#define REDIS_CLOSE 1 /* This client connection should be closed ASAP */
#define REDIS_SLAVE 2 /* This client is a slave server */
......@@ -230,11 +250,11 @@ static void freeSetObject(robj *o);
static void decrRefCount(void *o);
static robj *createObject(int type, void *ptr);
static void freeClient(redisClient *c);
static int loadDb(char *filename);
static int rdbLoad(char *filename);
static void addReply(redisClient *c, robj *obj);
static void addReplySds(redisClient *c, sds s);
static void incrRefCount(robj *o);
static int saveDbBackground(char *filename);
static int rdbSaveBackground(char *filename);
static robj *createStringObject(char *ptr, size_t len);
static void replicationFeedSlaves(list *slaves, struct redisCommand *cmd, int dictid, robj **argv, int argc);
static int syncWithMaster(void);
......@@ -641,7 +661,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
now-server.lastsave > sp->seconds) {
redisLog(REDIS_NOTICE,"%d changes in %d seconds. Saving...",
sp->changes, sp->seconds);
saveDbBackground(server.dbfilename);
rdbSaveBackground(server.dbfilename);
break;
}
}
......@@ -1394,12 +1414,37 @@ static void decrRefCount(void *obj) {
/*============================ DB saving/loading ============================ */
static int rdbSaveType(FILE *fp, unsigned char type) {
if (fwrite(&type,1,1,fp) == 0) return -1;
return 0;
}
static int rdbSaveLen(FILE *fp, uint32_t len) {
unsigned char buf[2];
if (len < (1<<6)) {
/* Save a 6 bit len */
buf[0] = (len&0xFF)|REDIS_RDB_6BITLEN;
if (fwrite(buf,1,1,fp) == 0) return -1;
} else if (len < (1<<14)) {
/* Save a 14 bit len */
buf[0] = ((len>>8)&0xFF)|REDIS_RDB_14BITLEN;
buf[1] = len&0xFF;
if (fwrite(buf,4,1,fp) == 0) return -1;
} else {
/* Save a 32 bit len */
buf[0] = REDIS_RDB_32BITLEN;
if (fwrite(buf,1,1,fp) == 0) return -1;
len = htonl(len);
if (fwrite(&len,4,1,fp) == 0) return -1;
}
return 0;
}
/* Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success */
static int saveDb(char *filename) {
static int rdbSave(char *filename) {
dictIterator *di = NULL;
dictEntry *de;
uint32_t len;
uint8_t type;
FILE *fp;
char tmpfile[256];
int j;
......@@ -1410,7 +1455,7 @@ static int saveDb(char *filename) {
redisLog(REDIS_WARNING, "Failed saving the DB: %s", strerror(errno));
return REDIS_ERR;
}
if (fwrite("REDIS0000",9,1,fp) == 0) goto werr;
if (fwrite("REDIS0001",9,1,fp) == 0) goto werr;
for (j = 0; j < server.dbnum; j++) {
dict *d = server.dict[j];
if (dictGetHashTableUsed(d) == 0) continue;
......@@ -1421,59 +1466,54 @@ static int saveDb(char *filename) {
}
/* Write the SELECT DB opcode */
type = REDIS_SELECTDB;
len = htonl(j);
if (fwrite(&type,1,1,fp) == 0) goto werr;
if (fwrite(&len,4,1,fp) == 0) goto werr;
if (rdbSaveType(fp,REDIS_SELECTDB) == -1) goto werr;
if (rdbSaveLen(fp,j) == -1) goto werr;
/* Iterate this DB writing every entry */
while((de = dictNext(di)) != NULL) {
robj *key = dictGetEntryKey(de);
robj *o = dictGetEntryVal(de);
type = o->type;
len = htonl(sdslen(key->ptr));
if (fwrite(&type,1,1,fp) == 0) goto werr;
if (fwrite(&len,4,1,fp) == 0) goto werr;
if (rdbSaveType(fp,o->type) == -1) goto werr;
if (rdbSaveLen(fp,sdslen(key->ptr)) == -1) goto werr;
if (fwrite(key->ptr,sdslen(key->ptr),1,fp) == 0) goto werr;
if (type == REDIS_STRING) {
if (o->type == REDIS_STRING) {
/* Save a string value */
sds sval = o->ptr;
len = htonl(sdslen(sval));
if (fwrite(&len,4,1,fp) == 0) goto werr;
if (rdbSaveLen(fp,sdslen(sval)) == -1) goto werr;
if (sdslen(sval) &&
fwrite(sval,sdslen(sval),1,fp) == 0) goto werr;
} else if (type == REDIS_LIST) {
} else if (o->type == REDIS_LIST) {
/* Save a list value */
list *list = o->ptr;
listNode *ln = list->head;
len = htonl(listLength(list));
if (fwrite(&len,4,1,fp) == 0) goto werr;
if (rdbSaveLen(fp,listLength(list)) == -1) goto werr;
while(ln) {
robj *eleobj = listNodeValue(ln);
len = htonl(sdslen(eleobj->ptr));
if (fwrite(&len,4,1,fp) == 0) goto werr;
if (sdslen(eleobj->ptr) && fwrite(eleobj->ptr,sdslen(eleobj->ptr),1,fp) == 0)
if (rdbSaveLen(fp,sdslen(eleobj->ptr)) == -1) goto werr;
if (sdslen(eleobj->ptr) &&
fwrite(eleobj->ptr,sdslen(eleobj->ptr),1,fp) == 0)
goto werr;
ln = ln->next;
}
} else if (type == REDIS_SET) {
} else if (o->type == REDIS_SET) {
/* Save a set value */
dict *set = o->ptr;
dictIterator *di = dictGetIterator(set);
dictEntry *de;
if (!set) oom("dictGetIteraotr");
len = htonl(dictGetHashTableUsed(set));
if (fwrite(&len,4,1,fp) == 0) goto werr;
if (rdbSaveLen(fp,dictGetHashTableUsed(set)) == -1) goto werr;
while((de = dictNext(di)) != NULL) {
robj *eleobj;
eleobj = dictGetEntryKey(de);
len = htonl(sdslen(eleobj->ptr));
if (fwrite(&len,4,1,fp) == 0) goto werr;
if (sdslen(eleobj->ptr) && fwrite(eleobj->ptr,sdslen(eleobj->ptr),1,fp) == 0)
if (rdbSaveLen(fp,sdslen(eleobj->ptr)) == -1) goto werr;
if (sdslen(eleobj->ptr) &&
fwrite(eleobj->ptr,sdslen(eleobj->ptr),1,fp) == 0)
goto werr;
}
dictReleaseIterator(di);
......@@ -1484,8 +1524,9 @@ static int saveDb(char *filename) {
dictReleaseIterator(di);
}
/* EOF opcode */
type = REDIS_EOF;
if (fwrite(&type,1,1,fp) == 0) goto werr;
if (rdbSaveType(fp,REDIS_EOF) == -1) goto werr;
/* Make sure data will not remain on the OS's output buffers */
fflush(fp);
fsync(fileno(fp));
fclose(fp);
......@@ -1510,14 +1551,14 @@ werr:
return REDIS_ERR;
}
static int saveDbBackground(char *filename) {
static int rdbSaveBackground(char *filename) {
pid_t childpid;
if (server.bgsaveinprogress) return REDIS_ERR;
if ((childpid = fork()) == 0) {
/* Child */
close(server.fd);
if (saveDb(filename) == REDIS_OK) {
if (rdbSave(filename) == REDIS_OK) {
exit(0);
} else {
exit(1);
......@@ -1531,90 +1572,109 @@ static int saveDbBackground(char *filename) {
return REDIS_OK; /* unreached */
}
static int loadType(FILE *fp) {
uint8_t type;
static int rdbLoadType(FILE *fp) {
unsigned char type;
if (fread(&type,1,1,fp) == 0) return -1;
return type;
}
static int loadDb(char *filename) {
static uint32_t rdbLoadLen(FILE *fp, int rdbver) {
unsigned char buf[2];
uint32_t len;
if (rdbver == 0) {
if (fread(&len,4,1,fp) == 0) return REDIS_RDB_LENERR;
return ntohl(len);
} else {
if (fread(buf,1,1,fp) == 0) return REDIS_RDB_LENERR;
if ((buf[0]&0xC0) == REDIS_RDB_6BITLEN) {
/* Read a 6 bit len */
return buf[0];
} else if ((buf[0]&0xC0) == REDIS_RDB_14BITLEN) {
/* Read a 14 bit len */
if (fread(buf+1,1,1,fp) == 0) return REDIS_RDB_LENERR;
return ((buf[0]&0x3F)<<8)|buf[1];
} else {
/* Read a 32 bit len */
if (fread(&len,4,1,fp) == 0) return REDIS_RDB_LENERR;
return ntohl(len);
}
}
return 0;
}
static robj *rdbLoadStringObject(FILE*fp,int rdbver) {
uint32_t len = rdbLoadLen(fp,rdbver);
sds val;
if (len == REDIS_RDB_LENERR) return NULL;
val = sdsnewlen(NULL,len);
if (len && fread(val,len,1,fp) == 0) {
sdsfree(val);
return NULL;
}
return createObject(REDIS_STRING,val);
}
static int rdbLoad(char *filename) {
FILE *fp;
char buf[REDIS_LOADBUF_LEN]; /* Try to use this buffer instead of */
char vbuf[REDIS_LOADBUF_LEN]; /* malloc() when the element is small */
char *key = NULL, *val = NULL;
uint32_t klen,vlen,dbid;
robj *keyobj = NULL;
uint32_t dbid;
int type;
int retval;
dict *d = server.dict[0];
char buf[1024];
int rdbver;
fp = fopen(filename,"r");
if (!fp) return REDIS_ERR;
if (fread(buf,9,1,fp) == 0) goto eoferr;
if (memcmp(buf,"REDIS0000",9) != 0) {
buf[9] = '\0';
if (memcmp(buf,"REDIS",5) != 0) {
fclose(fp);
redisLog(REDIS_WARNING,"Wrong signature trying to load DB from file");
return REDIS_ERR;
}
rdbver = atoi(buf+5);
if (rdbver > 1) {
fclose(fp);
redisLog(REDIS_WARNING,"Can't handle RDB format version %d",rdbver);
return REDIS_ERR;
}
while(1) {
robj *o;
/* Read type. */
if ((type = loadType(fp)) == -1) goto eoferr;
if ((type = rdbLoadType(fp)) == -1) goto eoferr;
if (type == REDIS_EOF) break;
/* Handle SELECT DB opcode as a special case */
if (type == REDIS_SELECTDB) {
if (fread(&dbid,4,1,fp) == 0) goto eoferr;
dbid = ntohl(dbid);
if ((dbid = rdbLoadLen(fp,rdbver)) == REDIS_RDB_LENERR) goto eoferr;
if (dbid >= (unsigned)server.dbnum) {
redisLog(REDIS_WARNING,"FATAL: Data file was created with a Redis server compiled to handle more than %d databases. Exiting\n", server.dbnum);
redisLog(REDIS_WARNING,"FATAL: Data file was created with a Redis server configured to handle more than %d databases. Exiting\n", server.dbnum);
exit(1);
}
d = server.dict[dbid];
continue;
}
/* Read key */
if (fread(&klen,4,1,fp) == 0) goto eoferr;
klen = ntohl(klen);
if (klen <= REDIS_LOADBUF_LEN) {
key = buf;
} else {
key = zmalloc(klen);
if (!key) oom("Loading DB from file");
}
if (fread(key,klen,1,fp) == 0) goto eoferr;
if ((keyobj = rdbLoadStringObject(fp,rdbver)) == NULL) goto eoferr;
if (type == REDIS_STRING) {
/* Read string value */
if (fread(&vlen,4,1,fp) == 0) goto eoferr;
vlen = ntohl(vlen);
if (vlen <= REDIS_LOADBUF_LEN) {
val = vbuf;
} else {
val = zmalloc(vlen);
if (!val) oom("Loading DB from file");
}
if (vlen && fread(val,vlen,1,fp) == 0) goto eoferr;
o = createObject(REDIS_STRING,sdsnewlen(val,vlen));
if ((o = rdbLoadStringObject(fp,rdbver)) == NULL) goto eoferr;
} else if (type == REDIS_LIST || type == REDIS_SET) {
/* Read list/set value */
uint32_t listlen;
if (fread(&listlen,4,1,fp) == 0) goto eoferr;
listlen = ntohl(listlen);
if ((listlen = rdbLoadLen(fp,rdbver)) == REDIS_RDB_LENERR)
goto eoferr;
o = (type == REDIS_LIST) ? createListObject() : createSetObject();
/* Load every single element of the list/set */
while(listlen--) {
robj *ele;
if (fread(&vlen,4,1,fp) == 0) goto eoferr;
vlen = ntohl(vlen);
if (vlen <= REDIS_LOADBUF_LEN) {
val = vbuf;
} else {
val = zmalloc(vlen);
if (!val) oom("Loading DB from file");
}
if (vlen && fread(val,vlen,1,fp) == 0) goto eoferr;
ele = createObject(REDIS_STRING,sdsnewlen(val,vlen));
if ((ele = rdbLoadStringObject(fp,rdbver)) == NULL) goto eoferr;
if (type == REDIS_LIST) {
if (!listAddNodeTail((list*)o->ptr,ele))
oom("listAddNodeTail");
......@@ -1622,30 +1682,23 @@ static int loadDb(char *filename) {
if (dictAdd((dict*)o->ptr,ele,NULL) == DICT_ERR)
oom("dictAdd");
}
/* free the temp buffer if needed */
if (val != vbuf) zfree(val);
val = NULL;
}
} else {
assert(0 != 0);
}
/* Add the new object in the hash table */
retval = dictAdd(d,createStringObject(key,klen),o);
retval = dictAdd(d,keyobj,o);
if (retval == DICT_ERR) {
redisLog(REDIS_WARNING,"Loading DB, duplicated key found! Unrecoverable error, exiting now.");
redisLog(REDIS_WARNING,"Loading DB, duplicated key (%s) found! Unrecoverable error, exiting now.", keyobj->ptr);
exit(1);
}
/* Iteration cleanup */
if (key != buf) zfree(key);
if (val != vbuf) zfree(val);
key = val = NULL;
keyobj = o = NULL;
}
fclose(fp);
return REDIS_OK;
eoferr: /* unexpected end of file is handled here with a fatal exit */
if (key != buf) zfree(key);
if (val != vbuf) zfree(val);
decrRefCount(keyobj);
redisLog(REDIS_WARNING,"Short read loading DB. Unrecoverable error, exiting now.");
exit(1);
return REDIS_ERR; /* Just to avoid warning */
......@@ -1894,7 +1947,7 @@ static void typeCommand(redisClient *c) {
}
static void saveCommand(redisClient *c) {
if (saveDb(server.dbfilename) == REDIS_OK) {
if (rdbSave(server.dbfilename) == REDIS_OK) {
addReply(c,shared.ok);
} else {
addReply(c,shared.err);
......@@ -1906,7 +1959,7 @@ static void bgsaveCommand(redisClient *c) {
addReplySds(c,sdsnew("-ERR background save already in progress\r\n"));
return;
}
if (saveDbBackground(server.dbfilename) == REDIS_OK) {
if (rdbSaveBackground(server.dbfilename) == REDIS_OK) {
addReply(c,shared.ok);
} else {
addReply(c,shared.err);
......@@ -1915,7 +1968,7 @@ static void bgsaveCommand(redisClient *c) {
static void shutdownCommand(redisClient *c) {
redisLog(REDIS_WARNING,"User requested shutdown, saving DB...");
if (saveDb(server.dbfilename) == REDIS_OK) {
if (rdbSave(server.dbfilename) == REDIS_OK) {
if (server.daemonize) {
unlink(server.pidfile);
}
......@@ -2508,13 +2561,13 @@ static void sinterstoreCommand(redisClient *c) {
static void flushdbCommand(redisClient *c) {
dictEmpty(c->dict);
addReply(c,shared.ok);
saveDb(server.dbfilename);
rdbSave(server.dbfilename);
}
static void flushallCommand(redisClient *c) {
emptyDb();
addReply(c,shared.ok);
saveDb(server.dbfilename);
rdbSave(server.dbfilename);
}
redisSortOperation *createSortOperation(int type, robj *pattern) {
......@@ -2923,7 +2976,8 @@ static void syncCommand(redisClient *c) {
if (c->flags & REDIS_SLAVE) return;
redisLog(REDIS_NOTICE,"Slave ask for syncronization");
if (flushClientOutput(c) == REDIS_ERR || saveDb(server.dbfilename) != REDIS_OK)
if (flushClientOutput(c) == REDIS_ERR ||
rdbSave(server.dbfilename) != REDIS_OK)
goto closeconn;
fd = open(server.dbfilename, O_RDONLY);
......@@ -3020,7 +3074,7 @@ static int syncWithMaster(void) {
return REDIS_ERR;
}
emptyDb();
if (loadDb(server.dbfilename) != REDIS_OK) {
if (rdbLoad(server.dbfilename) != REDIS_OK) {
redisLog(REDIS_WARNING,"Failed trying to load the MASTER synchronization DB from disk");
close(fd);
return REDIS_ERR;
......@@ -3079,7 +3133,7 @@ int main(int argc, char **argv) {
initServer();
if (server.daemonize) daemonize();
redisLog(REDIS_NOTICE,"Server started, Redis version " REDIS_VERSION);
if (loadDb(server.dbfilename) == REDIS_OK)
if (rdbLoad(server.dbfilename) == REDIS_OK)
redisLog(REDIS_NOTICE,"DB loaded from disk");
if (aeCreateFileEvent(server.el, server.fd, AE_READABLE,
acceptHandler, NULL, NULL) == AE_ERR) oom("creating file event");
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册