maxmemory implemented

This commit is contained in:
antirez 2009-05-27 22:53:20 +02:00
parent 16edf32dc3
commit 3fd78bcd45
2 changed files with 101 additions and 20 deletions

109
redis.c
View File

@ -80,8 +80,13 @@
#define REDIS_HT_MINSLOTS 16384 /* Never resize the HT under this */ #define REDIS_HT_MINSLOTS 16384 /* Never resize the HT under this */
/* Command flags */ /* Command flags */
#define REDIS_CMD_BULK 1 #define REDIS_CMD_BULK 1 /* Bulk write command */
#define REDIS_CMD_INLINE 2 #define REDIS_CMD_INLINE 2 /* Inline command */
/* REDIS_CMD_DENYOOM reserves a longer comment: all the commands marked with
this flags will return an error when the 'maxmemory' option is set in the
config file and the server is using more than maxmemory bytes of memory.
In short this commands are denied on low memory conditions. */
#define REDIS_CMD_DENYOOM 4
/* Object types */ /* Object types */
#define REDIS_STRING 0 #define REDIS_STRING 0
@ -246,6 +251,7 @@ struct redisServer {
redisClient *master; /* client that is master for this slave */ redisClient *master; /* client that is master for this slave */
int replstate; int replstate;
unsigned int maxclients; unsigned int maxclients;
unsigned int maxmemory;
/* Sort parameters - qsort_r() is only available under BSD so we /* Sort parameters - qsort_r() is only available under BSD so we
* have to take this state global, in order to pass it to sortCompare() */ * have to take this state global, in order to pass it to sortCompare() */
int sort_desc; int sort_desc;
@ -307,6 +313,7 @@ static int deleteKey(redisDb *db, robj *key);
static time_t getExpire(redisDb *db, robj *key); static time_t getExpire(redisDb *db, robj *key);
static int setExpire(redisDb *db, robj *key, time_t when); static int setExpire(redisDb *db, robj *key, time_t when);
static void updateSalvesWaitingBgsave(int bgsaveerr); static void updateSalvesWaitingBgsave(int bgsaveerr);
static void freeMemoryIfNeeded(void);
static void authCommand(redisClient *c); static void authCommand(redisClient *c);
static void pingCommand(redisClient *c); static void pingCommand(redisClient *c);
@ -371,38 +378,38 @@ static void slaveofCommand(redisClient *c);
static struct redisServer server; /* server global state */ static struct redisServer server; /* server global state */
static struct redisCommand cmdTable[] = { static struct redisCommand cmdTable[] = {
{"get",getCommand,2,REDIS_CMD_INLINE}, {"get",getCommand,2,REDIS_CMD_INLINE},
{"set",setCommand,3,REDIS_CMD_BULK}, {"set",setCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM},
{"setnx",setnxCommand,3,REDIS_CMD_BULK}, {"setnx",setnxCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM},
{"del",delCommand,-2,REDIS_CMD_INLINE}, {"del",delCommand,-2,REDIS_CMD_INLINE},
{"exists",existsCommand,2,REDIS_CMD_INLINE}, {"exists",existsCommand,2,REDIS_CMD_INLINE},
{"incr",incrCommand,2,REDIS_CMD_INLINE}, {"incr",incrCommand,2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM},
{"decr",decrCommand,2,REDIS_CMD_INLINE}, {"decr",decrCommand,2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM},
{"mget",mgetCommand,-2,REDIS_CMD_INLINE}, {"mget",mgetCommand,-2,REDIS_CMD_INLINE},
{"rpush",rpushCommand,3,REDIS_CMD_BULK}, {"rpush",rpushCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM},
{"lpush",lpushCommand,3,REDIS_CMD_BULK}, {"lpush",lpushCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM},
{"rpop",rpopCommand,2,REDIS_CMD_INLINE}, {"rpop",rpopCommand,2,REDIS_CMD_INLINE},
{"lpop",lpopCommand,2,REDIS_CMD_INLINE}, {"lpop",lpopCommand,2,REDIS_CMD_INLINE},
{"llen",llenCommand,2,REDIS_CMD_INLINE}, {"llen",llenCommand,2,REDIS_CMD_INLINE},
{"lindex",lindexCommand,3,REDIS_CMD_INLINE}, {"lindex",lindexCommand,3,REDIS_CMD_INLINE},
{"lset",lsetCommand,4,REDIS_CMD_BULK}, {"lset",lsetCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM},
{"lrange",lrangeCommand,4,REDIS_CMD_INLINE}, {"lrange",lrangeCommand,4,REDIS_CMD_INLINE},
{"ltrim",ltrimCommand,4,REDIS_CMD_INLINE}, {"ltrim",ltrimCommand,4,REDIS_CMD_INLINE},
{"lrem",lremCommand,4,REDIS_CMD_BULK}, {"lrem",lremCommand,4,REDIS_CMD_BULK},
{"sadd",saddCommand,3,REDIS_CMD_BULK}, {"sadd",saddCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM},
{"srem",sremCommand,3,REDIS_CMD_BULK}, {"srem",sremCommand,3,REDIS_CMD_BULK},
{"smove",smoveCommand,4,REDIS_CMD_BULK}, {"smove",smoveCommand,4,REDIS_CMD_BULK},
{"sismember",sismemberCommand,3,REDIS_CMD_BULK}, {"sismember",sismemberCommand,3,REDIS_CMD_BULK},
{"scard",scardCommand,2,REDIS_CMD_INLINE}, {"scard",scardCommand,2,REDIS_CMD_INLINE},
{"sinter",sinterCommand,-2,REDIS_CMD_INLINE}, {"sinter",sinterCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM},
{"sinterstore",sinterstoreCommand,-3,REDIS_CMD_INLINE}, {"sinterstore",sinterstoreCommand,-3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM},
{"sunion",sunionCommand,-2,REDIS_CMD_INLINE}, {"sunion",sunionCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM},
{"sunionstore",sunionstoreCommand,-3,REDIS_CMD_INLINE}, {"sunionstore",sunionstoreCommand,-3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM},
{"sdiff",sdiffCommand,-2,REDIS_CMD_INLINE}, {"sdiff",sdiffCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM},
{"sdiffstore",sdiffstoreCommand,-3,REDIS_CMD_INLINE}, {"sdiffstore",sdiffstoreCommand,-3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM},
{"smembers",sinterCommand,2,REDIS_CMD_INLINE}, {"smembers",sinterCommand,2,REDIS_CMD_INLINE},
{"incrby",incrbyCommand,3,REDIS_CMD_INLINE}, {"incrby",incrbyCommand,3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM},
{"decrby",decrbyCommand,3,REDIS_CMD_INLINE}, {"decrby",decrbyCommand,3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM},
{"getset",getSetCommand,3,REDIS_CMD_BULK}, {"getset",getSetCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM},
{"randomkey",randomkeyCommand,1,REDIS_CMD_INLINE}, {"randomkey",randomkeyCommand,1,REDIS_CMD_INLINE},
{"select",selectCommand,2,REDIS_CMD_INLINE}, {"select",selectCommand,2,REDIS_CMD_INLINE},
{"move",moveCommand,3,REDIS_CMD_INLINE}, {"move",moveCommand,3,REDIS_CMD_INLINE},
@ -422,7 +429,7 @@ static struct redisCommand cmdTable[] = {
{"sync",syncCommand,1,REDIS_CMD_INLINE}, {"sync",syncCommand,1,REDIS_CMD_INLINE},
{"flushdb",flushdbCommand,1,REDIS_CMD_INLINE}, {"flushdb",flushdbCommand,1,REDIS_CMD_INLINE},
{"flushall",flushallCommand,1,REDIS_CMD_INLINE}, {"flushall",flushallCommand,1,REDIS_CMD_INLINE},
{"sort",sortCommand,-2,REDIS_CMD_INLINE}, {"sort",sortCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM},
{"info",infoCommand,1,REDIS_CMD_INLINE}, {"info",infoCommand,1,REDIS_CMD_INLINE},
{"monitor",monitorCommand,1,REDIS_CMD_INLINE}, {"monitor",monitorCommand,1,REDIS_CMD_INLINE},
{"ttl",ttlCommand,2,REDIS_CMD_INLINE}, {"ttl",ttlCommand,2,REDIS_CMD_INLINE},
@ -864,6 +871,7 @@ static void initServerConfig() {
server.requirepass = NULL; server.requirepass = NULL;
server.shareobjects = 0; server.shareobjects = 0;
server.maxclients = 0; server.maxclients = 0;
server.maxmemory = 0;
ResetServerSaveParams(); ResetServerSaveParams();
appendServerSaveParams(60*60,1); /* save after 1 hour and 1 change */ appendServerSaveParams(60*60,1); /* save after 1 hour and 1 change */
@ -1024,6 +1032,8 @@ static void loadServerConfig(char *filename) {
} }
} else if (!strcasecmp(argv[0],"maxclients") && argc == 2) { } else if (!strcasecmp(argv[0],"maxclients") && argc == 2) {
server.maxclients = atoi(argv[1]); server.maxclients = atoi(argv[1]);
} else if (!strcasecmp(argv[0],"maxmemory") && argc == 2) {
server.maxmemory = atoi(argv[1]);
} else if (!strcasecmp(argv[0],"slaveof") && argc == 3) { } else if (!strcasecmp(argv[0],"slaveof") && argc == 3) {
server.masterhost = sdsnew(argv[1]); server.masterhost = sdsnew(argv[1]);
server.masterport = atoi(argv[2]); server.masterport = atoi(argv[2]);
@ -1207,6 +1217,9 @@ static int processCommand(redisClient *c) {
struct redisCommand *cmd; struct redisCommand *cmd;
long long dirty; long long dirty;
/* Free some memory if needed (maxmemory setting) */
if (server.maxmemory) freeMemoryIfNeeded();
/* The QUIT command is handled as a special case. Normal command /* The QUIT command is handled as a special case. Normal command
* procs are unable to close the client connection safely */ * procs are unable to close the client connection safely */
if (!strcasecmp(c->argv[0]->ptr,"quit")) { if (!strcasecmp(c->argv[0]->ptr,"quit")) {
@ -1223,6 +1236,10 @@ static int processCommand(redisClient *c) {
addReplySds(c,sdsnew("-ERR wrong number of arguments\r\n")); addReplySds(c,sdsnew("-ERR wrong number of arguments\r\n"));
resetClient(c); resetClient(c);
return 1; return 1;
} else if (server.maxmemory && cmd->flags & REDIS_CMD_DENYOOM && zmalloc_used_memory() > server.maxmemory) {
addReplySds(c,sdsnew("-ERR command not allowed when used memory > 'maxmemory'\r\n"));
resetClient(c);
return 1;
} else if (cmd->flags & REDIS_CMD_BULK && c->bulklen == -1) { } else if (cmd->flags & REDIS_CMD_BULK && c->bulklen == -1) {
int bulklen = atoi(c->argv[c->argc-1]->ptr); int bulklen = atoi(c->argv[c->argc-1]->ptr);
@ -3974,6 +3991,58 @@ static void slaveofCommand(redisClient *c) {
addReply(c,shared.ok); addReply(c,shared.ok);
} }
/* ============================ Maxmemory directive ======================== */
/* This function gets called when 'maxmemory' is set on the config file to limit
* the max memory used by the server, and we are out of memory.
* This function will try to, in order:
*
* - Free objects from the free list
* - Try to remove keys with an EXPIRE set
*
* It is not possible to free enough memory to reach used-memory < maxmemory
* the server will start refusing commands that will enlarge even more the
* memory usage.
*/
static void freeMemoryIfNeeded(void) {
while (server.maxmemory && zmalloc_used_memory() > server.maxmemory) {
if (listLength(server.objfreelist)) {
robj *o;
listNode *head = listFirst(server.objfreelist);
o = listNodeValue(head);
listDelNode(server.objfreelist,head);
zfree(o);
} else {
int j, k, freed = 0;
for (j = 0; j < server.dbnum; j++) {
int minttl = -1;
robj *minkey = NULL;
struct dictEntry *de;
if (dictSize(server.db[j].expires)) {
freed = 1;
/* From a sample of three keys drop the one nearest to
* the natural expire */
for (k = 0; k < 3; k++) {
time_t t;
de = dictGetRandomKey(server.db[j].expires);
t = (time_t) dictGetEntryVal(de);
if (minttl == -1 || t < minttl) {
minkey = dictGetEntryKey(de);
minttl = t;
}
}
deleteKey(server.db+j,minkey);
}
}
if (!freed) return; /* nothing to free... */
}
}
}
/* =================================== Main! ================================ */ /* =================================== Main! ================================ */
#ifdef __linux__ #ifdef __linux__

View File

@ -88,6 +88,18 @@ databases 16
# maxclients 128 # maxclients 128
# Don't use more memory than the specified amount of bytes.
# When the memory limit is reached Redis will try to remove keys with an
# EXPIRE set. It will try to start freeing keys that are going to expire
# in little time and preserve keys with a longer time to live.
# Redis will also try to remove objects from free lists if possible.
#
# If all this fails, Redis will start to reply with errors to commands
# that will use more memory, like SET, LPUSH, and so on, and will continue
# to reply to most read-only commands like GET.
# maxmemory <bytes>
############################### ADVANCED CONFIG ############################### ############################### ADVANCED CONFIG ###############################
# Glue small output buffers together in order to send small replies in a # Glue small output buffers together in order to send small replies in a