Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48e24d54b7 | ||
|
|
4a64bc3e40 | ||
|
|
61c5f4eccb | ||
|
|
124d94cc6b | ||
|
|
59e1357bff | ||
|
|
6d567d0c6f | ||
|
|
d6864d2999 | ||
|
|
d8679070b8 | ||
|
|
ca300f2d14 | ||
|
|
33c8a59e97 | ||
|
|
055958aa3b | ||
|
|
1d8078b7f1 | ||
|
|
c3d9da0062 | ||
|
|
7146221f38 | ||
|
|
bb5b8d3180 | ||
|
|
90cd445535 | ||
|
|
e0d0fb450c | ||
|
|
401383f1da | ||
|
|
1dabbd0a41 | ||
|
|
d695f3e899 | ||
|
|
593cf5989e | ||
|
|
92fd7dac35 | ||
|
|
3c46377ec5 | ||
|
|
698cbf15a3 | ||
|
|
9969d7f787 | ||
|
|
49b7f0ec45 | ||
|
|
301ed84ec8 | ||
|
|
7d016e6f39 | ||
|
|
438942a540 | ||
|
|
6bf60cbf5e | ||
|
|
1a7e68556f | ||
|
|
13f48d8dbf | ||
|
|
4d62a82b4a | ||
|
|
4685f253c3 | ||
|
|
025f936cc4 | ||
|
|
5d9a533591 | ||
|
|
72f5326076 | ||
|
|
53c9c299df | ||
|
|
d4090b169d | ||
|
|
29c89df46e | ||
|
|
14e1599660 | ||
|
|
4300a973b8 | ||
|
|
5a402ce2d5 | ||
|
|
515392c216 | ||
|
|
d5872e8e31 | ||
|
|
8cae6e955b | ||
|
|
791a295636 | ||
|
|
f2879c25d1 | ||
|
|
49b1e78820 | ||
|
|
7942e7090e | ||
|
|
beb5058e6f | ||
|
|
8ef716d19a | ||
|
|
5da1e640ad | ||
|
|
53edd42a4e | ||
|
|
e50b9a0757 | ||
|
|
e9abc94483 | ||
|
|
583194c8f4 | ||
|
|
319a4c04c7 | ||
|
|
a8c2aa0f44 | ||
|
|
f9c971ff87 | ||
|
|
3d61cb0cb1 | ||
|
|
ddc4d7f8c7 | ||
|
|
d51fdfda42 | ||
|
|
7ce7387202 | ||
|
|
b4b7c57cb0 | ||
|
|
c48355e920 | ||
|
|
99cb476500 | ||
|
|
514ee7135e | ||
|
|
025628bd76 | ||
|
|
f501f5f4c9 | ||
|
|
a49a57ccd1 | ||
|
|
0bc1993879 | ||
|
|
cb61d003ab | ||
|
|
d999f5a68e | ||
|
|
f1ab834658 | ||
|
|
8bb9cb38be | ||
|
|
a5d27d395f | ||
|
|
ea89733833 | ||
|
|
170adbf2c4 | ||
|
|
8a02f61b27 | ||
|
|
32e940c084 | ||
|
|
c460458d57 | ||
|
|
5912afc9d4 | ||
|
|
9276d78776 | ||
|
|
3ab63b5cc6 | ||
|
|
71bf5604c6 | ||
|
|
bf09e58d9d | ||
|
|
5ad4f7e0b1 | ||
|
|
711bf140f3 | ||
|
|
6007ea3bcb | ||
|
|
6d5d8d10a9 | ||
|
|
2e43bcffaf | ||
|
|
47bd2a09b4 | ||
|
|
4f7d1e46cf | ||
|
|
3626699f1f | ||
|
|
fe71dffbf2 | ||
|
|
8e491b1708 | ||
|
|
3da69a9f22 | ||
|
|
d4f55990f8 | ||
|
|
28fb193ccd | ||
|
|
c5f9f199df | ||
|
|
c7ec1a367a | ||
|
|
ab1f8ea508 | ||
|
|
1fa63a78bc | ||
|
|
568c83dda7 | ||
|
|
892b1c3c58 | ||
|
|
cbf6614c1a | ||
|
|
7cb8481053 | ||
|
|
8242d069f1 | ||
|
|
6ef80f4ed2 | ||
|
|
dc03e4c51b | ||
|
|
30978004b3 | ||
|
|
652e662d1a | ||
|
|
a0ff29bcf2 | ||
|
|
e2c0d89662 | ||
|
|
5b6c764711 | ||
|
|
a74ef35f07 | ||
|
|
4698284b41 | ||
|
|
718a826c51 | ||
|
|
3915e1c71a | ||
|
|
ce4c17308e | ||
|
|
490847c681 | ||
|
|
c20218eb57 | ||
|
|
34d87be519 | ||
|
|
0513de624c | ||
|
|
49f2f691cb | ||
|
|
c2ff9de31b | ||
|
|
c5a8c8e907 | ||
|
|
4363fa1d76 | ||
|
|
5a9bc7cc10 | ||
|
|
0b76524983 | ||
|
|
421582f845 | ||
|
|
3cfca5afdc | ||
|
|
0643ba066e | ||
|
|
809e2f4da8 | ||
|
|
ba3237604f | ||
|
|
ade9bf7cb3 | ||
|
|
64b28d1097 | ||
|
|
dcc5ee156f | ||
|
|
58a8c0626c | ||
|
|
27c0ab238b | ||
|
|
547ccc4b5e | ||
|
|
99e4cf4d84 | ||
|
|
1dccb6cffb | ||
|
|
f6c65d3f45 | ||
|
|
9d2972183e | ||
|
|
32e979801b | ||
|
|
f15bf6c8f8 | ||
|
|
9b0a47cbc8 | ||
|
|
f22a9c0f78 | ||
|
|
a57aa8831e | ||
|
|
ad0082fde4 | ||
|
|
b029ff11b6 | ||
|
|
5dcba26b31 | ||
|
|
7ae1d4d6f5 | ||
|
|
55e8d4cf1b | ||
|
|
d4a7c9e1ab | ||
|
|
4e8759c65e | ||
|
|
a8d7f00e2e | ||
|
|
af8a9de663 | ||
|
|
d815289d54 | ||
|
|
f58d67b015 |
+165
-1
@@ -10,6 +10,165 @@ 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 3.0.7 ] Release date: 28 jan 2016
|
||||
|
||||
Upgrade urgency MODERATE: this release fixes important Redis Cluster bugs.
|
||||
|
||||
* [FIX] avg_ttl reporting in INFO improved. (Salvatore Sanfilippo)
|
||||
* [FIX] Redis Cluster address update (via gossip section) processing improved
|
||||
to avoid initiating inwanted handshakes.
|
||||
* [FIX] Many fixes to MIGRATE multiple keys implementation. The command
|
||||
could handle errors in a faulty way leading to crashes or other
|
||||
unexpected behaviors. MIGRATE command refactoring.
|
||||
(The analysis of the faulty conditions was conducted by
|
||||
Kevin McGehee. The fix was developed by Salvatore Sanfilippo)
|
||||
* [FIX] A Redis Cluster node crash was fixed because of wrong handling of
|
||||
node->slaveof pointers.
|
||||
(Reported by JackyWoo, fixed by Salvatore Sanfilippo)
|
||||
* [FIX] Fix redis-trib rebalance when nodes need to be left empty because
|
||||
the specified weight is zero.
|
||||
(Reported by Shahar Mor, fixed by Salvatore Sanfilippo)
|
||||
* [FIX] MIGRATE: Never send -ASK redirections for MIGRATE when there are
|
||||
open slots. Redis-trib and other cluster management utility must
|
||||
always be free to move keys between nodes about open slots, in order
|
||||
to reshard, fix the cluster configuration, and so forth.
|
||||
(Salvatore Sanfilippo)
|
||||
* [FIX] Redis-trib is now able to fix more errors. A new CLUSTER subcommand
|
||||
called BUMPEPOCH was introduced in order to support new modes
|
||||
for the "fix" subcommand. (Salvatore Sanfilippo)
|
||||
* [NEW] Cluster/Sentinel tests now use OSX leak to perform leak detection
|
||||
at the end of every unit. (Salvatore Sanfilippo)
|
||||
* [NEW] Detect and show server crashes during Cluster/Sentinel tests.
|
||||
(Salvatore Sanfilippo)
|
||||
* [NEW] More reliable Cluster/Sentinel test becuase of timing errors and
|
||||
-LOADING errors. (Salvatore Sanfilippo)
|
||||
|
||||
--[ Redis 3.0.6 ] Release date: 18 Dec 2015
|
||||
|
||||
Upgrade urgency: MODERATE. We fixed a crash that happens very rarely, so
|
||||
updating does not hurt, but most users are unlikely to
|
||||
experience this condition because it requires some odd
|
||||
timing. However if you are a Redis Cluster user, upgrading
|
||||
is strongly adviced since this release includes very
|
||||
important improvements to Redis Cluster.
|
||||
|
||||
* [FIX] lua_struct.c/getnum security issue fixed. (Luca Bruno discovered it,
|
||||
patched by Sun He and Chris Lamb)
|
||||
* [FIX] Redis Cluster replica migration fixed. See issue #2924 for details.
|
||||
(Salvatore Sanfilippo)
|
||||
* [FIX] Fix a race condition in processCommand() because of interactions
|
||||
with freeMemoryIfNeeded(). Details in issue #2948 and especially
|
||||
in the commit message d999f5a. (Race found analytically by
|
||||
Oran Agra, patch by Salvatore Sanfilippo)
|
||||
|
||||
* [NEW] Backported from the upcoming Redis 3.2:
|
||||
MIGRATE now supports an extended multiple-keys pipelined mode, which
|
||||
is an order of magnitude faster. Redis Cluster now uses this mode
|
||||
in order to perform reshardings and rebalancings. (Salvatore Sanfilippo)
|
||||
* [NEW] Backported from the upcoming Redis 3.2:
|
||||
Redis Cluster has now support for rebalancing via the redis-trib
|
||||
rebalance command. Demo here:
|
||||
https://asciinema.org/a/0tw2e5740kouda0yhkqrm5790
|
||||
Official documentation will be available ASAP. (Salvatore Sanfilippo)
|
||||
* [NEW] Redis Cluster redis-trib.rb new "info" subcommand.
|
||||
* [NEW] Redis Cluster tests improved. (Salvatore Sanfilippo)
|
||||
* [NEW] Log offending memory access address on SIGSEGV/SIGBUS (Salvatore
|
||||
Sanfilippo)
|
||||
|
||||
--[ Redis 3.0.5 ] Release date: 15 Oct 2015
|
||||
|
||||
Upgrade urgency: MODERATE, the most important thing is a fix in the replication
|
||||
code that may make the slave hanging forever if the master
|
||||
remains with an open socket even if it is no longer able to
|
||||
reply.
|
||||
|
||||
* [FIX] MOVE now moves the TTL as well. A bug lasting forever... finally
|
||||
fixed thanks to Andy Grunwald that reported it.
|
||||
(reported by Andy Grunwald, fixed by Salvatore Sanfilippo)
|
||||
* [FIX] Fix a false positive in HSTRLEN test.
|
||||
* [FIX] Fix a bug in redis-cli --pipe mode that was not able to read back
|
||||
replies from the server incrementally. Now a mass import will use
|
||||
a lot less memory, and you can use --pipe to do incremental streaming.
|
||||
(reported by Twitter user @fsaintjacques, fixed by Salvatore
|
||||
Sanfilippo)
|
||||
* [FIX] Slave detection of master timeout. (fixed by Kevin McGehee, refactoring
|
||||
and regression test by Salvatore Sanfilippo)
|
||||
|
||||
* [NEW] Cluster: redis-trib fix can fix an additional case for opens lots.
|
||||
(Salvatore Sanfilippo)
|
||||
* [NEW] Cluster: redis-trib import support for --copy and --replace options
|
||||
(David Thomson)
|
||||
|
||||
--[ Redis 3.0.4 ] Release date: 8 Sep 2015
|
||||
|
||||
Upgrade urgency: HIGH for Redis and Sentinel. However note that in order to
|
||||
fix certain replication bugs, the replication internals were
|
||||
modified in a very heavy way. So while this release is
|
||||
conceptually saner, it may contain regressions. For this
|
||||
reason, before the release, QA activities were performed by
|
||||
me (antirez) and Redis Labs and no evident bug was found.
|
||||
|
||||
* [FIX] A number of bugs related to replication PSYNC and the (yet experimental)
|
||||
diskless replication feature were fixed. The bugs could lead to
|
||||
inconsistency between masters and slaves. (Salvatore Sanfilippo, Oran
|
||||
Agra fixed the issue found by Yuval Inbar)
|
||||
* [FIX] A replication bug in the context of PSYNC partial resynchonization was
|
||||
found and fixed. This bug happens even when diskless replication is off
|
||||
in the case different slaves connect at different times while the master
|
||||
is creating an RDB file, and later a partial resynchronization is
|
||||
attempted by a slave that connected not as the first one. (Salvatore
|
||||
Sanfilippo, Oran Agra)
|
||||
* [FIX] Chained replication and PSYNC interactions leading to potential stale
|
||||
chained slaves data set, see issue #2694. (Salvatore Sanfilippo fixed
|
||||
an issue reported by "GeorgeBJ" user at Github)
|
||||
* [FIX] redis-cli --scan iteration fixed when returned cursor overflows
|
||||
32 bit signed integer. (Ofir Luzon, Yuval Inbar)
|
||||
* [FIX] Sentinel: fixed a bug during the master switch process, where for a
|
||||
failed conditional check, the new configuration is rewritten, during
|
||||
a small window of time, in a corrupted way where the master is
|
||||
also reported to be one of the slaves. This bug is rare to trigger
|
||||
but apparently it happens in the wild, and the effect is to see
|
||||
a replication loop where the master will try to replicate with itself.
|
||||
A detailed explanation of the bug and its effects can be found in
|
||||
the commit message here: https://github.com/antirez/redis/commit/c20218eb5770b2cafb12bc7092313b8358fedc0a.
|
||||
The bug was found by Jan-Erik Rediger using a static analyzer and
|
||||
fixed by Salvatore Sanfilippo.
|
||||
* [FIX] Sentinel lack of arity checks for certain commands.
|
||||
(Rogerio Goncalves, Salvatore Sanfilippo)
|
||||
|
||||
* [NEW] Replication internals rewritten in order to be more resistant to bugs.
|
||||
The replication handshake in the slave side was rewritten as a non
|
||||
blocking state machine. (Salvatore Sanfilippo, Oran Agra)
|
||||
* [NEW] New "replication capabilities" feature introduced in order to signal
|
||||
from the master to the slave what are the features supported, so that
|
||||
the master can choose the kind of replication to start (diskless or
|
||||
not) when master and slave are of different versions. (Oran Agra,
|
||||
Salvatore Sanfilippo)
|
||||
* [NEW] Log clients details when SLAVEOF command is received. (Salvatore
|
||||
Sanfilippo with inputs from Nick Craver and Marc Gravell).
|
||||
|
||||
--[ Redis 3.0.3 ] Release date: 17 Jul 2015
|
||||
|
||||
Upgrade urgency: LOW for Redis and Sentinel.
|
||||
|
||||
* [FIX] Fix blocking operations timeout precision when HZ is at its default
|
||||
value (not increased) and there are thousands of clients connected
|
||||
at the same time. This bug affected Sidekiq users that experienced
|
||||
a very long delay for BLPOP and similar commands to return for
|
||||
timeout. Check commit b029ff1 for more info. (Salvatore Sanfilippo)
|
||||
* [FIX] MIGRATE "creating socket: Invalid argument" error fix. Check
|
||||
issues #2609 and #2612 for more info. (Salvatore Sanfilippo)
|
||||
* [FIX] Be able to connect to the master even when the slave is bound to
|
||||
just the loopback interface and has no valid public address in the
|
||||
network the master is reacahble. (Salvatore Sanfilippo)
|
||||
* [FIX] ZADD with options encoding promotion fixed. (linfangrong)
|
||||
* [FIX] Reset aof_delayed_fsync on CONFIG RESETSTATS. (Tom Kiemes)
|
||||
* [FIX] PFCOUNT key parsing in cluster fixed. (MOON_CLJ)
|
||||
* [FIX] Fix Solaris compilation of Redis 3.0. (Jan-Erik Rediger)
|
||||
|
||||
* [NEW] Variadic EXISTS command. Now the command accepts multiple arguments
|
||||
and returns the total count of existing keys.
|
||||
|
||||
--[ Redis 3.0.2 ] Release date: 4 Jun 2015
|
||||
|
||||
Upgrade urgency: HIGH for Redis because of a security issue.
|
||||
@@ -22,7 +181,7 @@ Upgrade urgency: HIGH for Redis because of a security issue.
|
||||
* [NEW] ZADD support for options: NX, XX, CH. See new doc at redis.io.
|
||||
(Salvatore Sanfilippo)
|
||||
* [NEW] Senitnel: CKQUORUM and FLUSHCONFIG commands back ported.
|
||||
(Salvatore Sanfilippo)
|
||||
(Salvatore Sanfilippo and Bill Anderson)
|
||||
|
||||
--[ Redis 3.0.1 ] Release date: 5 May 2015
|
||||
|
||||
@@ -608,6 +767,11 @@ non-backward compatible changes introduced in the 3.0 release:
|
||||
'?' is actually the role of the instance. M for master, S for slave, C
|
||||
if this process is a saving child (for RDB/AOF), and X for Sentinel.
|
||||
|
||||
* The default maxmemory policy in Redis 3.0 is no longer "volatile-lru" as
|
||||
it used to be in 2.8, but "noeviction". The policies behavior is the same
|
||||
(but LRU eviction is much more precise in 3.0), so only the default value
|
||||
changed. Just make sure to specify in your redis.conf what you mean.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Credits: Where not specified the implementation and design is done by
|
||||
|
||||
+9
-5
@@ -12,15 +12,17 @@ each source file that you contribute.
|
||||
|
||||
PLEASE DO NOT POST GENERAL QUESTIONS that are not about bugs or suspected
|
||||
bugs in the Github issues system. We'll be very happy to help you and provide
|
||||
all the support in the Redis Google Group.
|
||||
all the support Reddit sub:
|
||||
|
||||
Redis Google Group address:
|
||||
|
||||
https://groups.google.com/forum/?fromgroups#!forum/redis-db
|
||||
http://reddit.com/r/redis
|
||||
|
||||
There is also an active community of Redis users at Stack Overflow:
|
||||
|
||||
http://stackoverflow.com/questions/tagged/redis
|
||||
|
||||
# How to provide a patch for a new feature
|
||||
|
||||
1. Drop a message to the Redis Google Group with a proposal of semantics/API.
|
||||
1. If it is a major feature or a semantical change, please post it as a new submission in r/redis on Reddit at http://reddit.com/r/redis. Try to be passionate about why the feature is needed, make users upvote your proposal to gain traction and so forth. Read feedbacks about the community. But in this first step **please don't write code yet**.
|
||||
|
||||
2. If in step 1 you get an acknowledge from the project leaders, use the
|
||||
following procedure to submit a patch:
|
||||
@@ -31,4 +33,6 @@ each source file that you contribute.
|
||||
d. Initiate a pull request on github ( http://help.github.com/send-pull-requests/ )
|
||||
e. Done :)
|
||||
|
||||
For minor fixes just open a pull request on Github.
|
||||
|
||||
Thanks!
|
||||
|
||||
Vendored
+27
-30
@@ -91,7 +91,6 @@ void memrevifle(void *ptr, size_t len) {
|
||||
* behavior. */
|
||||
|
||||
typedef struct mp_buf {
|
||||
lua_State *L;
|
||||
unsigned char *b;
|
||||
size_t len, free;
|
||||
} mp_buf;
|
||||
@@ -111,27 +110,26 @@ mp_buf *mp_buf_new(lua_State *L) {
|
||||
/* Old size = 0; new size = sizeof(*buf) */
|
||||
buf = (mp_buf*)mp_realloc(L, NULL, 0, sizeof(*buf));
|
||||
|
||||
buf->L = L;
|
||||
buf->b = NULL;
|
||||
buf->len = buf->free = 0;
|
||||
return buf;
|
||||
}
|
||||
|
||||
void mp_buf_append(mp_buf *buf, const unsigned char *s, size_t len) {
|
||||
void mp_buf_append(lua_State *L, mp_buf *buf, const unsigned char *s, size_t len) {
|
||||
if (buf->free < len) {
|
||||
size_t newlen = buf->len+len;
|
||||
size_t newsize = (buf->len+len)*2;
|
||||
|
||||
buf->b = (unsigned char*)mp_realloc(buf->L, buf->b, buf->len, newlen*2);
|
||||
buf->free = newlen;
|
||||
buf->b = (unsigned char*)mp_realloc(L, buf->b, buf->len + buf->free, newsize);
|
||||
buf->free = newsize - buf->len;
|
||||
}
|
||||
memcpy(buf->b+buf->len,s,len);
|
||||
buf->len += len;
|
||||
buf->free -= len;
|
||||
}
|
||||
|
||||
void mp_buf_free(mp_buf *buf) {
|
||||
mp_realloc(buf->L, buf->b, buf->len, 0); /* realloc to 0 = free */
|
||||
mp_realloc(buf->L, buf, sizeof(*buf), 0);
|
||||
void mp_buf_free(lua_State *L, mp_buf *buf) {
|
||||
mp_realloc(L, buf->b, buf->len + buf->free, 0); /* realloc to 0 = free */
|
||||
mp_realloc(L, buf, sizeof(*buf), 0);
|
||||
}
|
||||
|
||||
/* ---------------------------- String cursor ----------------------------------
|
||||
@@ -173,7 +171,7 @@ void mp_cur_init(mp_cur *cursor, const unsigned char *s, size_t len) {
|
||||
|
||||
/* ------------------------- Low level MP encoding -------------------------- */
|
||||
|
||||
void mp_encode_bytes(mp_buf *buf, const unsigned char *s, size_t len) {
|
||||
void mp_encode_bytes(lua_State *L, mp_buf *buf, const unsigned char *s, size_t len) {
|
||||
unsigned char hdr[5];
|
||||
int hdrlen;
|
||||
|
||||
@@ -197,12 +195,12 @@ void mp_encode_bytes(mp_buf *buf, const unsigned char *s, size_t len) {
|
||||
hdr[4] = len&0xff;
|
||||
hdrlen = 5;
|
||||
}
|
||||
mp_buf_append(buf,hdr,hdrlen);
|
||||
mp_buf_append(buf,s,len);
|
||||
mp_buf_append(L,buf,hdr,hdrlen);
|
||||
mp_buf_append(L,buf,s,len);
|
||||
}
|
||||
|
||||
/* we assume IEEE 754 internal format for single and double precision floats. */
|
||||
void mp_encode_double(mp_buf *buf, double d) {
|
||||
void mp_encode_double(lua_State *L, mp_buf *buf, double d) {
|
||||
unsigned char b[9];
|
||||
float f = d;
|
||||
|
||||
@@ -211,16 +209,16 @@ void mp_encode_double(mp_buf *buf, double d) {
|
||||
b[0] = 0xca; /* float IEEE 754 */
|
||||
memcpy(b+1,&f,4);
|
||||
memrevifle(b+1,4);
|
||||
mp_buf_append(buf,b,5);
|
||||
mp_buf_append(L,buf,b,5);
|
||||
} else if (sizeof(d) == 8) {
|
||||
b[0] = 0xcb; /* double IEEE 754 */
|
||||
memcpy(b+1,&d,8);
|
||||
memrevifle(b+1,8);
|
||||
mp_buf_append(buf,b,9);
|
||||
mp_buf_append(L,buf,b,9);
|
||||
}
|
||||
}
|
||||
|
||||
void mp_encode_int(mp_buf *buf, int64_t n) {
|
||||
void mp_encode_int(lua_State *L, mp_buf *buf, int64_t n) {
|
||||
unsigned char b[9];
|
||||
int enclen;
|
||||
|
||||
@@ -289,10 +287,10 @@ void mp_encode_int(mp_buf *buf, int64_t n) {
|
||||
enclen = 9;
|
||||
}
|
||||
}
|
||||
mp_buf_append(buf,b,enclen);
|
||||
mp_buf_append(L,buf,b,enclen);
|
||||
}
|
||||
|
||||
void mp_encode_array(mp_buf *buf, int64_t n) {
|
||||
void mp_encode_array(lua_State *L, mp_buf *buf, int64_t n) {
|
||||
unsigned char b[5];
|
||||
int enclen;
|
||||
|
||||
@@ -312,10 +310,10 @@ void mp_encode_array(mp_buf *buf, int64_t n) {
|
||||
b[4] = n & 0xff;
|
||||
enclen = 5;
|
||||
}
|
||||
mp_buf_append(buf,b,enclen);
|
||||
mp_buf_append(L,buf,b,enclen);
|
||||
}
|
||||
|
||||
void mp_encode_map(mp_buf *buf, int64_t n) {
|
||||
void mp_encode_map(lua_State *L, mp_buf *buf, int64_t n) {
|
||||
unsigned char b[5];
|
||||
int enclen;
|
||||
|
||||
@@ -335,7 +333,7 @@ void mp_encode_map(mp_buf *buf, int64_t n) {
|
||||
b[4] = n & 0xff;
|
||||
enclen = 5;
|
||||
}
|
||||
mp_buf_append(buf,b,enclen);
|
||||
mp_buf_append(L,buf,b,enclen);
|
||||
}
|
||||
|
||||
/* --------------------------- Lua types encoding --------------------------- */
|
||||
@@ -345,12 +343,12 @@ void mp_encode_lua_string(lua_State *L, mp_buf *buf) {
|
||||
const char *s;
|
||||
|
||||
s = lua_tolstring(L,-1,&len);
|
||||
mp_encode_bytes(buf,(const unsigned char*)s,len);
|
||||
mp_encode_bytes(L,buf,(const unsigned char*)s,len);
|
||||
}
|
||||
|
||||
void mp_encode_lua_bool(lua_State *L, mp_buf *buf) {
|
||||
unsigned char b = lua_toboolean(L,-1) ? 0xc3 : 0xc2;
|
||||
mp_buf_append(buf,&b,1);
|
||||
mp_buf_append(L,buf,&b,1);
|
||||
}
|
||||
|
||||
/* Lua 5.3 has a built in 64-bit integer type */
|
||||
@@ -360,7 +358,7 @@ void mp_encode_lua_integer(lua_State *L, mp_buf *buf) {
|
||||
#else
|
||||
lua_Integer i = lua_tointeger(L,-1);
|
||||
#endif
|
||||
mp_encode_int(buf, (int64_t)i);
|
||||
mp_encode_int(L, buf, (int64_t)i);
|
||||
}
|
||||
|
||||
/* Lua 5.2 and lower only has 64-bit doubles, so we need to
|
||||
@@ -372,7 +370,7 @@ void mp_encode_lua_number(lua_State *L, mp_buf *buf) {
|
||||
if (IS_INT64_EQUIVALENT(n)) {
|
||||
mp_encode_lua_integer(L, buf);
|
||||
} else {
|
||||
mp_encode_double(buf,(double)n);
|
||||
mp_encode_double(L,buf,(double)n);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,7 +384,7 @@ void mp_encode_lua_table_as_array(lua_State *L, mp_buf *buf, int level) {
|
||||
size_t len = lua_rawlen(L,-1), j;
|
||||
#endif
|
||||
|
||||
mp_encode_array(buf,len);
|
||||
mp_encode_array(L,buf,len);
|
||||
for (j = 1; j <= len; j++) {
|
||||
lua_pushnumber(L,j);
|
||||
lua_gettable(L,-2);
|
||||
@@ -409,7 +407,7 @@ void mp_encode_lua_table_as_map(lua_State *L, mp_buf *buf, int level) {
|
||||
}
|
||||
|
||||
/* Step two: actually encoding of the map. */
|
||||
mp_encode_map(buf,len);
|
||||
mp_encode_map(L,buf,len);
|
||||
lua_pushnil(L);
|
||||
while(lua_next(L,-2)) {
|
||||
/* Stack: ... key value */
|
||||
@@ -474,10 +472,9 @@ void mp_encode_lua_table(lua_State *L, mp_buf *buf, int level) {
|
||||
|
||||
void mp_encode_lua_null(lua_State *L, mp_buf *buf) {
|
||||
unsigned char b[1];
|
||||
(void)L;
|
||||
|
||||
b[0] = 0xc0;
|
||||
mp_buf_append(buf,b,1);
|
||||
mp_buf_append(L,buf,b,1);
|
||||
}
|
||||
|
||||
void mp_encode_lua_type(lua_State *L, mp_buf *buf, int level) {
|
||||
@@ -534,7 +531,7 @@ int mp_pack(lua_State *L) {
|
||||
buf->free += buf->len;
|
||||
buf->len = 0;
|
||||
}
|
||||
mp_buf_free(buf);
|
||||
mp_buf_free(L, buf);
|
||||
|
||||
/* Concatenate all nargs buffers together */
|
||||
lua_concat(L, nargs);
|
||||
|
||||
Vendored
+6
-4
@@ -89,12 +89,14 @@ typedef struct Header {
|
||||
} Header;
|
||||
|
||||
|
||||
static int getnum (const char **fmt, int df) {
|
||||
static int getnum (lua_State *L, const char **fmt, int df) {
|
||||
if (!isdigit(**fmt)) /* no number? */
|
||||
return df; /* return default value */
|
||||
else {
|
||||
int a = 0;
|
||||
do {
|
||||
if (a > (INT_MAX / 10) || a * 10 > (INT_MAX - (**fmt - '0')))
|
||||
luaL_error(L, "integral size overflow");
|
||||
a = a*10 + *((*fmt)++) - '0';
|
||||
} while (isdigit(**fmt));
|
||||
return a;
|
||||
@@ -115,9 +117,9 @@ static size_t optsize (lua_State *L, char opt, const char **fmt) {
|
||||
case 'f': return sizeof(float);
|
||||
case 'd': return sizeof(double);
|
||||
case 'x': return 1;
|
||||
case 'c': return getnum(fmt, 1);
|
||||
case 'c': return getnum(L, fmt, 1);
|
||||
case 'i': case 'I': {
|
||||
int sz = getnum(fmt, sizeof(int));
|
||||
int sz = getnum(L, fmt, sizeof(int));
|
||||
if (sz > MAXINTSIZE)
|
||||
luaL_error(L, "integral size %d is larger than limit of %d",
|
||||
sz, MAXINTSIZE);
|
||||
@@ -150,7 +152,7 @@ static void controloptions (lua_State *L, int opt, const char **fmt,
|
||||
case '>': h->endian = BIG; return;
|
||||
case '<': h->endian = LITTLE; return;
|
||||
case '!': {
|
||||
int a = getnum(fmt, MAXALIGN);
|
||||
int a = getnum(L, fmt, MAXALIGN);
|
||||
if (!isp2(a))
|
||||
luaL_error(L, "alignment %d is not a power of 2", a);
|
||||
h->align = a;
|
||||
|
||||
+6
-1
@@ -1,4 +1,9 @@
|
||||
# Redis configuration file example
|
||||
# Redis configuration file example.
|
||||
#
|
||||
# Note that in order to read the configuration file, Redis must be
|
||||
# started with the file path as first argument:
|
||||
#
|
||||
# ./redis-server /path/to/redis.conf
|
||||
|
||||
# Note on units: when memory size is needed, it is possible to specify
|
||||
# it in the usual form of 1k 5GB 4M and so forth:
|
||||
|
||||
+23
-5
@@ -264,6 +264,7 @@ static int anetCreateSocket(char *err, int domain) {
|
||||
|
||||
#define ANET_CONNECT_NONE 0
|
||||
#define ANET_CONNECT_NONBLOCK 1
|
||||
#define ANET_CONNECT_BE_BINDING 2 /* Best effort binding. */
|
||||
static int anetTcpGenericConnect(char *err, char *addr, int port,
|
||||
char *source_addr, int flags)
|
||||
{
|
||||
@@ -295,7 +296,7 @@ static int anetTcpGenericConnect(char *err, char *addr, int port,
|
||||
if ((rv = getaddrinfo(source_addr, NULL, &hints, &bservinfo)) != 0)
|
||||
{
|
||||
anetSetError(err, "%s", gai_strerror(rv));
|
||||
goto end;
|
||||
goto error;
|
||||
}
|
||||
for (b = bservinfo; b != NULL; b = b->ai_next) {
|
||||
if (bind(s,b->ai_addr,b->ai_addrlen) != -1) {
|
||||
@@ -306,7 +307,7 @@ static int anetTcpGenericConnect(char *err, char *addr, int port,
|
||||
freeaddrinfo(bservinfo);
|
||||
if (!bound) {
|
||||
anetSetError(err, "bind: %s", strerror(errno));
|
||||
goto end;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
|
||||
@@ -331,9 +332,17 @@ error:
|
||||
close(s);
|
||||
s = ANET_ERR;
|
||||
}
|
||||
|
||||
end:
|
||||
freeaddrinfo(servinfo);
|
||||
return s;
|
||||
|
||||
/* Handle best effort binding: if a binding address was used, but it is
|
||||
* not possible to create a socket, try again without a binding address. */
|
||||
if (s == ANET_ERR && source_addr && (flags & ANET_CONNECT_BE_BINDING)) {
|
||||
return anetTcpGenericConnect(err,addr,port,NULL,flags);
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
int anetTcpConnect(char *err, char *addr, int port)
|
||||
@@ -346,9 +355,18 @@ int anetTcpNonBlockConnect(char *err, char *addr, int port)
|
||||
return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONBLOCK);
|
||||
}
|
||||
|
||||
int anetTcpNonBlockBindConnect(char *err, char *addr, int port, char *source_addr)
|
||||
int anetTcpNonBlockBindConnect(char *err, char *addr, int port,
|
||||
char *source_addr)
|
||||
{
|
||||
return anetTcpGenericConnect(err,addr,port,source_addr,ANET_CONNECT_NONBLOCK);
|
||||
return anetTcpGenericConnect(err,addr,port,source_addr,
|
||||
ANET_CONNECT_NONBLOCK);
|
||||
}
|
||||
|
||||
int anetTcpNonBlockBestEffortBindConnect(char *err, char *addr, int port,
|
||||
char *source_addr)
|
||||
{
|
||||
return anetTcpGenericConnect(err,addr,port,source_addr,
|
||||
ANET_CONNECT_NONBLOCK|ANET_CONNECT_BE_BINDING);
|
||||
}
|
||||
|
||||
int anetUnixGenericConnect(char *err, char *path, int flags)
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
int anetTcpConnect(char *err, char *addr, int port);
|
||||
int anetTcpNonBlockConnect(char *err, char *addr, int port);
|
||||
int anetTcpNonBlockBindConnect(char *err, char *addr, int port, char *source_addr);
|
||||
int anetTcpNonBlockBestEffortBindConnect(char *err, char *addr, int port, char *source_addr);
|
||||
int anetUnixConnect(char *err, char *path);
|
||||
int anetUnixNonBlockConnect(char *err, char *path);
|
||||
int anetRead(int fd, char *buf, int count);
|
||||
|
||||
@@ -1169,9 +1169,9 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
return REDIS_OK;
|
||||
|
||||
werr:
|
||||
redisLog(REDIS_WARNING,"Write error writing append only file on disk: %s", strerror(errno));
|
||||
fclose(fp);
|
||||
unlink(tmpfile);
|
||||
redisLog(REDIS_WARNING,"Write error writing append only file on disk: %s", strerror(errno));
|
||||
if (di) dictReleaseIterator(di);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
+346
-138
@@ -129,7 +129,7 @@ int clusterLoadConfig(char *filename) {
|
||||
/* Skip blank lines, they can be created either by users manually
|
||||
* editing nodes.conf or by the config writing process if stopped
|
||||
* before the truncate() call. */
|
||||
if (line[0] == '\n') continue;
|
||||
if (line[0] == '\n' || line[0] == '\0') continue;
|
||||
|
||||
/* Split the line into arguments for processing. */
|
||||
argv = sdssplitargs(line,&argc);
|
||||
@@ -358,6 +358,11 @@ void clusterSaveConfigOrDie(int do_fsync) {
|
||||
* On success REDIS_OK is returned, otherwise an error is logged and
|
||||
* the function returns REDIS_ERR to signal a lock was not acquired. */
|
||||
int clusterLockConfig(char *filename) {
|
||||
/* flock() does not exist on Solaris
|
||||
* and a fcntl-based solution won't help, as we constantly re-open that file,
|
||||
* which will release _all_ locks anyway
|
||||
*/
|
||||
#if !defined(__sun)
|
||||
/* To lock it, we need to open the file in a way it is created if
|
||||
* it does not exist, otherwise there is a race condition with other
|
||||
* processes. */
|
||||
@@ -385,6 +390,8 @@ int clusterLockConfig(char *filename) {
|
||||
}
|
||||
/* Lock acquired: leak the 'fd' by not closing it, so that we'll retain the
|
||||
* lock to the file as long as the process exists. */
|
||||
#endif /* __sun */
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
@@ -524,6 +531,7 @@ void clusterReset(int hard) {
|
||||
sdsfree(oldname);
|
||||
getRandomHexChars(myself->name, REDIS_CLUSTER_NAMELEN);
|
||||
clusterAddNode(myself);
|
||||
redisLog(REDIS_NOTICE,"Node hard reset, now I'm %.40s", myself->name);
|
||||
}
|
||||
|
||||
/* Make sure to persist the new config and update the state. */
|
||||
@@ -664,6 +672,7 @@ clusterNode *createClusterNode(char *nodename, int flags) {
|
||||
node->port = 0;
|
||||
node->fail_reports = listCreate();
|
||||
node->voted_time = 0;
|
||||
node->orphaned_time = 0;
|
||||
node->repl_offset_time = 0;
|
||||
node->repl_offset = 0;
|
||||
listSetFreeMethod(node->fail_reports,zfree);
|
||||
@@ -776,6 +785,8 @@ int clusterNodeRemoveSlave(clusterNode *master, clusterNode *slave) {
|
||||
(sizeof(*master->slaves) * remaining_slaves));
|
||||
}
|
||||
master->numslaves--;
|
||||
if (master->numslaves == 0)
|
||||
master->flags &= ~REDIS_NODE_MIGRATE_TO;
|
||||
return REDIS_OK;
|
||||
}
|
||||
}
|
||||
@@ -792,15 +803,10 @@ int clusterNodeAddSlave(clusterNode *master, clusterNode *slave) {
|
||||
sizeof(clusterNode*)*(master->numslaves+1));
|
||||
master->slaves[master->numslaves] = slave;
|
||||
master->numslaves++;
|
||||
master->flags |= REDIS_NODE_MIGRATE_TO;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
void clusterNodeResetSlaves(clusterNode *n) {
|
||||
zfree(n->slaves);
|
||||
n->numslaves = 0;
|
||||
n->slaves = NULL;
|
||||
}
|
||||
|
||||
int clusterCountNonFailingSlaves(clusterNode *n) {
|
||||
int j, okslaves = 0;
|
||||
|
||||
@@ -814,12 +820,10 @@ void freeClusterNode(clusterNode *n) {
|
||||
sds nodename;
|
||||
int j;
|
||||
|
||||
/* If the node is a master with associated slaves, we have to set
|
||||
/* If the node has associated slaves, we have to set
|
||||
* all the slaves->slaveof fields to NULL (unknown). */
|
||||
if (nodeIsMaster(n)) {
|
||||
for (j = 0; j < n->numslaves; j++)
|
||||
n->slaves[j]->slaveof = NULL;
|
||||
}
|
||||
for (j = 0; j < n->numslaves; j++)
|
||||
n->slaves[j]->slaveof = NULL;
|
||||
|
||||
/* Remove this node from the list of slaves of its master. */
|
||||
if (nodeIsSlave(n) && n->slaveof) clusterNodeRemoveSlave(n->slaveof,n);
|
||||
@@ -937,7 +941,7 @@ uint64_t clusterGetMaxEpoch(void) {
|
||||
/* If this node epoch is zero or is not already the greatest across the
|
||||
* cluster (from the POV of the local configuration), this function will:
|
||||
*
|
||||
* 1) Generate a new config epoch increment the current epoch.
|
||||
* 1) Generate a new config epoch, incrementing the current epoch.
|
||||
* 2) Assign the new epoch to this node, WITHOUT any consensus.
|
||||
* 3) Persist the configuration on disk before sending packets with the
|
||||
* new configuration.
|
||||
@@ -952,16 +956,16 @@ uint64_t clusterGetMaxEpoch(void) {
|
||||
* cases:
|
||||
*
|
||||
* 1) When slots are closed after importing. Otherwise resharding would be
|
||||
* too exansive.
|
||||
* too expansive.
|
||||
* 2) When CLUSTER FAILOVER is called with options that force a slave to
|
||||
* failover its master even if there is not master majority able to
|
||||
* create a new configuration epoch.
|
||||
*
|
||||
* Redis Cluster does not explode using this function, even in the case of
|
||||
* Redis Cluster will not explode using this function, even in the case of
|
||||
* a collision between this node and another node, generating the same
|
||||
* configuration epoch unilaterally, because the config epoch conflict
|
||||
* resolution algorithm will eventually move colliding nodes to different
|
||||
* config epochs. However usign this function may violate the "last failover
|
||||
* config epochs. However using this function may violate the "last failover
|
||||
* wins" rule, so should only be used with care. */
|
||||
int clusterBumpConfigEpochWithoutConsensus(void) {
|
||||
uint64_t maxEpoch = clusterGetMaxEpoch();
|
||||
@@ -1327,14 +1331,19 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) {
|
||||
}
|
||||
|
||||
/* If we already know this node, but it is not reachable, and
|
||||
* we see a different address in the gossip section, start an
|
||||
* handshake with the (possibly) new address: this will result
|
||||
* into a node address update if the handshake will be
|
||||
* successful. */
|
||||
* we see a different address in the gossip section of a node that
|
||||
* can talk with this other node, update the address, disconnect
|
||||
* the old link if any, so that we'll attempt to connect with the
|
||||
* new address. */
|
||||
if (node->flags & (REDIS_NODE_FAIL|REDIS_NODE_PFAIL) &&
|
||||
!(flags & REDIS_NODE_NOADDR) &&
|
||||
!(flags & (REDIS_NODE_FAIL|REDIS_NODE_PFAIL)) &&
|
||||
(strcasecmp(node->ip,g->ip) || node->port != ntohs(g->port)))
|
||||
{
|
||||
clusterStartHandshake(g->ip,ntohs(g->port));
|
||||
if (node->link) freeClusterLink(node->link);
|
||||
memcpy(node->ip,g->ip,REDIS_IP_STR_LEN);
|
||||
node->port = ntohs(g->port);
|
||||
node->flags &= ~REDIS_NODE_NOADDR;
|
||||
}
|
||||
} else {
|
||||
/* If it's not in NOADDR state and we don't have it, we
|
||||
@@ -1406,7 +1415,10 @@ int nodeUpdateAddressIfNeeded(clusterNode *node, clusterLink *link, int port) {
|
||||
void clusterSetNodeAsMaster(clusterNode *n) {
|
||||
if (nodeIsMaster(n)) return;
|
||||
|
||||
if (n->slaveof) clusterNodeRemoveSlave(n->slaveof,n);
|
||||
if (n->slaveof) {
|
||||
clusterNodeRemoveSlave(n->slaveof,n);
|
||||
if (n != myself) n->flags |= REDIS_NODE_MIGRATE_TO;
|
||||
}
|
||||
n->flags &= ~REDIS_NODE_SLAVE;
|
||||
n->flags |= REDIS_NODE_MASTER;
|
||||
n->slaveof = NULL;
|
||||
@@ -1425,8 +1437,8 @@ void clusterSetNodeAsMaster(clusterNode *n) {
|
||||
* node (see the function comments for more info).
|
||||
*
|
||||
* The 'sender' is the node for which we received a configuration update.
|
||||
* Sometimes it is not actually the "Sender" of the information, like in the case
|
||||
* we receive the info via an UPDATE packet. */
|
||||
* Sometimes it is not actually the "Sender" of the information, like in the
|
||||
* case we receive the info via an UPDATE packet. */
|
||||
void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoch, unsigned char *slots) {
|
||||
int j;
|
||||
clusterNode *curmaster, *newmaster = NULL;
|
||||
@@ -1530,9 +1542,6 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
clusterMsg *hdr = (clusterMsg*) link->rcvbuf;
|
||||
uint32_t totlen = ntohl(hdr->totlen);
|
||||
uint16_t type = ntohs(hdr->type);
|
||||
uint16_t flags = ntohs(hdr->flags);
|
||||
uint64_t senderCurrentEpoch = 0, senderConfigEpoch = 0;
|
||||
clusterNode *sender;
|
||||
|
||||
server.cluster->stats_bus_messages_received++;
|
||||
redisLog(REDIS_DEBUG,"--- Processing packet of type %d, %lu bytes",
|
||||
@@ -1540,9 +1549,17 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
|
||||
/* Perform sanity checks */
|
||||
if (totlen < 16) return 1; /* At least signature, version, totlen, count. */
|
||||
if (ntohs(hdr->ver) != CLUSTER_PROTO_VER)
|
||||
return 1; /* Can't handle versions other than the current one.*/
|
||||
if (totlen > sdslen(link->rcvbuf)) return 1;
|
||||
|
||||
if (ntohs(hdr->ver) != CLUSTER_PROTO_VER) {
|
||||
/* Can't handle messages of different versions. */
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint16_t flags = ntohs(hdr->flags);
|
||||
uint64_t senderCurrentEpoch = 0, senderConfigEpoch = 0;
|
||||
clusterNode *sender;
|
||||
|
||||
if (type == CLUSTERMSG_TYPE_PING || type == CLUSTERMSG_TYPE_PONG ||
|
||||
type == CLUSTERMSG_TYPE_MEET)
|
||||
{
|
||||
@@ -1704,7 +1721,10 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
/* If the reply has a non matching node ID we
|
||||
* disconnect this node and set it as not having an associated
|
||||
* address. */
|
||||
redisLog(REDIS_DEBUG,"PONG contains mismatching sender ID");
|
||||
redisLog(REDIS_DEBUG,"PONG contains mismatching sender ID. About node %.40s added %d ms ago, having flags %d",
|
||||
link->node->name,
|
||||
(int)(mstime()-(link->node->ctime)),
|
||||
link->node->flags);
|
||||
link->node->flags |= REDIS_NODE_NOADDR;
|
||||
link->node->ip[0] = '\0';
|
||||
link->node->port = 0;
|
||||
@@ -1757,12 +1777,10 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
if (nodeIsMaster(sender)) {
|
||||
/* Master turned into a slave! Reconfigure the node. */
|
||||
clusterDelNodeSlots(sender);
|
||||
sender->flags &= ~REDIS_NODE_MASTER;
|
||||
sender->flags &= ~(REDIS_NODE_MASTER|
|
||||
REDIS_NODE_MIGRATE_TO);
|
||||
sender->flags |= REDIS_NODE_SLAVE;
|
||||
|
||||
/* Remove the list of slaves from the node. */
|
||||
if (sender->numslaves) clusterNodeResetSlaves(sender);
|
||||
|
||||
/* Update config and state. */
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|
|
||||
CLUSTER_TODO_UPDATE_STATE);
|
||||
@@ -2618,7 +2636,9 @@ void clusterLogCantFailover(int reason) {
|
||||
|
||||
switch(reason) {
|
||||
case REDIS_CLUSTER_CANT_FAILOVER_DATA_AGE:
|
||||
msg = "Disconnected from master for longer than allowed.";
|
||||
msg = "Disconnected from master for longer than allowed. "
|
||||
"Please check the 'cluster-slave-validity-factor' configuration "
|
||||
"option.";
|
||||
break;
|
||||
case REDIS_CLUSTER_CANT_FAILOVER_WAITING_DELAY:
|
||||
msg = "Waiting the delay before I can start a new failover.";
|
||||
@@ -2854,7 +2874,7 @@ void clusterHandleSlaveFailover(void) {
|
||||
* Slave migration is the process that allows a slave of a master that is
|
||||
* already covered by at least another slave, to "migrate" to a master that
|
||||
* is orpaned, that is, left with no working slaves.
|
||||
* -------------------------------------------------------------------------- */
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/* This function is responsible to decide if this replica should be migrated
|
||||
* to a different (orphaned) master. It is called by the clusterCron() function
|
||||
@@ -2894,30 +2914,44 @@ void clusterHandleSlaveMigration(int max_slaves) {
|
||||
|
||||
/* Step 3: Idenitfy a candidate for migration, and check if among the
|
||||
* masters with the greatest number of ok slaves, I'm the one with the
|
||||
* smaller node ID.
|
||||
* smallest node ID (the "candidate slave").
|
||||
*
|
||||
* Note that this means that eventually a replica migration will occurr
|
||||
* Note: this means that eventually a replica migration will occurr
|
||||
* since slaves that are reachable again always have their FAIL flag
|
||||
* cleared. At the same time this does not mean that there are no
|
||||
* race conditions possible (two slaves migrating at the same time), but
|
||||
* this is extremely unlikely to happen, and harmless. */
|
||||
* cleared, so eventually there must be a candidate. At the same time
|
||||
* this does not mean that there are no race conditions possible (two
|
||||
* slaves migrating at the same time), but this is unlikely to
|
||||
* happen, and harmless when happens. */
|
||||
candidate = myself;
|
||||
di = dictGetSafeIterator(server.cluster->nodes);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
clusterNode *node = dictGetVal(de);
|
||||
int okslaves;
|
||||
int okslaves = 0, is_orphaned = 1;
|
||||
|
||||
/* Only iterate over working masters. */
|
||||
if (nodeIsSlave(node) || nodeFailed(node)) continue;
|
||||
/* If this master never had slaves so far, don't migrate. We want
|
||||
* to migrate to a master that remained orphaned, not masters that
|
||||
* were never configured to have slaves. */
|
||||
if (node->numslaves == 0) continue;
|
||||
okslaves = clusterCountNonFailingSlaves(node);
|
||||
/* We want to migrate only if this master is working, orphaned, and
|
||||
* used to have slaves or if failed over a master that had slaves
|
||||
* (MIGRATE_TO flag). This way we only migrate to instances that were
|
||||
* supposed to have replicas. */
|
||||
if (nodeIsSlave(node) || nodeFailed(node)) is_orphaned = 0;
|
||||
if (!(node->flags & REDIS_NODE_MIGRATE_TO)) is_orphaned = 0;
|
||||
|
||||
if (okslaves == 0 && target == NULL && node->numslots > 0)
|
||||
target = node;
|
||||
/* Check number of working slaves. */
|
||||
if (nodeIsMaster(node)) okslaves = clusterCountNonFailingSlaves(node);
|
||||
if (okslaves > 0) is_orphaned = 0;
|
||||
|
||||
if (is_orphaned) {
|
||||
if (!target && node->numslots > 0) target = node;
|
||||
|
||||
/* Track the starting time of the orphaned condition for this
|
||||
* master. */
|
||||
if (!node->orphaned_time) node->orphaned_time = mstime();
|
||||
} else {
|
||||
node->orphaned_time = 0;
|
||||
}
|
||||
|
||||
/* Check if I'm the slave candidate for the migration: attached
|
||||
* to a master with the maximum number of slaves and with the smallest
|
||||
* node ID. */
|
||||
if (okslaves == max_slaves) {
|
||||
for (j = 0; j < node->numslaves; j++) {
|
||||
if (memcmp(node->slaves[j]->name,
|
||||
@@ -2932,8 +2966,13 @@ void clusterHandleSlaveMigration(int max_slaves) {
|
||||
dictReleaseIterator(di);
|
||||
|
||||
/* Step 4: perform the migration if there is a target, and if I'm the
|
||||
* candidate. */
|
||||
if (target && candidate == myself) {
|
||||
* candidate, but only if the master is continuously orphaned for a
|
||||
* couple of seconds, so that during failovers, we give some time to
|
||||
* the natural slaves of this instance to advertise their switch from
|
||||
* the old master to the new one. */
|
||||
if (target && candidate == myself &&
|
||||
(mstime()-target->orphaned_time) > REDIS_CLUSTER_SLAVE_MIGRATION_DELAY)
|
||||
{
|
||||
redisLog(REDIS_WARNING,"Migrating to orphaned master %.40s",
|
||||
target->name);
|
||||
clusterSetMaster(target);
|
||||
@@ -3160,9 +3199,12 @@ void clusterCron(void) {
|
||||
|
||||
/* A master is orphaned if it is serving a non-zero number of
|
||||
* slots, have no working slaves, but used to have at least one
|
||||
* slave. */
|
||||
if (okslaves == 0 && node->numslots > 0 && node->numslaves)
|
||||
* slave, or failed over a master that used to have slaves. */
|
||||
if (okslaves == 0 && node->numslots > 0 &&
|
||||
node->flags & REDIS_NODE_MIGRATE_TO)
|
||||
{
|
||||
orphaned_masters++;
|
||||
}
|
||||
if (okslaves > max_slaves) max_slaves = okslaves;
|
||||
if (nodeIsSlave(myself) && myself->slaveof == node)
|
||||
this_slaves = okslaves;
|
||||
@@ -3314,11 +3356,45 @@ void bitmapClearBit(unsigned char *bitmap, int pos) {
|
||||
bitmap[byte] &= ~(1<<bit);
|
||||
}
|
||||
|
||||
/* Return non-zero if there is at least one master with slaves in the cluster.
|
||||
* Otherwise zero is returned. Used by clusterNodeSetSlotBit() to set the
|
||||
* MIGRATE_TO flag the when a master gets the first slot. */
|
||||
int clusterMastersHaveSlaves(void) {
|
||||
dictIterator *di = dictGetSafeIterator(server.cluster->nodes);
|
||||
dictEntry *de;
|
||||
int slaves = 0;
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
clusterNode *node = dictGetVal(de);
|
||||
|
||||
if (nodeIsSlave(node)) continue;
|
||||
slaves += node->numslaves;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
return slaves != 0;
|
||||
}
|
||||
|
||||
/* Set the slot bit and return the old value. */
|
||||
int clusterNodeSetSlotBit(clusterNode *n, int slot) {
|
||||
int old = bitmapTestBit(n->slots,slot);
|
||||
bitmapSetBit(n->slots,slot);
|
||||
if (!old) n->numslots++;
|
||||
if (!old) {
|
||||
n->numslots++;
|
||||
/* When a master gets its first slot, even if it has no slaves,
|
||||
* it gets flagged with MIGRATE_TO, that is, the master is a valid
|
||||
* target for replicas migration, if and only if at least one of
|
||||
* the other masters has slaves right now.
|
||||
*
|
||||
* Normally masters are valid targerts of replica migration if:
|
||||
* 1. The used to have slaves (but no longer have).
|
||||
* 2. They are slaves failing over a master that used to have slaves.
|
||||
*
|
||||
* However new masters with slots assigned are considered valid
|
||||
* migration tagets if the rest of the cluster is not a slave-less.
|
||||
*
|
||||
* See https://github.com/antirez/redis/issues/3043 for more info. */
|
||||
if (n->numslots == 1 && clusterMastersHaveSlaves())
|
||||
n->flags |= REDIS_NODE_MIGRATE_TO;
|
||||
}
|
||||
return old;
|
||||
}
|
||||
|
||||
@@ -3564,7 +3640,7 @@ void clusterSetMaster(clusterNode *n) {
|
||||
redisAssert(myself->numslots == 0);
|
||||
|
||||
if (nodeIsMaster(myself)) {
|
||||
myself->flags &= ~REDIS_NODE_MASTER;
|
||||
myself->flags &= ~(REDIS_NODE_MASTER|REDIS_NODE_MIGRATE_TO);
|
||||
myself->flags |= REDIS_NODE_SLAVE;
|
||||
clusterCloseAllSlots();
|
||||
} else {
|
||||
@@ -3732,8 +3808,10 @@ void clusterReplyMultiBulkSlots(redisClient *c) {
|
||||
* 2) end slot
|
||||
* 3) 1) master IP
|
||||
* 2) master port
|
||||
* 3) node ID
|
||||
* 4) 1) replica IP
|
||||
* 2) replica port
|
||||
* 3) node ID
|
||||
* ... continued until done
|
||||
*/
|
||||
|
||||
@@ -3774,18 +3852,20 @@ void clusterReplyMultiBulkSlots(redisClient *c) {
|
||||
start = -1;
|
||||
|
||||
/* First node reply position is always the master */
|
||||
addReplyMultiBulkLen(c, 2);
|
||||
addReplyMultiBulkLen(c, 3);
|
||||
addReplyBulkCString(c, node->ip);
|
||||
addReplyLongLong(c, node->port);
|
||||
addReplyBulkCBuffer(c, node->name, REDIS_CLUSTER_NAMELEN);
|
||||
|
||||
/* Remaining nodes in reply are replicas for slot range */
|
||||
for (i = 0; i < node->numslaves; i++) {
|
||||
/* This loop is copy/pasted from clusterGenNodeDescription()
|
||||
* with modifications for per-slot node aggregation */
|
||||
if (nodeFailed(node->slaves[i])) continue;
|
||||
addReplyMultiBulkLen(c, 2);
|
||||
addReplyMultiBulkLen(c, 3);
|
||||
addReplyBulkCString(c, node->slaves[i]->ip);
|
||||
addReplyLongLong(c, node->slaves[i]->port);
|
||||
addReplyBulkCBuffer(c, node->slaves[i]->name, REDIS_CLUSTER_NAMELEN);
|
||||
nested_elements++;
|
||||
}
|
||||
setDeferredMultiBulkLength(c, nested_replylen, nested_elements);
|
||||
@@ -3901,6 +3981,11 @@ void clusterCommand(redisClient *c) {
|
||||
int slot;
|
||||
clusterNode *n;
|
||||
|
||||
if (nodeIsSlave(myself)) {
|
||||
addReplyError(c,"Please use SETSLOT only with masters.");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((slot = getSlotOrReply(c,c->argv[2])) == -1) return;
|
||||
|
||||
if (!strcasecmp(c->argv[3]->ptr,"migrating") && c->argc == 5) {
|
||||
@@ -3985,6 +4070,13 @@ void clusterCommand(redisClient *c) {
|
||||
}
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_UPDATE_STATE);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"bumpepoch") && c->argc == 2) {
|
||||
/* CLUSTER BUMPEPOCH */
|
||||
int retval = clusterBumpConfigEpochWithoutConsensus();
|
||||
sds reply = sdscatprintf(sdsempty(),"+%s %llu\r\n",
|
||||
(retval == REDIS_OK) ? "BUMPED" : "STILL",
|
||||
(unsigned long long) myself->configEpoch);
|
||||
addReplySds(c,reply);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"info") && c->argc == 2) {
|
||||
/* CLUSTER INFO */
|
||||
char *statestr[] = {"ok","fail","needhelp"};
|
||||
@@ -4486,8 +4578,8 @@ migrateCachedSocket* migrateGetSocket(redisClient *c, robj *host, robj *port, lo
|
||||
}
|
||||
|
||||
/* Create the socket */
|
||||
fd = anetTcpNonBlockBindConnect(server.neterr,c->argv[1]->ptr,
|
||||
atoi(c->argv[2]->ptr),REDIS_BIND_ADDR);
|
||||
fd = anetTcpNonBlockConnect(server.neterr,c->argv[1]->ptr,
|
||||
atoi(c->argv[2]->ptr));
|
||||
if (fd == -1) {
|
||||
sdsfree(name);
|
||||
addReplyErrorFormat(c,"Can't connect to target node: %s",
|
||||
@@ -4550,18 +4642,28 @@ void migrateCloseTimedoutSockets(void) {
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
/* MIGRATE host port key dbid timeout [COPY | REPLACE] */
|
||||
/* MIGRATE host port key dbid timeout [COPY | REPLACE]
|
||||
*
|
||||
* On in the multiple keys form:
|
||||
*
|
||||
* MIGRATE host port "" dbid timeout [COPY | REPLACE] KEYS key1 key2 ... keyN */
|
||||
void migrateCommand(redisClient *c) {
|
||||
migrateCachedSocket *cs;
|
||||
int copy, replace, j;
|
||||
long timeout;
|
||||
long dbid;
|
||||
long long ttl, expireat;
|
||||
robj *o;
|
||||
robj **ov = NULL; /* Objects to migrate. */
|
||||
robj **kv = NULL; /* Key names. */
|
||||
robj **newargv = NULL; /* Used to rewrite the command as DEL ... keys ... */
|
||||
rio cmd, payload;
|
||||
int retry_num = 0;
|
||||
int may_retry = 1;
|
||||
int write_error = 0;
|
||||
|
||||
/* To support the KEYS option we need the following additional state. */
|
||||
int first_key = 3; /* Argument index of the first key. */
|
||||
int num_keys = 1; /* By default only migrate the 'key' argument. */
|
||||
|
||||
try_again:
|
||||
/* Initialization */
|
||||
copy = 0;
|
||||
replace = 0;
|
||||
@@ -4573,6 +4675,16 @@ try_again:
|
||||
copy = 1;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"replace")) {
|
||||
replace = 1;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"keys")) {
|
||||
if (sdslen(c->argv[3]->ptr) != 0) {
|
||||
addReplyError(c,
|
||||
"When using MIGRATE KEYS option, the key argument"
|
||||
" must be set to the empty string");
|
||||
return;
|
||||
}
|
||||
first_key = j+1;
|
||||
num_keys = c->argc - j - 1;
|
||||
break; /* All the remaining args are keys. */
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
@@ -4580,23 +4692,44 @@ try_again:
|
||||
}
|
||||
|
||||
/* Sanity check */
|
||||
if (getLongFromObjectOrReply(c,c->argv[5],&timeout,NULL) != REDIS_OK)
|
||||
return;
|
||||
if (getLongFromObjectOrReply(c,c->argv[4],&dbid,NULL) != REDIS_OK)
|
||||
if (getLongFromObjectOrReply(c,c->argv[5],&timeout,NULL) != REDIS_OK ||
|
||||
getLongFromObjectOrReply(c,c->argv[4],&dbid,NULL) != REDIS_OK)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (timeout <= 0) timeout = 1000;
|
||||
|
||||
/* Check if the key is here. If not we reply with success as there is
|
||||
* nothing to migrate (for instance the key expired in the meantime), but
|
||||
* we include such information in the reply string. */
|
||||
if ((o = lookupKeyRead(c->db,c->argv[3])) == NULL) {
|
||||
/* Check if the keys are here. If at least one key is to migrate, do it
|
||||
* otherwise if all the keys are missing reply with "NOKEY" to signal
|
||||
* the caller there was nothing to migrate. We don't return an error in
|
||||
* this case, since often this is due to a normal condition like the key
|
||||
* expiring in the meantime. */
|
||||
ov = zrealloc(ov,sizeof(robj*)*num_keys);
|
||||
kv = zrealloc(kv,sizeof(robj*)*num_keys);
|
||||
int oi = 0;
|
||||
|
||||
for (j = 0; j < num_keys; j++) {
|
||||
if ((ov[oi] = lookupKeyRead(c->db,c->argv[first_key+j])) != NULL) {
|
||||
kv[oi] = c->argv[first_key+j];
|
||||
oi++;
|
||||
}
|
||||
}
|
||||
num_keys = oi;
|
||||
if (num_keys == 0) {
|
||||
zfree(ov); zfree(kv);
|
||||
addReplySds(c,sdsnew("+NOKEY\r\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
try_again:
|
||||
write_error = 0;
|
||||
|
||||
/* Connect */
|
||||
cs = migrateGetSocket(c,c->argv[1],c->argv[2],timeout);
|
||||
if (cs == NULL) return; /* error sent to the client by migrateGetSocket() */
|
||||
if (cs == NULL) {
|
||||
zfree(ov); zfree(kv);
|
||||
return; /* error sent to the client by migrateGetSocket() */
|
||||
}
|
||||
|
||||
rioInitWithBuffer(&cmd,sdsempty());
|
||||
|
||||
@@ -4609,33 +4742,36 @@ try_again:
|
||||
}
|
||||
|
||||
/* Create RESTORE payload and generate the protocol to call the command. */
|
||||
expireat = getExpire(c->db,c->argv[3]);
|
||||
if (expireat != -1) {
|
||||
ttl = expireat-mstime();
|
||||
if (ttl < 1) ttl = 1;
|
||||
}
|
||||
redisAssertWithInfo(c,NULL,rioWriteBulkCount(&cmd,'*',replace ? 5 : 4));
|
||||
if (server.cluster_enabled)
|
||||
for (j = 0; j < num_keys; j++) {
|
||||
expireat = getExpire(c->db,kv[j]);
|
||||
if (expireat != -1) {
|
||||
ttl = expireat-mstime();
|
||||
if (ttl < 1) ttl = 1;
|
||||
}
|
||||
redisAssertWithInfo(c,NULL,rioWriteBulkCount(&cmd,'*',replace ? 5 : 4));
|
||||
if (server.cluster_enabled)
|
||||
redisAssertWithInfo(c,NULL,
|
||||
rioWriteBulkString(&cmd,"RESTORE-ASKING",14));
|
||||
else
|
||||
redisAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"RESTORE",7));
|
||||
redisAssertWithInfo(c,NULL,sdsEncodedObject(kv[j]));
|
||||
redisAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,kv[j]->ptr,
|
||||
sdslen(kv[j]->ptr)));
|
||||
redisAssertWithInfo(c,NULL,rioWriteBulkLongLong(&cmd,ttl));
|
||||
|
||||
/* Emit the payload argument, that is the serialized object using
|
||||
* the DUMP format. */
|
||||
createDumpPayload(&payload,ov[j]);
|
||||
redisAssertWithInfo(c,NULL,
|
||||
rioWriteBulkString(&cmd,"RESTORE-ASKING",14));
|
||||
else
|
||||
redisAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"RESTORE",7));
|
||||
redisAssertWithInfo(c,NULL,sdsEncodedObject(c->argv[3]));
|
||||
redisAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,c->argv[3]->ptr,
|
||||
sdslen(c->argv[3]->ptr)));
|
||||
redisAssertWithInfo(c,NULL,rioWriteBulkLongLong(&cmd,ttl));
|
||||
rioWriteBulkString(&cmd,payload.io.buffer.ptr,
|
||||
sdslen(payload.io.buffer.ptr)));
|
||||
sdsfree(payload.io.buffer.ptr);
|
||||
|
||||
/* Emit the payload argument, that is the serialized object using
|
||||
* the DUMP format. */
|
||||
createDumpPayload(&payload,o);
|
||||
redisAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,payload.io.buffer.ptr,
|
||||
sdslen(payload.io.buffer.ptr)));
|
||||
sdsfree(payload.io.buffer.ptr);
|
||||
|
||||
/* Add the REPLACE option to the RESTORE command if it was specified
|
||||
* as a MIGRATE option. */
|
||||
if (replace)
|
||||
redisAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"REPLACE",7));
|
||||
/* Add the REPLACE option to the RESTORE command if it was specified
|
||||
* as a MIGRATE option. */
|
||||
if (replace)
|
||||
redisAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"REPLACE",7));
|
||||
}
|
||||
|
||||
/* Transfer the query to the other node in 64K chunks. */
|
||||
errno = 0;
|
||||
@@ -4647,64 +4783,124 @@ try_again:
|
||||
while ((towrite = sdslen(buf)-pos) > 0) {
|
||||
towrite = (towrite > (64*1024) ? (64*1024) : towrite);
|
||||
nwritten = syncWrite(cs->fd,buf+pos,towrite,timeout);
|
||||
if (nwritten != (signed)towrite) goto socket_wr_err;
|
||||
if (nwritten != (signed)towrite) {
|
||||
write_error = 1;
|
||||
goto socket_err;
|
||||
}
|
||||
pos += nwritten;
|
||||
}
|
||||
}
|
||||
|
||||
/* Read back the reply. */
|
||||
{
|
||||
char buf1[1024];
|
||||
char buf2[1024];
|
||||
char buf1[1024]; /* Select reply. */
|
||||
char buf2[1024]; /* Restore reply. */
|
||||
|
||||
/* Read the two replies */
|
||||
if (select && syncReadLine(cs->fd, buf1, sizeof(buf1), timeout) <= 0)
|
||||
goto socket_rd_err;
|
||||
if (syncReadLine(cs->fd, buf2, sizeof(buf2), timeout) <= 0)
|
||||
goto socket_rd_err;
|
||||
/* Read the SELECT reply if needed. */
|
||||
if (select && syncReadLine(cs->fd, buf1, sizeof(buf1), timeout) <= 0)
|
||||
goto socket_err;
|
||||
|
||||
/* Read the RESTORE replies. */
|
||||
int error_from_target = 0;
|
||||
int socket_error = 0;
|
||||
int del_idx = 1; /* Index of the key argument for the replicated DEL op. */
|
||||
|
||||
if (!copy) newargv = zmalloc(sizeof(robj*)*(num_keys+1));
|
||||
|
||||
for (j = 0; j < num_keys; j++) {
|
||||
if (syncReadLine(cs->fd, buf2, sizeof(buf2), timeout) <= 0) {
|
||||
socket_error = 1;
|
||||
break;
|
||||
}
|
||||
if ((select && buf1[0] == '-') || buf2[0] == '-') {
|
||||
/* On error assume that last_dbid is no longer valid. */
|
||||
cs->last_dbid = -1;
|
||||
addReplyErrorFormat(c,"Target instance replied with error: %s",
|
||||
(select && buf1[0] == '-') ? buf1+1 : buf2+1);
|
||||
if (!error_from_target) {
|
||||
cs->last_dbid = -1;
|
||||
addReplyErrorFormat(c,"Target instance replied with error: %s",
|
||||
(select && buf1[0] == '-') ? buf1+1 : buf2+1);
|
||||
error_from_target = 1;
|
||||
}
|
||||
} else {
|
||||
/* Update the last_dbid in migrateCachedSocket */
|
||||
cs->last_dbid = dbid;
|
||||
robj *aux;
|
||||
|
||||
addReply(c,shared.ok);
|
||||
|
||||
if (!copy) {
|
||||
/* No COPY option: remove the local key, signal the change. */
|
||||
dbDelete(c->db,c->argv[3]);
|
||||
signalModifiedKey(c->db,c->argv[3]);
|
||||
dbDelete(c->db,kv[j]);
|
||||
signalModifiedKey(c->db,kv[j]);
|
||||
server.dirty++;
|
||||
|
||||
/* Translate MIGRATE as DEL for replication/AOF. */
|
||||
aux = createStringObject("DEL",3);
|
||||
rewriteClientCommandVector(c,2,aux,c->argv[3]);
|
||||
decrRefCount(aux);
|
||||
/* Populate the argument vector to replace the old one. */
|
||||
newargv[del_idx++] = kv[j];
|
||||
incrRefCount(kv[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* On socket error, if we want to retry, do it now before rewriting the
|
||||
* command vector. We only retry if we are sure nothing was processed
|
||||
* and we failed to read the first reply (j == 0 test). */
|
||||
if (!error_from_target && socket_error && j == 0 && may_retry &&
|
||||
errno != ETIMEDOUT)
|
||||
{
|
||||
goto socket_err; /* A retry is guaranteed because of tested conditions.*/
|
||||
}
|
||||
|
||||
if (!copy) {
|
||||
/* Translate MIGRATE as DEL for replication/AOF. */
|
||||
if (del_idx > 1) {
|
||||
newargv[0] = createStringObject("DEL",3);
|
||||
/* Note that the following call takes ownership of newargv. */
|
||||
replaceClientCommandVector(c,del_idx,newargv);
|
||||
} else {
|
||||
/* No key transfer acknowledged, no need to rewrite as DEL. */
|
||||
zfree(newargv);
|
||||
}
|
||||
newargv = NULL; /* Make it safe to call zfree() on it in the future. */
|
||||
}
|
||||
|
||||
/* If we are here and a socket error happened, we don't want to retry.
|
||||
* Just signal the problem to the client, but only do it if we don't
|
||||
* already queued a different error reported by the destination server. */
|
||||
if (!error_from_target && socket_error) {
|
||||
may_retry = 0;
|
||||
goto socket_err;
|
||||
}
|
||||
|
||||
if (!error_from_target) {
|
||||
/* Success! Update the last_dbid in migrateCachedSocket, so that we can
|
||||
* avoid SELECT the next time if the target DB is the same. Reply +OK. */
|
||||
cs->last_dbid = dbid;
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
/* On error we already sent it in the for loop above, and set
|
||||
* the curretly selected socket to -1 to force SELECT the next time. */
|
||||
}
|
||||
|
||||
sdsfree(cmd.io.buffer.ptr);
|
||||
zfree(ov); zfree(kv); zfree(newargv);
|
||||
if (socket_error) migrateCloseSocket(c->argv[1],c->argv[2]);
|
||||
return;
|
||||
|
||||
socket_wr_err:
|
||||
/* On socket errors we try to close the cached socket and try again.
|
||||
* It is very common for the cached socket to get closed, if just reopening
|
||||
* it works it's a shame to notify the error to the caller. */
|
||||
socket_err:
|
||||
/* Cleanup we want to perform in both the retry and no retry case.
|
||||
* Note: Closing the migrate socket will also force SELECT next time. */
|
||||
sdsfree(cmd.io.buffer.ptr);
|
||||
migrateCloseSocket(c->argv[1],c->argv[2]);
|
||||
if (errno != ETIMEDOUT && retry_num++ == 0) goto try_again;
|
||||
addReplySds(c,
|
||||
sdsnew("-IOERR error or timeout writing to target instance\r\n"));
|
||||
return;
|
||||
zfree(newargv);
|
||||
newargv = NULL; /* This will get reallocated on retry. */
|
||||
|
||||
socket_rd_err:
|
||||
sdsfree(cmd.io.buffer.ptr);
|
||||
migrateCloseSocket(c->argv[1],c->argv[2]);
|
||||
if (errno != ETIMEDOUT && retry_num++ == 0) goto try_again;
|
||||
/* Retry only if it's not a timeout and we never attempted a retry
|
||||
* (or the code jumping here did not set may_retry to zero). */
|
||||
if (errno != ETIMEDOUT && may_retry) {
|
||||
may_retry = 0;
|
||||
goto try_again;
|
||||
}
|
||||
|
||||
/* Cleanup we want to do if no retry is attempted. */
|
||||
zfree(ov); zfree(kv);
|
||||
addReplySds(c,
|
||||
sdsnew("-IOERR error or timeout reading from target node\r\n"));
|
||||
sdscatprintf(sdsempty(),
|
||||
"-IOERR error or timeout %s to target instance\r\n",
|
||||
write_error ? "writing" : "reading"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4764,14 +4960,17 @@ void readwriteCommand(redisClient *c) {
|
||||
* REDIS_CLUSTER_REDIR_CROSS_SLOT if the request contains multiple keys that
|
||||
* don't belong to the same hash slot.
|
||||
*
|
||||
* REDIS_CLUSTER_REDIR_UNSTABLE if the request contains mutliple keys
|
||||
* REDIS_CLUSTER_REDIR_UNSTABLE if the request contains multiple keys
|
||||
* belonging to the same slot, but the slot is not stable (in migration or
|
||||
* importing state, likely because a resharding is in progress).
|
||||
*
|
||||
* REDIS_CLUSTER_REDIR_DOWN_UNBOUND if the request addresses a slot which is
|
||||
* not bound to any node. In this case the cluster global state should be
|
||||
* already "down" but it is fragile to rely on the update of the global state,
|
||||
* so we also handle it here. */
|
||||
* so we also handle it here.
|
||||
*
|
||||
* REDIS_CLUSTER_REDIR_DOWN_STATE if the cluster is down but the user attempts
|
||||
* to execute a command that addresses one or more keys. */
|
||||
clusterNode *getNodeByQuery(redisClient *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot, int *error_code) {
|
||||
clusterNode *n = NULL;
|
||||
robj *firstkey = NULL;
|
||||
@@ -4878,14 +5077,23 @@ clusterNode *getNodeByQuery(redisClient *c, struct redisCommand *cmd, robj **arg
|
||||
}
|
||||
|
||||
/* No key at all in command? then we can serve the request
|
||||
* without redirections or errors. */
|
||||
* without redirections or errors in all the cases. */
|
||||
if (n == NULL) return myself;
|
||||
|
||||
/* Cluster is globally down but we got keys? We can't serve the request. */
|
||||
if (server.cluster->state != REDIS_CLUSTER_OK) {
|
||||
if (error_code) *error_code = REDIS_CLUSTER_REDIR_DOWN_STATE;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Return the hashslot by reference. */
|
||||
if (hashslot) *hashslot = slot;
|
||||
|
||||
/* This request is about a slot we are migrating into another instance?
|
||||
* Then if we have all the keys. */
|
||||
/* MIGRATE always works in the context of the local node if the slot
|
||||
* is open (migrating or importing state). We need to be able to freely
|
||||
* move keys among instances in this case. */
|
||||
if ((migrating_slot || importing_slot) && cmd->proc == migrateCommand)
|
||||
return myself;
|
||||
|
||||
/* If we don't have all the keys and we are migrating the slot, send
|
||||
* an ASK redirection. */
|
||||
|
||||
+7
-2
@@ -23,6 +23,7 @@
|
||||
#define REDIS_CLUSTER_DEFAULT_MIGRATION_BARRIER 1
|
||||
#define REDIS_CLUSTER_MF_TIMEOUT 5000 /* Milliseconds to do a manual failover. */
|
||||
#define REDIS_CLUSTER_MF_PAUSE_MULT 2 /* Master pause manual failover mult. */
|
||||
#define REDIS_CLUSTER_SLAVE_MIGRATION_DELAY 5000 /* Delay for slave migration */
|
||||
|
||||
/* Redirection errors returned by getNodeByQuery(). */
|
||||
#define REDIS_CLUSTER_REDIR_NONE 0 /* Node can serve the request. */
|
||||
@@ -53,7 +54,7 @@ typedef struct clusterLink {
|
||||
#define REDIS_NODE_HANDSHAKE 32 /* We have still to exchange the first ping */
|
||||
#define REDIS_NODE_NOADDR 64 /* We don't know the address of this node */
|
||||
#define REDIS_NODE_MEET 128 /* Send a MEET message to this node */
|
||||
#define REDIS_NODE_PROMOTED 256 /* Master was a slave promoted by failover */
|
||||
#define REDIS_NODE_MIGRATE_TO 256 /* Master elegible for replica migration. */
|
||||
#define REDIS_NODE_NULL_NAME "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
|
||||
#define nodeIsMaster(n) ((n)->flags & REDIS_NODE_MASTER)
|
||||
@@ -87,12 +88,16 @@ typedef struct clusterNode {
|
||||
int numslots; /* Number of slots handled by this node */
|
||||
int numslaves; /* Number of slave nodes, if this is a master */
|
||||
struct clusterNode **slaves; /* pointers to slave nodes */
|
||||
struct clusterNode *slaveof; /* pointer to the master node */
|
||||
struct clusterNode *slaveof; /* pointer to the master node. Note that it
|
||||
may be NULL even if the node is a slave
|
||||
if we don't have the master node in our
|
||||
tables. */
|
||||
mstime_t ping_sent; /* Unix time we sent latest ping */
|
||||
mstime_t pong_received; /* Unix time we received the pong */
|
||||
mstime_t fail_time; /* Unix time when FAIL flag was set */
|
||||
mstime_t voted_time; /* Last time we voted for a slave of this master */
|
||||
mstime_t repl_offset_time; /* Unix time we received offset for this node */
|
||||
mstime_t orphaned_time; /* Starting time of orphaned master condition */
|
||||
long long repl_offset; /* Last known repl offset for this node. */
|
||||
char ip[REDIS_IP_STR_LEN]; /* Latest known IP address of this node */
|
||||
int port; /* Latest known port of this node */
|
||||
|
||||
@@ -294,6 +294,7 @@ void loadServerConfigFromString(char *config) {
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"masterauth") && argc == 2) {
|
||||
zfree(server.masterauth);
|
||||
server.masterauth = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"slave-serve-stale-data") && argc == 2) {
|
||||
if ((server.repl_serve_stale_data = yesnotoi(argv[1])) == -1) {
|
||||
|
||||
@@ -293,13 +293,17 @@ void delCommand(redisClient *c) {
|
||||
addReplyLongLong(c,deleted);
|
||||
}
|
||||
|
||||
/* EXISTS key1 key2 ... key_N.
|
||||
* Return value is the number of keys existing. */
|
||||
void existsCommand(redisClient *c) {
|
||||
expireIfNeeded(c->db,c->argv[1]);
|
||||
if (dbExists(c->db,c->argv[1])) {
|
||||
addReply(c, shared.cone);
|
||||
} else {
|
||||
addReply(c, shared.czero);
|
||||
long long count = 0;
|
||||
int j;
|
||||
|
||||
for (j = 1; j < c->argc; j++) {
|
||||
expireIfNeeded(c->db,c->argv[j]);
|
||||
if (dbExists(c->db,c->argv[j])) count++;
|
||||
}
|
||||
addReplyLongLong(c,count);
|
||||
}
|
||||
|
||||
void selectCommand(redisClient *c) {
|
||||
@@ -710,7 +714,7 @@ void moveCommand(redisClient *c) {
|
||||
robj *o;
|
||||
redisDb *src, *dst;
|
||||
int srcid;
|
||||
long long dbid;
|
||||
long long dbid, expire;
|
||||
|
||||
if (server.cluster_enabled) {
|
||||
addReplyError(c,"MOVE is not allowed in cluster mode");
|
||||
@@ -744,6 +748,7 @@ void moveCommand(redisClient *c) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
expire = getExpire(c->db,c->argv[1]);
|
||||
|
||||
/* Return zero if the key already exists in the target DB */
|
||||
if (lookupKeyWrite(dst,c->argv[1]) != NULL) {
|
||||
@@ -751,6 +756,7 @@ void moveCommand(redisClient *c) {
|
||||
return;
|
||||
}
|
||||
dbAdd(dst,c->argv[1],o);
|
||||
if (expire != -1) setExpire(dst,c->argv[1],expire);
|
||||
incrRefCount(o);
|
||||
|
||||
/* OK! key moved, free the entry in the source DB */
|
||||
@@ -1125,6 +1131,33 @@ int *sortGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
|
||||
return keys;
|
||||
}
|
||||
|
||||
int *migrateGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
int i, num, first, *keys;
|
||||
REDIS_NOTUSED(cmd);
|
||||
|
||||
/* Assume the obvious form. */
|
||||
first = 3;
|
||||
num = 1;
|
||||
|
||||
/* But check for the extended one with the KEYS option. */
|
||||
if (argc > 6) {
|
||||
for (i = 6; i < argc; i++) {
|
||||
if (!strcasecmp(argv[i]->ptr,"keys") &&
|
||||
sdslen(argv[3]->ptr) == 0)
|
||||
{
|
||||
first = i+1;
|
||||
num = argc-first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keys = zmalloc(sizeof(int)*num);
|
||||
for (i = 0; i < num; i++) keys[i] = first+i;
|
||||
*numkeys = num;
|
||||
return keys;
|
||||
}
|
||||
|
||||
/* Slot to Key API. This is used by Redis Cluster in order to obtain in
|
||||
* a fast way a key that belongs to a specified hash slot. This is useful
|
||||
* while rehashing the cluster. */
|
||||
|
||||
+5
-1
@@ -415,7 +415,7 @@ void _redisAssertPrintClientInfo(redisClient *c) {
|
||||
if (c->argv[j]->type == REDIS_STRING && sdsEncodedObject(c->argv[j])) {
|
||||
arg = (char*) c->argv[j]->ptr;
|
||||
} else {
|
||||
snprintf(buf,sizeof(buf),"Object type: %d, encoding: %d",
|
||||
snprintf(buf,sizeof(buf),"Object type: %u, encoding: %u",
|
||||
c->argv[j]->type, c->argv[j]->encoding);
|
||||
arg = buf;
|
||||
}
|
||||
@@ -811,6 +811,10 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
bugReportStart();
|
||||
redisLog(REDIS_WARNING,
|
||||
" Redis %s crashed by signal: %d", REDIS_VERSION, sig);
|
||||
if (sig == SIGSEGV) {
|
||||
redisLog(REDIS_WARNING,
|
||||
" SIGSEGV caused by address: %p", (void*)info->si_addr);
|
||||
}
|
||||
redisLog(REDIS_WARNING,
|
||||
" Failed assertion: %s (%s:%d)", server.assert_failed,
|
||||
server.assert_file, server.assert_line);
|
||||
|
||||
+180
-8
@@ -1,4 +1,4 @@
|
||||
/* Automatically generated by utils/generate-command-help.rb, do not edit. */
|
||||
/* Automatically generated by generate-command-help.rb, do not edit. */
|
||||
|
||||
#ifndef __REDIS_HELP_H
|
||||
#define __REDIS_HELP_H
|
||||
@@ -15,7 +15,9 @@ static char *commandGroups[] = {
|
||||
"connection",
|
||||
"server",
|
||||
"scripting",
|
||||
"hyperloglog"
|
||||
"hyperloglog",
|
||||
"cluster",
|
||||
"geo"
|
||||
};
|
||||
|
||||
struct commandHelp {
|
||||
@@ -46,7 +48,7 @@ struct commandHelp {
|
||||
9,
|
||||
"1.0.0" },
|
||||
{ "BITCOUNT",
|
||||
"key [start] [end]",
|
||||
"key [start end]",
|
||||
"Count set bits in a string",
|
||||
1,
|
||||
"2.6.0" },
|
||||
@@ -81,7 +83,7 @@ struct commandHelp {
|
||||
9,
|
||||
"2.6.9" },
|
||||
{ "CLIENT KILL",
|
||||
"ip:port",
|
||||
"[ip:port] [ID client-id] [TYPE normal|slave|pubsub] [ADDR ip:port] [SKIPME yes/no]",
|
||||
"Kill the connection of a client",
|
||||
9,
|
||||
"2.4.0" },
|
||||
@@ -100,6 +102,116 @@ struct commandHelp {
|
||||
"Set the current connection name",
|
||||
9,
|
||||
"2.6.9" },
|
||||
{ "CLUSTER ADDSLOTS",
|
||||
"slot [slot ...]",
|
||||
"Assign new hash slots to receiving node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER COUNT-FAILURE-REPORTS",
|
||||
"node-id",
|
||||
"Return the number of failure reports active for a given node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER COUNTKEYSINSLOT",
|
||||
"slot",
|
||||
"Return the number of local keys in the specified hash slot",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER DELSLOTS",
|
||||
"slot [slot ...]",
|
||||
"Set hash slots as unbound in receiving node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER FAILOVER",
|
||||
"[FORCE|TAKEOVER]",
|
||||
"Forces a slave to perform a manual failover of its master.",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER FORGET",
|
||||
"node-id",
|
||||
"Remove a node from the nodes table",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER GETKEYSINSLOT",
|
||||
"slot count",
|
||||
"Return local key names in the specified hash slot",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER INFO",
|
||||
"-",
|
||||
"Provides info about Redis Cluster node state",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER KEYSLOT",
|
||||
"key",
|
||||
"Returns the hash slot of the specified key",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER MEET",
|
||||
"ip port",
|
||||
"Force a node cluster to handshake with another node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER NODES",
|
||||
"-",
|
||||
"Get Cluster config for the node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER REPLICATE",
|
||||
"node-id",
|
||||
"Reconfigure a node as a slave of the specified master node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER RESET",
|
||||
"[HARD|SOFT]",
|
||||
"Reset a Redis Cluster node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER SAVECONFIG",
|
||||
"-",
|
||||
"Forces the node to save cluster state on disk",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER SET-CONFIG-EPOCH",
|
||||
"config-epoch",
|
||||
"Set the configuration epoch in a new node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER SETSLOT",
|
||||
"slot IMPORTING|MIGRATING|STABLE|NODE [node-id]",
|
||||
"Bind an hash slot to a specific node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER SLAVES",
|
||||
"node-id",
|
||||
"List slave nodes of the specified master node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER SLOTS",
|
||||
"-",
|
||||
"Get array of Cluster slot to node mappings",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "COMMAND",
|
||||
"-",
|
||||
"Get array of Redis command details",
|
||||
9,
|
||||
"2.8.13" },
|
||||
{ "COMMAND COUNT",
|
||||
"-",
|
||||
"Get total number of Redis commands",
|
||||
9,
|
||||
"2.8.13" },
|
||||
{ "COMMAND GETKEYS",
|
||||
"-",
|
||||
"Extract keys given a full Redis command",
|
||||
9,
|
||||
"2.8.13" },
|
||||
{ "COMMAND INFO",
|
||||
"command-name [command-name ...]",
|
||||
"Get array of specific Redis command details",
|
||||
9,
|
||||
"2.8.13" },
|
||||
{ "CONFIG GET",
|
||||
"parameter",
|
||||
"Get the value of a configuration parameter",
|
||||
@@ -181,7 +293,7 @@ struct commandHelp {
|
||||
7,
|
||||
"1.2.0" },
|
||||
{ "EXISTS",
|
||||
"key",
|
||||
"key [key ...]",
|
||||
"Determine if a key exists",
|
||||
0,
|
||||
"1.0.0" },
|
||||
@@ -205,6 +317,36 @@ struct commandHelp {
|
||||
"Remove all keys from the current database",
|
||||
9,
|
||||
"1.0.0" },
|
||||
{ "GEOADD",
|
||||
"key longitude latitude member [longitude latitude member ...]",
|
||||
"Add one or more geospatial items in the geospatial index represented using a sorted set",
|
||||
13,
|
||||
"" },
|
||||
{ "GEODIST",
|
||||
"key member1 member2 [unit]",
|
||||
"Returns the distance between two members of a geospatial index",
|
||||
13,
|
||||
"" },
|
||||
{ "GEOHASH",
|
||||
"key member [member ...]",
|
||||
"Returns members of a geospatial index as standard geohash strings",
|
||||
13,
|
||||
"" },
|
||||
{ "GEOPOS",
|
||||
"key member [member ...]",
|
||||
"Returns longitude and latitude of members of a geospatial index",
|
||||
13,
|
||||
"" },
|
||||
{ "GEORADIUS",
|
||||
"key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]",
|
||||
"Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a point",
|
||||
13,
|
||||
"" },
|
||||
{ "GEORADIUSBYMEMBER",
|
||||
"key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]",
|
||||
"Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a member",
|
||||
13,
|
||||
"" },
|
||||
{ "GET",
|
||||
"key",
|
||||
"Get the value of a key",
|
||||
@@ -290,6 +432,11 @@ struct commandHelp {
|
||||
"Set the value of a hash field, only if the field does not exist",
|
||||
5,
|
||||
"2.0.0" },
|
||||
{ "HSTRLEN",
|
||||
"key field",
|
||||
"Get the length of the value of a hash field",
|
||||
5,
|
||||
"3.2.0" },
|
||||
{ "HVALS",
|
||||
"key",
|
||||
"Get all the values in a hash",
|
||||
@@ -490,6 +637,16 @@ struct commandHelp {
|
||||
"Return a random key from the keyspace",
|
||||
0,
|
||||
"1.0.0" },
|
||||
{ "READONLY",
|
||||
"-",
|
||||
"Enables read queries for a connection to a cluster slave node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "READWRITE",
|
||||
"-",
|
||||
"Disables read queries for a connection to a cluster slave node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "RENAME",
|
||||
"key newkey",
|
||||
"Rename a key",
|
||||
@@ -501,10 +658,15 @@ struct commandHelp {
|
||||
0,
|
||||
"1.0.0" },
|
||||
{ "RESTORE",
|
||||
"key ttl serialized-value",
|
||||
"key ttl serialized-value [REPLACE]",
|
||||
"Create a key using the provided serialized value, previously obtained using DUMP.",
|
||||
0,
|
||||
"2.6.0" },
|
||||
{ "ROLE",
|
||||
"-",
|
||||
"Return the role of the instance in the context of replication",
|
||||
9,
|
||||
"2.8.12" },
|
||||
{ "RPOP",
|
||||
"key",
|
||||
"Remove and get the last element in a list",
|
||||
@@ -512,7 +674,7 @@ struct commandHelp {
|
||||
"1.0.0" },
|
||||
{ "RPOPLPUSH",
|
||||
"source destination",
|
||||
"Remove the last element in a list, append it to another list and return it",
|
||||
"Remove the last element in a list, prepend it to another list and return it",
|
||||
2,
|
||||
"1.2.0" },
|
||||
{ "RPUSH",
|
||||
@@ -720,13 +882,18 @@ struct commandHelp {
|
||||
"Forget about all watched keys",
|
||||
7,
|
||||
"2.2.0" },
|
||||
{ "WAIT",
|
||||
"numslaves timeout",
|
||||
"Wait for the synchronous replication of all the write commands sent in the context of the current connection",
|
||||
0,
|
||||
"3.0.0" },
|
||||
{ "WATCH",
|
||||
"key [key ...]",
|
||||
"Watch the given keys to determine execution of the MULTI/EXEC block",
|
||||
7,
|
||||
"2.2.0" },
|
||||
{ "ZADD",
|
||||
"key score member [score member ...]",
|
||||
"key [NX|XX] [CH] [INCR] score member [score member ...]",
|
||||
"Add one or more members to a sorted set, or update its score if it already exists",
|
||||
4,
|
||||
"1.2.0" },
|
||||
@@ -800,6 +967,11 @@ struct commandHelp {
|
||||
"Return a range of members in a sorted set, by index, with scores ordered from high to low",
|
||||
4,
|
||||
"1.2.0" },
|
||||
{ "ZREVRANGEBYLEX",
|
||||
"key max min [LIMIT offset count]",
|
||||
"Return a range of members in a sorted set, by lexicographical range, ordered from higher to lower strings.",
|
||||
4,
|
||||
"2.8.9" },
|
||||
{ "ZREVRANGEBYSCORE",
|
||||
"key max min [WITHSCORES] [LIMIT offset count]",
|
||||
"Return a range of members in a sorted set, by score, with scores ordered from high to low",
|
||||
|
||||
+36
-17
@@ -105,6 +105,7 @@ redisClient *createClient(int fd) {
|
||||
c->repl_ack_off = 0;
|
||||
c->repl_ack_time = 0;
|
||||
c->slave_listening_port = 0;
|
||||
c->slave_capa = SLAVE_CAPA_NONE;
|
||||
c->reply = listCreate();
|
||||
c->reply_bytes = 0;
|
||||
c->obuf_soft_limit_reached_time = 0;
|
||||
@@ -666,20 +667,6 @@ void disconnectSlaves(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* This function is called when the slave lose the connection with the
|
||||
* master into an unexpected way. */
|
||||
void replicationHandleMasterDisconnection(void) {
|
||||
server.master = NULL;
|
||||
server.repl_state = REDIS_REPL_CONNECT;
|
||||
server.repl_down_since = server.unixtime;
|
||||
/* We lost connection with our master, force our slaves to resync
|
||||
* with us as well to load the new data set.
|
||||
*
|
||||
* If server.masterhost is NULL the user called SLAVEOF NO ONE so
|
||||
* slave resync is not needed. */
|
||||
if (server.masterhost != NULL) disconnectSlaves();
|
||||
}
|
||||
|
||||
void freeClient(redisClient *c) {
|
||||
listNode *ln;
|
||||
|
||||
@@ -1160,6 +1147,9 @@ void processInputBuffer(redisClient *c) {
|
||||
/* Only reset the client when the command was executed. */
|
||||
if (processCommand(c) == REDIS_OK)
|
||||
resetClient(c);
|
||||
/* freeMemoryIfNeeded may flush slave output buffers. This may result
|
||||
* into a slave, that may be the active client, to be freed. */
|
||||
if (server.current_client == NULL) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1540,16 +1530,39 @@ void rewriteClientCommandVector(redisClient *c, int argc, ...) {
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/* Completely replace the client command vector with the provided one. */
|
||||
void replaceClientCommandVector(redisClient *c, int argc, robj **argv) {
|
||||
freeClientArgv(c);
|
||||
zfree(c->argv);
|
||||
c->argv = argv;
|
||||
c->argc = argc;
|
||||
c->cmd = lookupCommandOrOriginal(c->argv[0]->ptr);
|
||||
redisAssertWithInfo(c,NULL,c->cmd != NULL);
|
||||
}
|
||||
|
||||
/* Rewrite a single item in the command vector.
|
||||
* The new val ref count is incremented, and the old decremented. */
|
||||
* The new val ref count is incremented, and the old decremented.
|
||||
*
|
||||
* It is possible to specify an argument over the current size of the
|
||||
* argument vector: in this case the array of objects gets reallocated
|
||||
* and c->argc set to the max value. However it's up to the caller to
|
||||
*
|
||||
* 1. Make sure there are no "holes" and all the arguments are set.
|
||||
* 2. If the original argument vector was longer than the one we
|
||||
* want to end with, it's up to the caller to set c->argc and
|
||||
* free the no longer used objects on c->argv. */
|
||||
void rewriteClientCommandArgument(redisClient *c, int i, robj *newval) {
|
||||
robj *oldval;
|
||||
|
||||
redisAssertWithInfo(c,NULL,i < c->argc);
|
||||
if (i >= c->argc) {
|
||||
c->argv = zrealloc(c->argv,sizeof(robj*)*(i+1));
|
||||
c->argc = i+1;
|
||||
c->argv[i] = NULL;
|
||||
}
|
||||
oldval = c->argv[i];
|
||||
c->argv[i] = newval;
|
||||
incrRefCount(newval);
|
||||
decrRefCount(oldval);
|
||||
if (oldval) decrRefCount(oldval);
|
||||
|
||||
/* If this is the command name make sure to fix c->cmd. */
|
||||
if (i == 0) {
|
||||
@@ -1679,6 +1692,12 @@ void flushSlavesOutputBuffers(void) {
|
||||
redisClient *slave = listNodeValue(ln);
|
||||
int events;
|
||||
|
||||
/* Note that the following will not flush output buffers of slaves
|
||||
* in STATE_ONLINE but having put_online_on_ack set to true: in this
|
||||
* case the writable event is never installed, since the purpose
|
||||
* of put_online_on_ack is to postpone the moment it is installed.
|
||||
* This is what we want since slaves in this state should not receive
|
||||
* writes before the first ACK. */
|
||||
events = aeGetFileEvents(server.el,slave->fd);
|
||||
if (events & AE_WRITABLE &&
|
||||
slave->replstate == REDIS_REPL_ONLINE &&
|
||||
|
||||
@@ -755,9 +755,9 @@ int rdbSave(char *filename) {
|
||||
return REDIS_OK;
|
||||
|
||||
werr:
|
||||
redisLog(REDIS_WARNING,"Write error saving DB on disk: %s", strerror(errno));
|
||||
fclose(fp);
|
||||
unlink(tmpfile);
|
||||
redisLog(REDIS_WARNING,"Write error saving DB on disk: %s", strerror(errno));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
@@ -1420,7 +1420,7 @@ int rdbSaveToSlavesSockets(void) {
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START) {
|
||||
clientids[numfds] = slave->id;
|
||||
fds[numfds++] = slave->fd;
|
||||
slave->replstate = REDIS_REPL_WAIT_BGSAVE_END;
|
||||
replicationSetupSlaveForFullResync(slave,getPsyncInitialOffset());
|
||||
/* Put the socket in non-blocking mode to simplify RDB transfer.
|
||||
* We'll restore it when the children returns (since duped socket
|
||||
* will share the O_NONBLOCK attribute with the parent). */
|
||||
@@ -1498,27 +1498,43 @@ int rdbSaveToSlavesSockets(void) {
|
||||
exitFromChild((retval == REDIS_OK) ? 0 : 1);
|
||||
} else {
|
||||
/* Parent */
|
||||
zfree(clientids); /* Not used by parent. Free ASAP. */
|
||||
server.stat_fork_time = ustime()-start;
|
||||
server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
|
||||
latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
|
||||
if (childpid == -1) {
|
||||
redisLog(REDIS_WARNING,"Can't save in background: fork: %s",
|
||||
strerror(errno));
|
||||
zfree(fds);
|
||||
|
||||
/* Undo the state change. The caller will perform cleanup on
|
||||
* all the slaves in BGSAVE_START state, but an early call to
|
||||
* replicationSetupSlaveForFullResync() turned it into BGSAVE_END */
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *slave = ln->value;
|
||||
int j;
|
||||
|
||||
for (j = 0; j < numfds; j++) {
|
||||
if (slave->id == clientids[j]) {
|
||||
slave->replstate = REDIS_REPL_WAIT_BGSAVE_START;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
close(pipefds[0]);
|
||||
close(pipefds[1]);
|
||||
return REDIS_ERR;
|
||||
} else {
|
||||
redisLog(REDIS_NOTICE,"Background RDB transfer started by pid %d",
|
||||
childpid);
|
||||
server.rdb_save_time_start = time(NULL);
|
||||
server.rdb_child_pid = childpid;
|
||||
server.rdb_child_type = REDIS_RDB_CHILD_TYPE_SOCKET;
|
||||
updateDictResizePolicy();
|
||||
}
|
||||
redisLog(REDIS_NOTICE,"Background RDB transfer started by pid %d",childpid);
|
||||
server.rdb_save_time_start = time(NULL);
|
||||
server.rdb_child_pid = childpid;
|
||||
server.rdb_child_type = REDIS_RDB_CHILD_TYPE_SOCKET;
|
||||
updateDictResizePolicy();
|
||||
zfree(clientids);
|
||||
zfree(fds);
|
||||
return REDIS_OK;
|
||||
return (childpid == -1) ? REDIS_ERR : REDIS_OK;
|
||||
}
|
||||
return REDIS_OK; /* unreached */
|
||||
return REDIS_OK; /* Unreached. */
|
||||
}
|
||||
|
||||
void saveCommand(redisClient *c) {
|
||||
|
||||
+14
-7
@@ -459,7 +459,7 @@ static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
|
||||
_prefix = sdscat(sdsnew(prefix),_prefixlen);
|
||||
|
||||
/* Setup prefix format for every entry */
|
||||
snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%dd) ",idxlen);
|
||||
snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%ud) ",idxlen);
|
||||
|
||||
for (i = 0; i < r->elements; i++) {
|
||||
/* Don't use the prefix for the first element, as the parent
|
||||
@@ -598,7 +598,7 @@ static int cliReadReply(int output_raw_strings) {
|
||||
p = strchr(s+1,' '); /* MOVED[S]3999[P]127.0.0.1:6381 */
|
||||
*p = '\0';
|
||||
slot = atoi(s+1);
|
||||
s = strchr(p+1,':'); /* MOVED 3999[P]127.0.0.1[S]6381 */
|
||||
s = strrchr(p+1,':'); /* MOVED 3999[P]127.0.0.1[S]6381 */
|
||||
*s = '\0';
|
||||
sdsfree(config.hostip);
|
||||
config.hostip = sdsnew(p+1);
|
||||
@@ -916,7 +916,8 @@ static void usage(void) {
|
||||
" (Note: when using --eval the comma separates KEYS[] from ARGV[] items)\n"
|
||||
"\n"
|
||||
"When no command is given, redis-cli starts in interactive mode.\n"
|
||||
"Type \"help\" in interactive mode for information on available commands.\n"
|
||||
"Type \"help\" in interactive mode for information on available commands\n"
|
||||
"and settings.\n"
|
||||
"\n",
|
||||
version, REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
|
||||
sdsfree(version);
|
||||
@@ -986,6 +987,7 @@ static void repl(void) {
|
||||
if (line[0] != '\0') {
|
||||
argv = sdssplitargs(line,&argc);
|
||||
if (history) linenoiseHistoryAdd(line);
|
||||
printf("HISTORY: %s\n", historyfile);
|
||||
if (historyfile) linenoiseHistorySave(historyfile);
|
||||
|
||||
if (argv == NULL) {
|
||||
@@ -1397,6 +1399,7 @@ static void getRDB(void) {
|
||||
* Bulk import (pipe) mode
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
#define PIPEMODE_WRITE_LOOP_MAX_BYTES (128*1024)
|
||||
static void pipeMode(void) {
|
||||
int fd = context->fd;
|
||||
long long errors = 0, replies = 0, obuf_len = 0, obuf_pos = 0;
|
||||
@@ -1473,6 +1476,8 @@ static void pipeMode(void) {
|
||||
|
||||
/* Handle the writable state: we can send protocol to the server. */
|
||||
if (mask & AE_WRITABLE) {
|
||||
ssize_t loop_nwritten = 0;
|
||||
|
||||
while(1) {
|
||||
/* Transfer current buffer to server. */
|
||||
if (obuf_len != 0) {
|
||||
@@ -1489,6 +1494,7 @@ static void pipeMode(void) {
|
||||
}
|
||||
obuf_len -= nwritten;
|
||||
obuf_pos += nwritten;
|
||||
loop_nwritten += nwritten;
|
||||
if (obuf_len != 0) break; /* Can't accept more data. */
|
||||
}
|
||||
/* If buffer is empty, load from stdin. */
|
||||
@@ -1524,7 +1530,8 @@ static void pipeMode(void) {
|
||||
obuf_pos = 0;
|
||||
}
|
||||
}
|
||||
if (obuf_len == 0 && eof) break;
|
||||
if ((obuf_len == 0 && eof) ||
|
||||
loop_nwritten > PIPEMODE_WRITE_LOOP_MAX_BYTES) break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1582,7 +1589,7 @@ static redisReply *sendScan(unsigned long long *it) {
|
||||
assert(reply->element[1]->type == REDIS_REPLY_ARRAY);
|
||||
|
||||
/* Update iterator */
|
||||
*it = atoi(reply->element[0]->str);
|
||||
*it = strtoull(reply->element[0]->str, NULL, 10);
|
||||
|
||||
return reply;
|
||||
}
|
||||
@@ -1873,7 +1880,7 @@ void bytesToHuman(char *s, long long n) {
|
||||
}
|
||||
if (n < 1024) {
|
||||
/* Bytes */
|
||||
sprintf(s,"%lluB",n);
|
||||
sprintf(s,"%lldB",n);
|
||||
return;
|
||||
} else if (n < (1024*1024)) {
|
||||
d = (double)n/(1024);
|
||||
@@ -2154,7 +2161,7 @@ static void intrinsicLatencyMode(void) {
|
||||
}
|
||||
|
||||
double avg_us = (double)run_time/runs;
|
||||
double avg_ns = avg_us * 10e3;
|
||||
double avg_ns = avg_us * 1e3;
|
||||
if (force_cancel_loop || end > test_end) {
|
||||
printf("\n%lld total runs "
|
||||
"(avg latency: "
|
||||
|
||||
+380
-57
@@ -25,6 +25,11 @@ require 'rubygems'
|
||||
require 'redis'
|
||||
|
||||
ClusterHashSlots = 16384
|
||||
MigrateDefaultTimeout = 60000
|
||||
MigrateDefaultPipeline = 10
|
||||
RebalanceDefaultThreshold = 2
|
||||
|
||||
$verbose = false
|
||||
|
||||
def xputs(s)
|
||||
case s[0..2]
|
||||
@@ -32,6 +37,8 @@ def xputs(s)
|
||||
color="29;1"
|
||||
when "[ER"
|
||||
color="31;1"
|
||||
when "[WA"
|
||||
color="31;1"
|
||||
when "[OK"
|
||||
color="32"
|
||||
when "[FA","***"
|
||||
@@ -86,7 +93,7 @@ class ClusterNode
|
||||
|
||||
def connect(o={})
|
||||
return if @r
|
||||
print "Connecting to node #{self}: "
|
||||
print "Connecting to node #{self}: " if $verbose
|
||||
STDOUT.flush
|
||||
begin
|
||||
@r = Redis.new(:host => @info[:host], :port => @info[:port], :timeout => 60)
|
||||
@@ -96,7 +103,7 @@ class ClusterNode
|
||||
exit 1 if o[:abort]
|
||||
@r = nil
|
||||
end
|
||||
xputs "OK"
|
||||
xputs "OK" if $verbose
|
||||
end
|
||||
|
||||
def assert_cluster
|
||||
@@ -288,6 +295,7 @@ class RedisTrib
|
||||
@nodes = []
|
||||
@fix = false
|
||||
@errors = []
|
||||
@timeout = MigrateDefaultTimeout
|
||||
end
|
||||
|
||||
def check_arity(req_args, num_args)
|
||||
@@ -302,11 +310,16 @@ class RedisTrib
|
||||
@nodes << node
|
||||
end
|
||||
|
||||
def reset_nodes
|
||||
@nodes = []
|
||||
end
|
||||
|
||||
def cluster_error(msg)
|
||||
@errors << msg
|
||||
xputs msg
|
||||
end
|
||||
|
||||
# Return the node with the specified ID or Nil.
|
||||
def get_node_by_name(name)
|
||||
@nodes.each{|n|
|
||||
return n if n.info[:name] == name.downcase
|
||||
@@ -314,6 +327,21 @@ class RedisTrib
|
||||
return nil
|
||||
end
|
||||
|
||||
# Like get_node_by_name but the specified name can be just the first
|
||||
# part of the node ID as long as the prefix in unique across the
|
||||
# cluster.
|
||||
def get_node_by_abbreviated_name(name)
|
||||
l = name.length
|
||||
candidates = []
|
||||
@nodes.each{|n|
|
||||
if n.info[:name][0...l] == name.downcase
|
||||
candidates << n
|
||||
end
|
||||
}
|
||||
return nil if candidates.length != 1
|
||||
candidates[0]
|
||||
end
|
||||
|
||||
# This function returns the master that has the least number of replicas
|
||||
# in the cluster. If there are multiple masters with the same smaller
|
||||
# number of replicas, one at random is returned.
|
||||
@@ -325,14 +353,30 @@ class RedisTrib
|
||||
sorted[0]
|
||||
end
|
||||
|
||||
def check_cluster
|
||||
def check_cluster(opt={})
|
||||
xputs ">>> Performing Cluster Check (using node #{@nodes[0]})"
|
||||
show_nodes
|
||||
show_nodes if !opt[:quiet]
|
||||
check_config_consistency
|
||||
check_open_slots
|
||||
check_slots_coverage
|
||||
end
|
||||
|
||||
def show_cluster_info
|
||||
masters = 0
|
||||
keys = 0
|
||||
@nodes.each{|n|
|
||||
if n.has_flag?("master")
|
||||
puts "#{n} (#{n.info[:name][0...8]}...) -> #{n.r.dbsize} keys | #{n.slots.length} slots | "+
|
||||
"#{n.info[:replicas].length} slaves."
|
||||
masters += 1
|
||||
keys += n.r.dbsize
|
||||
end
|
||||
}
|
||||
xputs "[OK] #{keys} keys in #{masters} masters."
|
||||
keys_per_slot = sprintf("%.2f",keys/16384.0)
|
||||
puts "#{keys_per_slot} keys per slot on average."
|
||||
end
|
||||
|
||||
# Merge slots of every known node. If the resulting slots are equal
|
||||
# to ClusterHashSlots, then all slots are served.
|
||||
def covered_slots
|
||||
@@ -363,7 +407,8 @@ class RedisTrib
|
||||
cluster_error \
|
||||
"[WARNING] Node #{n} has slots in migrating state (#{n.info[:migrating].keys.join(",")})."
|
||||
open_slots += n.info[:migrating].keys
|
||||
elsif n.info[:importing].size > 0
|
||||
end
|
||||
if n.info[:importing].size > 0
|
||||
cluster_error \
|
||||
"[WARNING] Node #{n} has slots in importing state (#{n.info[:importing].keys.join(",")})."
|
||||
open_slots += n.info[:importing].keys
|
||||
@@ -381,6 +426,7 @@ class RedisTrib
|
||||
def nodes_with_keys_in_slot(slot)
|
||||
nodes = []
|
||||
@nodes.each{|n|
|
||||
next if n.has_flag?("slave")
|
||||
nodes << n if n.r.cluster("getkeysinslot",slot,1).length > 0
|
||||
}
|
||||
nodes
|
||||
@@ -399,7 +445,7 @@ class RedisTrib
|
||||
not_covered.each{|slot|
|
||||
nodes = nodes_with_keys_in_slot(slot)
|
||||
slots[slot] = nodes
|
||||
xputs "Slot #{slot} has keys in #{nodes.length} nodes: #{nodes.join}"
|
||||
xputs "Slot #{slot} has keys in #{nodes.length} nodes: #{nodes.join(", ")}"
|
||||
}
|
||||
|
||||
none = slots.select {|k,v| v.length == 0}
|
||||
@@ -435,26 +481,50 @@ class RedisTrib
|
||||
xputs multi.keys.join(",")
|
||||
yes_or_die "Fix these slots by moving keys into a single node?"
|
||||
multi.each{|slot,nodes|
|
||||
xputs ">>> Covering slot #{slot} moving keys to #{nodes[0]}"
|
||||
# TODO
|
||||
# 1) Set all nodes as "MIGRATING" for this slot, so that we
|
||||
# can access keys in the hash slot using ASKING.
|
||||
# 2) Move everything to node[0]
|
||||
# 3) Clear MIGRATING from nodes, and ADDSLOTS the slot to
|
||||
# node[0].
|
||||
raise "TODO: Work in progress"
|
||||
target = get_node_with_most_keys_in_slot(nodes,slot)
|
||||
xputs ">>> Covering slot #{slot} moving keys to #{target}"
|
||||
|
||||
target.r.cluster('addslots',slot)
|
||||
target.r.cluster('setslot',slot,'stable')
|
||||
nodes.each{|src|
|
||||
next if src == target
|
||||
# Set the source node in 'importing' state (even if we will
|
||||
# actually migrate keys away) in order to avoid receiving
|
||||
# redirections for MIGRATE.
|
||||
src.r.cluster('setslot',slot,'importing',target.info[:name])
|
||||
move_slot(src,target,slot,:dots=>true,:fix=>true,:cold=>true)
|
||||
src.r.cluster('setslot',slot,'stable')
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# Return the owner of the specified slot
|
||||
def get_slot_owner(slot)
|
||||
def get_slot_owners(slot)
|
||||
owners = []
|
||||
@nodes.each{|n|
|
||||
next if n.has_flag?("slave")
|
||||
n.slots.each{|s,_|
|
||||
return n if s == slot
|
||||
owners << n if s == slot
|
||||
}
|
||||
}
|
||||
nil
|
||||
owners
|
||||
end
|
||||
|
||||
# Return the node, among 'nodes' with the greatest number of keys
|
||||
# in the specified slot.
|
||||
def get_node_with_most_keys_in_slot(nodes,slot)
|
||||
best = nil
|
||||
best_numkeys = 0
|
||||
@nodes.each{|n|
|
||||
next if n.has_flag?("slave")
|
||||
numkeys = n.r.cluster("countkeysinslot",slot)
|
||||
if numkeys > best_numkeys || best == nil
|
||||
best = n
|
||||
best_numkeys = numkeys
|
||||
end
|
||||
}
|
||||
return best
|
||||
end
|
||||
|
||||
# Slot 'slot' was found to be in importing or migrating state in one or
|
||||
@@ -465,16 +535,8 @@ class RedisTrib
|
||||
|
||||
# Try to obtain the current slot owner, according to the current
|
||||
# nodes configuration.
|
||||
owner = get_slot_owner(slot)
|
||||
|
||||
# If there is no slot owner, set as owner the slot with the biggest
|
||||
# number of keys, among the set of migrating / importing nodes.
|
||||
if !owner
|
||||
xputs "*** Fix me, some work to do here."
|
||||
# Select owner...
|
||||
# Use ADDSLOTS to assign the slot.
|
||||
exit 1
|
||||
end
|
||||
owners = get_slot_owners(slot)
|
||||
owner = owners[0] if owners.length == 1
|
||||
|
||||
migrating = []
|
||||
importing = []
|
||||
@@ -492,20 +554,77 @@ class RedisTrib
|
||||
puts "Set as migrating in: #{migrating.join(",")}"
|
||||
puts "Set as importing in: #{importing.join(",")}"
|
||||
|
||||
# If there is no slot owner, set as owner the slot with the biggest
|
||||
# number of keys, among the set of migrating / importing nodes.
|
||||
if !owner
|
||||
xputs ">>> Nobody claims ownership, selecting an owner..."
|
||||
owner = get_node_with_most_keys_in_slot(@nodes,slot)
|
||||
|
||||
# If we still don't have an owner, we can't fix it.
|
||||
if !owner
|
||||
xputs "[ERR] Can't select a slot owner. Impossible to fix."
|
||||
exit 1
|
||||
end
|
||||
|
||||
# Use ADDSLOTS to assign the slot.
|
||||
puts "*** Configuring #{owner} as the slot owner"
|
||||
owner.r.cluster("setslot",slot,"stable")
|
||||
owner.r.cluster("addslots",slot)
|
||||
# Make sure this information will propagate. Not strictly needed
|
||||
# since there is no past owner, so all the other nodes will accept
|
||||
# whatever epoch this node will claim the slot with.
|
||||
owner.r.cluster("bumpepoch")
|
||||
|
||||
# Remove the owner from the list of migrating/importing
|
||||
# nodes.
|
||||
migrating.delete(owner)
|
||||
importing.delete(owner)
|
||||
end
|
||||
|
||||
# If there are multiple owners of the slot, we need to fix it
|
||||
# so that a single node is the owner and all the other nodes
|
||||
# are in importing state. Later the fix can be handled by one
|
||||
# of the base cases above.
|
||||
#
|
||||
# Note that this case also covers multiple nodes having the slot
|
||||
# in migrating state, since migrating is a valid state only for
|
||||
# slot owners.
|
||||
if owners.length > 1
|
||||
owner = get_node_with_most_keys_in_slot(owners,slot)
|
||||
owners.each{|n|
|
||||
next if n == owner
|
||||
n.r.cluster('delslots',slot)
|
||||
n.r.cluster('setslot',slot,'importing',owner.info[:name])
|
||||
importing.delete(n) # Avoid duplciates
|
||||
importing << n
|
||||
}
|
||||
owner.r.cluster('bumpepoch')
|
||||
end
|
||||
|
||||
# Case 1: The slot is in migrating state in one slot, and in
|
||||
# importing state in 1 slot. That's trivial to address.
|
||||
if migrating.length == 1 && importing.length == 1
|
||||
move_slot(migrating[0],importing[0],slot,:verbose=>true,:fix=>true)
|
||||
move_slot(migrating[0],importing[0],slot,:dots=>true,:fix=>true)
|
||||
# Case 2: There are multiple nodes that claim the slot as importing,
|
||||
# they probably got keys about the slot after a restart so opened
|
||||
# the slot. In this case we just move all the keys to the owner
|
||||
# according to the configuration.
|
||||
elsif migrating.length == 0 && importing.length > 0
|
||||
xputs ">>> Moving all the #{slot} slot keys to its owner #{owner}"
|
||||
importing.each {|node|
|
||||
next if node == owner
|
||||
move_slot(node,owner,slot,:verbose=>true,:fix=>true,:cold=>true)
|
||||
move_slot(node,owner,slot,:dots=>true,:fix=>true,:cold=>true)
|
||||
xputs ">>> Setting #{slot} as STABLE in #{node}"
|
||||
node.r.cluster("setslot",slot,"stable")
|
||||
}
|
||||
# Case 3: There are no slots claiming to be in importing state, but
|
||||
# there is a migrating node that actually don't have any key. We
|
||||
# can just close the slot, probably a reshard interrupted in the middle.
|
||||
elsif importing.length == 0 && migrating.length == 1 &&
|
||||
migrating[0].r.cluster("getkeysinslot",slot,10).length == 0
|
||||
migrating[0].r.cluster("setslot",slot,"stable")
|
||||
else
|
||||
xputs "[ERR] Sorry, Redis-trib can't fix this slot yet (work in progress)"
|
||||
xputs "[ERR] Sorry, Redis-trib can't fix this slot yet (work in progress). Slot is set as migrating in #{migrating.join(",")}, as importing in #{importing.join(",")}, owner is #{owner}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -792,62 +911,238 @@ class RedisTrib
|
||||
# Options:
|
||||
# :verbose -- Print a dot for every moved key.
|
||||
# :fix -- We are moving in the context of a fix. Use REPLACE.
|
||||
# :cold -- Move keys without opening / reconfiguring the nodes.
|
||||
# :cold -- Move keys without opening slots / reconfiguring the nodes.
|
||||
# :update -- Update nodes.info[:slots] for source/target nodes.
|
||||
# :quiet -- Don't print info messages.
|
||||
def move_slot(source,target,slot,o={})
|
||||
o = {:pipeline => MigrateDefaultPipeline}.merge(o)
|
||||
|
||||
# We start marking the slot as importing in the destination node,
|
||||
# and the slot as migrating in the target host. Note that the order of
|
||||
# the operations is important, as otherwise a client may be redirected
|
||||
# to the target node that does not yet know it is importing this slot.
|
||||
print "Moving slot #{slot} from #{source} to #{target}: "; STDOUT.flush
|
||||
if !o[:quiet]
|
||||
print "Moving slot #{slot} from #{source} to #{target}: "
|
||||
STDOUT.flush
|
||||
end
|
||||
|
||||
if !o[:cold]
|
||||
target.r.cluster("setslot",slot,"importing",source.info[:name])
|
||||
source.r.cluster("setslot",slot,"migrating",target.info[:name])
|
||||
end
|
||||
# Migrate all the keys from source to target using the MIGRATE command
|
||||
while true
|
||||
keys = source.r.cluster("getkeysinslot",slot,10)
|
||||
keys = source.r.cluster("getkeysinslot",slot,o[:pipeline])
|
||||
break if keys.length == 0
|
||||
keys.each{|key|
|
||||
begin
|
||||
source.r.client.call(["migrate",target.info[:host],target.info[:port],key,0,15000])
|
||||
rescue => e
|
||||
if o[:fix] && e.to_s =~ /BUSYKEY/
|
||||
xputs "*** Target key #{key} exists. Replace it for FIX."
|
||||
source.r.client.call(["migrate",target.info[:host],target.info[:port],key,0,15000,:replace])
|
||||
else
|
||||
puts ""
|
||||
xputs "[ERR] #{e}"
|
||||
exit 1
|
||||
end
|
||||
begin
|
||||
source.r.client.call(["migrate",target.info[:host],target.info[:port],"",0,@timeout,:keys,*keys])
|
||||
rescue => e
|
||||
if o[:fix] && e.to_s =~ /BUSYKEY/
|
||||
xputs "*** Target key exists. Replacing it for FIX."
|
||||
source.r.client.call(["migrate",target.info[:host],target.info[:port],"",0,@timeout,:replace,:keys,*keys])
|
||||
else
|
||||
puts ""
|
||||
xputs "[ERR] #{e}"
|
||||
exit 1
|
||||
end
|
||||
print "." if o[:verbose]
|
||||
STDOUT.flush
|
||||
}
|
||||
end
|
||||
print "."*keys.length if o[:dots]
|
||||
STDOUT.flush
|
||||
end
|
||||
|
||||
puts
|
||||
puts if !o[:quiet]
|
||||
# Set the new node as the owner of the slot in all the known nodes.
|
||||
if !o[:cold]
|
||||
@nodes.each{|n|
|
||||
next if n.has_flag?("slave")
|
||||
n.r.cluster("setslot",slot,"node",target.info[:name])
|
||||
}
|
||||
end
|
||||
|
||||
# Update the node logical config
|
||||
if o[:update] then
|
||||
source.info[:slots].delete(slot)
|
||||
target.info[:slots][slot] = true
|
||||
end
|
||||
end
|
||||
|
||||
# redis-trib subcommands implementations
|
||||
# redis-trib subcommands implementations.
|
||||
|
||||
def check_cluster_cmd(argv,opt)
|
||||
load_cluster_info_from_node(argv[0])
|
||||
check_cluster
|
||||
end
|
||||
|
||||
def info_cluster_cmd(argv,opt)
|
||||
load_cluster_info_from_node(argv[0])
|
||||
show_cluster_info
|
||||
end
|
||||
|
||||
def rebalance_cluster_cmd(argv,opt)
|
||||
opt = {
|
||||
'pipeline' => MigrateDefaultPipeline,
|
||||
'threshold' => RebalanceDefaultThreshold
|
||||
}.merge(opt)
|
||||
|
||||
# Load nodes info before parsing options, otherwise we can't
|
||||
# handle --weight.
|
||||
load_cluster_info_from_node(argv[0])
|
||||
|
||||
# Options parsing
|
||||
threshold = opt['threshold'].to_i
|
||||
autoweights = opt['auto-weights']
|
||||
weights = {}
|
||||
opt['weight'].each{|w|
|
||||
fields = w.split("=")
|
||||
node = get_node_by_abbreviated_name(fields[0])
|
||||
if !node || !node.has_flag?("master")
|
||||
puts "*** No such master node #{fields[0]}"
|
||||
exit 1
|
||||
end
|
||||
weights[node.info[:name]] = fields[1].to_f
|
||||
} if opt['weight']
|
||||
useempty = opt['use-empty-masters']
|
||||
|
||||
# Assign a weight to each node, and compute the total cluster weight.
|
||||
total_weight = 0
|
||||
nodes_involved = 0
|
||||
@nodes.each{|n|
|
||||
if n.has_flag?("master")
|
||||
next if !useempty && n.slots.length == 0
|
||||
n.info[:w] = weights[n.info[:name]] ? weights[n.info[:name]] : 1
|
||||
total_weight += n.info[:w]
|
||||
nodes_involved += 1
|
||||
end
|
||||
}
|
||||
|
||||
# Check cluster, only proceed if it looks sane.
|
||||
check_cluster(:quiet => true)
|
||||
if @errors.length != 0
|
||||
puts "*** Please fix your cluster problems before rebalancing"
|
||||
exit 1
|
||||
end
|
||||
|
||||
# Calculate the slots balance for each node. It's the number of
|
||||
# slots the node should lose (if positive) or gain (if negative)
|
||||
# in order to be balanced.
|
||||
threshold = opt['threshold'].to_f
|
||||
threshold_reached = false
|
||||
@nodes.each{|n|
|
||||
if n.has_flag?("master")
|
||||
next if !n.info[:w]
|
||||
expected = ((ClusterHashSlots.to_f / total_weight) *
|
||||
n.info[:w]).to_i
|
||||
n.info[:balance] = n.slots.length - expected
|
||||
# Compute the percentage of difference between the
|
||||
# expected number of slots and the real one, to see
|
||||
# if it's over the threshold specified by the user.
|
||||
over_threshold = false
|
||||
if threshold > 0
|
||||
if n.slots.length > 0
|
||||
err_perc = (100-(100.0*expected/n.slots.length)).abs
|
||||
over_threshold = true if err_perc > threshold
|
||||
elsif expected > 0
|
||||
over_threshold = true
|
||||
end
|
||||
end
|
||||
threshold_reached = true if over_threshold
|
||||
end
|
||||
}
|
||||
if !threshold_reached
|
||||
xputs "*** No rebalancing needed! All nodes are within the #{threshold}% threshold."
|
||||
return
|
||||
end
|
||||
|
||||
# Only consider nodes we want to change
|
||||
sn = @nodes.select{|n|
|
||||
n.has_flag?("master") && n.info[:w]
|
||||
}
|
||||
|
||||
# Because of rounding, it is possible that the balance of all nodes
|
||||
# summed does not give 0. Make sure that nodes that have to provide
|
||||
# slots are always matched by nodes receiving slots.
|
||||
total_balance = sn.map{|x| x.info[:balance]}.reduce{|a,b| a+b}
|
||||
while total_balance > 0
|
||||
sn.each{|n|
|
||||
if n.info[:balance] < 0 && total_balance > 0
|
||||
n.info[:balance] -= 1
|
||||
total_balance -= 1
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
# Sort nodes by their slots balance.
|
||||
sn = sn.sort{|a,b|
|
||||
a.info[:balance] <=> b.info[:balance]
|
||||
}
|
||||
|
||||
xputs ">>> Rebalancing across #{nodes_involved} nodes. Total weight = #{total_weight}"
|
||||
|
||||
if $verbose
|
||||
sn.each{|n|
|
||||
puts "#{n} balance is #{n.info[:balance]} slots"
|
||||
}
|
||||
end
|
||||
|
||||
# Now we have at the start of the 'sn' array nodes that should get
|
||||
# slots, at the end nodes that must give slots.
|
||||
# We take two indexes, one at the start, and one at the end,
|
||||
# incrementing or decrementing the indexes accordingly til we
|
||||
# find nodes that need to get/provide slots.
|
||||
dst_idx = 0
|
||||
src_idx = sn.length - 1
|
||||
|
||||
while dst_idx < src_idx
|
||||
dst = sn[dst_idx]
|
||||
src = sn[src_idx]
|
||||
numslots = [dst.info[:balance],src.info[:balance]].map{|n|
|
||||
n.abs
|
||||
}.min
|
||||
|
||||
if numslots > 0
|
||||
puts "Moving #{numslots} slots from #{src} to #{dst}"
|
||||
|
||||
# Actaully move the slots.
|
||||
reshard_table = compute_reshard_table([src],numslots)
|
||||
if reshard_table.length != numslots
|
||||
xputs "*** Assertio failed: Reshard table != number of slots"
|
||||
exit 1
|
||||
end
|
||||
if opt['simulate']
|
||||
print "#"*reshard_table.length
|
||||
else
|
||||
reshard_table.each{|e|
|
||||
move_slot(e[:source],dst,e[:slot],
|
||||
:quiet=>true,
|
||||
:dots=>false,
|
||||
:update=>true,
|
||||
:pipeline=>opt['pipeline'])
|
||||
print "#"
|
||||
STDOUT.flush
|
||||
}
|
||||
end
|
||||
puts
|
||||
end
|
||||
|
||||
# Update nodes balance.
|
||||
dst.info[:balance] += numslots
|
||||
src.info[:balance] -= numslots
|
||||
dst_idx += 1 if dst.info[:balance] == 0
|
||||
src_idx -= 1 if src.info[:balance] == 0
|
||||
end
|
||||
end
|
||||
|
||||
def fix_cluster_cmd(argv,opt)
|
||||
@fix = true
|
||||
@timeout = opt['timeout'].to_i if opt['timeout']
|
||||
|
||||
load_cluster_info_from_node(argv[0])
|
||||
check_cluster
|
||||
end
|
||||
|
||||
def reshard_cluster_cmd(argv,opt)
|
||||
opt = {'pipeline' => MigrateDefaultPipeline}.merge(opt)
|
||||
|
||||
load_cluster_info_from_node(argv[0])
|
||||
check_cluster
|
||||
if @errors.length != 0
|
||||
@@ -855,6 +1150,8 @@ class RedisTrib
|
||||
exit 1
|
||||
end
|
||||
|
||||
@timeout = opt['timeout'].to_i if opt['timeout'].to_i
|
||||
|
||||
# Get number of slots
|
||||
if opt['slots']
|
||||
numslots = opt['slots'].to_i
|
||||
@@ -958,7 +1255,9 @@ class RedisTrib
|
||||
exit(1) if (yesno != "yes")
|
||||
end
|
||||
reshard_table.each{|e|
|
||||
move_slot(e[:source],target,e[:slot],:verbose=>true)
|
||||
move_slot(e[:source],target,e[:slot],
|
||||
:dots=>true,
|
||||
:pipeline=>opt['pipeline'])
|
||||
}
|
||||
end
|
||||
|
||||
@@ -1139,7 +1438,9 @@ class RedisTrib
|
||||
def import_cluster_cmd(argv,opt)
|
||||
source_addr = opt['from']
|
||||
xputs ">>> Importing data from #{source_addr} to cluster #{argv[1]}"
|
||||
|
||||
use_copy = opt['copy']
|
||||
use_replace = opt['replace']
|
||||
|
||||
# Check the existing cluster.
|
||||
load_cluster_info_from_node(argv[0])
|
||||
check_cluster
|
||||
@@ -1174,7 +1475,10 @@ class RedisTrib
|
||||
print "Migrating #{k} to #{target}: "
|
||||
STDOUT.flush
|
||||
begin
|
||||
source.client.call(["migrate",target.info[:host],target.info[:port],k,0,15000])
|
||||
cmd = ["migrate",target.info[:host],target.info[:port],k,0,@timeout]
|
||||
cmd << :copy if use_copy
|
||||
cmd << :replace if use_replace
|
||||
source.client.call(cmd)
|
||||
rescue => e
|
||||
puts e
|
||||
else
|
||||
@@ -1199,17 +1503,32 @@ class RedisTrib
|
||||
if ARGV[idx][0..1] == "--"
|
||||
option = ARGV[idx][2..-1]
|
||||
idx += 1
|
||||
|
||||
# --verbose is a global option
|
||||
if option == "verbose"
|
||||
$verbose = true
|
||||
next
|
||||
end
|
||||
|
||||
if ALLOWED_OPTIONS[cmd] == nil || ALLOWED_OPTIONS[cmd][option] == nil
|
||||
puts "Unknown option '#{option}' for command '#{cmd}'"
|
||||
exit 1
|
||||
end
|
||||
if ALLOWED_OPTIONS[cmd][option]
|
||||
if ALLOWED_OPTIONS[cmd][option] != false
|
||||
value = ARGV[idx]
|
||||
idx += 1
|
||||
else
|
||||
value = true
|
||||
end
|
||||
options[option] = value
|
||||
|
||||
# If the option is set to [], it's a multiple arguments
|
||||
# option. We just queue every new value into an array.
|
||||
if ALLOWED_OPTIONS[cmd][option] == []
|
||||
options[option] = [] if !options[option]
|
||||
options[option] << value
|
||||
else
|
||||
options[option] = value
|
||||
end
|
||||
else
|
||||
# Remaining arguments are not options.
|
||||
break
|
||||
@@ -1321,8 +1640,10 @@ end
|
||||
COMMANDS={
|
||||
"create" => ["create_cluster_cmd", -2, "host1:port1 ... hostN:portN"],
|
||||
"check" => ["check_cluster_cmd", 2, "host:port"],
|
||||
"info" => ["info_cluster_cmd", 2, "host:port"],
|
||||
"fix" => ["fix_cluster_cmd", 2, "host:port"],
|
||||
"reshard" => ["reshard_cluster_cmd", 2, "host:port"],
|
||||
"rebalance" => ["rebalance_cluster_cmd", -2, "host:port"],
|
||||
"add-node" => ["addnode_cluster_cmd", 3, "new_host:new_port existing_host:existing_port"],
|
||||
"del-node" => ["delnode_cluster_cmd", 3, "host:port node_id"],
|
||||
"set-timeout" => ["set_timeout_cluster_cmd", 3, "host:port milliseconds"],
|
||||
@@ -1334,8 +1655,10 @@ COMMANDS={
|
||||
ALLOWED_OPTIONS={
|
||||
"create" => {"replicas" => true},
|
||||
"add-node" => {"slave" => false, "master-id" => true},
|
||||
"import" => {"from" => :required},
|
||||
"reshard" => {"from" => true, "to" => true, "slots" => true, "yes" => false}
|
||||
"import" => {"from" => :required, "copy" => false, "replace" => false},
|
||||
"reshard" => {"from" => true, "to" => true, "slots" => true, "yes" => false, "timeout" => true, "pipeline" => true},
|
||||
"rebalance" => {"weight" => [], "auto-weights" => false, "use-empty-masters" => false, "timeout" => true, "simulate" => false, "pipeline" => true, "threshold" => true},
|
||||
"fix" => {"timeout" => MigrateDefaultTimeout},
|
||||
}
|
||||
|
||||
def show_help
|
||||
|
||||
+70
-44
@@ -129,7 +129,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"append",appendCommand,3,"wm",0,NULL,1,1,1,0,0},
|
||||
{"strlen",strlenCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"del",delCommand,-2,"w",0,NULL,1,-1,1,0,0},
|
||||
{"exists",existsCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"exists",existsCommand,-2,"rF",0,NULL,1,-1,1,0,0},
|
||||
{"setbit",setbitCommand,4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"getbit",getbitCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"setrange",setrangeCommand,4,"wm",0,NULL,1,1,1,0,0},
|
||||
@@ -261,7 +261,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"cluster",clusterCommand,-2,"ar",0,NULL,0,0,0,0,0},
|
||||
{"restore",restoreCommand,-4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"restore-asking",restoreCommand,-4,"wmk",0,NULL,1,1,1,0,0},
|
||||
{"migrate",migrateCommand,-6,"w",0,NULL,0,0,0,0,0},
|
||||
{"migrate",migrateCommand,-6,"w",0,migrateGetKeys,0,0,0,0,0},
|
||||
{"asking",askingCommand,1,"r",0,NULL,0,0,0,0,0},
|
||||
{"readonly",readonlyCommand,1,"rF",0,NULL,0,0,0,0,0},
|
||||
{"readwrite",readwriteCommand,1,"rF",0,NULL,0,0,0,0,0},
|
||||
@@ -280,7 +280,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"command",commandCommand,0,"rlt",0,NULL,0,0,0,0,0},
|
||||
{"pfselftest",pfselftestCommand,1,"r",0,NULL,0,0,0,0,0},
|
||||
{"pfadd",pfaddCommand,-2,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"pfcount",pfcountCommand,-2,"r",0,NULL,1,1,1,0,0},
|
||||
{"pfcount",pfcountCommand,-2,"r",0,NULL,1,-1,1,0,0},
|
||||
{"pfmerge",pfmergeCommand,-2,"wm",0,NULL,1,-1,1,0,0},
|
||||
{"pfdebug",pfdebugCommand,-3,"w",0,NULL,0,0,0,0,0},
|
||||
{"latency",latencyCommand,-2,"arslt",0,NULL,0,0,0,0,0}
|
||||
@@ -840,18 +840,22 @@ void activeExpireCycle(int type) {
|
||||
if ((de = dictGetRandomKey(db->expires)) == NULL) break;
|
||||
ttl = dictGetSignedIntegerVal(de)-now;
|
||||
if (activeExpireCycleTryExpire(db,de,now)) expired++;
|
||||
if (ttl < 0) ttl = 0;
|
||||
ttl_sum += ttl;
|
||||
ttl_samples++;
|
||||
if (ttl > 0) {
|
||||
/* We want the average TTL of keys yet not expired. */
|
||||
ttl_sum += ttl;
|
||||
ttl_samples++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update the average TTL stats for this database. */
|
||||
if (ttl_samples) {
|
||||
long long avg_ttl = ttl_sum/ttl_samples;
|
||||
|
||||
/* Do a simple running average with a few samples.
|
||||
* We just use the current estimate with a weight of 2%
|
||||
* and the previous estimate with a weight of 98%. */
|
||||
if (db->avg_ttl == 0) db->avg_ttl = avg_ttl;
|
||||
/* Smooth the value averaging with the previous one. */
|
||||
db->avg_ttl = (db->avg_ttl+avg_ttl)/2;
|
||||
db->avg_ttl = (db->avg_ttl/50)*49 + (avg_ttl/50);
|
||||
}
|
||||
|
||||
/* We can't block forever here even if there are many keys to
|
||||
@@ -902,9 +906,12 @@ long long getInstantaneousMetric(int metric) {
|
||||
return sum / REDIS_METRIC_SAMPLES;
|
||||
}
|
||||
|
||||
/* Check for timeouts. Returns non-zero if the client was terminated */
|
||||
int clientsCronHandleTimeout(redisClient *c) {
|
||||
time_t now = server.unixtime;
|
||||
/* Check for timeouts. Returns non-zero if the client was terminated.
|
||||
* The function gets the current time in milliseconds as argument since
|
||||
* it gets called multiple times in a loop, so calling gettimeofday() for
|
||||
* each iteration would be costly without any actual gain. */
|
||||
int clientsCronHandleTimeout(redisClient *c, mstime_t now_ms) {
|
||||
time_t now = now_ms/1000;
|
||||
|
||||
if (server.maxidletime &&
|
||||
!(c->flags & REDIS_SLAVE) && /* no timeout for slaves */
|
||||
@@ -920,7 +927,6 @@ int clientsCronHandleTimeout(redisClient *c) {
|
||||
/* Blocked OPS timeout is handled with milliseconds resolution.
|
||||
* However note that the actual resolution is limited by
|
||||
* server.hz. */
|
||||
mstime_t now_ms = mstime();
|
||||
|
||||
if (c->bpop.timeout != 0 && c->bpop.timeout < now_ms) {
|
||||
/* Handle blocking operation specific timeout. */
|
||||
@@ -962,17 +968,23 @@ int clientsCronResizeQueryBuffer(redisClient *c) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define CLIENTS_CRON_MIN_ITERATIONS 5
|
||||
void clientsCron(void) {
|
||||
/* 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. */
|
||||
/* Make sure to process at least numclients/server.hz 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 1
|
||||
* second. */
|
||||
int numclients = listLength(server.clients);
|
||||
int iterations = numclients/(server.hz*10);
|
||||
int iterations = numclients/server.hz;
|
||||
mstime_t now = mstime();
|
||||
|
||||
/* Process at least a few clients while we are at it, even if we need
|
||||
* to process less than CLIENTS_CRON_MIN_ITERATIONS to meet our contract
|
||||
* of processing each client once per second. */
|
||||
if (iterations < CLIENTS_CRON_MIN_ITERATIONS)
|
||||
iterations = (numclients < CLIENTS_CRON_MIN_ITERATIONS) ?
|
||||
numclients : CLIENTS_CRON_MIN_ITERATIONS;
|
||||
|
||||
if (iterations < 50)
|
||||
iterations = (numclients < 50) ? numclients : 50;
|
||||
while(listLength(server.clients) && iterations--) {
|
||||
redisClient *c;
|
||||
listNode *head;
|
||||
@@ -986,7 +998,7 @@ void clientsCron(void) {
|
||||
/* The following functions do different service checks on the client.
|
||||
* The protocol is that they return non-zero if the client was
|
||||
* terminated. */
|
||||
if (clientsCronHandleTimeout(c)) continue;
|
||||
if (clientsCronHandleTimeout(c,now)) continue;
|
||||
if (clientsCronResizeQueryBuffer(c)) continue;
|
||||
}
|
||||
}
|
||||
@@ -1164,7 +1176,13 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
|
||||
if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);
|
||||
|
||||
if (pid == server.rdb_child_pid) {
|
||||
if (pid == -1) {
|
||||
redisLog(LOG_WARNING,"wait3() returned an error: %s. "
|
||||
"rdb_child_pid = %d, aof_child_pid = %d",
|
||||
strerror(errno),
|
||||
(int) server.rdb_child_pid,
|
||||
(int) server.aof_child_pid);
|
||||
} else if (pid == server.rdb_child_pid) {
|
||||
backgroundSaveDoneHandler(exitcode,bysignal);
|
||||
} else if (pid == server.aof_child_pid) {
|
||||
backgroundRewriteDoneHandler(exitcode,bysignal);
|
||||
@@ -1680,17 +1698,19 @@ int listenToPort(int port, int *fds, int *count) {
|
||||
if (fds[*count] != ANET_ERR) {
|
||||
anetNonBlock(NULL,fds[*count]);
|
||||
(*count)++;
|
||||
|
||||
/* Bind the IPv4 address as well. */
|
||||
fds[*count] = anetTcpServer(server.neterr,port,NULL,
|
||||
server.tcp_backlog);
|
||||
if (fds[*count] != ANET_ERR) {
|
||||
anetNonBlock(NULL,fds[*count]);
|
||||
(*count)++;
|
||||
}
|
||||
}
|
||||
fds[*count] = anetTcpServer(server.neterr,port,NULL,
|
||||
server.tcp_backlog);
|
||||
if (fds[*count] != ANET_ERR) {
|
||||
anetNonBlock(NULL,fds[*count]);
|
||||
(*count)++;
|
||||
}
|
||||
/* Exit the loop if we were able to bind * on IPv4 or IPv6,
|
||||
/* Exit the loop if we were able to bind * on IPv4 and IPv6,
|
||||
* otherwise fds[*count] will be ANET_ERR and we'll print an
|
||||
* error and return to the caller with an error. */
|
||||
if (*count) break;
|
||||
if (*count == 2) break;
|
||||
} else if (strchr(server.bindaddr[j],':')) {
|
||||
/* Bind IPv6 address. */
|
||||
fds[*count] = anetTcp6Server(server.neterr,port,server.bindaddr[j],
|
||||
@@ -1740,6 +1760,7 @@ void resetServerStats(void) {
|
||||
}
|
||||
server.stat_net_input_bytes = 0;
|
||||
server.stat_net_output_bytes = 0;
|
||||
server.aof_delayed_fsync = 0;
|
||||
}
|
||||
|
||||
void initServer(void) {
|
||||
@@ -2166,22 +2187,21 @@ int processCommand(redisClient *c) {
|
||||
!(c->flags & REDIS_MASTER) &&
|
||||
!(c->flags & REDIS_LUA_CLIENT &&
|
||||
server.lua_caller->flags & REDIS_MASTER) &&
|
||||
!(c->cmd->getkeys_proc == NULL && c->cmd->firstkey == 0))
|
||||
!(c->cmd->getkeys_proc == NULL && c->cmd->firstkey == 0 &&
|
||||
c->cmd->proc != execCommand))
|
||||
{
|
||||
int hashslot;
|
||||
|
||||
if (server.cluster->state != REDIS_CLUSTER_OK) {
|
||||
flagTransaction(c);
|
||||
clusterRedirectClient(c,NULL,0,REDIS_CLUSTER_REDIR_DOWN_STATE);
|
||||
return REDIS_OK;
|
||||
} else {
|
||||
int error_code;
|
||||
clusterNode *n = getNodeByQuery(c,c->cmd,c->argv,c->argc,&hashslot,&error_code);
|
||||
if (n == NULL || n != server.cluster->myself) {
|
||||
int error_code;
|
||||
clusterNode *n = getNodeByQuery(c,c->cmd,c->argv,c->argc,
|
||||
&hashslot,&error_code);
|
||||
if (n == NULL || n != server.cluster->myself) {
|
||||
if (c->cmd->proc == execCommand) {
|
||||
discardTransaction(c);
|
||||
} else {
|
||||
flagTransaction(c);
|
||||
clusterRedirectClient(c,n,hashslot,error_code);
|
||||
return REDIS_OK;
|
||||
}
|
||||
clusterRedirectClient(c,n,hashslot,error_code);
|
||||
return REDIS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2192,6 +2212,12 @@ int processCommand(redisClient *c) {
|
||||
* is returning an error. */
|
||||
if (server.maxmemory) {
|
||||
int retval = freeMemoryIfNeeded();
|
||||
/* freeMemoryIfNeeded may flush slave output buffers. This may result
|
||||
* into a slave, that may be the active client, to be freed. */
|
||||
if (server.current_client == NULL) return REDIS_ERR;
|
||||
|
||||
/* It was impossible to free enough memory, and the command the client
|
||||
* is trying to execute is denied during OOM conditions? Error. */
|
||||
if ((c->cmd->flags & REDIS_CMD_DENYOOM) && retval == REDIS_ERR) {
|
||||
flagTransaction(c);
|
||||
addReply(c, shared.oomerr);
|
||||
@@ -2250,7 +2276,7 @@ int processCommand(redisClient *c) {
|
||||
c->cmd->proc != unsubscribeCommand &&
|
||||
c->cmd->proc != psubscribeCommand &&
|
||||
c->cmd->proc != punsubscribeCommand) {
|
||||
addReplyError(c,"only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context");
|
||||
addReplyError(c,"only (P)SUBSCRIBE / (P)UNSUBSCRIBE / PING / QUIT allowed in this context");
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
@@ -3651,6 +3677,7 @@ int main(int argc, char **argv) {
|
||||
if (server.daemonize) createPidFile();
|
||||
redisSetProcTitle(argv[0]);
|
||||
redisAsciiArt();
|
||||
checkTcpBacklogSettings();
|
||||
|
||||
if (!server.sentinel_mode) {
|
||||
/* Things not needed when running in Sentinel mode. */
|
||||
@@ -3658,7 +3685,6 @@ int main(int argc, char **argv) {
|
||||
#ifdef __linux__
|
||||
linuxMemoryWarnings();
|
||||
#endif
|
||||
checkTcpBacklogSettings();
|
||||
loadDataFromDisk();
|
||||
if (server.cluster_enabled) {
|
||||
if (verifyClusterConfigWithData() == REDIS_ERR) {
|
||||
|
||||
+32
-9
@@ -61,7 +61,7 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#include "version.h" /* Version macro */
|
||||
#include "util.h" /* Misc functions useful in many places */
|
||||
#include "latency.h" /* Latency monitor API */
|
||||
#include "sparkline.h" /* ASII graphs API */
|
||||
#include "sparkline.h" /* ASCII graphs API */
|
||||
|
||||
/* Error codes */
|
||||
#define REDIS_OK 0
|
||||
@@ -267,22 +267,37 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define REDIS_CLIENT_TYPE_PUBSUB 2 /* Clients subscribed to PubSub channels. */
|
||||
#define REDIS_CLIENT_TYPE_COUNT 3
|
||||
|
||||
/* Slave replication state - from the point of view of the slave. */
|
||||
/* Slave replication state. Used in server.repl_state for slaves to remember
|
||||
* what to do next. */
|
||||
#define REDIS_REPL_NONE 0 /* No active replication */
|
||||
#define REDIS_REPL_CONNECT 1 /* Must connect to master */
|
||||
#define REDIS_REPL_CONNECTING 2 /* Connecting to master */
|
||||
/* --- Handshake states, must be ordered --- */
|
||||
#define REDIS_REPL_RECEIVE_PONG 3 /* Wait for PING reply */
|
||||
#define REDIS_REPL_TRANSFER 4 /* Receiving .rdb from master */
|
||||
#define REDIS_REPL_CONNECTED 5 /* Connected to master */
|
||||
#define REDIS_REPL_SEND_AUTH 4 /* Send AUTH to master */
|
||||
#define REDIS_REPL_RECEIVE_AUTH 5 /* Wait for AUTH reply */
|
||||
#define REDIS_REPL_SEND_PORT 6 /* Send REPLCONF listening-port */
|
||||
#define REDIS_REPL_RECEIVE_PORT 7 /* Wait for REPLCONF reply */
|
||||
#define REDIS_REPL_SEND_CAPA 8 /* Send REPLCONF capa */
|
||||
#define REDIS_REPL_RECEIVE_CAPA 9 /* Wait for REPLCONF reply */
|
||||
#define REDIS_REPL_SEND_PSYNC 10 /* Send PSYNC */
|
||||
#define REDIS_REPL_RECEIVE_PSYNC 11 /* Wait for PSYNC reply */
|
||||
/* --- End of handshake states --- */
|
||||
#define REDIS_REPL_TRANSFER 12 /* Receiving .rdb from master */
|
||||
#define REDIS_REPL_CONNECTED 13 /* Connected to master */
|
||||
|
||||
/* Slave replication state - from the point of view of the master.
|
||||
/* State of slaves from the POV of the master. Used in client->replstate.
|
||||
* In SEND_BULK and ONLINE state the slave receives new updates
|
||||
* in its output queue. In the WAIT_BGSAVE state instead the server is waiting
|
||||
* to start the next background saving in order to send updates to it. */
|
||||
#define REDIS_REPL_WAIT_BGSAVE_START 6 /* We need to produce a new RDB file. */
|
||||
#define REDIS_REPL_WAIT_BGSAVE_END 7 /* Waiting RDB file creation to finish. */
|
||||
#define REDIS_REPL_SEND_BULK 8 /* Sending RDB file to slave. */
|
||||
#define REDIS_REPL_ONLINE 9 /* RDB file transmitted, sending just updates. */
|
||||
#define REDIS_REPL_WAIT_BGSAVE_START 14 /* We need to produce a new RDB file. */
|
||||
#define REDIS_REPL_WAIT_BGSAVE_END 15 /* Waiting RDB file creation to finish. */
|
||||
#define REDIS_REPL_SEND_BULK 16 /* Sending RDB file to slave. */
|
||||
#define REDIS_REPL_ONLINE 17 /* RDB file transmitted, sending just updates. */
|
||||
|
||||
/* Slave capabilities. */
|
||||
#define SLAVE_CAPA_NONE 0
|
||||
#define SLAVE_CAPA_EOF (1<<0) /* Can parse the RDB EOF streaming format. */
|
||||
|
||||
/* Synchronous read timeout - slave side */
|
||||
#define REDIS_REPL_SYNCIO_TIMEOUT 5
|
||||
@@ -543,8 +558,12 @@ typedef struct redisClient {
|
||||
long long reploff; /* replication offset if this is our master */
|
||||
long long repl_ack_off; /* replication ack offset, if this is a slave */
|
||||
long long repl_ack_time;/* replication ack time, if this is a slave */
|
||||
long long psync_initial_offset; /* FULLRESYNC reply offset other slaves
|
||||
copying this slave output buffer
|
||||
should use. */
|
||||
char replrunid[REDIS_RUN_ID_SIZE+1]; /* master run id if this is a master */
|
||||
int slave_listening_port; /* As configured with: SLAVECONF listening-port */
|
||||
int slave_capa; /* Slave capabilities: SLAVE_CAPA_* bitwise OR. */
|
||||
multiState mstate; /* MULTI/EXEC state */
|
||||
int btype; /* Type of blocking op if REDIS_BLOCKED. */
|
||||
blockingState bpop; /* blocking state */
|
||||
@@ -1052,6 +1071,7 @@ sds catClientInfoString(sds s, redisClient *client);
|
||||
sds getAllClientsInfoString(void);
|
||||
void rewriteClientCommandVector(redisClient *c, int argc, ...);
|
||||
void rewriteClientCommandArgument(redisClient *c, int i, robj *newval);
|
||||
void replaceClientCommandVector(redisClient *c, int argc, robj **argv);
|
||||
unsigned long getClientOutputBufferMemoryUsage(redisClient *c);
|
||||
void freeClientsInAsyncFreeQueue(void);
|
||||
void asyncCloseClientOnOutputBufferLimitReached(redisClient *c);
|
||||
@@ -1171,6 +1191,8 @@ int replicationCountAcksByOffset(long long offset);
|
||||
void replicationSendNewlineToMaster(void);
|
||||
long long replicationGetSlaveOffset(void);
|
||||
char *replicationGetSlaveName(redisClient *c);
|
||||
long long getPsyncInitialOffset(void);
|
||||
int replicationSetupSlaveForFullResync(redisClient *slave, long long offset);
|
||||
|
||||
/* Generic persistence functions */
|
||||
void startLoading(FILE *fp);
|
||||
@@ -1340,6 +1362,7 @@ void getKeysFreeResult(int *result);
|
||||
int *zunionInterGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys);
|
||||
int *evalGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
int *sortGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
int *migrateGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
|
||||
/* Cluster */
|
||||
void clusterInit(void);
|
||||
|
||||
+422
-177
@@ -201,6 +201,7 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
|
||||
listRewind(slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *slave = ln->value;
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START) continue;
|
||||
addReply(slave,selectcmd);
|
||||
}
|
||||
|
||||
@@ -349,6 +350,58 @@ long long addReplyReplicationBacklog(redisClient *c, long long offset) {
|
||||
return server.repl_backlog_histlen - skip;
|
||||
}
|
||||
|
||||
/* Return the offset to provide as reply to the PSYNC command received
|
||||
* from the slave. The returned value is only valid immediately after
|
||||
* the BGSAVE process started and before executing any other command
|
||||
* from clients. */
|
||||
long long getPsyncInitialOffset(void) {
|
||||
long long psync_offset = server.master_repl_offset;
|
||||
/* Add 1 to psync_offset if it the replication backlog does not exists
|
||||
* as when it will be created later we'll increment the offset by one. */
|
||||
if (server.repl_backlog == NULL) psync_offset++;
|
||||
return psync_offset;
|
||||
}
|
||||
|
||||
/* Send a FULLRESYNC reply in the specific case of a full resynchronization,
|
||||
* as a side effect setup the slave for a full sync in different ways:
|
||||
*
|
||||
* 1) Remember, into the slave client structure, the offset we sent
|
||||
* here, so that if new slaves will later attach to the same
|
||||
* background RDB saving process (by duplicating this client output
|
||||
* buffer), we can get the right offset from this slave.
|
||||
* 2) Set the replication state of the slave to WAIT_BGSAVE_END so that
|
||||
* we start accumulating differences from this point.
|
||||
* 3) Force the replication stream to re-emit a SELECT statement so
|
||||
* the new slave incremental differences will start selecting the
|
||||
* right database number.
|
||||
*
|
||||
* Normally this function should be called immediately after a successful
|
||||
* BGSAVE for replication was started, or when there is one already in
|
||||
* progress that we attached our slave to. */
|
||||
int replicationSetupSlaveForFullResync(redisClient *slave, long long offset) {
|
||||
char buf[128];
|
||||
int buflen;
|
||||
|
||||
slave->psync_initial_offset = offset;
|
||||
slave->replstate = REDIS_REPL_WAIT_BGSAVE_END;
|
||||
/* We are going to accumulate the incremental changes for this
|
||||
* slave as well. Set slaveseldb to -1 in order to force to re-emit
|
||||
* a SLEECT statement in the replication stream. */
|
||||
server.slaveseldb = -1;
|
||||
|
||||
/* Don't send this reply to slaves that approached us with
|
||||
* the old SYNC command. */
|
||||
if (!(slave->flags & REDIS_PRE_PSYNC)) {
|
||||
buflen = snprintf(buf,sizeof(buf),"+FULLRESYNC %s %lld\r\n",
|
||||
server.runid,offset);
|
||||
if (write(slave->fd,buf,buflen) != buflen) {
|
||||
freeClientAsync(slave);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* This function handles the PSYNC command from the point of view of a
|
||||
* master receiving a request for partial resynchronization.
|
||||
*
|
||||
@@ -422,18 +475,10 @@ int masterTryPartialResynchronization(redisClient *c) {
|
||||
return REDIS_OK; /* The caller can return, no full resync needed. */
|
||||
|
||||
need_full_resync:
|
||||
/* We need a full resync for some reason... notify the client. */
|
||||
psync_offset = server.master_repl_offset;
|
||||
/* Add 1 to psync_offset if it the replication backlog does not exists
|
||||
* as when it will be created later we'll increment the offset by one. */
|
||||
if (server.repl_backlog == NULL) psync_offset++;
|
||||
/* Again, we can't use the connection buffers (see above). */
|
||||
buflen = snprintf(buf,sizeof(buf),"+FULLRESYNC %s %lld\r\n",
|
||||
server.runid,psync_offset);
|
||||
if (write(c->fd,buf,buflen) != buflen) {
|
||||
freeClientAsync(c);
|
||||
return REDIS_OK;
|
||||
}
|
||||
/* We need a full resync for some reason... Note that we can't
|
||||
* reply to PSYNC right now if a full SYNC is needed. The reply
|
||||
* must include the master offset at the time the RDB file we transfer
|
||||
* is generated, so we need to delay the reply to that moment. */
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
@@ -441,18 +486,68 @@ need_full_resync:
|
||||
* socket target depending on the configuration, and making sure that
|
||||
* the script cache is flushed before to start.
|
||||
*
|
||||
* The mincapa argument is the bitwise AND among all the slaves capabilities
|
||||
* of the slaves waiting for this BGSAVE, so represents the slave capabilities
|
||||
* all the slaves support. Can be tested via SLAVE_CAPA_* macros.
|
||||
*
|
||||
* Side effects, other than starting a BGSAVE:
|
||||
*
|
||||
* 1) Handle the slaves in WAIT_START state, by preparing them for a full
|
||||
* sync if the BGSAVE was succesfully started, or sending them an error
|
||||
* and dropping them from the list of slaves.
|
||||
*
|
||||
* 2) Flush the Lua scripting script cache if the BGSAVE was actually
|
||||
* started.
|
||||
*
|
||||
* Returns REDIS_OK on success or REDIS_ERR otherwise. */
|
||||
int startBgsaveForReplication(void) {
|
||||
int startBgsaveForReplication(int mincapa) {
|
||||
int retval;
|
||||
int socket_target = server.repl_diskless_sync && (mincapa & SLAVE_CAPA_EOF);
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
redisLog(REDIS_NOTICE,"Starting BGSAVE for SYNC with target: %s",
|
||||
server.repl_diskless_sync ? "slaves sockets" : "disk");
|
||||
socket_target ? "slaves sockets" : "disk");
|
||||
|
||||
if (server.repl_diskless_sync)
|
||||
if (socket_target)
|
||||
retval = rdbSaveToSlavesSockets();
|
||||
else
|
||||
retval = rdbSaveBackground(server.rdb_filename);
|
||||
|
||||
/* If we failed to BGSAVE, remove the slaves waiting for a full
|
||||
* resynchorinization from the list of salves, inform them with
|
||||
* an error about what happened, close the connection ASAP. */
|
||||
if (retval == REDIS_ERR) {
|
||||
redisLog(REDIS_WARNING,"BGSAVE for replication failed");
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *slave = ln->value;
|
||||
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START) {
|
||||
slave->flags &= ~REDIS_SLAVE;
|
||||
listDelNode(server.slaves,ln);
|
||||
addReplyError(slave,
|
||||
"BGSAVE failed, replication can't continue");
|
||||
slave->flags |= REDIS_CLOSE_AFTER_REPLY;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* If the target is socket, rdbSaveToSlavesSockets() already setup
|
||||
* the salves for a full resync. Otherwise for disk target do it now.*/
|
||||
if (!socket_target) {
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *slave = ln->value;
|
||||
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START) {
|
||||
replicationSetupSlaveForFullResync(slave,
|
||||
getPsyncInitialOffset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Flush the script cache, since we need that slave differences are
|
||||
* accumulated without requiring slaves to match our cached scripts. */
|
||||
if (retval == REDIS_OK) replicationScriptCacheFlush();
|
||||
@@ -515,8 +610,16 @@ void syncCommand(redisClient *c) {
|
||||
/* Full resynchronization. */
|
||||
server.stat_sync_full++;
|
||||
|
||||
/* Here we need to check if there is a background saving operation
|
||||
* in progress, or if it is required to start one */
|
||||
/* Setup the slave as one waiting for BGSAVE to start. The following code
|
||||
* paths will change the state if we handle the slave differently. */
|
||||
c->replstate = REDIS_REPL_WAIT_BGSAVE_START;
|
||||
if (server.repl_disable_tcp_nodelay)
|
||||
anetDisableTcpNoDelay(NULL, c->fd); /* Non critical if it fails. */
|
||||
c->repldbfd = -1;
|
||||
c->flags |= REDIS_SLAVE;
|
||||
listAddNodeTail(server.slaves,c);
|
||||
|
||||
/* CASE 1: BGSAVE is in progress, with disk target. */
|
||||
if (server.rdb_child_pid != -1 &&
|
||||
server.rdb_child_type == REDIS_RDB_CHILD_TYPE_DISK)
|
||||
{
|
||||
@@ -532,51 +635,45 @@ void syncCommand(redisClient *c) {
|
||||
slave = ln->value;
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_END) break;
|
||||
}
|
||||
if (ln) {
|
||||
/* To attach this slave, we check that it has at least all the
|
||||
* capabilities of the slave that triggered the current BGSAVE. */
|
||||
if (ln && ((c->slave_capa & slave->slave_capa) == slave->slave_capa)) {
|
||||
/* Perfect, the server is already registering differences for
|
||||
* another slave. Set the right state, and copy the buffer. */
|
||||
copyClientOutputBuffer(c,slave);
|
||||
c->replstate = REDIS_REPL_WAIT_BGSAVE_END;
|
||||
replicationSetupSlaveForFullResync(c,slave->psync_initial_offset);
|
||||
redisLog(REDIS_NOTICE,"Waiting for end of BGSAVE for SYNC");
|
||||
} else {
|
||||
/* No way, we need to wait for the next BGSAVE in order to
|
||||
* register differences. */
|
||||
c->replstate = REDIS_REPL_WAIT_BGSAVE_START;
|
||||
redisLog(REDIS_NOTICE,"Waiting for next BGSAVE for SYNC");
|
||||
}
|
||||
|
||||
/* CASE 2: BGSAVE is in progress, with socket target. */
|
||||
} else if (server.rdb_child_pid != -1 &&
|
||||
server.rdb_child_type == REDIS_RDB_CHILD_TYPE_SOCKET)
|
||||
{
|
||||
/* There is an RDB child process but it is writing directly to
|
||||
* children sockets. We need to wait for the next BGSAVE
|
||||
* in order to synchronize. */
|
||||
c->replstate = REDIS_REPL_WAIT_BGSAVE_START;
|
||||
redisLog(REDIS_NOTICE,"Waiting for next BGSAVE for SYNC");
|
||||
|
||||
/* CASE 3: There is no BGSAVE is progress. */
|
||||
} else {
|
||||
if (server.repl_diskless_sync) {
|
||||
if (server.repl_diskless_sync && (c->slave_capa & SLAVE_CAPA_EOF)) {
|
||||
/* Diskless replication RDB child is created inside
|
||||
* replicationCron() since we want to delay its start a
|
||||
* few seconds to wait for more slaves to arrive. */
|
||||
c->replstate = REDIS_REPL_WAIT_BGSAVE_START;
|
||||
if (server.repl_diskless_sync_delay)
|
||||
redisLog(REDIS_NOTICE,"Delay next BGSAVE for SYNC");
|
||||
} else {
|
||||
/* Ok we don't have a BGSAVE in progress, let's start one. */
|
||||
if (startBgsaveForReplication() != REDIS_OK) {
|
||||
redisLog(REDIS_NOTICE,"Replication failed, can't BGSAVE");
|
||||
addReplyError(c,"Unable to perform background save");
|
||||
return;
|
||||
}
|
||||
c->replstate = REDIS_REPL_WAIT_BGSAVE_END;
|
||||
/* Target is disk (or the slave is not capable of supporting
|
||||
* diskless replication) and we don't have a BGSAVE in progress,
|
||||
* let's start one. */
|
||||
if (startBgsaveForReplication(c->slave_capa) != REDIS_OK) return;
|
||||
}
|
||||
}
|
||||
|
||||
if (server.repl_disable_tcp_nodelay)
|
||||
anetDisableTcpNoDelay(NULL, c->fd); /* Non critical if it fails. */
|
||||
c->repldbfd = -1;
|
||||
c->flags |= REDIS_SLAVE;
|
||||
server.slaveseldb = -1; /* Force to re-emit the SELECT command. */
|
||||
listAddNodeTail(server.slaves,c);
|
||||
if (listLength(server.slaves) == 1 && server.repl_backlog == NULL)
|
||||
createReplicationBacklog();
|
||||
return;
|
||||
@@ -613,6 +710,10 @@ void replconfCommand(redisClient *c) {
|
||||
&port,NULL) != REDIS_OK))
|
||||
return;
|
||||
c->slave_listening_port = port;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"capa")) {
|
||||
/* Ignore capabilities not understood by this master. */
|
||||
if (!strcasecmp(c->argv[j+1]->ptr,"eof"))
|
||||
c->slave_capa |= SLAVE_CAPA_EOF;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"ack")) {
|
||||
/* REPLCONF ACK is used by slave to inform the master the amount
|
||||
* of replication stream that it processed so far. It is an
|
||||
@@ -746,6 +847,7 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
|
||||
listNode *ln;
|
||||
int startbgsave = 0;
|
||||
int mincapa = -1;
|
||||
listIter li;
|
||||
|
||||
listRewind(server.slaves,&li);
|
||||
@@ -754,7 +856,8 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
|
||||
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START) {
|
||||
startbgsave = 1;
|
||||
slave->replstate = REDIS_REPL_WAIT_BGSAVE_END;
|
||||
mincapa = (mincapa == -1) ? slave->slave_capa :
|
||||
(mincapa & slave->slave_capa);
|
||||
} else if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_END) {
|
||||
struct redis_stat buf;
|
||||
|
||||
@@ -801,24 +904,18 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (startbgsave) {
|
||||
if (startBgsaveForReplication() != REDIS_OK) {
|
||||
listIter li;
|
||||
|
||||
listRewind(server.slaves,&li);
|
||||
redisLog(REDIS_WARNING,"SYNC failed. BGSAVE failed");
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *slave = ln->value;
|
||||
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START)
|
||||
freeClient(slave);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (startbgsave) startBgsaveForReplication(mincapa);
|
||||
}
|
||||
|
||||
/* ----------------------------------- SLAVE -------------------------------- */
|
||||
|
||||
/* Returns 1 if the given replication state is a handshake state,
|
||||
* 0 otherwise. */
|
||||
int slaveIsInHandshakeState(void) {
|
||||
return server.repl_state >= REDIS_REPL_RECEIVE_PONG &&
|
||||
server.repl_state <= REDIS_REPL_RECEIVE_PSYNC;
|
||||
}
|
||||
|
||||
/* Abort the async download of the bulk dataset while SYNC-ing with master */
|
||||
void replicationAbortSyncTransfer(void) {
|
||||
redisAssert(server.repl_state == REDIS_REPL_TRANSFER);
|
||||
@@ -1062,38 +1159,54 @@ error:
|
||||
* The command returns an sds string representing the result of the
|
||||
* operation. On error the first byte is a "-".
|
||||
*/
|
||||
char *sendSynchronousCommand(int fd, ...) {
|
||||
va_list ap;
|
||||
sds cmd = sdsempty();
|
||||
char *arg, buf[256];
|
||||
#define SYNC_CMD_READ (1<<0)
|
||||
#define SYNC_CMD_WRITE (1<<1)
|
||||
#define SYNC_CMD_FULL (SYNC_CMD_READ|SYNC_CMD_WRITE)
|
||||
char *sendSynchronousCommand(int flags, int fd, ...) {
|
||||
|
||||
/* Create the command to send to the master, we use simple inline
|
||||
* protocol for simplicity as currently we only send simple strings. */
|
||||
va_start(ap,fd);
|
||||
while(1) {
|
||||
arg = va_arg(ap, char*);
|
||||
if (arg == NULL) break;
|
||||
if (flags & SYNC_CMD_WRITE) {
|
||||
char *arg;
|
||||
va_list ap;
|
||||
sds cmd = sdsempty();
|
||||
va_start(ap,fd);
|
||||
|
||||
if (sdslen(cmd) != 0) cmd = sdscatlen(cmd," ",1);
|
||||
cmd = sdscat(cmd,arg);
|
||||
}
|
||||
cmd = sdscatlen(cmd,"\r\n",2);
|
||||
while(1) {
|
||||
arg = va_arg(ap, char*);
|
||||
if (arg == NULL) break;
|
||||
|
||||
/* Transfer command to the server. */
|
||||
if (syncWrite(fd,cmd,sdslen(cmd),server.repl_syncio_timeout*1000) == -1) {
|
||||
if (sdslen(cmd) != 0) cmd = sdscatlen(cmd," ",1);
|
||||
cmd = sdscat(cmd,arg);
|
||||
}
|
||||
cmd = sdscatlen(cmd,"\r\n",2);
|
||||
|
||||
/* Transfer command to the server. */
|
||||
if (syncWrite(fd,cmd,sdslen(cmd),server.repl_syncio_timeout*1000)
|
||||
== -1)
|
||||
{
|
||||
sdsfree(cmd);
|
||||
return sdscatprintf(sdsempty(),"-Writing to master: %s",
|
||||
strerror(errno));
|
||||
}
|
||||
sdsfree(cmd);
|
||||
return sdscatprintf(sdsempty(),"-Writing to master: %s",
|
||||
strerror(errno));
|
||||
va_end(ap);
|
||||
}
|
||||
sdsfree(cmd);
|
||||
|
||||
/* Read the reply from the server. */
|
||||
if (syncReadLine(fd,buf,sizeof(buf),server.repl_syncio_timeout*1000) == -1)
|
||||
{
|
||||
return sdscatprintf(sdsempty(),"-Reading from master: %s",
|
||||
strerror(errno));
|
||||
if (flags & SYNC_CMD_READ) {
|
||||
char buf[256];
|
||||
|
||||
if (syncReadLine(fd,buf,sizeof(buf),server.repl_syncio_timeout*1000)
|
||||
== -1)
|
||||
{
|
||||
return sdscatprintf(sdsempty(),"-Reading from master: %s",
|
||||
strerror(errno));
|
||||
}
|
||||
server.repl_transfer_lastio = server.unixtime;
|
||||
return sdsnew(buf);
|
||||
}
|
||||
return sdsnew(buf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Try a partial resynchronization with the master if we are about to reconnect.
|
||||
@@ -1110,6 +1223,19 @@ char *sendSynchronousCommand(int fd, ...) {
|
||||
* of successful partial resynchronization, the function will reuse
|
||||
* 'fd' as file descriptor of the server.master client structure.
|
||||
*
|
||||
* The function is split in two halves: if read_reply is 0, the function
|
||||
* writes the PSYNC command on the socket, and a new function call is
|
||||
* needed, with read_reply set to 1, in order to read the reply of the
|
||||
* command. This is useful in order to support non blocking operations, so
|
||||
* that we write, return into the event loop, and read when there are data.
|
||||
*
|
||||
* When read_reply is 0 the function returns PSYNC_WRITE_ERR if there
|
||||
* was a write error, or PSYNC_WAIT_REPLY to signal we need another call
|
||||
* with read_reply set to 1. However even when read_reply is set to 1
|
||||
* the function may return PSYNC_WAIT_REPLY again to signal there were
|
||||
* insufficient data to read to complete its work. We should re-enter
|
||||
* into the event loop and wait in such a case.
|
||||
*
|
||||
* The function returns:
|
||||
*
|
||||
* PSYNC_CONTINUE: If the PSYNC command succeded and we can continue.
|
||||
@@ -1118,35 +1244,68 @@ char *sendSynchronousCommand(int fd, ...) {
|
||||
* offset is saved.
|
||||
* PSYNC_NOT_SUPPORTED: If the server does not understand PSYNC at all and
|
||||
* the caller should fall back to SYNC.
|
||||
* PSYNC_WRITE_ERR: There was an error writing the command to the socket.
|
||||
* PSYNC_WAIT_REPLY: Call again the function with read_reply set to 1.
|
||||
*
|
||||
* Notable side effects:
|
||||
*
|
||||
* 1) As a side effect of the function call the function removes the readable
|
||||
* event handler from "fd", unless the return value is PSYNC_WAIT_REPLY.
|
||||
* 2) server.repl_master_initial_offset is set to the right value according
|
||||
* to the master reply. This will be used to populate the 'server.master'
|
||||
* structure replication offset.
|
||||
*/
|
||||
|
||||
#define PSYNC_CONTINUE 0
|
||||
#define PSYNC_FULLRESYNC 1
|
||||
#define PSYNC_NOT_SUPPORTED 2
|
||||
int slaveTryPartialResynchronization(int fd) {
|
||||
#define PSYNC_WRITE_ERROR 0
|
||||
#define PSYNC_WAIT_REPLY 1
|
||||
#define PSYNC_CONTINUE 2
|
||||
#define PSYNC_FULLRESYNC 3
|
||||
#define PSYNC_NOT_SUPPORTED 4
|
||||
int slaveTryPartialResynchronization(int fd, int read_reply) {
|
||||
char *psync_runid;
|
||||
char psync_offset[32];
|
||||
sds reply;
|
||||
|
||||
/* Initially set repl_master_initial_offset to -1 to mark the current
|
||||
* master run_id and offset as not valid. Later if we'll be able to do
|
||||
* a FULL resync using the PSYNC command we'll set the offset at the
|
||||
* right value, so that this information will be propagated to the
|
||||
* client structure representing the master into server.master. */
|
||||
server.repl_master_initial_offset = -1;
|
||||
/* Writing half */
|
||||
if (!read_reply) {
|
||||
/* Initially set repl_master_initial_offset to -1 to mark the current
|
||||
* master run_id and offset as not valid. Later if we'll be able to do
|
||||
* a FULL resync using the PSYNC command we'll set the offset at the
|
||||
* right value, so that this information will be propagated to the
|
||||
* client structure representing the master into server.master. */
|
||||
server.repl_master_initial_offset = -1;
|
||||
|
||||
if (server.cached_master) {
|
||||
psync_runid = server.cached_master->replrunid;
|
||||
snprintf(psync_offset,sizeof(psync_offset),"%lld", server.cached_master->reploff+1);
|
||||
redisLog(REDIS_NOTICE,"Trying a partial resynchronization (request %s:%s).", psync_runid, psync_offset);
|
||||
} else {
|
||||
redisLog(REDIS_NOTICE,"Partial resynchronization not possible (no cached master)");
|
||||
psync_runid = "?";
|
||||
memcpy(psync_offset,"-1",3);
|
||||
if (server.cached_master) {
|
||||
psync_runid = server.cached_master->replrunid;
|
||||
snprintf(psync_offset,sizeof(psync_offset),"%lld", server.cached_master->reploff+1);
|
||||
redisLog(REDIS_NOTICE,"Trying a partial resynchronization (request %s:%s).", psync_runid, psync_offset);
|
||||
} else {
|
||||
redisLog(REDIS_NOTICE,"Partial resynchronization not possible (no cached master)");
|
||||
psync_runid = "?";
|
||||
memcpy(psync_offset,"-1",3);
|
||||
}
|
||||
|
||||
/* Issue the PSYNC command */
|
||||
reply = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"PSYNC",psync_runid,psync_offset,NULL);
|
||||
if (reply != NULL) {
|
||||
redisLog(REDIS_WARNING,"Unable to send PSYNC to master: %s",reply);
|
||||
sdsfree(reply);
|
||||
aeDeleteFileEvent(server.el,fd,AE_READABLE);
|
||||
return PSYNC_WRITE_ERROR;
|
||||
}
|
||||
return PSYNC_WAIT_REPLY;
|
||||
}
|
||||
|
||||
/* Issue the PSYNC command */
|
||||
reply = sendSynchronousCommand(fd,"PSYNC",psync_runid,psync_offset,NULL);
|
||||
/* Reading half */
|
||||
reply = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
|
||||
if (sdslen(reply) == 0) {
|
||||
/* The master may send empty newlines after it receives PSYNC
|
||||
* and before to reply, just to keep the connection alive. */
|
||||
sdsfree(reply);
|
||||
return PSYNC_WAIT_REPLY;
|
||||
}
|
||||
|
||||
aeDeleteFileEvent(server.el,fd,AE_READABLE);
|
||||
|
||||
if (!strncmp(reply,"+FULLRESYNC",11)) {
|
||||
char *runid = NULL, *offset = NULL;
|
||||
@@ -1190,7 +1349,7 @@ int slaveTryPartialResynchronization(int fd) {
|
||||
return PSYNC_CONTINUE;
|
||||
}
|
||||
|
||||
/* If we reach this point we receied either an error since the master does
|
||||
/* If we reach this point we received either an error since the master does
|
||||
* not understand PSYNC, or an unexpected reply from the master.
|
||||
* Return PSYNC_NOT_SUPPORTED to the caller in both cases. */
|
||||
|
||||
@@ -1209,7 +1368,7 @@ int slaveTryPartialResynchronization(int fd) {
|
||||
}
|
||||
|
||||
void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
char tmpfile[256], *err;
|
||||
char tmpfile[256], *err = NULL;
|
||||
int dfd, maxtries = 5;
|
||||
int sockerr = 0, psync_result;
|
||||
socklen_t errlen = sizeof(sockerr);
|
||||
@@ -1228,16 +1387,12 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &sockerr, &errlen) == -1)
|
||||
sockerr = errno;
|
||||
if (sockerr) {
|
||||
aeDeleteFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE);
|
||||
redisLog(REDIS_WARNING,"Error condition on socket for SYNC: %s",
|
||||
strerror(sockerr));
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* If we were connecting, it's time to send a non blocking PING, we want to
|
||||
* make sure the master is able to reply before going into the actual
|
||||
* replication process where we have long timeouts in the order of
|
||||
* seconds (in the meantime the slave would block). */
|
||||
/* Send a PING to check the master is able to reply without errors. */
|
||||
if (server.repl_state == REDIS_REPL_CONNECTING) {
|
||||
redisLog(REDIS_NOTICE,"Non blocking connect for SYNC fired the event.");
|
||||
/* Delete the writable event so that the readable event remains
|
||||
@@ -1246,70 +1401,109 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
server.repl_state = REDIS_REPL_RECEIVE_PONG;
|
||||
/* Send the PING, don't check for errors at all, we have the timeout
|
||||
* that will take care about this. */
|
||||
syncWrite(fd,"PING\r\n",6,100);
|
||||
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"PING",NULL);
|
||||
if (err) goto write_error;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Receive the PONG command. */
|
||||
if (server.repl_state == REDIS_REPL_RECEIVE_PONG) {
|
||||
char buf[1024];
|
||||
|
||||
/* Delete the readable event, we no longer need it now that there is
|
||||
* the PING reply to read. */
|
||||
aeDeleteFileEvent(server.el,fd,AE_READABLE);
|
||||
|
||||
/* Read the reply with explicit timeout. */
|
||||
buf[0] = '\0';
|
||||
if (syncReadLine(fd,buf,sizeof(buf),
|
||||
server.repl_syncio_timeout*1000) == -1)
|
||||
{
|
||||
redisLog(REDIS_WARNING,
|
||||
"I/O error reading PING reply from master: %s",
|
||||
strerror(errno));
|
||||
goto error;
|
||||
}
|
||||
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
|
||||
|
||||
/* 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)
|
||||
if (err[0] != '+' &&
|
||||
strncmp(err,"-NOAUTH",7) != 0 &&
|
||||
strncmp(err,"-ERR operation not permitted",28) != 0)
|
||||
{
|
||||
redisLog(REDIS_WARNING,"Error reply to PING from master: '%s'",buf);
|
||||
redisLog(REDIS_WARNING,"Error reply to PING from master: '%s'",err);
|
||||
sdsfree(err);
|
||||
goto error;
|
||||
} else {
|
||||
redisLog(REDIS_NOTICE,
|
||||
"Master replied to PING, replication can continue...");
|
||||
}
|
||||
sdsfree(err);
|
||||
server.repl_state = REDIS_REPL_SEND_AUTH;
|
||||
}
|
||||
|
||||
/* AUTH with the master if required. */
|
||||
if(server.masterauth) {
|
||||
err = sendSynchronousCommand(fd,"AUTH",server.masterauth,NULL);
|
||||
if (server.repl_state == REDIS_REPL_SEND_AUTH) {
|
||||
if (server.masterauth) {
|
||||
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"AUTH",server.masterauth,NULL);
|
||||
if (err) goto write_error;
|
||||
server.repl_state = REDIS_REPL_RECEIVE_AUTH;
|
||||
return;
|
||||
} else {
|
||||
server.repl_state = REDIS_REPL_SEND_PORT;
|
||||
}
|
||||
}
|
||||
|
||||
/* Receive AUTH reply. */
|
||||
if (server.repl_state == REDIS_REPL_RECEIVE_AUTH) {
|
||||
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
|
||||
if (err[0] == '-') {
|
||||
redisLog(REDIS_WARNING,"Unable to AUTH to MASTER: %s",err);
|
||||
sdsfree(err);
|
||||
goto error;
|
||||
}
|
||||
sdsfree(err);
|
||||
server.repl_state = REDIS_REPL_SEND_PORT;
|
||||
}
|
||||
|
||||
/* Set the slave port, so that Master's INFO command can list the
|
||||
* slave listening port correctly. */
|
||||
{
|
||||
if (server.repl_state == REDIS_REPL_SEND_PORT) {
|
||||
sds port = sdsfromlonglong(server.port);
|
||||
err = sendSynchronousCommand(fd,"REPLCONF","listening-port",port,
|
||||
NULL);
|
||||
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"REPLCONF",
|
||||
"listening-port",port, NULL);
|
||||
sdsfree(port);
|
||||
if (err) goto write_error;
|
||||
sdsfree(err);
|
||||
server.repl_state = REDIS_REPL_RECEIVE_PORT;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Receive REPLCONF listening-port reply. */
|
||||
if (server.repl_state == REDIS_REPL_RECEIVE_PORT) {
|
||||
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
|
||||
/* Ignore the error if any, not all the Redis versions support
|
||||
* REPLCONF listening-port. */
|
||||
if (err[0] == '-') {
|
||||
redisLog(REDIS_NOTICE,"(Non critical) Master does not understand REPLCONF listening-port: %s", err);
|
||||
redisLog(REDIS_NOTICE,"(Non critical) Master does not understand "
|
||||
"REPLCONF listening-port: %s", err);
|
||||
}
|
||||
sdsfree(err);
|
||||
server.repl_state = REDIS_REPL_SEND_CAPA;
|
||||
}
|
||||
|
||||
/* Inform the master of our capabilities. While we currently send
|
||||
* just one capability, it is possible to chain new capabilities here
|
||||
* in the form of REPLCONF capa X capa Y capa Z ...
|
||||
* The master will ignore capabilities it does not understand. */
|
||||
if (server.repl_state == REDIS_REPL_SEND_CAPA) {
|
||||
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"REPLCONF",
|
||||
"capa","eof",NULL);
|
||||
if (err) goto write_error;
|
||||
sdsfree(err);
|
||||
server.repl_state = REDIS_REPL_RECEIVE_CAPA;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Receive CAPA reply. */
|
||||
if (server.repl_state == REDIS_REPL_RECEIVE_CAPA) {
|
||||
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
|
||||
/* Ignore the error if any, not all the Redis versions support
|
||||
* REPLCONF capa. */
|
||||
if (err[0] == '-') {
|
||||
redisLog(REDIS_NOTICE,"(Non critical) Master does not understand "
|
||||
"REPLCONF capa: %s", err);
|
||||
}
|
||||
sdsfree(err);
|
||||
server.repl_state = REDIS_REPL_SEND_PSYNC;
|
||||
}
|
||||
|
||||
/* Try a partial resynchonization. If we don't have a cached master
|
||||
@@ -1317,12 +1511,41 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
* to start a full resynchronization so that we get the master run id
|
||||
* and the global offset, to try a partial resync at the next
|
||||
* reconnection attempt. */
|
||||
psync_result = slaveTryPartialResynchronization(fd);
|
||||
if (server.repl_state == REDIS_REPL_SEND_PSYNC) {
|
||||
if (slaveTryPartialResynchronization(fd,0) == PSYNC_WRITE_ERROR) {
|
||||
err = sdsnew("Write error sending the PSYNC command.");
|
||||
goto write_error;
|
||||
}
|
||||
server.repl_state = REDIS_REPL_RECEIVE_PSYNC;
|
||||
return;
|
||||
}
|
||||
|
||||
/* If reached this point, we should be in REDIS_REPL_RECEIVE_PSYNC. */
|
||||
if (server.repl_state != REDIS_REPL_RECEIVE_PSYNC) {
|
||||
redisLog(REDIS_WARNING,"syncWithMaster(): state machine error, "
|
||||
"state should be RECEIVE_PSYNC but is %d",
|
||||
server.repl_state);
|
||||
goto error;
|
||||
}
|
||||
|
||||
psync_result = slaveTryPartialResynchronization(fd,1);
|
||||
if (psync_result == PSYNC_WAIT_REPLY) return; /* Try again later... */
|
||||
|
||||
/* Note: if PSYNC does not return WAIT_REPLY, it will take care of
|
||||
* uninstalling the read handler from the file descriptor. */
|
||||
|
||||
if (psync_result == PSYNC_CONTINUE) {
|
||||
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Master accepted a Partial Resynchronization.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* PSYNC failed or is not supported: we want our slaves to resync with us
|
||||
* as well, if we have any (chained replication case). The mater may
|
||||
* transfer us an entirely different data set and we have no way to
|
||||
* incrementally feed our slaves after that. */
|
||||
disconnectSlaves(); /* Force our slaves to resync with us as well. */
|
||||
freeReplicationBacklog(); /* Don't allow our chained slaves to PSYNC. */
|
||||
|
||||
/* Fall back to SYNC if needed. Otherwise psync_result == PSYNC_FULLRESYNC
|
||||
* and the server.repl_master_runid and repl_master_initial_offset are
|
||||
* already populated. */
|
||||
@@ -1368,16 +1591,22 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
return;
|
||||
|
||||
error:
|
||||
aeDeleteFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE);
|
||||
close(fd);
|
||||
server.repl_transfer_s = -1;
|
||||
server.repl_state = REDIS_REPL_CONNECT;
|
||||
return;
|
||||
|
||||
write_error: /* Handle sendSynchronousCommand(SYNC_CMD_WRITE) errors. */
|
||||
redisLog(REDIS_WARNING,"Sending command to master in replication handshake: %s", err);
|
||||
sdsfree(err);
|
||||
goto error;
|
||||
}
|
||||
|
||||
int connectWithMaster(void) {
|
||||
int fd;
|
||||
|
||||
fd = anetTcpNonBlockBindConnect(NULL,
|
||||
fd = anetTcpNonBlockBestEffortBindConnect(NULL,
|
||||
server.masterhost,server.masterport,REDIS_BIND_ADDR);
|
||||
if (fd == -1) {
|
||||
redisLog(REDIS_WARNING,"Unable to connect to MASTER: %s",
|
||||
@@ -1405,7 +1634,7 @@ void undoConnectWithMaster(void) {
|
||||
int fd = server.repl_transfer_s;
|
||||
|
||||
redisAssert(server.repl_state == REDIS_REPL_CONNECTING ||
|
||||
server.repl_state == REDIS_REPL_RECEIVE_PONG);
|
||||
slaveIsInHandshakeState());
|
||||
aeDeleteFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE);
|
||||
close(fd);
|
||||
server.repl_transfer_s = -1;
|
||||
@@ -1424,7 +1653,7 @@ int cancelReplicationHandshake(void) {
|
||||
if (server.repl_state == REDIS_REPL_TRANSFER) {
|
||||
replicationAbortSyncTransfer();
|
||||
} else if (server.repl_state == REDIS_REPL_CONNECTING ||
|
||||
server.repl_state == REDIS_REPL_RECEIVE_PONG)
|
||||
slaveIsInHandshakeState())
|
||||
{
|
||||
undoConnectWithMaster();
|
||||
} else {
|
||||
@@ -1470,6 +1699,17 @@ void replicationUnsetMaster(void) {
|
||||
server.repl_state = REDIS_REPL_NONE;
|
||||
}
|
||||
|
||||
/* This function is called when the slave lose the connection with the
|
||||
* master into an unexpected way. */
|
||||
void replicationHandleMasterDisconnection(void) {
|
||||
server.master = NULL;
|
||||
server.repl_state = REDIS_REPL_CONNECT;
|
||||
server.repl_down_since = server.unixtime;
|
||||
/* We lost connection with our master, don't disconnect slaves yet,
|
||||
* maybe we'll be able to PSYNC with our master later. We'll disconnect
|
||||
* the slaves only if we'll have to do a full resync with our master. */
|
||||
}
|
||||
|
||||
void slaveofCommand(redisClient *c) {
|
||||
/* SLAVEOF is not allowed in cluster mode as replication is automatically
|
||||
* configured using the current address of the master node. */
|
||||
@@ -1484,7 +1724,10 @@ void slaveofCommand(redisClient *c) {
|
||||
!strcasecmp(c->argv[2]->ptr,"one")) {
|
||||
if (server.masterhost) {
|
||||
replicationUnsetMaster();
|
||||
redisLog(REDIS_NOTICE,"MASTER MODE enabled (user request)");
|
||||
sds client = catClientInfoString(sdsempty(),c);
|
||||
redisLog(REDIS_NOTICE,
|
||||
"MASTER MODE enabled (user request from '%s')",client);
|
||||
sdsfree(client);
|
||||
}
|
||||
} else {
|
||||
long port;
|
||||
@@ -1502,8 +1745,10 @@ void slaveofCommand(redisClient *c) {
|
||||
/* There was no previous master or the user specified a different one,
|
||||
* we can continue. */
|
||||
replicationSetMaster(c->argv[1]->ptr, port);
|
||||
redisLog(REDIS_NOTICE,"SLAVE OF %s:%d enabled (user request)",
|
||||
server.masterhost, server.masterport);
|
||||
sds client = catClientInfoString(sdsempty(),c);
|
||||
redisLog(REDIS_NOTICE,"SLAVE OF %s:%d enabled (user request from '%s')",
|
||||
server.masterhost, server.masterport, client);
|
||||
sdsfree(client);
|
||||
}
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
@@ -1543,14 +1788,17 @@ void roleCommand(redisClient *c) {
|
||||
addReplyBulkCBuffer(c,"slave",5);
|
||||
addReplyBulkCString(c,server.masterhost);
|
||||
addReplyLongLong(c,server.masterport);
|
||||
switch(server.repl_state) {
|
||||
case REDIS_REPL_NONE: slavestate = "none"; break;
|
||||
case REDIS_REPL_CONNECT: slavestate = "connect"; break;
|
||||
case REDIS_REPL_CONNECTING: slavestate = "connecting"; break;
|
||||
case REDIS_REPL_RECEIVE_PONG: /* see next */
|
||||
case REDIS_REPL_TRANSFER: slavestate = "sync"; break;
|
||||
case REDIS_REPL_CONNECTED: slavestate = "connected"; break;
|
||||
default: slavestate = "unknown"; break;
|
||||
if (slaveIsInHandshakeState()) {
|
||||
slavestate = "handshake";
|
||||
} else {
|
||||
switch(server.repl_state) {
|
||||
case REDIS_REPL_NONE: slavestate = "none"; break;
|
||||
case REDIS_REPL_CONNECT: slavestate = "connect"; break;
|
||||
case REDIS_REPL_CONNECTING: slavestate = "connecting"; break;
|
||||
case REDIS_REPL_TRANSFER: slavestate = "sync"; break;
|
||||
case REDIS_REPL_CONNECTED: slavestate = "connected"; break;
|
||||
default: slavestate = "unknown"; break;
|
||||
}
|
||||
}
|
||||
addReplyBulkCString(c,slavestate);
|
||||
addReplyLongLong(c,server.master ? server.master->reploff : -1);
|
||||
@@ -1936,11 +2184,13 @@ long long replicationGetSlaveOffset(void) {
|
||||
|
||||
/* Replication cron function, called 1 time per second. */
|
||||
void replicationCron(void) {
|
||||
static long long replication_cron_loops = 0;
|
||||
|
||||
/* Non blocking connection timeout? */
|
||||
if (server.masterhost &&
|
||||
(server.repl_state == REDIS_REPL_CONNECTING ||
|
||||
server.repl_state == REDIS_REPL_RECEIVE_PONG) &&
|
||||
(time(NULL)-server.repl_transfer_lastio) > server.repl_timeout)
|
||||
slaveIsInHandshakeState()) &&
|
||||
(time(NULL)-server.repl_transfer_lastio) > server.repl_timeout)
|
||||
{
|
||||
redisLog(REDIS_WARNING,"Timeout connecting to the MASTER...");
|
||||
undoConnectWithMaster();
|
||||
@@ -1982,31 +2232,34 @@ 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 * server.hz))) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
robj *ping_argv[1];
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
robj *ping_argv[1];
|
||||
|
||||
/* First, send PING */
|
||||
/* First, send PING according to ping_slave_period. */
|
||||
if ((replication_cron_loops % server.repl_ping_slave_period) == 0) {
|
||||
ping_argv[0] = createStringObject("PING",4);
|
||||
replicationFeedSlaves(server.slaves, server.slaveseldb, ping_argv, 1);
|
||||
replicationFeedSlaves(server.slaves, server.slaveseldb,
|
||||
ping_argv, 1);
|
||||
decrRefCount(ping_argv[0]);
|
||||
}
|
||||
|
||||
/* Second, send a newline to all the slaves in pre-synchronization
|
||||
* stage, that is, slaves waiting for the master to create the RDB file.
|
||||
* The newline will be ignored by the slave but will refresh the
|
||||
* last-io timer preventing a timeout. */
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *slave = ln->value;
|
||||
/* Second, send a newline to all the slaves in pre-synchronization
|
||||
* stage, that is, slaves waiting for the master to create the RDB file.
|
||||
* The newline will be ignored by the slave but will refresh the
|
||||
* last-io timer preventing a timeout. In this case we ignore the
|
||||
* ping period and refresh the connection once per second since certain
|
||||
* timeouts are set at a few seconds (example: PSYNC response). */
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *slave = ln->value;
|
||||
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START ||
|
||||
(slave->replstate == REDIS_REPL_WAIT_BGSAVE_END &&
|
||||
server.rdb_child_type != REDIS_RDB_CHILD_TYPE_SOCKET))
|
||||
{
|
||||
if (write(slave->fd, "\n", 1) == -1) {
|
||||
/* Don't worry, it's just a ping. */
|
||||
}
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START ||
|
||||
(slave->replstate == REDIS_REPL_WAIT_BGSAVE_END &&
|
||||
server.rdb_child_type != REDIS_RDB_CHILD_TYPE_SOCKET))
|
||||
{
|
||||
if (write(slave->fd, "\n", 1) == -1) {
|
||||
/* Don't worry, it's just a ping. */
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2067,6 +2320,7 @@ void replicationCron(void) {
|
||||
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) {
|
||||
time_t idle, max_idle = 0;
|
||||
int slaves_waiting = 0;
|
||||
int mincapa = -1;
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
|
||||
@@ -2077,28 +2331,19 @@ void replicationCron(void) {
|
||||
idle = server.unixtime - slave->lastinteraction;
|
||||
if (idle > max_idle) max_idle = idle;
|
||||
slaves_waiting++;
|
||||
mincapa = (mincapa == -1) ? slave->slave_capa :
|
||||
(mincapa & slave->slave_capa);
|
||||
}
|
||||
}
|
||||
|
||||
if (slaves_waiting && max_idle > server.repl_diskless_sync_delay) {
|
||||
/* Start a BGSAVE. Usually with socket target, or with disk target
|
||||
* if there was a recent socket -> disk config change. */
|
||||
if (startBgsaveForReplication() == REDIS_OK) {
|
||||
/* It started! We need to change the state of slaves
|
||||
* from WAIT_BGSAVE_START to WAIT_BGSAVE_END in case
|
||||
* the current target is disk. Otherwise it was already done
|
||||
* by rdbSaveToSlavesSockets() which is called by
|
||||
* startBgsaveForReplication(). */
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *slave = ln->value;
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START)
|
||||
slave->replstate = REDIS_REPL_WAIT_BGSAVE_END;
|
||||
}
|
||||
}
|
||||
startBgsaveForReplication(mincapa);
|
||||
}
|
||||
}
|
||||
|
||||
/* Refresh the number of slaves with lag <= min-slaves-max-lag. */
|
||||
refreshGoodSlavesCount();
|
||||
replication_cron_loops++; /* Incremented with frequency 1 HZ. */
|
||||
}
|
||||
|
||||
+3
-1
@@ -598,6 +598,8 @@ void luaLoadLibraries(lua_State *lua) {
|
||||
void luaRemoveUnsupportedFunctions(lua_State *lua) {
|
||||
lua_pushnil(lua);
|
||||
lua_setglobal(lua,"loadfile");
|
||||
lua_pushnil(lua);
|
||||
lua_setglobal(lua,"dofile");
|
||||
}
|
||||
|
||||
/* This function installs metamethods in the global table _G that prevent
|
||||
@@ -895,7 +897,7 @@ int luaCreateFunction(redisClient *c, lua_State *lua, char *funcname, robj *body
|
||||
funcdef = sdscatlen(funcdef,funcname,42);
|
||||
funcdef = sdscatlen(funcdef,"() ",3);
|
||||
funcdef = sdscatlen(funcdef,body->ptr,sdslen(body->ptr));
|
||||
funcdef = sdscatlen(funcdef," end",4);
|
||||
funcdef = sdscatlen(funcdef,"\nend",4);
|
||||
|
||||
if (luaL_loadbuffer(lua,funcdef,sdslen(funcdef),"@user_script")) {
|
||||
addReplyErrorFormat(c,"Error compiling script (new function): %s\n",
|
||||
|
||||
+9
-4
@@ -1157,7 +1157,9 @@ void sentinelDelFlagsToDictOfRedisInstances(dict *instances, int flags) {
|
||||
* 1) Remove all slaves.
|
||||
* 2) Remove all sentinels.
|
||||
* 3) Remove most of the flags resulting from runtime operations.
|
||||
* 4) Reset timers to their default value.
|
||||
* 4) Reset timers to their default value. For example after a reset it will be
|
||||
* possible to failover again the same master ASAP, without waiting the
|
||||
* failover timeout delay.
|
||||
* 5) In the process of doing this undo the failover if in progress.
|
||||
* 6) Disconnect the connections with the master (will reconnect automatically).
|
||||
*/
|
||||
@@ -1180,7 +1182,7 @@ void sentinelResetMaster(sentinelRedisInstance *ri, int flags) {
|
||||
}
|
||||
ri->failover_state = SENTINEL_FAILOVER_STATE_NONE;
|
||||
ri->failover_state_change_time = 0;
|
||||
ri->failover_start_time = 0;
|
||||
ri->failover_start_time = 0; /* We can failover again ASAP. */
|
||||
ri->promoted_slave = NULL;
|
||||
sdsfree(ri->runid);
|
||||
sdsfree(ri->slave_master_host);
|
||||
@@ -1544,7 +1546,7 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
|
||||
slave_addr = master->addr;
|
||||
line = sdscatprintf(sdsempty(),
|
||||
"sentinel known-slave %s %s %d",
|
||||
master->name, ri->addr->ip, ri->addr->port);
|
||||
master->name, slave_addr->ip, slave_addr->port);
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,1);
|
||||
}
|
||||
dictReleaseIterator(di2);
|
||||
@@ -2790,6 +2792,7 @@ void sentinelCommand(redisClient *c) {
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"flushconfig")) {
|
||||
if (c->argc != 2) goto numargserr;
|
||||
sentinelFlushConfig();
|
||||
addReply(c,shared.ok);
|
||||
return;
|
||||
@@ -2797,6 +2800,7 @@ void sentinelCommand(redisClient *c) {
|
||||
/* SENTINEL REMOVE <name> */
|
||||
sentinelRedisInstance *ri;
|
||||
|
||||
if (c->argc != 3) goto numargserr;
|
||||
if ((ri = sentinelGetMasterByNameOrReplyError(c,c->argv[2]))
|
||||
== NULL) return;
|
||||
sentinelEvent(REDIS_WARNING,"-monitor",ri,"%@");
|
||||
@@ -2808,6 +2812,7 @@ void sentinelCommand(redisClient *c) {
|
||||
sentinelRedisInstance *ri;
|
||||
int usable;
|
||||
|
||||
if (c->argc != 3) goto numargserr;
|
||||
if ((ri = sentinelGetMasterByNameOrReplyError(c,c->argv[2]))
|
||||
== NULL) return;
|
||||
int result = sentinelIsQuorumReachable(ri,&usable);
|
||||
@@ -3513,7 +3518,7 @@ int compareSlavesForPromotion(const void *a, const void *b) {
|
||||
if ((*sa)->slave_repl_offset > (*sb)->slave_repl_offset) {
|
||||
return -1; /* a < b */
|
||||
} else if ((*sa)->slave_repl_offset < (*sb)->slave_repl_offset) {
|
||||
return 1; /* b > a */
|
||||
return 1; /* a > b */
|
||||
}
|
||||
|
||||
/* If the replication offset is the same select the slave with that has
|
||||
|
||||
+10
-2
@@ -678,9 +678,17 @@ void lremCommand(redisClient *c) {
|
||||
if (subject->encoding == REDIS_ENCODING_ZIPLIST)
|
||||
decrRefCount(obj);
|
||||
|
||||
if (listTypeLength(subject) == 0) dbDelete(c->db,c->argv[1]);
|
||||
if (removed) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"lrem",c->argv[1],c->db->id);
|
||||
}
|
||||
|
||||
if (listTypeLength(subject) == 0) {
|
||||
dbDelete(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
|
||||
}
|
||||
|
||||
addReplyLongLong(c,removed);
|
||||
if (removed) signalModifiedKey(c->db,c->argv[1]);
|
||||
}
|
||||
|
||||
/* This is the semantic of this command:
|
||||
|
||||
+1
-1
@@ -1248,7 +1248,7 @@ void zaddGenericCommand(redisClient *c, int flags) {
|
||||
if (zobj == NULL) {
|
||||
if (xx) goto reply_to_client; /* No key + XX option: nothing to do. */
|
||||
if (server.zset_max_ziplist_entries == 0 ||
|
||||
server.zset_max_ziplist_value < sdslen(c->argv[3]->ptr))
|
||||
server.zset_max_ziplist_value < sdslen(c->argv[scoreidx+1]->ptr))
|
||||
{
|
||||
zobj = createZsetObject();
|
||||
} else {
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
#define REDIS_VERSION "3.0.2"
|
||||
#define REDIS_VERSION "3.0.7"
|
||||
|
||||
+9
-1
@@ -613,7 +613,12 @@ static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsig
|
||||
/* When the insert position is not equal to the tail, we need to
|
||||
* make sure that the next entry can hold this entry's length in
|
||||
* its prevlen field. */
|
||||
int forcelarge = 0;
|
||||
nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;
|
||||
if (nextdiff == -4 && reqlen < 4) {
|
||||
nextdiff = 0;
|
||||
forcelarge = 1;
|
||||
}
|
||||
|
||||
/* Store offset because a realloc may change the address of zl. */
|
||||
offset = p-zl;
|
||||
@@ -626,7 +631,10 @@ static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsig
|
||||
memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);
|
||||
|
||||
/* Encode this entry's raw length in the next entry. */
|
||||
zipPrevEncodeLength(p+reqlen,reqlen);
|
||||
if (forcelarge)
|
||||
zipPrevEncodeLengthForceLarge(p+reqlen,reqlen);
|
||||
else
|
||||
zipPrevEncodeLength(p+reqlen,reqlen);
|
||||
|
||||
/* Update offset for tail */
|
||||
ZIPLIST_TAIL_OFFSET(zl) =
|
||||
|
||||
@@ -13,6 +13,24 @@ test "Cluster is up" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "Enable AOF in all the instances" {
|
||||
foreach_redis_id id {
|
||||
R $id config set appendonly yes
|
||||
# We use "appendfsync no" because it's fast but also guarantees that
|
||||
# write(2) is performed before replying to client.
|
||||
R $id config set appendfsync no
|
||||
}
|
||||
|
||||
foreach_redis_id id {
|
||||
wait_for_condition 1000 500 {
|
||||
[RI $id aof_rewrite_in_progress] == 0 &&
|
||||
[RI $id aof_enabled] == 1
|
||||
} else {
|
||||
fail "Failed to enable AOF on instance #$id"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Return nno-zero if the specified PID is about a process still in execution,
|
||||
# otherwise 0 is returned.
|
||||
proc process_is_running {pid} {
|
||||
@@ -100,3 +118,50 @@ test "Verify $numkeys keys for consistency with logical content" {
|
||||
assert {[$cluster lrange $key 0 -1] eq $value}
|
||||
}
|
||||
}
|
||||
|
||||
test "Crash and restart all the instances" {
|
||||
foreach_redis_id id {
|
||||
kill_instance redis $id
|
||||
restart_instance redis $id
|
||||
}
|
||||
}
|
||||
|
||||
test "Cluster should eventually be up again" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "Verify $numkeys keys after the crash & restart" {
|
||||
# Check that the Redis Cluster content matches our logical content.
|
||||
foreach {key value} [array get content] {
|
||||
assert {[$cluster lrange $key 0 -1] eq $value}
|
||||
}
|
||||
}
|
||||
|
||||
test "Disable AOF in all the instances" {
|
||||
foreach_redis_id id {
|
||||
R $id config set appendonly no
|
||||
}
|
||||
}
|
||||
|
||||
test "Verify slaves consistency" {
|
||||
set verified_masters 0
|
||||
foreach_redis_id id {
|
||||
set role [R $id role]
|
||||
lassign $role myrole myoffset slaves
|
||||
if {$myrole eq {slave}} continue
|
||||
set masterport [get_instance_attrib redis $id port]
|
||||
set masterdigest [R $id debug digest]
|
||||
foreach_redis_id sid {
|
||||
set srole [R $sid role]
|
||||
if {[lindex $srole 0] eq {master}} continue
|
||||
if {[lindex $srole 2] != $masterport} continue
|
||||
wait_for_condition 1000 500 {
|
||||
[R $sid debug digest] eq $masterdigest
|
||||
} else {
|
||||
fail "Master and slave data digest are different"
|
||||
}
|
||||
incr verified_masters
|
||||
}
|
||||
}
|
||||
assert {$verified_masters >= 5}
|
||||
}
|
||||
|
||||
@@ -45,3 +45,59 @@ foreach_redis_id id {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Now test the migration to a master which used to be a slave, after
|
||||
# a failver.
|
||||
|
||||
source "../tests/includes/init-tests.tcl"
|
||||
|
||||
# Create a cluster with 5 master and 10 slaves, so that we have 2
|
||||
# slaves for each master.
|
||||
test "Create a 5 nodes cluster" {
|
||||
create_cluster 5 10
|
||||
}
|
||||
|
||||
test "Cluster is up" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "Kill slave #7 of master #2. Only slave left is #12 now" {
|
||||
kill_instance redis 7
|
||||
}
|
||||
|
||||
set current_epoch [CI 1 cluster_current_epoch]
|
||||
|
||||
test "Killing master node #2, #12 should failover" {
|
||||
kill_instance redis 2
|
||||
}
|
||||
|
||||
test "Wait for failover" {
|
||||
wait_for_condition 1000 50 {
|
||||
[CI 1 cluster_current_epoch] > $current_epoch
|
||||
} else {
|
||||
fail "No failover detected"
|
||||
}
|
||||
}
|
||||
|
||||
test "Cluster should eventually be up again" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "Cluster is writable" {
|
||||
cluster_write_test 1
|
||||
}
|
||||
|
||||
test "Instance 12 is now a master without slaves" {
|
||||
assert {[RI 12 role] eq {master}}
|
||||
}
|
||||
|
||||
# The remaining instance is now without slaves. Some other slave
|
||||
# should migrate to it.
|
||||
|
||||
test "Master #12 should get at least one migrated replica" {
|
||||
wait_for_condition 1000 50 {
|
||||
[llength [lindex [R 12 role] 2]] >= 1
|
||||
} else {
|
||||
fail "Master #12 has no replicas"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,8 +99,25 @@ proc spawn_instance {type base_port count {conf {}}} {
|
||||
}
|
||||
}
|
||||
|
||||
proc log_crashes {} {
|
||||
set start_pattern {*REDIS BUG REPORT START*}
|
||||
set logs [glob */log.txt]
|
||||
foreach log $logs {
|
||||
set fd [open $log]
|
||||
set found 0
|
||||
while {[gets $fd line] >= 0} {
|
||||
if {[string match $start_pattern $line]} {
|
||||
puts "\n*** Crash report found in $log ***"
|
||||
set found 1
|
||||
}
|
||||
if {$found} {puts $line}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc cleanup {} {
|
||||
puts "Cleaning up..."
|
||||
log_crashes
|
||||
foreach pid $::pids {
|
||||
catch {exec kill -9 $pid}
|
||||
}
|
||||
@@ -110,6 +127,7 @@ proc cleanup {} {
|
||||
}
|
||||
|
||||
proc abort_sentinel_test msg {
|
||||
incr ::failed
|
||||
puts "WARNING: Aborting the test."
|
||||
puts ">>>>>>>> $msg"
|
||||
if {$::pause_on_error} pause_on_error
|
||||
@@ -248,6 +266,37 @@ proc test {descr code} {
|
||||
}
|
||||
}
|
||||
|
||||
# Check memory leaks when running on OSX using the "leaks" utility.
|
||||
proc check_leaks instance_types {
|
||||
if {[string match {*Darwin*} [exec uname -a]]} {
|
||||
puts -nonewline "Testing for memory leaks..."; flush stdout
|
||||
foreach type $instance_types {
|
||||
foreach_instance_id [set ::${type}_instances] id {
|
||||
if {[instance_is_killed $type $id]} continue
|
||||
set pid [get_instance_attrib $type $id pid]
|
||||
set output {0 leaks}
|
||||
catch {exec leaks $pid} output
|
||||
if {[string match {*process does not exist*} $output] ||
|
||||
[string match {*cannot examine*} $output]} {
|
||||
# In a few tests we kill the server process.
|
||||
set output "0 leaks"
|
||||
} else {
|
||||
puts -nonewline "$type/$pid "
|
||||
flush stdout
|
||||
}
|
||||
if {![string match {*0 leaks*} $output]} {
|
||||
puts [colorstr red "=== MEMORY LEAK DETECTED ==="]
|
||||
puts "Instance type $type, ID $id:"
|
||||
puts $output
|
||||
puts "==="
|
||||
incr ::failed
|
||||
}
|
||||
}
|
||||
}
|
||||
puts ""
|
||||
}
|
||||
}
|
||||
|
||||
# Execute all the units inside the 'tests' directory.
|
||||
proc run_tests {} {
|
||||
set tests [lsort [glob ../tests/*]]
|
||||
@@ -258,6 +307,7 @@ proc run_tests {} {
|
||||
if {[file isdirectory $test]} continue
|
||||
puts [colorstr yellow "Testing unit: [lindex [file split $test] end]"]
|
||||
source $test
|
||||
check_leaks {redis sentinel}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,5 +494,17 @@ proc restart_instance {type id} {
|
||||
set link [redis 127.0.0.1 $port]
|
||||
$link reconnect 1
|
||||
set_instance_attrib $type $id link $link
|
||||
|
||||
# Make sure the instance is not loading the dataset when this
|
||||
# function returns.
|
||||
while 1 {
|
||||
catch {[$link ping]} retval
|
||||
if {[string match {*LOADING*} $retval]} {
|
||||
after 100
|
||||
continue
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
-13
@@ -7,19 +7,19 @@ start_server [list overrides [list "dir" $server_path "dbfilename" "encodings.rd
|
||||
test "RDB encoding loading test" {
|
||||
r select 0
|
||||
csvdump r
|
||||
} {"compressible","string","aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
"hash","hash","a","1","aa","10","aaa","100","b","2","bb","20","bbb","200","c","3","cc","30","ccc","300","ddd","400","eee","5000000000",
|
||||
"hash_zipped","hash","a","1","b","2","c","3",
|
||||
"list","list","1","2","3","a","b","c","100000","6000000000","1","2","3","a","b","c","100000","6000000000","1","2","3","a","b","c","100000","6000000000",
|
||||
"list_zipped","list","1","2","3","a","b","c","100000","6000000000",
|
||||
"number","string","10"
|
||||
"set","set","1","100000","2","3","6000000000","a","b","c",
|
||||
"set_zipped_1","set","1","2","3","4",
|
||||
"set_zipped_2","set","100000","200000","300000","400000",
|
||||
"set_zipped_3","set","1000000000","2000000000","3000000000","4000000000","5000000000","6000000000",
|
||||
"string","string","Hello World"
|
||||
"zset","zset","a","1","b","2","c","3","aa","10","bb","20","cc","30","aaa","100","bbb","200","ccc","300","aaaa","1000","cccc","123456789","bbbb","5000000000",
|
||||
"zset_zipped","zset","a","1","b","2","c","3",
|
||||
} {"0","compressible","string","aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
"0","hash","hash","a","1","aa","10","aaa","100","b","2","bb","20","bbb","200","c","3","cc","30","ccc","300","ddd","400","eee","5000000000",
|
||||
"0","hash_zipped","hash","a","1","b","2","c","3",
|
||||
"0","list","list","1","2","3","a","b","c","100000","6000000000","1","2","3","a","b","c","100000","6000000000","1","2","3","a","b","c","100000","6000000000",
|
||||
"0","list_zipped","list","1","2","3","a","b","c","100000","6000000000",
|
||||
"0","number","string","10"
|
||||
"0","set","set","1","100000","2","3","6000000000","a","b","c",
|
||||
"0","set_zipped_1","set","1","2","3","4",
|
||||
"0","set_zipped_2","set","100000","200000","300000","400000",
|
||||
"0","set_zipped_3","set","1000000000","2000000000","3000000000","4000000000","5000000000","6000000000",
|
||||
"0","string","string","Hello World"
|
||||
"0","zset","zset","a","1","b","2","c","3","aa","10","bb","20","cc","30","aaa","100","bbb","200","ccc","300","aaaa","1000","cccc","123456789","bbbb","5000000000",
|
||||
"0","zset_zipped","zset","a","1","b","2","c","3",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,11 @@ proc stop_bg_complex_data {handle} {
|
||||
#
|
||||
# You can specifiy backlog size, ttl, delay before reconnection, test duration
|
||||
# in seconds, and an additional condition to verify at the end.
|
||||
proc test_psync {descr duration backlog_size backlog_ttl delay cond} {
|
||||
#
|
||||
# If reconnect is > 0, the test actually try to break the connection and
|
||||
# reconnect with the master, otherwise just the initial synchronization is
|
||||
# checked for consistency.
|
||||
proc test_psync {descr duration backlog_size backlog_ttl delay cond diskless reconnect} {
|
||||
start_server {tags {"repl"}} {
|
||||
start_server {} {
|
||||
|
||||
@@ -24,6 +28,8 @@ proc test_psync {descr duration backlog_size backlog_ttl delay cond} {
|
||||
|
||||
$master config set repl-backlog-size $backlog_size
|
||||
$master config set repl-backlog-ttl $backlog_ttl
|
||||
$master config set repl-diskless-sync $diskless
|
||||
$master config set repl-diskless-sync-delay 1
|
||||
|
||||
set load_handle0 [start_bg_complex_data $master_host $master_port 9 100000]
|
||||
set load_handle1 [start_bg_complex_data $master_host $master_port 11 100000]
|
||||
@@ -48,22 +54,24 @@ proc test_psync {descr duration backlog_size backlog_ttl delay cond} {
|
||||
}
|
||||
}
|
||||
|
||||
test "Test replication partial resync: $descr" {
|
||||
test "Test replication partial resync: $descr (diskless: $diskless, reconnect: $reconnect)" {
|
||||
# Now while the clients are writing data, break the maste-slave
|
||||
# link multiple times.
|
||||
for {set j 0} {$j < $duration*10} {incr j} {
|
||||
after 100
|
||||
# catch {puts "MASTER [$master dbsize] keys, SLAVE [$slave dbsize] keys"}
|
||||
if ($reconnect) {
|
||||
for {set j 0} {$j < $duration*10} {incr j} {
|
||||
after 100
|
||||
# catch {puts "MASTER [$master dbsize] keys, SLAVE [$slave dbsize] keys"}
|
||||
|
||||
if {($j % 20) == 0} {
|
||||
catch {
|
||||
if {$delay} {
|
||||
$slave multi
|
||||
$slave client kill $master_host:$master_port
|
||||
$slave debug sleep $delay
|
||||
$slave exec
|
||||
} else {
|
||||
$slave client kill $master_host:$master_port
|
||||
if {($j % 20) == 0} {
|
||||
catch {
|
||||
if {$delay} {
|
||||
$slave multi
|
||||
$slave client kill $master_host:$master_port
|
||||
$slave debug sleep $delay
|
||||
$slave exec
|
||||
} else {
|
||||
$slave client kill $master_host:$master_port
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,18 +106,23 @@ proc test_psync {descr duration backlog_size backlog_ttl delay cond} {
|
||||
}
|
||||
}
|
||||
|
||||
test_psync {ok psync} 6 1000000 3600 0 {
|
||||
assert {[s -1 sync_partial_ok] > 0}
|
||||
}
|
||||
foreach diskless {no yes} {
|
||||
test_psync {no reconnection, just sync} 6 1000000 3600 0 {
|
||||
} $diskless 0
|
||||
|
||||
test_psync {no backlog} 6 100 3600 0.5 {
|
||||
assert {[s -1 sync_partial_err] > 0}
|
||||
}
|
||||
test_psync {ok psync} 6 1000000 3600 0 {
|
||||
assert {[s -1 sync_partial_ok] > 0}
|
||||
} $diskless 1
|
||||
|
||||
test_psync {ok after delay} 3 100000000 3600 3 {
|
||||
assert {[s -1 sync_partial_ok] > 0}
|
||||
}
|
||||
test_psync {no backlog} 6 100 3600 0.5 {
|
||||
assert {[s -1 sync_partial_err] > 0}
|
||||
} $diskless 1
|
||||
|
||||
test_psync {backlog expired} 3 100000000 1 3 {
|
||||
assert {[s -1 sync_partial_err] > 0}
|
||||
test_psync {ok after delay} 3 100000000 3600 3 {
|
||||
assert {[s -1 sync_partial_ok] > 0}
|
||||
} $diskless 1
|
||||
|
||||
test_psync {backlog expired} 3 100000000 1 3 {
|
||||
assert {[s -1 sync_partial_err] > 0}
|
||||
} $diskless 1
|
||||
}
|
||||
|
||||
@@ -1,3 +1,56 @@
|
||||
proc log_file_matches {log pattern} {
|
||||
set fp [open $log r]
|
||||
set content [read $fp]
|
||||
close $fp
|
||||
string match $pattern $content
|
||||
}
|
||||
|
||||
start_server {tags {"repl"}} {
|
||||
set slave [srv 0 client]
|
||||
set slave_host [srv 0 host]
|
||||
set slave_port [srv 0 port]
|
||||
set slave_log [srv 0 stdout]
|
||||
start_server {} {
|
||||
set master [srv 0 client]
|
||||
set master_host [srv 0 host]
|
||||
set master_port [srv 0 port]
|
||||
|
||||
# Configure the master in order to hang waiting for the BGSAVE
|
||||
# operation, so that the slave remains in the handshake state.
|
||||
$master config set repl-diskless-sync yes
|
||||
$master config set repl-diskless-sync-delay 1000
|
||||
|
||||
# Use a short replication timeout on the slave, so that if there
|
||||
# are no bugs the timeout is triggered in a reasonable amount
|
||||
# of time.
|
||||
$slave config set repl-timeout 5
|
||||
|
||||
# Start the replication process...
|
||||
$slave slaveof $master_host $master_port
|
||||
|
||||
test {Slave enters handshake} {
|
||||
wait_for_condition 50 1000 {
|
||||
[string match *handshake* [$slave role]]
|
||||
} else {
|
||||
fail "Slave does not enter handshake state"
|
||||
}
|
||||
}
|
||||
|
||||
# But make the master unable to send
|
||||
# the periodic newlines to refresh the connection. The slave
|
||||
# should detect the timeout.
|
||||
$master debug sleep 10
|
||||
|
||||
test {Slave is able to detect timeout during handshake} {
|
||||
wait_for_condition 50 1000 {
|
||||
[log_file_matches $slave_log "*Timeout connecting to the MASTER*"]
|
||||
} else {
|
||||
fail "Slave is not able to detect timeout"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
start_server {tags {"repl"}} {
|
||||
set A [srv 0 client]
|
||||
set A_host [srv 0 host]
|
||||
|
||||
+36
-32
@@ -262,46 +262,50 @@ proc formatCommand {args} {
|
||||
|
||||
proc csvdump r {
|
||||
set o {}
|
||||
foreach k [lsort [{*}$r keys *]] {
|
||||
set type [{*}$r type $k]
|
||||
append o [csvstring $k] , [csvstring $type] ,
|
||||
switch $type {
|
||||
string {
|
||||
append o [csvstring [{*}$r get $k]] "\n"
|
||||
}
|
||||
list {
|
||||
foreach e [{*}$r lrange $k 0 -1] {
|
||||
append o [csvstring $e] ,
|
||||
for {set db 0} {$db < 16} {incr db} {
|
||||
{*}$r select $db
|
||||
foreach k [lsort [{*}$r keys *]] {
|
||||
set type [{*}$r type $k]
|
||||
append o [csvstring $db] , [csvstring $k] , [csvstring $type] ,
|
||||
switch $type {
|
||||
string {
|
||||
append o [csvstring [{*}$r get $k]] "\n"
|
||||
}
|
||||
append o "\n"
|
||||
}
|
||||
set {
|
||||
foreach e [lsort [{*}$r smembers $k]] {
|
||||
append o [csvstring $e] ,
|
||||
list {
|
||||
foreach e [{*}$r lrange $k 0 -1] {
|
||||
append o [csvstring $e] ,
|
||||
}
|
||||
append o "\n"
|
||||
}
|
||||
append o "\n"
|
||||
}
|
||||
zset {
|
||||
foreach e [{*}$r zrange $k 0 -1 withscores] {
|
||||
append o [csvstring $e] ,
|
||||
set {
|
||||
foreach e [lsort [{*}$r smembers $k]] {
|
||||
append o [csvstring $e] ,
|
||||
}
|
||||
append o "\n"
|
||||
}
|
||||
append o "\n"
|
||||
}
|
||||
hash {
|
||||
set fields [{*}$r hgetall $k]
|
||||
set newfields {}
|
||||
foreach {k v} $fields {
|
||||
lappend newfields [list $k $v]
|
||||
zset {
|
||||
foreach e [{*}$r zrange $k 0 -1 withscores] {
|
||||
append o [csvstring $e] ,
|
||||
}
|
||||
append o "\n"
|
||||
}
|
||||
set fields [lsort -index 0 $newfields]
|
||||
foreach kv $fields {
|
||||
append o [csvstring [lindex $kv 0]] ,
|
||||
append o [csvstring [lindex $kv 1]] ,
|
||||
hash {
|
||||
set fields [{*}$r hgetall $k]
|
||||
set newfields {}
|
||||
foreach {k v} $fields {
|
||||
lappend newfields [list $k $v]
|
||||
}
|
||||
set fields [lsort -index 0 $newfields]
|
||||
foreach kv $fields {
|
||||
append o [csvstring [lindex $kv 0]] ,
|
||||
append o [csvstring [lindex $kv 1]] ,
|
||||
}
|
||||
append o "\n"
|
||||
}
|
||||
append o "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
{*}$r select 9
|
||||
return $o
|
||||
}
|
||||
|
||||
|
||||
@@ -433,6 +433,32 @@ start_server {tags {"basic"}} {
|
||||
set e
|
||||
} {*ERR*index out of range}
|
||||
|
||||
test {MOVE can move key expire metadata as well} {
|
||||
r select 10
|
||||
r flushdb
|
||||
r select 9
|
||||
r set mykey foo ex 100
|
||||
r move mykey 10
|
||||
assert {[r ttl mykey] == -2}
|
||||
r select 10
|
||||
assert {[r ttl mykey] > 0 && [r ttl mykey] <= 100}
|
||||
assert {[r get mykey] eq "foo"}
|
||||
r select 9
|
||||
}
|
||||
|
||||
test {MOVE does not create an expire if it does not exist} {
|
||||
r select 10
|
||||
r flushdb
|
||||
r select 9
|
||||
r set mykey foo
|
||||
r move mykey 10
|
||||
assert {[r ttl mykey] == -2}
|
||||
r select 10
|
||||
assert {[r ttl mykey] == -1}
|
||||
assert {[r get mykey] eq "foo"}
|
||||
r select 9
|
||||
}
|
||||
|
||||
test {SET/GET keys in different DBs} {
|
||||
r set a hello
|
||||
r set b world
|
||||
|
||||
@@ -217,4 +217,95 @@ start_server {tags {"dump"}} {
|
||||
assert_match {IOERR*} $e
|
||||
}
|
||||
}
|
||||
|
||||
test {MIGRATE can migrate multiple keys at once} {
|
||||
set first [srv 0 client]
|
||||
r set key1 "v1"
|
||||
r set key2 "v2"
|
||||
r set key3 "v3"
|
||||
start_server {tags {"repl"}} {
|
||||
set second [srv 0 client]
|
||||
set second_host [srv 0 host]
|
||||
set second_port [srv 0 port]
|
||||
|
||||
assert {[$first exists key1] == 1}
|
||||
assert {[$second exists key1] == 0}
|
||||
set ret [r -1 migrate $second_host $second_port "" 9 5000 keys key1 key2 key3]
|
||||
assert {$ret eq {OK}}
|
||||
assert {[$first exists key1] == 0}
|
||||
assert {[$first exists key2] == 0}
|
||||
assert {[$first exists key3] == 0}
|
||||
assert {[$second get key1] eq {v1}}
|
||||
assert {[$second get key2] eq {v2}}
|
||||
assert {[$second get key3] eq {v3}}
|
||||
}
|
||||
}
|
||||
|
||||
test {MIGRATE with multiple keys must have empty key arg} {
|
||||
catch {r MIGRATE 127.0.0.1 6379 NotEmpty 9 5000 keys a b c} e
|
||||
set e
|
||||
} {*empty string*}
|
||||
|
||||
test {MIGRATE with mutliple keys migrate just existing ones} {
|
||||
set first [srv 0 client]
|
||||
r set key1 "v1"
|
||||
r set key2 "v2"
|
||||
r set key3 "v3"
|
||||
start_server {tags {"repl"}} {
|
||||
set second [srv 0 client]
|
||||
set second_host [srv 0 host]
|
||||
set second_port [srv 0 port]
|
||||
|
||||
set ret [r -1 migrate $second_host $second_port "" 9 5000 keys nokey-1 nokey-2 nokey-2]
|
||||
assert {$ret eq {NOKEY}}
|
||||
|
||||
assert {[$first exists key1] == 1}
|
||||
assert {[$second exists key1] == 0}
|
||||
set ret [r -1 migrate $second_host $second_port "" 9 5000 keys nokey-1 key1 nokey-2 key2 nokey-3 key3]
|
||||
assert {$ret eq {OK}}
|
||||
assert {[$first exists key1] == 0}
|
||||
assert {[$first exists key2] == 0}
|
||||
assert {[$first exists key3] == 0}
|
||||
assert {[$second get key1] eq {v1}}
|
||||
assert {[$second get key2] eq {v2}}
|
||||
assert {[$second get key3] eq {v3}}
|
||||
}
|
||||
}
|
||||
|
||||
test {MIGRATE with multiple keys: stress command rewriting} {
|
||||
set first [srv 0 client]
|
||||
r flushdb
|
||||
r mset a 1 b 2 c 3 d 4 c 5 e 6 f 7 g 8 h 9 i 10 l 11 m 12 n 13 o 14 p 15 q 16
|
||||
start_server {tags {"repl"}} {
|
||||
set second [srv 0 client]
|
||||
set second_host [srv 0 host]
|
||||
set second_port [srv 0 port]
|
||||
|
||||
set ret [r -1 migrate $second_host $second_port "" 9 5000 keys a b c d e f g h i l m n o p q]
|
||||
|
||||
assert {[$first dbsize] == 0}
|
||||
assert {[$second dbsize] == 15}
|
||||
}
|
||||
}
|
||||
|
||||
test {MIGRATE with multiple keys: delete just ack keys} {
|
||||
set first [srv 0 client]
|
||||
r flushdb
|
||||
r mset a 1 b 2 c 3 d 4 c 5 e 6 f 7 g 8 h 9 i 10 l 11 m 12 n 13 o 14 p 15 q 16
|
||||
start_server {tags {"repl"}} {
|
||||
set second [srv 0 client]
|
||||
set second_host [srv 0 host]
|
||||
set second_port [srv 0 port]
|
||||
|
||||
$second mset c _ d _; # Two busy keys and no REPLACE used
|
||||
|
||||
catch {r -1 migrate $second_host $second_port "" 9 5000 keys a b c d e f g h i l m n o p q} e
|
||||
|
||||
assert {[$first dbsize] == 2}
|
||||
assert {[$second dbsize] == 15}
|
||||
assert {[$first exists c] == 1}
|
||||
assert {[$first exists d] == 1}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -272,6 +272,10 @@ start_server {tags {"scripting"}} {
|
||||
} 0
|
||||
} {}
|
||||
|
||||
test {EVAL - Able to parse trailing comments} {
|
||||
r eval {return 'hello' --trailing comment} 0
|
||||
} {hello}
|
||||
|
||||
test {SCRIPTING FLUSH - is able to clear the scripts cache?} {
|
||||
r set mykey myval
|
||||
set v [r evalsha fd758d1589d044dd850a6f05d52f2eefd27f033f 1 mykey]
|
||||
|
||||
@@ -2,8 +2,8 @@ start_server {tags {"hash"}} {
|
||||
test {HSET/HLEN - Small hash creation} {
|
||||
array set smallhash {}
|
||||
for {set i 0} {$i < 8} {incr i} {
|
||||
set key [randstring 0 8 alpha]
|
||||
set val [randstring 0 8 alpha]
|
||||
set key __avoid_collisions__[randstring 0 8 alpha]
|
||||
set val __avoid_collisions__[randstring 0 8 alpha]
|
||||
if {[info exists smallhash($key)]} {
|
||||
incr i -1
|
||||
continue
|
||||
@@ -21,8 +21,8 @@ start_server {tags {"hash"}} {
|
||||
test {HSET/HLEN - Big hash creation} {
|
||||
array set bighash {}
|
||||
for {set i 0} {$i < 1024} {incr i} {
|
||||
set key [randstring 0 8 alpha]
|
||||
set val [randstring 0 8 alpha]
|
||||
set key __avoid_collisions__[randstring 0 8 alpha]
|
||||
set val __avoid_collisions__[randstring 0 8 alpha]
|
||||
if {[info exists bighash($key)]} {
|
||||
incr i -1
|
||||
continue
|
||||
@@ -33,7 +33,7 @@ start_server {tags {"hash"}} {
|
||||
list [r hlen bighash]
|
||||
} {1024}
|
||||
|
||||
test {Is the big hash encoded with a ziplist?} {
|
||||
test {Is the big hash encoded with an hash table?} {
|
||||
assert_encoding hashtable bighash
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,9 @@ GROUPS = [
|
||||
"connection",
|
||||
"server",
|
||||
"scripting",
|
||||
"hyperloglog"
|
||||
"hyperloglog",
|
||||
"cluster",
|
||||
"geo"
|
||||
].freeze
|
||||
|
||||
GROUPS_BY_NAME = Hash[*
|
||||
|
||||
Reference in New Issue
Block a user