From db52edaa0d6bcb455e46703ed082f80157723178 Mon Sep 17 00:00:00 2001 From: Luc Heinrich Date: Mon, 23 Mar 2009 12:28:28 +0100 Subject: [PATCH 01/13] Added gitignore file. --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..973ee7e3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.o +*.rdb +redis-cli +redis-server +redis-benchmark From ed329fcf25bb1c41949678eec125034326f86af2 Mon Sep 17 00:00:00 2001 From: Luc Heinrich Date: Mon, 23 Mar 2009 12:31:33 +0100 Subject: [PATCH 02/13] Allow to specify the pid file from the config file. --- redis.c | 9 ++++++++- redis.conf | 4 ++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/redis.c b/redis.c index 04504eec..dae4e0af 100644 --- a/redis.c +++ b/redis.c @@ -171,6 +171,7 @@ struct redisServer { int maxidletime; int dbnum; int daemonize; + char *pidfile; int bgsaveinprogress; struct saveparam *saveparams; int saveparamslen; @@ -715,6 +716,7 @@ static void initServerConfig() { server.bindaddr = NULL; server.glueoutputbuf = 1; server.daemonize = 0; + server.pidfile = "/var/run/redis.pid"; server.dbfilename = "dump.rdb"; ResetServerSaveParams(); @@ -878,6 +880,8 @@ static void loadServerConfig(char *filename) { else { err = "argument must be 'yes' or 'no'"; goto loaderr; } + } else if (!strcmp(argv[0],"pidfile") && argc == 2) { + server.pidfile = zstrdup(argv[1]); } else { err = "Bad directive or wrong number of arguments"; goto loaderr; } @@ -1899,6 +1903,9 @@ 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 (server.daemonize) { + unlink(server.pidfile); + } redisLog(REDIS_WARNING,"Server exit now, bye bye..."); exit(1); } else { @@ -3033,7 +3040,7 @@ static void daemonize(void) { if (fd > STDERR_FILENO) close(fd); } /* Try to write the pid file */ - fp = fopen("/var/run/redis.pid","w"); + fp = fopen(server.pidfile,"w"); if (fp) { fprintf(fp,"%d\n",getpid()); fclose(fp); diff --git a/redis.conf b/redis.conf index 19a9c640..3727efd5 100644 --- a/redis.conf +++ b/redis.conf @@ -4,6 +4,10 @@ # Note that Redis will write a pid file in /var/run/redis.pid when daemonized. daemonize no +# When run as a daemon, Redis write a pid file in /var/run/redis.pid by default. +# You can specify a custom pid file location here. +pidfile /var/run/redis.pid + # Accept connections on the specified port, default is 6379 port 6379 From 46713f83d46babbc9ad48d11c52cd954a8553846 Mon Sep 17 00:00:00 2001 From: Luc Heinrich Date: Mon, 23 Mar 2009 12:42:10 +0100 Subject: [PATCH 03/13] Display the port on server startup. --- redis.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redis.c b/redis.c index dae4e0af..fa0d6158 100644 --- a/redis.c +++ b/redis.c @@ -3063,7 +3063,7 @@ int main(int argc, char **argv) { redisLog(REDIS_NOTICE,"DB loaded from disk"); if (aeCreateFileEvent(server.el, server.fd, AE_READABLE, acceptHandler, NULL, NULL) == AE_ERR) oom("creating file event"); - redisLog(REDIS_NOTICE,"The server is now ready to accept connections"); + redisLog(REDIS_NOTICE,"The server is now ready to accept connections on port %d", server.port); aeMain(server.el); aeDeleteEventLoop(server.el); return 0; From b91f03a4d14c166bb8ead41e5b556b1080ccd559 Mon Sep 17 00:00:00 2001 From: Luc Heinrich Date: Mon, 23 Mar 2009 12:43:16 +0100 Subject: [PATCH 04/13] Fixed redis-cli readLine loop to correctly handle EOF. When using the shutdown command with redis-cli the server saves the database and, if successful, silently closes the connection. The redis-cli tool did not correcty handle this EOF case in its readLine loop and was therefore infinitely looping - and eating 100% of the CPU - while waiting for some data which would never come. --- redis-cli.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/redis-cli.c b/redis-cli.c index 38c986b3..cd106684 100644 --- a/redis-cli.c +++ b/redis-cli.c @@ -135,11 +135,13 @@ static sds cliReadLine(int fd) { while(1) { char c; + ssize_t ret; - if (read(fd,&c,1) == -1) { + ret = read(fd,&c,1); + if (ret == -1) { sdsfree(line); return NULL; - } else if (c == '\n') { + } else if ((ret == 0) || (c == '\n')) { break; } else { line = sdscatlen(line,&c,1); From 092dac2a64f912fe67be2e924c58979b200aa64b Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 23 Mar 2009 14:50:09 +0100 Subject: [PATCH 05/13] another missing free->zfree replacement fixed. Thanks to Ludo --- Makefile | 2 +- TODO | 19 +++++++++++++++++++ redis.c | 2 +- zmalloc.h | 2 +- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index bf9760f0..e45899e9 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ anet.o: anet.c anet.h benchmark.o: benchmark.c ae.h anet.h sds.h adlist.h dict.o: dict.c dict.h redis-cli.o: redis-cli.c anet.h sds.h adlist.h -redis.o: redis.c ae.h sds.h anet.h dict.h adlist.h +redis.o: redis.c ae.h sds.h anet.h dict.h adlist.h zmalloc.c zmalloc.h sds.o: sds.c sds.h sha1.o: sha1.c sha1.h zmalloc.o: zmalloc.c diff --git a/TODO b/TODO index 02595425..7d91a103 100644 --- a/TODO +++ b/TODO @@ -15,3 +15,22 @@ BETA 8 TODO $ - Make Redis aware of the memory it is using thanks to getrusage() and report this info with the INFO command. - INFO command: clients, slave/master, requests/second in the last N seconds, memory usage, uptime, dirty, lastsave + +FUTURE + +ROLLBACK command: + + ROLLBACK UNSET x + SET x 10 + EXPIRE x 3600 + COMMIT + + (multiple rollbacks are allowed) + + or alternatively + + TRANSACTION SET x 1000 + TRANSACTION EXPIRE x 1000 + COMMIT + + but this sucks since there is no way to check the error message. diff --git a/redis.c b/redis.c index 04504eec..e6606f18 100644 --- a/redis.c +++ b/redis.c @@ -2619,7 +2619,7 @@ static void sortCommand(redisClient *c) { /* Create a list of operations to perform for every sorted element. * Operations can be GET/DEL/INCR/DECR */ operations = listCreate(); - listSetFreeMethod(operations,free); + listSetFreeMethod(operations,zfree); j = 2; /* Now we need to protect sortval incrementing its count, in the future diff --git a/zmalloc.h b/zmalloc.h index aaddb9ba..9e70e877 100644 --- a/zmalloc.h +++ b/zmalloc.h @@ -33,7 +33,7 @@ void *zmalloc(size_t size); void *zrealloc(void *ptr, size_t size); -void *zfree(void *ptr); +void zfree(void *ptr); char *zstrdup(const char *s); size_t zmalloc_used_memory(void); From 5a039e3b95856ac9e59ce4a522f140b4ec014913 Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 23 Mar 2009 17:22:24 +0100 Subject: [PATCH 06/13] lucsky changes imported. pid file path can now be configured, redis-cli fixes --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 973ee7e3..14a7720d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ redis-cli redis-server redis-benchmark +doc-tools From 87eca72788a18969f7e350fdbd75ced5a8b8e893 Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 23 Mar 2009 19:43:39 +0100 Subject: [PATCH 07/13] MONITOR command implemented. --- .gitignore | 1 + redis.c | 32 +++++++++++++++++++++++--------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 14a7720d..c9119a2f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ redis-cli redis-server redis-benchmark doc-tools +mkrelease.sh diff --git a/redis.c b/redis.c index 40b9264e..c7b10dfd 100644 --- a/redis.c +++ b/redis.c @@ -89,6 +89,7 @@ #define REDIS_CLOSE 1 /* This client connection should be closed ASAP */ #define REDIS_SLAVE 2 /* This client is a slave server */ #define REDIS_MASTER 4 /* This client is a master server */ +#define REDIS_MONITOR 8 /* This client is a slave monitor, see MONITOR */ /* Server replication state */ #define REDIS_REPL_NONE 0 /* No active replication */ @@ -138,7 +139,7 @@ typedef struct redisClient { list *reply; int sentlen; time_t lastinteraction; /* time of the last interaction, used for timeout */ - int flags; /* REDIS_CLOSE | REDIS_SLAVE */ + int flags; /* REDIS_CLOSE | REDIS_SLAVE | REDIS_MONITOR */ int slaveseldb; /* slave selected db, if this client is a slave */ } redisClient; @@ -154,7 +155,7 @@ struct redisServer { dict **dict; long long dirty; /* changes to DB from the last save */ list *clients; - list *slaves; + list *slaves, *monitors; char neterr[ANET_ERR_LEN]; aeEventLoop *el; int cronloops; /* number of times the cron function run */ @@ -235,7 +236,7 @@ static void addReplySds(redisClient *c, sds s); static void incrRefCount(robj *o); static int saveDbBackground(char *filename); static robj *createStringObject(char *ptr, size_t len); -static void replicationFeedSlaves(struct redisCommand *cmd, int dictid, robj **argv, int argc); +static void replicationFeedSlaves(list *slaves, struct redisCommand *cmd, int dictid, robj **argv, int argc); static int syncWithMaster(void); static void pingCommand(redisClient *c); @@ -283,6 +284,7 @@ static void sortCommand(redisClient *c); static void lremCommand(redisClient *c); static void infoCommand(redisClient *c); static void mgetCommand(redisClient *c); +static void monitorCommand(redisClient *c); /*================================= Globals ================================= */ @@ -335,6 +337,7 @@ static struct redisCommand cmdTable[] = { {"flushall",flushallCommand,1,REDIS_CMD_INLINE}, {"sort",sortCommand,-2,REDIS_CMD_INLINE}, {"info",infoCommand,1,REDIS_CMD_INLINE}, + {"monitor",monitorCommand,1,REDIS_CMD_INLINE}, {NULL,NULL,0,0} }; @@ -739,11 +742,12 @@ static void initServer() { server.clients = listCreate(); server.slaves = listCreate(); + server.monitors = listCreate(); server.objfreelist = listCreate(); createSharedObjects(); server.el = aeCreateEventLoop(); server.dict = zmalloc(sizeof(dict*)*server.dbnum); - if (!server.dict || !server.clients || !server.slaves || !server.el || !server.objfreelist) + if (!server.dict || !server.clients || !server.slaves || !server.monitors || !server.el || !server.objfreelist) oom("server initialization"); /* Fatal OOM */ server.fd = anetTcpServer(server.neterr, server.port, server.bindaddr); if (server.fd == -1) { @@ -922,9 +926,10 @@ static void freeClient(redisClient *c) { assert(ln != NULL); listDelNode(server.clients,ln); if (c->flags & REDIS_SLAVE) { - ln = listSearchKey(server.slaves,c); + list *l = (c->flags & REDIS_MONITOR) ? server.monitors : server.slaves; + ln = listSearchKey(l,c); assert(ln != NULL); - listDelNode(server.slaves,ln); + listDelNode(l,ln); } if (c->flags & REDIS_MASTER) { server.master = NULL; @@ -1083,7 +1088,9 @@ static int processCommand(redisClient *c) { dirty = server.dirty; cmd->proc(c); if (server.dirty-dirty != 0 && listLength(server.slaves)) - replicationFeedSlaves(cmd,c->dictid,c->argv,c->argc); + replicationFeedSlaves(server.slaves,cmd,c->dictid,c->argv,c->argc); + if (listLength(server.monitors)) + replicationFeedSlaves(server.monitors,cmd,c->dictid,c->argv,c->argc); server.stat_numcommands++; /* Prepare the client for the next command */ @@ -1095,8 +1102,8 @@ static int processCommand(redisClient *c) { return 1; } -static void replicationFeedSlaves(struct redisCommand *cmd, int dictid, robj **argv, int argc) { - listNode *ln = server.slaves->head; +static void replicationFeedSlaves(list *slaves, struct redisCommand *cmd, int dictid, robj **argv, int argc) { + listNode *ln = slaves->head; robj *outv[REDIS_MAX_ARGS*4]; /* enough room for args, spaces, newlines */ int outc = 0, j; @@ -3021,6 +3028,13 @@ static int syncWithMaster(void) { return REDIS_OK; } +static void monitorCommand(redisClient *c) { + c->flags |= (REDIS_SLAVE|REDIS_MONITOR); + c->slaveseldb = 0; + if (!listAddNodeTail(server.monitors,c)) oom("listAddNodeTail"); + addReply(c,shared.ok); +} + /* =================================== Main! ================================ */ static void daemonize(void) { From cf3f0c012df3abeee9bc2ec511820c1a859ecfc7 Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 23 Mar 2009 21:33:15 +0100 Subject: [PATCH 08/13] Now MONITOR/SYNC cannot be issued multiple times --- TODO | 2 ++ redis.c | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/TODO b/TODO index 7d91a103..9bf8644f 100644 --- a/TODO +++ b/TODO @@ -34,3 +34,5 @@ ROLLBACK command: COMMIT but this sucks since there is no way to check the error message. + +- Prevent the client to issue SYNC or MONITOR multiple times diff --git a/redis.c b/redis.c index c7b10dfd..0fc66795 100644 --- a/redis.c +++ b/redis.c @@ -2919,6 +2919,9 @@ static void syncCommand(redisClient *c) { time_t start = time(NULL); char sizebuf[32]; + /* ignore SYNC if aleady slave or in monitor mode */ + if (c->flags & REDIS_SLAVE) return; + redisLog(REDIS_NOTICE,"Slave ask for syncronization"); if (flushClientOutput(c) == REDIS_ERR || saveDb(server.dbfilename) != REDIS_OK) goto closeconn; @@ -3029,6 +3032,9 @@ static int syncWithMaster(void) { } static void monitorCommand(redisClient *c) { + /* ignore MONITOR if aleady slave or in monitor mode */ + if (c->flags & REDIS_SLAVE) return; + c->flags |= (REDIS_SLAVE|REDIS_MONITOR); c->slaveseldb = 0; if (!listAddNodeTail(server.monitors,c)) oom("listAddNodeTail"); From 5a6948fbc02568a26e21c0a69e5e921edef8c1b9 Mon Sep 17 00:00:00 2001 From: antirez Date: Tue, 24 Mar 2009 00:43:38 +0100 Subject: [PATCH 09/13] random tested mode for test-redis.tcl, minor other stuff, version switched to 0.8 --- .gitignore | 1 + TODO | 33 ++------------------------------- redis.c | 2 +- test-redis.tcl | 43 +++++++++++++++++++++++++++++++++++-------- 4 files changed, 39 insertions(+), 40 deletions(-) diff --git a/.gitignore b/.gitignore index c9119a2f..c8a56bc7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ redis-server redis-benchmark doc-tools mkrelease.sh +release diff --git a/TODO b/TODO index 9bf8644f..00885d17 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,4 @@ -BETA 8 TODO +- Protocol changes as discussed in the Redis group - keys expire - sunion ssub - write integers in a special way on disk (and on memory?) @@ -6,33 +6,4 @@ BETA 8 TODO - network layer stresser in test in demo - maxclients directive - check 'server.dirty' everywere -- replication tests -- command line client. If the last argument of a bulk command is missing get it from stdin. Example: - $ echo "bar" | redis-client SET foo - $ redis-client SET foo bar - $ redis-client GET foo - bar - $ -- Make Redis aware of the memory it is using thanks to getrusage() and report this info with the INFO command. -- INFO command: clients, slave/master, requests/second in the last N seconds, memory usage, uptime, dirty, lastsave - -FUTURE - -ROLLBACK command: - - ROLLBACK UNSET x - SET x 10 - EXPIRE x 3600 - COMMIT - - (multiple rollbacks are allowed) - - or alternatively - - TRANSACTION SET x 1000 - TRANSACTION EXPIRE x 1000 - COMMIT - - but this sucks since there is no way to check the error message. - -- Prevent the client to issue SYNC or MONITOR multiple times +- replication automated tests diff --git a/redis.c b/redis.c index 0fc66795..58b85606 100644 --- a/redis.c +++ b/redis.c @@ -27,7 +27,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#define REDIS_VERSION "0.07" +#define REDIS_VERSION "0.08" #include #include diff --git a/test-redis.tcl b/test-redis.tcl index 99b56463..9bfab204 100644 --- a/test-redis.tcl +++ b/test-redis.tcl @@ -784,37 +784,37 @@ proc redis_sismember {fd key val} { } proc redis_sinter {fd args} { - redis_writenl $fd "sinter [join $args]\r\n" + redis_writenl $fd "sinter [join $args]" redis_multi_bulk_read $fd } proc redis_sinterstore {fd args} { - redis_writenl $fd "sinterstore [join $args]\r\n" + redis_writenl $fd "sinterstore [join $args]" redis_read_retcode $fd } proc redis_smembers {fd key} { - redis_writenl $fd "smembers $key\r\n" + redis_writenl $fd "smembers $key" redis_multi_bulk_read $fd } proc redis_echo {fd str} { - redis_writenl $fd "echo [string length $str]\r\n$str\r\n" - redis_writenl $fd "smembers $key\r\n" + redis_writenl $fd "echo [string length $str]\r\n$str" + redis_writenl $fd "smembers $key" } proc redis_save {fd} { - redis_writenl $fd "save\r\n" + redis_writenl $fd "save" redis_read_retcode $fd } proc redis_flushall {fd} { - redis_writenl $fd "flushall\r\n" + redis_writenl $fd "flushall" redis_read_retcode $fd } proc redis_flushdb {fd} { - redis_writenl $fd "flushdb\r\n" + redis_writenl $fd "flushdb" redis_read_retcode $fd } @@ -823,8 +823,35 @@ proc redis_lrem {fd key count val} { redis_read_integer $fd } +proc stress {} { + set fd [socket 127.0.0.1 6379] + fconfigure $fd -translation binary + redis_flushall $fd + while 1 { + set randkey [expr int(rand()*10000)] + set randval [expr int(rand()*10000)] + set randidx0 [expr int(rand()*10)] + set randidx1 [expr int(rand()*10)] + set cmd [expr int(rand()*10)] + if {$cmd == 0} {redis_set $fd $randkey $randval} + if {$cmd == 1} {redis_get $fd $randkey} + if {$cmd == 2} {redis_incr $fd $randkey} + if {$cmd == 3} {redis_lpush $fd $randkey $randval} + if {$cmd == 4} {redis_rpop $fd $randkey} + if {$cmd == 5} {redis_del $fd $randkey} + if {$cmd == 6} {redis_lrange $fd $randkey $randidx0 $randidx1} + if {$cmd == 7} {redis_ltrim $fd $randkey $randidx0 $randidx1} + if {$cmd == 8} {redis_lindex $fd $randkey $randidx0} + if {$cmd == 9} {redis_lset $fd $randkey $randidx0 $randval} + flush stdout + } + close $fd +} + if {[llength $argv] == 0} { main 127.0.0.1 6379 +} elseif {[llength $argv] == 1 && [lindex $argv 0] eq {stress}} { + stress } else { main [lindex $argv 0] [lindex $argv 1] } From 796d05f855f00e0fda05129dd62c39e8670283e7 Mon Sep 17 00:00:00 2001 From: antirez Date: Tue, 24 Mar 2009 11:28:34 +0100 Subject: [PATCH 10/13] Python client library updated, thanks to Ludo! --- client-libraries/python/redis.py | 33 ++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/client-libraries/python/redis.py b/client-libraries/python/redis.py index e844f812..954c2769 100644 --- a/client-libraries/python/redis.py +++ b/client-libraries/python/redis.py @@ -140,6 +140,19 @@ class Redis(object): self._write('GET %s\r\n' % name) return self._get_value() + def mget(self, *args): + """ + >>> r = Redis() + >>> r.set('a', 'pippo'), r.set('b', 15), r.set('c', '\\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n'), r.set('d', '\\r\\n') + ('OK', 'OK', 'OK', 'OK') + >>> r.mget('a', 'b', 'c', 'd') + ['pippo', '15', '\\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n', '\\r\\n'] + >>> + """ + self.connect() + self._write('MGET %s\r\n' % ' '.join(args)) + return self._get_multi_response() + def incr(self, name, amount=1): """ >>> r = Redis() @@ -827,6 +840,26 @@ class Redis(object): self._write('%s\r\n' % ('FLUSHALL' if all_dbs else 'FLUSHDB')) return self._get_simple_response() + def info(self): + """ + >>> r = Redis() + >>> info = r.info() + >>> info and isinstance(info, dict) + True + >>> isinstance(info.get('connected_clients'), int) + True + >>> + """ + self.connect() + self._write('INFO\r\n') + info = dict() + for l in self._get_value().split('\r\n'): + if not l: + continue + k, v = l.split(':', 1) + info[k] = int(v) if v.isdigit() else v + return info + def _get_value(self, negative_as_nil=False): data = self._read().strip() if data == 'nil' or (negative_as_nil and data == '-1'): From c937aa89b5835d12a1b5900beaa471908f3a236d Mon Sep 17 00:00:00 2001 From: antirez Date: Tue, 24 Mar 2009 13:37:32 +0100 Subject: [PATCH 11/13] Server replies now in the new format, test-redis.tcl and redis-cli modified accordingly --- redis-cli.c | 165 +++++++++++++++++++++--------------------- redis.c | 193 ++++++++++++++++++++++++------------------------- test-redis.tcl | 149 +++++++++++++++++++------------------- 3 files changed, 252 insertions(+), 255 deletions(-) diff --git a/redis-cli.c b/redis-cli.c index cd106684..6ced5abc 100644 --- a/redis-cli.c +++ b/redis-cli.c @@ -40,11 +40,6 @@ #define REDIS_CMD_INLINE 1 #define REDIS_CMD_BULK 2 -#define REDIS_CMD_INTREPLY 4 -#define REDIS_CMD_RETCODEREPLY 8 -#define REDIS_CMD_BULKREPLY 16 -#define REDIS_CMD_MULTIBULKREPLY 32 -#define REDIS_CMD_SINGLELINEREPLY 64 #define REDIS_NOTUSED(V) ((void) V) @@ -60,54 +55,56 @@ struct redisCommand { }; static struct redisCommand cmdTable[] = { - {"get",2,REDIS_CMD_INLINE|REDIS_CMD_BULKREPLY}, - {"set",3,REDIS_CMD_BULK|REDIS_CMD_RETCODEREPLY}, - {"setnx",3,REDIS_CMD_BULK|REDIS_CMD_INTREPLY}, - {"del",2,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, - {"exists",2,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, - {"incr",2,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, - {"decr",2,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, - {"rpush",3,REDIS_CMD_BULK|REDIS_CMD_RETCODEREPLY}, - {"lpush",3,REDIS_CMD_BULK|REDIS_CMD_RETCODEREPLY}, - {"rpop",2,REDIS_CMD_INLINE|REDIS_CMD_BULKREPLY}, - {"lpop",2,REDIS_CMD_INLINE|REDIS_CMD_BULKREPLY}, - {"llen",2,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, - {"lindex",3,REDIS_CMD_INLINE|REDIS_CMD_BULKREPLY}, - {"lset",4,REDIS_CMD_BULK|REDIS_CMD_RETCODEREPLY}, - {"lrange",4,REDIS_CMD_INLINE|REDIS_CMD_MULTIBULKREPLY}, - {"ltrim",4,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, - {"lrem",4,REDIS_CMD_BULK|REDIS_CMD_INTREPLY}, - {"sadd",3,REDIS_CMD_BULK|REDIS_CMD_INTREPLY}, - {"srem",3,REDIS_CMD_BULK|REDIS_CMD_INTREPLY}, - {"sismember",3,REDIS_CMD_BULK|REDIS_CMD_INTREPLY}, - {"scard",2,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, - {"sinter",-2,REDIS_CMD_INLINE|REDIS_CMD_MULTIBULKREPLY}, - {"sinterstore",-3,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, - {"smembers",2,REDIS_CMD_INLINE|REDIS_CMD_MULTIBULKREPLY}, - {"incrby",3,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, - {"decrby",3,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, - {"randomkey",1,REDIS_CMD_INLINE|REDIS_CMD_SINGLELINEREPLY}, - {"select",2,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, - {"move",3,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, - {"rename",3,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, - {"renamenx",3,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, - {"keys",2,REDIS_CMD_INLINE|REDIS_CMD_BULKREPLY}, - {"dbsize",1,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, - {"ping",1,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, - {"echo",2,REDIS_CMD_BULK|REDIS_CMD_BULKREPLY}, - {"save",1,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, - {"bgsave",1,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, - {"shutdown",1,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, - {"lastsave",1,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, - {"type",2,REDIS_CMD_INLINE|REDIS_CMD_SINGLELINEREPLY}, - {"flushdb",1,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, - {"flushall",1,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, - {"sort",-2,REDIS_CMD_INLINE|REDIS_CMD_MULTIBULKREPLY}, - {"info",1,REDIS_CMD_INLINE|REDIS_CMD_BULKREPLY}, - {"mget",-2,REDIS_CMD_INLINE|REDIS_CMD_MULTIBULKREPLY}, + {"get",2,REDIS_CMD_INLINE}, + {"set",3,REDIS_CMD_BULK}, + {"setnx",3,REDIS_CMD_BULK}, + {"del",2,REDIS_CMD_INLINE}, + {"exists",2,REDIS_CMD_INLINE}, + {"incr",2,REDIS_CMD_INLINE}, + {"decr",2,REDIS_CMD_INLINE}, + {"rpush",3,REDIS_CMD_BULK}, + {"lpush",3,REDIS_CMD_BULK}, + {"rpop",2,REDIS_CMD_INLINE}, + {"lpop",2,REDIS_CMD_INLINE}, + {"llen",2,REDIS_CMD_INLINE}, + {"lindex",3,REDIS_CMD_INLINE}, + {"lset",4,REDIS_CMD_BULK}, + {"lrange",4,REDIS_CMD_INLINE}, + {"ltrim",4,REDIS_CMD_INLINE}, + {"lrem",4,REDIS_CMD_BULK}, + {"sadd",3,REDIS_CMD_BULK}, + {"srem",3,REDIS_CMD_BULK}, + {"sismember",3,REDIS_CMD_BULK}, + {"scard",2,REDIS_CMD_INLINE}, + {"sinter",-2,REDIS_CMD_INLINE}, + {"sinterstore",-3,REDIS_CMD_INLINE}, + {"smembers",2,REDIS_CMD_INLINE}, + {"incrby",3,REDIS_CMD_INLINE}, + {"decrby",3,REDIS_CMD_INLINE}, + {"randomkey",1,REDIS_CMD_INLINE}, + {"select",2,REDIS_CMD_INLINE}, + {"move",3,REDIS_CMD_INLINE}, + {"rename",3,REDIS_CMD_INLINE}, + {"renamenx",3,REDIS_CMD_INLINE}, + {"keys",2,REDIS_CMD_INLINE}, + {"dbsize",1,REDIS_CMD_INLINE}, + {"ping",1,REDIS_CMD_INLINE}, + {"echo",2,REDIS_CMD_BULK}, + {"save",1,REDIS_CMD_INLINE}, + {"bgsave",1,REDIS_CMD_INLINE}, + {"shutdown",1,REDIS_CMD_INLINE}, + {"lastsave",1,REDIS_CMD_INLINE}, + {"type",2,REDIS_CMD_INLINE}, + {"flushdb",1,REDIS_CMD_INLINE}, + {"flushall",1,REDIS_CMD_INLINE}, + {"sort",-2,REDIS_CMD_INLINE}, + {"info",1,REDIS_CMD_INLINE}, + {"mget",-2,REDIS_CMD_INLINE}, {NULL,0,0} }; +static int cliReadReply(int fd); + static struct redisCommand *lookupCommand(char *name) { int j = 0; while(cmdTable[j].name != NULL) { @@ -150,38 +147,26 @@ static sds cliReadLine(int fd) { return sdstrim(line,"\r\n"); } -static int cliReadInlineReply(int fd, int type) { +static int cliReadSingleLineReply(int fd) { sds reply = cliReadLine(fd); if (reply == NULL) return 1; printf("%s\n", reply); - if (type == REDIS_CMD_SINGLELINEREPLY) return 0; - if (type == REDIS_CMD_INTREPLY) return atoi(reply) < 0; - if (type == REDIS_CMD_RETCODEREPLY) return reply[0] == '-'; return 0; } -static int cliReadBulkReply(int fd, int multibulk) { +static int cliReadBulkReply(int fd) { sds replylen = cliReadLine(fd); char *reply, crlf[2]; - int bulklen, error = 0; + int bulklen; if (replylen == NULL) return 1; - if (strcmp(replylen,"nil") == 0) { - sdsfree(replylen); - printf("(nil)\n"); - return 0; - } bulklen = atoi(replylen); - if (multibulk && bulklen == -1) { + if (bulklen == -1) { sdsfree(replylen); printf("(nil)"); return 0; } - if (bulklen < 0) { - bulklen = -bulklen; - error = 1; - } reply = zmalloc(bulklen); anetRead(fd,reply,bulklen); anetRead(fd,crlf,2); @@ -189,10 +174,10 @@ static int cliReadBulkReply(int fd, int multibulk) { zfree(reply); return 1; } - if (!multibulk && isatty(fileno(stdout)) && reply[bulklen-1] != '\n') + if (isatty(fileno(stdout)) && reply[bulklen-1] != '\n') printf("\n"); zfree(reply); - return error; + return 0; } static int cliReadMultiBulkReply(int fd) { @@ -200,21 +185,45 @@ static int cliReadMultiBulkReply(int fd) { int elements, c = 1; if (replylen == NULL) return 1; - if (strcmp(replylen,"nil") == 0) { + elements = atoi(replylen); + if (elements == -1) { sdsfree(replylen); printf("(nil)\n"); return 0; } - elements = atoi(replylen); + if (elements == 0) { + printf("(empty list or set)\n"); + } while(elements--) { printf("%d. ", c); - if (cliReadBulkReply(fd,1)) return 1; - printf("\n"); + if (cliReadReply(fd)) return 1; c++; } return 0; } +static int cliReadReply(int fd) { + char type; + + if (anetRead(fd,&type,1) <= 0) exit(1); + switch(type) { + case '-': + printf("(error) "); + cliReadSingleLineReply(fd); + return 1; + case '+': + case ':': + return cliReadSingleLineReply(fd); + case '$': + return cliReadBulkReply(fd); + case '*': + return cliReadMultiBulkReply(fd); + default: + printf("protocol error, got '%c' as reply type byte\n", type); + return 1; + } +} + static int cliSendCommand(int argc, char **argv) { struct redisCommand *rc = lookupCommand(argv[0]); int fd, j, retval = 0; @@ -247,17 +256,7 @@ static int cliSendCommand(int argc, char **argv) { cmd = sdscat(cmd,"\r\n"); } anetWrite(fd,cmd,sdslen(cmd)); - if (rc->flags & REDIS_CMD_INTREPLY) { - retval = cliReadInlineReply(fd,REDIS_CMD_INTREPLY); - } else if (rc->flags & REDIS_CMD_RETCODEREPLY) { - retval = cliReadInlineReply(fd,REDIS_CMD_RETCODEREPLY); - } else if (rc->flags & REDIS_CMD_SINGLELINEREPLY) { - retval = cliReadInlineReply(fd,REDIS_CMD_SINGLELINEREPLY); - } else if (rc->flags & REDIS_CMD_BULKREPLY) { - retval = cliReadBulkReply(fd,0); - } else if (rc->flags & REDIS_CMD_MULTIBULKREPLY) { - retval = cliReadMultiBulkReply(fd); - } + retval = cliReadReply(fd); if (retval) { close(fd); return retval; diff --git a/redis.c b/redis.c index 58b85606..5174b333 100644 --- a/redis.c +++ b/redis.c @@ -214,10 +214,10 @@ typedef struct _redisSortOperation { } redisSortOperation; struct sharedObjectsStruct { - robj *crlf, *ok, *err, *zerobulk, *nil, *zero, *one, *pong, *space, - *minus1, *minus2, *minus3, *minus4, - *wrongtypeerr, *nokeyerr, *wrongtypeerrbulk, *nokeyerrbulk, - *syntaxerr, *syntaxerrbulk, + robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *pong, *space, + *colon, *minus1, *nullbulk, *nullmultibulk, + *emptymultibulk, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr, + *outofrangeerr, *plus, *select0, *select1, *select2, *select3, *select4, *select5, *select6, *select7, *select8, *select9; } shared; @@ -660,29 +660,28 @@ static void createSharedObjects(void) { shared.crlf = createObject(REDIS_STRING,sdsnew("\r\n")); shared.ok = createObject(REDIS_STRING,sdsnew("+OK\r\n")); shared.err = createObject(REDIS_STRING,sdsnew("-ERR\r\n")); - shared.zerobulk = createObject(REDIS_STRING,sdsnew("0\r\n\r\n")); - shared.nil = createObject(REDIS_STRING,sdsnew("nil\r\n")); - shared.zero = createObject(REDIS_STRING,sdsnew("0\r\n")); - shared.one = createObject(REDIS_STRING,sdsnew("1\r\n")); + shared.emptybulk = createObject(REDIS_STRING,sdsnew("$0\r\n\r\n")); + shared.czero = createObject(REDIS_STRING,sdsnew(":0\r\n")); + shared.cone = createObject(REDIS_STRING,sdsnew(":1\r\n")); + shared.nullbulk = createObject(REDIS_STRING,sdsnew("$-1\r\n")); + shared.nullmultibulk = createObject(REDIS_STRING,sdsnew("*-1\r\n")); + shared.emptymultibulk = createObject(REDIS_STRING,sdsnew("*0\r\n")); /* no such key */ shared.minus1 = createObject(REDIS_STRING,sdsnew("-1\r\n")); - /* operation against key holding a value of the wrong type */ - shared.minus2 = createObject(REDIS_STRING,sdsnew("-2\r\n")); - /* src and dest objects are the same */ - shared.minus3 = createObject(REDIS_STRING,sdsnew("-3\r\n")); - /* out of range argument */ - shared.minus4 = createObject(REDIS_STRING,sdsnew("-4\r\n")); shared.pong = createObject(REDIS_STRING,sdsnew("+PONG\r\n")); shared.wrongtypeerr = createObject(REDIS_STRING,sdsnew( "-ERR Operation against a key holding the wrong kind of value\r\n")); - shared.wrongtypeerrbulk = createObject(REDIS_STRING,sdscatprintf(sdsempty(),"%d\r\n%s",-sdslen(shared.wrongtypeerr->ptr)+2,shared.wrongtypeerr->ptr)); shared.nokeyerr = createObject(REDIS_STRING,sdsnew( "-ERR no such key\r\n")); - shared.nokeyerrbulk = createObject(REDIS_STRING,sdscatprintf(sdsempty(),"%d\r\n%s",-sdslen(shared.nokeyerr->ptr)+2,shared.nokeyerr->ptr)); shared.syntaxerr = createObject(REDIS_STRING,sdsnew( "-ERR syntax error\r\n")); - shared.syntaxerrbulk = createObject(REDIS_STRING,sdscatprintf(sdsempty(),"%d\r\n%s",-sdslen(shared.syntaxerr->ptr)+2,shared.syntaxerr->ptr)); + shared.sameobjecterr = createObject(REDIS_STRING,sdsnew( + "-ERR source and destination objects are the same\r\n")); + shared.outofrangeerr = createObject(REDIS_STRING,sdsnew( + "-ERR index out of range\r\n")); shared.space = createObject(REDIS_STRING,sdsnew(" ")); + shared.colon = createObject(REDIS_STRING,sdsnew(":")); + shared.plus = createObject(REDIS_STRING,sdsnew("+")); shared.select0 = createStringObject("select 0\r\n",10); shared.select1 = createStringObject("select 1\r\n",10); shared.select2 = createStringObject("select 2\r\n",10); @@ -1654,7 +1653,7 @@ static void pingCommand(redisClient *c) { } static void echoCommand(redisClient *c) { - addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n", + addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n", (int)sdslen(c->argv[1]->ptr))); addReply(c,c->argv[1]); addReply(c,shared.crlf); @@ -1671,7 +1670,7 @@ static void setGenericCommand(redisClient *c, int nx) { dictReplace(c->dict,c->argv[1],c->argv[2]); incrRefCount(c->argv[2]); } else { - addReply(c,shared.zero); + addReply(c,shared.czero); return; } } else { @@ -1679,7 +1678,7 @@ static void setGenericCommand(redisClient *c, int nx) { incrRefCount(c->argv[2]); } server.dirty++; - addReply(c, nx ? shared.one : shared.ok); + addReply(c, nx ? shared.cone : shared.ok); } static void setCommand(redisClient *c) { @@ -1695,14 +1694,14 @@ static void getCommand(redisClient *c) { de = dictFind(c->dict,c->argv[1]); if (de == NULL) { - addReply(c,shared.nil); + addReply(c,shared.nullbulk); } else { robj *o = dictGetEntryVal(de); if (o->type != REDIS_STRING) { - addReply(c,shared.wrongtypeerrbulk); + addReply(c,shared.wrongtypeerr); } else { - addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",(int)sdslen(o->ptr))); + addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",(int)sdslen(o->ptr))); addReply(c,o); addReply(c,shared.crlf); } @@ -1713,18 +1712,18 @@ static void mgetCommand(redisClient *c) { dictEntry *de; int j; - addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",c->argc-1)); + addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",c->argc-1)); for (j = 1; j < c->argc; j++) { de = dictFind(c->dict,c->argv[j]); if (de == NULL) { - addReply(c,shared.minus1); + addReply(c,shared.nullbulk); } else { robj *o = dictGetEntryVal(de); if (o->type != REDIS_STRING) { - addReply(c,shared.minus1); + addReply(c,shared.nullbulk); } else { - addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",(int)sdslen(o->ptr))); + addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",(int)sdslen(o->ptr))); addReply(c,o); addReply(c,shared.crlf); } @@ -1762,6 +1761,7 @@ static void incrDecrCommand(redisClient *c, int incr) { incrRefCount(c->argv[1]); } server.dirty++; + addReply(c,shared.colon); addReply(c,o); addReply(c,shared.crlf); } @@ -1789,9 +1789,9 @@ static void decrbyCommand(redisClient *c) { static void delCommand(redisClient *c) { if (dictDelete(c->dict,c->argv[1]) == DICT_OK) { server.dirty++; - addReply(c,shared.one); + addReply(c,shared.cone); } else { - addReply(c,shared.zero); + addReply(c,shared.czero); } } @@ -1800,9 +1800,9 @@ static void existsCommand(redisClient *c) { de = dictFind(c->dict,c->argv[1]); if (de == NULL) - addReply(c,shared.zero); + addReply(c,shared.czero); else - addReply(c,shared.one); + addReply(c,shared.cone); } static void selectCommand(redisClient *c) { @@ -1822,6 +1822,7 @@ static void randomkeyCommand(redisClient *c) { if (de == NULL) { addReply(c,shared.crlf); } else { + addReply(c,shared.plus); addReply(c,dictGetEntryKey(de)); addReply(c,shared.crlf); } @@ -1852,18 +1853,18 @@ static void keysCommand(redisClient *c) { } } dictReleaseIterator(di); - lenobj->ptr = sdscatprintf(sdsempty(),"%lu\r\n",keyslen+(numkeys ? (numkeys-1) : 0)); + lenobj->ptr = sdscatprintf(sdsempty(),"$%lu\r\n",keyslen+(numkeys ? (numkeys-1) : 0)); addReply(c,shared.crlf); } static void dbsizeCommand(redisClient *c) { addReplySds(c, - sdscatprintf(sdsempty(),"%lu\r\n",dictGetHashTableUsed(c->dict))); + sdscatprintf(sdsempty(),":%lu\r\n",dictGetHashTableUsed(c->dict))); } static void lastsaveCommand(redisClient *c) { addReplySds(c, - sdscatprintf(sdsempty(),"%lu\r\n",server.lastsave)); + sdscatprintf(sdsempty(),":%lu\r\n",server.lastsave)); } static void typeCommand(redisClient *c) { @@ -1872,14 +1873,14 @@ static void typeCommand(redisClient *c) { de = dictFind(c->dict,c->argv[1]); if (de == NULL) { - type = "none"; + type = "+none"; } else { robj *o = dictGetEntryVal(de); switch(o->type) { - case REDIS_STRING: type = "string"; break; - case REDIS_LIST: type = "list"; break; - case REDIS_SET: type = "set"; break; + case REDIS_STRING: type = "+string"; break; + case REDIS_LIST: type = "+list"; break; + case REDIS_SET: type = "+set"; break; default: type = "unknown"; break; } } @@ -1927,19 +1928,13 @@ static void renameGenericCommand(redisClient *c, int nx) { /* To use the same key as src and dst is probably an error */ if (sdscmp(c->argv[1]->ptr,c->argv[2]->ptr) == 0) { - if (nx) - addReply(c,shared.minus3); - else - addReplySds(c,sdsnew("-ERR src and dest key are the same\r\n")); + addReply(c,shared.sameobjecterr); return; } de = dictFind(c->dict,c->argv[1]); if (de == NULL) { - if (nx) - addReply(c,shared.minus1); - else - addReply(c,shared.nokeyerr); + addReply(c,shared.nokeyerr); return; } o = dictGetEntryVal(de); @@ -1947,7 +1942,7 @@ static void renameGenericCommand(redisClient *c, int nx) { if (dictAdd(c->dict,c->argv[2],o) == DICT_ERR) { if (nx) { decrRefCount(o); - addReply(c,shared.zero); + addReply(c,shared.czero); return; } dictReplace(c->dict,c->argv[2],o); @@ -1956,7 +1951,7 @@ static void renameGenericCommand(redisClient *c, int nx) { } dictDelete(c->dict,c->argv[1]); server.dirty++; - addReply(c,nx ? shared.one : shared.ok); + addReply(c,nx ? shared.cone : shared.ok); } static void renameCommand(redisClient *c) { @@ -1977,7 +1972,7 @@ static void moveCommand(redisClient *c) { src = c->dict; srcid = c->dictid; if (selectDb(c,atoi(c->argv[2]->ptr)) == REDIS_ERR) { - addReply(c,shared.minus4); + addReply(c,shared.outofrangeerr); return; } dst = c->dict; @@ -1987,14 +1982,14 @@ static void moveCommand(redisClient *c) { /* If the user is moving using as target the same * DB as the source DB it is probably an error. */ if (src == dst) { - addReply(c,shared.minus3); + addReply(c,shared.sameobjecterr); return; } /* Check if the element exists and get a reference */ de = dictFind(c->dict,c->argv[1]); if (!de) { - addReply(c,shared.zero); + addReply(c,shared.czero); return; } @@ -2002,7 +1997,7 @@ static void moveCommand(redisClient *c) { key = dictGetEntryKey(de); o = dictGetEntryVal(de); if (dictAdd(dst,key,o) == DICT_ERR) { - addReply(c,shared.zero); + addReply(c,shared.czero); return; } incrRefCount(key); @@ -2011,7 +2006,7 @@ static void moveCommand(redisClient *c) { /* OK! key moved, free the entry in the source DB */ dictDelete(src,c->argv[1]); server.dirty++; - addReply(c,shared.one); + addReply(c,shared.cone); } /* =================================== Lists ================================ */ @@ -2064,15 +2059,15 @@ static void llenCommand(redisClient *c) { de = dictFind(c->dict,c->argv[1]); if (de == NULL) { - addReply(c,shared.zero); + addReply(c,shared.czero); return; } else { robj *o = dictGetEntryVal(de); if (o->type != REDIS_LIST) { - addReply(c,shared.minus2); + addReply(c,shared.wrongtypeerr); } else { l = o->ptr; - addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",listLength(l))); + addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",listLength(l))); } } } @@ -2083,22 +2078,22 @@ static void lindexCommand(redisClient *c) { de = dictFind(c->dict,c->argv[1]); if (de == NULL) { - addReply(c,shared.nil); + addReply(c,shared.nullbulk); } else { robj *o = dictGetEntryVal(de); if (o->type != REDIS_LIST) { - addReply(c,shared.wrongtypeerrbulk); + addReply(c,shared.wrongtypeerr); } else { list *list = o->ptr; listNode *ln; ln = listIndex(list, index); if (ln == NULL) { - addReply(c,shared.nil); + addReply(c,shared.nullbulk); } else { robj *ele = listNodeValue(ln); - addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",(int)sdslen(ele->ptr))); + addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",(int)sdslen(ele->ptr))); addReply(c,ele); addReply(c,shared.crlf); } @@ -2124,7 +2119,7 @@ static void lsetCommand(redisClient *c) { ln = listIndex(list, index); if (ln == NULL) { - addReplySds(c,sdsnew("-ERR index out of range\r\n")); + addReply(c,shared.outofrangeerr); } else { robj *ele = listNodeValue(ln); @@ -2143,12 +2138,12 @@ static void popGenericCommand(redisClient *c, int where) { de = dictFind(c->dict,c->argv[1]); if (de == NULL) { - addReply(c,shared.nil); + addReply(c,shared.nullbulk); } else { robj *o = dictGetEntryVal(de); if (o->type != REDIS_LIST) { - addReply(c,shared.wrongtypeerrbulk); + addReply(c,shared.wrongtypeerr); } else { list *list = o->ptr; listNode *ln; @@ -2159,10 +2154,10 @@ static void popGenericCommand(redisClient *c, int where) { ln = listLast(list); if (ln == NULL) { - addReply(c,shared.nil); + addReply(c,shared.nullbulk); } else { robj *ele = listNodeValue(ln); - addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",(int)sdslen(ele->ptr))); + addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",(int)sdslen(ele->ptr))); addReply(c,ele); addReply(c,shared.crlf); listDelNode(list,ln); @@ -2187,12 +2182,12 @@ static void lrangeCommand(redisClient *c) { de = dictFind(c->dict,c->argv[1]); if (de == NULL) { - addReply(c,shared.nil); + addReply(c,shared.nullmultibulk); } else { robj *o = dictGetEntryVal(de); if (o->type != REDIS_LIST) { - addReply(c,shared.wrongtypeerrbulk); + addReply(c,shared.wrongtypeerr); } else { list *list = o->ptr; listNode *ln; @@ -2209,7 +2204,7 @@ static void lrangeCommand(redisClient *c) { /* indexes sanity checks */ if (start > end || start >= llen) { /* Out of range start or start > end result in empty list */ - addReply(c,shared.zero); + addReply(c,shared.emptymultibulk); return; } if (end >= llen) end = llen-1; @@ -2217,10 +2212,10 @@ static void lrangeCommand(redisClient *c) { /* Return the result in form of a multi-bulk reply */ ln = listIndex(list, start); - addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",rangelen)); + addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",rangelen)); for (j = 0; j < rangelen; j++) { ele = listNodeValue(ln); - addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",(int)sdslen(ele->ptr))); + addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",(int)sdslen(ele->ptr))); addReply(c,ele); addReply(c,shared.crlf); ln = ln->next; @@ -2290,7 +2285,7 @@ static void lremCommand(redisClient *c) { robj *o = dictGetEntryVal(de); if (o->type != REDIS_LIST) { - addReply(c,shared.minus2); + addReply(c,shared.wrongtypeerr); } else { list *list = o->ptr; listNode *ln, *next; @@ -2314,7 +2309,7 @@ static void lremCommand(redisClient *c) { } ln = next; } - addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",removed)); + addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",removed)); } } } @@ -2333,16 +2328,16 @@ static void saddCommand(redisClient *c) { } else { set = dictGetEntryVal(de); if (set->type != REDIS_SET) { - addReply(c,shared.minus2); + addReply(c,shared.wrongtypeerr); return; } } if (dictAdd(set->ptr,c->argv[2],NULL) == DICT_OK) { incrRefCount(c->argv[2]); server.dirty++; - addReply(c,shared.one); + addReply(c,shared.cone); } else { - addReply(c,shared.zero); + addReply(c,shared.czero); } } @@ -2351,20 +2346,20 @@ static void sremCommand(redisClient *c) { de = dictFind(c->dict,c->argv[1]); if (de == NULL) { - addReply(c,shared.zero); + addReply(c,shared.czero); } else { robj *set; set = dictGetEntryVal(de); if (set->type != REDIS_SET) { - addReply(c,shared.minus2); + addReply(c,shared.wrongtypeerr); return; } if (dictDelete(set->ptr,c->argv[2]) == DICT_OK) { server.dirty++; - addReply(c,shared.one); + addReply(c,shared.cone); } else { - addReply(c,shared.zero); + addReply(c,shared.czero); } } } @@ -2374,19 +2369,19 @@ static void sismemberCommand(redisClient *c) { de = dictFind(c->dict,c->argv[1]); if (de == NULL) { - addReply(c,shared.zero); + addReply(c,shared.czero); } else { robj *set; set = dictGetEntryVal(de); if (set->type != REDIS_SET) { - addReply(c,shared.minus2); + addReply(c,shared.wrongtypeerr); return; } if (dictFind(set->ptr,c->argv[2])) - addReply(c,shared.one); + addReply(c,shared.cone); else - addReply(c,shared.zero); + addReply(c,shared.czero); } } @@ -2396,15 +2391,15 @@ static void scardCommand(redisClient *c) { de = dictFind(c->dict,c->argv[1]); if (de == NULL) { - addReply(c,shared.zero); + addReply(c,shared.czero); return; } else { robj *o = dictGetEntryVal(de); if (o->type != REDIS_SET) { - addReply(c,shared.minus2); + addReply(c,shared.wrongtypeerr); } else { s = o->ptr; - addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n", + addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n", dictGetHashTableUsed(s))); } } @@ -2431,13 +2426,13 @@ static void sinterGenericCommand(redisClient *c, robj **setskeys, int setsnum, r de = dictFind(c->dict,setskeys[j]); if (!de) { zfree(dv); - addReply(c,dstkey ? shared.nokeyerr : shared.nil); + addReply(c,shared.nokeyerr); return; } setobj = dictGetEntryVal(de); if (setobj->type != REDIS_SET) { zfree(dv); - addReply(c,dstkey ? shared.wrongtypeerr : shared.wrongtypeerrbulk); + addReply(c,shared.wrongtypeerr); return; } dv[j] = setobj->ptr; @@ -2479,7 +2474,7 @@ static void sinterGenericCommand(redisClient *c, robj **setskeys, int setsnum, r continue; /* at least one set does not contain the member */ ele = dictGetEntryKey(de); if (!dstkey) { - addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",sdslen(ele->ptr))); + addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",sdslen(ele->ptr))); addReply(c,ele); addReply(c,shared.crlf); cardinality++; @@ -2491,7 +2486,7 @@ static void sinterGenericCommand(redisClient *c, robj **setskeys, int setsnum, r dictReleaseIterator(di); if (!dstkey) - lenobj->ptr = sdscatprintf(sdsempty(),"%d\r\n",cardinality); + lenobj->ptr = sdscatprintf(sdsempty(),"*%d\r\n",cardinality); else addReply(c,shared.ok); zfree(dv); @@ -2621,12 +2616,12 @@ static void sortCommand(redisClient *c) { /* Lookup the key to sort. It must be of the right types */ de = dictFind(c->dict,c->argv[1]); if (de == NULL) { - addReply(c,shared.nokeyerrbulk); + addReply(c,shared.nokeyerr); return; } sortval = dictGetEntryVal(de); if (sortval->type != REDIS_SET && sortval->type != REDIS_LIST) { - addReply(c,shared.wrongtypeerrbulk); + addReply(c,shared.wrongtypeerr); return; } @@ -2680,7 +2675,7 @@ static void sortCommand(redisClient *c) { } else { decrRefCount(sortval); listRelease(operations); - addReply(c,shared.syntaxerrbulk); + addReply(c,shared.syntaxerr); return; } j++; @@ -2761,11 +2756,11 @@ static void sortCommand(redisClient *c) { /* Send command output to the output buffer, performing the specified * GET/DEL/INCR/DECR operations if any. */ outputlen = getop ? getop*(end-start+1) : end-start+1; - addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",outputlen)); + addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",outputlen)); for (j = start; j <= end; j++) { listNode *ln = operations->head; if (!getop) { - addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n", + addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n", sdslen(vector[j].obj->ptr))); addReply(c,vector[j].obj); addReply(c,shared.crlf); @@ -2779,7 +2774,7 @@ static void sortCommand(redisClient *c) { if (!val || val->type != REDIS_STRING) { addReply(c,shared.minus1); } else { - addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n", + addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n", sdslen(val->ptr))); addReply(c,val); addReply(c,shared.crlf); @@ -2827,7 +2822,7 @@ static void infoCommand(redisClient *c) { uptime, uptime/(3600*24) ); - addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",sdslen(info))); + addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n",sdslen(info))); addReplySds(c,info); addReply(c,shared.crlf); } @@ -2930,7 +2925,7 @@ static void syncCommand(redisClient *c) { if (fd == -1 || fstat(fd,&sb) == -1) goto closeconn; len = sb.st_size; - snprintf(sizebuf,32,"%d\r\n",len); + snprintf(sizebuf,32,"$%d\r\n",len); if (syncWrite(c->fd,sizebuf,strlen(sizebuf),5) == -1) goto closeconn; while(len) { char buf[1024]; @@ -2982,7 +2977,7 @@ static int syncWithMaster(void) { strerror(errno)); return REDIS_ERR; } - dumpsize = atoi(buf); + dumpsize = atoi(buf+1); redisLog(REDIS_NOTICE,"Receiving %d bytes data dump from MASTER",dumpsize); /* Read the bulk write data on a temp file */ snprintf(tmpfile,256,"temp-%d.%ld.rdb",(int)time(NULL),(long int)random()); diff --git a/test-redis.tcl b/test-redis.tcl index 9bfab204..bc39f85d 100644 --- a/test-redis.tcl +++ b/test-redis.tcl @@ -124,16 +124,16 @@ proc main {server port} { puts -nonewline $fd "SET k1 4\r\nxyzk\r\nGET k1\r\nPING\r\n" flush $fd set res {} - append res [string match +OK* [redis_read_retcode $fd]] - append res [redis_bulk_read $fd] - append res [string match +PONG* [redis_read_retcode $fd]] + append res [string match OK* [redis_read_reply $fd]] + append res [redis_read_reply $fd] + append res [string match PONG* [redis_read_reply $fd]] format $res } {1xyzk1} test {Non existing command} { puts -nonewline $fd "foo\r\n" flush $fd - string match -ERR* [redis_read_retcode $fd] + string match ERR* [redis_read_reply $fd] } {1} test {Basic LPUSH, RPUSH, LLENGTH, LINDEX} { @@ -181,19 +181,19 @@ proc main {server port} { redis_del $fd mylist redis_set $fd mylist foobar redis_llen $fd mylist - } {-2} + } {ERR*} test {LINDEX against non-list value error} { redis_lindex $fd mylist 0 - } {*ERROR*} + } {ERR*} test {LPUSH against non-list value error} { redis_lpush $fd mylist 0 - } {-ERR*} + } {ERR*} test {RPUSH against non-list value error} { redis_rpush $fd mylist 0 - } {-ERR*} + } {ERR*} test {RENAME basic usage} { redis_set $fd mykey hello @@ -236,11 +236,11 @@ proc main {server port} { test {RENAME against non existing source key} { redis_rename $fd nokey foobar - } {-ERR*} + } {ERR*} test {RENAME where source and dest key is the same} { redis_rename $fd mykey mykey - } {-ERR*} + } {ERR*} test {DEL all keys again (DB 0)} { foreach key [redis_keys $fd *] { @@ -309,7 +309,7 @@ proc main {server port} { test {LPOP against non list value} { redis_set $fd notalist foo redis_lpop $fd notalist - } {*ERROR*against*} + } {ERR*kind*} test {Mass LPUSH/LPOP} { set sum 0 @@ -363,16 +363,16 @@ proc main {server port} { test {LSET out of range index} { redis_lset $fd mylist 10 foo - } {-ERR*range*} + } {ERR*range*} test {LSET against non existing key} { redis_lset $fd nosuchkey 10 foo - } {-ERR*key*} + } {ERR*key*} test {LSET against non list value} { redis_set $fd nolist foobar redis_lset $fd nolist 0 foo - } {-ERR*value*} + } {ERR*value*} test {SADD, SCARD, SISMEMBER, SMEMBERS basics} { redis_sadd $fd myset foo @@ -391,7 +391,7 @@ proc main {server port} { test {SADD against non set} { redis_sadd $fd mylist foo - } {-2} + } {ERR*kind*} test {SREM basics} { redis_sadd $fd myset ciao @@ -431,7 +431,7 @@ proc main {server port} { redis_set $fd myemptykey {} redis_set $fd mynormalkey {blablablba} redis_save $fd - } {+OK} + } {OK} test {Create a random list} { set tosort {} @@ -606,221 +606,224 @@ proc redis_readnl {fd len} { return $buf } -proc redis_bulk_read {fd {multi 0}} { - set count [redis_read_integer $fd] - if {$count eq {nil}} return {} - if {$multi && $count == -1} return {} - set len [expr {abs($count)}] - set buf [redis_readnl $fd $len] - if {$count < 0} {return "***ERROR*** $buf"} +proc redis_bulk_read {fd} { + set count [redis_read_line $fd] + if {$count == -1} return {} + set buf [redis_readnl $fd $count] return $buf } proc redis_multi_bulk_read fd { - set count [redis_read_integer $fd] - if {$count eq {nil}} return {} - if {$count < 0} { - set len [expr {abs($count)}] - set buf [redis_readnl $fd $len] - return "***ERROR*** $buf" - } + set count [redis_read_line $fd] + if {$count == -1} return {} set l {} for {set i 0} {$i < $count} {incr i} { - lappend l [redis_bulk_read $fd 1] + lappend l [redis_read_reply $fd] } return $l } -proc redis_read_retcode fd { - set retcode [string trim [gets $fd]] - # puts "S: $retcode" - return $retcode +proc redis_read_line fd { + string trim [gets $fd] } -proc redis_read_integer fd { - string trim [gets $fd] +proc redis_read_reply fd { + set type [read $fd 1] + if {$type eq {:}} { + redis_read_line $fd + } elseif {$type eq {-}} { + redis_read_line $fd + } elseif {$type eq {+}} { + redis_read_line $fd + } elseif {$type eq {$}} { + redis_bulk_read $fd + } elseif {$type eq {*}} { + redis_multi_bulk_read $fd + } else { + error "Bad protocol: $type as initial reply byte" + } } ### Actual API ### proc redis_set {fd key val} { redis_writenl $fd "set $key [string length $val]\r\n$val" - redis_read_retcode $fd + redis_read_reply $fd } proc redis_setnx {fd key val} { redis_writenl $fd "setnx $key [string length $val]\r\n$val" - redis_read_integer $fd + redis_read_reply $fd } proc redis_get {fd key} { redis_writenl $fd "get $key" - redis_bulk_read $fd + redis_read_reply $fd } proc redis_select {fd id} { redis_writenl $fd "select $id" - redis_read_retcode $fd + redis_read_reply $fd } proc redis_move {fd key id} { redis_writenl $fd "move $key $id" - redis_read_integer $fd + redis_read_reply $fd } proc redis_del {fd key} { redis_writenl $fd "del $key" - redis_read_integer $fd + redis_read_reply $fd } proc redis_keys {fd pattern} { redis_writenl $fd "keys $pattern" - split [redis_bulk_read $fd] + split [redis_read_reply $fd] } proc redis_dbsize {fd} { redis_writenl $fd "dbsize" - redis_read_integer $fd + redis_read_reply $fd } proc redis_incr {fd key} { redis_writenl $fd "incr $key" - redis_read_integer $fd + redis_read_reply $fd } proc redis_decr {fd key} { redis_writenl $fd "decr $key" - redis_read_integer $fd + redis_read_reply $fd } proc redis_exists {fd key} { redis_writenl $fd "exists $key" - redis_read_integer $fd + redis_read_reply $fd } proc redis_lpush {fd key val} { redis_writenl $fd "lpush $key [string length $val]\r\n$val" - redis_read_retcode $fd + redis_read_reply $fd } proc redis_rpush {fd key val} { redis_writenl $fd "rpush $key [string length $val]\r\n$val" - redis_read_retcode $fd + redis_read_reply $fd } proc redis_llen {fd key} { redis_writenl $fd "llen $key" - redis_read_integer $fd + redis_read_reply $fd } proc redis_scard {fd key} { redis_writenl $fd "scard $key" - redis_read_integer $fd + redis_read_reply $fd } proc redis_lindex {fd key index} { redis_writenl $fd "lindex $key $index" - redis_bulk_read $fd + redis_read_reply $fd } proc redis_lrange {fd key first last} { redis_writenl $fd "lrange $key $first $last" - redis_multi_bulk_read $fd + redis_read_reply $fd } proc redis_mget {fd args} { redis_writenl $fd "mget [join $args]" - redis_multi_bulk_read $fd + redis_read_reply $fd } proc redis_sort {fd key {params {}}} { redis_writenl $fd "sort $key $params" - redis_multi_bulk_read $fd + redis_read_reply $fd } proc redis_ltrim {fd key first last} { redis_writenl $fd "ltrim $key $first $last" - redis_read_retcode $fd + redis_read_reply $fd } proc redis_rename {fd key1 key2} { redis_writenl $fd "rename $key1 $key2" - redis_read_retcode $fd + redis_read_reply $fd } proc redis_renamenx {fd key1 key2} { redis_writenl $fd "renamenx $key1 $key2" - redis_read_integer $fd + redis_read_reply $fd } proc redis_lpop {fd key} { redis_writenl $fd "lpop $key" - redis_bulk_read $fd + redis_read_reply $fd } proc redis_rpop {fd key} { redis_writenl $fd "rpop $key" - redis_bulk_read $fd + redis_read_reply $fd } proc redis_lset {fd key index val} { redis_writenl $fd "lset $key $index [string length $val]\r\n$val" - redis_read_retcode $fd + redis_read_reply $fd } proc redis_sadd {fd key val} { redis_writenl $fd "sadd $key [string length $val]\r\n$val" - redis_read_integer $fd + redis_read_reply $fd } proc redis_srem {fd key val} { redis_writenl $fd "srem $key [string length $val]\r\n$val" - redis_read_integer $fd + redis_read_reply $fd } proc redis_sismember {fd key val} { redis_writenl $fd "sismember $key [string length $val]\r\n$val" - redis_read_integer $fd + redis_read_reply $fd } proc redis_sinter {fd args} { redis_writenl $fd "sinter [join $args]" - redis_multi_bulk_read $fd + redis_read_reply $fd } proc redis_sinterstore {fd args} { redis_writenl $fd "sinterstore [join $args]" - redis_read_retcode $fd + redis_read_reply $fd } proc redis_smembers {fd key} { redis_writenl $fd "smembers $key" - redis_multi_bulk_read $fd + redis_read_reply $fd } proc redis_echo {fd str} { redis_writenl $fd "echo [string length $str]\r\n$str" - redis_writenl $fd "smembers $key" + redis_read_reply $fd } proc redis_save {fd} { redis_writenl $fd "save" - redis_read_retcode $fd + redis_read_reply $fd } proc redis_flushall {fd} { redis_writenl $fd "flushall" - redis_read_retcode $fd + redis_read_reply $fd } proc redis_flushdb {fd} { redis_writenl $fd "flushdb" - redis_read_retcode $fd + redis_read_reply $fd } proc redis_lrem {fd key count val} { redis_writenl $fd "lrem $key $count [string length $val]\r\n$val" - redis_read_integer $fd + redis_read_reply $fd } proc stress {} { From fab3a740ab3f4eb4e5f1697973150f516b7c8d38 Mon Sep 17 00:00:00 2001 From: antirez Date: Tue, 24 Mar 2009 13:38:25 +0100 Subject: [PATCH 12/13] protocol doc changed --- doc/FAQ.html | 2 +- doc/ProtocolSpecification.html | 96 +++++++++++++--------------------- 2 files changed, 37 insertions(+), 61 deletions(-) diff --git a/doc/FAQ.html b/doc/FAQ.html index d3580843..87be5de5 100644 --- a/doc/FAQ.html +++ b/doc/FAQ.html @@ -34,7 +34,7 @@ So Redis offers more features:

  • Keys can store different data t
    • We wrote a simple Twitter Clone using just Redis as database. Download the source code from the download section and imagine to write it with a plain key-value DB without support for lists and sets... it's much harder.
    • Multiple DBs. Using the SELECT command the client can select different datasets. This is useful because Redis provides a MOVE atomic primitive that moves a key form a DB to another one, if the target DB already contains such a key it returns an error: this basically means a way to perform locking in distributed processing.
    • So what is Redis really about? The User interface with the programmer. Redis aims to export to the programmer the right tools to model a wide range of problems. Sets, Lists with O(1) push operation, lrange and ltrim, server-side fast intersection between sets, are primitives that allow to model complex problems with a key value database.
    -

    Isn't this key-value thing just hype?

    I imagine key-value DBs, in the short term future, to be used like you use memory in a program, with lists, hashes, and so on. With Redis it's like this, but this special kind of memory containing your data structures is shared, atomic, persistent.

    When we write code it is obvious, when we take data in memory, to use the most sensible data structure for the work, right? Incredibly when data is put inside a relational DB this is no longer true, and we create an absurd data model even if our need is to put data and get this data back in the same order we put it inside (an ORDER BY is required when the data should be already sorted. Strange, dont' you think?).

    Key-value DBs bring this back at home, to create sensible data models and use the right data structures for the problem we are trying to solve.

    Can I backup a Redis DB while the server is working?

    Yes you can. When Redis saves the DB it actually creates a temp file, then rename(2) that temp file name to the destination file name. So even while the server is working it is safe to save the database file just with the cp unix command. Note that you can use master-slave replication in order to have redundancy of data, but if all you need is backups, cp or scp will do the work pretty well.

    What's the Redis memory footprint?

    Worst case scenario: 1 Million keys with the key being the natural numbers from 0 to 999999 and the string "Hello World" as value use 100MB on my Intel macbook (32bit). Note that the same data stored linearly in an unique string takes something like 16MB, this is the norm because with small keys and values there is a lot of overhead. Memcached will perform similarly.

    With large keys/values the ratio is much better of course.

    64 bit systems will use much more memory than 32 bit systems to store the same keys, especially if the keys and values are small, this is because pointers takes 8 bytes in 64 bit systems. But of course the advantage is that you can have a lot of memory in 64 bit systems, so to run large Redis servers a 64 bit system is more or less required.

    I like Redis high level operations and features, but I don't like it takes everything in memory and I can't have a dataset larger the memory. Plans to change this?

    The whole key-value hype started for a reason: performances. Redis takes the whole dataset in memory and writes asynchronously on disk in order to be very fast, you have the best of both worlds: hyper-speed and persistence of data, but the price to pay is exactly this, that the dataset must fit on your computers RAM.

    If the data is larger then memory, and this data is stored on disk, what happens is that the bottleneck of the disk I/O speed will start to ruin the performances. Maybe not in benchmarks, but once you have real load with distributed key accesses the data must come from disk, and the disk is damn slow. Not only, but Redis supports higher level data structures than the plain values. To implement this things on disk is even slower.

    Redis will always continue to hold the whole dataset in memory because this days scalability requires to use RAM as storage media, and RAM is getting cheaper and cheaper. Today it is common for an entry level server to have 16 GB of RAM! And in the 64-bit era there are no longer limits to the amount of RAM you can have in theory.

    Ok but I absolutely need to have a DB larger than memory, still I need the Redis features

    One possible solution is to use both MySQL and Redis at the same time, basically take the state on Redis, and all the things that get accessed very frequently: user auth tokens, Redis Lists with chronologically ordered IDs of the last N-comments, N-posts, and so on. Then use MySQL as a simple storage engine for larger data, that is just create a table with an auto-incrementing ID as primary key and a large BLOB field as data field. Access MySQL data only by primary key (the ID). The application will run the high traffic queries against Redis but when there is to take the big data will ask MySQL for specific resources IDs.

    I have an empty Redis server but INFO and logs are reporting megabytes of memory in use!

    This may happen and it's prefectly ok. Redis objects are small C structures allocated and freed a lot of times. This costs a lot of CPU so instead of being freed, released objects are taken into a free list and reused when needed. This memory is taken exactly by this free objects ready to be reused.

    What happens if Redis runs out of memory?

    With modern operating systems malloc() returning NULL is not common, usually the server will start swapping and Redis performances will be disastrous so you'll know it's time to use more Redis servers or get more RAM.

    However it is planned to add a configuration directive to tell Redis to stop accepting queries but instead to SAVE the latest data and quit if it is using more than a given amount of memory. Also the new INFO command (work in progress in this days) will report the amount of memory Redis is using so you can write scripts that monitor your Redis servers checking for critical conditions.

    Update: redis SVN is able to know how much memory it is using and report it via the INFO command.

    What Redis means actually?

    Redis means two things: +

    Isn't this key-value thing just hype?

    I imagine key-value DBs, in the short term future, to be used like you use memory in a program, with lists, hashes, and so on. With Redis it's like this, but this special kind of memory containing your data structures is shared, atomic, persistent.

    When we write code it is obvious, when we take data in memory, to use the most sensible data structure for the work, right? Incredibly when data is put inside a relational DB this is no longer true, and we create an absurd data model even if our need is to put data and get this data back in the same order we put it inside (an ORDER BY is required when the data should be already sorted. Strange, dont' you think?).

    Key-value DBs bring this back at home, to create sensible data models and use the right data structures for the problem we are trying to solve.

    Can I backup a Redis DB while the server is working?

    Yes you can. When Redis saves the DB it actually creates a temp file, then rename(2) that temp file name to the destination file name. So even while the server is working it is safe to save the database file just with the cp unix command. Note that you can use master-slave replication in order to have redundancy of data, but if all you need is backups, cp or scp will do the work pretty well.

    What's the Redis memory footprint?

    Worst case scenario: 1 Million keys with the key being the natural numbers from 0 to 999999 and the string "Hello World" as value use 100MB on my Intel macbook (32bit). Note that the same data stored linearly in an unique string takes something like 16MB, this is the norm because with small keys and values there is a lot of overhead. Memcached will perform similarly.

    With large keys/values the ratio is much better of course.

    64 bit systems will use much more memory than 32 bit systems to store the same keys, especially if the keys and values are small, this is because pointers takes 8 bytes in 64 bit systems. But of course the advantage is that you can have a lot of memory in 64 bit systems, so to run large Redis servers a 64 bit system is more or less required.

    I like Redis high level operations and features, but I don't like it takes everything in memory and I can't have a dataset larger the memory. Plans to change this?

    The whole key-value hype started for a reason: performances. Redis takes the whole dataset in memory and writes asynchronously on disk in order to be very fast, you have the best of both worlds: hyper-speed and persistence of data, but the price to pay is exactly this, that the dataset must fit on your computers RAM.

    If the data is larger then memory, and this data is stored on disk, what happens is that the bottleneck of the disk I/O speed will start to ruin the performances. Maybe not in benchmarks, but once you have real load from multiple clients with distributed key accesses the data must come from disk, and the disk is damn slow. Not only, but Redis supports higher level data structures than the plain values. To implement this things on disk is even slower.

    Redis will always continue to hold the whole dataset in memory because this days scalability requires to use RAM as storage media, and RAM is getting cheaper and cheaper. Today it is common for an entry level server to have 16 GB of RAM! And in the 64-bit era there are no longer limits to the amount of RAM you can have in theory.

    Ok but I absolutely need to have a DB larger than memory, still I need the Redis features

    You may try to load a dataset larger than your memory in Redis and see what happens, basically if you are using a modern Operating System, and you have a lot of data in the DB that is rarely accessed, the OS's virtual memory implementation will try to swap rarely used pages of memory on the disk, to only recall this pages when they are needed. If you have many large values rarely used this will work. If your DB is big because you have tons of little values accessed at random without a specific pattern this will not work (at low level a page is usually 4096 bytes, and you can have different keys/values stored at a single page. The OS can't swap this page on disk if there are even few keys used frequently).

    Another possible solution is to use both MySQL and Redis at the same time, basically take the state on Redis, and all the things that get accessed very frequently: user auth tokens, Redis Lists with chronologically ordered IDs of the last N-comments, N-posts, and so on. Then use MySQL as a simple storage engine for larger data, that is just create a table with an auto-incrementing ID as primary key and a large BLOB field as data field. Access MySQL data only by primary key (the ID). The application will run the high traffic queries against Redis but when there is to take the big data will ask MySQL for specific resources IDs.

    I have an empty Redis server but INFO and logs are reporting megabytes of memory in use!

    This may happen and it's prefectly ok. Redis objects are small C structures allocated and freed a lot of times. This costs a lot of CPU so instead of being freed, released objects are taken into a free list and reused when needed. This memory is taken exactly by this free objects ready to be reused.

    What happens if Redis runs out of memory?

    With modern operating systems malloc() returning NULL is not common, usually the server will start swapping and Redis performances will be disastrous so you'll know it's time to use more Redis servers or get more RAM.

    However it is planned to add a configuration directive to tell Redis to stop accepting queries but instead to SAVE the latest data and quit if it is using more than a given amount of memory. Also the new INFO command (work in progress in this days) will report the amount of memory Redis is using so you can write scripts that monitor your Redis servers checking for critical conditions.

    Update: redis SVN is able to know how much memory it is using and report it via the INFO command.

    What Redis means actually?

    Redis means two things:
    • it's a joke on the word Redistribute (instead to use just a Relational DB redistribute your workload among Redis servers)
    • it means REmote DIctionary Server

    Why did you started the Redis project?

    In order to scale LLOOGG. But after I got the basic server working I liked the idea to share the work with other guys, and Redis was turned into an open source project. diff --git a/doc/ProtocolSpecification.html b/doc/ProtocolSpecification.html index 95d454ab..9551c6a5 100644 --- a/doc/ProtocolSpecification.html +++ b/doc/ProtocolSpecification.html @@ -16,7 +16,7 @@

    ProtocolSpecification

    @@ -35,14 +35,12 @@ terminated by "\r\n" (CRLF).

    Simpl server/client chat (the server chat starts with S:, the client chat with C:)

     C: PING
     S: +PONG
    -
    An inline command is a CRLF-terminated string sent to the client. The server -usually replies to inline commands with a single line that can be a number -or a return code.

    When the server replies with a status code (that is a one line reply just indicating if the operation succeeded or not), if the first character of the -reply is a "+" then the command succeeded, if it is a "-" then the following -part of the string is an error.

    The following is another example of an INLINE command returning an integer:

    +
    An inline command is a CRLF-terminated string sent to the client. The server can reply to commands in different ways: +
    • With an error message (the first byte of the reply will be "-")
    • With a single line reply (the first byte of the reply will be "+)
    • With bulk data (the first byte of the reply will be "$")
    • With multi-bulk data, a list of values (the first byte of the reply will be "*")
    • With an integer number (the first byte of the reply will be ":")
    +The following is another example of an INLINE command returning an integer:

     C: EXISTS somekey
    -S: 0
    -
    Since 'somekey' does not exist the server returned '0'.

    Note that the EXISTS command takes one argument. Arguments are separated +S: :0 +Since 'somekey' does not exist the server returned ':0'.

    Note that the EXISTS command takes one argument. Arguments are separated simply by spaces.

    Bulk commands

    A bulk command is exactly like an inline command, but the last argument of the command must be a stream of bytes in order to send data to the server. the "SET" command is a bulk command, see the following example:

    @@ -58,82 +56,60 @@ sent by the client in the above sample:

    "SET mykey 6\r

    Bulk replies

    The server may reply to an inline or bulk command with a bulk reply. See the following example:

     C: GET mykey
    -S: 6
    +S: $6
     S: foobar
     
    A bulk reply is very similar to the last argument of a bulk command. The -server sends as the first line the number of bytes of the actual reply -followed by CRLF, then the bytes are sent followed by additional two bytes -for the final CRLF. The exact sequence sent by the server is:

    "6\r\nfoobar\r\n"
    +server sends as the first line a "$" byte followed by the number of bytes +of the actual reply followed by CRLF, then the bytes are sent followed by +additional two bytes for the final CRLF. The exact sequence sent by the +server is:

    "$6\r\nfoobar\r\n"
    If the requested value does not exist the bulk reply will use the special -value 'nil' instead to send the line containing the number of bytes to read. -This is an example:

    +value -1 as data length, example:

     C: GET nonexistingkey
    -S: nil
    -
    The client library API should not return an empty string, but a nil object. +S: $-1 +
    The client library API should not return an empty string, but a nil object, when the requested object does not exist. For example a Ruby library should return 'nil' while a C library should return -NULL.

    Bulk reply error reporting

    Bulk replies can signal errors, for example trying to use GET against a list -value is not permitted. Bulk replies use a negative bytes count in order to -signal an error. An error string of ABS(bytes_count) bytes will follow. See -the following example:

    -S: GET alistkey
    -S: -38
    -S: -ERR Requested element is not a string
    -
    -38 means: sorry your operation resulted in an error, but a 38 bytes string -that explains this error will follow. Client APIs should abort on this kind -of errors, for example a PHP client should call the die() function.

    The following commands reply with a bulk reply: GET, KEYS, LINDEX, LPOP, RPOP

    Multi-Bulk replies

    Commands similar to LRANGE needs to return multiple values (every element +NULL, and so forth.

    Multi-Bulk replies

    Commands similar to LRANGE needs to return multiple values (every element of the list is a value, and LRANGE needs to return more than a single element). This is accomplished using multiple bulk writes, prefixed by an initial line indicating how many bulk writes will follow. -Example:

    +The first byte of a multi bulk reply is always *. Example:

     C: LRANGE mylist 0 3
    -S: 4
    -S: 3
    +S: *4
    +S: $3
     S: foo
    -S: 3
    +S: $3
     S: bar
    -S: 5
    +S: $5
     S: Hello
    -S: 5
    +S: $5
     S: World
    -
    The first line the server sent is "4\r\n" in order to specify that four bulk +
    The first line the server sent is "4\r\n" in order to specify that four bulk write will follow. Then every bulk write is transmitted.

    If the specified key does not exist instead of the number of elements in the -list, the special value 'nil' is sent. Example:

    +list, the special value -1 is sent as count. Example:

     C: LRANGE nokey 0 1
    -S: nil
    +S: *-1
     
    A client library API SHOULD return a nil object and not an empty list when this -happens. This makes possible to distinguish between empty list and non existing ones.

    Nil elements in Multi-Bulk replies

    Single elements of a multi bulk reply may have -1 length, in order to signal that this elements are missing and not empty strings. This can happen with the SORT command when used with the GET pattern option when the specified key is missing. Example of a multi bulk reply containing an empty element:

    -S: 3
    -S: 3
    +happens. This makes possible to distinguish between empty list and non existing ones.

    Nil elements in Multi-Bulk replies

    Single elements of a multi bulk reply may have -1 length, in order to signal that this elements are missing and not empty strings. This can happen with the SORT command when used with the GET pattern option when the specified key is missing. Example of a multi bulk reply containing an empty element:

    +S: *3
    +S: $3
     S: foo
    -S: -1
    -S: 3
    +S: $-1
    +S: $3
     S: bar
    -
    The second element is nul. The client library should return something like this:

    +
    The second element is nul. The client library should return something like this:

     ["foo",nil,"bar"]
    -

    Multi-Bulk replies errors

    Like bulk reply errors Multi-bulk reply errors are reported using a negative -count. Example:

    -C: LRANGE stringkey 0 1
    -S: -38
    -S: -ERR Requested element is not a string
    -
    The following commands reply with a multi-bulk reply: LRANGE, LINTER

    Check the Bulk replies errors section for more information.

    Status code reply

    As already seen a status code reply is in the form of a single line string -terminated by "\r\n". For example:

    +

    Single line reply

    As already seen a single line reply is in the form of a single line string +starting with "+" terminated by "\r\n". For example:

     +OK
    -
    and

    --ERR no suck key
    -
    are two examples of status code replies. The first character of a status code reply is always "+" or "-".

    The following commands reply with a status code reply: -PING, SET, SELECT, SAVE, BGSAVE, SHUTDOWN, RENAME, LPUSH, RPUSH, LSET, LTRIM

    Integer reply

    This type of reply is just a CRLF terminated string representing an integer. For example "0\r\n", or "1000\r\n" are integer replies.

    With commands like INCR or LASTSAVE using the integer reply to actually return a value there is no special meaning for the returned integer. It is just an incremental number for INCR, a UNIX time for LASTSAVE and so on.

    Some commands like EXISTS will return 1 for true and 0 for false.

    Other commands like SADD, SREM and SETNX will return 1 if the operation was actually done, 0 otherwise, and a negative value if the operation is invalid (for example SADD against a non-set value), accordingly to this table: -
    --1 no such key
    --2 operation against the a key holding a value of the wrong type
    --3 source and destiantion objects/dbs are the same
    --4 argument out of range
    -
    -In all this cases it is mandatory that the client raises an error instead to pass the negative value to the caller. Please check the commands documentation for the exact behaviour.

    The following commands will reply with an integer reply: SETNX, DEL, EXISTS, INCR, INCRBY, DECR, DECRBY, DBSIZE, LASTSAVE, RENAMENX, MOVE, LLEN, SADD, SREM, SISMEMBER, SCARD

    The commands that will never return a negative integer (commands that can't fail) are: INCR, DECR, INCRBY, DECRBY, LASTSAVE, EXISTS, SETNX, DEL, DBSIZE.

    Single line reply

    This replies are just single line strings terminated by CRLF. Only two commands reply in this way currently, RANDOMKEY and TYPE.

    Multiple commands and pipelining

    A client can use the same connection in order to issue multiple commands. +
    The client library should return everything after the "+", that is, the string "OK" in the example.

    The following commands reply with a status code reply: +PING, SET, SELECT, SAVE, BGSAVE, SHUTDOWN, RENAME, LPUSH, RPUSH, LSET, LTRIM

    Integer reply

    This type of reply is just a CRLF terminated string representing an integer, prefixed by a ":" byte. For example ":0\r\n", or ":1000\r\n" are integer replies.

    With commands like INCR or LASTSAVE using the integer reply to actually return a value there is no special meaning for the returned integer. It is just an incremental number for INCR, a UNIX time for LASTSAVE and so on.

    Some commands like EXISTS will return 1 for true and 0 for false.

    Other commands like SADD, SREM and SETNX will return 1 if the operation was actually done, 0 otherwise.

    The following commands will reply with an integer reply: SETNX, DEL, EXISTS, INCR, INCRBY, DECR, DECRBY, DBSIZE, LASTSAVE, RENAMENX, MOVE, LLEN, SADD, SREM, SISMEMBER, SCARD

    Multiple commands and pipelining

    A client can use the same connection in order to issue multiple commands. Pipelining is supported so multiple commands can be sent with a single write operation by the client, it is not needed to read the server reply in order to issue the next command. All the replies can be read at the end.

    Usually Redis server and client will have a very fast link so this is not very important to support this feature in a client implementation, still if an application needs to issue a very large number of commands in short -time to use pipelining can be much faster.

    +time to use pipelining can be much faster. +

    From 9eb00f21f0afd16b311daa0795b1d45ca7da8874 Mon Sep 17 00:00:00 2001 From: antirez Date: Tue, 24 Mar 2009 14:22:42 +0100 Subject: [PATCH 13/13] protocol fix in SORT reply with null elements --- redis.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redis.c b/redis.c index 5174b333..f432825f 100644 --- a/redis.c +++ b/redis.c @@ -2772,7 +2772,7 @@ static void sortCommand(redisClient *c) { if (sop->type == REDIS_SORT_GET) { if (!val || val->type != REDIS_STRING) { - addReply(c,shared.minus1); + addReply(c,shared.nullbulk); } else { addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n", sdslen(val->ptr)));