Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6589821035 | ||
|
|
48f4f77189 | ||
|
|
b3281e93c3 | ||
|
|
2677c97d39 | ||
|
|
1d426bf53b | ||
|
|
da2dd8991b | ||
|
|
13f84841b5 | ||
|
|
ef3a95fa6f | ||
|
|
665b819eb4 | ||
|
|
bb562a6414 | ||
|
|
e166abad10 | ||
|
|
aec5ea5d07 | ||
|
|
2d24bf2f94 | ||
|
|
e7a61d287e | ||
|
|
78bae8b078 | ||
|
|
bf6c5d960d | ||
|
|
95fc9cc0a6 | ||
|
|
5de5efa364 | ||
|
|
1f2d5941a1 | ||
|
|
60aa3a67e8 | ||
|
|
921aec17b6 | ||
|
|
e174290307 | ||
|
|
a3273dbfa6 | ||
|
|
e9318d92db | ||
|
|
803c5b140d | ||
|
|
74373cd930 | ||
|
|
b5047d9b2f | ||
|
|
34b420dbf3 | ||
|
|
02ac84ddbd | ||
|
|
45bfab8596 |
@@ -14,6 +14,18 @@ HIGH: There is a critical bug that may affect a subset of users. Upgrade!
|
||||
CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--[ Redis 2.6.11 ]
|
||||
|
||||
UPGRADE URGENCY: LOW, however updating is encouraged if you have many instances
|
||||
per server and you want to lower the CPU / energy usage.
|
||||
|
||||
* [BUGFIX] Replication: more strict error checking for master PING reply.
|
||||
* [BUGFIX] redis-cli: use keepalive socket option for improved reliability.
|
||||
* [BUGFIX] Allow AUTH while loading the DB in memory.
|
||||
* [BUGFIX] Don't segfault on unbalanced quotes while parsing config file.
|
||||
* [IMPROVED] serverCron() frequency is now a runtime parameter (was REDIS_HZ).
|
||||
* [IMPROVED] Use a lot less CPU when idle, even with many configured DBs.
|
||||
|
||||
--[ Redis 2.6.10 ]
|
||||
|
||||
UPGRADE URGENCY: MODERATE, this release contains many non-critical fixes
|
||||
|
||||
@@ -4,17 +4,64 @@
|
||||
Redis Manifesto
|
||||
===============
|
||||
|
||||
1 - A DSL for Abstract Data Types. Redis is a DSL (Domain Specific Language) that manipulates abstract data types and implemented as a TCP daemon. Commands manipulate a key space where keys are binary-safe strings and values are different kinds of abstract data types. Every data type represents an abstract version of a fundamental data structure. For instance Redis Lists are an abstract representation of linked lists. In Redis, the essence of a data type isn't just the kind of operations that the data types support, but also the space and time complexity of the data type and the operations performed upon it.
|
||||
1 - A DSL for Abstract Data Types. Redis is a DSL (Domain Specific Language)
|
||||
that manipulates abstract data types and implemented as a TCP daemon.
|
||||
Commands manipulate a key space where keys are binary-safe strings and
|
||||
values are different kinds of abstract data types. Every data type
|
||||
represents an abstract version of a fundamental data structure. For instance
|
||||
Redis Lists are an abstract representation of linked lists. In Redis, the
|
||||
essence of a data type isn't just the kind of operations that the data types
|
||||
support, but also the space and time complexity of the data type and the
|
||||
operations performed upon it.
|
||||
|
||||
2 - Memory storage is #1. The Redis data set, composed of defined key-value pairs, is primarily stored in the computer's memory. The amount of memory in all kinds of computers, including entry-level servers, is increasing significantly each year. Memory is fast, and allows Redis to have very predictable performance. Datasets composed of 10k or 40 millions keys will perform similarly. Complex data types like Redis Sorted Sets are easy to implement and manipulate in memory with good performance, making Redis very simple. Redis will continue to explore alternative options (where data can be optionally stored on disk, say) but the main goal of the project remains the development of an in-memory database.
|
||||
2 - Memory storage is #1. The Redis data set, composed of defined key-value
|
||||
pairs, is primarily stored in the computer's memory. The amount of memory in
|
||||
all kinds of computers, including entry-level servers, is increasing
|
||||
significantly each year. Memory is fast, and allows Redis to have very
|
||||
predictable performance. Datasets composed of 10k or 40 millions keys will
|
||||
perform similarly. Complex data types like Redis Sorted Sets are easy to
|
||||
implement and manipulate in memory with good performance, making Redis very
|
||||
simple. Redis will continue to explore alternative options (where data can
|
||||
be optionally stored on disk, say) but the main goal of the project remains
|
||||
the development of an in-memory database.
|
||||
|
||||
3 - Fundamental data structures for a fundamental API. The Redis API is a direct consequence of fundamental data structures. APIs can often be arbitrary but not an API that resembles the nature of fundamental data structures. If we ever meet intelligent life forms from another part of the universe, they'll likely know, understand and recognize the same basic data structures we have in our computer science books. Redis will avoid intermediate layers in API, so that the complexity is obvious and more complex operations can be performed as the sum of the basic operations.
|
||||
3 - Fundamental data structures for a fundamental API. The Redis API is a direct
|
||||
consequence of fundamental data structures. APIs can often be arbitrary but
|
||||
not an API that resembles the nature of fundamental data structures. If we
|
||||
ever meet intelligent life forms from another part of the universe, they'll
|
||||
likely know, understand and recognize the same basic data structures we have
|
||||
in our computer science books. Redis will avoid intermediate layers in API,
|
||||
so that the complexity is obvious and more complex operations can be
|
||||
performed as the sum of the basic operations.
|
||||
|
||||
4 - Code is like a poem; it's not just something we write to reach some practical result. Sometimes people that are far from the Redis philosophy suggest using other code written by other authors (frequently in other languages) in order to implement something Redis currently lacks. But to us this is like if Shakespeare decided to end Enrico IV using the Paradiso from the Divina Commedia. Is using any external code a bad idea? Not at all. Like in "One Thousand and One Nights" smaller self contained stories are embedded in a bigger story, we'll be happy to use beautiful self contained libraries when needed. At the same time, when writing the Redis story we're trying to write smaller stories that will fit in to other code.
|
||||
4 - Code is like a poem; it's not just something we write to reach some
|
||||
practical result. Sometimes people that are far from the Redis philosophy
|
||||
suggest using other code written by other authors (frequently in other
|
||||
languages) in order to implement something Redis currently lacks. But to us
|
||||
this is like if Shakespeare decided to end Enrico IV using the Paradiso from
|
||||
the Divina Commedia. Is using any external code a bad idea? Not at all. Like
|
||||
in "One Thousand and One Nights" smaller self contained stories are embedded
|
||||
in a bigger story, we'll be happy to use beautiful self contained libraries
|
||||
when needed. At the same time, when writing the Redis story we're trying to
|
||||
write smaller stories that will fit in to other code.
|
||||
|
||||
5 - We're against complexity. We believe designing systems is a fight against complexity. We'll accept to fight the complexity when it's worthwhile but we'll try hard to recognize when a small feature is not worth 1000s of lines of code. Most of the time the best way to fight complexity is by not creating it at all.
|
||||
5 - We're against complexity. We believe designing systems is a fight against
|
||||
complexity. We'll accept to fight the complexity when it's worthwhile but
|
||||
we'll try hard to recognize when a small feature is not worth 1000s of lines
|
||||
of code. Most of the time the best way to fight complexity is by not
|
||||
creating it at all.
|
||||
|
||||
6 - Two levels of API. The Redis API has two levels: 1) a subset of the API fits naturally into a distributed version of Redis and 2) a more complex API that supports multi-key operations. Both are useful if used judiciously but there's no way to make the more complex multi-keys API distributed in an opaque way without violating our other principles. We don't want to provide the illusion of something that will work magically when actually it can't in all cases. Instead we'll provide commands to quickly migrate keys from one instance to another to perform multi-key operations and expose the tradeoffs to the user.
|
||||
|
||||
7 - We optimize for joy. We believe writing code is a lot of hard work, and the only way it can be worth is by enjoying it. When there is no longer joy in writing code, the best thing to do is stop. To prevent this, we'll avoid taking paths that will make Redis less of a joy to develop.
|
||||
6 - Two levels of API. The Redis API has two levels: 1) a subset of the API fits
|
||||
naturally into a distributed version of Redis and 2) a more complex API that
|
||||
supports multi-key operations. Both are useful if used judiciously but
|
||||
there's no way to make the more complex multi-keys API distributed in an
|
||||
opaque way without violating our other principles. We don't want to provide
|
||||
the illusion of something that will work magically when actually it can't in
|
||||
all cases. Instead we'll provide commands to quickly migrate keys from one
|
||||
instance to another to perform multi-key operations and expose the tradeoffs
|
||||
to the user.
|
||||
|
||||
7 - We optimize for joy. We believe writing code is a lot of hard work, and the
|
||||
only way it can be worth is by enjoying it. When there is no longer joy in
|
||||
writing code, the best thing to do is stop. To prevent this, we'll avoid
|
||||
taking paths that will make Redis less of a joy to develop.
|
||||
|
||||
+21
-1
@@ -175,7 +175,7 @@ dir ./
|
||||
# still reply to client requests, possibly with out of date data, or the
|
||||
# data set may just be empty if this is the first synchronization.
|
||||
#
|
||||
# 2) if slave-serve-stale data is set to 'no' the slave will reply with
|
||||
# 2) if slave-serve-stale-data is set to 'no' the slave will reply with
|
||||
# an error "SYNC with master in progress" to all the kind of commands
|
||||
# but to INFO and SLAVEOF.
|
||||
#
|
||||
@@ -272,6 +272,9 @@ slave-priority 100
|
||||
# an empty string:
|
||||
#
|
||||
# rename-command CONFIG ""
|
||||
#
|
||||
# Please note that changing the name of commands that are logged into the
|
||||
# AOF file or transmitted to slaves may cause problems.
|
||||
|
||||
################################### LIMITS ####################################
|
||||
|
||||
@@ -560,6 +563,23 @@ client-output-buffer-limit normal 0 0 0
|
||||
client-output-buffer-limit slave 256mb 64mb 60
|
||||
client-output-buffer-limit pubsub 32mb 8mb 60
|
||||
|
||||
# Redis calls an internal function to perform many background tasks, like
|
||||
# closing connections of clients in timeot, purging expired keys that are
|
||||
# never requested, and so forth.
|
||||
#
|
||||
# Not all tasks are perforemd with the same frequency, but Redis checks for
|
||||
# tasks to perform accordingly to the specified "hz" value.
|
||||
#
|
||||
# By default "hz" is set to 10. Raising the value will use more CPU when
|
||||
# Redis is idle, but at the same time will make Redis more responsive when
|
||||
# there are many keys expiring at the same time, and timeouts may be
|
||||
# handled with more precision.
|
||||
#
|
||||
# The range is between 1 and 500, however a value over 100 is usually not
|
||||
# a good idea. Most users should use the default of 10 and raise this up to
|
||||
# 100 only in environments where very low latency is required.
|
||||
hz 10
|
||||
|
||||
################################## INCLUDES ###################################
|
||||
|
||||
# Include one or more other config files here. This is useful if you
|
||||
|
||||
+22
-1
@@ -68,11 +68,21 @@ void loadServerConfigFromString(char *config) {
|
||||
linenum = i+1;
|
||||
lines[i] = sdstrim(lines[i]," \t\r\n");
|
||||
|
||||
/* Skip comments and blank lines*/
|
||||
/* Skip comments and blank lines */
|
||||
if (lines[i][0] == '#' || lines[i][0] == '\0') continue;
|
||||
|
||||
/* Split into arguments */
|
||||
argv = sdssplitargs(lines[i],&argc);
|
||||
if (argv == NULL) {
|
||||
err = "Unbalanced quotes in configuration line";
|
||||
goto loaderr;
|
||||
}
|
||||
|
||||
/* Skip this line if the resulting command vector is empty. */
|
||||
if (argc == 0) {
|
||||
sdsfreesplitres(argv,argc);
|
||||
return;
|
||||
}
|
||||
sdstolower(argv[0]);
|
||||
|
||||
/* Execute config directives */
|
||||
@@ -265,6 +275,10 @@ void loadServerConfigFromString(char *config) {
|
||||
if ((server.daemonize = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"hz") && argc == 2) {
|
||||
server.hz = atoi(argv[1]);
|
||||
if (server.hz < REDIS_MIN_HZ) server.hz = REDIS_MIN_HZ;
|
||||
if (server.hz > REDIS_MAX_HZ) server.hz = REDIS_MAX_HZ;
|
||||
} else if (!strcasecmp(argv[0],"appendonly") && argc == 2) {
|
||||
int yes;
|
||||
|
||||
@@ -484,6 +498,12 @@ void configSetCommand(redisClient *c) {
|
||||
}
|
||||
freeMemoryIfNeeded();
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"hz")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll < 0) goto badfmt;
|
||||
server.hz = (int) ll;
|
||||
if (server.hz < REDIS_MIN_HZ) server.hz = REDIS_MIN_HZ;
|
||||
if (server.hz > REDIS_MAX_HZ) server.hz = REDIS_MAX_HZ;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"maxmemory-policy")) {
|
||||
if (!strcasecmp(o->ptr,"volatile-lru")) {
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU;
|
||||
@@ -810,6 +830,7 @@ void configGetCommand(redisClient *c) {
|
||||
config_get_numerical_field("maxclients",server.maxclients);
|
||||
config_get_numerical_field("watchdog-period",server.watchdog_period);
|
||||
config_get_numerical_field("slave-priority",server.slave_priority);
|
||||
config_get_numerical_field("hz",server.hz);
|
||||
|
||||
/* Bool (yes/no) values */
|
||||
config_get_bool_field("no-appendfsync-on-rewrite",
|
||||
|
||||
+1
-1
@@ -794,7 +794,7 @@ void enableWatchdog(int period) {
|
||||
/* If the configured period is smaller than twice the timer period, it is
|
||||
* too short for the software watchdog to work reliably. Fix it now
|
||||
* if needed. */
|
||||
min_period = (1000/REDIS_HZ)*2;
|
||||
min_period = (1000/server.hz)*2;
|
||||
if (period < min_period) period = min_period;
|
||||
watchdogScheduleSignal(period); /* Adjust the current timer. */
|
||||
server.watchdog_period = period;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
GIT_SHA1=`(git show-ref --head --hash=8 2> /dev/null || echo 00000000) | head -n1`
|
||||
GIT_DIRTY=`git diff 2> /dev/null | wc -l`
|
||||
GIT_DIRTY=`git diff --no-ext-diff 2> /dev/null | wc -l`
|
||||
test -f release.h || touch release.h
|
||||
(cat release.h | grep SHA1 | grep $GIT_SHA1) && \
|
||||
(cat release.h | grep DIRTY | grep $GIT_DIRTY) && exit 0 # Already up-to-date
|
||||
|
||||
+2
-2
@@ -1259,7 +1259,7 @@ void rewriteClientCommandVector(redisClient *c, int argc, ...) {
|
||||
/* Replace argv and argc with our new versions. */
|
||||
c->argv = argv;
|
||||
c->argc = argc;
|
||||
c->cmd = lookupCommand(c->argv[0]->ptr);
|
||||
c->cmd = lookupCommandOrOriginal(c->argv[0]->ptr);
|
||||
redisAssertWithInfo(c,NULL,c->cmd != NULL);
|
||||
va_end(ap);
|
||||
}
|
||||
@@ -1277,7 +1277,7 @@ void rewriteClientCommandArgument(redisClient *c, int i, robj *newval) {
|
||||
|
||||
/* If this is the command name make sure to fix c->cmd. */
|
||||
if (i == 0) {
|
||||
c->cmd = lookupCommand(c->argv[0]->ptr);
|
||||
c->cmd = lookupCommandOrOriginal(c->argv[0]->ptr);
|
||||
redisAssertWithInfo(c,NULL,c->cmd != NULL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,7 +278,6 @@ void subscribeCommand(redisClient *c) {
|
||||
void unsubscribeCommand(redisClient *c) {
|
||||
if (c->argc == 1) {
|
||||
pubsubUnsubscribeAllChannels(c,1);
|
||||
return;
|
||||
} else {
|
||||
int j;
|
||||
|
||||
@@ -297,7 +296,6 @@ void psubscribeCommand(redisClient *c) {
|
||||
void punsubscribeCommand(redisClient *c) {
|
||||
if (c->argc == 1) {
|
||||
pubsubUnsubscribeAllPatterns(c,1);
|
||||
return;
|
||||
} else {
|
||||
int j;
|
||||
|
||||
|
||||
+8
-2
@@ -56,6 +56,7 @@
|
||||
#define OUTPUT_STANDARD 0
|
||||
#define OUTPUT_RAW 1
|
||||
#define OUTPUT_CSV 2
|
||||
#define REDIS_CLI_KEEPALIVE_INTERVAL 15 /* seconds */
|
||||
|
||||
static redisContext *context;
|
||||
static struct config {
|
||||
@@ -332,6 +333,12 @@ static int cliConnect(int force) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Set aggressive KEEP_ALIVE socket option in the Redis context socket
|
||||
* in order to prevent timeouts caused by the execution of long
|
||||
* commands. At the same time this improves the detection of real
|
||||
* errors. */
|
||||
anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);
|
||||
|
||||
/* Do AUTH and select the right DB. */
|
||||
if (cliAuth() != REDIS_OK)
|
||||
return REDIS_ERR;
|
||||
@@ -843,8 +850,7 @@ static void repl() {
|
||||
}
|
||||
}
|
||||
/* Free the argument vector */
|
||||
while(argc--) sdsfree(argv[argc]);
|
||||
zfree(argv);
|
||||
sdsfreesplitres(argv,argc);
|
||||
}
|
||||
/* linenoise() returns malloc-ed lines like readline() */
|
||||
free(line);
|
||||
|
||||
+146
-62
@@ -207,7 +207,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"pexpireat",pexpireatCommand,3,"w",0,NULL,1,1,1,0,0},
|
||||
{"keys",keysCommand,2,"rS",0,NULL,0,0,0,0,0},
|
||||
{"dbsize",dbsizeCommand,1,"r",0,NULL,0,0,0,0,0},
|
||||
{"auth",authCommand,2,"rs",0,NULL,0,0,0,0,0},
|
||||
{"auth",authCommand,2,"rsl",0,NULL,0,0,0,0,0},
|
||||
{"ping",pingCommand,1,"r",0,NULL,0,0,0,0,0},
|
||||
{"echo",echoCommand,2,"r",0,NULL,0,0,0,0,0},
|
||||
{"save",saveCommand,1,"ars",0,NULL,0,0,0,0,0},
|
||||
@@ -570,36 +570,32 @@ int htNeedsResize(dict *dict) {
|
||||
|
||||
/* If the percentage of used slots in the HT reaches REDIS_HT_MINFILL
|
||||
* we resize the hash table to save memory */
|
||||
void tryResizeHashTables(void) {
|
||||
int j;
|
||||
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
if (htNeedsResize(server.db[j].dict))
|
||||
dictResize(server.db[j].dict);
|
||||
if (htNeedsResize(server.db[j].expires))
|
||||
dictResize(server.db[j].expires);
|
||||
}
|
||||
void tryResizeHashTables(int dbid) {
|
||||
if (htNeedsResize(server.db[dbid].dict))
|
||||
dictResize(server.db[dbid].dict);
|
||||
if (htNeedsResize(server.db[dbid].expires))
|
||||
dictResize(server.db[dbid].expires);
|
||||
}
|
||||
|
||||
/* Our hash table implementation performs rehashing incrementally while
|
||||
* we write/read from the hash table. Still if the server is idle, the hash
|
||||
* table will use two tables for a long time. So we try to use 1 millisecond
|
||||
* of CPU time at every serverCron() loop in order to rehash some key. */
|
||||
void incrementallyRehash(void) {
|
||||
int j;
|
||||
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
/* Keys dictionary */
|
||||
if (dictIsRehashing(server.db[j].dict)) {
|
||||
dictRehashMilliseconds(server.db[j].dict,1);
|
||||
break; /* already used our millisecond for this loop... */
|
||||
}
|
||||
/* Expires */
|
||||
if (dictIsRehashing(server.db[j].expires)) {
|
||||
dictRehashMilliseconds(server.db[j].expires,1);
|
||||
break; /* already used our millisecond for this loop... */
|
||||
}
|
||||
* of CPU time at every call of this function to perform some rehahsing.
|
||||
*
|
||||
* The function returns 1 if some rehashing was performed, otherwise 0
|
||||
* is returned. */
|
||||
int incrementallyRehash(int dbid) {
|
||||
/* Keys dictionary */
|
||||
if (dictIsRehashing(server.db[dbid].dict)) {
|
||||
dictRehashMilliseconds(server.db[dbid].dict,1);
|
||||
return 1; /* already used our millisecond for this loop... */
|
||||
}
|
||||
/* Expires */
|
||||
if (dictIsRehashing(server.db[dbid].expires)) {
|
||||
dictRehashMilliseconds(server.db[dbid].expires,1);
|
||||
return 1; /* already used our millisecond for this loop... */
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* This function is called once a background process of some kind terminates,
|
||||
@@ -620,28 +616,57 @@ void updateDictResizePolicy(void) {
|
||||
/* Try to expire a few timed out keys. The algorithm used is adaptive and
|
||||
* will use few CPU cycles if there are few expiring keys, otherwise
|
||||
* it will get more aggressive to avoid that too much memory is used by
|
||||
* keys that can be removed from the keyspace. */
|
||||
* keys that can be removed from the keyspace.
|
||||
*
|
||||
* No more than REDIS_DBCRON_DBS_PER_CALL databases are tested at every
|
||||
* iteration. */
|
||||
void activeExpireCycle(void) {
|
||||
int j, iteration = 0;
|
||||
/* This function has some global state in order to continue the work
|
||||
* incrementally across calls. */
|
||||
static unsigned int current_db = 0; /* Last DB tested. */
|
||||
static int timelimit_exit = 0; /* Time limit hit in previous call? */
|
||||
|
||||
unsigned int j, iteration = 0;
|
||||
unsigned int dbs_per_call = REDIS_DBCRON_DBS_PER_CALL;
|
||||
long long start = ustime(), timelimit;
|
||||
|
||||
/* We usually should test REDIS_DBCRON_DBS_PER_CALL per iteration, with
|
||||
* two exceptions:
|
||||
*
|
||||
* 1) Don't test more DBs than we have.
|
||||
* 2) If last time we hit the time limit, we want to scan all DBs
|
||||
* in this iteration, as there is work to do in some DB and we don't want
|
||||
* expired keys to use memory for too much time. */
|
||||
if (dbs_per_call > server.dbnum || timelimit_exit)
|
||||
dbs_per_call = server.dbnum;
|
||||
|
||||
/* We can use at max REDIS_EXPIRELOOKUPS_TIME_PERC percentage of CPU time
|
||||
* per iteration. Since this function gets called with a frequency of
|
||||
* REDIS_HZ times per second, the following is the max amount of
|
||||
* server.hz times per second, the following is the max amount of
|
||||
* microseconds we can spend in this function. */
|
||||
timelimit = 1000000*REDIS_EXPIRELOOKUPS_TIME_PERC/REDIS_HZ/100;
|
||||
timelimit = 1000000*REDIS_EXPIRELOOKUPS_TIME_PERC/server.hz/100;
|
||||
timelimit_exit = 0;
|
||||
if (timelimit <= 0) timelimit = 1;
|
||||
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
for (j = 0; j < dbs_per_call; j++) {
|
||||
int expired;
|
||||
redisDb *db = server.db+j;
|
||||
redisDb *db = server.db+(current_db % server.dbnum);
|
||||
|
||||
/* Increment the DB now so we are sure if we run out of time
|
||||
* in the current DB we'll restart from the next. This allows to
|
||||
* distribute the time evenly across DBs. */
|
||||
current_db++;
|
||||
|
||||
/* Continue to expire if at the end of the cycle more than 25%
|
||||
* of the keys were expired. */
|
||||
do {
|
||||
unsigned long num = dictSize(db->expires);
|
||||
unsigned long slots = dictSlots(db->expires);
|
||||
long long now = mstime();
|
||||
unsigned long num, slots;
|
||||
long long now;
|
||||
|
||||
/* If there is nothing to expire try next DB ASAP. */
|
||||
if ((num = dictSize(db->expires)) == 0) break;
|
||||
slots = dictSlots(db->expires);
|
||||
now = mstime();
|
||||
|
||||
/* When there are less than 1% filled slots getting random
|
||||
* keys is expensive, so stop here waiting for better times...
|
||||
@@ -675,8 +700,12 @@ void activeExpireCycle(void) {
|
||||
* expire. So after a given amount of milliseconds return to the
|
||||
* caller waiting for the other active expire cycle. */
|
||||
iteration++;
|
||||
if ((iteration & 0xf) == 0 && /* check once every 16 cycles. */
|
||||
(ustime()-start) > timelimit) return;
|
||||
if ((iteration & 0xf) == 0 && /* check once every 16 iterations. */
|
||||
(ustime()-start) > timelimit)
|
||||
{
|
||||
timelimit_exit = 1;
|
||||
return;
|
||||
}
|
||||
} while (expired > REDIS_EXPIRELOOKUPS_PER_CRON/4);
|
||||
}
|
||||
}
|
||||
@@ -762,13 +791,13 @@ int clientsCronResizeQueryBuffer(redisClient *c) {
|
||||
}
|
||||
|
||||
void clientsCron(void) {
|
||||
/* Make sure to process at least 1/(REDIS_HZ*10) of clients per call.
|
||||
* Since this function is called REDIS_HZ times per second we are sure that
|
||||
/* Make sure to process at least 1/(server.hz*10) of clients per call.
|
||||
* Since this function is called server.hz times per second we are sure that
|
||||
* in the worst case we process all the clients in 10 seconds.
|
||||
* In normal conditions (a reasonable number of clients) we process
|
||||
* all the clients in a shorter time. */
|
||||
int numclients = listLength(server.clients);
|
||||
int iterations = numclients/(REDIS_HZ*10);
|
||||
int iterations = numclients/(server.hz*10);
|
||||
|
||||
if (iterations < 50)
|
||||
iterations = (numclients < 50) ? numclients : 50;
|
||||
@@ -790,7 +819,51 @@ void clientsCron(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* This is our timer interrupt, called REDIS_HZ times per second.
|
||||
/* This function handles 'background' operations we are required to do
|
||||
* incrementally in Redis databases, such as active key expiring, resizing,
|
||||
* rehashing. */
|
||||
void databasesCron(void) {
|
||||
/* Expire keys by random sampling. Not required for slaves
|
||||
* as master will synthesize DELs for us. */
|
||||
if (server.masterhost == NULL) activeExpireCycle();
|
||||
|
||||
/* Perform hash tables rehashing if needed, but only if there are no
|
||||
* other processes saving the DB on disk. Otherwise rehashing is bad
|
||||
* as will cause a lot of copy-on-write of memory pages. */
|
||||
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) {
|
||||
/* We use global counters so if we stop the computation at a given
|
||||
* DB we'll be able to start from the successive in the next
|
||||
* cron loop iteration. */
|
||||
static unsigned int resize_db = 0;
|
||||
static unsigned int rehash_db = 0;
|
||||
unsigned int dbs_per_call = REDIS_DBCRON_DBS_PER_CALL;
|
||||
unsigned int j;
|
||||
|
||||
/* Don't test more DBs than we have. */
|
||||
if (dbs_per_call > server.dbnum) dbs_per_call = server.dbnum;
|
||||
|
||||
/* Resize */
|
||||
for (j = 0; j < dbs_per_call; j++) {
|
||||
tryResizeHashTables(resize_db % server.dbnum);
|
||||
resize_db++;
|
||||
}
|
||||
|
||||
/* Rehash */
|
||||
if (server.activerehashing) {
|
||||
for (j = 0; j < dbs_per_call; j++) {
|
||||
int work_done = incrementallyRehash(rehash_db % server.dbnum);
|
||||
rehash_db++;
|
||||
if (work_done) {
|
||||
/* If the function did some work, stop here, we'll do
|
||||
* more at the next cron loop. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* This is our timer interrupt, called server.hz times per second.
|
||||
* Here is where we do a number of things that need to be done asynchronously.
|
||||
* For instance:
|
||||
*
|
||||
@@ -804,7 +877,7 @@ void clientsCron(void) {
|
||||
* - Replication reconnection.
|
||||
* - Many more...
|
||||
*
|
||||
* Everything directly called here will be called REDIS_HZ times per second,
|
||||
* Everything directly called here will be called server.hz times per second,
|
||||
* so in order to throttle execution of things we want to do less frequently
|
||||
* a macro is used: run_with_period(milliseconds) { .... }
|
||||
*/
|
||||
@@ -868,17 +941,6 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
}
|
||||
}
|
||||
|
||||
/* We don't want to resize the hash tables while a background saving
|
||||
* is in progress: the saving child is created using fork() that is
|
||||
* implemented with a copy-on-write semantic in most modern systems, so
|
||||
* if we resize the HT while there is the saving child at work actually
|
||||
* a lot of memory movements in the parent will cause a lot of pages
|
||||
* copied. */
|
||||
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) {
|
||||
tryResizeHashTables();
|
||||
if (server.activerehashing) incrementallyRehash();
|
||||
}
|
||||
|
||||
/* Show information about connected clients */
|
||||
if (!server.sentinel_mode) {
|
||||
run_with_period(5000) {
|
||||
@@ -893,6 +955,9 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
/* We need to do a few operations on clients asynchronously. */
|
||||
clientsCron();
|
||||
|
||||
/* Handle background operations on Redis databases. */
|
||||
databasesCron();
|
||||
|
||||
/* Start a scheduled AOF rewrite if this was requested by the user while
|
||||
* a BGSAVE was in progress. */
|
||||
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1 &&
|
||||
@@ -959,11 +1024,6 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
* cron function is called. */
|
||||
if (server.aof_flush_postponed_start) flushAppendOnlyFile(0);
|
||||
|
||||
/* Expire a few keys per cycle, only if this is a master.
|
||||
* On slaves we wait for DEL operations synthesized by the master
|
||||
* in order to guarantee a strict consistency. */
|
||||
if (server.masterhost == NULL) activeExpireCycle();
|
||||
|
||||
/* Close clients that need to be closed asynchronous */
|
||||
freeClientsInAsyncFreeQueue();
|
||||
|
||||
@@ -977,7 +1037,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
}
|
||||
|
||||
server.cronloops++;
|
||||
return 1000/REDIS_HZ;
|
||||
return 1000/server.hz;
|
||||
}
|
||||
|
||||
/* This function gets called every time Redis is entering the
|
||||
@@ -1083,6 +1143,7 @@ void createSharedObjects(void) {
|
||||
|
||||
void initServerConfig() {
|
||||
getRandomHexChars(server.runid,REDIS_RUN_ID_SIZE);
|
||||
server.hz = REDIS_DEFAULT_HZ;
|
||||
server.runid[REDIS_RUN_ID_SIZE] = '\0';
|
||||
server.arch_bits = (sizeof(long) == 8) ? 64 : 32;
|
||||
server.port = REDIS_SERVERPORT;
|
||||
@@ -1185,6 +1246,7 @@ void initServerConfig() {
|
||||
* initial configuration, since command names may be changed via
|
||||
* redis.conf using the rename-command directive. */
|
||||
server.commands = dictCreate(&commandTableDictType,NULL);
|
||||
server.orig_commands = dictCreate(&commandTableDictType,NULL);
|
||||
populateCommandTable();
|
||||
server.delCommand = lookupCommandByCString("del");
|
||||
server.multiCommand = lookupCommandByCString("multi");
|
||||
@@ -1331,7 +1393,10 @@ void initServer() {
|
||||
server.unixtime = time(NULL);
|
||||
server.lastbgsave_status = REDIS_OK;
|
||||
server.stop_writes_on_bgsave_err = 1;
|
||||
aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL);
|
||||
if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
|
||||
redisPanic("create time event failed");
|
||||
exit(1);
|
||||
}
|
||||
if (server.ipfd > 0 && aeCreateFileEvent(server.el,server.ipfd,AE_READABLE,
|
||||
acceptTcpHandler,NULL) == AE_ERR) redisPanic("Unrecoverable error creating server.ipfd file event.");
|
||||
if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE,
|
||||
@@ -1371,7 +1436,7 @@ void populateCommandTable(void) {
|
||||
for (j = 0; j < numcommands; j++) {
|
||||
struct redisCommand *c = redisCommandTable+j;
|
||||
char *f = c->sflags;
|
||||
int retval;
|
||||
int retval1, retval2;
|
||||
|
||||
while(*f != '\0') {
|
||||
switch(*f) {
|
||||
@@ -1392,8 +1457,11 @@ void populateCommandTable(void) {
|
||||
f++;
|
||||
}
|
||||
|
||||
retval = dictAdd(server.commands, sdsnew(c->name), c);
|
||||
assert(retval == DICT_OK);
|
||||
retval1 = dictAdd(server.commands, sdsnew(c->name), c);
|
||||
/* Populate an additional dictionary that will be unaffected
|
||||
* by rename-command statements in redis.conf. */
|
||||
retval2 = dictAdd(server.orig_commands, sdsnew(c->name), c);
|
||||
redisAssert(retval1 == DICT_OK && retval2 == DICT_OK);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1461,6 +1529,20 @@ struct redisCommand *lookupCommandByCString(char *s) {
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/* Lookup the command in the current table, if not found also check in
|
||||
* the original table containing the original command names unaffected by
|
||||
* redis.conf rename-command statement.
|
||||
*
|
||||
* This is used by functions rewriting the argument vector such as
|
||||
* rewriteClientCommandVector() in order to set client->cmd pointer
|
||||
* correctly even if the command was renamed. */
|
||||
struct redisCommand *lookupCommandOrOriginal(sds name) {
|
||||
struct redisCommand *cmd = dictFetchValue(server.commands, name);
|
||||
|
||||
if (!cmd) cmd = dictFetchValue(server.orig_commands,name);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/* Propagate the specified command (in the context of the specified database id)
|
||||
* to AOF and Slaves.
|
||||
*
|
||||
@@ -1882,6 +1964,7 @@ sds genRedisInfoString(char *section) {
|
||||
"tcp_port:%d\r\n"
|
||||
"uptime_in_seconds:%ld\r\n"
|
||||
"uptime_in_days:%ld\r\n"
|
||||
"hz:%d\r\n"
|
||||
"lru_clock:%ld\r\n",
|
||||
REDIS_VERSION,
|
||||
redisGitSHA1(),
|
||||
@@ -1900,6 +1983,7 @@ sds genRedisInfoString(char *section) {
|
||||
server.port,
|
||||
uptime,
|
||||
uptime/(3600*24),
|
||||
server.hz,
|
||||
(unsigned long) server.lruclock);
|
||||
}
|
||||
|
||||
|
||||
+10
-4
@@ -67,13 +67,16 @@
|
||||
#define REDIS_ERR -1
|
||||
|
||||
/* Static server configuration */
|
||||
#define REDIS_HZ 100 /* Time interrupt calls/sec. */
|
||||
#define REDIS_DEFAULT_HZ 10 /* Time interrupt calls/sec. */
|
||||
#define REDIS_MIN_HZ 1
|
||||
#define REDIS_MAX_HZ 500
|
||||
#define REDIS_SERVERPORT 6379 /* TCP port */
|
||||
#define REDIS_MAXIDLETIME 0 /* default client timeout: infinite */
|
||||
#define REDIS_DEFAULT_DBNUM 16
|
||||
#define REDIS_CONFIGLINE_MAX 1024
|
||||
#define REDIS_EXPIRELOOKUPS_PER_CRON 10 /* lookup 10 expires per loop */
|
||||
#define REDIS_EXPIRELOOKUPS_TIME_PERC 25 /* CPU max % for keys collection */
|
||||
#define REDIS_DBCRON_DBS_PER_CALL 16
|
||||
#define REDIS_MAX_WRITE_PER_EVENT (1024*64)
|
||||
#define REDIS_SHARED_SELECT_CMDS 10
|
||||
#define REDIS_SHARED_INTEGERS 10000
|
||||
@@ -291,8 +294,8 @@
|
||||
|
||||
/* Using the following macro you can run code inside serverCron() with the
|
||||
* specified period, specified in milliseconds.
|
||||
* The actual resolution depends on REDIS_HZ. */
|
||||
#define run_with_period(_ms_) if (!(server.cronloops%((_ms_)/(1000/REDIS_HZ))))
|
||||
* The actual resolution depends on server.hz. */
|
||||
#define run_with_period(_ms_) if ((_ms_ <= 1000/server.hz) || !(server.cronloops%((_ms_)/(1000/server.hz))))
|
||||
|
||||
/* We can print the stacktrace, so our assert is defined this way: */
|
||||
#define redisAssertWithInfo(_c,_o,_e) ((_e)?(void)0 : (_redisAssertWithInfo(_c,_o,#_e,__FILE__,__LINE__),_exit(1)))
|
||||
@@ -494,8 +497,10 @@ typedef struct redisOpArray {
|
||||
|
||||
struct redisServer {
|
||||
/* General */
|
||||
int hz; /* serverCron() calls frequency in hertz */
|
||||
redisDb *db;
|
||||
dict *commands; /* Command table hash table */
|
||||
dict *commands; /* Command table */
|
||||
dict *orig_commands; /* Command table before command renaming. */
|
||||
aeEventLoop *el;
|
||||
unsigned lruclock:22; /* Clock incrementing every minute, for LRU */
|
||||
unsigned lruclock_padding:10;
|
||||
@@ -945,6 +950,7 @@ int processCommand(redisClient *c);
|
||||
void setupSignalHandlers(void);
|
||||
struct redisCommand *lookupCommand(sds name);
|
||||
struct redisCommand *lookupCommandByCString(char *s);
|
||||
struct redisCommand *lookupCommandOrOriginal(sds name);
|
||||
void call(redisClient *c, int flags);
|
||||
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int flags);
|
||||
void alsoPropagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int target);
|
||||
|
||||
+11
-6
@@ -579,11 +579,16 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* We don't care about the reply, it can be +PONG or an error since
|
||||
* the server requires AUTH. As long as it replies correctly, it's
|
||||
* fine from our point of view. */
|
||||
if (buf[0] != '-' && buf[0] != '+') {
|
||||
redisLog(REDIS_WARNING,"Unexpected reply to PING from master.");
|
||||
/* We accept only two replies as valid, a positive +PONG reply
|
||||
* (we just check for "+") or an authentication error.
|
||||
* Note that older versions of Redis replied with "operation not
|
||||
* permitted" instead of using a proper error code, so we test
|
||||
* both. */
|
||||
if (buf[0] != '+' &&
|
||||
strncmp(buf,"-NOAUTH",7) != 0 &&
|
||||
strncmp(buf,"-ERR operation not permitted",28) != 0)
|
||||
{
|
||||
redisLog(REDIS_WARNING,"Error reply to PING from master: '%s'",buf);
|
||||
goto error;
|
||||
} else {
|
||||
redisLog(REDIS_NOTICE,
|
||||
@@ -800,7 +805,7 @@ void replicationCron(void) {
|
||||
* So slaves can implement an explicit timeout to masters, and will
|
||||
* be able to detect a link disconnection even if the TCP connection
|
||||
* will not actually go down. */
|
||||
if (!(server.cronloops % (server.repl_ping_slave_period * REDIS_HZ))) {
|
||||
if (!(server.cronloops % (server.repl_ping_slave_period * server.hz))) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
|
||||
@@ -475,11 +475,18 @@ int hex_digit_to_int(char c) {
|
||||
* foo bar "newline are supported\n" and "\xff\x00otherstuff"
|
||||
*
|
||||
* The number of arguments is stored into *argc, and an array
|
||||
* of sds is returned. The caller should sdsfree() all the returned
|
||||
* strings and finally zfree() the array itself.
|
||||
* of sds is returned.
|
||||
*
|
||||
* The caller should free the resulting array of sds strings with
|
||||
* sdsfreesplitres().
|
||||
*
|
||||
* Note that sdscatrepr() is able to convert back a string into
|
||||
* a quoted string in the same format sdssplitargs() is able to parse.
|
||||
*
|
||||
* The function returns the allocated tokens on success, even when the
|
||||
* input string is empty, or NULL if the input contains unbalanced
|
||||
* quotes or closed quotes followed by non space characters
|
||||
* as in: "foo"bar or "foo'
|
||||
*/
|
||||
sds *sdssplitargs(const char *line, int *argc) {
|
||||
const char *p = line;
|
||||
@@ -576,6 +583,8 @@ sds *sdssplitargs(const char *line, int *argc) {
|
||||
(*argc)++;
|
||||
current = NULL;
|
||||
} else {
|
||||
/* Even on empty input string return something not NULL. */
|
||||
if (vector == NULL) vector = zmalloc(sizeof(void*));
|
||||
return vector;
|
||||
}
|
||||
}
|
||||
@@ -585,16 +594,10 @@ err:
|
||||
sdsfree(vector[*argc]);
|
||||
zfree(vector);
|
||||
if (current) sdsfree(current);
|
||||
*argc = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void sdssplitargs_free(sds *argv, int argc) {
|
||||
int j;
|
||||
|
||||
for (j = 0 ;j < argc; j++) sdsfree(argv[j]);
|
||||
zfree(argv);
|
||||
}
|
||||
|
||||
/* Modify the string substituting all the occurrences of the set of
|
||||
* characters specified in the 'from' string to the corresponding character
|
||||
* in the 'to' array.
|
||||
|
||||
@@ -88,7 +88,6 @@ void sdstoupper(sds s);
|
||||
sds sdsfromlonglong(long long value);
|
||||
sds sdscatrepr(sds s, const char *p, size_t len);
|
||||
sds *sdssplitargs(const char *line, int *argc);
|
||||
void sdssplitargs_free(sds *argv, int argc);
|
||||
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);
|
||||
|
||||
/* Low level functions exposed to the user API */
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
#define REDIS_VERSION "2.6.10"
|
||||
#define REDIS_VERSION "2.6.11"
|
||||
|
||||
@@ -50,6 +50,10 @@ start_server {tags {"introspection"}} {
|
||||
assert_match {*foobar*} [r client list]
|
||||
$rd close
|
||||
# Now the client should no longer be listed
|
||||
string match {*foobar*} [r client list]
|
||||
} {0}
|
||||
wait_for_condition 50 100 {
|
||||
[string match {*foobar*} [r client list]] == 0
|
||||
} else {
|
||||
fail "Client still listed in CLIENT LIST after SETNAME."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user