Compare commits

...
162 Commits
Author SHA1 Message Date
antirez 48e24d54b7 Ziplist: insertion bug under particular conditions fixed.
Ziplists had a bug that was discovered while investigating a different
issue, resulting in a corrupted ziplist representation, and a likely
segmentation foult and/or data corruption of the last element of the
ziplist, once the ziplist is accessed again.

The bug happens when a specific set of insertions / deletions is
performed so that an entry is encoded to have a "prevlen" field (the
length of the previous entry) of 5 bytes but with a count that could be
encoded in a "prevlen" field of a since byte. This could happen when the
"cascading update" process called by ziplistInsert()/ziplistDelete() in
certain contitious forces the prevlen to be bigger than necessary in
order to avoid too much data moving around.

Once such an entry is generated, inserting a very small entry
immediately before it will result in a resizing of the ziplist for a
count smaller than the current ziplist length (which is a violation,
inserting code expects the ziplist to get bigger actually). So an FF
byte is inserted in a misplaced position. Moreover a realloc() is
performed with a count smaller than the ziplist current length so the
final bytes could be trashed as well.

SECURITY IMPLICATIONS:

Currently it looks like an attacker can only crash a Redis server by
providing specifically choosen commands. However a FF byte is written
and there are other memory operations that depend on a wrong count, so
even if it is not immediately apparent how to mount an attack in order
to execute code remotely, it is not impossible at all that this could be
done. Attacks always get better... and we did not spent enough time in
order to think how to exploit this issue, but security researchers
or malicious attackers could.
2017-02-01 15:03:18 +01:00
antirez 4a64bc3e40 Cluster: handle zero bytes at the end of nodes.conf. 2016-12-06 09:06:20 +01:00
Salvatore SanfilippoandGitHub 61c5f4eccb Merge pull request #3300 from andyli029/patch-3
fix comment "b > a" to "a > b"
2016-06-10 09:14:30 +02:00
andyli 124d94cc6b fix comment "b>a" to "a > b" 2016-06-07 14:42:50 +08:00
Adam Baldwinandantirez 59e1357bff Removed dofile() from Lua 2016-05-08 18:05:07 +02:00
Jan-Erik Redigerandantirez 6d567d0c6f Fix nanosecond conversion
1 microsecond = 1000 nanoseconds
1e3 = 1000
10e3 = 10000
2016-05-05 16:21:43 +02:00
Oran Agraandantirez d6864d2999 additional fix to issue #2948 2016-05-05 14:01:08 +02:00
antirez d8679070b8 New masters with slots are now targets of migration if others are.
This fixes issue #3043.

Before this fix, after a complete resharding of a master slots
to other nodes, the master remains empty and the slaves migrate away
to other masters with non-zero nodes. However the old master now empty,
is no longer considered a target for migration, because the system has
no way to tell it had slaves in the past.

This fix leaves the algorithm used in the past untouched, but adds a
new rule. When a new or old master which is empty and without slaves,
are assigend with their first slot, if other masters in the cluster have
slaves, they are automatically considered to be targets for replicas
migration.
2016-05-05 14:00:23 +02:00
antirez ca300f2d14 Minor redis-cli wording change in --help output. 2016-05-05 13:58:37 +02:00
antirez 33c8a59e97 redis-cli: don't free historyfile, is used later. 2016-05-05 13:58:11 +02:00
antirez 055958aa3b Cluster: make getNodeByQuery() responsible of -CLUSTERDOWN errors.
This fixes a bug introduced by d827dbf, and makes the code consistent
with the logic of always allowing, while the cluster is down, commands
that don't target any key.

As a side effect the code is also simpler now.
2016-05-05 11:47:01 +02:00
David Cavarandantirez 1d8078b7f1 Reverse redirect address parse
Fix issue in case the redirect address is in ipv6 format. Parse from behind to extract last part of the response which represents actual port.
2016-05-05 10:13:26 +02:00
antirez c3d9da0062 Bind both IPv4 and IPv6 or exit with an error by default.
Thanks to @tushar2708 for the PR. I applied a slightly different fix.
Thanks to @cespare for reporting.

Close #3024
Close #3020
2016-05-05 10:05:14 +02:00
Salvatore Sanfilippo 7146221f38 Merge pull request #3133 from c2nes/check-slots-exec
Ensure slots are rechecked on EXEC.
2016-05-05 09:32:07 +02:00
Ryosuke Hasebeandantirez bb5b8d3180 fix variable 2016-05-05 08:56:58 +02:00
Ryosuke Hasebeandantirez 90cd445535 fix check_open_slots 2016-05-05 08:56:58 +02:00
Chris ThunesandChris Thunes e0d0fb450c Ensure slots are rechecked on EXEC.
Fixes #2515.
2016-03-11 15:25:48 -05:00
antirez 401383f1da Remove Lua state reference from buffers in lua_cmsgpack. 2016-02-10 09:16:48 +01:00
yoav@monfort.co.ilandantirez 1dabbd0a41 cmsgpack: pass correct osize values to lua allocator, update correct buf free space in cmsgpack 2016-02-10 09:16:48 +01:00
antirez d695f3e899 Fix NOTIFY macro names after cherry pick of fix. 2016-02-05 15:57:42 +01:00
Itamar Haberandantirez 593cf5989e Adds keyspace notifications for lrem 2016-02-05 15:56:34 +01:00
antirez 92fd7dac35 Merge branch '3.0' of github.com:/antirez/redis into 3.0 2016-01-31 21:46:30 +01:00
antirez 3c46377ec5 Fix wrong macro name after merge of 3.2 code. (again). 2016-01-31 21:46:07 +01:00
Salvatore Sanfilippo 698cbf15a3 Merge pull request #3048 from itamarhaber/patch-4
Removes more spuriousness from 3.0.7
2016-01-31 08:57:14 +01:00
Itamar Haber 9969d7f787 Removes more spuriousness from 3.0.7 2016-01-30 23:23:19 +02:00
antirez 49b7f0ec45 Fix wrong macro name after merge of 3.2 code. 2016-01-29 22:22:12 +01:00
antirez 301ed84ec8 Typo ASII -> ASCII fixed in comment. 2016-01-29 12:08:37 +01:00
antirez 7d016e6f39 Cluster: include node IDs in SLOTS output.
CLUSTER SLOTS now includes IDs in the nodes description associated with
a given slot range. Certain client libraries implementations need a way
to reference a node in an unique way, so they were relying on CLUSTER
NODES, that is not a stable API and may change frequently depending on
Redis Cluster future requirements.
2016-01-29 12:02:27 +01:00
antirez 438942a540 Remove spurious entries in 3.0.7 changelog.
Certain things were only applicable to 3.2.0 RC2 and RC3.
2016-01-29 09:18:48 +01:00
antirez 6bf60cbf5e UPDATE: Redis 3.0.7.
We had to fix a few last minutes bugs.
2016-01-28 13:02:27 +01:00
antirez 1a7e68556f Use a smoother running average for avg_ttl in INFO.
Reported here:
https://www.reddit.com/r/redis/comments/42r0i0/avg_ttl_varies_a_lot/
2016-01-26 15:29:48 +01:00
antirez 13f48d8dbf Fix merge conflicts from 3.2. 2016-01-26 14:25:49 +01:00
antirez 4d62a82b4a Cluster: mismatch sender ID log put back at DEBUG level. 2016-01-26 14:24:24 +01:00
antirez 4685f253c3 Cluster: fix missing ntohs() call to access gossip section port. 2016-01-26 14:22:46 +01:00
antirez 025f936cc4 Better address udpate strategy when processing gossip sections.
The change covers the case where:

1. There is a node we can't reach (in fail or pfail state).
2. We see a different address for this node, in the gossip section sent
to us by a node that, instead, is able to talk with the node we cannot
talk to.

In this case it's a good bet to switch to the address reported by this
node, since there was an address switch and it is able to talk with the
node and we are not.

However previosuly this was done in a dangerous way, by initiating an
handshake. The handshake, using the MEET packet, forces the receiver to
join our cluster, and this is not a good idea. If the node in question
really just switched address, but is the same node, it already knows about
us, so we just need to perform an address update and a reconnection.

So with this commit instead we just update the address of the node,
release the node link if any, and attempt to reconnect in the next
clusterCron() cycle.

The commit also improves debugging messages printed by Cluster during
address or ID switches.
2016-01-26 14:22:32 +01:00
antirez 5d9a533591 Fix memory leak in masterauth config option loading. 2016-01-26 14:21:49 +01:00
antirez 72f5326076 Fix merge issues with 3.2 backports. 2016-01-25 15:57:52 +01:00
antirez 53c9c299df Redis 3.0.7. 2016-01-25 15:54:36 +01:00
antirez d4090b169d Minor MIGRATE refactoring.
Centralize cleanup of newargv in a single place.
Add more comments to help a bit following a complex function.

Related to issue #3016.
2016-01-25 15:23:08 +01:00
antirez 29c89df46e More variadic MIGRATE fixes.
Another leak was fixed in the case of syntax error by restructuring the
allocation strategy for the two dynamic vectors.

We also make sure to always close the cached socket on I/O errors so that
all the I/O errors are handled the same, even if we had a previously
queued error of a different kind from the destination server.

Thanks to Kevin McGehee. Related to issue #3016.
2016-01-25 15:23:04 +01:00
antirez 14e1599660 Various fixes to MIGRATE with multiple keys.
In issue #3016 Kevin McGehee identified multiple very serious issues in
the new implementation of MIGRATE. This commit attempts to restructure
the code in oder to avoid mistakes, an analysis of the new
implementation is in progress in order to check for possible edge cases.
2016-01-25 15:22:58 +01:00
antirez 4300a973b8 Test: Handle LOADING in restart_instance. 2016-01-25 15:21:57 +01:00
antirez 5a402ce2d5 Detect and show crashes on Sentinel/Cluster tests. 2016-01-25 15:21:53 +01:00
antirez 515392c216 Cluster: fix setting nodes slaveof pointer to NULL on node release.
With this commit we preserve the list of nodes that have .slaveof set
to the node, even when the node is turned into a slave, and make sure to
fix the .slaveof pointers to NULL when a node is freed from memory,
regardless of the fact it's a slave or a master.

Basically we try to remember the logical master in the current
configuration even if the logical master advertised it as a slave
already. However we still remember the associations, so that when a node
is freed we can fix them.

This should fix issue #3002.
2016-01-25 15:21:49 +01:00
antirez d5872e8e31 Cluster: clarify node->slave may be NULL. 2016-01-25 15:21:43 +01:00
antirez 8cae6e955b Cluster: fix rebalancing to always empty nodes.
Because of rounding error even with weight=0 sometimes a node was left
with an assigned slot.

Close #3001.
2016-01-25 15:21:40 +01:00
antirez 791a295636 Cluster: redis-trib move_to_slot: don't send SETSLOT to slaves. 2016-01-25 15:21:36 +01:00
antirez f2879c25d1 Cluster: fix redis-trib reference of variable in warning. 2016-01-25 15:21:31 +01:00
antirez 49b1e78820 CLUSTER BUMPEPOCH initial implementation fixed. 2016-01-25 15:21:27 +01:00
antirez 7942e7090e Cluster: implement redis-trib fix when slot is open without owners.
Still work to do.
2016-01-25 15:21:24 +01:00
antirez beb5058e6f Cluster: implement redis-trib fix for uncovered slots. 2016-01-25 15:21:20 +01:00
antirez 8ef716d19a Cluster: CLUSTER BUMPEPOCH introduced to help redis-trib fix.
Sometimes during "fixes" we have to setup a new configuration and assign
slots to nodes. With BUMPEPOCH we can make sure the new configuration of
the node will win if there are conflicting configurations (for example
another node is *also* claiming the same slot because the cluster is
totally messed up).
2016-01-25 15:21:17 +01:00
antirez 5da1e640ad Cluster: don't allow CLUSTER SETSLOT with slaves. 2016-01-25 15:21:13 +01:00
antirez 53edd42a4e Cluster: check packets length before accessing far fields. 2016-01-19 13:18:08 +01:00
antirez e50b9a0757 Scripting: handle trailing comments.
This fix, provided by Paul Kulchenko (@pkulchenko), allows the Lua
scripting engine to evaluate statements with a trailing comment like the
following one:

    EVAL "print() --comment" 0

Lua can't parse the above if the string does not end with a newline, so
now a final newline is always added automatically. This does not change
the SHA1 of scripts since the SHA1 is computed on the body we pass to
EVAL, without the other code we add to register the function.

Close #2951.
2016-01-08 15:45:18 +01:00
antirez e9abc94483 Allow MIGRATE to always be called on local keys for open slots.
Extend the MIGRATE extra freedom to be able to be called in the context
of the local slot, anytime there is a slot open in one or the other
direction (importing or migrating). This is useful for redis-trib to fix
the cluster when it has in an odd state.

Thix fix allows "redis-trib fix" to make its work in certain cases where
previously an error was reported.
2016-01-08 15:36:37 +01:00
antirez 583194c8f4 Fix typos & grammar in clusterBumpConfigEpochWithoutConsensus() comment. 2016-01-08 15:35:44 +01:00
antirez 319a4c04c7 Cluster: don't send -ASK to MIGRATE.
For non existing keys, we don't want to send -ASK redirections to
MIGRATE, since when moving slots from the migrating node to the
importing node, we want just to ignore keys that are no longer there.
They may be expired or deleted between the GETKEYSINSLOT call and the
MIGRATE call. Otherwise this causes an error during migrations with
redis-trib (or equivalent cluster management tools).
2016-01-06 12:24:13 +01:00
antirez a8c2aa0f44 Cluster test: do leaks detection with OSX leaks utility. 2016-01-02 13:25:27 +01:00
antirez f9c971ff87 redis-trib: Remove duplicated key in hash initialization. 2016-01-02 13:25:27 +01:00
antirez 3d61cb0cb1 Suppress harmless warnings. 2015-12-18 16:19:47 +01:00
antirez ddc4d7f8c7 Changelog typo fixed. 2015-12-18 16:14:55 +01:00
antirez d51fdfda42 Redis 3.0.6 2015-12-18 16:10:43 +01:00
antirez 7ce7387202 Cluster: rebalance now supports --threshold option. 2015-12-18 15:52:22 +01:00
antirez b4b7c57cb0 Cluster: redis-trib reshard / rebalance --pipeline support. 2015-12-18 15:52:22 +01:00
antirez c48355e920 Cluster: verify slaves consistency after resharding. 2015-12-18 11:34:08 +01:00
antirez 99cb476500 Fix CMD_DENYOOM macro name after backporting. 2015-12-18 09:15:47 +01:00
antirez 514ee7135e Cluster: allows abbreviated node IDs with rebalance --weight option. 2015-12-18 09:12:24 +01:00
antirez 025628bd76 Cluster: rebalancing option --simulate, and a fix. 2015-12-18 09:12:20 +01:00
antirez f501f5f4c9 Cluster: redis-trib rebalance initial implementation. 2015-12-18 09:12:16 +01:00
antirez a49a57ccd1 Initial implementation of redis-trib info subcommand. 2015-12-18 09:12:11 +01:00
antirez 0bc1993879 fix sprintf and snprintf format string
There are some cases of printing unsigned integer with %d conversion
specificator and vice versa (signed integer with %u specificator).

Patch by Sergey Polovko. Backported to Redis from Disque.
2015-12-18 09:10:51 +01:00
antirez cb61d003ab Cluster: resharding test now checks AOF consistency.
It's a key invariant that when AOF is enabled, after the cluster
reshards, a crash-recovery event causes all the keys to be still fine
with the expected logical content. Now this is part of unit 04.
2015-12-17 17:53:33 +01:00
antirez d999f5a68e Fix a race that may lead to the active (slave) client to be freed.
In issue #2948 a crash was reported in processCommand(). Later Oran Agra
(@oranagra) traced the bug (in private chat) in the following sequence
of events:

1. Some maxmemory is set.
2. The slave is the currently active client and is executing PING or
   REPLCONF or whatever a slave can send to its master.
3. freeMemoryIfNeeded() is called since maxmemory is set.
4. flushSlavesOutputBuffers() is called by freeMemoryIfNeeded().
5. During slaves buffers flush, a write error could be encoutered in
   writeToClient() or sendReplyToClient() depending on the version of
   Redis. This will trigger freeClient() against the currently active
   client, so a segmentation fault will likely happen in
   processCommand() immediately after the call to freeMemoryIfNeeded().

There are different possible fixes:

1. Add flags to writeToClient() (recent versions code base) so that
   we can ignore the write errors, and use this flag in
   flushSlavesOutputBuffers(). However this is not simple to do in older
   versions of Redis.
2. Use freeClientAsync() during write errors. This works but changes the
   current behavior of releasing clients ASAP when possible. Normally
   we write to clients during the normal event loop processing, in the
   writable client, where there is no active client, so no care must be
   taken.
3. The fix of this commit: to detect that the current client is no
   longer valid. This fix is a bit "ad-hoc", but works across all the
   versions and has the advantage of not changing the remaining
   behavior. Only alters what happens during this race condition,
   hopefully.
2015-12-17 09:48:44 +01:00
antirez f1ab834658 Log address causing SIGSEGV. 2015-12-15 18:02:18 +01:00
Sun Heandantirez 8bb9cb38be lua_struct.c/getnum: throw error if overflow happen
Fix issue #2855
2015-12-14 17:58:55 +01:00
antirez a5d27d395f Fix 3.0 merge issues with new MIGRATE. 2015-12-13 10:23:04 +01:00
antirez ea89733833 Cluster: redis-trib: use variadic MIGRATE.
We use the new variadic/pipelined MIGRATE for faster migration.
Testing is not easy because to see the time it takes for a slot to be
migrated requires a very large data set, but even with all the overhead
of migrating multiple slots and to setup them properly, what used to
take 4 seconds (1 million keys, 200 slots migrated) is now 1.6 which is
a good improvement. However the improvement can be a lot larger if:

1. We use large datasets where a single slot has many keys.
2. By moving more than 10 keys per iteration, making this configurable,
   which is planned.

Close #2710
Close #2711
2015-12-13 10:17:44 +01:00
antirez 170adbf2c4 MIGRATE: Fix key extraction for new form. 2015-12-13 10:17:40 +01:00
antirez 8a02f61b27 MIGRATE: test more corner cases. 2015-12-13 10:17:37 +01:00
antirez 32e940c084 MIGRATE: Fix new argument rewriting refcount handling. 2015-12-13 10:17:33 +01:00
antirez c460458d57 MIGRATE: fix replies processing and argument rewriting.
We need to process replies after errors in order to delete keys
successfully transferred. Also argument rewriting was fixed since
it was broken in several ways. Now a fresh argument vector is created
and set if we are acknowledged of at least one key.
2015-12-13 10:17:26 +01:00
antirez 5912afc9d4 Test: pipelined MIGRATE tests added. 2015-12-13 10:16:46 +01:00
antirez 9276d78776 Pipelined multiple keys MIGRATE. 2015-12-13 10:16:03 +01:00
antirez 3ab63b5cc6 Cluster: redis-trib migrate default timeout set to 60 sec. 2015-12-11 11:00:39 +01:00
Salvatore Sanfilippo 71bf5604c6 Merge pull request #2918 from danmaz74/3.0
redis-trib.rb: --timeout XXXXX option added to fix and reshard
2015-12-11 10:57:05 +01:00
antirez bf09e58d9d Cluster: replica migration with delay.
We wait a fixed amount of time (5 seconds currently) much greater than
the usual Cluster node to node communication latency, before migrating.
This way when a failover occurs, before detecting the new master as a
target for migration, we give the time to its natural slaves (the slaves
of the failed over master) to announce they switched to the new master,
preventing an useless migration operation.
2015-12-11 09:26:15 +01:00
antirez 5ad4f7e0b1 Cluster: more reliable migration tests.
The old version was modeled with two failovers, however after the first
it is possible that another slave will migrate to the new master, since
for some time the new master is not backed by any slave. Probably there
should be some pause after a failover, before the migration. Anyway the
test is simpler in this way, and depends less on timing.
2015-12-10 13:00:18 +01:00
antirez 711bf140f3 Fix merge of cluster migrate-to flag. 2015-12-10 09:31:28 +01:00
antirez 6007ea3bcb Cluster: more reliable replicas migration test. 2015-12-10 09:30:27 +01:00
antirez 6d5d8d10a9 Remove debugging message left there for error. 2015-12-10 09:30:22 +01:00
antirez 2e43bcffaf Fix replicas migration by adding a new flag.
Some time ago I broken replicas migration (reported in #2924).
The idea was to prevent masters without replicas from getting replicas
because of replica migration, I remember it to create issues with tests,
but there is no clue in the commit message about why it was so
undesirable.

However my patch as a side effect totally ruined the concept of replicas
migration since we want it to work also for instances that, technically,
never had slaves in the past: promoted slaves.

So now instead the ability to be targeted by replicas migration, is a
new flag "migrate-to". It only applies to masters, and is set in the
following two cases:

1. When a master gets a slave, it is set.
2. When a slave turns into a master because of fail over, it is set.

This way replicas migration targets are only masters that used to have
slaves, and slaves of masters (that used to have slaves... obviously)
and are promoted.

The new flag is only internal, and is never exposed in the output nor
persisted in the nodes configuration, since all the information to
handle it are implicit in the cluster configuration we already have.
2015-12-10 09:30:13 +01:00
daniele 47bd2a09b4 redis-trib.rb: --timeout XXXXX option added to fix and reshard commands. Defaults to 15000 milliseconds 2015-12-06 22:47:57 +01:00
antirez 4f7d1e46cf Fix renamed define after merge. 2015-11-27 16:10:34 +01:00
antirez 3626699f1f Handle wait3() errors.
My guess was that wait3() with WNOHANG could never return -1 and an
error. However issue #2897 may possibly indicate that this could happen
under non clear conditions. While we try to understand this better,
better to handle a return value of -1 explicitly, otherwise in the
case a BGREWRITE is in progress but wait3() returns -1, the effect is to
match the first branch of the if/else block since server.rdb_child_pid
is -1, and call backgroundSaveDoneHandler() without a good reason, that
will, in turn, crash the Redis server with an assertion.
2015-11-27 16:09:49 +01:00
antirez fe71dffbf2 Redis Cluster: hint about validity factor when slave can't failover. 2015-11-27 11:34:30 +01:00
antirez 8e491b1708 Remove "s" flag for MIGRATE in command table.
Maybe there are legitimate use cases for MIGRATE inside Lua scripts, at
least for now. When the command will be executed in an asynchronous
fashion (planned) it is possible we'll no longer be able to permit it
from within Lua scripts.
2015-11-17 15:40:47 +01:00
antirez 3da69a9f22 Update redis-cli help and the script to generate it. 2015-11-17 15:40:18 +01:00
antirez d4f55990f8 Fix MIGRATE entry in command table.
Thanks to Oran Agra (@oranagra) for reporting. Key extraction would not
work otherwise and it does not make sense to take wrong data in the
command table.
2015-11-17 15:35:47 +01:00
antirez 28fb193ccd Fix error reply in subscribed Pub/Sub mode.
PING is now a valid command to issue in this context.
2015-11-09 11:12:03 +01:00
antirez c5f9f199df CONTRIBUTING updated. 2015-10-27 12:07:54 +01:00
antirez c7ec1a367a Redis 3.0.5 2015-10-15 15:44:54 +02:00
David Thomsonandantirez ab1f8ea508 Add back blank line 2015-10-15 13:06:31 +02:00
David Thomsonandantirez 1fa63a78bc Update import command to optionally use copy and replace parameters 2015-10-15 13:06:31 +02:00
antirez 568c83dda7 Cluster: redis-trib fix, coverage for migrating=1 case.
Kinda related to #2770.
2015-10-15 13:06:31 +02:00
antirez 892b1c3c58 Redis.conf example: make clear user must pass its path as argument. 2015-10-15 12:46:17 +02:00
antirez cbf6614c1a Regression test for issue #2813. 2015-10-15 11:25:19 +02:00
antirez 7cb8481053 Move end-comment of handshake states.
For an error I missed the last handshake state.
Related to issue #2813.
2015-10-15 10:22:13 +02:00
antirez 8242d069f1 Make clear that slave handshake states must be ordered.
Make sure that people from the future will not break this rule.
Related to issue #2813.
2015-10-15 10:22:13 +02:00
antirez 6ef80f4ed2 Minor changes to PR #2813.
* Function to test for slave handshake renamed slaveIsInHandshakeState.
* Function no longer accepts arguments since it always tests the
  same global state.
* Test for state translated to a range test since defines are guaranteed
  to stay in order in the future.
* Use the new function in the ROLE command implementation as well.
2015-10-15 10:22:13 +02:00
Kevin McGeheeandantirez dc03e4c51b Fix master timeout during handshake
This change allows a slave to properly time out a dead master during
the extended asynchronous synchronization state machine.  Now, slaves
will record their last interaction with the master and apply the
replication timeout before a response to the PSYNC request is received.
2015-10-15 10:22:13 +02:00
antirez 30978004b3 redis-cli pipe mode: don't stay in the write loop forever.
The code was broken and resulted in redis-cli --pipe to, most of the
times, writing everything received in the standard input to the Redis
connection socket without ever reading back the replies, until all the
content to write was written.

This means that Redis had to accumulate all the output in the output
buffers of the client, consuming a lot of memory.

Fixed thanks to the original report of anomalies in the behavior
provided by Twitter user @fsaintjacques.
2015-09-30 16:27:19 +02:00
antirez 652e662d1a Test: fix false positive in HSTRLEN test.
HINCRBY* tests later used the value "tmp" that was sometimes generated
by the random key generation function. The result was ovewriting what
Tcl expected to be inside Redis with another value, causing the next
HSTRLEN test to fail.
2015-09-15 09:38:26 +02:00
antirez a0ff29bcf2 Test: MOVE expire test improved.
Related to #2765.
2015-09-14 12:37:13 +02:00
antirez e2c0d89662 MOVE re-add TTL check fixed.
getExpire() returns -1 when no expire exists.

Related to #2765.
2015-09-14 12:36:34 +02:00
antirez 5b6c764711 MOVE now can move TTL metadata as well.
MOVE was not able to move the TTL: when a key was moved into a different
database number, it became persistent like if PERSIST was used.

In some incredible way (I guess almost nobody uses Redis MOVE) this bug
remained unnoticed inside Redis internals for many years.
Finally Andy Grunwald discovered it and opened an issue.

This commit fixes the bug and adds a regression test.

Close #2765.
2015-09-14 12:32:23 +02:00
antirez a74ef35f07 Release note typo fixed: senitel -> sentinel. 2015-09-08 10:41:03 +02:00
antirez 4698284b41 Redis 3.0.4. 2015-09-08 10:02:10 +02:00
antirez 718a826c51 Sentinel: command arity check added where missing. 2015-09-08 09:33:39 +02:00
Rogerio Goncalvesandantirez 3915e1c71a Check args before run ckquorum. Fix issue #2635 2015-09-08 09:28:25 +02:00
antirez ce4c17308e Fix merge issues in 490847c. 2015-09-07 17:29:21 +02:00
antirez 490847c681 Undo slaves state change on failed rdbSaveToSlavesSockets().
As Oran Agra suggested, in startBgsaveForReplication() when the BGSAVE
attempt returns an error, we scan the list of slaves in order to remove
them since there is no way to serve them currently.

However we check for the replication state BGSAVE_START, which was
modified by rdbSaveToSlaveSockets() before forking(). So when fork fails
the state of slaves remain BGSAVE_END and no cleanup is performed.

This commit fixes the problem by making rdbSaveToSlavesSockets() able to
undo the state change on fork failure.
2015-09-07 16:21:24 +02:00
antirez c20218eb57 Sentinel: fix bug in config rewriting during failover
We have a check to rewrite the config properly when a failover is in
progress, in order to add the current (already failed over) master as
slave, and don't include in the slave list the promoted slave itself.

However there was an issue, the variable with the right address was
computed but never used when the code was modified, and no tests are
available for this feature for two reasons:

1. The Sentinel unit test currently does not test Sentinel ability to
persist its state at all.
2. It is a very hard to trigger state since it lasts for little time in
the context of the testing framework.

However this feature should be covered in the test in some way.

The bug was found by @badboy using the clang static analyzer.

Effects of the bug on safety of Sentinel
===

This bug results in severe issues in the following case:

1. A Sentinel is elected leader.
2. During the failover, it persists a wrong config with a known-slave
entry listing the master address.
3. The Sentinel crashes and restarts, reading invalid configuration from
disk.
4. It sees that the slave now does not obey the logical configuration
(should replicate from the current master), so it sends a SLAVEOF
command to the master (since the slave master is the same) creating a
replication loop (attempt to replicate from itself) which Redis is
currently unable to detect.
5. This means that the master is no longer available because of the bug.

However the lack of availability should be only transient (at least
in my tests, but other states could be possible where the problem
is not recovered automatically) because:

6. Sentinels treat masters reporting to be slaves as failing.
7. A new failover is triggered, and a slave is promoted to master.

Bug lifetime
===

The bug is there forever. Commit 16237d78 actually tried to fix the bug
but in the wrong way (the computed variable was never used! My fault).
So this bug is there basically since the start of Sentinel.

Since the bug is hard to trigger, I remember little reports matching
this condition, but I remember at least a few. Also in automated tests
where instances were stopped and restarted multiple times automatically
I remember hitting this issue, however I was not able to reproduce nor
to determine with the information I had at the time what was causing the
issue.
2015-09-07 15:52:44 +02:00
antirez 34d87be519 Sentinel: clarify effect of resetting failover_start_time. 2015-09-07 15:52:33 +02:00
ubuntuandantirez 0513de624c SCAN iter parsing changed from atoi to chartoull 2015-09-07 13:25:30 +02:00
antirez 49f2f691cb Log client details on SLAVEOF command having an effect. 2015-08-21 15:30:49 +02:00
antirez c2ff9de31b startBgsaveForReplication(): handle waiting slaves state change.
Before this commit, after triggering a BGSAVE it was up to the caller of
startBgsavForReplication() to handle slaves in WAIT_BGSAVE_START in
order to update them accordingly. However when the replication target is
the socket, this is not possible since the process of updating the
slaves and sending the FULLRESYNC reply must be coupled with the process
of starting an RDB save (the reason is, we need to send the FULLSYNC
command and spawn a child that will start to send RDB data to the slaves
ASAP).

This commit moves the responsibility of handling slaves in
WAIT_BGSAVE_START to startBgsavForReplication() so that for both
diskless and disk-based replication we have the same chain of
responsiblity. In order accomodate such change, the syncCommand() also
needs to put the client in the slave list ASAP (just after the initial
checks) and not at the end, so that startBgsavForReplication() can find
the new slave alrady in the list.

Another related change is what happens if the BGSAVE fails because of
fork() or other errors: we now remove the slave from the list of slaves
and send an error, scheduling the slave connection to be terminated.

As a side effect of this change the following errors found by
Oran Agra are fixed (thanks!):

1. rdbSaveToSlavesSockets() on failed fork will get the slaves cleaned
up, otherwise they remain in a wrong state forever since we setup them
for full resync before actually trying to fork.

2. updateSlavesWaitingBgsave() with replication target set as "socket"
was broken since the function changed the slaves state from
WAIT_BGSAVE_START to WAIT_BGSAVE_END via
replicationSetupSlaveForFullResync(), so later rdbSaveToSlavesSockets()
will not find any slave in the right state (WAIT_BGSAVE_START) to feed.
2015-08-21 11:55:14 +02:00
antirez c5a8c8e907 Fixed issues introduced during last merge. 2015-08-20 10:21:50 +02:00
antirez 4363fa1d76 Force slaves to resync after unsuccessful PSYNC.
Using chained replication where C is slave of B which is in turn slave of
A, if B reconnects the replication link with A but discovers it is no
longer possible to PSYNC, slaves of B must be disconnected and PSYNC
not allowed, since the new B dataset may be completely different after
the synchronization with the master.

Note that there are varius semantical differences in the way this is
handled now compared to the past. In the past the semantics was:

1. When a slave lost connection with its master, disconnected the chained
slaves ASAP. Which is not needed since after a successful PSYNC with the
master, the slaves can continue and don't need to resync in turn.

2. However after a failed PSYNC the replication backlog was not reset, so a
slave was able to PSYNC successfully even if the instance did a full
sync with its master, containing now an entirely different data set.

Now instead chained slaves are not disconnected when the slave lose the
connection with its master, but only when it is forced to full SYNC with
its master. This means that if the slave having chained slaves does a
successful PSYNC all its slaves can continue without troubles.

See issue #2694 for more details.
2015-08-20 10:19:42 +02:00
antirez 5a9bc7cc10 replicationHandleMasterDisconnection() belongs to replication.c. 2015-08-20 10:19:10 +02:00
antirez 0b76524983 flushSlavesOutputBuffers(): details clarified via comments.
Talking with @oranagra we had to reason a little bit to understand if
this function could ever flush the output buffers of the wrong slaves,
having online state but actually not being ready to receive writes
before the first ACK is received from them (this happens with diskless
replication).

Next time we'll just read this comment.
2015-08-20 10:15:13 +02:00
antirez 421582f845 checkTcpBacklogSetting() now called in Sentinel mode too. 2015-08-20 10:14:48 +02:00
antirez 3cfca5afdc slaveTryPartialResynchronization and syncWithMaster: better synergy.
It is simpler if removing the read event handler from the FD is up to
slaveTryPartialResynchronization, after all it is only called in the
context of syncWithMaster.

This commit also makes sure that on error all the event handlers are
removed from the socket before closing it.
2015-08-07 12:20:03 +02:00
antirez 0643ba066e syncWithMaster(): non blocking state machine. 2015-08-07 12:20:03 +02:00
antirez 809e2f4da8 startBgsaveForReplication(): log what you really do. 2015-08-06 12:34:25 +02:00
antirez ba3237604f Replication: add REPLCONF CAPA EOF support.
Add the concept of slaves capabilities to Redis, the slave now presents
to the Redis master with a set of capabilities in the form:

    REPLCONF capa SOMECAPA capa OTHERCAPA ...

This has the effect of setting slave->slave_capa with the corresponding
SLAVE_CAPA macros that the master can test later to understand if it
the slave will understand certain formats and protocols of the
replication process. This makes it much simpler to introduce new
replication capabilities in the future in a way that don't break old
slaves or masters.

This patch was designed and implemented together with Oran Agra
(@oranagra).
2015-08-06 12:33:52 +02:00
antirez ade9bf7cb3 Fix synchronous readline "\n" handling.
Our function to read a line with a timeout handles newlines as requests
to refresh the timeout, however the code kept subtracting the buffer
size left every time a newline was received, for a bug in the loop
logic. Fixed by this commit.
2015-08-05 16:59:23 +02:00
antirez 64b28d1097 Fix replication slave pings period.
For PINGs we use the period configured by the user, but for the newlines
of slaves waiting for an RDB to be created (including slaves waiting for
the FULLRESYNC reply) we need to ping with frequency of 1 second, since
the timeout is fixed and needs to be refreshed.
2015-08-05 16:59:16 +02:00
antirez dcc5ee156f Fix RDB encoding test for new csvdump format. 2015-08-05 14:05:39 +02:00
antirez 58a8c0626c Remove slave state change handled by replicationSetupSlaveForFullResync(). 2015-08-05 13:59:22 +02:00
antirez 27c0ab238b Make sure we re-emit SELECT after each new slave full sync setup.
In previous commits we moved the FULLRESYNC to the moment we start the
BGSAVE, so that the offset we provide is the right one. However this
also means that we need to re-emit the SELECT statement every time a new
slave starts to accumulate the changes.

To obtian this effect in a more clean way, the function that sends the
FULLRESYNC reply was overloaded with a more important role of also doing
this and chanigng the slave state. So it was renamed to
replicationSetupSlaveForFullResync() to better reflect what it does now.
2015-08-05 13:57:38 +02:00
antirez 547ccc4b5e Test: csvdump now scans all DBs. 2015-08-05 13:53:32 +02:00
antirez 99e4cf4d84 Don't send SELECT to slaves in WAIT_BGSAVE_START state. 2015-08-05 13:53:28 +02:00
antirez 1dccb6cffb PSYNC test: also test the vanilla SYNC. 2015-08-05 13:52:57 +02:00
antirez f6c65d3f45 syncCommand() comments improved. 2015-08-05 13:52:50 +02:00
antirez 9d2972183e PSYNC initial offset fix.
This commit attempts to fix a bug involving PSYNC and diskless
replication (currently experimental) found by Yuval Inbar from Redis Labs
and that was later found to have even more far reaching effects (the bug also
exists when diskstore is off).

The gist of the bug is that, a Redis master replies with +FULLRESYNC to
a PSYNC attempt that fails and requires a full resynchronization.
However, the baseline offset sent along with FULLRESYNC was always the
current master replication offset. This is not ok, because there are
many reasosn that may delay the RDB file creation. And... guess what,
the master offset we communicate must be the one of the time the RDB
was created. So for example:

1) When the BGSAVE for replication is delayed since there is one
   already but is not good for replication.
2) When the BGSAVE is not needed as we attach one currently ongoing.
3) When because of diskless replication the BGSAVE is delayed.

In all the above cases the PSYNC reply is wrong and the slave may
reconnect later claiming to need a wrong offset: this may cause
data curruption later.
2015-08-05 13:51:43 +02:00
antirez 32e979801b Test PSYNC with diskless replication.
Thanks to Oran Agra from Redis Labs for providing this patch.
2015-08-05 13:45:02 +02:00
antirez f15bf6c8f8 Redis 3.0.3. 2015-07-17 11:50:21 +02:00
Jan-Erik Redigerandantirez 9b0a47cbc8 Do not attempt to lock on Solaris 2015-07-17 11:04:24 +02:00
MOON_CLJandantirez f22a9c0f78 pfcount support multi keys 2015-07-17 10:56:28 +02:00
Yongyue Sunandantirez a57aa8831e bugfix: errno might change before logging
Signed-off-by: Yongyue Sun <abioy.sun@gmail.com>
2015-07-17 10:47:26 +02:00
Tom Kiemesandantirez ad0082fde4 Fix: aof_delayed_fsync is not reset
aof_delayed_fsync was not set to 0 when calling CONFIG RESETSTAT
2015-07-17 10:39:30 +02:00
antirez b029ff11b6 Client timeout handling improved.
The previos attempt to process each client at least once every ten
seconds was not a good idea, because:

1. Usually because of the past min iterations set to 50, you get much
better processing period most of the times.

2. However when there are many clients and a normal setting for
server.hz, the edge case is triggered, and waiting 10 seconds for a
BLPOP that asked for 1 second is not ok.

3. Moreover, because of the high min-itereations limit of 50, when HZ
was set to an high value, the actual behavior was to process a lot of
clients per second.

Also the function checking for timeouts called gettimeofday() at each
iteration which can be costly.

The new implementation will try to process each client once per second,
gets the current time as argument, and does not attempt to process more
than 5 clients per iteration if not needed.

So now:

1. The CPU usage of an idle Redis process is the same or better.
2. The CPU usage of a busy Redis process is the same or better.
3. However a non trivial amount of work may be performed per iteration
when there are many many clients. In this particular case the user may
want to raise the "HZ" value if needed.

Btw with 4000 clients it was still not possible to noticy any actual
latency created by processing 400 clients per second, since the work
performed for each client is pretty small.
2015-07-16 11:03:40 +02:00
antirez 5dcba26b31 Clarify a comment in clientsCron(). 2015-07-16 11:03:40 +02:00
antirez 7ae1d4d6f5 EXISTS is now variadic.
The new return value is the number of keys existing, among the ones
specified in the command line, counting the same key multiple times if
given multiple times (and if it exists).

See PR #2667.
2015-07-13 18:23:18 +02:00
antirez 55e8d4cf1b Add 3.0 changed config default for maxmemory policy to release notes. 2015-06-29 17:23:52 +02:00
linfangrongandantirez d4a7c9e1ab Update t_zset.c 2015-06-11 15:15:45 +02:00
antirez 4e8759c65e Use best effort address binding to connect to the master
We usually want to reach the master using the address of the interface
Redis is bound to (via the "bind" config option). That's useful since
the master will get (and publish) the slave address getting the peer
name of the incoming socket connection from the slave.

However, when this is not possible, for example because the slave is
bound to the loopback interface but repliaces from a master accessed via
an external interface, we want to still connect with the master even
from a different interface: in this case it is not really important that
the master will provide any other address, while it is vital to be able
to replicate correctly.

Related to issues #2609 and #2612.
2015-06-11 13:01:06 +02:00
antirez a8d7f00e2e anet.c: new API anetTcpNonBlockBestEffortBindConnect()
This performs a best effort source address binding attempt. If it is
possible to bind the local address and still have a successful
connect(), then this socket is returned. Otherwise the call is retried
without source address binding attempt.

Related to issues #2609 and #2612.
2015-06-11 13:01:04 +02:00
antirez af8a9de663 anetTcpGenericConnect(), jump to error not end on error
Two code paths jumped to the "ok, return the socket to the user" code
path to handle error conditions.

Related to issues #2609 and #2612.
2015-06-11 13:00:55 +02:00
antirez d815289d54 Don't try to bind the source address for MIGRATE
Related to issues #2609 and #2612.
2015-06-11 13:00:52 +02:00
antirez f58d67b015 Fix 3.0.2 release notes to give full credits. 2015-06-04 11:59:45 +02:00
39 changed files with 2283 additions and 611 deletions
+165 -1
View File
@@ -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
View File
@@ -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!
+27 -30
View File
@@ -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);
+6 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+1
View File
@@ -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);
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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 */
+1
View File
@@ -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) {
+39 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 &&
+28 -12
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1 +1 @@
#define REDIS_VERSION "3.0.2"
#define REDIS_VERSION "3.0.7"
+9 -1
View File
@@ -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) =
+65
View File
@@ -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"
}
}
+62
View File
@@ -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
View File
@@ -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",
}
}
+38 -25
View File
@@ -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
}
+53
View File
@@ -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
View File
@@ -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
}
+26
View File
@@ -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
+91
View File
@@ -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}
}
}
}
+4
View File
@@ -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]
+5 -5
View File
@@ -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
}
+3 -1
View File
@@ -12,7 +12,9 @@ GROUPS = [
"connection",
"server",
"scripting",
"hyperloglog"
"hyperloglog",
"cluster",
"geo"
].freeze
GROUPS_BY_NAME = Hash[*