first commit

This commit is contained in:
antirez 2009-03-22 10:30:00 +01:00
commit ed9b544e10
110 changed files with 13641 additions and 0 deletions

12
BETATESTING.txt Normal file
View File

@ -0,0 +1,12 @@
Hello betatester!
This Redis Server distribution is just a preview, it is by no mean an usable
product, but probably it can already give you some feeling about what the
final release is going to be.
Be aware that if you want to use Redis in production the server may not be perfectly stable or may cotanin unfixed bugs. We did our best to ensure this distribution is of good quality and bug free but the development is currently very fast.
Please send feedbacks to antirez at gmail dot com.
Enjoy,
antirez

1
BUGS Normal file
View File

@ -0,0 +1 @@
Plese check http://code.google.com/p/redis/issues/list

10
COPYING Normal file
View File

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

58
Makefile Normal file
View File

@ -0,0 +1,58 @@
# Redis Makefile
# Copyright (C) 2009 Salvatore Sanfilippo <antirez at gmail dot com>
# This file is released under the BSD license, see the COPYING file
DEBUG?= -g
CFLAGS?= -O2 -Wall -W -DSDS_ABORT_ON_OOM
CCOPT= $(CFLAGS)
OBJ = adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o
BENCHOBJ = ae.o anet.o benchmark.o sds.o adlist.o zmalloc.o
CLIOBJ = anet.o sds.o adlist.o redis-cli.o zmalloc.o
PRGNAME = redis-server
BENCHPRGNAME = redis-benchmark
CLIPRGNAME = redis-cli
all: redis-server redis-benchmark redis-cli
# Deps (use make dep to generate this)
adlist.o: adlist.c adlist.h
ae.o: ae.c ae.h
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
sds.o: sds.c sds.h
sha1.o: sha1.c sha1.h
zmalloc.o: zmalloc.c
redis-server: $(OBJ)
$(CC) -o $(PRGNAME) $(CCOPT) $(DEBUG) $(OBJ)
@echo ""
@echo "Hint: To run the test-redis.tcl script is a good idea."
@echo "Launch the redis server with ./redis-server, then in another"
@echo "terminal window enter this directory and run 'make test'."
@echo ""
redis-benchmark: $(BENCHOBJ)
$(CC) -o $(BENCHPRGNAME) $(CCOPT) $(DEBUG) $(BENCHOBJ)
redis-cli: $(CLIOBJ)
$(CC) -o $(CLIPRGNAME) $(CCOPT) $(DEBUG) $(CLIOBJ)
.c.o:
$(CC) -c $(CCOPT) $(DEBUG) $(COMPILE_TIME) $<
clean:
rm -rf $(PRGNAME) $(BENCHPRGNAME) $(CLIPRGNAME) *.o
dep:
$(CC) -MM *.c
test:
tclsh test-redis.tcl
bench:
./redis-benchmark

1
README Normal file
View File

@ -0,0 +1 @@
Check the 'doc' directory. doc/README.html is a good starting point :)

17
TODO Normal file
View File

@ -0,0 +1,17 @@
BETA 8 TODO
- keys expire
- sunion ssub
- write integers in a special way on disk (and on memory?)
- compact types for disk storing of short strings (no 4 bytes overhead!)
- 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

285
adlist.c Normal file
View File

@ -0,0 +1,285 @@
/* adlist.c - A generic doubly linked list implementation
*
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdlib.h>
#include "adlist.h"
#include "zmalloc.h"
/* Create a new list. The created list can be freed with
* AlFreeList(), but private value of every node need to be freed
* by the user before to call AlFreeList().
*
* On error, NULL is returned. Otherwise the pointer to the new list. */
list *listCreate(void)
{
struct list *list;
if ((list = zmalloc(sizeof(*list))) == NULL)
return NULL;
list->head = list->tail = NULL;
list->len = 0;
list->dup = NULL;
list->free = NULL;
list->match = NULL;
return list;
}
/* Free the whole list.
*
* This function can't fail. */
void listRelease(list *list)
{
unsigned int len;
listNode *current, *next;
current = list->head;
len = list->len;
while(len--) {
next = current->next;
if (list->free) list->free(current->value);
zfree(current);
current = next;
}
zfree(list);
}
/* Add a new node to the list, to head, contaning the specified 'value'
* pointer as value.
*
* On error, NULL is returned and no operation is performed (i.e. the
* list remains unaltered).
* On success the 'list' pointer you pass to the function is returned. */
list *listAddNodeHead(list *list, void *value)
{
listNode *node;
if ((node = zmalloc(sizeof(*node))) == NULL)
return NULL;
node->value = value;
if (list->len == 0) {
list->head = list->tail = node;
node->prev = node->next = NULL;
} else {
node->prev = NULL;
node->next = list->head;
list->head->prev = node;
list->head = node;
}
list->len++;
return list;
}
/* Add a new node to the list, to tail, contaning the specified 'value'
* pointer as value.
*
* On error, NULL is returned and no operation is performed (i.e. the
* list remains unaltered).
* On success the 'list' pointer you pass to the function is returned. */
list *listAddNodeTail(list *list, void *value)
{
listNode *node;
if ((node = zmalloc(sizeof(*node))) == NULL)
return NULL;
node->value = value;
if (list->len == 0) {
list->head = list->tail = node;
node->prev = node->next = NULL;
} else {
node->prev = list->tail;
node->next = NULL;
list->tail->next = node;
list->tail = node;
}
list->len++;
return list;
}
/* Remove the specified node from the specified list.
* It's up to the caller to free the private value of the node.
*
* This function can't fail. */
void listDelNode(list *list, listNode *node)
{
if (node->prev)
node->prev->next = node->next;
else
list->head = node->next;
if (node->next)
node->next->prev = node->prev;
else
list->tail = node->prev;
if (list->free) list->free(node->value);
zfree(node);
list->len--;
}
/* Returns a list iterator 'iter'. After the initialization every
* call to listNextElement() will return the next element of the list.
*
* This function can't fail. */
listIter *listGetIterator(list *list, int direction)
{
listIter *iter;
if ((iter = zmalloc(sizeof(*iter))) == NULL) return NULL;
if (direction == AL_START_HEAD)
iter->next = list->head;
else
iter->next = list->tail;
iter->direction = direction;
return iter;
}
/* Release the iterator memory */
void listReleaseIterator(listIter *iter) {
zfree(iter);
}
/* Return the next element of an iterator.
* It's valid to remove the currently returned element using
* listDelNode(), but not to remove other elements.
*
* The function returns a pointer to the next element of the list,
* or NULL if there are no more elements, so the classical usage patter
* is:
*
* iter = listGetItarotr(list,<direction>);
* while ((node = listNextIterator(iter)) != NULL) {
* DoSomethingWith(listNodeValue(node));
* }
*
* */
listNode *listNextElement(listIter *iter)
{
listNode *current = iter->next;
if (current != NULL) {
if (iter->direction == AL_START_HEAD)
iter->next = current->next;
else
iter->next = current->prev;
}
return current;
}
/* Duplicate the whole list. On out of memory NULL is returned.
* On success a copy of the original list is returned.
*
* The 'Dup' method set with listSetDupMethod() function is used
* to copy the node value. Otherwise the same pointer value of
* the original node is used as value of the copied node.
*
* The original list both on success or error is never modified. */
list *listDup(list *orig)
{
list *copy;
listIter *iter;
listNode *node;
if ((copy = listCreate()) == NULL)
return NULL;
copy->dup = orig->dup;
copy->free = orig->free;
copy->match = orig->match;
iter = listGetIterator(orig, AL_START_HEAD);
while((node = listNextElement(iter)) != NULL) {
void *value;
if (copy->dup) {
value = copy->dup(node->value);
if (value == NULL) {
listRelease(copy);
listReleaseIterator(iter);
return NULL;
}
} else
value = node->value;
if (listAddNodeTail(copy, value) == NULL) {
listRelease(copy);
listReleaseIterator(iter);
return NULL;
}
}
listReleaseIterator(iter);
return copy;
}
/* Search the list for a node matching a given key.
* The match is performed using the 'match' method
* set with listSetMatchMethod(). If no 'match' method
* is set, the 'value' pointer of every node is directly
* compared with the 'key' pointer.
*
* On success the first matching node pointer is returned
* (search starts from head). If no matching node exists
* NULL is returned. */
listNode *listSearchKey(list *list, void *key)
{
listIter *iter;
listNode *node;
iter = listGetIterator(list, AL_START_HEAD);
while((node = listNextElement(iter)) != NULL) {
if (list->match) {
if (list->match(node->value, key)) {
listReleaseIterator(iter);
return node;
}
} else {
if (key == node->value) {
listReleaseIterator(iter);
return node;
}
}
}
listReleaseIterator(iter);
return NULL;
}
/* Return the element at the specified zero-based index
* where 0 is the head, 1 is the element next to head
* and so on. Negative integers are used in order to count
* from the tail, -1 is the last element, -2 the penultimante
* and so on. If the index is out of range NULL is returned. */
listNode *listIndex(list *list, int index) {
listNode *n;
if (index < 0) {
index = (-index)-1;
n = list->tail;
while(index-- && n) n = n->prev;
} else {
n = list->head;
while(index-- && n) n = n->next;
}
return n;
}

90
adlist.h Normal file
View File

@ -0,0 +1,90 @@
/* adlist.h - A generic doubly linked list implementation
*
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __ADLIST_H__
#define __ADLIST_H__
/* Node, List, and Iterator are the only data structures used currently. */
typedef struct listNode {
struct listNode *prev;
struct listNode *next;
void *value;
} listNode;
typedef struct list {
listNode *head;
listNode *tail;
void *(*dup)(void *ptr);
void (*free)(void *ptr);
int (*match)(void *ptr, void *key);
unsigned int len;
} list;
typedef struct listIter {
listNode *next;
listNode *prev;
int direction;
} listIter;
/* Functions implemented as macros */
#define listLength(l) ((l)->len)
#define listFirst(l) ((l)->head)
#define listLast(l) ((l)->tail)
#define listPrevNode(n) ((n)->prev)
#define listNextNode(n) ((n)->next)
#define listNodeValue(n) ((n)->value)
#define listSetDupMethod(l,m) ((l)->dup = (m))
#define listSetFreeMethod(l,m) ((l)->free = (m))
#define listSetMatchMethod(l,m) ((l)->match = (m))
#define listGetDupMethod(l) ((l)->dup)
#define listGetFree(l) ((l)->free)
#define listGetMatchMethod(l) ((l)->match)
/* Prototypes */
list *listCreate(void);
void listRelease(list *list);
list *listAddNodeHead(list *list, void *value);
list *listAddNodeTail(list *list, void *value);
void listDelNode(list *list, listNode *node);
listIter *listGetIterator(list *list, int direction);
listNode *listNextElement(listIter *iter);
void listReleaseIterator(listIter *iter);
list *listDup(list *orig);
listNode *listSearchKey(list *list, void *key);
listNode *listIndex(list *list, int index);
/* Directions for iterators */
#define AL_START_HEAD 0
#define AL_START_TAIL 1
#endif /* __ADLIST_H__ */

368
ae.c Normal file
View File

@ -0,0 +1,368 @@
/* A simple event-driven programming library. Originally I wrote this code
* for the Jim's event-loop (Jim is a Tcl interpreter) but later translated
* it in form of a library for easy reuse.
*
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include "ae.h"
#include "zmalloc.h"
aeEventLoop *aeCreateEventLoop(void) {
aeEventLoop *eventLoop;
eventLoop = zmalloc(sizeof(*eventLoop));
if (!eventLoop) return NULL;
eventLoop->fileEventHead = NULL;
eventLoop->timeEventHead = NULL;
eventLoop->timeEventNextId = 0;
eventLoop->stop = 0;
return eventLoop;
}
void aeDeleteEventLoop(aeEventLoop *eventLoop) {
zfree(eventLoop);
}
void aeStop(aeEventLoop *eventLoop) {
eventLoop->stop = 1;
}
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
aeFileProc *proc, void *clientData,
aeEventFinalizerProc *finalizerProc)
{
aeFileEvent *fe;
fe = zmalloc(sizeof(*fe));
if (fe == NULL) return AE_ERR;
fe->fd = fd;
fe->mask = mask;
fe->fileProc = proc;
fe->finalizerProc = finalizerProc;
fe->clientData = clientData;
fe->next = eventLoop->fileEventHead;
eventLoop->fileEventHead = fe;
return AE_OK;
}
void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask)
{
aeFileEvent *fe, *prev = NULL;
fe = eventLoop->fileEventHead;
while(fe) {
if (fe->fd == fd && fe->mask == mask) {
if (prev == NULL)
eventLoop->fileEventHead = fe->next;
else
prev->next = fe->next;
if (fe->finalizerProc)
fe->finalizerProc(eventLoop, fe->clientData);
zfree(fe);
return;
}
prev = fe;
fe = fe->next;
}
}
static void aeGetTime(long *seconds, long *milliseconds)
{
struct timeval tv;
gettimeofday(&tv, NULL);
*seconds = tv.tv_sec;
*milliseconds = tv.tv_usec/1000;
}
static void aeAddMillisecondsToNow(long long milliseconds, long *sec, long *ms) {
long cur_sec, cur_ms, when_sec, when_ms;
aeGetTime(&cur_sec, &cur_ms);
when_sec = cur_sec + milliseconds/1000;
when_ms = cur_ms + milliseconds%1000;
if (when_ms >= 1000) {
when_sec ++;
when_ms -= 1000;
}
*sec = when_sec;
*ms = when_ms;
}
long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
aeTimeProc *proc, void *clientData,
aeEventFinalizerProc *finalizerProc)
{
long long id = eventLoop->timeEventNextId++;
aeTimeEvent *te;
te = zmalloc(sizeof(*te));
if (te == NULL) return AE_ERR;
te->id = id;
aeAddMillisecondsToNow(milliseconds,&te->when_sec,&te->when_ms);
te->timeProc = proc;
te->finalizerProc = finalizerProc;
te->clientData = clientData;
te->next = eventLoop->timeEventHead;
eventLoop->timeEventHead = te;
return id;
}
int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id)
{
aeTimeEvent *te, *prev = NULL;
te = eventLoop->timeEventHead;
while(te) {
if (te->id == id) {
if (prev == NULL)
eventLoop->timeEventHead = te->next;
else
prev->next = te->next;
if (te->finalizerProc)
te->finalizerProc(eventLoop, te->clientData);
zfree(te);
return AE_OK;
}
prev = te;
te = te->next;
}
return AE_ERR; /* NO event with the specified ID found */
}
/* Search the first timer to fire.
* This operation is useful to know how many time the select can be
* put in sleep without to delay any event.
* If there are no timers NULL is returned.
*
* Note that's O(N) since time events are unsorted. */
static aeTimeEvent *aeSearchNearestTimer(aeEventLoop *eventLoop)
{
aeTimeEvent *te = eventLoop->timeEventHead;
aeTimeEvent *nearest = NULL;
while(te) {
if (!nearest || te->when_sec < nearest->when_sec ||
(te->when_sec == nearest->when_sec &&
te->when_ms < nearest->when_ms))
nearest = te;
te = te->next;
}
return nearest;
}
/* Process every pending time event, then every pending file event
* (that may be registered by time event callbacks just processed).
* Without special flags the function sleeps until some file event
* fires, or when the next time event occurrs (if any).
*
* If flags is 0, the function does nothing and returns.
* if flags has AE_ALL_EVENTS set, all the kind of events are processed.
* if flags has AE_FILE_EVENTS set, file events are processed.
* if flags has AE_TIME_EVENTS set, time events are processed.
* if flags has AE_DONT_WAIT set the function returns ASAP until all
* the events that's possible to process without to wait are processed.
*
* The function returns the number of events processed. */
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
int maxfd = 0, numfd = 0, processed = 0;
fd_set rfds, wfds, efds;
aeFileEvent *fe = eventLoop->fileEventHead;
aeTimeEvent *te;
long long maxId;
AE_NOTUSED(flags);
/* Nothing to do? return ASAP */
if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_ZERO(&efds);
/* Check file events */
if (flags & AE_FILE_EVENTS) {
while (fe != NULL) {
if (fe->mask & AE_READABLE) FD_SET(fe->fd, &rfds);
if (fe->mask & AE_WRITABLE) FD_SET(fe->fd, &wfds);
if (fe->mask & AE_EXCEPTION) FD_SET(fe->fd, &efds);
if (maxfd < fe->fd) maxfd = fe->fd;
numfd++;
fe = fe->next;
}
}
/* Note that we want call select() even if there are no
* file events to process as long as we want to process time
* events, in order to sleep until the next time event is ready
* to fire. */
if (numfd || ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
int retval;
aeTimeEvent *shortest = NULL;
struct timeval tv, *tvp;
if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
shortest = aeSearchNearestTimer(eventLoop);
if (shortest) {
long now_sec, now_ms;
/* Calculate the time missing for the nearest
* timer to fire. */
aeGetTime(&now_sec, &now_ms);
tvp = &tv;
tvp->tv_sec = shortest->when_sec - now_sec;
if (shortest->when_ms < now_ms) {
tvp->tv_usec = ((shortest->when_ms+1000) - now_ms)*1000;
tvp->tv_sec --;
} else {
tvp->tv_usec = (shortest->when_ms - now_ms)*1000;
}
} else {
/* If we have to check for events but need to return
* ASAP because of AE_DONT_WAIT we need to se the timeout
* to zero */
if (flags & AE_DONT_WAIT) {
tv.tv_sec = tv.tv_usec = 0;
tvp = &tv;
} else {
/* Otherwise we can block */
tvp = NULL; /* wait forever */
}
}
retval = select(maxfd+1, &rfds, &wfds, &efds, tvp);
if (retval > 0) {
fe = eventLoop->fileEventHead;
while(fe != NULL) {
int fd = (int) fe->fd;
if ((fe->mask & AE_READABLE && FD_ISSET(fd, &rfds)) ||
(fe->mask & AE_WRITABLE && FD_ISSET(fd, &wfds)) ||
(fe->mask & AE_EXCEPTION && FD_ISSET(fd, &efds)))
{
int mask = 0;
if (fe->mask & AE_READABLE && FD_ISSET(fd, &rfds))
mask |= AE_READABLE;
if (fe->mask & AE_WRITABLE && FD_ISSET(fd, &wfds))
mask |= AE_WRITABLE;
if (fe->mask & AE_EXCEPTION && FD_ISSET(fd, &efds))
mask |= AE_EXCEPTION;
fe->fileProc(eventLoop, fe->fd, fe->clientData, mask);
processed++;
/* After an event is processed our file event list
* may no longer be the same, so what we do
* is to clear the bit for this file descriptor and
* restart again from the head. */
fe = eventLoop->fileEventHead;
FD_CLR(fd, &rfds);
FD_CLR(fd, &wfds);
FD_CLR(fd, &efds);
} else {
fe = fe->next;
}
}
}
}
/* Check time events */
if (flags & AE_TIME_EVENTS) {
te = eventLoop->timeEventHead;
maxId = eventLoop->timeEventNextId-1;
while(te) {
long now_sec, now_ms;
long long id;
if (te->id > maxId) {
te = te->next;
continue;
}
aeGetTime(&now_sec, &now_ms);
if (now_sec > te->when_sec ||
(now_sec == te->when_sec && now_ms >= te->when_ms))
{
int retval;
id = te->id;
retval = te->timeProc(eventLoop, id, te->clientData);
/* After an event is processed our time event list may
* no longer be the same, so we restart from head.
* Still we make sure to don't process events registered
* by event handlers itself in order to don't loop forever.
* To do so we saved the max ID we want to handle. */
if (retval != AE_NOMORE) {
aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);
} else {
aeDeleteTimeEvent(eventLoop, id);
}
te = eventLoop->timeEventHead;
} else {
te = te->next;
}
}
}
return processed; /* return the number of processed file/time events */
}
/* Wait for millseconds until the given file descriptor becomes
* writable/readable/exception */
int aeWait(int fd, int mask, long long milliseconds) {
struct timeval tv;
fd_set rfds, wfds, efds;
int retmask = 0, retval;
tv.tv_sec = milliseconds/1000;
tv.tv_usec = (milliseconds%1000)*1000;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_ZERO(&efds);
if (mask & AE_READABLE) FD_SET(fd,&rfds);
if (mask & AE_WRITABLE) FD_SET(fd,&wfds);
if (mask & AE_EXCEPTION) FD_SET(fd,&efds);
if ((retval = select(fd+1, &rfds, &wfds, &efds, &tv)) > 0) {
if (FD_ISSET(fd,&rfds)) retmask |= AE_READABLE;
if (FD_ISSET(fd,&wfds)) retmask |= AE_WRITABLE;
if (FD_ISSET(fd,&efds)) retmask |= AE_EXCEPTION;
return retmask;
} else {
return retval;
}
}
void aeMain(aeEventLoop *eventLoop)
{
eventLoop->stop = 0;
while (!eventLoop->stop)
aeProcessEvents(eventLoop, AE_ALL_EVENTS);
}

106
ae.h Normal file
View File

@ -0,0 +1,106 @@
/* A simple event-driven programming library. Originally I wrote this code
* for the Jim's event-loop (Jim is a Tcl interpreter) but later translated
* it in form of a library for easy reuse.
*
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __AE_H__
#define __AE_H__
struct aeEventLoop;
/* Types and data structures */
typedef void aeFileProc(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask);
typedef int aeTimeProc(struct aeEventLoop *eventLoop, long long id, void *clientData);
typedef void aeEventFinalizerProc(struct aeEventLoop *eventLoop, void *clientData);
/* File event structure */
typedef struct aeFileEvent {
int fd;
int mask; /* one of AE_(READABLE|WRITABLE|EXCEPTION) */
aeFileProc *fileProc;
aeEventFinalizerProc *finalizerProc;
void *clientData;
struct aeFileEvent *next;
} aeFileEvent;
/* Time event structure */
typedef struct aeTimeEvent {
long long id; /* time event identifier. */
long when_sec; /* seconds */
long when_ms; /* milliseconds */
aeTimeProc *timeProc;
aeEventFinalizerProc *finalizerProc;
void *clientData;
struct aeTimeEvent *next;
} aeTimeEvent;
/* State of an event based program */
typedef struct aeEventLoop {
long long timeEventNextId;
aeFileEvent *fileEventHead;
aeTimeEvent *timeEventHead;
int stop;
} aeEventLoop;
/* Defines */
#define AE_OK 0
#define AE_ERR -1
#define AE_READABLE 1
#define AE_WRITABLE 2
#define AE_EXCEPTION 4
#define AE_FILE_EVENTS 1
#define AE_TIME_EVENTS 2
#define AE_ALL_EVENTS (AE_FILE_EVENTS|AE_TIME_EVENTS)
#define AE_DONT_WAIT 4
#define AE_NOMORE -1
/* Macros */
#define AE_NOTUSED(V) ((void) V)
/* Prototypes */
aeEventLoop *aeCreateEventLoop(void);
void aeDeleteEventLoop(aeEventLoop *eventLoop);
void aeStop(aeEventLoop *eventLoop);
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
aeFileProc *proc, void *clientData,
aeEventFinalizerProc *finalizerProc);
void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask);
long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
aeTimeProc *proc, void *clientData,
aeEventFinalizerProc *finalizerProc);
int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id);
int aeProcessEvents(aeEventLoop *eventLoop, int flags);
int aeWait(int fd, int mask, long long milliseconds);
void aeMain(aeEventLoop *eventLoop);
#endif

268
anet.c Normal file
View File

@ -0,0 +1,268 @@
/* anet.c -- Basic TCP socket stuff made a bit less boring
*
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <netdb.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include "anet.h"
static void anetSetError(char *err, const char *fmt, ...)
{
va_list ap;
if (!err) return;
va_start(ap, fmt);
vsnprintf(err, ANET_ERR_LEN, fmt, ap);
va_end(ap);
}
int anetNonBlock(char *err, int fd)
{
int flags;
/* Set the socket nonblocking.
* Note that fcntl(2) for F_GETFL and F_SETFL can't be
* interrupted by a signal. */
if ((flags = fcntl(fd, F_GETFL)) == -1) {
anetSetError(err, "fcntl(F_GETFL): %s\n", strerror(errno));
return ANET_ERR;
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
anetSetError(err, "fcntl(F_SETFL,O_NONBLOCK): %s\n", strerror(errno));
return ANET_ERR;
}
return ANET_OK;
}
int anetTcpNoDelay(char *err, int fd)
{
int yes = 1;
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1)
{
anetSetError(err, "setsockopt TCP_NODELAY: %s\n", strerror(errno));
return ANET_ERR;
}
return ANET_OK;
}
int anetSetSendBuffer(char *err, int fd, int buffsize)
{
if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &buffsize, sizeof(buffsize)) == -1)
{
anetSetError(err, "setsockopt SO_SNDBUF: %s\n", strerror(errno));
return ANET_ERR;
}
return ANET_OK;
}
int anetTcpKeepAlive(char *err, int fd)
{
int yes = 1;
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)) == -1) {
anetSetError(err, "setsockopt SO_KEEPALIVE: %s\n", strerror(errno));
return ANET_ERR;
}
return ANET_OK;
}
int anetResolve(char *err, char *host, char *ipbuf)
{
struct sockaddr_in sa;
sa.sin_family = AF_INET;
if (inet_aton(host, &sa.sin_addr) == 0) {
struct hostent *he;
he = gethostbyname(host);
if (he == NULL) {
anetSetError(err, "can't resolve: %s\n", host);
return ANET_ERR;
}
memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));
}
strcpy(ipbuf,inet_ntoa(sa.sin_addr));
return ANET_OK;
}
#define ANET_CONNECT_NONE 0
#define ANET_CONNECT_NONBLOCK 1
static int anetTcpGenericConnect(char *err, char *addr, int port, int flags)
{
int s, on = 1;
struct sockaddr_in sa;
if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
anetSetError(err, "creating socket: %s\n", strerror(errno));
return ANET_ERR;
}
/* Make sure connection-intensive things like the redis benckmark
* will be able to close/open sockets a zillion of times */
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
if (inet_aton(addr, &sa.sin_addr) == 0) {
struct hostent *he;
he = gethostbyname(addr);
if (he == NULL) {
anetSetError(err, "can't resolve: %s\n", addr);
close(s);
return ANET_ERR;
}
memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));
}
if (flags & ANET_CONNECT_NONBLOCK) {
if (anetNonBlock(err,s) != ANET_OK)
return ANET_ERR;
}
if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
if (errno == EINPROGRESS &&
flags & ANET_CONNECT_NONBLOCK)
return s;
anetSetError(err, "connect: %s\n", strerror(errno));
close(s);
return ANET_ERR;
}
return s;
}
int anetTcpConnect(char *err, char *addr, int port)
{
return anetTcpGenericConnect(err,addr,port,ANET_CONNECT_NONE);
}
int anetTcpNonBlockConnect(char *err, char *addr, int port)
{
return anetTcpGenericConnect(err,addr,port,ANET_CONNECT_NONBLOCK);
}
/* Like read(2) but make sure 'count' is read before to return
* (unless error or EOF condition is encountered) */
int anetRead(int fd, void *buf, int count)
{
int nread, totlen = 0;
while(totlen != count) {
nread = read(fd,buf,count-totlen);
if (nread == 0) return totlen;
if (nread == -1) return -1;
totlen += nread;
buf += nread;
}
return totlen;
}
/* Like write(2) but make sure 'count' is read before to return
* (unless error is encountered) */
int anetWrite(int fd, void *buf, int count)
{
int nwritten, totlen = 0;
while(totlen != count) {
nwritten = write(fd,buf,count-totlen);
if (nwritten == 0) return totlen;
if (nwritten == -1) return -1;
totlen += nwritten;
buf += nwritten;
}
return totlen;
}
int anetTcpServer(char *err, int port, char *bindaddr)
{
int s, on = 1;
struct sockaddr_in sa;
if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
anetSetError(err, "socket: %s\n", strerror(errno));
return ANET_ERR;
}
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
anetSetError(err, "setsockopt SO_REUSEADDR: %s\n", strerror(errno));
close(s);
return ANET_ERR;
}
memset(&sa,0,sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
sa.sin_addr.s_addr = htonl(INADDR_ANY);
if (bindaddr) {
if (inet_aton(bindaddr, &sa.sin_addr) == 0) {
anetSetError(err, "Invalid bind address\n");
close(s);
return ANET_ERR;
}
}
if (bind(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
anetSetError(err, "bind: %s\n", strerror(errno));
close(s);
return ANET_ERR;
}
if (listen(s, 32) == -1) {
anetSetError(err, "listen: %s\n", strerror(errno));
close(s);
return ANET_ERR;
}
return s;
}
int anetAccept(char *err, int serversock, char *ip, int *port)
{
int fd;
struct sockaddr_in sa;
unsigned int saLen;
while(1) {
saLen = sizeof(sa);
fd = accept(serversock, (struct sockaddr*)&sa, &saLen);
if (fd == -1) {
if (errno == EINTR)
continue;
else {
anetSetError(err, "accept: %s\n", strerror(errno));
return ANET_ERR;
}
}
break;
}
if (ip) strcpy(ip,inet_ntoa(sa.sin_addr));
if (port) *port = ntohs(sa.sin_port);
return fd;
}

49
anet.h Normal file
View File

@ -0,0 +1,49 @@
/* anet.c -- Basic TCP socket stuff made a bit less boring
*
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef ANET_H
#define ANET_H
#define ANET_OK 0
#define ANET_ERR -1
#define ANET_ERR_LEN 256
int anetTcpConnect(char *err, char *addr, int port);
int anetTcpNonBlockConnect(char *err, char *addr, int port);
int anetRead(int fd, void *buf, int count);
int anetResolve(char *err, char *host, char *ipbuf);
int anetTcpServer(char *err, int port, char *bindaddr);
int anetAccept(char *err, int serversock, char *ip, int *port);
int anetWrite(int fd, void *buf, int count);
int anetNonBlock(char *err, int fd);
int anetTcpNoDelay(char *err, int fd);
int anetTcpKeepAlive(char *err, int fd);
#endif

460
benchmark.c Normal file
View File

@ -0,0 +1,460 @@
/* Redis benchmark utility.
*
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/time.h>
#include <signal.h>
#include <assert.h>
#include "ae.h"
#include "anet.h"
#include "sds.h"
#include "adlist.h"
#include "zmalloc.h"
#define REPLY_INT 0
#define REPLY_RETCODE 1
#define REPLY_BULK 2
#define CLIENT_CONNECTING 0
#define CLIENT_SENDQUERY 1
#define CLIENT_READREPLY 2
#define MAX_LATENCY 5000
#define REDIS_NOTUSED(V) ((void) V)
static struct config {
int numclients;
int requests;
int liveclients;
int donerequests;
int keysize;
int datasize;
aeEventLoop *el;
char *hostip;
int hostport;
int keepalive;
long long start;
long long totlatency;
int *latency;
list *clients;
int quiet;
int loop;
} config;
typedef struct _client {
int state;
int fd;
sds obuf;
sds ibuf;
int readlen; /* readlen == -1 means read a single line */
unsigned int written; /* bytes of 'obuf' already written */
int replytype;
long long start; /* start time in milliseconds */
} *client;
/* Prototypes */
static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask);
static void createMissingClients(client c);
/* Implementation */
static long long mstime(void) {
struct timeval tv;
long long mst;
gettimeofday(&tv, NULL);
mst = ((long)tv.tv_sec)*1000;
mst += tv.tv_usec/1000;
return mst;
}
static void freeClient(client c) {
listNode *ln;
aeDeleteFileEvent(config.el,c->fd,AE_WRITABLE);
aeDeleteFileEvent(config.el,c->fd,AE_READABLE);
sdsfree(c->ibuf);
sdsfree(c->obuf);
close(c->fd);
zfree(c);
config.liveclients--;
ln = listSearchKey(config.clients,c);
assert(ln != NULL);
listDelNode(config.clients,ln);
}
static void freeAllClients(void) {
listNode *ln = config.clients->head, *next;
while(ln) {
next = ln->next;
freeClient(ln->value);
ln = next;
}
}
static void resetClient(client c) {
aeDeleteFileEvent(config.el,c->fd,AE_WRITABLE);
aeDeleteFileEvent(config.el,c->fd,AE_READABLE);
aeCreateFileEvent(config.el,c->fd, AE_WRITABLE,writeHandler,c,NULL);
sdsfree(c->ibuf);
c->ibuf = sdsempty();
c->readlen = (c->replytype == REPLY_BULK) ? -1 : 0;
c->written = 0;
c->state = CLIENT_SENDQUERY;
c->start = mstime();
}
static void clientDone(client c) {
long long latency;
config.donerequests ++;
latency = mstime() - c->start;
if (latency > MAX_LATENCY) latency = MAX_LATENCY;
config.latency[latency]++;
if (config.donerequests == config.requests) {
freeClient(c);
aeStop(config.el);
return;
}
if (config.keepalive) {
resetClient(c);
} else {
config.liveclients--;
createMissingClients(c);
config.liveclients++;
freeClient(c);
}
}
static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask)
{
char buf[1024];
int nread;
client c = privdata;
REDIS_NOTUSED(el);
REDIS_NOTUSED(fd);
REDIS_NOTUSED(mask);
nread = read(c->fd, buf, 1024);
if (nread == -1) {
fprintf(stderr, "Reading from socket: %s\n", strerror(errno));
freeClient(c);
return;
}
if (nread == 0) {
fprintf(stderr, "EOF from client\n");
freeClient(c);
return;
}
c->ibuf = sdscatlen(c->ibuf,buf,nread);
if (c->replytype == REPLY_INT ||
c->replytype == REPLY_RETCODE ||
(c->replytype == REPLY_BULK && c->readlen == -1)) {
char *p;
if ((p = strchr(c->ibuf,'\n')) != NULL) {
if (c->replytype == REPLY_BULK) {
*p = '\0';
*(p-1) = '\0';
if (memcmp(c->ibuf,"nil",3) == 0) {
clientDone(c);
return;
}
c->readlen = atoi(c->ibuf)+2;
c->ibuf = sdsrange(c->ibuf,(p-c->ibuf)+1,-1);
} else {
c->ibuf = sdstrim(c->ibuf,"\r\n");
clientDone(c);
return;
}
}
}
/* bulk read */
if ((unsigned)c->readlen == sdslen(c->ibuf))
clientDone(c);
}
static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask)
{
client c = privdata;
REDIS_NOTUSED(el);
REDIS_NOTUSED(fd);
REDIS_NOTUSED(mask);
if (c->state == CLIENT_CONNECTING) {
c->state = CLIENT_SENDQUERY;
c->start = mstime();
}
if (sdslen(c->obuf) > c->written) {
void *ptr = c->obuf+c->written;
int len = sdslen(c->obuf) - c->written;
int nwritten = write(c->fd, ptr, len);
if (nwritten == -1) {
fprintf(stderr, "Writing to socket: %s\n", strerror(errno));
freeClient(c);
return;
}
c->written += nwritten;
if (sdslen(c->obuf) == c->written) {
aeDeleteFileEvent(config.el,c->fd,AE_WRITABLE);
aeCreateFileEvent(config.el,c->fd,AE_READABLE,readHandler,c,NULL);
c->state = CLIENT_READREPLY;
}
}
}
static client createClient(void) {
client c = zmalloc(sizeof(struct _client));
char err[ANET_ERR_LEN];
c->fd = anetTcpNonBlockConnect(err,config.hostip,config.hostport);
if (c->fd == ANET_ERR) {
zfree(c);
fprintf(stderr,"Connect: %s\n",err);
return NULL;
}
anetTcpNoDelay(NULL,c->fd);
c->obuf = sdsempty();
c->ibuf = sdsempty();
c->readlen = 0;
c->written = 0;
c->state = CLIENT_CONNECTING;
aeCreateFileEvent(config.el, c->fd, AE_WRITABLE, writeHandler, c, NULL);
config.liveclients++;
listAddNodeTail(config.clients,c);
return c;
}
static void createMissingClients(client c) {
while(config.liveclients < config.numclients) {
client new = createClient();
if (!new) continue;
sdsfree(new->obuf);
new->obuf = sdsdup(c->obuf);
new->replytype = c->replytype;
if (c->replytype == REPLY_BULK)
new->readlen = -1;
}
}
static void showLatencyReport(char *title) {
int j, seen = 0;
float perc, reqpersec;
reqpersec = (float)config.donerequests/((float)config.totlatency/1000);
if (!config.quiet) {
printf("====== %s ======\n", title);
printf(" %d requests completed in %.2f seconds\n", config.donerequests,
(float)config.totlatency/1000);
printf(" %d parallel clients\n", config.numclients);
printf(" %d bytes payload\n", config.datasize);
printf(" keep alive: %d\n", config.keepalive);
printf("\n");
for (j = 0; j <= MAX_LATENCY; j++) {
if (config.latency[j]) {
seen += config.latency[j];
perc = ((float)seen*100)/config.donerequests;
printf("%.2f%% <= %d milliseconds\n", perc, j);
}
}
printf("%.2f requests per second\n\n", reqpersec);
} else {
printf("%s: %.2f requests per second\n", title, reqpersec);
}
}
static void prepareForBenchmark(void)
{
memset(config.latency,0,sizeof(int)*(MAX_LATENCY+1));
config.start = mstime();
config.donerequests = 0;
}
static void endBenchmark(char *title) {
config.totlatency = mstime()-config.start;
showLatencyReport(title);
freeAllClients();
}
void parseOptions(int argc, char **argv) {
int i;
for (i = 1; i < argc; i++) {
int lastarg = i==argc-1;
if (!strcmp(argv[i],"-c") && !lastarg) {
config.numclients = atoi(argv[i+1]);
i++;
} else if (!strcmp(argv[i],"-n") && !lastarg) {
config.requests = atoi(argv[i+1]);
i++;
} else if (!strcmp(argv[i],"-k") && !lastarg) {
config.keepalive = atoi(argv[i+1]);
i++;
} else if (!strcmp(argv[i],"-h") && !lastarg) {
char *ip = zmalloc(32);
if (anetResolve(NULL,argv[i+1],ip) == ANET_ERR) {
printf("Can't resolve %s\n", argv[i]);
exit(1);
}
config.hostip = ip;
i++;
} else if (!strcmp(argv[i],"-p") && !lastarg) {
config.hostport = atoi(argv[i+1]);
i++;
} else if (!strcmp(argv[i],"-d") && !lastarg) {
config.datasize = atoi(argv[i+1]);
i++;
if (config.datasize < 1) config.datasize=1;
if (config.datasize > 1024*1024) config.datasize = 1024*1024;
} else if (!strcmp(argv[i],"-q")) {
config.quiet = 1;
} else if (!strcmp(argv[i],"-l")) {
config.loop = 1;
} else {
printf("Wrong option '%s' or option argument missing\n\n",argv[i]);
printf("Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>]\n\n");
printf(" -h <hostname> Server hostname (default 127.0.0.1)\n");
printf(" -p <hostname> Server port (default 6379)\n");
printf(" -c <clients> Number of parallel connections (default 50)\n");
printf(" -n <requests> Total number of requests (default 10000)\n");
printf(" -d <size> Data size of SET/GET value in bytes (default 2)\n");
printf(" -k <boolean> 1=keep alive 0=reconnect (default 1)\n");
printf(" -q Quiet. Just show query/sec values\n");
printf(" -l Loop. Run the tests forever\n");
exit(1);
}
}
}
int main(int argc, char **argv) {
client c;
signal(SIGHUP, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
config.numclients = 50;
config.requests = 10000;
config.liveclients = 0;
config.el = aeCreateEventLoop();
config.keepalive = 1;
config.donerequests = 0;
config.datasize = 3;
config.quiet = 0;
config.loop = 0;
config.latency = NULL;
config.clients = listCreate();
config.latency = zmalloc(sizeof(int)*(MAX_LATENCY+1));
config.hostip = "127.0.0.1";
config.hostport = 6379;
parseOptions(argc,argv);
if (config.keepalive == 0) {
printf("WARNING: keepalive disabled, you probably need 'echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse' in order to use a lot of clients/requests\n");
}
do {
prepareForBenchmark();
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"PING\r\n");
c->replytype = REPLY_RETCODE;
createMissingClients(c);
aeMain(config.el);
endBenchmark("PING");
prepareForBenchmark();
c = createClient();
if (!c) exit(1);
c->obuf = sdscatprintf(c->obuf,"SET foo %d\r\n",config.datasize);
{
char *data = zmalloc(config.datasize+2);
memset(data,'x',config.datasize);
data[config.datasize] = '\r';
data[config.datasize+1] = '\n';
c->obuf = sdscatlen(c->obuf,data,config.datasize+2);
}
c->replytype = REPLY_RETCODE;
createMissingClients(c);
aeMain(config.el);
endBenchmark("SET");
prepareForBenchmark();
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"GET foo\r\n");
c->replytype = REPLY_BULK;
c->readlen = -1;
createMissingClients(c);
aeMain(config.el);
endBenchmark("GET");
prepareForBenchmark();
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"INCR counter\r\n");
c->replytype = REPLY_INT;
createMissingClients(c);
aeMain(config.el);
endBenchmark("INCR");
prepareForBenchmark();
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LPUSH mylist 3\r\nbar\r\n");
c->replytype = REPLY_INT;
createMissingClients(c);
aeMain(config.el);
endBenchmark("LPUSH");
prepareForBenchmark();
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LPOP mylist\r\n");
c->replytype = REPLY_BULK;
c->readlen = -1;
createMissingClients(c);
aeMain(config.el);
endBenchmark("LPOP");
printf("\n");
} while(config.loop);
return 0;
}

28
client-libraries/README Normal file
View File

@ -0,0 +1,28 @@
Redis client libraries
----------------------
In this directory you'll find client libraries for different languages.
This are the latest releases available at the time this Redis tar.gz for this
release was created, and are good for most uses, but if you need more fresh
code or recent bugfixes read more.
How to get the lastest versions of client libraries source code
---------------------------------------------------------------
Note that while the PHP and Python versions are the most uptodate available
libraries, the Ruby and Erlang libraries have their own sites so you may want
to grab this libraries from their main sites:
Ruby lib source code:
http://github.com/ezmobius/redis-rb/tree/master
Erlang lib source code:
http://bitbucket.org/adroll/erldis/
For the languages with development code in the Redis SVN, check this urls for unstable versions of the libs:
Python lib source code:
http://code.google.com/p/redis/source/browse/#svn/trunk/client-libraries/python
PHP lib source code:
http://code.google.com/p/redis/source/browse/#svn/trunk/client-libraries/php

View File

@ -0,0 +1,2 @@
repo: 9e1f35ed7fdc7b3da7f5ff66a71d1975b85e2ae5
node: 7f98e864d76b0b2a7427049b943fb1c0dad0df2a

View File

@ -0,0 +1,2 @@
syntax: glob
*.beam

View File

@ -0,0 +1,22 @@
Copyright (c) 2009
adroll.com
Valentino Volonghi
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,29 @@
LIBDIR=`erl -eval 'io:format("~s~n", [code:lib_dir()])' -s init stop -noshell`
all:
mkdir -p ebin/
(cd src;$(MAKE))
(cd test;$(MAKE))
clean: clean_tests
(cd src;$(MAKE) clean)
rm -rf erl_crash.dump *.beam
clean_tests:
(cd test;$(MAKE) clean)
rm -rf erl_crash.dump *.beam
test: clean
mkdir -p ebin/
(cd src;$(MAKE))
(cd test;$(MAKE))
(cd test;$(MAKE) test)
testrun: all
mkdir -p ebin/
(cd test;$(MAKE) test)
install: all
mkdir -p ${LIBDIR}/erldis-0.0.1/{ebin,include}
for i in ebin/*.beam; do install $$i $(LIBDIR)/erldis-0.0.1/$$i ; done
for i in include/*.hrl; do install $$i $(LIBDIR)/erldis-0.0.1/$$i ; done

View File

@ -0,0 +1 @@
-record(redis, {socket,buffer=[],reply_caller,parsers,remaining=0,pstate=empty,results=[]}).

View File

@ -0,0 +1,9 @@
include ../support/include.mk
all: $(EBIN_FILES)
debug:
$(MAKE) DEBUG=-DDEBUG
clean:
rm -rf $(EBIN_FILES) erl_crash.dump

View File

@ -0,0 +1,272 @@
-module(client).
-behavior(gen_server).
-export([start/1, start/2, connect/1, connect/2, asend/2, send/3, send/2,
disconnect/1, ssend/3, str/1, format/1, sformat/1, ssend/2,
get_all_results/1]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
-include("erldis.hrl").
-define(EOL, "\r\n").
%% Helpers
str(X) when is_list(X) ->
X;
str(X) when is_atom(X) ->
atom_to_list(X);
str(X) when is_binary(X) ->
binary_to_list(X);
str(X) when is_integer(X) ->
integer_to_list(X);
str(X) when is_float(X) ->
float_to_list(X).
format([], Result) ->
string:join(lists:reverse(Result), ?EOL);
format([Line|Rest], Result) ->
JoinedLine = string:join([str(X) || X <- Line], " "),
format(Rest, [JoinedLine|Result]).
format(Lines) ->
format(Lines, []).
sformat(Line) ->
format([Line], []).
get_parser(Cmd)
when Cmd =:= set orelse Cmd =:= setnx orelse Cmd =:= del
orelse Cmd =:= exists orelse Cmd =:= rename orelse Cmd =:= renamenx
orelse Cmd =:= rpush orelse Cmd =:= lpush orelse Cmd =:= ltrim
orelse Cmd =:= lset orelse Cmd =:= sadd orelse Cmd =:= srem
orelse Cmd =:= sismember orelse Cmd =:= select orelse Cmd =:= move
orelse Cmd =:= save orelse Cmd =:= bgsave orelse Cmd =:= flushdb
orelse Cmd =:= flushall ->
fun proto:parse/2;
get_parser(Cmd) when Cmd =:= lrem ->
fun proto:parse_special/2;
get_parser(Cmd)
when Cmd =:= incr orelse Cmd =:= incrby orelse Cmd =:= decr
orelse Cmd =:= decrby orelse Cmd =:= llen orelse Cmd =:= scard ->
fun proto:parse_int/2;
get_parser(Cmd) when Cmd =:= type ->
fun proto:parse_types/2;
get_parser(Cmd) when Cmd =:= randomkey ->
fun proto:parse_string/2;
get_parser(Cmd)
when Cmd =:= get orelse Cmd =:= lindex orelse Cmd =:= lpop
orelse Cmd =:= rpop ->
fun proto:single_stateful_parser/2;
get_parser(Cmd)
when Cmd =:= keys orelse Cmd =:= lrange orelse Cmd =:= sinter
orelse Cmd =:= smembers orelse Cmd =:= sort ->
fun proto:stateful_parser/2.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Exported API
start(Host) ->
connect(Host).
start(Host, Port) ->
connect(Host, Port).
connect(Host) ->
connect(Host, 6379).
connect(Host, Port) ->
gen_server:start_link(?MODULE, [Host, Port], []).
ssend(Client, Cmd) -> ssend(Client, Cmd, []).
ssend(Client, Cmd, Args) ->
gen_server:cast(Client, {send, sformat([Cmd|Args]), get_parser(Cmd)}).
send(Client, Cmd) -> send(Client, Cmd, []).
send(Client, Cmd, Args) ->
gen_server:cast(Client, {send,
string:join([str(Cmd), format(Args)], " "), get_parser(Cmd)}).
asend(Client, Cmd) ->
gen_server:cast(Client, {asend, Cmd}).
disconnect(Client) ->
gen_server:call(Client, disconnect).
get_all_results(Client) ->
gen_server:call(Client, get_all_results).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% gen_server callbacks
init([Host, Port]) ->
process_flag(trap_exit, true),
ConnectOptions = [list, {active, once}, {packet, line}, {nodelay, true}],
case gen_tcp:connect(Host, Port, ConnectOptions) of
{error, Why} ->
{error, {socket_error, Why}};
{ok, Socket} ->
{ok, #redis{socket=Socket, parsers=queue:new()}}
end.
handle_call({send, Cmd, Parser}, From, State=#redis{parsers=Parsers}) ->
gen_tcp:send(State#redis.socket, [Cmd|?EOL]),
{noreply, State#redis{reply_caller=fun(V) -> gen_server:reply(From, lists:nth(1, V)) end,
parsers=queue:in(Parser, Parsers), remaining=1}};
handle_call(disconnect, _From, State) ->
{stop, normal, ok, State};
handle_call(get_all_results, From, State) ->
case queue:is_empty(State#redis.parsers) of
true ->
% answers came earlier than we could start listening...
% Very unlikely but totally possible.
{reply, lists:reverse(State#redis.results), State#redis{results=[]}};
false ->
% We are here earlier than results came, so just make
% ourselves wait until stuff is ready.
{noreply, State#redis{reply_caller=fun(V) -> gen_server:reply(From, V) end}}
end;
handle_call(_, _From, State) -> {noreply, State}.
handle_cast({asend, Cmd}, State) ->
gen_tcp:send(State#redis.socket, [Cmd|?EOL]),
{noreply, State};
handle_cast({send, Cmd, Parser}, State=#redis{parsers=Parsers, remaining=Remaining}) ->
% how we should do here: if remaining is already != 0 then we'll
% let handle_info take care of keeping track how many remaining things
% there are. If instead it's 0 we are the first call so let's just
% do it.
gen_tcp:send(State#redis.socket, [Cmd|?EOL]),
NewParsers = queue:in(Parser, Parsers),
case Remaining of
0 ->
{noreply, State#redis{remaining=1, parsers=NewParsers}};
_ ->
{noreply, State#redis{parsers=NewParsers}}
end;
handle_cast(_Msg, State) -> {noreply, State}.
trim2({ok, S}) ->
string:substr(S, 1, length(S)-2);
trim2(S) ->
trim2({ok, S}).
% This is useful to know if there are more messages still coming.
get_remaining(ParsersQueue) ->
case queue:is_empty(ParsersQueue) of
true -> 0;
false -> 1
end.
% This function helps with pipelining by creating a pubsub system with
% the caller. The caller could submit multiple requests and not listen
% until later when all or some of them have been answered, at that
% point 2 conditions can be true:
% 1) We still need to process more things in this response chain
% 2) We are finished.
%
% And these 2 are together with the following 2:
% 1) We called get_all_results before the end of the responses.
% 2) We called get_all_results after the end of the responses.
%
% If there's stuff missing in the chain we just push results, this also
% happens when there's nothing more to process BUT we haven't requested
% results yet.
% In case we have requested results: if requests are not yet ready we
% just push them, otherwise we finally answer all of them.
save_or_reply(Result, State=#redis{results=Results, reply_caller=ReplyCaller, parsers=Parsers}) ->
case get_remaining(Parsers) of
1 ->
State#redis{results=[Result|Results], remaining=1, pstate=empty, buffer=[]};
0 ->
% We don't reverse results here because if all the requests
% come in and then we submit another one, if we reverse
% they will be scrambled in the results field of the record.
% instead if we wait just before we reply they will be
% in the right order.
FullResults = [Result|Results],
NewState = case ReplyCaller of
undefined ->
State#redis{results=FullResults};
_ ->
ReplyCaller(lists:reverse(FullResults)),
State#redis{results=[]}
end,
NewState#redis{remaining=0, pstate=empty,
reply_caller=undefined, buffer=[],
parsers=Parsers}
end.
handle_info({tcp, Socket, Data}, State) ->
{{value, Parser}, NewParsers} = queue:out(State#redis.parsers),
Trimmed = trim2(Data),
NewState = case {State#redis.remaining-1, Parser(State#redis.pstate, Trimmed)} of
% This line contained an error code. Next line will hold
% The error message that we will parse.
{0, error} ->
% reinsert the parser in the front, next step is still gonna be needed
State#redis{remaining=1, pstate=error,
parsers=queue:in_r(Parser, NewParsers)};
% The stateful parser just started and tells us the number
% of results that we will have to parse for those calls
% where more than one result is expected. The next
% line will start with the first item to read.
{0, {hold, Remaining}} ->
% Reset the remaining value to the number of results
% that we need to parse.
% and reinsert the parser in the front, next step is still gonna be needed
State#redis{remaining=Remaining, pstate=read,
parsers=queue:in_r(Parser, NewParsers)};
% We either had only one thing to read or we are at the
% end of the stuff that we need to read. either way
% just pack up the buffer and send.
{0, {read, NBytes}} ->
inet:setopts(Socket, [{packet, 0}]), % go into raw mode to read bytes
CurrentValue = trim2(gen_tcp:recv(Socket, NBytes+2)), % also consume the \r\n
inet:setopts(Socket, [{packet, line}]), % go back to line mode
OldBuffer = State#redis.buffer,
case OldBuffer of
[] ->
save_or_reply(CurrentValue, State#redis{parsers=NewParsers});
_ ->
save_or_reply(lists:reverse([CurrentValue|OldBuffer]), State#redis{parsers=NewParsers})
end;
% The stateful parser tells us to read some bytes
{N, {read, NBytes}} ->
inet:setopts(Socket, [{packet, 0}]), % go into raw mode to read bytes
CurrentValue = trim2(gen_tcp:recv(Socket, NBytes+2)), % also consume the \r\n
inet:setopts(Socket, [{packet, line}]), % go back to line mode
OldBuffer = State#redis.buffer,
State#redis{remaining=N, buffer=[CurrentValue|OldBuffer],
pstate=read, parsers=queue:in_r(Parser, NewParsers)};
% Simple return values contained in a single line
{0, Value} ->
save_or_reply(Value, State#redis{parsers=NewParsers})
end,
inet:setopts(Socket, [{active, once}]),
{noreply, NewState};
handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, State) ->
case State#redis.socket of
undefined ->
pass;
Socket ->
gen_tcp:close(Socket)
end,
ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

View File

@ -0,0 +1,82 @@
-module(erldis).
-compile(export_all).
-define(EOL, "\r\n").
%% helpers
flatten({error, Message}) ->
{error, Message};
flatten(List) when is_list(List)->
lists:flatten(List).
%% exposed API
connect(Host) ->
client:connect(Host).
quit(Client) ->
client:asend(Client, "QUIT"),
client:disconnect(Client).
%% Commands operating on string values
internal_set_like(Client, Command, Key, Value) ->
client:send(Client, Command, [[Key, length(Value)],
[Value]]).
get_all_results(Client) -> client:get_all_results(Client).
set(Client, Key, Value) -> internal_set_like(Client, set, Key, Value).
setnx(Client, Key, Value) -> internal_set_like(Client, setnx, Key, Value).
incr(Client, Key) -> client:ssend(Client, incr, [Key]).
incrby(Client, Key, By) -> client:ssend(Client, incrby, [Key, By]).
decr(Client, Key) -> client:ssend(Client, decr, [Key]).
decrby(Client, Key, By) -> client:ssend(Client, decrby, [Key, By]).
get(Client, Key) -> client:ssend(Client, get, [Key]).
%% Commands operating on every value
exists(Client, Key) -> client:ssend(Client, exists, [Key]).
del(Client, Key) -> client:ssend(Client, del, [Key]).
type(Client, Key) -> client:ssend(Client, type, [Key]).
keys(Client, Pattern) -> client:ssend(Client, keys, [Pattern]).
randomkey(Client, Key) -> client:ssend(Client, randomkey, [Key]).
rename(Client, OldKey, NewKey) -> client:ssend(Client, rename, [OldKey, NewKey]).
renamenx(Client, OldKey, NewKey) -> client:ssend(Client, renamenx, [OldKey, NewKey]).
%% Commands operating on both lists and sets
sort(Client, Key) -> client:ssend(Client, sort, [Key]).
sort(Client, Key, Extra) -> client:ssend(Client, sort, [Key, Extra]).
%% Commands operating on lists
rpush(Client, Key, Value) -> internal_set_like(Client, rpush, Key, Value).
lpush(Client, Key, Value) -> internal_set_like(Client, lpush, Key, Value).
llen(Client, Key) -> client:ssend(Client, llen, [Key]).
lrange(Client, Key, Start, End) -> client:ssend(Client, lrange, [Key, Start, End]).
ltrim(Client, Key, Start, End) -> client:ssend(Client, ltrim, [Key, Start, End]).
lindex(Client, Key, Index) -> client:ssend(Client, lindex, [Key, Index]).
lpop(Client, Key) -> client:ssend(Client, lpop, [Key]).
rpop(Client, Key) -> client:ssend(Client, rpop, [Key]).
lrem(Client, Key, Number, Value) ->
client:send(Client, lrem, [[Key, Number, length(Value)],
[Value]]).
lset(Client, Key, Index, Value) ->
client:send(Client, lset, [[Key, Index, length(Value)],
[Value]]).
%% Commands operating on sets
sadd(Client, Key, Value) -> internal_set_like(Client, sadd, Key, Value).
srem(Client, Key, Value) -> internal_set_like(Client, srem, Key, Value).
scard(Client, Key) -> client:ssend(Client, scard, [Key]).
sismember(Client, Key, Value) -> internal_set_like(Client, sismember, Key, Value).
sintersect(Client, Keys) -> client:ssend(Client, sinter, Keys).
smembers(Client, Key) -> client:ssend(Client, smembers, [Key]).
%% Multiple DB commands
flushdb(Client) -> client:ssend(Client, flushdb).
flushall(Client) -> client:ssend(Client, flushall).
select(Client, Index) -> client:ssend(Client, select, [Index]).
move(Client, Key, DBIndex) -> client:ssend(Client, move, [Key, DBIndex]).
save(Client) -> client:ssend(Client, save).
bgsave(Client) -> client:ssend(Client, bgsave).
lastsave(Client) -> client:ssend(Client, lastsave).
shutdown(Client) -> client:asend(Client, shutdown).

View File

@ -0,0 +1,68 @@
-module(proto).
-export([parse/2, parse_int/2, parse_types/2,
parse_string/2, stateful_parser/2,
single_stateful_parser/2, parse_special/2]).
parse(empty, "+OK") ->
ok;
parse(empty, "+PONG") ->
pong;
parse(empty, "0") ->
false;
parse(empty, "1") ->
true;
parse(empty, "-1") ->
{error, no_such_key};
parse(empty, "-2") ->
{error, wrong_type};
parse(empty, "-3") ->
{error, same_db};
parse(empty, "-4") ->
{error, argument_out_of_range};
parse(empty, "-" ++ Message) ->
{error, Message}.
parse_special(empty, "-1") ->
parse(empty, "-1");
parse_special(empty, "-2") ->
parse(empty, "-2");
parse_special(empty, N) ->
list_to_integer(N).
parse_int(empty, "-ERR " ++ Message) ->
{error, Message};
parse_int(empty, Value) ->
list_to_integer(Value).
parse_string(empty, Message) ->
Message.
parse_types(empty, "none") -> none;
parse_types(empty, "string") -> string;
parse_types(empty, "list") -> list;
parse_types(empty, "set") -> set.
% I'm used when redis returns multiple results
stateful_parser(empty, "nil") ->
nil;
stateful_parser(error, "-ERR " ++ Error) ->
{error, Error};
stateful_parser(empty, "-" ++ _ErrorLength) ->
error;
stateful_parser(empty, NumberOfElements) ->
{hold, list_to_integer(NumberOfElements)};
stateful_parser(read, ElementSize) ->
{read, list_to_integer(ElementSize)}.
% I'm used when redis returns just one result
single_stateful_parser(empty, "nil") ->
nil;
single_stateful_parser(error, "-ERR " ++ Error) ->
{error, Error};
single_stateful_parser(empty, "-" ++ _ErrorLength) ->
error;
single_stateful_parser(empty, ElementSize) ->
{read, list_to_integer(ElementSize)}.

View File

@ -0,0 +1,51 @@
## -*- makefile -*-
ERL := erl
ERLC := $(ERL)c
INCLUDE_DIRS := ../include $(wildcard ../deps/*/include)
EBIN_DIRS := $(wildcard ../deps/*/ebin)
ERLC_FLAGS := -W $(INCLUDE_DIRS:../%=-I ../%) $(EBIN_DIRS:%=-pa %)
ifndef no_debug_info
ERLC_FLAGS += +debug_info
endif
ifdef debug
ERLC_FLAGS += -Ddebug
endif
ifdef test
ERLC_FLAGS += -DTEST
endif
EBIN_DIR := ../ebin
DOC_DIR := ../doc
EMULATOR := beam
ERL_TEMPLATE := $(wildcard *.et)
ERL_SOURCES := $(wildcard *.erl)
ERL_HEADERS := $(wildcard *.hrl) $(wildcard ../include/*.hrl)
ERL_OBJECTS := $(ERL_SOURCES:%.erl=$(EBIN_DIR)/%.beam)
ERL_TEMPLATES := $(ERL_TEMPLATE:%.et=$(EBIN_DIR)/%.beam)
ERL_OBJECTS_LOCAL := $(ERL_SOURCES:%.erl=./%.$(EMULATOR))
APP_FILES := $(wildcard *.app)
EBIN_FILES = $(ERL_OBJECTS) $(APP_FILES:%.app=../ebin/%.app) $(ERL_TEMPLATES)
MODULES = $(ERL_SOURCES:%.erl=%)
../ebin/%.app: %.app
cp $< $@
$(EBIN_DIR)/%.$(EMULATOR): %.erl
$(ERLC) $(ERLC_FLAGS) -o $(EBIN_DIR) $<
$(EBIN_DIR)/%.$(EMULATOR): %.et
$(ERL) -noshell -pa ../../elib/erltl/ebin/ -eval "erltl:compile(atom_to_list('$<'), [{outdir, \"../ebin\"}, report_errors, report_warnings, nowarn_unused_vars])." -s init stop
./%.$(EMULATOR): %.erl
$(ERLC) $(ERLC_FLAGS) -o . $<
$(DOC_DIR)/%.html: %.erl
$(ERL) -noshell -run edoc file $< -run init stop
mv *.html $(DOC_DIR)

View File

@ -0,0 +1,12 @@
include ../support/include.mk
all: $(EBIN_FILES)
clean:
rm -rf $(EBIN_FILES) erl_crash.dump
test: $(MODULES)
./$(MODULES):
@echo "Running tests for $@"
erl -pa ../ebin -run $@ test -run init stop -noshell

View File

@ -0,0 +1,88 @@
-module(erldis_tests).
-include_lib("eunit/include/eunit.hrl").
-include("erldis.hrl").
quit_test() ->
{ok, Client} = erldis:connect("localhost"),
ok = erldis:quit(Client),
false = is_process_alive(Client).
utils_test() ->
?assertEqual(client:str(1), "1"),
?assertEqual(client:str(atom), "atom"),
?assertEqual(client:format([[1, 2, 3]]), "1 2 3"),
?assertEqual(client:format([[1,2,3], [4,5,6]]), "1 2 3\r\n4 5 6").
pipeline_test() ->
{ok, Client} = erldis:connect("localhost"),
erldis:flushall(Client),
erldis:get(Client, "pippo"),
erldis:set(Client, "hello", "kitty!"),
erldis:setnx(Client, "foo", "bar"),
erldis:setnx(Client, "foo", "bar"),
[ok, nil, ok, true, false] = erldis:get_all_results(Client),
erldis:exists(Client, "hello"),
erldis:exists(Client, "foo"),
erldis:get(Client, "foo"),
erldis:del(Client, "hello"),
erldis:del(Client, "foo"),
erldis:exists(Client, "hello"),
erldis:exists(Client, "foo"),
[true, true, "bar", true, true, false, false] = erldis:get_all_results(Client),
erldis:set(Client, "pippo", "pluto"),
erldis:sadd(Client, "pippo", "paperino"),
% foo doesn't exist, the result will be nil
erldis:lrange(Client, "foo", 1, 2),
erldis:lrange(Client, "pippo", 1, 2),
[ok, {error, wrong_type}, nil,
{error, "Operation against a key holding the wrong kind of value"}
] = erldis:get_all_results(Client),
erldis:del(Client, "pippo"),
[true] = erldis:get_all_results(Client),
erldis:rpush(Client, "a_list", "1"),
erldis:rpush(Client, "a_list", "2"),
erldis:rpush(Client, "a_list", "3"),
erldis:rpush(Client, "a_list", "1"),
erldis:lrem(Client, "a_list", 1, "1"),
erldis:lrange(Client, "a_list", 0, 2),
[ok, ok, ok, ok, 1, ["2", "3", "1"]] = erldis:get_all_results(Client),
erldis:sort(Client, "a_list"),
erldis:sort(Client, "a_list", "DESC"),
erldis:lrange(Client, "a_list", 0, 2),
erldis:sort(Client, "a_list", "LIMIT 0 2 ASC"),
[["1", "2", "3"], ["3", "2", "1"], ["2", "3", "1"],
["1", "2"]] = erldis:get_all_results(Client),
ok = erldis:quit(Client).
% inline_tests(Client) ->
% [?_assertMatch(ok, erldis:set(Client, "hello", "kitty!")),
% ?_assertMatch(false, erldis:setnx(Client, "hello", "kitty!")),
% ?_assertMatch(true, erldis:exists(Client, "hello")),
% ?_assertMatch(true, erldis:del(Client, "hello")),
% ?_assertMatch(false, erldis:exists(Client, "hello")),
%
% ?_assertMatch(true, erldis:setnx(Client, "hello", "kitty!")),
% ?_assertMatch(true, erldis:exists(Client, "hello")),
% ?_assertMatch("kitty!", erldis:get(Client, "hello")),
% ?_assertMatch(true, erldis:del(Client, "hello")),
%
%
% ?_assertMatch(1, erldis:incr(Client, "pippo"))
% ,?_assertMatch(2, erldis:incr(Client, "pippo"))
% ,?_assertMatch(1, erldis:decr(Client, "pippo"))
% ,?_assertMatch(0, erldis:decr(Client, "pippo"))
% ,?_assertMatch(-1, erldis:decr(Client, "pippo"))
%
% ,?_assertMatch(6, erldis:incrby(Client, "pippo", 7))
% ,?_assertMatch(2, erldis:decrby(Client, "pippo", 4))
% ,?_assertMatch(-2, erldis:decrby(Client, "pippo", 4))
% ,?_assertMatch(true, erldis:del(Client, "pippo"))
% ].

View File

@ -0,0 +1,10 @@
-module(proto_tests).
-include_lib("eunit/include/eunit.hrl").
parse_test() ->
ok = proto:parse(empty, "+OK"),
pong = proto:parse(empty, "+PONG"),
false = proto:parse(empty, "0"),
true = proto:parse(empty, "1"),
{error, no_such_key} = proto:parse(empty, "-1").

View File

@ -0,0 +1,330 @@
<?php
/*******************************************************************************
* Redis PHP Bindings - http://code.google.com/p/redis/
*
* Copyright 2009 Ludovico Magnocavallo
* Released under the same license as Redis.
*
* Version: 0.1
*
* $Revision: 139 $
* $Date: 2009-03-15 22:59:40 +0100 (Dom, 15 Mar 2009) $
*
******************************************************************************/
class Redis {
var $server;
var $port;
var $_sock;
function Redis($host, $port=6379) {
$this->host = $host;
$this->port = $port;
}
function connect() {
if ($this->_sock)
return;
if ($sock = fsockopen($this->host, $this->port, $errno, $errstr)) {
$this->_sock = $sock;
return;
}
$msg = "Cannot open socket to {$this->host}:{$this->port}";
if ($errno || $errmsg)
$msg .= "," . ($errno ? " error $errno" : "") . ($errmsg ? " $errmsg" : "");
trigger_error("$msg.", E_USER_ERROR);
}
function disconnect() {
if ($this->_sock)
@fclose($this->_sock);
$this->_sock = null;
}
function &ping() {
$this->connect();
$this->_write("PING\r\n");
return $this->_simple_response();
}
function &do_echo($s) {
$this->connect();
$this->_write("ECHO " . strlen($s) . "\r\n$s\r\n");
return $this->_get_value();
}
function &set($name, $value, $preserve=false) {
$this->connect();
$this->_write(
($preserve ? 'SETNX' : 'SET') .
" $name " . strlen($value) . "\r\n$value\r\n"
);
return $preserve ? $this->_numeric_response() : $this->_simple_response();
}
function &get($name) {
$this->connect();
$this->_write("GET $name\r\n");
return $this->_get_value();
}
function &incr($name, $amount=1) {
$this->connect();
if ($amount == 1)
$this->_write("INCR $name\r\n");
else
$this->_write("INCRBY $name $amount\r\n");
return $this->_numeric_response();
}
function &decr($name, $amount=1) {
$this->connect();
if ($amount == 1)
$this->_write("DECR $name\r\n");
else
$this->_write("DECRBY $name $amount\r\n");
return $this->_numeric_response();
}
function &exists($name) {
$this->connect();
$this->_write("EXISTS $name\r\n");
return $this->_numeric_response();
}
function &delete($name) {
$this->connect();
$this->_write("DEL $name\r\n");
return $this->_numeric_response();
}
function &keys($pattern) {
$this->connect();
$this->_write("KEYS $pattern\r\n");
return explode(' ', $this->_get_value());
}
function &randomkey() {
$this->connect();
$this->_write("RANDOMKEY\r\n");
$s =& trim($this->_read());
$this->_check_for_error($s);
return $s;
}
function &rename($src, $dst, $preserve=False) {
$this->connect();
if ($preserve) {
$this->_write("RENAMENX $src $dst\r\n");
return $this->_numeric_response();
}
$this->_write("RENAME $src $dst\r\n");
return trim($this->_simple_response());
}
function &push($name, $value, $tail=true) {
// default is to append the element to the list
$this->connect();
$this->_write(
($tail ? 'RPUSH' : 'LPUSH') .
" $name " . strlen($value) . "\r\n$value\r\n"
);
return $this->_simple_response();
}
function &ltrim($name, $start, $end) {
$this->connect();
$this->_write("LTRIM $name $start $end\r\n");
return $this->_simple_response();
}
function &lindex($name, $index) {
$this->connect();
$this->_write("LINDEX $name $index\r\n");
return $this->_get_value();
}
function &pop($name, $tail=true) {
$this->connect();
$this->_write(
($tail ? 'RPOP' : 'LPOP') .
" $name\r\n"
);
return $this->_get_value();
}
function &llen($name) {
$this->connect();
$this->_write("LLEN $name\r\n");
return $this->_numeric_response();
}
function &lrange($name, $start, $end) {
$this->connect();
$this->_write("LRANGE $name $start $end\r\n");
return $this->_get_multi();
}
function &sort($name, $query=false) {
$this->connect();
if ($query === false) {
$this->_write("SORT $name\r\n");
} else {
$this->_write("SORT $name $query\r\n");
}
return $this->_get_multi();
}
function &lset($name, $value, $index) {
$this->connect();
$this->_write("LSET $name $index " . strlen($value) . "\r\n$value\r\n");
return $this->_simple_response();
}
function &sadd($name, $value) {
$this->connect();
$this->_write("SADD $name " . strlen($value) . "\r\n$value\r\n");
return $this->_numeric_response();
}
function &srem($name, $value) {
$this->connect();
$this->_write("SREM $name " . strlen($value) . "\r\n$value\r\n");
return $this->_numeric_response();
}
function &sismember($name, $value) {
$this->connect();
$this->_write("SISMEMBER $name " . strlen($value) . "\r\n$value\r\n");
return $this->_numeric_response();
}
function &sinter($sets) {
$this->connect();
$this->_write('SINTER ' . implode(' ', $sets) . "\r\n");
return $this->_get_multi();
}
function &smembers($name) {
$this->connect();
$this->_write("SMEMBERS $name\r\n");
return $this->_get_multi();
}
function &scard($name) {
$this->connect();
$this->_write("SCARD $name\r\n");
return $this->_numeric_response();
}
function &select_db($name) {
$this->connect();
$this->_write("SELECT $name\r\n");
return $this->_simple_response();
}
function &move($name, $db) {
$this->connect();
$this->_write("MOVE $name $db\r\n");
return $this->_numeric_response();
}
function &save($background=false) {
$this->connect();
$this->_write(($background ? "BGSAVE\r\n" : "SAVE\r\n"));
return $this->_simple_response();
}
function &lastsave() {
$this->connect();
$this->_write("LASTSAVE\r\n");
return $this->_numeric_response();
}
function &_write($s) {
while ($s) {
$i = fwrite($this->_sock, $s);
if ($i == 0)
break;
$s = substr($s, $i);
}
}
function &_read($len=1024) {
if ($s = fgets($this->_sock))
return $s;
$this->disconnect();
trigger_error("Cannot read from socket.", E_USER_ERROR);
}
function _check_for_error(&$s) {
if (!$s || $s[0] != '-')
return;
if (substr($s, 0, 4) == '-ERR')
trigger_error("Redis error: " . trim(substr($s, 4)), E_USER_ERROR);
trigger_error("Redis error: " . substr(trim($this->_read()), 5), E_USER_ERROR);
}
function &_simple_response() {
$s =& trim($this->_read());
if ($s[0] == '+')
return substr($s, 1);
if ($err =& $this->_check_for_error($s))
return $err;
trigger_error("Cannot parse first line '$s' for a simple response", E_USER_ERROR);
}
function &_numeric_response($allow_negative=True) {
$s =& trim($this->_read());
$i = (int)$s;
if ($i . '' == $s) {
if (!$allow_negative && $i < 0)
$this->_check_for_error($s);
return $i;
}
if ($s == 'nil')
return null;
trigger_error("Cannot parse '$s' as numeric response.");
}
function &_get_value() {
$s =& trim($this->_read());
if ($s == 'nil')
return '';
else if ($s[0] == '-')
$this->_check_for_error($s);
$i = (int)$s;
if ($i . '' != $s)
trigger_error("Cannot parse '$s' as data length.");
$buffer = '';
while ($i > 0) {
$s = $this->_read();
$l = strlen($s);
$i -= $l;
if ($l > $i) // ending crlf
$s = rtrim($s);
$buffer .= $s;
}
if ($i == 0) // let's restore the trailing crlf
$buffer .= $this->_read();
return $buffer;
}
function &_get_multi() {
$results = array();
$num =& $this->_numeric_response(false);
if ($num === false)
return $results;
while ($num) {
$results[] =& $this->_get_value();
$num -= 1;
}
return $results;
}
}
?>

View File

@ -0,0 +1,78 @@
<?php
// poor man's tests
require_once('redis.php');
$r =& new Redis('localhost');
$r->connect();
echo $r->ping() . "\n";
echo $r->do_echo('ECHO test') . "\n";
echo "SET aaa " . $r->set('aaa', 'bbb') . "\n";
echo "SETNX aaa " . $r->set('aaa', 'ccc', true) . "\n";
echo "GET aaa " . $r->get('aaa') . "\n";
echo "INCR aaa " . $r->incr('aaa') . "\n";
echo "GET aaa " . $r->get('aaa') . "\n";
echo "INCRBY aaa 3 " . $r->incr('aaa', 2) . "\n";
echo "GET aaa " . $r->get('aaa') . "\n";
echo "DECR aaa " . $r->decr('aaa') . "\n";
echo "GET aaa " . $r->get('aaa') . "\n";
echo "DECRBY aaa 2 " . $r->decr('aaa', 2) . "\n";
echo "GET aaa " . $r->get('aaa') . "\n";
echo "EXISTS aaa " . $r->exists('aaa') . "\n";
echo "EXISTS fsfjslfjkls " . $r->exists('fsfjslfjkls') . "\n";
echo "DELETE aaa " . $r->delete('aaa') . "\n";
echo "EXISTS aaa " . $r->exists('aaa') . "\n";
echo 'SET a1 a2 a3' . $r->set('a1', 'a') . $r->set('a2', 'b') . $r->set('a3', 'c') . "\n";
echo 'KEYS a* ' . print_r($r->keys('a*'), true) . "\n";
echo 'RANDOMKEY ' . $r->randomkey('a*') . "\n";
echo 'RENAME a1 a0 ' . $r->rename('a1', 'a0') . "\n";
echo 'RENAMENX a0 a2 ' . $r->rename('a0', 'a2', true) . "\n";
echo 'RENAMENX a0 a1 ' . $r->rename('a0', 'a1', true) . "\n";
echo 'LPUSH a0 aaa ' . $r->push('a0', 'aaa') . "\n";
echo 'LPUSH a0 bbb ' . $r->push('a0', 'bbb') . "\n";
echo 'RPUSH a0 ccc ' . $r->push('a0', 'ccc', false) . "\n";
echo 'LLEN a0 ' . $r->llen('a0') . "\n";
echo 'LRANGE sdkjhfskdjfh 0 100 ' . print_r($r->lrange('sdkjhfskdjfh', 0, 100), true) . "\n";
echo 'LRANGE a0 0 0 ' . print_r($r->lrange('sdkjhfskdjfh', 0, 0), true) . "\n";
echo 'LRANGE a0 0 100 ' . print_r($r->lrange('a0', 0, 100), true) . "\n";
echo 'LTRIM a0 0 1 ' . $r->ltrim('a0', 0, 1) . "\n";
echo 'LRANGE a0 0 100 ' . print_r($r->lrange('a0', 0, 100), true) . "\n";
echo 'LINDEX a0 0 ' . $r->lindex('a0', 0) . "\n";
echo 'LPUSH a0 bbb ' . $r->push('a0', 'bbb') . "\n";
echo 'LRANGE a0 0 100 ' . print_r($r->lrange('a0', 0, 100), true) . "\n";
echo 'RPOP a0 ' . $r->pop('a0') . "\n";
echo 'LPOP a0 ' . $r->pop('a0', false) . "\n";
echo 'LSET a0 ccc 0 ' . $r->lset('a0', 'ccc', 0) . "\n";
echo 'LRANGE a0 0 100 ' . print_r($r->lrange('a0', 0, 100), true) . "\n";
echo 'SADD s0 aaa ' . $r->sadd('s0', 'aaa') . "\n";
echo 'SADD s0 aaa ' . $r->sadd('s0', 'aaa') . "\n";
echo 'SADD s0 bbb ' . $r->sadd('s0', 'bbb') . "\n";
echo 'SREM s0 bbb ' . $r->srem('s0', 'bbb') . "\n";
echo 'SISMEMBER s0 aaa ' . $r->sismember('s0', 'aaa') . "\n";
echo 'SISMEMBER s0 bbb ' . $r->sismember('s0', 'bbb') . "\n";
echo 'SADD s0 bbb ' . $r->sadd('s0', 'bbb') . "\n";
echo 'SADD s1 bbb ' . $r->sadd('s1', 'bbb') . "\n";
echo 'SADD s1 aaa ' . $r->sadd('s1', 'aaa') . "\n";
echo 'SINTER s0 s1 ' . print_r($r->sinter(array('s0', 's1')), true) . "\n";
echo 'SREM s0 bbb ' . $r->srem('s0', 'bbb') . "\n";
echo 'SINTER s0 s1 ' . print_r($r->sinter(array('s0', 's1')), true) . "\n";
echo 'SMEMBERS s1 ' . print_r($r->smembers('s1'), true) . "\n";
echo 'SELECT 1 ' . $r->select_db(1) . "\n";
echo 'SMEMBERS s1 ' . print_r($r->smembers('s1'), true) . "\n";
echo 'SELECT 0 ' . $r->select_db(0) . "\n";
echo 'SMEMBERS s1 ' . print_r($r->smembers('s1'), true) . "\n";
echo 'MOVE s1 1 ' . $r->move('s1', 1) . "\n";
echo 'SMEMBERS s1 ' . print_r($r->smembers('s1'), true) . "\n";
echo 'SELECT 1 ' . $r->select_db(1) . "\n";
echo 'SMEMBERS s1 ' . print_r($r->smembers('s1'), true) . "\n";
echo 'SELECT 0 ' . $r->select_db(0) . "\n";
echo 'SAVE ' . $r->save() . "\n";
echo 'BGSAVE ' . $r->save(true) . "\n";
echo 'LASTSAVE ' . $r->lastsave() . "\n";
?>

View File

@ -0,0 +1,930 @@
#!/usr/bin/python
""" redis.py - A client for the Redis daemon.
"""
__author__ = "Ludovico Magnocavallo <ludo\x40qix\x2eit>"
__copyright__ = "Copyright 2009, Ludovico Magnocavallo"
__license__ = "MIT"
__version__ = "0.5"
__revision__ = "$LastChangedRevision: 175 $"[22:-2]
__date__ = "$LastChangedDate: 2009-03-17 16:15:55 +0100 (Mar, 17 Mar 2009) $"[18:-2]
# TODO: Redis._get_multi_response
import socket
BUFSIZE = 4096
class RedisError(Exception): pass
class ConnectionError(RedisError): pass
class ResponseError(RedisError): pass
class InvalidResponse(RedisError): pass
class InvalidData(RedisError): pass
class Redis(object):
"""The main Redis client.
"""
def __init__(self, host=None, port=None, timeout=None):
self.host = host or 'localhost'
self.port = port or 6379
if timeout:
socket.setdefaulttimeout(timeout)
self._sock = None
self._fp = None
def _write(self, s):
"""
>>> r = Redis()
>>> r.connect()
>>> r._sock.close()
>>> try:
... r._write('pippo')
... except ConnectionError, e:
... print e
Error 9 while writing to socket. Bad file descriptor.
>>>
>>>
"""
try:
self._sock.sendall(s)
except socket.error, e:
if e.args[0] == 32:
# broken pipe
self.disconnect()
raise ConnectionError("Error %s while writing to socket. %s." % tuple(e.args))
def _read(self):
try:
return self._fp.readline()
except socket.error, e:
if e.args and e.args[0] == errno.EAGAIN:
return
self.disconnect()
raise ConnectionError("Error %s while reading from socket. %s." % tuple(e.args))
if not data:
self.disconnect()
raise ConnectionError("Socket connection closed when reading.")
return data
def ping(self):
"""
>>> r = Redis()
>>> r.ping()
'PONG'
>>>
"""
self.connect()
self._write('PING\r\n')
return self._get_simple_response()
def set(self, name, value, preserve=False):
"""
>>> r = Redis()
>>> r.set('a', 'pippo')
'OK'
>>> try:
... r.set('a', u'pippo \u3235')
... except InvalidData, e:
... print e
Error encoding unicode value for key 'a': 'ascii' codec can't encode character u'\u3235' in position 15: ordinal not in range(128).
>>> r.set('b', 105.2)
'OK'
>>> r.set('b', 'xxx', preserve=True)
0
>>> r.get('b')
'105.2'
>>>
"""
self.connect()
# the following will raise an error for unicode values that can't be encoded to ascii
# we could probably add an 'encoding' arg to init, but then what do we do with get()?
# convert back to unicode? and what about ints, or pickled values?
try:
value = value if isinstance(value, basestring) else str(value)
self._write('%s %s %s\r\n%s\r\n' % (
'SETNX' if preserve else 'SET', name, len(value), value
))
except UnicodeEncodeError, e:
raise InvalidData("Error encoding unicode value for key '%s': %s." % (name, e))
return self._get_numeric_response() if preserve else self._get_simple_response()
def get(self, name):
"""
>>> 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.get('a')
'pippo'
>>> r.get('b')
'15'
>>> r.get('d')
'\\r\\n'
>>> r.get('b')
'15'
>>> r.get('c')
'\\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n'
>>> r.get('c')
'\\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n'
>>> r.get('ajhsd')
>>>
"""
self.connect()
self._write('GET %s\r\n' % name)
return self._get_value()
def incr(self, name, amount=1):
"""
>>> r = Redis()
>>> r.delete('a')
1
>>> r.incr('a')
1
>>> r.incr('a')
2
>>> r.incr('a', 2)
4
>>>
"""
self.connect()
if amount == 1:
self._write('INCR %s\r\n' % name)
else:
self._write('INCRBY %s %s\r\n' % (name, amount))
return self._get_numeric_response()
def decr(self, name, amount=1):
"""
>>> r = Redis()
>>> if r.get('a'):
... r.delete('a')
... else:
... print 1
1
>>> r.decr('a')
-1
>>> r.decr('a')
-2
>>> r.decr('a', 5)
-7
>>>
"""
self.connect()
if amount == 1:
self._write('DECR %s\r\n' % name)
else:
self._write('DECRBY %s %s\r\n' % (name, amount))
return self._get_numeric_response()
def exists(self, name):
"""
>>> r = Redis()
>>> r.exists('dsjhfksjdhfkdsjfh')
0
>>> r.set('a', 'a')
'OK'
>>> r.exists('a')
1
>>>
"""
self.connect()
self._write('EXISTS %s\r\n' % name)
return self._get_numeric_response()
def delete(self, name):
"""
>>> r = Redis()
>>> r.delete('dsjhfksjdhfkdsjfh')
0
>>> r.set('a', 'a')
'OK'
>>> r.delete('a')
1
>>> r.exists('a')
0
>>> r.delete('a')
0
>>>
"""
self.connect()
self._write('DEL %s\r\n' % name)
return self._get_numeric_response()
def key_type(self, name):
"""
Not yet implemented.
"""
self.connect()
self._write('TYPE %s\r\n' % name)
return self._get_simple_response()
def keys(self, pattern):
"""
>>> r = Redis()
>>> r.flush()
'OK'
>>> r.set('a', 'a')
'OK'
>>> r.keys('a*')
['a']
>>> r.set('a2', 'a')
'OK'
>>> r.keys('a*')
['a', 'a2']
>>> r.delete('a2')
1
>>> r.keys('sjdfhskjh*')
[]
>>>
"""
self.connect()
self._write('KEYS %s\r\n' % pattern)
return self._get_value().split()
def randomkey(self):
"""
>>> r = Redis()
>>> r.set('a', 'a')
'OK'
>>> isinstance(r.randomkey(), str)
True
>>>
"""
#raise NotImplementedError("Implemented but buggy, do not use.")
self.connect()
self._write('RANDOMKEY\r\n')
data = self._read().strip()
self._check_for_error(data)
return data
def rename(self, src, dst, preserve=False):
"""
>>> r = Redis()
>>> try:
... r.rename('a', 'a')
... except ResponseError, e:
... print e
src and dest key are the same
>>> r.rename('a', 'b')
'OK'
>>> try:
... r.rename('a', 'b')
... except ResponseError, e:
... print e
no such key
>>> r.set('a', 1)
'OK'
>>> r.rename('b', 'a', preserve=True)
0
>>>
"""
self.connect()
if preserve:
self._write('RENAMENX %s %s\r\n' % (src, dst))
return self._get_numeric_response()
else:
self._write('RENAME %s %s\r\n' % (src, dst))
return self._get_simple_response().strip()
def push(self, name, value, tail=False):
"""
>>> r = Redis()
>>> r.delete('l')
1
>>> r.push('l', 'a')
'OK'
>>> r.set('a', 'a')
'OK'
>>> try:
... r.push('a', 'a')
... except ResponseError, e:
... print e
Operation against a key holding the wrong kind of value
>>>
"""
self.connect()
# same considerations on unicode as in set() apply here
try:
value = value if isinstance(value, basestring) else str(value)
self._write('%s %s %s\r\n%s\r\n' % (
'LPUSH' if tail else 'RPUSH', name, len(value), value
))
except UnicodeEncodeError, e:
raise InvalidData("Error encoding unicode value for element in list '%s': %s." % (name, e))
return self._get_simple_response()
def llen(self, name):
"""
>>> r = Redis()
>>> r.delete('l')
1
>>> r.push('l', 'a')
'OK'
>>> r.llen('l')
1
>>> r.push('l', 'a')
'OK'
>>> r.llen('l')
2
>>>
"""
self.connect()
self._write('LLEN %s\r\n' % name)
return self._get_numeric_response()
def lrange(self, name, start, end):
"""
>>> r = Redis()
>>> r.delete('l')
1
>>> r.lrange('l', 0, 1)
[]
>>> r.push('l', 'aaa')
'OK'
>>> r.lrange('l', 0, 1)
['aaa']
>>> r.push('l', 'bbb')
'OK'
>>> r.lrange('l', 0, 0)
['aaa']
>>> r.lrange('l', 0, 1)
['aaa', 'bbb']
>>> r.lrange('l', -1, 0)
[]
>>> r.lrange('l', -1, -1)
['bbb']
>>>
"""
self.connect()
self._write('LRANGE %s %s %s\r\n' % (name, start, end))
return self._get_multi_response()
def ltrim(self, name, start, end):
"""
>>> r = Redis()
>>> r.delete('l')
1
>>> try:
... r.ltrim('l', 0, 1)
... except ResponseError, e:
... print e
no such key
>>> r.push('l', 'aaa')
'OK'
>>> r.push('l', 'bbb')
'OK'
>>> r.push('l', 'ccc')
'OK'
>>> r.ltrim('l', 0, 1)
'OK'
>>> r.llen('l')
2
>>> r.ltrim('l', 99, 95)
'OK'
>>> r.llen('l')
0
>>>
"""
self.connect()
self._write('LTRIM %s %s %s\r\n' % (name, start, end))
return self._get_simple_response()
def lindex(self, name, index):
"""
>>> r = Redis()
>>> res = r.delete('l')
>>> r.lindex('l', 0)
>>> r.push('l', 'aaa')
'OK'
>>> r.lindex('l', 0)
'aaa'
>>> r.lindex('l', 2)
>>> r.push('l', 'ccc')
'OK'
>>> r.lindex('l', 1)
'ccc'
>>> r.lindex('l', -1)
'ccc'
>>>
"""
self.connect()
self._write('LINDEX %s %s\r\n' % (name, index))
return self._get_value()
def pop(self, name, tail=False):
"""
>>> r = Redis()
>>> r.delete('l')
1
>>> r.pop('l')
>>> r.push('l', 'aaa')
'OK'
>>> r.push('l', 'bbb')
'OK'
>>> r.pop('l')
'aaa'
>>> r.pop('l')
'bbb'
>>> r.pop('l')
>>> r.push('l', 'aaa')
'OK'
>>> r.push('l', 'bbb')
'OK'
>>> r.pop('l', tail=True)
'bbb'
>>> r.pop('l')
'aaa'
>>> r.pop('l')
>>>
"""
self.connect()
self._write('%s %s\r\n' % ('RPOP' if tail else 'LPOP', name))
return self._get_value()
def lset(self, name, index, value):
"""
>>> r = Redis()
>>> r.delete('l')
1
>>> try:
... r.lset('l', 0, 'a')
... except ResponseError, e:
... print e
no such key
>>> r.push('l', 'aaa')
'OK'
>>> try:
... r.lset('l', 1, 'a')
... except ResponseError, e:
... print e
index out of range
>>> r.lset('l', 0, 'bbb')
'OK'
>>> r.lrange('l', 0, 1)
['bbb']
>>>
"""
self.connect()
try:
value = value if isinstance(value, basestring) else str(value)
self._write('LSET %s %s %s\r\n%s\r\n' % (
name, index, len(value), value
))
except UnicodeEncodeError, e:
raise InvalidData("Error encoding unicode value for element %s in list '%s': %s." % (index, name, e))
return self._get_simple_response()
def lrem(self, name, value, num=0):
"""
>>> r = Redis()
>>> r.delete('l')
1
>>> r.push('l', 'aaa')
'OK'
>>> r.push('l', 'bbb')
'OK'
>>> r.push('l', 'aaa')
'OK'
>>> r.lrem('l', 'aaa')
2
>>> r.lrange('l', 0, 10)
['bbb']
>>> r.push('l', 'aaa')
'OK'
>>> r.push('l', 'aaa')
'OK'
>>> r.lrem('l', 'aaa', 1)
1
>>> r.lrem('l', 'aaa', 1)
1
>>> r.lrem('l', 'aaa', 1)
0
>>>
"""
self.connect()
try:
value = value if isinstance(value, basestring) else str(value)
self._write('LREM %s %s %s\r\n%s\r\n' % (
name, num, len(value), value
))
except UnicodeEncodeError, e:
raise InvalidData("Error encoding unicode value for element %s in list '%s': %s." % (index, name, e))
return self._get_numeric_response()
def sort(self, name, by=None, get=None, start=None, num=None, desc=False, alpha=False):
"""
>>> r = Redis()
>>> r.delete('l')
1
>>> r.push('l', 'ccc')
'OK'
>>> r.push('l', 'aaa')
'OK'
>>> r.push('l', 'ddd')
'OK'
>>> r.push('l', 'bbb')
'OK'
>>> r.sort('l', alpha=True)
['aaa', 'bbb', 'ccc', 'ddd']
>>> r.delete('l')
1
>>> for i in range(1, 5):
... res = r.push('l', 1.0 / i)
>>> r.sort('l')
['0.25', '0.333333333333', '0.5', '1.0']
>>> r.sort('l', desc=True)
['1.0', '0.5', '0.333333333333', '0.25']
>>> r.sort('l', desc=True, start=2, num=1)
['0.333333333333']
>>> r.set('weight_0.5', 10)
'OK'
>>> r.sort('l', desc=True, by='weight_*')
['0.5', '1.0', '0.333333333333', '0.25']
>>> for i in r.sort('l', desc=True):
... res = r.set('test_%s' % i, 100 - float(i))
>>> r.sort('l', desc=True, get='test_*')
['99.0', '99.5', '99.6666666667', '99.75']
>>> r.sort('l', desc=True, by='weight_*', get='test_*')
['99.5', '99.0', '99.6666666667', '99.75']
>>> r.sort('l', desc=True, by='weight_*', get='missing_*')
[None, None, None, None]
>>>
"""
stmt = ['SORT', name]
if by:
stmt.append("BY %s" % by)
if start and num:
stmt.append("LIMIT %s %s" % (start, num))
if get is None:
pass
elif isinstance(get, basestring):
stmt.append("GET %s" % get)
elif isinstance(get, list) or isinstance(get, tuple):
for g in get:
stmt.append("GET %s" % g)
else:
raise RedisError("Invalid parameter 'get' for Redis sort")
if desc:
stmt.append("DESC")
if alpha:
stmt.append("ALPHA")
self.connect()
self._write(' '.join(stmt + ["\r\n"]))
return self._get_multi_response()
def sadd(self, name, value):
"""
>>> r = Redis()
>>> res = r.delete('s')
>>> r.sadd('s', 'a')
1
>>> r.sadd('s', 'b')
1
>>>
"""
self.connect()
# same considerations on unicode as in set() apply here
try:
value = value if isinstance(value, basestring) else str(value)
self._write('SADD %s %s\r\n%s\r\n' % (
name, len(value), value
))
except UnicodeEncodeError, e:
raise InvalidData("Error encoding unicode value for element in set '%s': %s." % (name, e))
return self._get_numeric_response()
def srem(self, name, value):
"""
>>> r = Redis()
>>> r.delete('s')
1
>>> r.srem('s', 'aaa')
0
>>> r.sadd('s', 'b')
1
>>> r.srem('s', 'b')
1
>>> r.sismember('s', 'b')
0
>>>
"""
self.connect()
# same considerations on unicode as in set() apply here
try:
value = value if isinstance(value, basestring) else str(value)
self._write('SREM %s %s\r\n%s\r\n' % (
name, len(value), value
))
except UnicodeEncodeError, e:
raise InvalidData("Error encoding unicode value for element in set '%s': %s." % (name, e))
return self._get_numeric_response()
def sismember(self, name, value):
"""
>>> r = Redis()
>>> r.delete('s')
1
>>> r.sismember('s', 'b')
0
>>> r.sadd('s', 'a')
1
>>> r.sismember('s', 'b')
0
>>> r.sismember('s', 'a')
1
>>>
"""
self.connect()
# same considerations on unicode as in set() apply here
try:
value = value if isinstance(value, basestring) else str(value)
self._write('SISMEMBER %s %s\r\n%s\r\n' % (
name, len(value), value
))
except UnicodeEncodeError, e:
raise InvalidData("Error encoding unicode value for element in set '%s': %s." % (name, e))
return self._get_numeric_response()
def sinter(self, *args):
"""
>>> r = Redis()
>>> res = r.delete('s1')
>>> res = r.delete('s2')
>>> res = r.delete('s3')
>>> r.sadd('s1', 'a')
1
>>> r.sadd('s2', 'a')
1
>>> r.sadd('s3', 'b')
1
>>> try:
... r.sinter()
... except ResponseError, e:
... print e
wrong number of arguments
>>> try:
... r.sinter('l')
... except ResponseError, e:
... print e
Operation against a key holding the wrong kind of value
>>> r.sinter('s1', 's2', 's3')
set([])
>>> r.sinter('s1', 's2')
set(['a'])
>>>
"""
self.connect()
self._write('SINTER %s\r\n' % ' '.join(args))
return set(self._get_multi_response())
def sinterstore(self, dest, *args):
"""
>>> r = Redis()
>>> res = r.delete('s1')
>>> res = r.delete('s2')
>>> res = r.delete('s3')
>>> r.sadd('s1', 'a')
1
>>> r.sadd('s2', 'a')
1
>>> r.sadd('s3', 'b')
1
>>> r.sinterstore('s_s', 's1', 's2', 's3')
'OK'
>>> r.sinterstore('s_s', 's1', 's2')
'OK'
>>> r.smembers('s_s')
set(['a'])
>>>
"""
self.connect()
self._write('SINTERSTORE %s %s\r\n' % (dest, ' '.join(args)))
return self._get_simple_response()
def smembers(self, name):
"""
>>> r = Redis()
>>> r.delete('s')
1
>>> r.sadd('s', 'a')
1
>>> r.sadd('s', 'b')
1
>>> try:
... r.smembers('l')
... except ResponseError, e:
... print e
Operation against a key holding the wrong kind of value
>>> r.smembers('s')
set(['a', 'b'])
>>>
"""
self.connect()
self._write('SMEMBERS %s\r\n' % name)
return set(self._get_multi_response())
def select(self, db):
"""
>>> r = Redis()
>>> r.delete('a')
1
>>> r.select(1)
'OK'
>>> r.set('a', 1)
'OK'
>>> r.select(0)
'OK'
>>> r.get('a')
>>>
"""
self.connect()
self._write('SELECT %s\r\n' % db)
return self._get_simple_response()
def move(self, name, db):
"""
>>> r = Redis()
>>> r.select(0)
'OK'
>>> r.set('a', 'a')
'OK'
>>> r.select(1)
'OK'
>>> if r.get('a'):
... r.delete('a')
... else:
... print 1
1
>>> r.select(0)
'OK'
>>> r.move('a', 1)
1
>>> r.get('a')
>>> r.select(1)
'OK'
>>> r.get('a')
'a'
>>> r.select(0)
'OK'
>>>
"""
self.connect()
self._write('MOVE %s %s\r\n' % (name, db))
return self._get_numeric_response()
def save(self, background=False):
"""
>>> r = Redis()
>>> r.save()
'OK'
>>> try:
... resp = r.save(background=True)
... except ResponseError, e:
... assert str(e) == 'background save already in progress', str(e)
... else:
... assert resp == 'OK'
>>>
"""
self.connect()
if background:
self._write('BGSAVE\r\n')
else:
self._write('SAVE\r\n')
return self._get_simple_response()
def lastsave(self):
"""
>>> import time
>>> r = Redis()
>>> t = int(time.time())
>>> r.save()
'OK'
>>> r.lastsave() >= t
True
>>>
"""
self.connect()
self._write('LASTSAVE\r\n')
return self._get_numeric_response()
def flush(self, all_dbs=False):
"""
>>> r = Redis()
>>> r.flush()
'OK'
>>> r.flush(all_dbs=True)
'OK'
>>>
"""
self.connect()
self._write('%s\r\n' % ('FLUSHALL' if all_dbs else 'FLUSHDB'))
return self._get_simple_response()
def _get_value(self, negative_as_nil=False):
data = self._read().strip()
if data == 'nil' or (negative_as_nil and data == '-1'):
return
elif data[0] == '-':
self._check_for_error(data)
try:
l = int(data)
except (TypeError, ValueError):
raise ResponseError("Cannot parse response '%s' as data length." % data)
buf = []
while l > 0:
data = self._read()
l -= len(data)
if len(data) > l:
# we got the ending crlf
data = data.rstrip()
buf.append(data)
if l == 0:
# the data has a trailing crlf embedded, let's restore it
buf.append(self._read())
return ''.join(buf)
def _get_simple_response(self):
data = self._read().strip()
if data[0] == '+':
return data[1:]
self._check_for_error(data)
raise InvalidResponse("Cannot parse first line '%s' for a simple response." % data, data)
def _get_numeric_response(self, allow_negative=True):
data = self._read().strip()
try:
value = int(data)
except (TypeError, ValueError), e:
pass
else:
if not allow_negative and value < 0:
self._check_for_error(data)
return value
self._check_for_error(data)
raise InvalidResponse("Cannot parse first line '%s' for a numeric response: %s." % (data, e), data)
def _get_multi_response(self):
results = list()
try:
num = self._get_numeric_response(allow_negative=False)
except InvalidResponse, e:
if e.args[1] == 'nil':
return results
raise
while num:
results.append(self._get_value(negative_as_nil=True))
num -= 1
return results
def _check_for_error(self, data):
if not data or data[0] != '-':
return
if data.startswith('-ERR'):
raise ResponseError(data[4:].strip())
try:
error_len = int(data[1:])
except (TypeError, ValueError):
raise ResponseError("Unknown error format '%s'." % data)
error_message = self._read().strip()[5:]
raise ResponseError(error_message)
def disconnect(self):
if isinstance(self._sock, socket.socket):
try:
self._sock.close()
except socket.error:
pass
self._sock = None
self._fp = None
def connect(self):
"""
>>> r = Redis()
>>> r.connect()
>>> isinstance(r._sock, socket.socket)
True
>>>
"""
if isinstance(self._sock, socket.socket):
return
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((self.host, self.port))
except socket.error, e:
raise ConnectionError("Error %s connecting to %s:%s. %s." % (e.args[0], self.host, self.port, e.args[1]))
else:
self._sock = sock
self._fp = self._sock.makefile('r')
if __name__ == '__main__':
import doctest
doctest.testmod()

View File

@ -0,0 +1,20 @@
Copyright (c) 2009 Ezra Zygmuntowicz
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,31 @@
# redis-rb
A ruby client library for the redis key value storage system.
## Information about redis
Redis is a key value store with some interesting features:
1. It's fast.
2. Keys are strings but values can have types of "NONE", "STRING", "LIST", or "SET". List's can be atomically push'd, pop'd, lpush'd, lpop'd and indexed. This allows you to store things like lists of comments under one key while retaining the ability to append comments without reading and putting back the whole list.
See [redis on code.google.com](http://code.google.com/p/redis/wiki/README) for more information.
## Dependencies
1. redis -
rake redis:install
2. dtach -
rake dtach:install
3. svn - git is the new black, but we need it for the google codes.
## Setup
Use the tasks mentioned above (in Dependencies) to get your machine setup.
## Examples
Check the examples/ directory. *Note* you need to have redis-server running first.

View File

@ -0,0 +1,12 @@
== redis
A ruby client library for the redis key value storage system:
http://code.google.com/p/redis/wiki/README
redis is a key value store with some interesting features:
1. fast
2. keys are strings but values can have types of "NONE","STRING","LIST","SET"
list's can be atomicaly push'd, pop'd and lpush'd, lpop'd and indexed so you
can store things like lists of comments under one key and still be able to
append comments without reading and putting back the whole list.

View File

@ -0,0 +1,58 @@
require 'rubygems'
require 'rake/gempackagetask'
require 'rubygems/specification'
require 'date'
require 'spec/rake/spectask'
require 'tasks/redis.tasks'
GEM = 'redis'
GEM_VERSION = '0.0.2'
AUTHORS = ['Ezra Zygmuntowicz', 'Taylor Weibley']
EMAIL = "ez@engineyard.com"
HOMEPAGE = "http://github.com/ezmobius/redis-rb"
SUMMARY = "Ruby client library for redis key value storage server"
spec = Gem::Specification.new do |s|
s.name = GEM
s.version = GEM_VERSION
s.platform = Gem::Platform::RUBY
s.has_rdoc = true
s.extra_rdoc_files = ["LICENSE"]
s.summary = SUMMARY
s.description = s.summary
s.authors = AUTHORS
s.email = EMAIL
s.homepage = HOMEPAGE
# Uncomment this to add a dependency
# s.add_dependency "foo"
s.require_path = 'lib'
s.autorequire = GEM
s.files = %w(LICENSE README.markdown Rakefile) + Dir.glob("{lib,spec}/**/*")
end
task :default => :spec
desc "Run specs"
Spec::Rake::SpecTask.new do |t|
t.spec_files = FileList['spec/**/*_spec.rb']
t.spec_opts = %w(-fs --color)
end
Rake::GemPackageTask.new(spec) do |pkg|
pkg.gem_spec = spec
end
desc "install the gem locally"
task :install => [:package] do
sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
end
desc "create a gemspec file"
task :make_spec do
File.open("#{GEM}.gemspec", "w") do |file|
file.puts spec.to_ruby
end
end

View File

@ -0,0 +1,15 @@
require 'benchmark'
$:.push File.join(File.dirname(__FILE__), 'lib')
require 'redis'
times = 20000
@r = Redis.new
@r['foo'] = "The first line we sent to the server is some text"
Benchmark.bmbm do |x|
x.report("set") { 20000.times {|i| @r["foo#{i}"] = "The first line we sent to the server is some text"; @r["foo#{i}"]} }
end
@r.keys('*').each do |k|
@r.delete k
end

View File

@ -0,0 +1,33 @@
require 'fileutils'
class RedisCluster
def initialize(opts={})
opts = {:port => 6379, :host => 'localhost', :basedir => "#{Dir.pwd}/rdsrv" }.merge(opts)
FileUtils.mkdir_p opts[:basedir]
opts[:size].times do |i|
port = opts[:port] + i
FileUtils.mkdir_p "#{opts[:basedir]}/#{port}"
File.open("#{opts[:basedir]}/#{port}.conf", 'w'){|f| f.write(make_config(port, "#{opts[:basedir]}/#{port}", "#{opts[:basedir]}/#{port}.log"))}
system(%Q{#{File.join(File.expand_path(File.dirname(__FILE__)), "../redis/redis-server #{opts[:basedir]}/#{port}.conf &" )}})
end
end
def make_config(port=6379, data=port, logfile='stdout', loglevel='debug')
config = %Q{
timeout 300
save 900 1
save 300 10
save 60 10000
dir #{data}
loglevel #{loglevel}
logfile #{logfile}
databases 16
port #{port}
}
end
end
RedisCluster.new :size => 4

View File

@ -0,0 +1,16 @@
require 'rubygems'
require 'redis'
r = Redis.new
r.delete('foo')
puts
p'set foo to "bar"'
r['foo'] = 'bar'
puts
p 'value of foo'
p r['foo']

View File

@ -0,0 +1,18 @@
require 'rubygems'
require 'redis'
r = Redis.new
puts
p 'incr'
r.delete 'counter'
p r.incr('counter')
p r.incr('counter')
p r.incr('counter')
puts
p 'decr'
p r.decr('counter')
p r.decr('counter')
p r.decr('counter')

View File

@ -0,0 +1,26 @@
require 'rubygems'
require 'redis'
r = Redis.new
r.delete 'logs'
puts
p "pushing log messages into a LIST"
r.push_tail 'logs', 'some log message'
r.push_tail 'logs', 'another log message'
r.push_tail 'logs', 'yet another log message'
r.push_tail 'logs', 'also another log message'
puts
p 'contents of logs LIST'
p r.list_range('logs', 0, -1)
puts
p 'Trim logs LIST to last 2 elements(easy circular buffer)'
r.list_trim('logs', -2, -1)
p r.list_range('logs', 0, -1)

View File

@ -0,0 +1,36 @@
require 'rubygems'
require 'redis'
r = Redis.new
r.delete 'foo-tags'
r.delete 'bar-tags'
puts
p "create a set of tags on foo-tags"
r.set_add 'foo-tags', 'one'
r.set_add 'foo-tags', 'two'
r.set_add 'foo-tags', 'three'
puts
p "create a set of tags on bar-tags"
r.set_add 'bar-tags', 'three'
r.set_add 'bar-tags', 'four'
r.set_add 'bar-tags', 'five'
puts
p 'foo-tags'
p r.set_members('foo-tags')
puts
p 'bar-tags'
p r.set_members('bar-tags')
puts
p 'intersection of foo-tags and bar-tags'
p r.set_intersect('foo-tags', 'bar-tags')

View File

@ -0,0 +1,11 @@
require 'benchmark'
$:.push File.join(File.dirname(__FILE__), 'lib')
require 'redis'
times = 20000
@r = Redis.new
(0..1000000).each{|x|
@r[x] = "Hello World"
puts x if (x > 0 and x % 10000) == 0
}

View File

@ -0,0 +1,188 @@
#--
# = timeout.rb
#
# execution timeout
#
# = Copyright
#
# Copyright - (C) 2008 Evan Phoenix
# Copyright:: (C) 2000 Network Applied Communication Laboratory, Inc.
# Copyright:: (C) 2000 Information-technology Promotion Agency, Japan
#
#++
#
# = Description
#
# A way of performing a potentially long-running operation in a thread, and
# terminating it's execution if it hasn't finished within fixed amount of
# time.
#
# Previous versions of timeout didn't use a module for namespace. This version
# provides both Timeout.timeout, and a backwards-compatible #timeout.
#
# = Synopsis
#
# require 'timeout'
# status = Timeout::timeout(5) {
# # Something that should be interrupted if it takes too much time...
# }
#
require 'thread'
module Timeout
##
# Raised by Timeout#timeout when the block times out.
class Error<Interrupt
end
# A mutex to protect @requests
@mutex = Mutex.new
# All the outstanding TimeoutRequests
@requests = []
# Represents +thr+ asking for it to be timeout at in +secs+
# seconds. At timeout, raise +exc+.
class TimeoutRequest
def initialize(secs, thr, exc)
@left = secs
@thread = thr
@exception = exc
end
attr_reader :thread, :left
# Called because +time+ seconds have gone by. Returns
# true if the request has no more time left to run.
def elapsed(time)
@left -= time
@left <= 0
end
# Raise @exception if @thread.
def cancel
if @thread and @thread.alive?
@thread.raise @exception, "execution expired"
end
@left = 0
end
# Abort this request, ie, we don't care about tracking
# the thread anymore.
def abort
@thread = nil
@left = 0
end
end
def self.add_timeout(time, exc)
@controller ||= Thread.new do
while true
if @requests.empty?
sleep
next
end
min = nil
@mutex.synchronize do
min = @requests.min { |a,b| a.left <=> b.left }
end
slept_for = sleep(min.left)
@mutex.synchronize do
@requests.delete_if do |r|
if r.elapsed(slept_for)
r.cancel
true
else
false
end
end
end
end
end
req = TimeoutRequest.new(time, Thread.current, exc)
@mutex.synchronize do
@requests << req
end
@controller.run
return req
end
##
# Executes the method's block. If the block execution terminates before +sec+
# seconds has passed, it returns true. If not, it terminates the execution
# and raises +exception+ (which defaults to Timeout::Error).
#
# Note that this is both a method of module Timeout, so you can 'include
# Timeout' into your classes so they have a #timeout method, as well as a
# module method, so you can call it directly as Timeout.timeout().
def timeout(sec, exception=Error)
return yield if sec == nil or sec.zero?
raise ThreadError, "timeout within critical session" if Thread.critical
req = Timeout.add_timeout sec, exception
begin
yield sec
ensure
req.abort
end
end
module_function :timeout
end
##
# Identical to:
#
# Timeout::timeout(n, e, &block).
#
# Defined for backwards compatibility with earlier versions of timeout.rb, see
# Timeout#timeout.
def timeout(n, e=Timeout::Error, &block) # :nodoc:
Timeout::timeout(n, e, &block)
end
##
# Another name for Timeout::Error, defined for backwards compatibility with
# earlier versions of timeout.rb.
TimeoutError = Timeout::Error # :nodoc:
if __FILE__ == $0
p timeout(5) {
45
}
p timeout(5, TimeoutError) {
45
}
p timeout(nil) {
54
}
p timeout(0) {
54
}
p timeout(5) {
loop {
p 10
sleep 1
}
}
end

View File

@ -0,0 +1,111 @@
require 'redis'
require 'hash_ring'
class DistRedis
attr_reader :ring
def initialize(*servers)
srvs = []
servers.each do |s|
server, port = s.split(':')
srvs << Redis.new(:host => server, :port => port)
end
@ring = HashRing.new srvs
end
def node_for_key(key)
if key =~ /\{(.*)?\}/
key = $1
end
@ring.get_node(key)
end
def add_server(server)
server, port = server.split(':')
@ring.add_node Redis.new(:host => server, :port => port)
end
def method_missing(sym, *args, &blk)
if redis = node_for_key(args.first)
redis.send sym, *args, &blk
else
super
end
end
def keys(glob)
keyz = []
@ring.nodes.each do |red|
keyz.concat red.keys(glob)
end
keyz
end
def save
@ring.nodes.each do |red|
red.save
end
end
def bgsave
@ring.nodes.each do |red|
red.bgsave
end
end
def quit
@ring.nodes.each do |red|
red.quit
end
end
def delete_cloud!
@ring.nodes.each do |red|
red.keys("*").each do |key|
red.delete key
end
end
end
end
if __FILE__ == $0
r = DistRedis.new 'localhost:6379', 'localhost:6380', 'localhost:6381', 'localhost:6382'
r['urmom'] = 'urmom'
r['urdad'] = 'urdad'
r['urmom1'] = 'urmom1'
r['urdad1'] = 'urdad1'
r['urmom2'] = 'urmom2'
r['urdad2'] = 'urdad2'
r['urmom3'] = 'urmom3'
r['urdad3'] = 'urdad3'
p r['urmom']
p r['urdad']
p r['urmom1']
p r['urdad1']
p r['urmom2']
p r['urdad2']
p r['urmom3']
p r['urdad3']
r.push_tail 'listor', 'foo1'
r.push_tail 'listor', 'foo2'
r.push_tail 'listor', 'foo3'
r.push_tail 'listor', 'foo4'
r.push_tail 'listor', 'foo5'
p r.pop_tail 'listor'
p r.pop_tail 'listor'
p r.pop_tail 'listor'
p r.pop_tail 'listor'
p r.pop_tail 'listor'
puts "key distribution:"
r.ring.nodes.each do |red|
p [red.port, red.keys("*")]
end
r.delete_cloud!
p r.keys('*')
end

View File

@ -0,0 +1,73 @@
require 'digest/md5'
class HashRing
attr_reader :ring, :sorted_keys, :replicas, :nodes
# nodes is a list of objects that have a proper to_s representation.
# replicas indicates how many virtual points should be used pr. node,
# replicas are required to improve the distribution.
def initialize(nodes=[], replicas=3)
@replicas = replicas
@ring = {}
@nodes = []
@sorted_keys = []
nodes.each do |node|
add_node(node)
end
end
# Adds a `node` to the hash ring (including a number of replicas).
def add_node(node)
@nodes << node
@replicas.times do |i|
key = gen_key("#{node}:#{i}")
@ring[key] = node
@sorted_keys << key
end
@sorted_keys.sort!
end
def remove_node(node)
@replicas.times do |i|
key = gen_key("#{node}:#{count}")
@ring.delete(key)
@sorted_keys.reject! {|k| k == key}
end
end
# get the node in the hash ring for this key
def get_node(key)
get_node_pos(key)[0]
end
def get_node_pos(key)
return [nil,nil] if @ring.size == 0
key = gen_key(key)
nodes = @sorted_keys
nodes.size.times do |i|
node = nodes[i]
if key <= node
return [@ring[node], i]
end
end
[@ring[nodes[0]], 0]
end
def iter_nodes(key)
return [nil,nil] if @ring.size == 0
node, pos = get_node_pos(key)
@sorted_keys[pos..-1].each do |k|
yield @ring[k]
end
end
def gen_key(key)
key = Digest::MD5.hexdigest(key)
((key[3] << 24) | (key[2] << 16) | (key[1] << 8) | key[0])
end
end
# ring = HashRing.new ['server1', 'server2', 'server3']
# p ring
# #
# p ring.get_node "kjhjkjlkjlkkh"
#

View File

@ -0,0 +1,836 @@
require 'socket'
require File.join(File.dirname(__FILE__),'better_timeout')
require 'set'
class RedisError < StandardError
end
class Redis
OK = "+OK".freeze
ERRCODE = "-".freeze
NIL = 'nil'.freeze
CTRLF = "\r\n".freeze
def to_s
"#{host}:#{port}"
end
def port
@opts[:port]
end
def host
@opts[:host]
end
def initialize(opts={})
@opts = {:host => 'localhost', :port => '6379'}.merge(opts)
end
# SET key value
# Time complexity: O(1)
# Set the string value as value of the key. The string can't be longer
# than 1073741824 bytes (1 GB).
#
# Return value: status code reply
def []=(key, val)
val = redis_marshal(val)
timeout_retry(3, 3){
write "SET #{key} #{val.to_s.size}\r\n#{val}\r\n"
status_code_reply
}
end
# SETNX key value
#
# Time complexity: O(1)
# SETNX works exactly like SET with the only difference that if the key
# already exists no operation is performed. SETNX actually means "SET if Not eXists".
#
# *Return value: integer reply, specifically:
#
# 1 if the key was set 0 if the key was not set
def set_unless_exists(key, val)
val = redis_marshal(val)
timeout_retry(3, 3){
write "SETNX #{key} #{val.to_s.size}\r\n#{val}\r\n"
integer_reply == 1
}
end
# GET key
# Time complexity: O(1)
# Get the value of the specified key. If the key does not exist the special value
# 'nil' is returned. If the value stored at key is not a string an error is
# returned because GET can only handle string values.
#
# Return value: bulk reply
def [](key)
timeout_retry(3, 3){
write "GET #{key}\r\n"
redis_unmarshal(bulk_reply)
}
end
# INCR key
# INCRBY key value
# Time complexity: O(1)
# Increment the number stored at key by one. If the key does not exist or contains
# a value of a wrong type, set the key to the value of "1" (like if the previous
# value was zero).
#
# INCRBY works just like INCR but instead to increment by 1 the increment is value.
#
# Return value: integer reply
def incr(key, increment=nil)
timeout_retry(3, 3){
if increment
write "INCRBY #{key} #{increment}\r\n"
else
write "INCR #{key}\r\n"
end
integer_reply
}
end
# DECR key
#
# DECRBY key value
#
# Time complexity: O(1) Like INCR/INCRBY but decrementing instead of incrementing.
def decr(key, increment=nil)
timeout_retry(3, 3){
if increment
write "DECRBY #{key} #{increment}\r\n"
else
write "DECR #{key}\r\n"
end
integer_reply
}
end
# RANDOMKEY
# Time complexity: O(1)
# Returns a random key from the currently seleted DB.
#
# Return value: single line reply
def randkey
timeout_retry(3, 3){
write "RANDOMKEY\r\n"
single_line_reply
}
end
# RENAME oldkey newkey
#
# Atomically renames the key oldkey to newkey. If the source and destination
# name are the same an error is returned. If newkey already exists it is
# overwritten.
#
# Return value: status code reply
def rename!(oldkey, newkey)
timeout_retry(3, 3){
write "RENAME #{oldkey} #{newkey}\r\n"
status_code_reply
}
end
# RENAMENX oldkey newkey
# Just like RENAME but fails if the destination key newkey already exists.
#
# *Return value: integer reply, specifically:
#
# 1 if the key was renamed 0 if the target key already exist -1 if the
# source key does not exist -3 if source and destination keys are the same
def rename(oldkey, newkey)
timeout_retry(3, 3){
write "RENAMENX #{oldkey} #{newkey}\r\n"
case integer_reply
when -1
raise RedisError, "source key: #{oldkey} does not exist"
when 0
raise RedisError, "target key: #{oldkey} already exists"
when -3
raise RedisError, "source and destination keys are the same"
when 1
true
end
}
end
# EXISTS key
# Time complexity: O(1)
# Test if the specified key exists. The command returns "0" if the key
# exists, otherwise "1" is returned. Note that even keys set with an empty
# string as value will return "1".
#
# *Return value: integer reply, specifically:
#
# 1 if the key exists 0 if the key does not exist
def key?(key)
timeout_retry(3, 3){
write "EXISTS #{key}\r\n"
integer_reply == 1
}
end
# DEL key
# Time complexity: O(1)
# Remove the specified key. If the key does not exist no operation is
# performed. The command always returns success.
#
# *Return value: integer reply, specifically:
#
# 1 if the key was removed 0 if the key does not exist
def delete(key)
timeout_retry(3, 3){
write "DEL #{key}\r\n"
integer_reply == 1
}
end
# KEYS pattern
# Time complexity: O(n) (with n being the number of keys in the DB)
# Returns all the keys matching the glob-style pattern as space separated strings.
# For example if you have in the database the keys "foo" and "foobar" the command
# "KEYS foo*" will return "foo foobar".
#
# Note that while the time complexity for this operation is O(n) the constant times
# are pretty low. For example Redis running on an entry level laptop can scan a 1
# million keys database in 40 milliseconds. Still it's better to consider this one
# of the slow commands that may ruin the DB performance if not used with care.
#
# Return value: bulk reply
def keys(glob)
timeout_retry(3, 3){
write "KEYS #{glob}\r\n"
bulk_reply.split(' ')
}
end
# TYPE key
#
# Time complexity: O(1) Return the type of the value stored at key in form of
# a string. The type can be one of "none", "string", "list", "set". "none" is
# returned if the key does not exist.
#
# Return value: single line reply
def type?(key)
timeout_retry(3, 3){
write "TYPE #{key}\r\n"
single_line_reply
}
end
# RPUSH key string
#
# Time complexity: O(1)
# Add the given string to the tail of the list contained at key. If the key
# does not exist an empty list is created just before the append operation.
# If the key exists but is not a List an error is returned.
#
# Return value: status code reply
def push_tail(key, string)
timeout_retry(3, 3){
write "RPUSH #{key} #{string.to_s.size}\r\n#{string.to_s}\r\n"
status_code_reply
}
end
# LPUSH key string
# Time complexity: O(1)
# Add the given string to the head of the list contained at key. If the
# key does not exist an empty list is created just before the append operation.
# If the key exists but is not a List an error is returned.
#
# Return value: status code reply
def push_head(key, string)
timeout_retry(3, 3){
write "LPUSH #{key} #{string.to_s.size}\r\n#{string.to_s}\r\n"
status_code_reply
}
end
# LPOP key
#
# Time complexity: O(1)
# Atomically return and remove the first element of the list. For example if
# the list contains the elements "a","b","c" LPOP will return "a" and the
# list will become "b","c".
#
# If the key does not exist or the list is already empty the special value
# 'nil' is returned.
#
# Return value: bulk reply
def pop_head(key)
timeout_retry(3, 3){
write "LPOP #{key}\r\n"
bulk_reply
}
end
# RPOP key
# This command works exactly like LPOP, but the last element instead
# of the first element of the list is returned/deleted.
def pop_tail(key)
timeout_retry(3, 3){
write "RPOP #{key}\r\n"
bulk_reply
}
end
# LSET key index value
# Time complexity: O(N) (with N being the length of the list)
# Set the list element at index (see LINDEX for information about the index argument) with the new value. Out of range indexes will generate an error. Note that setting the first or last elements of the list is O(1).
#
# Return value: status code reply
def list_set(key, index, val)
timeout_retry(3, 3){
write "LSET #{key} #{index} #{val.to_s.size}\r\n#{val}\r\n"
status_code_reply
}
end
# LLEN key
# Time complexity: O(1)
# Return the length of the list stored at the specified key. If the key does not
# exist zero is returned (the same behaviour as for empty lists). If the value
# stored at key is not a list the special value -1 is returned. Note: client
# library should raise an exception when -1 is returned instead to pass the
# value back to the caller like a normal list length value.
#
# *Return value: integer reply, specifically:
#
# the length of the list as an integer
# >=
# 0 if the operation succeeded -2 if the specified key does not hold a list valu
def list_length(key)
timeout_retry(3, 3){
write "LLEN #{key}\r\n"
case i = integer_reply
when -2
raise RedisError, "key: #{key} does not hold a list value"
else
i
end
}
end
# LRANGE key start end
# Time complexity: O(n) (with n being the length of the range)
# Return the specified elements of the list stored at the specified key. Start
# and end are zero-based indexes. 0 is the first element of the list (the list head),
# 1 the next element and so on.
#
# For example LRANGE foobar 0 2 will return the first three elements of the list.
#
# start and end can also be negative numbers indicating offsets from the end of the list.
# For example -1 is the last element of the list, -2 the penultimate element and so on.
#
# Indexes out of range will not produce an error: if start is over the end of the list,
# or start > end, an empty list is returned. If end is over the end of the list Redis
# will threat it just like the last element of the list.
#
# Return value: multi bulk reply
def list_range(key, start, ending)
timeout_retry(3, 3){
write "LRANGE #{key} #{start} #{ending}\r\n"
multi_bulk_reply
}
end
# LTRIM key start end
# Time complexity: O(n) (with n being len of list - len of range)
# Trim an existing list so that it will contain only the specified range of
# elements specified. Start and end are zero-based indexes. 0 is the first
# element of the list (the list head), 1 the next element and so on.
#
# For example LTRIM foobar 0 2 will modify the list stored at foobar key so that
# only the first three elements of the list will remain.
#
# start and end can also be negative numbers indicating offsets from the end of
# the list. For example -1 is the last element of the list, -2 the penultimate
# element and so on.
#
# Indexes out of range will not produce an error: if start is over the end of
# the list, or start > end, an empty list is left as value. If end over the
# end of the list Redis will threat it just like the last element of the list.
#
# Hint: the obvious use of LTRIM is together with LPUSH/RPUSH. For example:
#
# LPUSH mylist <someelement> LTRIM mylist 0 99
# The above two commands will push elements in the list taking care that the
# list will not grow without limits. This is very useful when using Redis
# to store logs for example. It is important to note that when used in this
# way LTRIM is an O(1) operation because in the average case just one element
# is removed from the tail of the list.
#
# Return value: status code reply
def list_trim(key, start, ending)
timeout_retry(3, 3){
write "LTRIM #{key} #{start} #{ending}\r\n"
status_code_reply
}
end
# LINDEX key index
# Time complexity: O(n) (with n being the length of the list)
# Return the specified element of the list stored at the specified key. 0 is
# the first element, 1 the second and so on. Negative indexes are supported,
# for example -1 is the last element, -2 the penultimate and so on.
#
# If the value stored at key is not of list type an error is returned. If
# the index is out of range an empty string is returned.
#
# Note that even if the average time complexity is O(n) asking for the first
# or the last element of the list is O(1).
#
# Return value: bulk reply
def list_index(key, index)
timeout_retry(3, 3){
write "LINDEX #{key} #{index}\r\n"
bulk_reply
}
end
# SADD key member
# Time complexity O(1)
# Add the specified member to the set value stored at key. If member is
# already a member of the set no operation is performed. If key does not
# exist a new set with the specified member as sole member is crated. If
# the key exists but does not hold a set value an error is returned.
#
# *Return value: integer reply, specifically:
#
# 1 if the new element was added 0 if the new element was already a member
# of the set -2 if the key contains a non set value
def set_add(key, member)
timeout_retry(3, 3){
write "SADD #{key} #{member.to_s.size}\r\n#{member}\r\n"
case integer_reply
when 1
true
when 0
false
when -2
raise RedisError, "key: #{key} contains a non set value"
end
}
end
# SREM key member
#
# Time complexity O(1)
# Remove the specified member from the set value stored at key. If member
# was not a member of the set no operation is performed. If key does not
# exist or does not hold a set value an error is returned.
#
# *Return value: integer reply, specifically:
#
# 1 if the new element was removed 0 if the new element was not a member
# of the set -2 if the key does not hold a set value
def set_delete(key, member)
timeout_retry(3, 3){
write "SREM #{key} #{member.to_s.size}\r\n#{member}\r\n"
case integer_reply
when 1
true
when 0
false
when -2
raise RedisError, "key: #{key} contains a non set value"
end
}
end
# SCARD key
# Time complexity O(1)
# Return the set cardinality (number of elements). If the key does not
# exist 0 is returned, like for empty sets. If the key does not hold a
# set value -1 is returned. Client libraries should raise an error when -1
# is returned instead to pass the value to the caller.
#
# *Return value: integer reply, specifically:
#
# the cardinality (number of elements) of the set as an integer
# >=
# 0 if the operation succeeded -2 if the specified key does not hold a set value
def set_count(key)
timeout_retry(3, 3){
write "SCARD #{key}\r\n"
case i = integer_reply
when -2
raise RedisError, "key: #{key} contains a non set value"
else
i
end
}
end
# SISMEMBER key member
#
# Time complexity O(1)
# Return 1 if member is a member of the set stored at key, otherwise 0 is
# returned. On error a negative value is returned. Client libraries should
# raise an error when a negative value is returned instead to pass the value
# to the caller.
#
# *Return value: integer reply, specifically:
#
# 1 if the element is a member of the set 0 if the element is not a member of
# the set OR if the key does not exist -2 if the key does not hold a set value
def set_member?(key, member)
timeout_retry(3, 3){
write "SISMEMBER #{key} #{member.to_s.size}\r\n#{member}\r\n"
case integer_reply
when 1
true
when 0
false
when -2
raise RedisError, "key: #{key} contains a non set value"
end
}
end
# SINTER key1 key2 ... keyN
# Time complexity O(N*M) worst case where N is the cardinality of the smallest
# set and M the number of sets
# Return the members of a set resulting from the intersection of all the sets
# hold at the specified keys. Like in LRANGE the result is sent to the client
# as a multi-bulk reply (see the protocol specification for more information).
# If just a single key is specified, then this command produces the same
# result as SELEMENTS. Actually SELEMENTS is just syntax sugar for SINTERSECT.
#
# If at least one of the specified keys does not exist or does not hold a set
# value an error is returned.
#
# Return value: multi bulk reply
def set_intersect(*keys)
timeout_retry(3, 3){
write "SINTER #{keys.join(' ')}\r\n"
Set.new(multi_bulk_reply)
}
end
# SINTERSTORE dstkey key1 key2 ... keyN
#
# Time complexity O(N*M) worst case where N is the cardinality of the smallest set and M the number of sets
# This commnad works exactly like SINTER but instead of being returned the resulting set is sotred as dstkey.
#
# Return value: status code reply
def set_inter_store(destkey, *keys)
timeout_retry(3, 3){
write "SINTERSTORE #{destkey} #{keys.join(' ')}\r\n"
status_code_reply
}
end
# SMEMBERS key
#
# Time complexity O(N)
# Return all the members (elements) of the set value stored at key.
# This is just syntax glue for SINTERSECT.
def set_members(key)
timeout_retry(3, 3){
write "SMEMBERS #{key}\r\n"
Set.new(multi_bulk_reply)
}
end
# SORT key [BY pattern] [GET|DEL|INCR|DECR pattern] [ASC|DESC] [LIMIT start count]
# Sort the elements contained in the List or Set value at key. By default sorting is
# numeric with elements being compared as double precision floating point numbers.
# This is the simplest form of SORT.
# SORT mylist
#
# Assuming mylist contains a list of numbers, the return value will be the list of
# numbers ordered from the smallest to the bigger number. In order to get the sorting
# in reverse order use DESC:
# SORT mylist DESC
#
# ASC is also supported but it's the default so you don't really need it. If you
# want to sort lexicographically use ALPHA. Note that Redis is utf-8 aware
# assuming you set the right value for the LC_COLLATE environment variable.
#
# Sort is able to limit the number of results using the LIMIT option:
# SORT mylist LIMIT 0 10
# In the above example SORT will return only 10 elements, starting from the first one
# (star is zero-based). Almost all the sort options can be mixed together. For example:
# SORT mylist LIMIT 0 10 ALPHA DESC
# Will sort mylist lexicographically, in descending order, returning only the first
# 10 elements.
# Sometimes you want to sort elements using external keys as weights to compare
# instead to compare the actual List or Set elements. For example the list mylist
# may contain the elements 1, 2, 3, 4, that are just the unique IDs of objects
# stored at object_1, object_2, object_3 and object_4, while the keys weight_1,
# weight_2, weight_3 and weight_4 can contain weights we want to use to sort the
# list of objects identifiers. We can use the following command:
# SORT mylist BY weight_*
# the BY option takes a pattern (weight_* in our example) that is used in order to
# generate the key names of the weights used for sorting. Weight key names are obtained
# substituting the first occurrence of * with the actual value of the elements on the
# list (1,2,3,4 in our example).
# Still our previous example will return just the sorted IDs. Often it is needed to
# get the actual objects sorted (object_1, ..., object_4 in the example). We can do
# it with the following command:
# SORT mylist BY weight_* GET object_*
# Note that GET can be used multiple times in order to get more key for every
# element of the original List or Set sorted.
# redis.sort 'index', :by => 'weight_*',
# :order => 'DESC ALPHA',
# :limit => [0,10],
# :get => 'obj_*'
def sort(key, opts={})
cmd = "SORT #{key}"
cmd << " BY #{opts[:by]}" if opts[:by]
cmd << " GET #{opts[:get]}" if opts[:get]
cmd << " INCR #{opts[:incr]}" if opts[:incr]
cmd << " DEL #{opts[:del]}" if opts[:del]
cmd << " DECR #{opts[:decr]}" if opts[:decr]
cmd << " #{opts[:order]}" if opts[:order]
cmd << " LIMIT #{opts[:limit].join(' ')}" if opts[:limit]
cmd << "\r\n"
write cmd
multi_bulk_reply
end
# ADMIN functions for redis
# SELECT index
#
# Select the DB with having the specified zero-based numeric index.
# For default every new client connection is automatically selected to DB 0.
# Return value: status code reply
def select_db(index)
timeout_retry(3, 3){
write "SELECT #{index}\r\n"
status_code_reply
}
end
# MOVE key dbindex
#
# Move the specified key from the currently selected DB to the specified
# destination DB. Note that this command returns 1 only if the key was
# successfully moved, and 0 if the target key was already there or if
# the source key was not found at all, so it is possible to use MOVE
# as a locking primitive.
#
# *Return value: integer reply, specifically:
#
# 1 if the key was moved 0 if the key was not moved because already
# present on the target DB or was not found in the current DB. -3
# if the destination DB is the same as the source DB -4 if the database
# index if out of range
def move(key, index)
timeout_retry(3, 3){
write "MOVE #{index}\r\n"
case integer_reply
when 1
true
when 0
false
when -3
raise RedisError, "destination db same as source db"
when -4
raise RedisError, "db index if out of range"
end
}
end
# SAVE
#
# Save the DB on disk. The server hangs while the saving is not completed,
# no connection is served in the meanwhile. An OK code is returned when
# the DB was fully stored in disk.
# Return value: status code reply
def save
timeout_retry(3, 3){
write "SAVE\r\n"
status_code_reply
}
end
# BGSAVE
#
# Save the DB in background. The OK code is immediately returned. Redis
# forks, the parent continues to server the clients, the child saves
# the DB on disk then exit. A client my be able to check if the operation
# succeeded using the LASTSAVE command.
# Return value: status code reply
def bgsave
timeout_retry(3, 3){
write "BGSAVE\r\n"
status_code_reply
}
end
# LASTSAVE
#
# Return the UNIX TIME of the last DB save executed with success. A client
# may check if a BGSAVE command succeeded reading the LASTSAVE value, then
# issuing a BGSAVE command and checking at regular intervals every N seconds
# if LASTSAVE changed.
#
# Return value: integer reply (UNIX timestamp)
def lastsave
timeout_retry(3, 3){
write "LASTSAVE\r\n"
integer_reply
}
end
def quit
timeout_retry(3, 3){
write "QUIT\r\n"
status_code_reply
}
end
private
def redis_unmarshal(obj)
if obj[0] == 4
Marshal.load(obj)
else
obj
end
end
def redis_marshal(obj)
case obj
when String, Integer
obj
else
Marshal.dump(obj)
end
end
def close
socket.close unless socket.closed?
end
def timeout_retry(time, retries, &block)
timeout(time, &block)
rescue TimeoutError
retries -= 1
retry unless retries < 0
end
def socket
connect if (!@socket or @socket.closed?)
@socket
end
def connect
@socket = TCPSocket.new(@opts[:host], @opts[:port])
@socket.sync = true
@socket
end
def read(length, nodebug=true)
retries = 3
res = socket.read(length)
puts "read: #{res}" if @opts[:debug] && nodebug
res
rescue
retries -= 1
if retries > 0
connect
retry
end
end
def write(data)
puts "write: #{data}" if @opts[:debug]
retries = 3
socket.write(data)
rescue
retries -= 1
if retries > 0
connect
retry
end
end
def nibble_end
read(2)
end
def read_proto
print "read proto: " if @opts[:debug]
buff = ""
while (char = read(1, false))
print char if @opts[:debug]
buff << char
break if buff[-2..-1] == CTRLF
end
puts if @opts[:debug]
buff[0..-3]
end
def status_code_reply
res = read_proto
if res.index(ERRCODE) == 0
raise RedisError, res
else
true
end
end
def bulk_reply
res = read_proto
if res.index(ERRCODE) == 0
err = read(res.to_i.abs)
nibble_end
raise RedisError, err
elsif res != NIL
val = read(res.to_i.abs)
nibble_end
val
else
nil
end
end
def multi_bulk_reply
res = read_proto
if res.index(ERRCODE) == 0
err = read(res.to_i.abs)
nibble_end
raise RedisError, err
elsif res == NIL
nil
else
items = Integer(res)
list = []
items.times do
len = Integer(read_proto)
if len == -1
nil
else
list << read(len)
end
nibble_end
end
list
end
end
def single_line_reply
read_proto
end
def integer_reply
Integer(read_proto)
end
end

View File

@ -0,0 +1,267 @@
require File.dirname(__FILE__) + '/spec_helper'
class Foo
attr_accessor :bar
def initialize(bar)
@bar = bar
end
def ==(other)
@bar == other.bar
end
end
describe "redis" do
before do
@r = Redis.new
@r.select_db(15) # use database 15 for testing so we dont accidentally step on you real data
@r['foo'] = 'bar'
end
after do
@r.keys('*').each {|k| @r.delete k }
end
it "should properly marshall objects" do
class MyFail; def fail; 'it will' end; end
@r['fail'] = MyFail.new
@r['fail'].fail.should == 'it will'
end
it "should be able to GET a key" do
@r['foo'].should == 'bar'
end
it "should be able to SET a key" do
@r['foo'] = 'nik'
@r['foo'].should == 'nik'
end
it "should be able to SETNX(set_unless_exists)" do
@r['foo'] = 'nik'
@r['foo'].should == 'nik'
@r.set_unless_exists 'foo', 'bar'
@r['foo'].should == 'nik'
end
it "should be able to INCR(increment) a key" do
@r.delete('counter')
@r.incr('counter').should == 1
@r.incr('counter').should == 2
@r.incr('counter').should == 3
end
it "should be able to DECR(decrement) a key" do
@r.delete('counter')
@r.incr('counter').should == 1
@r.incr('counter').should == 2
@r.incr('counter').should == 3
@r.decr('counter').should == 2
@r.decr('counter').should == 1
@r.decr('counter').should == 0
end
it "should be able to RANDKEY(return a random key)" do
@r.randkey.should_not be_nil
end
it "should be able to RENAME a key" do
@r.delete 'foo'
@r.delete 'bar'
@r['foo'] = 'hi'
@r.rename! 'foo', 'bar'
@r['bar'].should == 'hi'
end
it "should be able to RENAMENX(rename unless the new key already exists) a key" do
@r.delete 'foo'
@r.delete 'bar'
@r['foo'] = 'hi'
@r['bar'] = 'ohai'
lambda {@r.rename 'foo', 'bar'}.should raise_error(RedisError)
@r['bar'].should == 'ohai'
end
it "should be able to EXISTS(check if key exists)" do
@r['foo'] = 'nik'
@r.key?('foo').should be_true
@r.delete 'foo'
@r.key?('foo').should be_false
end
it "should be able to KEYS(glob for keys)" do
@r.keys("f*").each do |key|
@r.delete key
end
@r['f'] = 'nik'
@r['fo'] = 'nak'
@r['foo'] = 'qux'
@r.keys("f*").sort.should == ['f','fo', 'foo'].sort
end
it "should be able to check the TYPE of a key" do
@r['foo'] = 'nik'
@r.type?('foo').should == "string"
@r.delete 'foo'
@r.type?('foo').should == "none"
end
it "should be able to push to the head of a list" do
@r.push_head "list", 'hello'
@r.push_head "list", 42
@r.type?('list').should == "list"
@r.list_length('list').should == 2
@r.pop_head('list').should == '42'
@r.delete('list')
end
it "should be able to push to the tail of a list" do
@r.push_tail "list", 'hello'
@r.type?('list').should == "list"
@r.list_length('list').should == 1
@r.delete('list')
end
it "should be able to pop the tail of a list" do
@r.push_tail "list", 'hello'
@r.push_tail "list", 'goodbye'
@r.type?('list').should == "list"
@r.list_length('list').should == 2
@r.pop_tail('list').should == 'goodbye'
@r.delete('list')
end
it "should be able to pop the head of a list" do
@r.push_tail "list", 'hello'
@r.push_tail "list", 'goodbye'
@r.type?('list').should == "list"
@r.list_length('list').should == 2
@r.pop_head('list').should == 'hello'
@r.delete('list')
end
it "should be able to get the length of a list" do
@r.push_tail "list", 'hello'
@r.push_tail "list", 'goodbye'
@r.type?('list').should == "list"
@r.list_length('list').should == 2
@r.delete('list')
end
it "should be able to get a range of values from a list" do
@r.push_tail "list", 'hello'
@r.push_tail "list", 'goodbye'
@r.push_tail "list", '1'
@r.push_tail "list", '2'
@r.push_tail "list", '3'
@r.type?('list').should == "list"
@r.list_length('list').should == 5
@r.list_range('list', 2, -1).should == ['1', '2', '3']
@r.delete('list')
end
it "should be able to trim a list" do
@r.push_tail "list", 'hello'
@r.push_tail "list", 'goodbye'
@r.push_tail "list", '1'
@r.push_tail "list", '2'
@r.push_tail "list", '3'
@r.type?('list').should == "list"
@r.list_length('list').should == 5
@r.list_trim 'list', 0, 1
@r.list_length('list').should == 2
@r.list_range('list', 0, -1).should == ['hello', 'goodbye']
@r.delete('list')
end
it "should be able to get a value by indexing into a list" do
@r.push_tail "list", 'hello'
@r.push_tail "list", 'goodbye'
@r.type?('list').should == "list"
@r.list_length('list').should == 2
@r.list_index('list', 1).should == 'goodbye'
@r.delete('list')
end
it "should be able to set a value by indexing into a list" do
@r.push_tail "list", 'hello'
@r.push_tail "list", 'hello'
@r.type?('list').should == "list"
@r.list_length('list').should == 2
@r.list_set('list', 1, 'goodbye').should be_true
@r.list_index('list', 1).should == 'goodbye'
@r.delete('list')
end
it "should be able add members to a set" do
@r.set_add "set", 'key1'
@r.set_add "set", 'key2'
@r.type?('set').should == "set"
@r.set_count('set').should == 2
@r.set_members('set').sort.should == ['key1', 'key2'].sort
@r.delete('set')
end
it "should be able delete members to a set" do
@r.set_add "set", 'key1'
@r.set_add "set", 'key2'
@r.type?('set').should == "set"
@r.set_count('set').should == 2
@r.set_members('set').should == Set.new(['key1', 'key2'])
@r.set_delete('set', 'key1')
@r.set_count('set').should == 1
@r.set_members('set').should == Set.new(['key2'])
@r.delete('set')
end
it "should be able count the members of a set" do
@r.set_add "set", 'key1'
@r.set_add "set", 'key2'
@r.type?('set').should == "set"
@r.set_count('set').should == 2
@r.delete('set')
end
it "should be able test for set membership" do
@r.set_add "set", 'key1'
@r.set_add "set", 'key2'
@r.type?('set').should == "set"
@r.set_count('set').should == 2
@r.set_member?('set', 'key1').should be_true
@r.set_member?('set', 'key2').should be_true
@r.set_member?('set', 'notthere').should be_false
@r.delete('set')
end
it "should be able to do set intersection" do
@r.set_add "set", 'key1'
@r.set_add "set", 'key2'
@r.set_add "set2", 'key2'
@r.set_intersect('set', 'set2').should == Set.new(['key2'])
@r.delete('set')
end
it "should be able to do set intersection and store the results in a key" do
@r.set_add "set", 'key1'
@r.set_add "set", 'key2'
@r.set_add "set2", 'key2'
@r.set_inter_store('newone', 'set', 'set2')
@r.set_members('newone').should == Set.new(['key2'])
@r.delete('set')
end
it "should be able to do crazy SORT queries" do
@r['dog_1'] = 'louie'
@r.push_tail 'dogs', 1
@r['dog_2'] = 'lucy'
@r.push_tail 'dogs', 2
@r['dog_3'] = 'max'
@r.push_tail 'dogs', 3
@r['dog_4'] = 'taj'
@r.push_tail 'dogs', 4
@r.sort('dogs', :get => 'dog_*', :limit => [0,1]).should == ['louie']
@r.sort('dogs', :get => 'dog_*', :limit => [0,1], :order => 'desc alpha').should == ['taj']
end
end

View File

@ -0,0 +1,4 @@
require 'rubygems'
$TESTING=true
$:.push File.join(File.dirname(__FILE__), '..', 'lib')
require 'redis'

View File

@ -0,0 +1,116 @@
# Inspired by rabbitmq.rake the Redbox project at http://github.com/rick/redbox/tree/master
require 'fileutils'
class RedisRunner
def self.redisdir
"/tmp/redis/"
end
def self.redisconfdir
'/etc/redis.conf'
end
def self.dtach_socket
'/tmp/redis.dtach'
end
# Just check for existance of dtach socket
def self.running?
File.exists? dtach_socket
end
def self.start
puts 'Detach with Ctrl+\ Re-attach with rake redis:attach'
sleep 3
exec "dtach -A #{dtach_socket} redis-server #{redisconfdir}"
end
def self.attach
exec "dtach -a #{dtach_socket}"
end
def self.stop
sh 'killall redis-server'
end
end
namespace :redis do
desc 'About redis'
task :about do
puts "\nSee http://code.google.com/p/redis/ for information about redis.\n\n"
end
desc 'Start redis'
task :start do
RedisRunner.start
end
desc 'Stop redis'
task :stop do
RedisRunner.stop
end
desc 'Attach to redis dtach socket'
task :attach do
RedisRunner.attach
end
desc 'Install the lastest redis from svn'
task :install => [:about, :download, :make] do
sh 'sudo cp /tmp/redis/redis-server /usr/bin/'
sh 'sudo cp /tmp/redis/redis-benchmark /usr/bin/'
puts 'Installed redis-server and redis-benchmark to /usr/bin/'
unless File.exists?('/etc/redis.conf')
sh 'sudo cp /tmp/redis/redis.conf /etc/'
puts "Installed redis.conf to /etc/ \n You should look at this file!"
end
end
task :make do
sh "cd #{RedisRunner.redisdir} && make clean"
sh "cd #{RedisRunner.redisdir} && make"
end
desc "Download package"
task :download do
system 'svn checkout http://redis.googlecode.com/svn/trunk /tmp/redis' unless File.exists?(RedisRunner.redisdir)
system 'svn up' if File.exists?("#{RedisRunner.redisdir}/.svn")
end
end
namespace :dtach do
desc 'About dtach'
task :about do
puts "\nSee http://dtach.sourceforge.net/ for information about dtach.\n\n"
end
desc 'Install dtach 0.8 from source'
task :install => [:about] do
Dir.chdir('/tmp/')
unless File.exists?('/tmp/dtach-0.8.tar.gz')
require 'net/http'
Net::HTTP.start('superb-west.dl.sourceforge.net') do |http|
resp = http.get('/sourceforge/dtach/dtach-0.8.tar.gz')
open('/tmp/dtach-0.8.tar.gz', 'wb') do |file| file.write(resp.body) end
end
end
unless File.directory?('/tmp/dtach-0.8')
system('tar xzf dtach-0.8.tar.gz')
end
Dir.chdir('/tmp/dtach-0.8/')
sh 'cd /tmp/dtach-0.8/ && ./configure && make'
sh 'sudo cp /tmp/dtach-0.8/dtach /usr/bin/'
puts 'Dtach successfully installed to /usr/bin.'
end
end

579
dict.c Normal file
View File

@ -0,0 +1,579 @@
/* Hash Tables Implementation.
*
* This file implements in memory hash tables with insert/del/replace/find/
* get-random-element operations. Hash tables will auto resize if needed
* tables of power of two in size are used, collisions are handled by
* chaining. See the source code for more information... :)
*
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <assert.h>
#include "dict.h"
#include "zmalloc.h"
/* ---------------------------- Utility funcitons --------------------------- */
static void _dictPanic(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "\nDICT LIBRARY PANIC: ");
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n\n");
va_end(ap);
}
/* ------------------------- Heap Management Wrappers------------------------ */
static void *_dictAlloc(int size)
{
void *p = zmalloc(size);
if (p == NULL)
_dictPanic("Out of memory");
return p;
}
static void _dictFree(void *ptr) {
zfree(ptr);
}
/* -------------------------- private prototypes ---------------------------- */
static int _dictExpandIfNeeded(dict *ht);
static unsigned int _dictNextPower(unsigned int size);
static int _dictKeyIndex(dict *ht, const void *key);
static int _dictInit(dict *ht, dictType *type, void *privDataPtr);
/* -------------------------- hash functions -------------------------------- */
/* Thomas Wang's 32 bit Mix Function */
unsigned int dictIntHashFunction(unsigned int key)
{
key += ~(key << 15);
key ^= (key >> 10);
key += (key << 3);
key ^= (key >> 6);
key += ~(key << 11);
key ^= (key >> 16);
return key;
}
/* Identity hash function for integer keys */
unsigned int dictIdentityHashFunction(unsigned int key)
{
return key;
}
/* Generic hash function (a popular one from Bernstein).
* I tested a few and this was the best. */
unsigned int dictGenHashFunction(const unsigned char *buf, int len) {
unsigned int hash = 5381;
while (len--)
hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */
return hash;
}
/* ----------------------------- API implementation ------------------------- */
/* Reset an hashtable already initialized with ht_init().
* NOTE: This function should only called by ht_destroy(). */
static void _dictReset(dict *ht)
{
ht->table = NULL;
ht->size = 0;
ht->sizemask = 0;
ht->used = 0;
}
/* Create a new hash table */
dict *dictCreate(dictType *type,
void *privDataPtr)
{
dict *ht = _dictAlloc(sizeof(*ht));
_dictInit(ht,type,privDataPtr);
return ht;
}
/* Initialize the hash table */
int _dictInit(dict *ht, dictType *type,
void *privDataPtr)
{
_dictReset(ht);
ht->type = type;
ht->privdata = privDataPtr;
return DICT_OK;
}
/* Resize the table to the minimal size that contains all the elements,
* but with the invariant of a USER/BUCKETS ration near to <= 1 */
int dictResize(dict *ht)
{
int minimal = ht->used;
if (minimal < DICT_HT_INITIAL_SIZE)
minimal = DICT_HT_INITIAL_SIZE;
return dictExpand(ht, minimal);
}
/* Expand or create the hashtable */
int dictExpand(dict *ht, unsigned int size)
{
dict n; /* the new hashtable */
unsigned int realsize = _dictNextPower(size), i;
/* the size is invalid if it is smaller than the number of
* elements already inside the hashtable */
if (ht->used > size)
return DICT_ERR;
_dictInit(&n, ht->type, ht->privdata);
n.size = realsize;
n.sizemask = realsize-1;
n.table = _dictAlloc(realsize*sizeof(dictEntry*));
/* Initialize all the pointers to NULL */
memset(n.table, 0, realsize*sizeof(dictEntry*));
/* Copy all the elements from the old to the new table:
* note that if the old hash table is empty ht->size is zero,
* so dictExpand just creates an hash table. */
n.used = ht->used;
for (i = 0; i < ht->size && ht->used > 0; i++) {
dictEntry *he, *nextHe;
if (ht->table[i] == NULL) continue;
/* For each hash entry on this slot... */
he = ht->table[i];
while(he) {
unsigned int h;
nextHe = he->next;
/* Get the new element index */
h = dictHashKey(ht, he->key) & n.sizemask;
he->next = n.table[h];
n.table[h] = he;
ht->used--;
/* Pass to the next element */
he = nextHe;
}
}
assert(ht->used == 0);
_dictFree(ht->table);
/* Remap the new hashtable in the old */
*ht = n;
return DICT_OK;
}
/* Add an element to the target hash table */
int dictAdd(dict *ht, void *key, void *val)
{
int index;
dictEntry *entry;
/* Get the index of the new element, or -1 if
* the element already exists. */
if ((index = _dictKeyIndex(ht, key)) == -1)
return DICT_ERR;
/* Allocates the memory and stores key */
entry = _dictAlloc(sizeof(*entry));
entry->next = ht->table[index];
ht->table[index] = entry;
/* Set the hash entry fields. */
dictSetHashKey(ht, entry, key);
dictSetHashVal(ht, entry, val);
ht->used++;
return DICT_OK;
}
/* Add an element, discarding the old if the key already exists */
int dictReplace(dict *ht, void *key, void *val)
{
dictEntry *entry;
/* Try to add the element. If the key
* does not exists dictAdd will suceed. */
if (dictAdd(ht, key, val) == DICT_OK)
return DICT_OK;
/* It already exists, get the entry */
entry = dictFind(ht, key);
/* Free the old value and set the new one */
dictFreeEntryVal(ht, entry);
dictSetHashVal(ht, entry, val);
return DICT_OK;
}
/* Search and remove an element */
static int dictGenericDelete(dict *ht, const void *key, int nofree)
{
unsigned int h;
dictEntry *he, *prevHe;
if (ht->size == 0)
return DICT_ERR;
h = dictHashKey(ht, key) & ht->sizemask;
he = ht->table[h];
prevHe = NULL;
while(he) {
if (dictCompareHashKeys(ht, key, he->key)) {
/* Unlink the element from the list */
if (prevHe)
prevHe->next = he->next;
else
ht->table[h] = he->next;
if (!nofree) {
dictFreeEntryKey(ht, he);
dictFreeEntryVal(ht, he);
}
_dictFree(he);
ht->used--;
return DICT_OK;
}
prevHe = he;
he = he->next;
}
return DICT_ERR; /* not found */
}
int dictDelete(dict *ht, const void *key) {
return dictGenericDelete(ht,key,0);
}
int dictDeleteNoFree(dict *ht, const void *key) {
return dictGenericDelete(ht,key,1);
}
/* Destroy an entire hash table */
int _dictClear(dict *ht)
{
unsigned int i;
/* Free all the elements */
for (i = 0; i < ht->size && ht->used > 0; i++) {
dictEntry *he, *nextHe;
if ((he = ht->table[i]) == NULL) continue;
while(he) {
nextHe = he->next;
dictFreeEntryKey(ht, he);
dictFreeEntryVal(ht, he);
_dictFree(he);
ht->used--;
he = nextHe;
}
}
/* Free the table and the allocated cache structure */
_dictFree(ht->table);
/* Re-initialize the table */
_dictReset(ht);
return DICT_OK; /* never fails */
}
/* Clear & Release the hash table */
void dictRelease(dict *ht)
{
_dictClear(ht);
_dictFree(ht);
}
dictEntry *dictFind(dict *ht, const void *key)
{
dictEntry *he;
unsigned int h;
if (ht->size == 0) return NULL;
h = dictHashKey(ht, key) & ht->sizemask;
he = ht->table[h];
while(he) {
if (dictCompareHashKeys(ht, key, he->key))
return he;
he = he->next;
}
return NULL;
}
dictIterator *dictGetIterator(dict *ht)
{
dictIterator *iter = _dictAlloc(sizeof(*iter));
iter->ht = ht;
iter->index = -1;
iter->entry = NULL;
iter->nextEntry = NULL;
return iter;
}
dictEntry *dictNext(dictIterator *iter)
{
while (1) {
if (iter->entry == NULL) {
iter->index++;
if (iter->index >=
(signed)iter->ht->size) break;
iter->entry = iter->ht->table[iter->index];
} else {
iter->entry = iter->nextEntry;
}
if (iter->entry) {
/* We need to save the 'next' here, the iterator user
* may delete the entry we are returning. */
iter->nextEntry = iter->entry->next;
return iter->entry;
}
}
return NULL;
}
void dictReleaseIterator(dictIterator *iter)
{
_dictFree(iter);
}
/* Return a random entry from the hash table. Useful to
* implement randomized algorithms */
dictEntry *dictGetRandomKey(dict *ht)
{
dictEntry *he;
unsigned int h;
int listlen, listele;
if (ht->size == 0) return NULL;
do {
h = random() & ht->sizemask;
he = ht->table[h];
} while(he == NULL);
/* Now we found a non empty bucket, but it is a linked
* list and we need to get a random element from the list.
* The only sane way to do so is to count the element and
* select a random index. */
listlen = 0;
while(he) {
he = he->next;
listlen++;
}
listele = random() % listlen;
he = ht->table[h];
while(listele--) he = he->next;
return he;
}
/* ------------------------- private functions ------------------------------ */
/* Expand the hash table if needed */
static int _dictExpandIfNeeded(dict *ht)
{
/* If the hash table is empty expand it to the intial size,
* if the table is "full" dobule its size. */
if (ht->size == 0)
return dictExpand(ht, DICT_HT_INITIAL_SIZE);
if (ht->used == ht->size)
return dictExpand(ht, ht->size*2);
return DICT_OK;
}
/* Our hash table capability is a power of two */
static unsigned int _dictNextPower(unsigned int size)
{
unsigned int i = DICT_HT_INITIAL_SIZE;
if (size >= 2147483648U)
return 2147483648U;
while(1) {
if (i >= size)
return i;
i *= 2;
}
}
/* Returns the index of a free slot that can be populated with
* an hash entry for the given 'key'.
* If the key already exists, -1 is returned. */
static int _dictKeyIndex(dict *ht, const void *key)
{
unsigned int h;
dictEntry *he;
/* Expand the hashtable if needed */
if (_dictExpandIfNeeded(ht) == DICT_ERR)
return -1;
/* Compute the key hash value */
h = dictHashKey(ht, key) & ht->sizemask;
/* Search if this slot does not already contain the given key */
he = ht->table[h];
while(he) {
if (dictCompareHashKeys(ht, key, he->key))
return -1;
he = he->next;
}
return h;
}
void dictEmpty(dict *ht) {
_dictClear(ht);
}
#define DICT_STATS_VECTLEN 50
void dictPrintStats(dict *ht) {
unsigned int i, slots = 0, chainlen, maxchainlen = 0;
unsigned int totchainlen = 0;
unsigned int clvector[DICT_STATS_VECTLEN];
if (ht->used == 0) {
printf("No stats available for empty dictionaries\n");
return;
}
for (i = 0; i < DICT_STATS_VECTLEN; i++) clvector[i] = 0;
for (i = 0; i < ht->size; i++) {
dictEntry *he;
if (ht->table[i] == NULL) {
clvector[0]++;
continue;
}
slots++;
/* For each hash entry on this slot... */
chainlen = 0;
he = ht->table[i];
while(he) {
chainlen++;
he = he->next;
}
clvector[(chainlen < DICT_STATS_VECTLEN) ? chainlen : (DICT_STATS_VECTLEN-1)]++;
if (chainlen > maxchainlen) maxchainlen = chainlen;
totchainlen += chainlen;
}
printf("Hash table stats:\n");
printf(" table size: %d\n", ht->size);
printf(" number of elements: %d\n", ht->used);
printf(" different slots: %d\n", slots);
printf(" max chain length: %d\n", maxchainlen);
printf(" avg chain length (counted): %.02f\n", (float)totchainlen/slots);
printf(" avg chain length (computed): %.02f\n", (float)ht->used/slots);
printf(" Chain length distribution:\n");
for (i = 0; i < DICT_STATS_VECTLEN-1; i++) {
if (clvector[i] == 0) continue;
printf(" %s%d: %d (%.02f%%)\n",(i == DICT_STATS_VECTLEN-1)?">= ":"", i, clvector[i], ((float)clvector[i]/ht->size)*100);
}
}
/* ----------------------- StringCopy Hash Table Type ------------------------*/
static unsigned int _dictStringCopyHTHashFunction(const void *key)
{
return dictGenHashFunction(key, strlen(key));
}
static void *_dictStringCopyHTKeyDup(void *privdata, const void *key)
{
int len = strlen(key);
char *copy = _dictAlloc(len+1);
DICT_NOTUSED(privdata);
memcpy(copy, key, len);
copy[len] = '\0';
return copy;
}
static void *_dictStringKeyValCopyHTValDup(void *privdata, const void *val)
{
int len = strlen(val);
char *copy = _dictAlloc(len+1);
DICT_NOTUSED(privdata);
memcpy(copy, val, len);
copy[len] = '\0';
return copy;
}
static int _dictStringCopyHTKeyCompare(void *privdata, const void *key1,
const void *key2)
{
DICT_NOTUSED(privdata);
return strcmp(key1, key2) == 0;
}
static void _dictStringCopyHTKeyDestructor(void *privdata, void *key)
{
DICT_NOTUSED(privdata);
_dictFree((void*)key); /* ATTENTION: const cast */
}
static void _dictStringKeyValCopyHTValDestructor(void *privdata, void *val)
{
DICT_NOTUSED(privdata);
_dictFree((void*)val); /* ATTENTION: const cast */
}
dictType dictTypeHeapStringCopyKey = {
_dictStringCopyHTHashFunction, /* hash function */
_dictStringCopyHTKeyDup, /* key dup */
NULL, /* val dup */
_dictStringCopyHTKeyCompare, /* key compare */
_dictStringCopyHTKeyDestructor, /* key destructor */
NULL /* val destructor */
};
/* This is like StringCopy but does not auto-duplicate the key.
* It's used for intepreter's shared strings. */
dictType dictTypeHeapStrings = {
_dictStringCopyHTHashFunction, /* hash function */
NULL, /* key dup */
NULL, /* val dup */
_dictStringCopyHTKeyCompare, /* key compare */
_dictStringCopyHTKeyDestructor, /* key destructor */
NULL /* val destructor */
};
/* This is like StringCopy but also automatically handle dynamic
* allocated C strings as values. */
dictType dictTypeHeapStringCopyKeyValue = {
_dictStringCopyHTHashFunction, /* hash function */
_dictStringCopyHTKeyDup, /* key dup */
_dictStringKeyValCopyHTValDup, /* val dup */
_dictStringCopyHTKeyCompare, /* key compare */
_dictStringCopyHTKeyDestructor, /* key destructor */
_dictStringKeyValCopyHTValDestructor, /* val destructor */
};

136
dict.h Normal file
View File

@ -0,0 +1,136 @@
/* Hash Tables Implementation.
*
* This file implements in memory hash tables with insert/del/replace/find/
* get-random-element operations. Hash tables will auto resize if needed
* tables of power of two in size are used, collisions are handled by
* chaining. See the source code for more information... :)
*
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __DICT_H
#define __DICT_H
#define DICT_OK 0
#define DICT_ERR 1
/* Unused arguments generate annoying warnings... */
#define DICT_NOTUSED(V) ((void) V)
typedef struct dictEntry {
void *key;
void *val;
struct dictEntry *next;
} dictEntry;
typedef struct dictType {
unsigned int (*hashFunction)(const void *key);
void *(*keyDup)(void *privdata, const void *key);
void *(*valDup)(void *privdata, const void *obj);
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
void (*keyDestructor)(void *privdata, void *key);
void (*valDestructor)(void *privdata, void *obj);
} dictType;
typedef struct dict {
dictEntry **table;
dictType *type;
unsigned int size;
unsigned int sizemask;
unsigned int used;
void *privdata;
} dict;
typedef struct dictIterator {
dict *ht;
int index;
dictEntry *entry, *nextEntry;
} dictIterator;
/* This is the initial size of every hash table */
#define DICT_HT_INITIAL_SIZE 16
/* ------------------------------- Macros ------------------------------------*/
#define dictFreeEntryVal(ht, entry) \
if ((ht)->type->valDestructor) \
(ht)->type->valDestructor((ht)->privdata, (entry)->val)
#define dictSetHashVal(ht, entry, _val_) do { \
if ((ht)->type->valDup) \
entry->val = (ht)->type->valDup((ht)->privdata, _val_); \
else \
entry->val = (_val_); \
} while(0)
#define dictFreeEntryKey(ht, entry) \
if ((ht)->type->keyDestructor) \
(ht)->type->keyDestructor((ht)->privdata, (entry)->key)
#define dictSetHashKey(ht, entry, _key_) do { \
if ((ht)->type->keyDup) \
entry->key = (ht)->type->keyDup((ht)->privdata, _key_); \
else \
entry->key = (_key_); \
} while(0)
#define dictCompareHashKeys(ht, key1, key2) \
(((ht)->type->keyCompare) ? \
(ht)->type->keyCompare((ht)->privdata, key1, key2) : \
(key1) == (key2))
#define dictHashKey(ht, key) (ht)->type->hashFunction(key)
#define dictGetEntryKey(he) ((he)->key)
#define dictGetEntryVal(he) ((he)->val)
#define dictGetHashTableSize(ht) ((ht)->size)
#define dictGetHashTableUsed(ht) ((ht)->used)
/* API */
dict *dictCreate(dictType *type, void *privDataPtr);
int dictExpand(dict *ht, unsigned int size);
int dictAdd(dict *ht, void *key, void *val);
int dictReplace(dict *ht, void *key, void *val);
int dictDelete(dict *ht, const void *key);
int dictDeleteNoFree(dict *ht, const void *key);
void dictRelease(dict *ht);
dictEntry * dictFind(dict *ht, const void *key);
int dictResize(dict *ht);
dictIterator *dictGetIterator(dict *ht);
dictEntry *dictNext(dictIterator *iter);
void dictReleaseIterator(dictIterator *iter);
dictEntry *dictGetRandomKey(dict *ht);
void dictPrintStats(dict *ht);
unsigned int dictGenHashFunction(const unsigned char *buf, int len);
void dictEmpty(dict *ht);
/* Hash table types */
extern dictType dictTypeHeapStringCopyKey;
extern dictType dictTypeHeapStrings;
extern dictType dictTypeHeapStringCopyKeyValue;
#endif /* __DICT_H */

121
doc/Benchmarks.html Normal file
View File

@ -0,0 +1,121 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>Benchmarks: Contents</b><br>&nbsp;&nbsp;<a href="#How Fast is Redis?">How Fast is Redis?</a><br>&nbsp;&nbsp;<a href="#Latency percentiles">Latency percentiles</a>
</div>
<h1 class="wikiname">Benchmarks</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="How Fast is Redis?">How Fast is Redis?</a></h1>Redis includes the <code name="code" class="python">redis-benchmark</code> utility that simulates SETs/GETs done by N clients at the same time sending M total queries (it is similar to the Apache's <code name="code" class="python">ab</code> utility). Below you'll find the full output of the benchmark executed against a Linux box.<br/><br/><ul><li> The test was done with 50 simultaneous clients performing 100000 requests.</li><li> The value SET and GET is a 256 bytes string.</li><li> The Linux box is running <b>Linux 2.6</b>, it's <b>Xeon X3320 2.5Ghz</b>.</li><li> Text executed using the loopback interface (127.0.0.1).</li></ul>
Results: <b>about 110000 SETs per second, about 81000 GETs per second.</b><h1><a name="Latency percentiles">Latency percentiles</a></h1><pre class="codeblock python" name="code">
./redis-benchmark -n 100000
====== SET ======
100007 requests completed in 0.88 seconds
50 parallel clients
3 bytes payload
keep alive: 1
58.50% &lt;= 0 milliseconds
99.17% &lt;= 1 milliseconds
99.58% &lt;= 2 milliseconds
99.85% &lt;= 3 milliseconds
99.90% &lt;= 6 milliseconds
100.00% &lt;= 9 milliseconds
114293.71 requests per second
====== GET ======
100000 requests completed in 1.23 seconds
50 parallel clients
3 bytes payload
keep alive: 1
43.12% &lt;= 0 milliseconds
96.82% &lt;= 1 milliseconds
98.62% &lt;= 2 milliseconds
100.00% &lt;= 3 milliseconds
81234.77 requests per second
====== INCR ======
100018 requests completed in 1.46 seconds
50 parallel clients
3 bytes payload
keep alive: 1
32.32% &lt;= 0 milliseconds
96.67% &lt;= 1 milliseconds
99.14% &lt;= 2 milliseconds
99.83% &lt;= 3 milliseconds
99.88% &lt;= 4 milliseconds
99.89% &lt;= 5 milliseconds
99.96% &lt;= 9 milliseconds
100.00% &lt;= 18 milliseconds
68458.59 requests per second
====== LPUSH ======
100004 requests completed in 1.14 seconds
50 parallel clients
3 bytes payload
keep alive: 1
62.27% &lt;= 0 milliseconds
99.74% &lt;= 1 milliseconds
99.85% &lt;= 2 milliseconds
99.86% &lt;= 3 milliseconds
99.89% &lt;= 5 milliseconds
99.93% &lt;= 7 milliseconds
99.96% &lt;= 9 milliseconds
100.00% &lt;= 22 milliseconds
100.00% &lt;= 208 milliseconds
88109.25 requests per second
====== LPOP ======
100001 requests completed in 1.39 seconds
50 parallel clients
3 bytes payload
keep alive: 1
54.83% &lt;= 0 milliseconds
97.34% &lt;= 1 milliseconds
99.95% &lt;= 2 milliseconds
99.96% &lt;= 3 milliseconds
99.96% &lt;= 4 milliseconds
100.00% &lt;= 9 milliseconds
100.00% &lt;= 208 milliseconds
71994.96 requests per second
</pre>Notes: changing the payload from 256 to 1024 or 4096 bytes does not change the numbers significantly (but reply packets are glued together up to 1024 bytes so GETs may be slower with big payloads). The same for the number of clients, from 50 to 256 clients I got the same numbers. With only 10 clients it starts to get a bit slower.<br/><br/>You can expect different results from different boxes. For example a low profile box like <b>Intel core duo T5500 clocked at 1.66Ghz running Linux 2.6</b> will output the following:
<pre class="codeblock python python" name="code">
./redis-benchmark -q -n 100000
SET: 53684.38 requests per second
GET: 45497.73 requests per second
INCR: 39370.47 requests per second
LPUSH: 34803.41 requests per second
LPOP: 37367.20 requests per second
</pre>
</div>
</div>
</div>
</body>
</html>

39
doc/BgsaveCommand.html Normal file
View File

@ -0,0 +1,39 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>BgsaveCommand: Contents</b><br>&nbsp;&nbsp;<a href="#BGSAVE">BGSAVE</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">BgsaveCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="BGSAVE">BGSAVE</a></h1>
<blockquote>Save the DB in background. The OK code is immediately returned.Redis forks, the parent continues to server the clients, the childsaves the DB on disk then exit. A client my be able to check if theoperation succeeded using the <a href="LastsaveCommand.html">LASTSAVE</a> command.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Status code reply</a><h2><a name="See also">See also</a></h2>
<ul><li> <a href="SaveCommand.html">SAVE</a></li><li> <a href="ShutdownCommand.html">SHUTDOWN</a></li></ul>
</div>
</div>
</div>
</body>
</html>

44
doc/CommandReference.html Normal file
View File

@ -0,0 +1,44 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>CommandReference: Contents</b><br>&nbsp;&nbsp;<a href="#Redis Command Reference">Redis Command Reference</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Connection handling">Connection handling</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Commands operating on string values">Commands operating on string values</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Commands operating on the key space">Commands operating on the key space</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Commands operating on lists">Commands operating on lists</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Commands operating on sets">Commands operating on sets</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Multiple databases handling commands">Multiple databases handling commands</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Sorting">Sorting</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Persistence control commands">Persistence control commands</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Remote server control commands">Remote server control commands</a>
</div>
<h1 class="wikiname">CommandReference</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="Redis Command Reference">Redis Command Reference</a></h1>Every command name links to a specific wiki page describing the behavior of the command.<h2><a name="Connection handling">Connection handling</a></h2><ul><li> <a href="QuitCommand.html">QUIT</a> <code name="code" class="python">close the connection</code></li></ul>
<h2><a name="Commands operating on string values">Commands operating on string values</a></h2><ul><li> <a href="SetCommand.html">SET</a> <i>key</i> <i>value</i> <code name="code" class="python">set a key to a string value</code></li><li> <a href="GetCommand.html">GET</a> <i>key</i> <code name="code" class="python">return the string value of the key</code></li><li> <a href="SetnxCommand.html">SETNX</a> <i>key</i> <i>value</i> <code name="code" class="python">set a key to a string value if the key does not exist</code></li><li> <a href="IncrCommand.html">INCR</a> <i>key</i> <code name="code" class="python">increment the integer value of key</code></li><li> <a href="IncrCommand.html">INCRBY</a> <i>key</i> <i>integer</i><code name="code" class="python"> increment the integer value of key by integer</code></li><li> <a href="IncrCommand.html">INCR</a> <i>key</i> <code name="code" class="python">decrement the integer value of key</code></li><li> <a href="IncrCommand.html">DECRBY</a> <i>key</i> <i>integer</i> <code name="code" class="python">decrement the integer value of key by integer</code></li><li> <a href="ExistsCommand.html">EXISTS</a> <i>key</i> <code name="code" class="python">test if a key exists</code></li><li> <a href="DelCommand.html">DEL</a> <i>key</i> <code name="code" class="python">delete a key</code></li><li> <a href="TypeCommand.html">TYPE</a> <i>key</i> <code name="code" class="python">return the type of the value stored at key</code></li></ul>
<h2><a name="Commands operating on the key space">Commands operating on the key space</a></h2><ul><li> <a href="KeysCommand.html">KEYS</a> <i>pattern</i> <code name="code" class="python">return all the keys matching a given pattern</code></li><li> <a href="RandomkeyCommand.html">RANDOMKEY</a> <code name="code" class="python">return a random key from the key space</code></li><li> <a href="RenameCommand.html">RENAME</a> <i>oldname</i> <i>newname</i> <code name="code" class="python">rename the old key in the new one, destroing the newname key if it already exists</code></li><li> <a href="RenamenxCommand.html">RENAMENX</a> <i>oldname</i> <i>newname</i> <code name="code" class="python">rename the old key in the new one, if the newname key does not already exist</code></li><li> <a href="Dbsize.html">DBSIZE</a> <code name="code" class="python">return the number of keys in the current db</code></li></ul>
<h2><a name="Commands operating on lists">Commands operating on lists</a></h2><ul><li> <a href="RpushCommand.html">RPUSH</a> <i>key</i> <i>value</i> <code name="code" class="python">Append an element to the tail of the List value at key</code></li><li> <a href="RpushCommand.html">LPUSH</a> <i>key</i> <i>value</i> <code name="code" class="python">Append an element to the head of the List value at key</code></li><li> <a href="LlenCommand.html">LLEN</a> <i>key</i> <code name="code" class="python">Return the length of the List value at key</code></li><li> <a href="LrangeCommand.html">LRANGE</a> <i>key</i> <i>start</i> <i>end</i> <code name="code" class="python">Return a range of elements from the List at key</code></li><li> <a href="LtrimCommand.html">LTRIM</a> <i>key</i> <i>start</i> <i>end</i> <code name="code" class="python">Trim the list at key to the specified range of elements</code></li><li> <a href="LindexCommand.html">LINDEX</a> <i>key</i> <i>index</i> <code name="code" class="python">Return the element at index position from the List at key</code></li><li> <a href="LsetCommand.html">LSET</a> <i>key</i> <i>index</i> <i>value</i> <code name="code" class="python">Set a new value as the element at index position of the List at key</code></li><li> <a href="LremCommand.html">LREM</a> <i>key</i> <i>count</i> <i>value</i> <code name="code" class="python">Remove the first-N, last-N, or all the elements matching value from the List at key</code></li><li> <a href="LpopCommand.html">LPOP</a> <i>key</i> <code name="code" class="python">Return and remove (atomically) the first element of the List at key</code></li><li> <a href="LpopCommand.html">RPOP</a> <i>key</i> <code name="code" class="python">Return and remove (atomically) the last element of the List at key</code></li></ul>
<h2><a name="Commands operating on sets">Commands operating on sets</a></h2><ul><li> <a href="SaddCommand.html">SADD</a> <i>key</i> <i>member</i> <code name="code" class="python">Add the specified member to the Set value at key</code></li><li> <a href="SremCommand.html">SREM</a> <i>key</i> <i>member</i> <code name="code" class="python">Remove the specified member from the Set value at key</code></li><li> <a href="ScardCommand.html">SCARD</a> <i>key</i> <code name="code" class="python">Return the number of elements (the cardinality) of the Set at key</code></li><li> <a href="SismemberCommand.html">SISMEMBER</a> <i>key</i> <i>member</i> <code name="code" class="python">Test if the specified value is a member of the Set at key</code></li><li> <a href="SinterCommand.html">SINTER</a> <i>key1</i> <i>key2</i> ... <i>keyN</i> <code name="code" class="python">Return the intersection between the Sets stored at key1, key2, ..., keyN</code></li><li> <a href="SinterCommand.html">SINTERSTORE</a> <i>dstkey</i> <i>key1</i> <i>key2</i> ... <i>keyN</i> <code name="code" class="python">Compute the intersection between the Sets stored at key1, key2, ..., keyN, and store the resulting Set at dstkey</code></li><li> <a href="SmembersCommand.html">SMEMBERS</a> <i>key</i> <code name="code" class="python">Return all the members of the Set value at key</code></li></ul>
<h2><a name="Multiple databases handling commands">Multiple databases handling commands</a></h2><ul><li> <a href="SelectCommand.html">SELECT</a> <i>index</i> <code name="code" class="python">Select the DB having the specified index</code></li><li> <a href="MoveCommand.html">MOVE</a> <i>key</i> <i>dbindex</i> <code name="code" class="python">Move the key from the currently selected DB to the DB having as index dbindex</code></li><li> <a href="FlushdbCommand.html">FLUSHDB</a> <code name="code" class="python">Remove all the keys of the currently selected DB</code></li><li> <a href="FlushallCommand.html">FLUSHALL</a> <code name="code" class="python">Remove all the keys from all the databases</code></li></ul>
<h2><a name="Sorting">Sorting</a></h2><ul><li> <a href="SortCommand.html">SORT</a> <i>key</i> BY <i>pattern</i> LIMIT <i>start</i> <i>end</i> GET <i>pattern</i> ASC|DESC ALPHA <code name="code" class="python">Sort a Set or a List accordingly to the specified parameters</code></li></ul>
<h2><a name="Persistence control commands">Persistence control commands</a></h2><ul><li> <a href="SaveCommand.html">SAVE</a> <code name="code" class="python">Synchronously save the DB on disk</code></li><li> <a href="BgsaveCommand.html">BGSAVE</a> <code name="code" class="python">Asynchronously save the DB on disk</code></li><li> <a href="LastsaveCommand.html">LASTSAVE</a> <code name="code" class="python">Return the UNIX time stamp of the last successfully saving of the dataset on disk</code></li><li> <a href="ShutdownCommand.html">SHUTDOWN</a> <code name="code" class="python">Synchronously save the DB on disk, then shutdown the server</code></li></ul>
<h2><a name="Remote server control commands">Remote server control commands</a></h2><ul><li> <a href="InfoCommand.html">INFO</a> <code name="code" class="python">provide information and statistics about the server</code></li></ul>
</div>
</div>
</div>
</body>
</html>

36
doc/Credits.html Normal file
View File

@ -0,0 +1,36 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>Credits: Contents</b><br>&nbsp;&nbsp;<a href="#Credits">Credits</a>
</div>
<h1 class="wikiname">Credits</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="Credits">Credits</a></h1><ul><li> The Redis server was designed and written by <a href="http://invece.org" target="_blank">Salvatore Sanfilippo (aka antirez)</a></li><li> The Ruby client library was written by <a href="http://brainspl.at/" target="_blank">Ezra Zygmuntowicz (aka ezmobius)</a></li><li> The Python and PHP client libraries were written by <a href="http://qix.it" target="_blank">Ludovico Magnocavallo (aka ludo)</a></li><li> The Erlang client library was written by <a href="http://www.adroll.com/" target="_blank">Valentino Volonghi of Adroll</a></li><li> <b>brettbender</b> found and fixed a but in sds.c that caused the server to crash at least on 64 bit systems, and anyway to be buggy since we used the same vararg thing against vsprintf without to call va_start and va_end every time.</li></ul>
</div>
</div>
</div>
</body>
</html>

38
doc/DbsizeCommand.html Normal file
View File

@ -0,0 +1,38 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>DbsizeCommand: Contents</b><br>&nbsp;&nbsp;<a href="#DBSIZE">DBSIZE</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">DbsizeCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="DBSIZE">DBSIZE</a></h1><blockquote>Return the number of keys in the currently selected database.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a><h2><a name="See also">See also</a></h2>
<ul><li> <a href="SelectCommand.html">SELECT</a></li><li> <a href="InfoCommand.html">INFO</a></li></ul>
</div>
</div>
</div>
</body>
</html>

42
doc/DelCommand.html Normal file
View File

@ -0,0 +1,42 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>DelCommand: Contents</b><br>&nbsp;&nbsp;<a href="#DEL _key_">DEL _key_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">DelCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="DEL _key_">DEL _key_</a></h1>
<i>Time complexity: O(1)</i><blockquote>Remove the specified key. If the key does not existno operation is performed. The command always returns success.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically:<br/><br/><pre class="codeblock python" name="code">
1 if the key was removed
0 if the key does not exist
</pre><h2><a name="See also">See also</a></h2>
<ul><li> <a href="SetCommand.html">SET</a></li><li> <a href="GetCommand.html">GET</a></li><li> <a href="ExistsCommand.html">EXISTS</a></li><li> <a href="LdelCommand.html">LDEL</a></li></ul>
</div>
</div>
</div>
</body>
</html>

37
doc/DesignPatterns.html Normal file
View File

@ -0,0 +1,37 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>DesignPatterns: Contents</b>
</div>
<h1 class="wikiname">DesignPatterns</h1>
<div class="summary">
</div>
<div class="narrow">
Use random keys instead of incremental keys in order to avoid a single-key that gets incremented by many servers. This can can't be distributed among servers.
</div>
</div>
</div>
</body>
</html>

42
doc/ExistsCommand.html Normal file
View File

@ -0,0 +1,42 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>ExistsCommand: Contents</b><br>&nbsp;&nbsp;<a href="#EXISTS _key_">EXISTS _key_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">ExistsCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="EXISTS _key_">EXISTS _key_</a></h1>
<i>Time complexity: O(1)</i><blockquote>Test if the specified key exists. The command returns&quot;0&quot; if the key exists, otherwise &quot;1&quot; is returned.Note that even keys set with an empty string as value willreturn &quot;1&quot;.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically:<br/><br/><pre class="codeblock python" name="code">
1 if the key exists.
0 if the key does not exist.
</pre><h2><a name="See also">See also</a></h2>
<ul><li> <a href="SetnxCommand.html">SETNX</a> is a <code name="code" class="python">SET if not EXISTS</code> atomic operation.</li><li> <a href="SismemberCommand.html">SISMEMBER</a> test if an element is a member of a Set.</li></ul>
</div>
</div>
</div>
</body>
</html>

47
doc/FAQ.html Normal file

File diff suppressed because one or more lines are too long

39
doc/FlushallCommand.html Normal file
View File

@ -0,0 +1,39 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>FlushallCommand: Contents</b><br>&nbsp;&nbsp;<a href="#FLUSHALL">FLUSHALL</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">FlushallCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="FLUSHALL">FLUSHALL</a></h1>
<blockquote>Delete all the keys of all the existing databases, not just the currently selected one. This command never fails.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Status code reply</a><h2><a name="See also">See also</a></h2>
<ul><li> <a href="FlushdbCommand.html">FLUSHDB</a></li></ul>
</div>
</div>
</div>
</body>
</html>

39
doc/FlushdbCommand.html Normal file
View File

@ -0,0 +1,39 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>FlushdbCommand: Contents</b><br>&nbsp;&nbsp;<a href="#FLUSHDB">FLUSHDB</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">FlushdbCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="FLUSHDB">FLUSHDB</a></h1>
<blockquote>Delete all the keys of the currently selected DB. This command never fails.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Status code reply</a><h2><a name="See also">See also</a></h2>
<ul><li> <a href="FlushallCommand.html">FLUSHALL</a></li></ul>
</div>
</div>
</div>
</body>
</html>

39
doc/GetCommand.html Normal file
View File

@ -0,0 +1,39 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>GetCommand: Contents</b><br>&nbsp;&nbsp;<a href="#GET _key_">GET _key_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">GetCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="GET _key_">GET _key_</a></h1>
<i>Time complexity: O(1)</i><blockquote>Get the value of the specified key. If the keydoes not exist the special value 'nil' is returned.If the value stored at <i>key</i> is not a string an erroris returned because GET can only handle string values.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a><h2><a name="See also">See also</a></h2>
<ul><li> <a href="SetCommand.html">SET</a></li><li> <a href="SetnxCommand.html">SETNX</a></li><li> <a href="IncrCommand.html">INCR</a></li></ul>
</div>
</div>
</div>
</body>
</html>

43
doc/IncrCommand.html Normal file
View File

@ -0,0 +1,43 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>IncrCommand: Contents</b><br>&nbsp;&nbsp;<a href="#INCR _key_">INCR _key_</a><br>&nbsp;&nbsp;<a href="#INCRBY _key_ _integer_">INCRBY _key_ _integer_</a><br>&nbsp;&nbsp;<a href="#DECR _key_ _integer_">DECR _key_ _integer_</a><br>&nbsp;&nbsp;<a href="#DECRBY _key_ _integer_">DECRBY _key_ _integer_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">IncrCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="INCR _key_">INCR _key_</a></h1>
<h1><a name="INCRBY _key_ _integer_">INCRBY _key_ _integer_</a></h1>
<h1><a name="DECR _key_ _integer_">DECR _key_ _integer_</a></h1>
<h1><a name="DECRBY _key_ _integer_">DECRBY _key_ _integer_</a></h1>
<i>Time complexity: O(1)</i><blockquote>Increment or decrement the number stored at <i>key</i> by one. If the key doesnot exist or contains a value of a wrong type, set the key to thevalue of &quot;0&quot; before to perform the increment or decrement operation.</blockquote>
<blockquote>INCRBY and DECRBY work just like INCR and DECR but instead toincrement/decrement by 1 the increment/decrement is <i>integer</i>.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, this commands will reply with the new value of <i>key</i> after the increment or decrement.<h2><a name="See also">See also</a></h2>
<ul><li> <a href="GetCommand.html">GET</a></li></ul>
</div>
</div>
</div>
</body>
</html>

50
doc/InfoCommand.html Normal file
View File

@ -0,0 +1,50 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>InfoCommand: Contents</b><br>&nbsp;&nbsp;<a href="#INFO">INFO</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Notes">Notes</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">InfoCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="INFO">INFO</a></h1><blockquote>The info command returns different information and statistics about the server in an format that's simple to parse by computers and easy to red by huamns.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a>, specifically in the following format:<br/><br/><pre class="codeblock python" name="code">
edis_version:0.07
connected_clients:1
connected_slaves:0
used_memory:3187
changes_since_last_save:0
last_save_time:1237655729
total_connections_received:1
total_commands_processed:1
uptime_in_seconds:25
uptime_in_days:0
</pre>All the fields are in the form <code name="code" class="python">field:value</code><h2><a name="Notes">Notes</a></h2><ul><li> <code name="code" class="python">used_memory</code> is returned in bytes, and is the total number of bytes allocated by the program using <code name="code" class="python">malloc</code>.</li><li> <code name="code" class="python">uptime_in_days</code> is redundant since the uptime in seconds contains already the full uptime information, this field is only mainly present for humans.</li><li> <code name="code" class="python">changes_since_last_save</code> does not refer to the number of key changes, but to the number of operations that produced some kind of change in the dataset.</li></ul>
<h2><a name="See also">See also</a></h2>
<ul><li> <a href="LastsaveCommand.html">LASTSAVE</a></li><li> <a href="DbsizeCommand.html">DBSIZE</a></li></ul>
</div>
</div>
</div>
</body>
</html>

42
doc/KeysCommand.html Normal file
View File

@ -0,0 +1,42 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>KeysCommand: Contents</b><br>&nbsp;&nbsp;<a href="#KEYS _pattern_">KEYS _pattern_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">KeysCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="KEYS _pattern_">KEYS _pattern_</a></h1>
<i>Time complexity: O(n) (with n being the number of keys in the DB, and assuming keys and pattern of limited length)</i><blockquote>Returns all the keys matching the glob-style <i>pattern</i> asspace separated strings. For example if you have in thedatabase the keys &quot;foo&quot; and &quot;foobar&quot; the command &quot;KEYS foo<code name="code" class="python">*</code>&quot;will return &quot;foo foobar&quot;.</blockquote>
<blockquote>Note that while the time complexity for this operation is O(n)the constant times are pretty low. For example Redis runningon an entry level laptop can scan a 1 million keys databasein 40 milliseconds. Still it's better to consider this one ofthe slow commands that may ruin the DB performance if not usedwith care.</blockquote>
Glob style patterns examples:
<ul><li> h?llo will match hello hallo hhllo</li><li> h<b>llo will match hllo heeeello
<blockquote>* h<a href="ae.html">ae</a>llo will match hello and hallo, but not hillo</blockquote>Use \ to escape special chars if you want to match them verbatim.<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a>, specifically a string in the form of space separated list of keys. Note that most client libraries will return an Array of keys and not a single string with space separated keys (that is, split by &quot; &quot; is performed in the client library usually).<h2><a name="See also">See also</a></h2>
<blockquote>* <a href="RandomkeyCommand.html">RANDOMKEY</a> to get the name of a randomly selected key in O(1).</blockquote></b></li></ul>
</div>
</div>
</div>
</body>
</html>

39
doc/LastsaveCommand.html Normal file
View File

@ -0,0 +1,39 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>LastsaveCommand: Contents</b><br>&nbsp;&nbsp;<a href="#LASTSAVE">LASTSAVE</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">LastsaveCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="LASTSAVE">LASTSAVE</a></h1>
<blockquote>Return the UNIX TIME of the last DB save executed with success.A client may check if a <a href="BgsaveCommand.html">BGSAVE</a> command succeeded reading the LASTSAVEvalue, then issuing a <a href="BgsaveCommand.html">BGSAVE</a> command and checking at regular intervalsevery N seconds if LASTSAVE changed.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically an UNIX time stamp.<h2><a name="See also">See also</a></h2>
<ul><li> <a href="BgsaveCommand.html">BGSAVE</a></li></ul>
</div>
</div>
</div>
</body>
</html>

41
doc/LindexCommand.html Normal file
View File

@ -0,0 +1,41 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>LindexCommand: Contents</b><br>&nbsp;&nbsp;<a href="#LINDEX _key_ _index_">LINDEX _key_ _index_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">LindexCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="LINDEX _key_ _index_">LINDEX _key_ _index_</a></h1>
<i>Time complexity: O(n) (with n being the length of the list)</i><blockquote>Return the specified element of the list stored at the specifiedkey. 0 is the first element, 1 the second and so on. Negative indexesare supported, for example -1 is the last element, -2 the penultimateand so on.</blockquote>
<blockquote>If the value stored at key is not of list type an error is returned.If the index is out of range an empty string is returned.</blockquote>
<blockquote>Note that even if the average time complexity is O(n) asking forthe first or the last element of the list is O(1).</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a>, specifically the requested element.<h2><a name="See also">See also</a></h2>
<ul><li> <a href="LlenCommand.html">LLEN</a></li><li> <a href="LrangeCommand.html">LRANGE</a></li><li> <a href="LtrimCommand.html">LTRIM</a></li><li> <a href="LdelCommand.html">LDEL</a></li><li> <a href="LindexCommand.html">LINDEX</a></li><li> <a href="LsetCommand.html">LSET</a></li></ul>
</div>
</div>
</div>
</body>
</html>

42
doc/LlenCommand.html Normal file
View File

@ -0,0 +1,42 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>LlenCommand: Contents</b><br>&nbsp;&nbsp;<a href="#LLEN _key_">LLEN _key_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">LlenCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="LLEN _key_">LLEN _key_</a></h1>
<i>Time complexity: O(1)</i><blockquote>Return the length of the list stored at the specified key. If thekey does not exist zero is returned (the same behaviour as forempty lists). If the value stored at <i>key</i> is not a list an error is returned.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically:<br/><br/><pre class="codeblock python" name="code">
The length of the list as an integer `&gt;=` 0 if the operation succeeded
-2 if the specified key does not hold a list value
</pre>Note that library clients should raise an error if -2 is returned by the Redis server instead to pass the negative value back to the caller.<h2><a name="See also">See also</a></h2>
<ul><li> <a href="LlenCommand.html">LLEN</a></li><li> <a href="LrangeCommand.html">LRANGE</a></li><li> <a href="LtrimCommand.html">LTRIM</a></li><li> <a href="LdelCommand.html">LDEL</a></li><li> <a href="LindexCommand.html">LINDEX</a></li><li> <a href="LsetCommand.html">LSET</a></li></ul>
</div>
</div>
</div>
</body>
</html>

41
doc/LpopCommand.html Normal file
View File

@ -0,0 +1,41 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>LpopCommand: Contents</b><br>&nbsp;&nbsp;<a href="#LPOP _key_">LPOP _key_</a><br>&nbsp;&nbsp;<a href="#RPOP _key_">RPOP _key_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">LpopCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="LPOP _key_">LPOP _key_</a></h1>
<h1><a name="RPOP _key_">RPOP _key_</a></h1>
<i>Time complexity: O(1)</i><blockquote>Atomically return and remove the first (LPOP) or last (RPOP) elementof the list. For example if the list contains the elements &quot;a&quot;,&quot;b&quot;,&quot;c&quot; LPOPwill return &quot;a&quot; and the list will become &quot;b&quot;,&quot;c&quot;.</blockquote>
<blockquote>If the <i>key</i> does not exist or the list is already empty the specialvalue 'nil' is returned.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a><h2><a name="See also">See also</a></h2>
<ul><li> <a href="LlenCommand.html">LLEN</a></li><li> <a href="LrangeCommand.html">LRANGE</a></li><li> <a href="LtrimCommand.html">LTRIM</a></li><li> <a href="LdelCommand.html">LDEL</a></li><li> <a href="LindexCommand.html">LINDEX</a></li><li> <a href="LsetCommand.html">LSET</a></li></ul>
</div>
</div>
</div>
</body>
</html>

42
doc/LrangeCommand.html Normal file
View File

@ -0,0 +1,42 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>LrangeCommand: Contents</b><br>&nbsp;&nbsp;<a href="#LRANGE _key_ _start_ _end_">LRANGE _key_ _start_ _end_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">LrangeCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="LRANGE _key_ _start_ _end_">LRANGE _key_ _start_ _end_</a></h1>
<i>Time complexity: O(n) (with n being the length of the range)</i><blockquote>Return the specified elements of the list stored at the specifiedkey. Start and end are zero-based indexes. 0 is the first elementof the list (the list head), 1 the next element and so on.</blockquote>
<blockquote>For example LRANGE foobar 0 2 will return the first three elementsof the list.</blockquote>
<blockquote>_start_ and <i>end</i> can also be negative numbers indicating offsetsfrom the end of the list. For example -1 is the last element ofthe list, -2 the penultimate element and so on.</blockquote>
<blockquote>Indexes out of range will not produce an error: if start is overthe end of the list, or start <code name="code" class="python">&gt;</code> end, an empty list is returned.If end is over the end of the list Redis will threat it just likethe last element of the list.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi bulk reply</a>, specifically a list of elements in the specified range.<h2><a name="See also">See also</a></h2>
<ul><li> <a href="LlenCommand.html">LLEN</a></li><li> <a href="LrangeCommand.html">LRANGE</a></li><li> <a href="LtrimCommand.html">LTRIM</a></li><li> <a href="LdelCommand.html">LDEL</a></li><li> <a href="LindexCommand.html">LINDEX</a></li><li> <a href="LsetCommand.html">LSET</a></li></ul>
</div>
</div>
</div>
</body>
</html>

43
doc/LremCommand.html Normal file
View File

@ -0,0 +1,43 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>LremCommand: Contents</b><br>&nbsp;&nbsp;<a href="#LREM _key_ _count_ _value_">LREM _key_ _count_ _value_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">LremCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="LREM _key_ _count_ _value_">LREM _key_ _count_ _value_</a></h1>
<i>Time complexity: O(N) (with N being the length of the list)</i><blockquote>Remove the first <i>count</i> occurrences of the <i>value</i> element from the list.If <i>count</i> is zero all the elements are removed. If <i>count</i> is negativeelements are removed from tail to head, instead to go from head to tailthat is the normal behaviour. So for example LREM with count -2 and_hello_ as value to remove against the list (a,b,c,hello,x,hello,hello) willlave the list (a,b,c,hello,x). The number of removed elements is returnedas an integer, see below for more information aboht the returned value.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer Reply</a>, specifically:<br/><br/><pre class="codeblock python" name="code">
The number of removed elements if the operation succeeded
-1 if the specified key does not exist
-2 if the specified key does not hold a list value
</pre><h2><a name="See also">See also</a></h2>
<ul><li> <a href="LlenCommand.html">LLEN</a></li><li> <a href="LrangeCommand.html">LRANGE</a></li><li> <a href="LtrimCommand.html">LTRIM</a></li><li> <a href="LdelCommand.html">LDEL</a></li><li> <a href="LindexCommand.html">LINDEX</a></li><li> <a href="LsetCommand.html">LSET</a></li></ul>
</div>
</div>
</div>
</body>
</html>

39
doc/LsetCommand.html Normal file
View File

@ -0,0 +1,39 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>LsetCommand: Contents</b><br>&nbsp;&nbsp;<a href="#LSET _key_ _index_ _value_">LSET _key_ _index_ _value_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">LsetCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="LSET _key_ _index_ _value_">LSET _key_ _index_ _value_</a></h1>
<i>Time complexity: O(N) (with N being the length of the list)</i><blockquote>Set the list element at <i>index</i> (see LINDEX for information about the_index_ argument) with the new <i>value</i>. Out of range indexes willgenerate an error. Note that setting the first or last elements ofthe list is O(1).</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Status code reply</a><h2><a name="See also">See also</a></h2>
<ul><li> <a href="LlenCommand.html">LLEN</a></li><li> <a href="LrangeCommand.html">LRANGE</a></li><li> <a href="LtrimCommand.html">LTRIM</a></li><li> <a href="LdelCommand.html">LDEL</a></li><li> <a href="LindexCommand.html">LINDEX</a></li><li> <a href="LsetCommand.html">LSET</a></li></ul>
</div>
</div>
</div>
</body>
</html>

47
doc/LtrimCommand.html Normal file
View File

@ -0,0 +1,47 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>LtrimCommand: Contents</b><br>&nbsp;&nbsp;<a href="#LTRIM _key_ _start_ _end_">LTRIM _key_ _start_ _end_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">LtrimCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="LTRIM _key_ _start_ _end_">LTRIM _key_ _start_ _end_</a></h1>
<i>Time complexity: O(n) (with n being len of list - len of range)</i><blockquote>Trim an existing list so that it will contain only the specifiedrange of elements specified. Start and end are zero-based indexes.0 is the first element of the list (the list head), 1 the next elementand so on.</blockquote>
<blockquote>For example LTRIM foobar 0 2 will modify the list stored at foobarkey so that only the first three elements of the list will remain.</blockquote>
<blockquote>_start_ and <i>end</i> can also be negative numbers indicating offsetsfrom the end of the list. For example -1 is the last element ofthe list, -2 the penultimate element and so on.</blockquote>
<blockquote>Indexes out of range will not produce an error: if start is overthe end of the list, or start &gt; end, an empty list is left as value.If end over the end of the list Redis will threat it just likethe last element of the list.</blockquote>
<blockquote>Hint: the obvious use of LTRIM is together with LPUSH/RPUSH. For example:</blockquote>
<pre class="codeblock python" name="code">
LPUSH mylist &lt;someelement&gt;
LTRIM mylist 0 99
</pre><blockquote>The above two commands will push elements in the list taking care thatthe list will not grow without limits. This is very useful when usingRedis to store logs for example. It is important to note that when usedin this way LTRIM is an O(1) operation because in the average casejust one element is removed from the tail of the list.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Status code reply</a><h2><a name="See also">See also</a></h2>
<ul><li> <a href="LlenCommand.html">LLEN</a></li><li> <a href="LrangeCommand.html">LRANGE</a></li><li> <a href="LtrimCommand.html">LTRIM</a></li><li> <a href="LdelCommand.html">LDEL</a></li><li> <a href="LindexCommand.html">LINDEX</a></li><li> <a href="LsetCommand.html">LSET</a></li></ul>
</div>
</div>
</div>
</body>
</html>

42
doc/MoveCommand.html Normal file
View File

@ -0,0 +1,42 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>MoveCommand: Contents</b><br>&nbsp;&nbsp;<a href="#MOVE _key_ _dbindex_">MOVE _key_ _dbindex_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">MoveCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="MOVE _key_ _dbindex_">MOVE _key_ _dbindex_</a></h1>
<blockquote>Move the specified key from the currently selected DB to the specifieddestination DB. Note that this command returns 1 only if the key wassuccessfully moved, and 0 if the target key was already there or if thesource key was not found at all, so it is possible to use MOVE as a lockingprimitive.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically:<br/><br/>1 if the key was moved
0 if the key was not moved because already present on the target DB or was not found in the current DB.
-3 if the destination DB is the same as the source DB
-4 if the database index if out of range<h2><a name="See also">See also</a></h2>
<ul><li> <a href="SelectCommand.html">SELECT</a></li><li> <a href="RenameCommand.html">RENAME</a></li></ul>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,143 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- 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>
</div>
<h1 class="wikiname">ProtocolSpecification</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="Protocol Specification">Protocol Specification</a></h1>The Redis protocol is a compromise between being easy to parse by a computer
and being easy to parse by an human. Before reading this section you are
strongly encouraged to read the &quot;REDIS TUTORIAL&quot; section of this README in order
to get a first feeling of the protocol playing with it by TELNET.<h2><a name="Networking layer">Networking layer</a></h2>A client connects to a Redis server creating a TCP connection to the port 6973.
Every redis command or data transmitted by the client and the server is
terminated by &quot;\r\n&quot; (CRLF).<h2><a name="Simple INLINE commands">Simple INLINE commands</a></h2>The simplest commands are the inline commands. This is an example of a
server/client chat (the server chat starts with S:, the client chat with C:)<br/><br/><pre class="codeblock python" name="code">
C: PING
S: +PONG
</pre>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.<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
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
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
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.
the &quot;SET&quot; command is a bulk command, see the following example:<br/><br/><pre class="codeblock python python python" name="code">
C: SET mykey 6
C: foobar
S: +OK
</pre>The last argument of the commnad is '6'. This specify the number of DATA
bytes that will follow (note that even this bytes are terminated by two
additional bytes of CRLF).<br/><br/>All the bulk commands are in this exact form: instead of the last argument
the number of bytes that will follow is specified, followed by the bytes,
and CRLF. In order to be more clear for the programmer this is the string
sent by the client in the above sample:<br/><br/><blockquote>&quot;SET mykey 6\r\nfoobar\r\n&quot;</blockquote>
<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">
C: GET mykey
S: 6
S: foobar
</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
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:<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
value 'nil' instead to send the line containing the number of bytes to read.
This is an example:<br/><br/><pre class="codeblock python python python python python" name="code">
C: GET nonexistingkey
S: nil
</pre>The client library API should not return an empty string, but a nil object.
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
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,
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">
C: LRANGE mylist 0 3
S: 4
S: 3
S: foo
S: 3
S: bar
S: 5
S: Hello
S: 5
S: World
</pre>The first line the server sent is &quot;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
list, the special value 'nil' is sent. Example:<br/><br/><pre class="codeblock python python python python python python python python" name="code">
C: LRANGE nokey 0 1
S: nil
</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">
S: 3
S: 3
S: foo
S: -1
S: 3
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">
[&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
count. Example:<br/><br/><pre class="codeblock python 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
</pre>and<br/><br/><pre class="codeblock python python python python python python python python python python python python python" name="code">
-ERR no suck key
</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
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
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.<br/><br/>
</div>
</div>
</div>
</body>
</html>

38
doc/QuitCommand.html Normal file
View File

@ -0,0 +1,38 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>QuitCommand: Contents</b><br>&nbsp;&nbsp;<a href="#Quit">Quit</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a>
</div>
<h1 class="wikiname">QuitCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="Quit">Quit</a></h1><blockquote>Ask the server to silently close the connection.</blockquote>
<h2><a name="Return value">Return value</a></h2>None. The connection is closed as soon as the QUIT command is received.
</div>
</div>
</div>
</body>
</html>

109
doc/README.html Normal file
View File

@ -0,0 +1,109 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>README: Contents</b><br>&nbsp;&nbsp;<a href="#Introduction">Introduction</a><br>&nbsp;&nbsp;<a href="#Beyond key-value databases">Beyond key-value databases</a><br>&nbsp;&nbsp;<a href="#What are the differences between Redis and Memcached?">What are the differences between Redis and Memcached?</a><br>&nbsp;&nbsp;<a href="#What are the differences between Redis and Tokyo Cabinet / Tyrant?">What are the differences between Redis and Tokyo Cabinet / Tyrant?</a><br>&nbsp;&nbsp;<a href="#Does Redis support locking?">Does Redis support locking?</a><br>&nbsp;&nbsp;<a href="#Multiple databases support">Multiple databases support</a><br>&nbsp;&nbsp;<a href="#Redis Data Types">Redis Data Types</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Implementation Details">Implementation Details</a><br>&nbsp;&nbsp;<a href="#Redis Tutorial">Redis Tutorial</a><br>&nbsp;&nbsp;<a href="#License">License</a><br>&nbsp;&nbsp;<a href="#Credits">Credits</a>
</div>
<h1 class="wikiname">README</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="Introduction">Introduction</a></h1>Redis is a database. To be more specific redis is a very simple database
implementing a dictionary where keys are associated with values. For example
I can set the key &quot;surname_1992&quot; to the string &quot;Smith&quot;.<br/><br/>Redis takes the whole dataset in memory, but the dataset is persistent
since from time to time Redis writes a dump of the dataset on disk asynchronously. The dump is loaded every time the server is restarted. This means that if a system crash occurs the last few queries can get lost (that is acceptable in many applications), so we supported master-slave replication from the early days.<h1><a name="Beyond key-value databases">Beyond key-value databases</a></h1>In most key-value databases keys and values are simple strings. In Redis keys are just strings too, but the associated values can be Strings, Lists and Sets, and there are commands to perform complex atomic operations against this data types, so you can think at Redis as a data structures server.<br/><br/>For example you can append elements to a list stored at the key &quot;mylist&quot; using the LPUSH or RPUSH operation in O(1). Later you'll be able to get a range of elements with LRANGE or trim the list with LTRIM. Sets are very flexible too, it is possible to add and remove elements from Sets (unsorted collections of strings), and then ask for server-side intersection of Sets.<br/><br/>All this features, the support for sorting Lists and Sets, allow to use Redis as the sole DB for your scalable application without the need of any relational database. <a href="TwitterAlikeExample.html">We wrote a simple Twitter clone in PHP + Redis</a> to show a real world example, the link points to an article explaining the design and internals in very simple words.<h1><a name="What are the differences between Redis and Memcached?">What are the differences between Redis and Memcached?</a></h1>In the following ways:<br/><br/><ul><li> Memcached is not persistent, it just holds everything in memory without saving since its main goal is to be used as a cache. Redis instead can be used as the main DB for the application. We <a href="TwitterAlikeExample.html">wrote a simple Twitter clone</a> using only Redis as database.</li></ul>
<ul><li> Like memcached Redis uses a key-value model, but while keys can just be strings, values in Redis can be lists and sets, and complex operations like intersections, set/get n-th element of lists, pop/push of elements, can be performed against sets and lists. It is possible to use lists as message queues.</li></ul>
<h1><a name="What are the differences between Redis and Tokyo Cabinet / Tyrant?">What are the differences between Redis and Tokyo Cabinet / Tyrant?</a></h1>Redis and Tokyo can be used for the same applications, but actually they are <b>ery</b> different beasts:<br/><br/><ul><li> Tokyo is purely key-value, everything beyond key-value storing of strings is delegated to an embedded Lua interpreter. AFAIK there is no way to guarantee atomicity of operations like pushing into a list, and every time you want to have data structures inside a Tokyo key you have to perform some kind of object serialization/de-serialization.</li></ul>
<ul><li> Tokyo stores data on disk, synchronously, this means you can have datasets bigger than memory, but that under load, like every kind of process that relay on the disk I/O for speed, the performances may start to degrade. With Redis you don't have this problems but you have another problem: the dataset in every single server must fit in your memory.</li></ul>
<ul><li> Redis is generally an higher level beast in the operations supported. Things like SORTing, Server-side set-intersections, can't be done with Tokyo. But Redis is not an on-disk DB engine like Tokyo: the latter can be used as a fast DB engine in your C project without the networking overhead just linking to the library. Still remember that in many scalable applications you need multiple servers talking with multiple servers, so the server-client model is almost always needed.</li></ul>
<h1><a name="Does Redis support locking?">Does Redis support locking?</a></h1>No, the idea is to provide atomic primitives in order to make the programmer
able to use redis with locking free algorithms. For example imagine you have
10 computers and 1 redis server. You want to count words in a very large text.
This large text is split among the 10 computers, every computer will process
its part and use Redis's INCR command to atomically increment a counter
for every occurrence of the word found.<br/><br/>INCR/DECR are not the only atomic primitives, there are others like PUSH/POP
on lists, POP RANDOM KEY operations, UPDATE and so on. For example you can
use Redis like a Tuple Space (<a href="http://en.wikipedia.org/wiki/Tuple_space" target="_blank">http://en.wikipedia.org/wiki/Tuple_space</a>) in
order to implement distributed algorithms.<br/><br/>(News: locking with key-granularity is now planned)<h1><a name="Multiple databases support">Multiple databases support</a></h1>Another synchronization primitive is the support for multiple DBs. By default DB 0 is selected for every new connection, but using the SELECT command it is possible to select a different database. The MOVE operation can move an item from one DB to another atomically. This can be used as a base for locking free algorithms together with the 'RANDOMKEY' or 'POPRANDOMKEY' commands.<h1><a name="Redis Data Types">Redis Data Types</a></h1>Redis supports the following three data types as values:<br/><br/><ul><li> Strings: just any sequence of bytes. Redis strings are binary safe so they can not just hold text, but images, compressed data and everything else.</li><li> Lists: lists of strings, with support for operations like append a new string on head, on tail, list length, obtain a range of elements, truncate the list to a given length, sort the list, and so on.</li><li> Sets: an unsorted set of strings. It is possible to add or delete elements from a set, to perform set intersection, union, subtraction, and so on.</li></ul>
Values can be Strings, Lists or Sets. Keys can be a subset of strings not containing newlines (&quot;\n&quot;) and spaces (&quot; &quot;).<br/><br/>Note that sometimes strings may hold numeric vaules that must be parsed by
Redis. An example is the INCR command that atomically increments the number
stored at the specified key. In this case Redis is able to handle integers
that can be stored inside a 'long long' type, that is a 64-bit signed integer.<h2><a name="Implementation Details">Implementation Details</a></h2>Strings are implemented as dynamically allocated strings of characters.
Lists are implemented as doubly linked lists with cached length.
Sets are implemented using hash tables that use chaining to resolve collisions.<h1><a name="Redis Tutorial">Redis Tutorial</a></h1>(note, you can skip this section if you are only interested in &quot;formal&quot; doc.)<br/><br/>Later in this document you can find detailed information about Redis commands,
the protocol specification, and so on. This kind of documentation is useful
but... if you are new to Redis it is also BORING! The Redis protocol is designed
so that is both pretty efficient to be parsed by computers, but simple enough
to be used by humans just poking around with the 'telnet' command, so this
section will show to the reader how to play a bit with Redis to get an initial
feeling about it, and how it works.<br/><br/>To start just compile redis with 'make' and start it with './redis-server'.
The server will start and log stuff on the standard output, if you want
it to log more edit redis.conf, set the loglevel to debug, and restart it.<br/><br/>You can specify a configuration file as unique parameter:<br/><br/><blockquote>./redis-server /etc/redis.conf</blockquote>
This is NOT required. The server will start even without a configuration file
using a default built-in configuration.<br/><br/>Now let's try to set a key to a given value:<br/><br/><pre class="codeblock python" name="code">
$ telnet localhost 6379
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
SET foo 3
bar
+OK
</pre>The first line we sent to the server is &quot;set foo 3&quot;. This means &quot;set the key
foo with the following three bytes I'll send you&quot;. The following line is
the &quot;bar&quot; string, that is, the three bytes. So the effect is to set the
key &quot;foo&quot; to the value &quot;bar&quot;. Very simple!<br/><br/>(note that you can send commands in lowercase and it will work anyway,
commands are not case sensitive)<br/><br/>Note that after the first and the second line we sent to the server there
is a newline at the end. The server expects commands terminated by &quot;\r\n&quot;
and sequence of bytes terminated by &quot;\r\n&quot;. This is a minimal overhead from
the point of view of both the server and client but allows us to play with
Redis with the telnet command easily.<br/><br/>The last line of the chat between server and client is &quot;+OK&quot;. This means
our key was added without problems. Actually SET can never fail but
the &quot;+OK&quot; sent lets us know that the server received everything and
the command was actually executed.<br/><br/>Let's try to get the key content now:<br/><br/><pre class="codeblock python python" name="code">
GET foo
3
bar
</pre>Ok that's very similar to 'set', just the other way around. We sent &quot;get foo&quot;,
the server replied with a first line that is just a number of bytes the value
stored at key contained, followed by the actual bytes. Again &quot;\r\n&quot; are appended
both to the bytes count and the actual data.<br/><br/>What about requesting a non existing key?<br/><br/><pre class="codeblock python python python" name="code">
GET blabla
nil
</pre>When the key does not exist instead of the length just the &quot;nil&quot; string is sent.
Another way to check if a given key exists or not is indeed the EXISTS command:<br/><br/><pre class="codeblock python python python python" name="code">
EXISTS nokey
0
EXISTS foo
1
</pre>As you can see the server replied '0' the first time since 'nokey' does not
exist, and '1' for 'foo', a key that actually exists.<br/><br/>Ok... now you know the basics, read the <a href="CommandReference.html">REDIS COMMAND REFERENCE</a> section to
learn all the commands supported by Redis and the <a href="ProtocolSpecification.html">PROTOCOL SPECIFICATION</a>
section for more details about the protocol used if you plan to implement one
for a language missing a decent client implementation.<h1><a name="License">License</a></h1>Redis is released under the BSD license. See the COPYING file for more information.<h1><a name="Credits">Credits</a></h1>Redis is written and maintained by Salvatore Sanfilippo, Aka 'antirez'.<br/><br/>Enjoy,
antirez
</div>
</div>
</div>
</body>
</html>

39
doc/RandomkeyCommand.html Normal file
View File

@ -0,0 +1,39 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>RandomkeyCommand: Contents</b><br>&nbsp;&nbsp;<a href="#RANDOMKEY">RANDOMKEY</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">RandomkeyCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="RANDOMKEY">RANDOMKEY</a></h1>
<i>Time complexity: O(1)</i><blockquote>Return a randomly selected key from the currently selected DB.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Singe line reply</a>, specifically the randomly selected key or an empty string is the database is empty.<h2><a name="See also">See also</a></h2>
<ul><li> <a href="KeysCommand.html">KEYS</a></li></ul>
</div>
</div>
</div>
</body>
</html>

39
doc/RenameCommand.html Normal file
View File

@ -0,0 +1,39 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>RenameCommand: Contents</b><br>&nbsp;&nbsp;<a href="#RENAME _oldkey_ _newkey_">RENAME _oldkey_ _newkey_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">RenameCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="RENAME _oldkey_ _newkey_">RENAME _oldkey_ _newkey_</a></h1>
<i>Time complexity: O(1)</i><blockquote>Atomically renames the key <i>oldkey</i> to <i>newkey</i>. If the source anddestination name are the same an error is returned. If <i>newkey</i>already exists it is overwritten.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Status code repy</a><h2><a name="See also">See also</a></h2>
<ul><li> <a href="RenamenxCommand.html">RENAMENX</a> if you don't want overwrite the destionation key if it exists.</li></ul>
</div>
</div>
</div>
</body>
</html>

44
doc/RenamenxCommand.html Normal file
View File

@ -0,0 +1,44 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>RenamenxCommand: Contents</b><br>&nbsp;&nbsp;<a href="#RENAMENX _oldkey_ _newkey_">RENAMENX _oldkey_ _newkey_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">RenamenxCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="RENAMENX _oldkey_ _newkey_">RENAMENX _oldkey_ _newkey_</a></h1>
<i>Time complexity: O(1)</i><blockquote>Rename <i>oldkey</i> into <i>newkey</i> but fails if the destination key <i>newkey</i> already exists.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically:<br/><br/><pre class="codeblock python" name="code">
1 if the key was renamed
0 if the target key already exist
-1 if the source key does not exist
-3 if source and destination keys are the same
</pre><h2><a name="See also">See also</a></h2>
<ul><li> <a href="RenameCommand.html">RENAME</a> is like RENAMENX but overwrite existing destionation key.</li></ul>
</div>
</div>
</div>
</body>
</html>

44
doc/ReplyTypes.html Normal file
View File

@ -0,0 +1,44 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>ReplyTypes: Contents</b><br>&nbsp;&nbsp;<a href="#Redis Reply Types">Redis Reply Types</a><br>&nbsp;&nbsp;<a href="#Status code reply">Status code reply</a><br>&nbsp;&nbsp;<a href="#Integer reply">Integer reply</a><br>&nbsp;&nbsp;<a href="#Bulk reply">Bulk reply</a><br>&nbsp;&nbsp;<a href="#Multi bulk reply">Multi bulk reply</a>
</div>
<h1 class="wikiname">ReplyTypes</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="Redis Reply Types">Redis Reply Types</a></h1>Redis commands can reply to the client with four different kind of replies, you can find the protocol level specification of this replies in the <a href="ProtocolSpecification.html">Redis Protocol Specification</a>. This page is instead an higher level description of the four types of replies from the point of view of the final user.<h1><a name="Status code reply">Status code reply</a></h1>
Status code replies are in the form of a <b>+OK</b> from the server, or a <b>-ERR</b> followed by an error string. At the protocol level this replies are sent as a single line. Client libraries should return <i>true</i> on <b>OK</b>, and should raise an exception in form of an error that stops the execution of the program on <b>ERR</b> replies from server, because this kind of replies are used by operations that usually fails because of a programming error, an inconsistent DB, and so on.<h1><a name="Integer reply">Integer reply</a></h1>
At protocol level integer replies are single line replies in form of a decimal singed number. Redis commands returning <i>true</i> or <i>false</i> will use an integer reply and &quot;1&quot; and &quot;0&quot; as replies. A negative value in an integer reply is used to signal an error by all the commands, with the exception of <a href="IncrCommand.html">INCR</a>/<a href="IncrCommand.html">INCRBY</a>/<a href="IncrCommand.html">DECR</a>/<a href="IncrCommand.html">DECRBY</a> where negative return values are allowed (this command never fails).<br/><br/>All the integer replies using negative values to return errors will use the same values to signal the same errors:
<b> -1 key not found
</b> -2 key contains a value of the wrong type
<b> -3 source object and destination object are the same
</b> -4 out of range argument<br/><br/>Integer replies are usually passed by client libraries as integer values. On negative integer reply an exception should be raised (excluding the INCR family commands).<h1><a name="Bulk reply">Bulk reply</a></h1>
A bulk reply is a binary-safe reply that is used to return a single string value (string is not limited to alphanumerical strings, it may contain binary data of any kind). Client libraries will usually return a string as return value of Redis commands returning bulk replies. There is a special bulk reply that signal that the element does not exist. When this happens the client library should return 'nil', 'false', or some other special element that can be distinguished by an empty string.<h1><a name="Multi bulk reply">Multi bulk reply</a></h1>
While a bulk reply returns a single string value, multi bulk replies are used to return multiple values: lists, sets, and so on. Elements of a bulk reply can be missing (-1 length count at protocol level). Client libraries should return 'nil' or 'false' in order to make this elements distinguishable from empty strings. Client libraries should return multi bulk replies that are about ordered elements like list ranges as lists, and bulk replies about sets as hashes.
</div>
</div>
</div>
</body>
</html>

40
doc/RpushCommand.html Normal file
View File

@ -0,0 +1,40 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>RpushCommand: Contents</b><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#RPUSH _key_ _string_">RPUSH _key_ _string_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#LPUSH _key_ _string_">LPUSH _key_ _string_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">RpushCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h3><a name="RPUSH _key_ _string_">RPUSH _key_ _string_</a></h3>
<h3><a name="LPUSH _key_ _string_">LPUSH _key_ _string_</a></h3>
<i>Time complexity: O(1)</i><blockquote>Add the <i>string</i> value to the head (RPUSH) or tail (LPUSH) of the liststored at <i>key</i>. If the key does not exist an empty list is created just beforethe append operation. If the key exists but is not a List an erroris returned.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Status code reply</a><h2><a name="See also">See also</a></h2>
<ul><li> <a href="LlenCommand.html">LLEN</a></li><li> <a href="LrangeCommand.html">LRANGE</a></li><li> <a href="LtrimCommand.html">LTRIM</a></li><li> <a href="LdelCommand.html">LDEL</a></li><li> <a href="LindexCommand.html">LINDEX</a></li><li> <a href="LsetCommand.html">LSET</a></li></ul>
</div>
</div>
</div>
</body>
</html>

43
doc/SaddCommand.html Normal file
View File

@ -0,0 +1,43 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>SaddCommand: Contents</b><br>&nbsp;&nbsp;<a href="#SADD _key_ _member_">SADD _key_ _member_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">SaddCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="SADD _key_ _member_">SADD _key_ _member_</a></h1>
<i>Time complexity O(1)</i><blockquote>Add the specified <i>member</i> to the set value stored at <i>key</i>. If <i>member</i>is already a member of the set no operation is performed. If <i>key</i>does not exist a new set with the specified <i>member</i> as sole member iscrated. If the key exists but does not hold a set value an error isreturned.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically:<br/><br/><pre class="codeblock python" name="code">
1 if the new element was added
0 if the new element was already a member of the set
-2 if the key contains a non set value
</pre><h2><a name="See also">See also</a></h2>
<ul><li> <a href="SremCommand.html">SREM</a></li><li> <a href="SismemberCommand.html">SISMEMBER</a></li><li> <a href="ScardCommand.html">SCARD</a></li><li> <a href="SmembersCommand.html">SMEMBERS</a></li><li> <a href="SinterCommand.html">SINTER</a></li><li> <a href="SinterstoreCommand.html">SINTERSTORE</a></li></ul>
</div>
</div>
</div>
</body>
</html>

39
doc/SaveCommand.html Normal file
View File

@ -0,0 +1,39 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>SaveCommand: Contents</b><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#SAVE">SAVE</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">SaveCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h3><a name="SAVE">SAVE</a></h3>
<blockquote>Save the DB on disk. The server hangs while the saving is notcompleted, no connection is served in the meanwhile. An OK codeis returned when the DB was fully stored in disk.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Status code reply</a><h2><a name="See also">See also</a></h2>
<ul><li> <a href="BgsaveCommand.html">BGSAVE</a></li><li> <a href="ShutdownCommand.html">SHUTDOWN</a></li></ul>
</div>
</div>
</div>
</body>
</html>

42
doc/ScardCommand.html Normal file
View File

@ -0,0 +1,42 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>ScardCommand: Contents</b><br>&nbsp;&nbsp;<a href="#SCARD _key_">SCARD _key_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">ScardCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="SCARD _key_">SCARD _key_</a></h1>
<i>Time complexity O(1)</i><blockquote>Return the set cardinality (number of elements). If the <i>key</i> does notexist 0 is returned, like for empty sets. If the <i>key</i> does not holda set value -1 is returned. Client libraries should raise an errorwhen -1 is returned instead to pass the value to the caller.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically:<br/><br/><pre class="codeblock python" name="code">
the cardinality (number of elements) of the set as an integer `&gt;=` 0 if the operation succeeded
-2 if the specified key does not hold a set value
</pre><h2><a name="See also">See also</a></h2>
<ul><li> <a href="SremCommand.html">SREM</a></li><li> <a href="SismemberCommand.html">SISMEMBER</a></li><li> <a href="ScardCommand.html">SCARD</a></li><li> <a href="SmembersCommand.html">SMEMBERS</a></li><li> <a href="SinterCommand.html">SINTER</a></li><li> <a href="SinterstoreCommand.html">SINTERSTORE</a></li></ul>
</div>
</div>
</div>
</body>
</html>

39
doc/SelectCommand.html Normal file
View File

@ -0,0 +1,39 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>SelectCommand: Contents</b><br>&nbsp;&nbsp;<a href="#SELECT _index_">SELECT _index_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">SelectCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="SELECT _index_">SELECT _index_</a></h1>
<blockquote>Select the DB with having the specified zero-based numeric index.For default every new client connection is automatically selectedto DB 0.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Status code reply</a><h2><a name="See also">See also</a></h2>
<ul><li> <a href="FlushdbCommand.html">FLUSHDB</a></li><li> <a href="MoveCommand.html">MOVE</a></li></ul>
</div>
</div>
</div>
</body>
</html>

39
doc/SetCommand.html Normal file
View File

@ -0,0 +1,39 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>SetCommand: Contents</b><br>&nbsp;&nbsp;<a href="#SET _key_ _value_">SET _key_ _value_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">SetCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="SET _key_ _value_">SET _key_ _value_</a></h1>
<i>Time complexity: O(1)</i><blockquote>Set the string <i>value</i> as value of the <i>key</i>.The string can't be longer than 1073741824 bytes (1 GB).</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Status code reply</a><h2><a name="See also">See also</a></h2>
<ul><li> <a href="SetnxCommand.html">SETNX</a> is like SET but don't perform the operation if the target key already exists.</li></ul>
</div>
</div>
</div>
</body>
</html>

42
doc/SetnxCommand.html Normal file
View File

@ -0,0 +1,42 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>SetnxCommand: Contents</b><br>&nbsp;&nbsp;<a href="#SETNX _key_ _value_">SETNX _key_ _value_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">SetnxCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="SETNX _key_ _value_">SETNX _key_ _value_</a></h1>
<i>Time complexity: O(1)</i><blockquote>SETNX works exactly like <a href="SetCommand.html">SET</a> with the only difference thatif the key already exists no operation is performed.SETNX actually means &quot;SET if Not eXists&quot;.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically:<br/><br/><pre class="codeblock python" name="code">
1 if the key was set
0 if the key was not set
</pre><h2><a name="See also">See also</a></h2>
<ul><li> <a href="SetCommand.html">SET</a></li></ul>
</div>
</div>
</div>
</body>
</html>

39
doc/ShutdownCommand.html Normal file
View File

@ -0,0 +1,39 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>ShutdownCommand: Contents</b><br>&nbsp;&nbsp;<a href="#SHUTDOWN">SHUTDOWN</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">ShutdownCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="SHUTDOWN">SHUTDOWN</a></h1>
<blockquote>Stop all the clients, save the DB, then quit the server. This commandsmakes sure that the DB is switched off without the lost of any data.This is not guaranteed if the client uses simply &quot;SAVE&quot; and then&quot;QUIT&quot; because other clients may alter the DB data between the twocommands.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Status code reply</a> on error. On success nothing is returned since the server quits and the connection is closed.<h2><a name="See also">See also</a></h2>
<ul><li> <a href="SaveCommand.html">SAVE</a></li><li> <a href="BgsaveCommand.html">BGSAVE</a></li></ul>
</div>
</div>
</div>
</body>
</html>

40
doc/SinterCommand.html Normal file
View File

@ -0,0 +1,40 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>SinterCommand: Contents</b><br>&nbsp;&nbsp;<a href="#SINTER _key1_ _key2_ ... _keyN_">SINTER _key1_ _key2_ ... _keyN_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">SinterCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="SINTER _key1_ _key2_ ... _keyN_">SINTER _key1_ _key2_ ... _keyN_</a></h1>
<i>Time complexity O(N<b>M) worst case where N is the cardinality of the smallest set and M the number of sets_<br/><br/><blockquote>Return the members of a set resulting from the intersection of all thesets hold at the specified keys. Like in LRANGE the result is sent tothe client as a multi-bulk reply (see the protocol specification formore information). If just a single key is specified, then this commandproduces the same result as SELEMENTS. Actually SELEMENTS is just syntaxsugar for SINTERSECT.</blockquote>
<blockquote>If at least one of the specified keys does not exist or does not holda set value an error is returned.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi bulk reply</a>, specifically the list of common elements.<h2><a name="See also">See also</a></h2>
<blockquote>* <a href="SremCommand.html">SREM</a>* <a href="SismemberCommand.html">SISMEMBER</a>* <a href="ScardCommand.html">SCARD</a>* <a href="SmembersCommand.html">SMEMBERS</a>* <a href="SinterCommand.html">SINTER</a>* <a href="SinterstoreCommand.html">SINTERSTORE</a></blockquote></b></i>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,39 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>SinterstoreCommand: Contents</b><br>&nbsp;&nbsp;<a href="#SINTERSTORE _dstkey_ _key1_ _key2_ ... _keyN_">SINTERSTORE _dstkey_ _key1_ _key2_ ... _keyN_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">SinterstoreCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="SINTERSTORE _dstkey_ _key1_ _key2_ ... _keyN_">SINTERSTORE _dstkey_ _key1_ _key2_ ... _keyN_</a></h1>
<i>Time complexity O(N<b>M) worst case where N is the cardinality of the smallest set and M the number of sets_<br/><br/><blockquote>This commnad works exactly like SINTER but instead of being returned the resulting set is sotred as _dstkey_.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Status code reply</a><h2><a name="See also">See also</a></h2>
<blockquote>* <a href="SremCommand.html">SREM</a>* <a href="SismemberCommand.html">SISMEMBER</a>* <a href="ScardCommand.html">SCARD</a>* <a href="SmembersCommand.html">SMEMBERS</a>* <a href="SinterCommand.html">SINTER</a>* <a href="SinterstoreCommand.html">SINTERSTORE</a></blockquote></b></i>
</div>
</div>
</div>
</body>
</html>

43
doc/SismemberCommand.html Normal file
View File

@ -0,0 +1,43 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>SismemberCommand: Contents</b><br>&nbsp;&nbsp;<a href="#SISMEMBER _key_ _member_">SISMEMBER _key_ _member_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">SismemberCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="SISMEMBER _key_ _member_">SISMEMBER _key_ _member_</a></h1>
<i>Time complexity O(1)</i><blockquote>Return 1 if <i>member</i> is a member of the set stored at <i>key</i>, otherwise0 is returned. On error a negative value is returned. Client librariesshould raise an error when a negative value is returned instead to passthe value to the caller.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically:<br/><br/><pre class="codeblock python" name="code">
1 if the element is a member of the set
0 if the element is not a member of the set OR if the key does not exist
-2 if the key does not hold a set value
</pre><h2><a name="See also">See also</a></h2>
<ul><li> <a href="SremCommand.html">SREM</a></li><li> <a href="SismemberCommand.html">SISMEMBER</a></li><li> <a href="ScardCommand.html">SCARD</a></li><li> <a href="SmembersCommand.html">SMEMBERS</a></li><li> <a href="SinterCommand.html">SINTER</a></li><li> <a href="SinterstoreCommand.html">SINTERSTORE</a></li></ul>
</div>
</div>
</div>
</body>
</html>

39
doc/SmembersCommand.html Normal file
View File

@ -0,0 +1,39 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>SmembersCommand: Contents</b><br>&nbsp;&nbsp;<a href="#SMEMBERS _key_">SMEMBERS _key_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">SmembersCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="SMEMBERS _key_">SMEMBERS _key_</a></h1>
<i>Time complexity O(N)</i><blockquote>Return all the members (elements) of the set value stored at <i>key</i>. Thisis just syntax glue for <a href="SintersectCommand.html">SINTERSECT</a>.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi bulk reply</a><h2><a name="See also">See also</a></h2>
<ul><li> <a href="SremCommand.html">SREM</a></li><li> <a href="SismemberCommand.html">SISMEMBER</a></li><li> <a href="ScardCommand.html">SCARD</a></li><li> <a href="SmembersCommand.html">SMEMBERS</a></li><li> <a href="SinterCommand.html">SINTER</a></li><li> <a href="SinterstoreCommand.html">SINTERSTORE</a></li></ul>
</div>
</div>
</div>
</body>
</html>

60
doc/SortCommand.html Normal file
View File

@ -0,0 +1,60 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>SortCommand: Contents</b><br>&nbsp;&nbsp;<a href="#SORT _key_ BY _pattern_ LIMIT _start_ _end_ GET _pattern_ ASC|DESC ALPHA">SORT _key_ BY _pattern_ LIMIT _start_ _end_ GET _pattern_ ASC|DESC ALPHA</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See Also">See Also</a>
</div>
<h1 class="wikiname">SortCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="SORT _key_ BY _pattern_ LIMIT _start_ _end_ GET _pattern_ ASC|DESC ALPHA">SORT _key_ BY _pattern_ LIMIT _start_ _end_ GET _pattern_ ASC|DESC ALPHA</a></h1>
<blockquote>Sort the elements contained in the List or Set value at <i>key</i>. By defaultsorting is numeric with elements being compared as double precisionfloating point numbers. This is the simplest form of SORT.</blockquote>
<pre class="codeblock python" name="code">
SORT mylist
</pre><blockquote>Assuming mylist contains a list of numbers, the return value will bethe list of numbers ordered from the smallest to the bigger number.In order to get the sorting in reverse order use DESC:</blockquote>
<pre class="codeblock python python" name="code">
SORT mylist DESC
</pre><blockquote>ASC is also supported but it's the default so you don't really need it.If you want to sort lexicographically use ALPHA. Note that Redis isutf-8 aware assuming you set the right value for the LC_COLLATEenvironment variable.</blockquote>
<blockquote>Sort is able to limit the number of results using the LIMIT option:</blockquote>
<pre class="codeblock python python python" name="code">
SORT mylist LIMIT 0 10
</pre><blockquote>In the above example SORT will return only 10 elements, starting fromthe first one (star is zero-based). Almost all the sort options canbe mixed together. For example:</blockquote>
<pre class="codeblock python python python python" name="code">
SORT mylist LIMIT 0 10 ALPHA DESC
</pre><blockquote>Will sort <i>mylist</i> lexicographically, in descending order, returning onlythe first 10 elements.</blockquote>
<blockquote>Sometimes you want to sort elements using external keys as weights tocompare instead to compare the actual List or Set elements. For examplethe list <i>mylist</i> may contain the elements 1, 2, 3, 4, that are justthe unique IDs of objects stored at object_1, object_2, object_3and object_4, while the keys weight_1, weight_2, weight_3 and weight_4can contain weights we want to use to sort the list of objectsidentifiers. We can use the following command:</blockquote>
<pre class="codeblock python python python python python" name="code">
SORT mylist BY weight_*
</pre><blockquote>the BY option takes a pattern (<code name="code" class="python">weight_*</code> in our example) that is usedin order to generate the key names of the weights used for sorting.Weight key names are obtained substituting the first occurrence of <code name="code" class="python">*</code>with the actual value of the elements on the list (1,2,3,4 in our example).</blockquote>
<blockquote>Still our previous example will return just the sorted IDs. Often it isneeded to get the actual objects sorted (object_1, ..., object_4 in theexample). We can do it with the following command:</blockquote>
<pre class="codeblock python python python python python python" name="code">
SORT mylist BY weight_* GET object_*
</pre><blockquote>Note that GET can be used multiple times in order to get more keys forevery element of the original List or Set sorted.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi bulk reply</a>, specifically a list of sorted elements.<h2><a name="See Also">See Also</a></h2>
<ul><li> <a href="SinterCommand.html">SINTER</a></li></ul>
</div>
</div>
</div>
</body>
</html>

43
doc/SremCommand.html Normal file
View File

@ -0,0 +1,43 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>SremCommand: Contents</b><br>&nbsp;&nbsp;<a href="#SREM _key_ _member_">SREM _key_ _member_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">SremCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="SREM _key_ _member_">SREM _key_ _member_</a></h1>
<i>Time complexity O(1)</i><blockquote>Remove the specified <i>member</i> from the set value stored at <i>key</i>. If_member_ was not a member of the set no operation is performed. If <i>key</i>does not exist or does not hold a set value an error is returned.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically:<br/><br/><pre class="codeblock python" name="code">
1 if the new element was removed
0 if the new element was not a member of the set
-2 if the key does not hold a set value
</pre><h2><a name="See also">See also</a></h2>
<ul><li> <a href="SremCommand.html">SREM</a></li><li> <a href="SismemberCommand.html">SISMEMBER</a></li><li> <a href="ScardCommand.html">SCARD</a></li><li> <a href="SmembersCommand.html">SMEMBERS</a></li><li> <a href="SinterCommand.html">SINTER</a></li><li> <a href="SinterstoreCommand.html">SINTERSTORE</a></li></ul>
</div>
</div>
</div>
</body>
</html>

38
doc/TemplateCommand.html Normal file
View File

@ -0,0 +1,38 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>TemplateCommand: Contents</b><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">TemplateCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">ReplyTypes</a><h2><a name="See also">See also</a></h2>
<ul><li> []</li></ul>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,252 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>TwitterAlikeExample: Contents</b><br>&nbsp;&nbsp;<a href="#A case study: Design and implementation of a simple Twitter clone using only the Redis key-value store as database and PHP">A case study: Design and implementation of a simple Twitter clone using only the Redis key-value store as database and PHP</a><br>&nbsp;&nbsp;<a href="#Key-value stores basics">Key-value stores basics</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Atomic operations">Atomic operations</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Beyond key-value stores">Beyond key-value stores</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#The set data type">The set data type</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Prerequisites">Prerequisites</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Data layout">Data layout</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Following, followers and updates">Following, followers and updates</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Authentication">Authentication</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Updates">Updates</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Paginating updates">Paginating updates</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Following users">Following users</a><br>&nbsp;&nbsp;<a href="#Making it horizontally scalable">Making it horizontally scalable</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Hashing the key">Hashing the key</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Special keys">Special keys</a>
</div>
<h1 class="wikiname">TwitterAlikeExample</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="A case study: Design and implementation of a simple Twitter clone using only the Redis key-value store as database and PHP">A case study: Design and implementation of a simple Twitter clone using only the Redis key-value store as database and PHP</a></h1>In this article I'll explain the design and the implementation of a <a href="http://retwis.antirez.com" target="_blank">simple clone of Twitter</a> written using PHP and <a href="http://code.google.com/p/redis/" target="_blank">Redis</a> as only database. The programming community uses to look at key-value stores like special databases that can't be used as drop in replacement for a relational database for the development of web applications. This article will try to prove the contrary.<br/><br/>Our Twitter clone, <a href="http://retwis.antirez.com" target="_blank">called Retwis</a>, is structurally simple, has very good performances, and can be distributed among N web servers and M Redis servers with very little efforts. You can find the source code <a href="http://code.google.com/p/redis/downloads/list" target="_blank">here</a>.<br/><br/>We use PHP for the example since it can be read by everybody. The same (or... much better) results can be obtained using Ruby, Python, Erlang, and so on.
<h1><a name="Key-value stores basics">Key-value stores basics</a></h1>
The essence of a key-value store is the ability to store some data, called <i>value</i>, inside a key. This data can later be retrieved only if we know the exact key used to store it. There is no way to search something by value. So for example I can use the command SET to store the value <b>bar</b> at key <b>foo</b>:<br/><br/><pre class="codeblock python" name="code">
SET foo bar
</pre>Redis will store our data permanently, so we can later ask for &quot;<i>What is the value stored at key foo?</i>&quot; and Redis will reply with <b>bar</b>:<br/><br/><pre class="codeblock python python" name="code">
GET foo =&gt; bar
</pre>Other common operations provided by key-value stores are DEL used to delete a given key, and the associated value, SET-if-not-exists (called SETNX on Redis) that sets a key only if it does not already exist, and INCR that is able to atomically increment a number stored at a given key:<br/><br/><pre class="codeblock python python python" name="code">
SET foo 10
INCR foo =&gt; 11
INCR foo =&gt; 12
INCR foo =&gt; 13
</pre><h2><a name="Atomic operations">Atomic operations</a></h2>
So far it should be pretty simple, but there is something special about INCR. Think about this, why to provide such an operation if we can do it ourself with a bit of code? After all it is as simple as:<br/><br/><pre class="codeblock python python python python" name="code">
x = GET foo
x = x + 1
SET foo x
</pre>The problem is that doing the increment this way will work as long as there is only a client working with the value <i>x</i> at a time. See what happens if two computers are accessing this data at the same time:<br/><br/><pre class="codeblock python python python python python" name="code">
x = GET foo (yields 10)
y = GET foo (yields 10)
x = x + 1 (x is now 11)
y = y + 1 (y is now 11)
SET foo x (foo is now 11)
SET foo y (foo is now 11)
</pre>Something is wrong with that! We incremented the value two times, but instead to go from 10 to 12 our key holds 11. This is because the INCR operation done with <code name="code" class="python">GET / increment / SET</code> <b>is not an atomic operation</b>. Instead the INCR provided by Redis, Memcached, ..., are atomic implementations, the server will take care to protect the get-increment-set for all the time needed to complete in order to prevent simultaneous accesses.<br/><br/>What makes Redis different from other key-value stores is that it provides more operations similar to INCR that can be used together to model complex problems. This is why you can use Redis to write whole web applications without using an SQL database and without to get mad.
<h2><a name="Beyond key-value stores">Beyond key-value stores</a></h2>
In this section we will see what Redis features we need to build our Twitter clone. The first thing to know is that Redis values can be more than strings. Redis supports Lists and Sets as values, and there are atomic operations to operate against this more advanced values so we are safe even with multiple accesses against the same key. Let's start from Lists:<br/><br/><pre class="codeblock python python python python python python" name="code">
LPUSH mylist a (now mylist holds one element list 'a')
LPUSH mylist b (now mylist holds 'b,a')
LPUSH mylist c (now mylist holds 'c,b,a')
</pre>LPUSH means <i>Left Push</i>, that is, add an element to the left (or to the head) of the list stored at <i>mylist</i>. If the key <i>mylist</i> does not exist it is automatically created by Redis as an empty list before the PUSH operation. As you can imagine, there is also the RPUSH operation that adds the element on the right of the list (on the tail).<br/><br/>This is very useful for our Twitter clone. Updates of users can be stored into a list stored at <code name="code" class="python">username:updates</code> for instance. There are operations to get data or information from Lists of course. For instance LRANGE returns a range of the list, or the whole list.<br/><br/><pre class="codeblock python python python python python python python" name="code">
LRANGE mylist 0 1 =&gt; c,b
</pre>LRANGE uses zero-based indexes, that is the first element is 0, the second 1, and so on. The command aguments are <code name="code" class="python">LRANGE key first-index last-index</code>. The <i>last index</i> argument can be negative, with a special meaning: -1 is the last element of the list, -2 the penultimate, and so on. So in order to get the whole list we can use:<br/><br/><pre class="codeblock python python python python python python python python" name="code">
LRANGE mylist 0 -1 =&gt; c,b,a
</pre>Other important operations are LLEN that returns the length of the list, and LTRIM that is like LRANGE but instead of returning the specified range <b>trims</b> the list, so it is like <i>Get range from mylist, Set this range as new value</i> but atomic. We will use only this List operations, but make sure to check the <a href="http://code.google.com/p/redis/wiki/README" target="_blank">Redis documentation</a> to discover all the List operations supported by Redis.
<h3><a name="The set data type">The set data type</a></h3>
There is more than Lists, Redis also supports Sets, that are unsorted collection of elements. It is possible to add, remove, and test for existence of members, and perform intersection between different Sets. Of course it is possible to ask for the list or the number of elements of a Set. Some example will make it more clear. Keep in mind that SADD is the <i>add to set</i> operation, SREM is the <i>remove from set</i> operation, <i>sismember</i> is the <i>test if it is a member</i> operation, and SINTER is <i>perform intersection</i> operation. Other operations are SCARD that is used to get the cardinality (the number of elements) of a Set, and SMEMBERS that will return all the members of a Set.<br/><br/><pre class="codeblock python python python python python python python python python" name="code">
SADD myset a
SADD myset b
SADD myset foo
SADD myset bar
SCARD myset =&gt; 4
SMEMBERS myset =&gt; bar,a,foo,b
</pre>Note that SMEMBERS does not return the elements in the same order we added them, since Sets are <b>unsorted</b> collections of elements. When you want to store the order it is better to use Lists instead. Some more operations against Sets:<br/><br/><pre class="codeblock python python python python python python python python python python" name="code">
SADD mynewset b
SADD mynewset foo
SADD mynewset hello
SINTER myset mynewset =&gt; foo,b
</pre>SINTER can return the intersection between Sets but it is not limited to two sets, you may ask for intersection of 4,5 or 10000 Sets. Finally let's check how SISMEMBER works:<br/><br/><pre class="codeblock python python python python python python python python python python python" name="code">
SISMEMBER myset foo =&gt; 1
SISMEMBER myset notamember =&gt; 0
</pre>Ok I think we are ready to start coding!
<h2><a name="Prerequisites">Prerequisites</a></h2>
If you didn't download it already please <a href="http://code.google.com/p/redis/wiki/README" target="_blank">grab the source code of Retwis</a>. It's a simple tar.gz file with a few of .php files inside. The implementation is very simple. You will find the PHP library client inside (redis.php) that is used to talk with the Redis server from PHP. This library was written by <a href="http://qix.it" target="_blank">Ludovico Magnocavallo</a> and you are free to reuse this in your own projects, but for updated version of the library please download the Redis distribution.<br/><br/>Another thing you probably want is a working Redis server. Just get the source, compile with make, and run with ./redis-server and you are done. No configuration is required at all in order to play with it or to run Retwis in your computer.
<h2><a name="Data layout">Data layout</a></h2>
Working with a relational database this is the stage were the database layout should be produced in form of tables, indexes, and so on. We don't have tables, so what should be designed? We need to identify what keys are needed to represent our objects and what kind of values this keys need to hold.<br/><br/>Let's start from Users. We need to represent this users of course, with the username, userid, password, followers and following users, and so on. The first question is, what should identify an user inside our system? The username can be a good idea since it is unique, but it is also too big, and we want to stay low on memory. So like if our DB was a relational one we can associate an unique ID to every user. Every other reference to this user will be done by id. That's very simple to do, because we have our atomic INCR operation! When we create a new user we can do something like this, assuming the user is callled &quot;antirez&quot;:<br/><br/><pre class="codeblock python python python python python python python python python python python python" name="code">
INCR global:nextUserId =&gt; 1000
SET uid:1000:username antirez
SET uid:1000:password p1pp0
</pre>We use the <i>global:nextUserId</i> key in order to always get an unique ID for every new user. Then we use this unique ID to populate all the other keys holding our user data. <b>This is a Design Pattern</b> with key-values stores! Keep it in mind.
Besides the fields already defined, we need some more stuff in order to fully define an User. For example sometimes it can be useful to be able to get the user ID from the username, so we set this key too:<br/><br/><pre class="codeblock python python python python python python python python python python python python python" name="code">
SET username:antirez:uid 1000
</pre>This may appear strange at first, but remember that we are only able to access data by key! It's not possible to tell Redis to return the key that holds a specific value. This is also <b>our strength</b>, this new paradigm is forcing us to organize the data so that everything is accessible by <i>primary key</i>, speaking with relational DBs language.
<h2><a name="Following, followers and updates">Following, followers and updates</a></h2>
There is another central need in our system. Every user has followers users and following users. We have a perfect data structure for this work! That is... Sets. So let's add this two new fields to our schema:<br/><br/><pre class="codeblock python python python python python python python python python python python python python python" name="code">
uid:1000:followers =&gt; Set of uids of all the followers users
uid:1000:following =&gt; Set of uids of all the following users
</pre>Another important thing we need is a place were we can add the updates to display in the user home page. We'll need to access this data in chronological order later, from the most recent update to the older ones, so the perfect kind of Value for this work is a List. Basically every new update will be LPUSHed in the user updates key, and thanks to LRANGE we can implement pagination and so on. Note that we use the words <i>updates</i> and <i>posts</i> interchangeably, since updates are actually &quot;little posts&quot; in some way.<br/><br/><pre class="codeblock python python python python python python python python python python python python python python python" name="code">
uid:1000:posts =&gt; a List of post ids, every new post is LPUSHed here.
</pre>
<h2><a name="Authentication">Authentication</a></h2>
Ok we have more or less everything about the user, but authentication. We'll handle authentication in a simple but robust way: we don't want to use PHP sessions or other things like this, our system must be ready in order to be distributed among different servers, so we'll take the whole state in our Redis database. So all we need is a random string to set as the cookie of an authenticated user, and a key that will tell us what is the user ID of the client holding such a random string. We need two keys in order to make this thing working in a robust way:<br/><br/><pre class="codeblock python python python python python python python python python python python python python python python python" name="code">
SET uid:1000:auth fea5e81ac8ca77622bed1c2132a021f9
SET auth:fea5e81ac8ca77622bed1c2132a021f9 1000
</pre>In order to authenticate an user we'll do this simple work (login.php):
<ul><li> Get the username and password via the login form</li><li> Check if the username:<code name="code" class="python">&lt;username&gt;</code>:uid key actually exists</li><li> If it exists we have the user id, (i.e. 1000)</li><li> Check if uid:1000:password matches, if not, error message</li><li> Ok authenticated! Set &quot;fea5e81ac8ca77622bed1c2132a021f9&quot; (the value of uid:1000:auth) as &quot;auth&quot; cookie</li></ul>
This is the actual code:<br/><br/><pre class="codeblock python python python python python python python python python python python python python python python python python" name="code">
include(&quot;retwis.php&quot;);
# Form sanity checks
if (!gt(&quot;username&quot;) || !gt(&quot;password&quot;))
goback(&quot;You need to enter both username and password to login.&quot;);
# The form is ok, check if the username is available
$username = gt(&quot;username&quot;);
$password = gt(&quot;password&quot;);
$r = redisLink();
$userid = $r-&gt;get(&quot;username:$username:id&quot;);
if (!$userid)
goback(&quot;Wrong username or password&quot;);
$realpassword = $r-&gt;get(&quot;uid:$userid:password&quot;);
if ($realpassword != $password)
goback(&quot;Wrong useranme or password&quot;);
# Username / password OK, set the cookie and redirect to index.php
$authsecret = $r-&gt;get(&quot;uid:$userid:auth&quot;);
setcookie(&quot;auth&quot;,$authsecret,time()+3600*24*365);
header(&quot;Location: index.php&quot;);
</pre>This happens every time the users log in, but we also need a function isLoggedIn in order to check if a given user is already authenticated or not. These are the logical steps preformed by the <code name="code" class="python">isLoggedIn</code> function:
<ul><li> Get the &quot;auth&quot; cookie from the user. If there is no cookie, the user is not logged in, of course. Let's call the value of this cookie <code name="code" class="python">&lt;authcookie&gt;</code></li><li> Check if auth:<code name="code" class="python">&lt;authcookie&gt;</code> exists, and what the value (the user id) is (1000 in the exmple).</li><li> In order to be sure check that uid:1000:auth matches.</li><li> Ok the user is authenticated, and we loaded a bit of information in the $User global variable.</li></ul>
The code is simpler than the description, possibly:<br/><br/><pre class="codeblock python python python python python python python python python python python python python python python python python python" name="code">
function isLoggedIn() {
global $User, $_COOKIE;
if (isset($User)) return true;
if (isset($_COOKIE['auth'])) {
$r = redisLink();
$authcookie = $_COOKIE['auth'];
if ($userid = $r-&gt;get(&quot;auth:$authcookie&quot;)) {
if ($r-&gt;get(&quot;uid:$userid:auth&quot;) != $authcookie) return false;
loadUserInfo($userid);
return true;
}
}
return false;
}
function loadUserInfo($userid) {
global $User;
$r = redisLink();
$User['id'] = $userid;
$User['username'] = $r-&gt;get(&quot;uid:$userid:username&quot;);
return true;
}
</pre><code name="code" class="python">loadUserInfo</code> as separated function is an overkill for our application, but it's a good template for a complex application. The only thing it's missing from all the authentication is the logout. What we do on logout? That's simple, we'll just change the random string in uid:1000:auth, remove the old auth:<code name="code" class="python">&lt;oldauthstring&gt;</code> and add a new auth:<code name="code" class="python">&lt;newauthstring&gt;</code>.<br/><br/><b>Important:</b> the logout procedure explains why we don't just authenticate the user after the lookup of auth:<code name="code" class="python">&lt;randomstring&gt;</code>, but double check it against uid:1000:auth. The true authentication string is the latter, the auth:<code name="code" class="python">&lt;randomstring&gt;</code> is just an authentication key that may even be volatile, or if there are bugs in the program or a script gets interrupted we may even end with multiple auth:<code name="code" class="python">&lt;something&gt;</code> keys pointing to the same user id. The logout code is the following (logout.php):<br/><br/><pre class="codeblock python python python python python python python python python python python python python python python python python python python" name="code">
include(&quot;retwis.php&quot;);
if (!isLoggedIn()) {
header(&quot;Location: index.php&quot;);
exit;
}
$r = redisLink();
$newauthsecret = getrand();
$userid = $User['id'];
$oldauthsecret = $r-&gt;get(&quot;uid:$userid:auth&quot;);
$r-&gt;set(&quot;uid:$userid:auth&quot;,$newauthsecret);
$r-&gt;set(&quot;auth:$newauthsecret&quot;,$userid);
$r-&gt;delete(&quot;auth:$oldauthsecret&quot;);
header(&quot;Location: index.php&quot;);
</pre>That is just what we described and should be simple to undestand.
<h2><a name="Updates">Updates</a></h2>
Updates, also known as posts, are even simpler. In order to create a new post on the database we do something like this:<br/><br/><pre class="codeblock python python python python python python python python python python python python python python python python python python python python" name="code">
INCR global:nextPostId =&gt; 10343
SET post:10343 &quot;$owner_id|$time|I'm having fun with Retwis&quot;
</pre>As you can se the user id and time of the post are stored directly inside the string, we don't need to lookup by time or user id in the example application so it is better to compact everything inside the post string.<br/><br/>After we create a post we obtain the post id. We need to LPUSH this post id in every user that's following the author of the post, and of course in the list of posts of the author. This is the file update.php that shows how this is performed:<br/><br/><pre class="codeblock python python python python python python python python python python python python python python python python python python python python python" name="code">
include(&quot;retwis.php&quot;);
if (!isLoggedIn() || !gt(&quot;status&quot;)) {
header(&quot;Location:index.php&quot;);
exit;
}
$r = redisLink();
$postid = $r-&gt;incr(&quot;global:nextPostId&quot;);
$status = str_replace(&quot;\n&quot;,&quot; &quot;,gt(&quot;status&quot;));
$post = $User['id'].&quot;|&quot;.time().&quot;|&quot;.$status;
$r-&gt;set(&quot;post:$postid&quot;,$post);
$followers = $r-&gt;smembers(&quot;uid:&quot;.$User['id'].&quot;:followers&quot;);
if ($followers === false) $followers = Array();
$followers[] = $User['id']; /* Add the post to our own posts too */
foreach($followers as $fid) {
$r-&gt;push(&quot;uid:$fid:posts&quot;,$postid,false);
}
# Push the post on the timeline, and trim the timeline to the
# newest 1000 elements.
$r-&gt;push(&quot;global:timeline&quot;,$postid,false);
$r-&gt;ltrim(&quot;global:timeline&quot;,0,1000);
header(&quot;Location: index.php&quot;);
</pre>The core of the function is the <code name="code" class="python">foreach</code>. We get using SMEMBERS all the followers of the current user, then the loop will LPUSH the post against the uid:<code name="code" class="python">&lt;userid&gt;</code>:posts of every follower.<br/><br/>Note that we also maintain a timeline with all the posts. In order to do so what is needed is just to LPUSH the post against global:timeline. Let's face it, do you start thinking it was a bit strange to have to sort things added in chronological order using ORDER BY with SQL? I think so indeed.
<h2><a name="Paginating updates">Paginating updates</a></h2>
Now it should be pretty clear how we can user LRANGE in order to get ranges of posts, and render this posts on the screen. The code is simple:<br/><br/><pre class="codeblock python python python python python python python python python python python python python python python python python python python python python python" name="code">
function showPost($id) {
$r = redisLink();
$postdata = $r-&gt;get(&quot;post:$id&quot;);
if (!$postdata) return false;
$aux = explode(&quot;|&quot;,$postdata);
$id = $aux[0];
$time = $aux[1];
$username = $r-&gt;get(&quot;uid:$id:username&quot;);
$post = join(array_splice($aux,2,count($aux)-2),&quot;|&quot;);
$elapsed = strElapsed($time);
$userlink = &quot;&lt;a class=\&quot;username\&quot; href=\&quot;profile.php?u=&quot;.urlencode($username).&quot;\&quot;&gt;&quot;.utf8entities($username).&quot;&lt;/a&gt;&quot;;
echo('&lt;div class=&quot;post&quot;&gt;'.$userlink.' '.utf8entities($post).&quot;&lt;br&gt;&quot;);
echo('&lt;i&gt;posted '.$elapsed.' ago via web&lt;/i&gt;&lt;/div&gt;');
return true;
}
function showUserPosts($userid,$start,$count) {
$r = redisLink();
$key = ($userid == -1) ? &quot;global:timeline&quot; : &quot;uid:$userid:posts&quot;;
$posts = $r-&gt;lrange($key,$start,$start+$count);
$c = 0;
foreach($posts as $p) {
if (showPost($p)) $c++;
if ($c == $count) break;
}
return count($posts) == $count+1;
}
</pre><code name="code" class="python">showPost</code> will simply convert and print a Post in HTML while <code name="code" class="python">showUserPosts</code> get range of posts passing them to <code name="code" class="python">showPosts</code>.<h2><a name="Following users">Following users</a></h2>If user id 1000 (antirez) wants to follow user id 1001 (pippo), we can do this with just two SADD:<br/><br/><pre class="codeblock python python python python python python python python python python python python python python python python python python python python python python python" name="code">
SADD uid:1000:following 1001
SADD uid:1001:followers 1000
</pre>Note the same pattern again and again, in theory with a relational database the list of following and followers is a single table with fields like <code name="code" class="python">following_id</code> and <code name="code" class="python">follower_id</code>. With queries you can extract the followers or following of every user. With a key-value DB that's a bit different as we need to set both the <code name="code" class="python">1000 is following 1001</code> and <code name="code" class="python">1001 is followed by 1000</code> relations. This is the price to pay, but on the other side accessing the data is simpler and ultra-fast. And having this things as separated sets allows us to do interesting stuff, for example using SINTER we can have the intersection of 'following' of two different users, so we may add a feature to our Twitter clone so that it is able to say you at warp speed, when you visit somebody' else profile, &quot;you and foobar have 34 followers in common&quot; and things like that.<br/><br/>You can find the code that sets or removes a following/follower relation at follow.php. It is trivial as you can see.
<h1><a name="Making it horizontally scalable">Making it horizontally scalable</a></h1>
Gentle reader, if you reached this point you are already an hero, thank you. Before to talk about scaling horizontally it is worth to check the performances on a single server. Retwis is <b>amazingly fast</b>, without any kind of cache. On a very slow and loaded server, apache benchmark with 100 parallel clients issuing 100000 requests measured the average pageview to take 5 milliseconds. This means you can serve millions of users every day with just a single Linux box, and this one was monkey asses slow! Go figure with more recent hardware.<br/><br/>So, first of all, probably you will not need more than one server for a lot of applications, even when you have a lot of users. But let's assume we <b>are</b> Twitter and need to handle a huge amount of traffic. What to do?
<h3><a name="Hashing the key">Hashing the key</a></h3>
The first thing to do is to hash the key and issue the request on different servers based on the key hash. There are a lot of well known algorithms to do so, for example check the Redis Ruby library client that implements <i>consistent hashing</i>, but the general idea is that you can turn your key into a number, and than take the reminder of the division of this number by the number of servers you have:<br/><br/><pre class="codeblock python python python python python python python python python python python python python python python python python python python python python python python python" name="code">
server_id = crc32(key) % number_of_servers
</pre>This has a lot of problems since if you add one server you need to move too much keys and so on, but this is the general idea even if you use a better hashing scheme like consistent hashing.<br/><br/>Ok, are key accesses distributed among the key space? Well, all the user data will be partitioned among different servers. There are no inter-keys operations used (like SINTER, otherwise you need to care that things you want to intersect will end in the same server. <b>This is why Redis unlike memcached does not force a specific hashing scheme, it's application specific</b>). Btw there are keys that are accessed more frequently.<h3><a name="Special keys">Special keys</a></h3>For example every time we post a new message, we <b>need</b> to increment the <code name="code" class="python">global:nextPostId</code> key. How to fix this problem? A Single server will get a lot if increments. The simplest way to handle this is to have a dedicated server just for increments. This is probably an overkill btw unless you have really a lot of traffic. There is another trick. The ID does not really need to be an incremental number, but just <b>it needs to be unique</b>. So you can get a random string long enough to be unlikely (almost impossible, if it's md5-size) to collide, and you are done. We successfully eliminated our main problem to make it really horizontally scalable!<br/><br/>There is another one: global:timeline. There is no fix for this, if you need to take something in order you can split among different servers and <b>then merge</b> when you need to get the data back, or take it ordered and use a single key. Again if you really have so much posts per second, you can use a single server just for this. Remember that with commodity hardware Redis is able to handle 100000 writes for second, that's enough even for Twitter, I guess.<br/><br/>Please feel free to use the comments below for questions and feedbacks.
</div>
</div>
</div>
</body>
</html>

44
doc/TypeCommand.html Normal file
View File

@ -0,0 +1,44 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>TypeCommand: Contents</b><br>&nbsp;&nbsp;<a href="#TYPE _key_">TYPE _key_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#See also">See also</a>
</div>
<h1 class="wikiname">TypeCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="TYPE _key_">TYPE _key_</a></h1>
<i>Time complexity: O(1)</i><blockquote>Return the type of the value stored at <i>key</i> in form of astring. The type can be one of &quot;none&quot;, &quot;string&quot;, &quot;list&quot;, &quot;set&quot;.&quot;none&quot; is returned if the key does not exist.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Single line reply</a>, specifically:<br/><br/><pre class="codeblock python" name="code">
&quot;none&quot; if the key does not exist
&quot;string&quot; if the key contains a String value
&quot;list&quot; if the key contains a List value
&quot;set&quot; if the key contains a Set value
</pre><h2><a name="See also">See also</a></h2>
<ul><li> <a href="DataTypes.html">Redis Data Types</a></li></ul>
</div>
</div>
</div>
</body>
</html>

40
doc/VersionControl.html Normal file
View File

@ -0,0 +1,40 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>VersionControl: Contents</b><br>&nbsp;&nbsp;<a href="#VERSION">VERSION</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a>
</div>
<h1 class="wikiname">VersionControl</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="VERSION">VERSION</a></h1>Return the server version as a float string. Example:<br/><br/><pre class="codeblock python" name="code">
VERSION
0.07
</pre>It is guaranteed that if you compare versions as floats newer versions will be greater than older versions.<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Single line reply</a>
</div>
</div>
</div>
</body>
</html>

36
doc/index.html Normal file
View File

@ -0,0 +1,36 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>index: Contents</b><br>&nbsp;&nbsp;<a href="#Redis Documentation">Redis Documentation</a>
</div>
<h1 class="wikiname">index</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="Redis Documentation">Redis Documentation</a></h1>Hello! The followings are pointers to different parts of the Redis Documentation.<br/><br/><ul><li> <a href="README.html">The README</a> is the best starting point to know more about the project.</li><li> <a href="CommandReference.html">The command reference</a> is a description of all the Redis commands with links to command specific pages.</li><li> <a href="ProtocolSpecification.html">The Protocol Specification</a> is all you need in order to implement a Redis client library for a missing language. PHP, Python, Ruby and Erlang are already supported.</li><li> <a href="TwitterAlikeExample.html">This is a tuturial about creating a Twitter clone using *only* Redis as database, no relational DB at all is used</a>, it is a good start to understand the key-value database paradigm.</li><li> <a href="FAQ.html">Our FAQ</a> contains of course some answers to common questions about Redis.</li><li> <a href="Benchmarks.html">The benchmark page</a> is about the speed performances of Redis.</li><li> <b>New!</b> video: <a href="http://mwrc2009.confreaks.com/13-mar-2009-19-24-redis-key-value-nirvana-ezra-zygmuntowicz.html" target="_blank">watch the Ezra Zygmuntowicz talk about Redis</a> to know the most important Redis ideas in few minutes.</li></ul>
</div>
</div>
</div>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More