提交 0c9ca0e1 编写于 作者: L Ludovico Magnocavallo

git mess :)

*.o
*.rdb
redis-cli
redis-server
redis-benchmark
doc-tools
mkrelease.sh
release
...@@ -23,7 +23,7 @@ anet.o: anet.c anet.h ...@@ -23,7 +23,7 @@ anet.o: anet.c anet.h
benchmark.o: benchmark.c ae.h anet.h sds.h adlist.h benchmark.o: benchmark.c ae.h anet.h sds.h adlist.h
dict.o: dict.c dict.h dict.o: dict.c dict.h
redis-cli.o: redis-cli.c anet.h sds.h adlist.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 sds.o: sds.c sds.h
sha1.o: sha1.c sha1.h sha1.o: sha1.c sha1.h
zmalloc.o: zmalloc.c zmalloc.o: zmalloc.c
......
BETA 8 TODO - Protocol changes as discussed in the Redis group
- keys expire - keys expire
- sunion ssub - sunion ssub
- write integers in a special way on disk (and on memory?) - write integers in a special way on disk (and on memory?)
...@@ -6,12 +6,4 @@ BETA 8 TODO ...@@ -6,12 +6,4 @@ BETA 8 TODO
- network layer stresser in test in demo - network layer stresser in test in demo
- maxclients directive - maxclients directive
- check 'server.dirty' everywere - check 'server.dirty' everywere
- replication tests - replication automated 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
...@@ -34,7 +34,7 @@ So Redis offers more features:<br/><br/><ul><li> Keys can store different data t ...@@ -34,7 +34,7 @@ So Redis offers more features:<br/><br/><ul><li> Keys can store different data t
<ul><li> We wrote a <a href="http://retwis.antirez.com" target="_blank">simple Twitter Clone</a> 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 <b>much</b> harder.</li></ul> <ul><li> We wrote a <a href="http://retwis.antirez.com" target="_blank">simple Twitter Clone</a> 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 <b>much</b> harder.</li></ul>
<ul><li> 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.</li></ul> <ul><li> 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.</li></ul>
<ul><li> <b>So what is Redis really about?</b> The User interface with the programmer. Redis aims to export to the programmer the right tools to model a wide range of problems. <b>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</b>.</li></ul> <ul><li> <b>So what is Redis really about?</b> The User interface with the programmer. Redis aims to export to the programmer the right tools to model a wide range of problems. <b>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</b>.</li></ul>
<h1><a name="Isn't this key-value thing just hype?">Isn't this key-value thing just hype?</a></h1>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.<br/><br/>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?).<br/><br/>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.<h1><a name="Can I backup a Redis DB while the server is working?">Can I backup a Redis DB while the server is working?</a></h1>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 <i>cp</i> 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.<h1><a name="What's the Redis memory footprint?">What's the Redis memory footprint?</a></h1>Worst case scenario: 1 Million keys with the key being the natural numbers from 0 to 999999 and the string &quot;Hello World&quot; 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.<br/><br/>With large keys/values the ratio is much better of course.<br/><br/>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.<h1><a name="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?">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?</a></h1>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.<br/><br/>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.<br/><br/>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.<h1><a name="Ok but I absolutely need to have a DB larger than memory, still I need the Redis features">Ok but I absolutely need to have a DB larger than memory, still I need the Redis features</a></h1>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.<h1><a name="I have an empty Redis server but INFO and logs are reporting megabytes of memory in use!">I have an empty Redis server but INFO and logs are reporting megabytes of memory in use!</a></h1>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.<h1><a name="What happens if Redis runs out of memory?">What happens if Redis runs out of memory?</a></h1>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.<br/><br/>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.<br/><br/>Update: redis SVN is able to know how much memory it is using and report it via the <a href="InfoCommand.html">INFO</a> command.<h1><a name="What Redis means actually?">What Redis means actually?</a></h1>Redis means two things: <h1><a name="Isn't this key-value thing just hype?">Isn't this key-value thing just hype?</a></h1>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.<br/><br/>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?).<br/><br/>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.<h1><a name="Can I backup a Redis DB while the server is working?">Can I backup a Redis DB while the server is working?</a></h1>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 <i>cp</i> 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.<h1><a name="What's the Redis memory footprint?">What's the Redis memory footprint?</a></h1>Worst case scenario: 1 Million keys with the key being the natural numbers from 0 to 999999 and the string &quot;Hello World&quot; 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.<br/><br/>With large keys/values the ratio is much better of course.<br/><br/>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.<h1><a name="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?">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?</a></h1>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.<br/><br/>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.<br/><br/>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.<h1><a name="Ok but I absolutely need to have a DB larger than memory, still I need the Redis features">Ok but I absolutely need to have a DB larger than memory, still I need the Redis features</a></h1>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).<br/><br/>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.<h1><a name="I have an empty Redis server but INFO and logs are reporting megabytes of memory in use!">I have an empty Redis server but INFO and logs are reporting megabytes of memory in use!</a></h1>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.<h1><a name="What happens if Redis runs out of memory?">What happens if Redis runs out of memory?</a></h1>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.<br/><br/>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.<br/><br/>Update: redis SVN is able to know how much memory it is using and report it via the <a href="InfoCommand.html">INFO</a> command.<h1><a name="What Redis means actually?">What Redis means actually?</a></h1>Redis means two things:
<ul><li> it's a joke on the word Redistribute (instead to use just a Relational DB redistribute your workload among Redis servers)</li><li> it means REmote DIctionary Server</li></ul> <ul><li> it's a joke on the word Redistribute (instead to use just a Relational DB redistribute your workload among Redis servers)</li><li> it means REmote DIctionary Server</li></ul>
<h1><a name="Why did you started the Redis project?">Why did you started the Redis project?</a></h1>In order to scale <a href="http://lloogg.com" target="_blank">LLOOGG</a>. 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. <h1><a name="Why did you started the Redis project?">Why did you started the Redis project?</a></h1>In order to scale <a href="http://lloogg.com" target="_blank">LLOOGG</a>. 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.
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
<div id="pagecontent"> <div id="pagecontent">
<div class="index"> <div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. --> <!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>ProtocolSpecification: Contents</b><br>&nbsp;&nbsp;<a href="#Protocol Specification">Protocol Specification</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Networking layer">Networking layer</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Simple INLINE commands">Simple INLINE commands</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Bulk commands">Bulk commands</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Bulk replies">Bulk replies</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Bulk reply error reporting">Bulk reply error reporting</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Multi-Bulk replies">Multi-Bulk replies</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Nil elements in Multi-Bulk replies">Nil elements in Multi-Bulk replies</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Multi-Bulk replies errors">Multi-Bulk replies errors</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Status code reply">Status code reply</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Integer reply">Integer reply</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Single line reply">Single line reply</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Multiple commands and pipelining">Multiple commands and pipelining</a> <b>ProtocolSpecification: Contents</b><br>&nbsp;&nbsp;<a href="#Protocol Specification">Protocol Specification</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Networking layer">Networking layer</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Simple INLINE commands">Simple INLINE commands</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Bulk commands">Bulk commands</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Bulk replies">Bulk replies</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Multi-Bulk replies">Multi-Bulk replies</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Nil elements in Multi-Bulk replies">Nil elements in Multi-Bulk replies</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Single line reply">Single line reply</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Integer reply">Integer reply</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Multiple commands and pipelining">Multiple commands and pipelining</a>
</div> </div>
<h1 class="wikiname">ProtocolSpecification</h1> <h1 class="wikiname">ProtocolSpecification</h1>
...@@ -35,14 +35,12 @@ terminated by &quot;\r\n&quot; (CRLF).<h2><a name="Simple INLINE commands">Simpl ...@@ -35,14 +35,12 @@ terminated by &quot;\r\n&quot; (CRLF).<h2><a name="Simple INLINE commands">Simpl
server/client chat (the server chat starts with S:, the client chat with C:)<br/><br/><pre class="codeblock python" name="code"> server/client chat (the server chat starts with S:, the client chat with C:)<br/><br/><pre class="codeblock python" name="code">
C: PING C: PING
S: +PONG S: +PONG
</pre>An inline command is a CRLF-terminated string sent to the client. The server </pre>An inline command is a CRLF-terminated string sent to the client. The server can reply to commands in different ways:
usually replies to inline commands with a single line that can be a number <ul><li> With an error message (the first byte of the reply will be &quot;-&quot;)</li><li> With a single line reply (the first byte of the reply will be &quot;+)</li><li> With bulk data (the first byte of the reply will be &quot;$&quot;)</li><li> With multi-bulk data, a list of values (the first byte of the reply will be &quot;<code name="code" class="python">*</code>&quot;)</li><li> With an integer number (the first byte of the reply will be &quot;:&quot;)</li></ul>
or a return code.<br/><br/>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 The following is another example of an INLINE command returning an integer:<br/><br/><pre class="codeblock python python" name="code">
reply is a &quot;+&quot; then the command succeeded, if it is a &quot;-&quot; then the following
part of the string is an error.<br/><br/>The following is another example of an INLINE command returning an integer:<br/><br/><pre class="codeblock python python" name="code">
C: EXISTS somekey C: EXISTS somekey
S: 0 S: :0
</pre>Since 'somekey' does not exist the server returned '0'.<br/><br/>Note that the EXISTS command takes one argument. Arguments are separated </pre>Since 'somekey' does not exist the server returned ':0'.<br/><br/>Note that the EXISTS command takes one argument. Arguments are separated
simply by spaces.<h2><a name="Bulk commands">Bulk commands</a></h2>A bulk command is exactly like an inline command, but the last argument simply by spaces.<h2><a name="Bulk commands">Bulk commands</a></h2>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. of the command must be a stream of bytes in order to send data to the server.
the &quot;SET&quot; command is a bulk command, see the following example:<br/><br/><pre class="codeblock python python python" name="code"> the &quot;SET&quot; command is a bulk command, see the following example:<br/><br/><pre class="codeblock python python python" name="code">
...@@ -58,82 +56,60 @@ sent by the client in the above sample:<br/><br/><blockquote>&quot;SET mykey 6\r ...@@ -58,82 +56,60 @@ sent by the client in the above sample:<br/><br/><blockquote>&quot;SET mykey 6\r
<h2><a name="Bulk replies">Bulk replies</a></h2>The server may reply to an inline or bulk command with a bulk reply. See <h2><a name="Bulk replies">Bulk replies</a></h2>The server may reply to an inline or bulk command with a bulk reply. See
the following example:<br/><br/><pre class="codeblock python python python python" name="code"> the following example:<br/><br/><pre class="codeblock python python python python" name="code">
C: GET mykey C: GET mykey
S: 6 S: $6
S: foobar S: foobar
</pre>A bulk reply is very similar to the last argument of a bulk command. The </pre>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 server sends as the first line a &quot;$&quot; byte followed by the number of bytes
followed by CRLF, then the bytes are sent followed by additional two bytes of the actual reply followed by CRLF, then the bytes are sent followed by
for the final CRLF. The exact sequence sent by the server is:<br/><br/><blockquote>&quot;6\r\nfoobar\r\n&quot;</blockquote> additional two bytes for the final CRLF. The exact sequence sent by the
server is:<br/><br/><blockquote>&quot;$6\r\nfoobar\r\n&quot;</blockquote>
If the requested value does not exist the bulk reply will use the special 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. value -1 as data length, example:<br/><br/><pre class="codeblock python python python python python" name="code">
This is an example:<br/><br/><pre class="codeblock python python python python python" name="code">
C: GET nonexistingkey C: GET nonexistingkey
S: nil S: $-1
</pre>The client library API should not return an empty string, but a nil object. </pre>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 For example a Ruby library should return 'nil' while a C library should return
NULL.<h2><a name="Bulk reply error reporting">Bulk reply error reporting</a></h2>Bulk replies can signal errors, for example trying to use GET against a list NULL, and so forth.<h2><a name="Multi-Bulk replies">Multi-Bulk replies</a></h2>Commands similar to LRANGE needs to return multiple values (every element
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:<br/><br/><pre class="codeblock python python python python python python" name="code">
S: GET alistkey
S: -38
S: -ERR Requested element is not a string
</pre>-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.<br/><br/>The following commands reply with a bulk reply: GET, KEYS, LINDEX, LPOP, RPOP<h2><a name="Multi-Bulk replies">Multi-Bulk replies</a></h2>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, 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. prefixed by an initial line indicating how many bulk writes will follow.
Example:<br/><br/><pre class="codeblock python python python python python python python" name="code"> The first byte of a multi bulk reply is always <code name="code" class="python">*</code>. Example:<br/><br/><pre class="codeblock python python python python python python" name="code">
C: LRANGE mylist 0 3 C: LRANGE mylist 0 3
S: 4 S: *4
S: 3 S: $3
S: foo S: foo
S: 3 S: $3
S: bar S: bar
S: 5 S: $5
S: Hello S: Hello
S: 5 S: $5
S: World S: World
</pre>The first line the server sent is &quot;4\r\n&quot; in order to specify that four bulk </pre>The first line the server sent is &quot;<b>4\r\n&quot; in order to specify that four bulk
write will follow. Then every bulk write is transmitted.<br/><br/>If the specified key does not exist instead of the number of elements in the write will follow. Then every bulk write is transmitted.<br/><br/>If the specified key does not exist instead of the number of elements in the
list, the special value 'nil' is sent. Example:<br/><br/><pre class="codeblock python python python python python python python python" name="code"> list, the special value -1 is sent as count. Example:<br/><br/><pre class="codeblock python python python python python python python" name="code">
C: LRANGE nokey 0 1 C: LRANGE nokey 0 1
S: nil S: *-1
</pre>A client library API SHOULD return a nil object and not an empty list when this </pre>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.<h2><a name="Nil elements in Multi-Bulk replies">Nil elements in Multi-Bulk replies</a></h2>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 <i>pattern</i> option when the specified key is missing. Example of a multi bulk reply containing an empty element:<br/><br/><pre class="codeblock python python python python python python python python python" name="code"> happens. This makes possible to distinguish between empty list and non existing ones.<h2><a name="Nil elements in Multi-Bulk replies">Nil elements in Multi-Bulk replies</a></h2>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 <i>pattern</i> option when the specified key is missing. Example of a multi bulk reply containing an empty element:<br/><br/><pre class="codeblock python python python python python python python python" name="code">
S: 3 S: *3
S: 3 S: $3
S: foo S: foo
S: -1 S: $-1
S: 3 S: $3
S: bar S: bar
</pre>The second element is nul. The client library should return something like this:<br/><br/><pre class="codeblock python python python python python python python python python python" name="code"> </pre>The second element is nul. The client library should return something like this:<br/><br/><pre class="codeblock python python python python python python python python python" name="code">
[&quot;foo&quot;,nil,&quot;bar&quot;] [&quot;foo&quot;,nil,&quot;bar&quot;]
</pre><h2><a name="Multi-Bulk replies errors">Multi-Bulk replies errors</a></h2>Like bulk reply errors Multi-bulk reply errors are reported using a negative </pre><h2><a name="Single line reply">Single line reply</a></h2>As already seen a single line reply is in the form of a single line string
count. Example:<br/><br/><pre class="codeblock python python python python python python python python python python python" name="code"> starting with &quot;+&quot; terminated by &quot;\r\n&quot;. For example:<br/><br/><pre class="codeblock python python python python python python python python python python" name="code">
C: LRANGE stringkey 0 1
S: -38
S: -ERR Requested element is not a string
</pre>The following commands reply with a multi-bulk reply: LRANGE, LINTER<br/><br/>Check the Bulk replies errors section for more information.<h2><a name="Status code reply">Status code reply</a></h2>As already seen a status code reply is in the form of a single line string
terminated by &quot;\r\n&quot;. For example:<br/><br/><pre class="codeblock python python python python python python python python python python python python" name="code">
+OK +OK
</pre>and<br/><br/><pre class="codeblock python python python python python python python python python python python python python" name="code"> </pre>The client library should return everything after the &quot;+&quot;, that is, the string &quot;OK&quot; in the example.<br/><br/>The following commands reply with a status code reply:
-ERR no suck key PING, SET, SELECT, SAVE, BGSAVE, SHUTDOWN, RENAME, LPUSH, RPUSH, LSET, LTRIM<h2><a name="Integer reply">Integer reply</a></h2>This type of reply is just a CRLF terminated string representing an integer, prefixed by a &quot;:&quot; byte. For example &quot;:0\r\n&quot;, or &quot;:1000\r\n&quot; are integer replies.<br/><br/>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.<br/><br/>Some commands like EXISTS will return 1 for true and 0 for false.<br/><br/>Other commands like SADD, SREM and SETNX will return 1 if the operation was actually done, 0 otherwise.<br/><br/>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<h2><a name="Multiple commands and pipelining">Multiple commands and pipelining</a></h2>A client can use the same connection in order to issue multiple commands.
</pre>are two examples of status code replies. The first character of a status code reply is always &quot;+&quot; or &quot;-&quot;.<br/><br/>The following commands reply with a status code reply:
PING, SET, SELECT, SAVE, BGSAVE, SHUTDOWN, RENAME, LPUSH, RPUSH, LSET, LTRIM<h2><a name="Integer reply">Integer reply</a></h2>This type of reply is just a CRLF terminated string representing an integer. For example &quot;0\r\n&quot;, or &quot;1000\r\n&quot; are integer replies.<br/><br/>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.<br/><br/>Some commands like EXISTS will return 1 for true and 0 for false.<br/><br/>Other commands like SADD, SREM and SETNX will return 1 if the operation was actually done, 0 otherwise, and <b>a negative value</b> if the operation is invalid (for example SADD against a non-set value), accordingly to this table:
<pre class="codeblock python python python python python python python python python python python python python python" name="code">
-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
</pre>
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.<br/><br/>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<br/><br/>The commands that will never return a negative integer (commands that can't fail) are: INCR, DECR, INCRBY, DECRBY, LASTSAVE, EXISTS, SETNX, DEL, DBSIZE.<h2><a name="Single line reply">Single line reply</a></h2>This replies are just single line strings terminated by CRLF. Only two commands reply in this way currently, RANDOMKEY and TYPE.<h2><a name="Multiple commands and pipelining">Multiple commands and pipelining</a></h2>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 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 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.<br/><br/>Usually Redis server and client will have a very fast link so this is not in order to issue the next command. All the replies can be read at the end.<br/><br/>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 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 if an application needs to issue a very large number of commands in short
time to use pipelining can be much faster.<br/><br/> time to use pipelining can be much faster.
</b>
</div> </div>
</div> </div>
......
...@@ -40,11 +40,6 @@ ...@@ -40,11 +40,6 @@
#define REDIS_CMD_INLINE 1 #define REDIS_CMD_INLINE 1
#define REDIS_CMD_BULK 2 #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) #define REDIS_NOTUSED(V) ((void) V)
...@@ -60,54 +55,56 @@ struct redisCommand { ...@@ -60,54 +55,56 @@ struct redisCommand {
}; };
static struct redisCommand cmdTable[] = { static struct redisCommand cmdTable[] = {
{"get",2,REDIS_CMD_INLINE|REDIS_CMD_BULKREPLY}, {"get",2,REDIS_CMD_INLINE},
{"set",3,REDIS_CMD_BULK|REDIS_CMD_RETCODEREPLY}, {"set",3,REDIS_CMD_BULK},
{"setnx",3,REDIS_CMD_BULK|REDIS_CMD_INTREPLY}, {"setnx",3,REDIS_CMD_BULK},
{"del",2,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, {"del",2,REDIS_CMD_INLINE},
{"exists",2,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, {"exists",2,REDIS_CMD_INLINE},
{"incr",2,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, {"incr",2,REDIS_CMD_INLINE},
{"decr",2,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, {"decr",2,REDIS_CMD_INLINE},
{"rpush",3,REDIS_CMD_BULK|REDIS_CMD_RETCODEREPLY}, {"rpush",3,REDIS_CMD_BULK},
{"lpush",3,REDIS_CMD_BULK|REDIS_CMD_RETCODEREPLY}, {"lpush",3,REDIS_CMD_BULK},
{"rpop",2,REDIS_CMD_INLINE|REDIS_CMD_BULKREPLY}, {"rpop",2,REDIS_CMD_INLINE},
{"lpop",2,REDIS_CMD_INLINE|REDIS_CMD_BULKREPLY}, {"lpop",2,REDIS_CMD_INLINE},
{"llen",2,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, {"llen",2,REDIS_CMD_INLINE},
{"lindex",3,REDIS_CMD_INLINE|REDIS_CMD_BULKREPLY}, {"lindex",3,REDIS_CMD_INLINE},
{"lset",4,REDIS_CMD_BULK|REDIS_CMD_RETCODEREPLY}, {"lset",4,REDIS_CMD_BULK},
{"lrange",4,REDIS_CMD_INLINE|REDIS_CMD_MULTIBULKREPLY}, {"lrange",4,REDIS_CMD_INLINE},
{"ltrim",4,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, {"ltrim",4,REDIS_CMD_INLINE},
{"lrem",4,REDIS_CMD_BULK|REDIS_CMD_INTREPLY}, {"lrem",4,REDIS_CMD_BULK},
{"sadd",3,REDIS_CMD_BULK|REDIS_CMD_INTREPLY}, {"sadd",3,REDIS_CMD_BULK},
{"srem",3,REDIS_CMD_BULK|REDIS_CMD_INTREPLY}, {"srem",3,REDIS_CMD_BULK},
{"sismember",3,REDIS_CMD_BULK|REDIS_CMD_INTREPLY}, {"sismember",3,REDIS_CMD_BULK},
{"scard",2,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, {"scard",2,REDIS_CMD_INLINE},
{"sinter",-2,REDIS_CMD_INLINE|REDIS_CMD_MULTIBULKREPLY}, {"sinter",-2,REDIS_CMD_INLINE},
{"sinterstore",-3,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, {"sinterstore",-3,REDIS_CMD_INLINE},
{"smembers",2,REDIS_CMD_INLINE|REDIS_CMD_MULTIBULKREPLY}, {"smembers",2,REDIS_CMD_INLINE},
{"incrby",3,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, {"incrby",3,REDIS_CMD_INLINE},
{"decrby",3,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, {"decrby",3,REDIS_CMD_INLINE},
{"randomkey",1,REDIS_CMD_INLINE|REDIS_CMD_SINGLELINEREPLY}, {"randomkey",1,REDIS_CMD_INLINE},
{"select",2,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, {"select",2,REDIS_CMD_INLINE},
{"move",3,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, {"move",3,REDIS_CMD_INLINE},
{"rename",3,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, {"rename",3,REDIS_CMD_INLINE},
{"renamenx",3,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, {"renamenx",3,REDIS_CMD_INLINE},
{"keys",2,REDIS_CMD_INLINE|REDIS_CMD_BULKREPLY}, {"keys",2,REDIS_CMD_INLINE},
{"dbsize",1,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, {"dbsize",1,REDIS_CMD_INLINE},
{"ping",1,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, {"ping",1,REDIS_CMD_INLINE},
{"echo",2,REDIS_CMD_BULK|REDIS_CMD_BULKREPLY}, {"echo",2,REDIS_CMD_BULK},
{"save",1,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, {"save",1,REDIS_CMD_INLINE},
{"bgsave",1,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, {"bgsave",1,REDIS_CMD_INLINE},
{"shutdown",1,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, {"shutdown",1,REDIS_CMD_INLINE},
{"lastsave",1,REDIS_CMD_INLINE|REDIS_CMD_INTREPLY}, {"lastsave",1,REDIS_CMD_INLINE},
{"type",2,REDIS_CMD_INLINE|REDIS_CMD_SINGLELINEREPLY}, {"type",2,REDIS_CMD_INLINE},
{"flushdb",1,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, {"flushdb",1,REDIS_CMD_INLINE},
{"flushall",1,REDIS_CMD_INLINE|REDIS_CMD_RETCODEREPLY}, {"flushall",1,REDIS_CMD_INLINE},
{"sort",-2,REDIS_CMD_INLINE|REDIS_CMD_MULTIBULKREPLY}, {"sort",-2,REDIS_CMD_INLINE},
{"info",1,REDIS_CMD_INLINE|REDIS_CMD_BULKREPLY}, {"info",1,REDIS_CMD_INLINE},
{"mget",-2,REDIS_CMD_INLINE|REDIS_CMD_MULTIBULKREPLY}, {"mget",-2,REDIS_CMD_INLINE},
{NULL,0,0} {NULL,0,0}
}; };
static int cliReadReply(int fd);
static struct redisCommand *lookupCommand(char *name) { static struct redisCommand *lookupCommand(char *name) {
int j = 0; int j = 0;
while(cmdTable[j].name != NULL) { while(cmdTable[j].name != NULL) {
...@@ -135,11 +132,13 @@ static sds cliReadLine(int fd) { ...@@ -135,11 +132,13 @@ static sds cliReadLine(int fd) {
while(1) { while(1) {
char c; char c;
ssize_t ret;
if (read(fd,&c,1) == -1) { ret = read(fd,&c,1);
if (ret == -1) {
sdsfree(line); sdsfree(line);
return NULL; return NULL;
} else if (c == '\n') { } else if ((ret == 0) || (c == '\n')) {
break; break;
} else { } else {
line = sdscatlen(line,&c,1); line = sdscatlen(line,&c,1);
...@@ -148,38 +147,26 @@ static sds cliReadLine(int fd) { ...@@ -148,38 +147,26 @@ static sds cliReadLine(int fd) {
return sdstrim(line,"\r\n"); return sdstrim(line,"\r\n");
} }
static int cliReadInlineReply(int fd, int type) { static int cliReadSingleLineReply(int fd) {
sds reply = cliReadLine(fd); sds reply = cliReadLine(fd);
if (reply == NULL) return 1; if (reply == NULL) return 1;
printf("%s\n", reply); 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; return 0;
} }
static int cliReadBulkReply(int fd, int multibulk) { static int cliReadBulkReply(int fd) {
sds replylen = cliReadLine(fd); sds replylen = cliReadLine(fd);
char *reply, crlf[2]; char *reply, crlf[2];
int bulklen, error = 0; int bulklen;
if (replylen == NULL) return 1; if (replylen == NULL) return 1;
if (strcmp(replylen,"nil") == 0) {
sdsfree(replylen);
printf("(nil)\n");
return 0;
}
bulklen = atoi(replylen); bulklen = atoi(replylen);
if (multibulk && bulklen == -1) { if (bulklen == -1) {
sdsfree(replylen); sdsfree(replylen);
printf("(nil)"); printf("(nil)");
return 0; return 0;
} }
if (bulklen < 0) {
bulklen = -bulklen;
error = 1;
}
reply = zmalloc(bulklen); reply = zmalloc(bulklen);
anetRead(fd,reply,bulklen); anetRead(fd,reply,bulklen);
anetRead(fd,crlf,2); anetRead(fd,crlf,2);
...@@ -187,10 +174,10 @@ static int cliReadBulkReply(int fd, int multibulk) { ...@@ -187,10 +174,10 @@ static int cliReadBulkReply(int fd, int multibulk) {
zfree(reply); zfree(reply);
return 1; return 1;
} }
if (!multibulk && isatty(fileno(stdout)) && reply[bulklen-1] != '\n') if (isatty(fileno(stdout)) && reply[bulklen-1] != '\n')
printf("\n"); printf("\n");
zfree(reply); zfree(reply);
return error; return 0;
} }
static int cliReadMultiBulkReply(int fd) { static int cliReadMultiBulkReply(int fd) {
...@@ -198,21 +185,45 @@ static int cliReadMultiBulkReply(int fd) { ...@@ -198,21 +185,45 @@ static int cliReadMultiBulkReply(int fd) {
int elements, c = 1; int elements, c = 1;
if (replylen == NULL) return 1; if (replylen == NULL) return 1;
if (strcmp(replylen,"nil") == 0) { elements = atoi(replylen);
if (elements == -1) {
sdsfree(replylen); sdsfree(replylen);
printf("(nil)\n"); printf("(nil)\n");
return 0; return 0;
} }
elements = atoi(replylen); if (elements == 0) {
printf("(empty list or set)\n");
}
while(elements--) { while(elements--) {
printf("%d. ", c); printf("%d. ", c);
if (cliReadBulkReply(fd,1)) return 1; if (cliReadReply(fd)) return 1;
printf("\n");
c++; c++;
} }
return 0; 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) { static int cliSendCommand(int argc, char **argv) {
struct redisCommand *rc = lookupCommand(argv[0]); struct redisCommand *rc = lookupCommand(argv[0]);
int fd, j, retval = 0; int fd, j, retval = 0;
...@@ -245,17 +256,7 @@ static int cliSendCommand(int argc, char **argv) { ...@@ -245,17 +256,7 @@ static int cliSendCommand(int argc, char **argv) {
cmd = sdscat(cmd,"\r\n"); cmd = sdscat(cmd,"\r\n");
} }
anetWrite(fd,cmd,sdslen(cmd)); anetWrite(fd,cmd,sdslen(cmd));
if (rc->flags & REDIS_CMD_INTREPLY) { retval = cliReadReply(fd);
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);
}
if (retval) { if (retval) {
close(fd); close(fd);
return retval; return retval;
......
此差异已折叠。
...@@ -4,6 +4,10 @@ ...@@ -4,6 +4,10 @@
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized. # Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
daemonize no 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 # Accept connections on the specified port, default is 6379
port 6379 port 6379
......
...@@ -124,16 +124,16 @@ proc main {server port} { ...@@ -124,16 +124,16 @@ proc main {server port} {
puts -nonewline $fd "SET k1 4\r\nxyzk\r\nGET k1\r\nPING\r\n" puts -nonewline $fd "SET k1 4\r\nxyzk\r\nGET k1\r\nPING\r\n"
flush $fd flush $fd
set res {} set res {}
append res [string match +OK* [redis_read_retcode $fd]] append res [string match OK* [redis_read_reply $fd]]
append res [redis_bulk_read $fd] append res [redis_read_reply $fd]
append res [string match +PONG* [redis_read_retcode $fd]] append res [string match PONG* [redis_read_reply $fd]]
format $res format $res
} {1xyzk1} } {1xyzk1}
test {Non existing command} { test {Non existing command} {
puts -nonewline $fd "foo\r\n" puts -nonewline $fd "foo\r\n"
flush $fd flush $fd
string match -ERR* [redis_read_retcode $fd] string match ERR* [redis_read_reply $fd]
} {1} } {1}
test {Basic LPUSH, RPUSH, LLENGTH, LINDEX} { test {Basic LPUSH, RPUSH, LLENGTH, LINDEX} {
...@@ -181,19 +181,19 @@ proc main {server port} { ...@@ -181,19 +181,19 @@ proc main {server port} {
redis_del $fd mylist redis_del $fd mylist
redis_set $fd mylist foobar redis_set $fd mylist foobar
redis_llen $fd mylist redis_llen $fd mylist
} {-2} } {ERR*}
test {LINDEX against non-list value error} { test {LINDEX against non-list value error} {
redis_lindex $fd mylist 0 redis_lindex $fd mylist 0
} {*ERROR*} } {ERR*}
test {LPUSH against non-list value error} { test {LPUSH against non-list value error} {
redis_lpush $fd mylist 0 redis_lpush $fd mylist 0
} {-ERR*} } {ERR*}
test {RPUSH against non-list value error} { test {RPUSH against non-list value error} {
redis_rpush $fd mylist 0 redis_rpush $fd mylist 0
} {-ERR*} } {ERR*}
test {RENAME basic usage} { test {RENAME basic usage} {
redis_set $fd mykey hello redis_set $fd mykey hello
...@@ -236,11 +236,11 @@ proc main {server port} { ...@@ -236,11 +236,11 @@ proc main {server port} {
test {RENAME against non existing source key} { test {RENAME against non existing source key} {
redis_rename $fd nokey foobar redis_rename $fd nokey foobar
} {-ERR*} } {ERR*}
test {RENAME where source and dest key is the same} { test {RENAME where source and dest key is the same} {
redis_rename $fd mykey mykey redis_rename $fd mykey mykey
} {-ERR*} } {ERR*}
test {DEL all keys again (DB 0)} { test {DEL all keys again (DB 0)} {
foreach key [redis_keys $fd *] { foreach key [redis_keys $fd *] {
...@@ -309,7 +309,7 @@ proc main {server port} { ...@@ -309,7 +309,7 @@ proc main {server port} {
test {LPOP against non list value} { test {LPOP against non list value} {
redis_set $fd notalist foo redis_set $fd notalist foo
redis_lpop $fd notalist redis_lpop $fd notalist
} {*ERROR*against*} } {ERR*kind*}
test {Mass LPUSH/LPOP} { test {Mass LPUSH/LPOP} {
set sum 0 set sum 0
...@@ -363,16 +363,16 @@ proc main {server port} { ...@@ -363,16 +363,16 @@ proc main {server port} {
test {LSET out of range index} { test {LSET out of range index} {
redis_lset $fd mylist 10 foo redis_lset $fd mylist 10 foo
} {-ERR*range*} } {ERR*range*}
test {LSET against non existing key} { test {LSET against non existing key} {
redis_lset $fd nosuchkey 10 foo redis_lset $fd nosuchkey 10 foo
} {-ERR*key*} } {ERR*key*}
test {LSET against non list value} { test {LSET against non list value} {
redis_set $fd nolist foobar redis_set $fd nolist foobar
redis_lset $fd nolist 0 foo redis_lset $fd nolist 0 foo
} {-ERR*value*} } {ERR*value*}
test {SADD, SCARD, SISMEMBER, SMEMBERS basics} { test {SADD, SCARD, SISMEMBER, SMEMBERS basics} {
redis_sadd $fd myset foo redis_sadd $fd myset foo
...@@ -391,7 +391,7 @@ proc main {server port} { ...@@ -391,7 +391,7 @@ proc main {server port} {
test {SADD against non set} { test {SADD against non set} {
redis_sadd $fd mylist foo redis_sadd $fd mylist foo
} {-2} } {ERR*kind*}
test {SREM basics} { test {SREM basics} {
redis_sadd $fd myset ciao redis_sadd $fd myset ciao
...@@ -431,7 +431,7 @@ proc main {server port} { ...@@ -431,7 +431,7 @@ proc main {server port} {
redis_set $fd myemptykey {} redis_set $fd myemptykey {}
redis_set $fd mynormalkey {blablablba} redis_set $fd mynormalkey {blablablba}
redis_save $fd redis_save $fd
} {+OK} } {OK}
test {Create a random list} { test {Create a random list} {
set tosort {} set tosort {}
...@@ -606,225 +606,255 @@ proc redis_readnl {fd len} { ...@@ -606,225 +606,255 @@ proc redis_readnl {fd len} {
return $buf return $buf
} }
proc redis_bulk_read {fd {multi 0}} { proc redis_bulk_read {fd} {
set count [redis_read_integer $fd] set count [redis_read_line $fd]
if {$count eq {nil}} return {} if {$count == -1} return {}
if {$multi && $count == -1} return {} set buf [redis_readnl $fd $count]
set len [expr {abs($count)}]
set buf [redis_readnl $fd $len]
if {$count < 0} {return "***ERROR*** $buf"}
return $buf return $buf
} }
proc redis_multi_bulk_read fd { proc redis_multi_bulk_read fd {
set count [redis_read_integer $fd] set count [redis_read_line $fd]
if {$count eq {nil}} return {} if {$count == -1} return {}
if {$count < 0} {
set len [expr {abs($count)}]
set buf [redis_readnl $fd $len]
return "***ERROR*** $buf"
}
set l {} set l {}
for {set i 0} {$i < $count} {incr i} { for {set i 0} {$i < $count} {incr i} {
lappend l [redis_bulk_read $fd 1] lappend l [redis_read_reply $fd]
} }
return $l return $l
} }
proc redis_read_retcode fd { proc redis_read_line fd {
set retcode [string trim [gets $fd]] string trim [gets $fd]
# puts "S: $retcode"
return $retcode
} }
proc redis_read_integer fd { proc redis_read_reply fd {
string trim [gets $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 ### ### Actual API ###
proc redis_set {fd key val} { proc redis_set {fd key val} {
redis_writenl $fd "set $key [string length $val]\r\n$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} { proc redis_setnx {fd key val} {
redis_writenl $fd "setnx $key [string length $val]\r\n$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} { proc redis_get {fd key} {
redis_writenl $fd "get $key" redis_writenl $fd "get $key"
redis_bulk_read $fd redis_read_reply $fd
} }
proc redis_select {fd id} { proc redis_select {fd id} {
redis_writenl $fd "select $id" redis_writenl $fd "select $id"
redis_read_retcode $fd redis_read_reply $fd
} }
proc redis_move {fd key id} { proc redis_move {fd key id} {
redis_writenl $fd "move $key $id" redis_writenl $fd "move $key $id"
redis_read_integer $fd redis_read_reply $fd
} }
proc redis_del {fd key} { proc redis_del {fd key} {
redis_writenl $fd "del $key" redis_writenl $fd "del $key"
redis_read_integer $fd redis_read_reply $fd
} }
proc redis_keys {fd pattern} { proc redis_keys {fd pattern} {
redis_writenl $fd "keys $pattern" redis_writenl $fd "keys $pattern"
split [redis_bulk_read $fd] split [redis_read_reply $fd]
} }
proc redis_dbsize {fd} { proc redis_dbsize {fd} {
redis_writenl $fd "dbsize" redis_writenl $fd "dbsize"
redis_read_integer $fd redis_read_reply $fd
} }
proc redis_incr {fd key} { proc redis_incr {fd key} {
redis_writenl $fd "incr $key" redis_writenl $fd "incr $key"
redis_read_integer $fd redis_read_reply $fd
} }
proc redis_decr {fd key} { proc redis_decr {fd key} {
redis_writenl $fd "decr $key" redis_writenl $fd "decr $key"
redis_read_integer $fd redis_read_reply $fd
} }
proc redis_exists {fd key} { proc redis_exists {fd key} {
redis_writenl $fd "exists $key" redis_writenl $fd "exists $key"
redis_read_integer $fd redis_read_reply $fd
} }
proc redis_lpush {fd key val} { proc redis_lpush {fd key val} {
redis_writenl $fd "lpush $key [string length $val]\r\n$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} { proc redis_rpush {fd key val} {
redis_writenl $fd "rpush $key [string length $val]\r\n$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} { proc redis_llen {fd key} {
redis_writenl $fd "llen $key" redis_writenl $fd "llen $key"
redis_read_integer $fd redis_read_reply $fd
} }
proc redis_scard {fd key} { proc redis_scard {fd key} {
redis_writenl $fd "scard $key" redis_writenl $fd "scard $key"
redis_read_integer $fd redis_read_reply $fd
} }
proc redis_lindex {fd key index} { proc redis_lindex {fd key index} {
redis_writenl $fd "lindex $key $index" redis_writenl $fd "lindex $key $index"
redis_bulk_read $fd redis_read_reply $fd
} }
proc redis_lrange {fd key first last} { proc redis_lrange {fd key first last} {
redis_writenl $fd "lrange $key $first $last" redis_writenl $fd "lrange $key $first $last"
redis_multi_bulk_read $fd redis_read_reply $fd
} }
proc redis_mget {fd args} { proc redis_mget {fd args} {
redis_writenl $fd "mget [join $args]" redis_writenl $fd "mget [join $args]"
redis_multi_bulk_read $fd redis_read_reply $fd
} }
proc redis_sort {fd key {params {}}} { proc redis_sort {fd key {params {}}} {
redis_writenl $fd "sort $key $params" redis_writenl $fd "sort $key $params"
redis_multi_bulk_read $fd redis_read_reply $fd
} }
proc redis_ltrim {fd key first last} { proc redis_ltrim {fd key first last} {
redis_writenl $fd "ltrim $key $first $last" redis_writenl $fd "ltrim $key $first $last"
redis_read_retcode $fd redis_read_reply $fd
} }
proc redis_rename {fd key1 key2} { proc redis_rename {fd key1 key2} {
redis_writenl $fd "rename $key1 $key2" redis_writenl $fd "rename $key1 $key2"
redis_read_retcode $fd redis_read_reply $fd
} }
proc redis_renamenx {fd key1 key2} { proc redis_renamenx {fd key1 key2} {
redis_writenl $fd "renamenx $key1 $key2" redis_writenl $fd "renamenx $key1 $key2"
redis_read_integer $fd redis_read_reply $fd
} }
proc redis_lpop {fd key} { proc redis_lpop {fd key} {
redis_writenl $fd "lpop $key" redis_writenl $fd "lpop $key"
redis_bulk_read $fd redis_read_reply $fd
} }
proc redis_rpop {fd key} { proc redis_rpop {fd key} {
redis_writenl $fd "rpop $key" redis_writenl $fd "rpop $key"
redis_bulk_read $fd redis_read_reply $fd
} }
proc redis_lset {fd key index val} { proc redis_lset {fd key index val} {
redis_writenl $fd "lset $key $index [string length $val]\r\n$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} { proc redis_sadd {fd key val} {
redis_writenl $fd "sadd $key [string length $val]\r\n$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} { proc redis_srem {fd key val} {
redis_writenl $fd "srem $key [string length $val]\r\n$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} { proc redis_sismember {fd key val} {
redis_writenl $fd "sismember $key [string length $val]\r\n$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} { proc redis_sinter {fd args} {
redis_writenl $fd "sinter [join $args]\r\n" redis_writenl $fd "sinter [join $args]"
redis_multi_bulk_read $fd redis_read_reply $fd
} }
proc redis_sinterstore {fd args} { proc redis_sinterstore {fd args} {
redis_writenl $fd "sinterstore [join $args]\r\n" redis_writenl $fd "sinterstore [join $args]"
redis_read_retcode $fd redis_read_reply $fd
} }
proc redis_smembers {fd key} { proc redis_smembers {fd key} {
redis_writenl $fd "smembers $key\r\n" redis_writenl $fd "smembers $key"
redis_multi_bulk_read $fd redis_read_reply $fd
} }
proc redis_echo {fd str} { proc redis_echo {fd str} {
redis_writenl $fd "echo [string length $str]\r\n$str\r\n" redis_writenl $fd "echo [string length $str]\r\n$str"
redis_writenl $fd "smembers $key\r\n" redis_read_reply $fd
} }
proc redis_save {fd} { proc redis_save {fd} {
redis_writenl $fd "save\r\n" redis_writenl $fd "save"
redis_read_retcode $fd redis_read_reply $fd
} }
proc redis_flushall {fd} { proc redis_flushall {fd} {
redis_writenl $fd "flushall\r\n" redis_writenl $fd "flushall"
redis_read_retcode $fd redis_read_reply $fd
} }
proc redis_flushdb {fd} { proc redis_flushdb {fd} {
redis_writenl $fd "flushdb\r\n" redis_writenl $fd "flushdb"
redis_read_retcode $fd redis_read_reply $fd
} }
proc redis_lrem {fd key count val} { proc redis_lrem {fd key count val} {
redis_writenl $fd "lrem $key $count [string length $val]\r\n$val" redis_writenl $fd "lrem $key $count [string length $val]\r\n$val"
redis_read_integer $fd redis_read_reply $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} { if {[llength $argv] == 0} {
main 127.0.0.1 6379 main 127.0.0.1 6379
} elseif {[llength $argv] == 1 && [lindex $argv 0] eq {stress}} {
stress
} else { } else {
main [lindex $argv 0] [lindex $argv 1] main [lindex $argv 0] [lindex $argv 1]
} }
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
void *zmalloc(size_t size); void *zmalloc(size_t size);
void *zrealloc(void *ptr, size_t size); void *zrealloc(void *ptr, size_t size);
void *zfree(void *ptr); void zfree(void *ptr);
char *zstrdup(const char *s); char *zstrdup(const char *s);
size_t zmalloc_used_memory(void); size_t zmalloc_used_memory(void);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册