Compare commits

..
30 Commits
Author SHA1 Message Date
antirez 6589821035 Redis 2.6.11 2013-03-11 19:51:10 +01:00
antirez 48f4f77189 activeExpireCycle() smarter with many DBs and under expire pressure.
activeExpireCycle() tries to test just a few DBs per iteration so that
it scales if there are many configured DBs in the Redis instance.
However this commit makes it a bit smarter when one a few of those DBs
are under expiration pressure and there are many many keys to expire.

What we do is to remember if in the last iteration had to return because
we ran out of time. In that case the next iteration we'll test all the
configured DBs so that we are sure we'll test again the DB under
pressure.

Before of this commit after some mass-expire in a given DB the function
tested just a few of the next DBs, possibly empty, a few per iteration,
so it took a long time for the function to reach again the DB under
pressure. This resulted in a lot of memory being used by already expired
keys and never accessed by clients.
2013-03-11 11:34:49 +01:00
antirez b3281e93c3 In databasesCron() never test more DBs than we have. 2013-03-11 11:34:45 +01:00
antirez 2677c97d39 Make comment name match var name in activeExpireCycle(). 2013-03-11 11:34:34 +01:00
antirez 1d426bf53b Optimize inner loop of activeExpireCycle() for no-expires case. 2013-03-11 11:34:18 +01:00
antirez da2dd8991b REDIS_DBCRON_DBS_PER_SEC -> REDIS_DBCRON_DBS_PER_CALL 2013-03-11 11:34:14 +01:00
antirez 13f84841b5 activeExpireCycle(): process only a small number of DBs per iteration.
This small number of DBs is set to 16 so actually in the default
configuraiton Redis should behave exactly like in the past.
However the difference is that when the user configures a very large
number of DBs we don't do an O(N) operation, consuming a non trivial
amount of CPU per serverCron() iteration.
2013-03-11 11:34:10 +01:00
antirez ef3a95fa6f Use unsigned integers for DB ids, for defined wrap-to-zero. 2013-03-11 11:34:05 +01:00
antirez 665b819eb4 Only resize/rehash a few databases per cron iteration.
This is the first step to lower the CPU usage when many databases are
configured. The other is to also process a limited number of DBs per
call in the active expire cycle.
2013-03-11 11:33:59 +01:00
antirez bb562a6414 Actually call databasesCron() inside serverCron(). 2013-03-11 11:30:26 +01:00
antirez e166abad10 Move Redis databases background processing to databasesCron(). 2013-03-11 11:30:21 +01:00
antirez aec5ea5d07 serverCron() frequency is now a runtime parameter (was REDIS_HZ).
REDIS_HZ is the frequency our serverCron() function is called with.
A more frequent call to this function results into less latency when the
server is trying to handle very expansive background operations like
mass expires of a lot of keys at the same time.

Redis 2.4 used to have an HZ of 10. This was good enough with almost
every setup, but the incremental key expiration algorithm was working a
bit better under *extreme* pressure when HZ was set to 100 for Redis
2.6.

However for most users a latency spike of 30 milliseconds when million
of keys are expiring at the same time is acceptable, on the other hand a
default HZ of 100 in Redis 2.6 was causing idle instances to use some
CPU time compared to Redis 2.4. The CPU usage was in the order of 0.3%
for an idle instance, however this is a shame as more energy is consumed
by the server, if not important resources.

This commit introduces HZ as a runtime parameter, that can be queried by
INFO or CONFIG GET, and can be modified with CONFIG SET. At the same
time the default frequency is set back to 10.

In this way we default to a sane value of 10, but allows users to
easily switch to values up to 500 for near real-time applications if
needed and if they are willing to pay this small CPU usage penalty.
2013-03-11 11:28:55 +01:00
Gengliang Wang 2d24bf2f94 Removed useless "return" statements in pubsub.c
(original commit message edited)
2013-03-06 16:50:56 +01:00
antirez e7a61d287e API to lookup commands with their original name.
A new server.orig_commands table was added to the server structure, this
contains a copy of the commant table unaffected by rename-command
statements in redis.conf.

A new API lookupCommandOrOriginal() was added that checks both tables,
new first, old later, so that rewriteClientCommandVector() and friends
can lookup commands with their new or original name in order to fix the
client->cmd pointer when the argument vector is renamed.

This fixes the segfault of issue #986, but does not fix a wider range of
problems resulting from renaming commands that actually operate on data
and are registered into the AOF file or propagated to slaves... That is
command renaming should be handled with care.
2013-03-06 16:36:52 +01:00
antirez 78bae8b078 Add a warning about command renaming in redis.conf. 2013-03-06 16:36:52 +01:00
antirez bf6c5d960d Handle a non-impossible empty argv in loadServerConfigFromString().
Usually this does not happens since we trim for " \t\r\n", but if there
are other chars that return true with isspace(), we may end with an
empty argv. Better to handle the condition in an explicit way.
2013-03-06 12:44:28 +01:00
antirez 95fc9cc0a6 redis-cli: use sdsfreesplitres() instead of hand-coding it. 2013-03-06 12:44:23 +01:00
antirez 5de5efa364 sds.c: sdssplitargs_free() removed as it was a duplicate. 2013-03-06 12:44:19 +01:00
antirez 1f2d5941a1 More specific error message in loadServerConfigFromString(). 2013-03-06 12:44:16 +01:00
antirez 60aa3a67e8 sdssplitargs(): on error set *argc to 0.
This makes programs not checking the return value for NULL much safer
since with this change:

1) It is still possible to iterate the zero-length result without
crashes.
2) sdssplitargs_free will work against NULL and 0 count.
2013-03-06 12:44:10 +01:00
antirez 921aec17b6 sdssplitargs(): now returns NULL only on error.
An empty input string also resulted into the function returning NULL
making it harder for the caller to distinguish between error and empty
string without checking the original input string length.
2013-03-06 12:44:06 +01:00
charsyam e174290307 Don't segfault on unbalanced quotes. 2013-03-06 12:44:02 +01:00
antirez a3273dbfa6 Allow AUTH while loading the DB in memory.
While Redis is loading the AOF or RDB file in memory only a subset of
commands are allowed. This commit adds AUTH to this subset.
2013-03-06 11:51:26 +01:00
antirez e9318d92db redis-cli: use keepalive socket option.
This should improve things in two ways:

1) Prevent timeouts caused by the execution of long commands.
2) Improve detection of real connection errors.

This is mostly effective only on Linux because of the bogus default
keepalive settings. In Linux we have OS-specific calls to set the
keepalive interval to reasonable values.
2013-03-04 11:16:24 +01:00
0x20h 803c5b140d suppress external diff program when using git diff. 2013-03-04 10:59:22 +01:00
Stam He 74373cd930 add a check for aeCreateTimeEvent
1) Add a check for aeCreateTimeEvent in function initServer.
2013-03-04 10:52:53 +01:00
Stam He b5047d9b2f point 2 of slave-serve-stale-data miss '-' between 'stale' and 'data' 2013-03-04 10:49:30 +01:00
antirez 34b420dbf3 Replication: more strict error checking for master PING reply. 2013-02-12 16:59:38 +01:00
antirez 02ac84ddbd Test: avoid false positives in CLIENT SETNAME closed connection test. 2013-02-12 13:27:32 +01:00
Steven Penny 45bfab8596 Format to fit 80 columns
This makes it readable on GitHub and editors without auto wrapping.
2013-02-12 13:11:47 +01:00
16 changed files with 308 additions and 103 deletions
+12
View File
@@ -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
+55 -8
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
}
-2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+12 -9
View File
@@ -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.
-1
View File
@@ -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
View File
@@ -1 +1 @@
#define REDIS_VERSION "2.6.10"
#define REDIS_VERSION "2.6.11"
+6 -2
View File
@@ -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."
}
}
}