Compare commits
427
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53bc8a77d1 | ||
|
|
033d717295 | ||
|
|
186adefd56 | ||
|
|
4fe4c4b34d | ||
|
|
eea0d41fb9 | ||
|
|
672e2672aa | ||
|
|
df0c8c28ad | ||
|
|
b5f0d0d6f9 | ||
|
|
7f8ea7a50f | ||
|
|
29aed9c48f | ||
|
|
81af783fec | ||
|
|
e62e3c4cb2 | ||
|
|
5697f4f85d | ||
|
|
90bbf1159b | ||
|
|
9996c37e74 | ||
|
|
33f63ff988 | ||
|
|
308c570e04 | ||
|
|
1d70312028 | ||
|
|
0e1b5ff63b | ||
|
|
c268d1ebd5 | ||
|
|
32ca949c76 | ||
|
|
122157f220 | ||
|
|
d357e3ee8f | ||
|
|
d0c84acc97 | ||
|
|
6f49395148 | ||
|
|
440ee603b7 | ||
|
|
a7fd2eb594 | ||
|
|
a82c000bdb | ||
|
|
8c6e86805c | ||
|
|
41a7241603 | ||
|
|
5685d15af1 | ||
|
|
5efa5501e8 | ||
|
|
687f84a377 | ||
|
|
d279024081 | ||
|
|
be1594905f | ||
|
|
b8a71e5a77 | ||
|
|
f727000f0c | ||
|
|
4db4a5d5d6 | ||
|
|
d9227938c8 | ||
|
|
e10ee0728a | ||
|
|
e84dcabf83 | ||
|
|
b5cdd42bf3 | ||
|
|
26eb7ec731 | ||
|
|
73737c4b85 | ||
|
|
15a1c261ea | ||
|
|
ef92a30767 | ||
|
|
d3ddeff2d2 | ||
|
|
ce5e625a5e | ||
|
|
34b68b7e89 | ||
|
|
bdfa26fb51 | ||
|
|
0a707babb1 | ||
|
|
85d2a8091c | ||
|
|
fafe29cd4c | ||
|
|
5fef9b63d2 | ||
|
|
8522f295fa | ||
|
|
83c92d50a6 | ||
|
|
ed18587f52 | ||
|
|
6eadeb7236 | ||
|
|
c738f0f4fc | ||
|
|
d8ffd2b2ba | ||
|
|
35b94422f6 | ||
|
|
1255460c96 | ||
|
|
baf930176b | ||
|
|
77503edf10 | ||
|
|
d757d400c1 | ||
|
|
ef164836b1 | ||
|
|
5c291ba9f6 | ||
|
|
4b1cb0c44f | ||
|
|
b1f3bf8858 | ||
|
|
473a8bd7d8 | ||
|
|
650b48ea7c | ||
|
|
b7a6a6c486 | ||
|
|
402b1170bc | ||
|
|
f188fa4a15 | ||
|
|
5069e0b9cd | ||
|
|
9fe28612b9 | ||
|
|
a5b7065bdf | ||
|
|
1826167519 | ||
|
|
43570e9319 | ||
|
|
4ca7c5bcde | ||
|
|
c4c7389fea | ||
|
|
6fc17adcd8 | ||
|
|
0906ca8a3e | ||
|
|
402815cc3f | ||
|
|
6e60e2467b | ||
|
|
b1f6f2ace0 | ||
|
|
e244bee60c | ||
|
|
6b4e9558bd | ||
|
|
a11ae385e9 | ||
|
|
bb00154f0e | ||
|
|
e285a57073 | ||
|
|
0ab06bb500 | ||
|
|
eeab60d1d5 | ||
|
|
cdd2bd5632 | ||
|
|
357b039fdb | ||
|
|
ba86119414 | ||
|
|
a8464828b6 | ||
|
|
3acae3dee3 | ||
|
|
b008863edb | ||
|
|
71d71814a9 | ||
|
|
a0050e5030 | ||
|
|
3bff0291d8 | ||
|
|
08174e730f | ||
|
|
e687621e15 | ||
|
|
0c8fdb349c | ||
|
|
19ac9af09f | ||
|
|
7b25ff4bf7 | ||
|
|
7b670805cf | ||
|
|
e99e45be38 | ||
|
|
25f72e9e0d | ||
|
|
aff94a706e | ||
|
|
5b5dd73093 | ||
|
|
ca6c793844 | ||
|
|
82556c1687 | ||
|
|
5e928eff64 | ||
|
|
00726b2e0e | ||
|
|
57b34a73c2 | ||
|
|
816e22bc6f | ||
|
|
d28b239fe9 | ||
|
|
69d0585d61 | ||
|
|
53ca563ff7 | ||
|
|
5e1224dc1d | ||
|
|
964faba650 | ||
|
|
5e21acec54 | ||
|
|
00aa96f34b | ||
|
|
a3848a7429 | ||
|
|
030336d4ef | ||
|
|
e4c2afb9c0 | ||
|
|
e19e078c77 | ||
|
|
66f67208d3 | ||
|
|
db929041b5 | ||
|
|
90b67e416a | ||
|
|
fdace9bc50 | ||
|
|
64fbd15706 | ||
|
|
159926a170 | ||
|
|
fc33779283 | ||
|
|
67e15a0ff7 | ||
|
|
ed52bbe134 | ||
|
|
fd137b0781 | ||
|
|
8b3d5a8eb0 | ||
|
|
46f9a78d9c | ||
|
|
9f12467c9c | ||
|
|
10bc0aea8d | ||
|
|
8be9394ec7 | ||
|
|
8cc09325c8 | ||
|
|
da084bdf84 | ||
|
|
a7dbd28cff | ||
|
|
cf500998e8 | ||
|
|
2c4a1eccda | ||
|
|
5786846122 | ||
|
|
68fb3019a0 | ||
|
|
e7e6aa49d0 | ||
|
|
b964380817 | ||
|
|
1c6671ab90 | ||
|
|
e3f5a38695 | ||
|
|
5b69b984b3 | ||
|
|
2c1fa11124 | ||
|
|
0b9f09c4e0 | ||
|
|
e526d32635 | ||
|
|
ccbabf01e1 | ||
|
|
df5782f4d0 | ||
|
|
e872394176 | ||
|
|
4e12ecdba5 | ||
|
|
05b5032302 | ||
|
|
4407ca6fc8 | ||
|
|
099ae637ab | ||
|
|
2b531f729d | ||
|
|
437cddee0d | ||
|
|
109ec9c6a5 | ||
|
|
f5bdaf366e | ||
|
|
ae6103ebf6 | ||
|
|
63ba5f5a8c | ||
|
|
e0389f031e | ||
|
|
5056450b02 | ||
|
|
f553fc442f | ||
|
|
19938b9103 | ||
|
|
628b9fa535 | ||
|
|
aecb59b0b2 | ||
|
|
e6fda1c9ed | ||
|
|
9c7bb351ab | ||
|
|
80d7f7081c | ||
|
|
6415fe5623 | ||
|
|
f403fca654 | ||
|
|
21c9372454 | ||
|
|
f307a51049 | ||
|
|
08516c1ab6 | ||
|
|
0c01e293ef | ||
|
|
e262442fd4 | ||
|
|
8ad9123a14 | ||
|
|
9cb2b5003f | ||
|
|
07daaa6ec4 | ||
|
|
691846e38d | ||
|
|
7f18689a00 | ||
|
|
6672e101e8 | ||
|
|
df2c02785a | ||
|
|
dc774e1bdc | ||
|
|
00cb4c5dee | ||
|
|
2c1b4dcd40 | ||
|
|
7acccc7ef9 | ||
|
|
8be30eca17 | ||
|
|
f3aa086b6d | ||
|
|
f704678d07 | ||
|
|
c1f97815ca | ||
|
|
c16f638dc5 | ||
|
|
e6d57660ed | ||
|
|
650b21fb2c | ||
|
|
6aa1b23269 | ||
|
|
a0b27c329a | ||
|
|
7d04f823e1 | ||
|
|
93e5876a72 | ||
|
|
bb57749a86 | ||
|
|
0faae58091 | ||
|
|
c03e7ceaaa | ||
|
|
f4839dfb27 | ||
|
|
87dd31427c | ||
|
|
3610af42b2 | ||
|
|
c7344144f4 | ||
|
|
be20651435 | ||
|
|
8c6ce3dd6a | ||
|
|
1080e272de | ||
|
|
47fbbd9fbc | ||
|
|
6875a15858 | ||
|
|
74fd89b321 | ||
|
|
cf8f72c184 | ||
|
|
6837863902 | ||
|
|
431573ae98 | ||
|
|
8a3dde208d | ||
|
|
046e691a6f | ||
|
|
74d86dc13b | ||
|
|
c69c40a4ae | ||
|
|
b2a02cc92d | ||
|
|
01fe750c6a | ||
|
|
1e7b99809b | ||
|
|
f701a347b8 | ||
|
|
6f4be45997 | ||
|
|
49b576cb6c | ||
|
|
317ec18214 | ||
|
|
19ecf7cdba | ||
|
|
73c2bcca85 | ||
|
|
65e0452886 | ||
|
|
4af72d326e | ||
|
|
ee18135a19 | ||
|
|
9fa96697a9 | ||
|
|
a7478079a1 | ||
|
|
46904ae22b | ||
|
|
2dd8c46249 | ||
|
|
ae211be98b | ||
|
|
571d6b01de | ||
|
|
06b2a389eb | ||
|
|
fd70c68c97 | ||
|
|
24c350245d | ||
|
|
7f274e3bfe | ||
|
|
a219339ee0 | ||
|
|
9928b53102 | ||
|
|
fd5e8c0132 | ||
|
|
10c8d86242 | ||
|
|
26292670fa | ||
|
|
c641074a21 | ||
|
|
c9ac817cc0 | ||
|
|
205c2ccc0c | ||
|
|
561e793472 | ||
|
|
8f0b74910a | ||
|
|
63aacbe86c | ||
|
|
8b6a674a5d | ||
|
|
128dcee43d | ||
|
|
b104015fb3 | ||
|
|
f5f281f906 | ||
|
|
f308f67703 | ||
|
|
0774d4928a | ||
|
|
a86e24de93 | ||
|
|
9997b51ff8 | ||
|
|
04cd7f773d | ||
|
|
32f188cdf7 | ||
|
|
0c8f0079f9 | ||
|
|
668a95a583 | ||
|
|
ca2a3b3240 | ||
|
|
ee513ee958 | ||
|
|
9e82461af0 | ||
|
|
e0ef510630 | ||
|
|
e917de12d0 | ||
|
|
07036ad45e | ||
|
|
a02424e29b | ||
|
|
412a759a83 | ||
|
|
48702e0011 | ||
|
|
efd0346ea3 | ||
|
|
47e3f1f16c | ||
|
|
117557192e | ||
|
|
01eee56f4f | ||
|
|
ab8e1bbcdc | ||
|
|
94129415bf | ||
|
|
db0d9f4326 | ||
|
|
3b80e0a41d | ||
|
|
399fca8f45 | ||
|
|
0c38b3c934 | ||
|
|
d7451a0110 | ||
|
|
c3aff0c20a | ||
|
|
fdf737e132 | ||
|
|
81efa0d296 | ||
|
|
0618d26b89 | ||
|
|
a2a72b87e0 | ||
|
|
c8485703f5 | ||
|
|
d610d2343d | ||
|
|
2a951ce502 | ||
|
|
9c9914d779 | ||
|
|
3119f4f694 | ||
|
|
14f77b343a | ||
|
|
63fc5dc8b1 | ||
|
|
afe28cfd75 | ||
|
|
aa5898f53e | ||
|
|
c0915ad1a0 | ||
|
|
4b3c87a027 | ||
|
|
7c8964a8cf | ||
|
|
7c359449d5 | ||
|
|
6766fc561e | ||
|
|
9a7cf31960 | ||
|
|
503938022f | ||
|
|
7eec424953 | ||
|
|
0abe98cb4d | ||
|
|
a0ea8f235e | ||
|
|
6f4b5ef6d5 | ||
|
|
4d5ba5962c | ||
|
|
ef4e465316 | ||
|
|
313f8831ed | ||
|
|
5ee2394474 | ||
|
|
fa8aca5236 | ||
|
|
cf746c398c | ||
|
|
cbf51b60a2 | ||
|
|
23a12bb8c1 | ||
|
|
d2e16801f0 | ||
|
|
21557a0d8b | ||
|
|
290a5d7ff6 | ||
|
|
b2668466f4 | ||
|
|
a76bca16e6 | ||
|
|
1d9eb47f9d | ||
|
|
e4833ed8bf | ||
|
|
7c092b679f | ||
|
|
23addbb5a3 | ||
|
|
a46811693d | ||
|
|
32db37c3c4 | ||
|
|
1d62f83364 | ||
|
|
9104f1e672 | ||
|
|
eacc0951a2 | ||
|
|
1f8005ca09 | ||
|
|
cbeaf17374 | ||
|
|
9c206cc8e8 | ||
|
|
24265edb6c | ||
|
|
7e274194bf | ||
|
|
d242b698f4 | ||
|
|
cd8b149dff | ||
|
|
c12ebb4633 | ||
|
|
a2b3f2eae2 | ||
|
|
cb7ccc9ad0 | ||
|
|
48fa34bf9e | ||
|
|
ff20d05650 | ||
|
|
6a95ddb248 | ||
|
|
2431b63ff4 | ||
|
|
68e9597e8a | ||
|
|
31ed0911fa | ||
|
|
edf48d17a2 | ||
|
|
7f521d764f | ||
|
|
491bcbaf73 | ||
|
|
55643361f1 | ||
|
|
4f6ed3412e | ||
|
|
55a2e10b03 | ||
|
|
56e18ba4f6 | ||
|
|
cd68a1d45a | ||
|
|
4af2acf2b0 | ||
|
|
fecfd721df | ||
|
|
b18109e351 | ||
|
|
2ed707bcaa | ||
|
|
2666f737b0 | ||
|
|
6e2e6d5b8c | ||
|
|
ec593b582e | ||
|
|
5a8c9f94a6 | ||
|
|
ef01bbe1f5 | ||
|
|
2b4bc4f900 | ||
|
|
21a7f9e7ef | ||
|
|
48d74f2039 | ||
|
|
8a6774e209 | ||
|
|
6bb42b5bfe | ||
|
|
37be5987e6 | ||
|
|
b037c897ae | ||
|
|
f7cb83778b | ||
|
|
a11749f9f6 | ||
|
|
8292dd50cd | ||
|
|
68a3d5602e | ||
|
|
bbbe68aa0d | ||
|
|
6e4662e479 | ||
|
|
bd31fcf16e | ||
|
|
2a35abcf12 | ||
|
|
c1786693b1 | ||
|
|
48af4d4f26 | ||
|
|
58b6dd9beb | ||
|
|
c36a5dce54 | ||
|
|
3c1672da7d | ||
|
|
116617c5e7 | ||
|
|
767846dc5b | ||
|
|
14143fbede | ||
|
|
0296aab6da | ||
|
|
dd73a7bf43 | ||
|
|
910b6d34f2 | ||
|
|
0725988a07 | ||
|
|
4513d8fcd4 | ||
|
|
9721255178 | ||
|
|
6d550f2de4 | ||
|
|
585e9fb886 | ||
|
|
8b5196addf | ||
|
|
2e3f6b0fb3 | ||
|
|
a221ae5ce2 | ||
|
|
77c6fa65f1 | ||
|
|
f406a09495 | ||
|
|
01de246843 | ||
|
|
8f25428772 | ||
|
|
cc97305ec3 | ||
|
|
fa6f4f21c3 | ||
|
|
30214fff3e | ||
|
|
cfcb09bf76 | ||
|
|
8e12fae05e | ||
|
|
6a01545744 | ||
|
|
969a4f1db3 | ||
|
|
cb92a1ef08 | ||
|
|
6987a95952 | ||
|
|
ad85f520f8 | ||
|
|
b82b66b51d | ||
|
|
b6e04f5584 | ||
|
|
0ee1a78c86 | ||
|
|
ff6a75a0c1 |
+221
-73
@@ -1,83 +1,231 @@
|
||||
Redis 2.6 release notes
|
||||
Redis 3.0 release notes
|
||||
=======================
|
||||
|
||||
Migrating from 2.4 to 2.6
|
||||
WARNING: Redis 3.0 is currently a BETA not suitable for production environments.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
Upgrade urgency levels:
|
||||
|
||||
LOW: No need to upgrade unless there are new features you want to use.
|
||||
MODERATE: Program an upgrade of the server, but it's not urgent.
|
||||
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.0 Beta 5 (version 2.9.54) ] Release date: 26 may 2014
|
||||
|
||||
This is the 5th beta of Redis 3.0.0. It does not include any real
|
||||
worthwhile change (just three days passed since the previous beta), but
|
||||
fixes two stupid bugs preventing cluster tests to pass.
|
||||
|
||||
--[ Redis 3.0.0 Beta 4 (version 2.9.53) ] Release date: 23 may 2014
|
||||
|
||||
This is the fourth beta of Redis 3.0.0.
|
||||
|
||||
>> General changes
|
||||
|
||||
* [NEW] Scripting engine performances improvements.
|
||||
* [NEW] Log format slightly changed to report current node role.
|
||||
|
||||
* [FIX] Correct the HyperLogLog stale cache flag to prevent unnecessary
|
||||
computation.
|
||||
|
||||
>> Cluster changes
|
||||
|
||||
* [NEW] redis-trib: ability to import data from standalone Redis instances.
|
||||
* [NEW] redis-trib: "fix" subcommand much better at fixing errors.
|
||||
* [NEW] CLUSTER FAILOVER FORCE implemented.
|
||||
* [NEW] CLUSTER RESET implemented, it is now possible to completely reset
|
||||
nodes and create a new cluster without restarting anything.
|
||||
* [NEW] Slave validity factor (max estimated data age to still failover)
|
||||
is now under the control of the user, and can be configured via
|
||||
redis.conf or CONFIG SET. Option name cluster-slave-validity-factor.
|
||||
* [NEW] Cluster test: failure detection and failover initial tests.
|
||||
* [NEW] CLUSTER MEET: better error messages when address is invalid.
|
||||
* [NEW] Bulk-accept new Cluster nodes in the Cluster bus instead of
|
||||
performing just a single accept() per event fired.
|
||||
|
||||
* [FIX] Bypass data_age check for manual failovers.
|
||||
* [FIX] Fixed data_age computation / check integer overflow.
|
||||
* [FIX] Various fixes to Tcl client.tcl Redis Cluster client used in tests.
|
||||
* [FIX] Better handling of stolen slots.
|
||||
* [FIX] Don't accept cluster bus connections during startup.
|
||||
|
||||
>> Sentinel changes
|
||||
|
||||
* [NEW] Generate +config-update-from event when a new config is received.
|
||||
* [NEW] Log when a failover will be re-attempted again.
|
||||
|
||||
* [FIX] Sentinel: Add "dir /tmp" directive in example sentinel.conf.
|
||||
|
||||
--[ Redis 3.0.0 Beta 3 (version 2.9.52) ] Release date: 5 may 2014
|
||||
|
||||
This is the third beta of Redis 3.0.0.
|
||||
|
||||
>> General changes
|
||||
|
||||
* [NEW] New data structure: the HyperLogLog (see 2.8 release notes).
|
||||
* [NEW] Lexicograhical range queries in sorted sets (see 2.8 release notes).
|
||||
* [NEW] LRU algorithm precision greatly improved.
|
||||
|
||||
* [FIX] Redis is now much more responsive to reply with LOADING / BUSY errors.
|
||||
|
||||
>> Cluster changes
|
||||
|
||||
* [NEW] Cluster testing framework and initial tests.
|
||||
* [NEW] Cluster epochs collision resolution (make Redis Cluster more resilient
|
||||
to admin and programming errors).
|
||||
* [NEW] Persist / fsync some global state to ensure correct crash-recovery
|
||||
semantics.
|
||||
* [NEW] New command introduced: CLUSTER SET-CONFIG-EPOCH, still not used
|
||||
by redis-trib. Will be used to speedup the assignment of unique
|
||||
epochs to different nodes at cluster creation time. For now this is
|
||||
handled as a side effect of the cluster epochs collision resolution.
|
||||
|
||||
* [FIX] Different fixes to redis-trib cluster creation.
|
||||
* [FIX] Fix an error in the CLUSTER NODES output for nodes slots.
|
||||
|
||||
>> Sentinel changes
|
||||
|
||||
* [NEW] Sentinels are now able to send update messages in a peer-to-peer
|
||||
fashion even if no Redis instances are available. Now the Sentinel
|
||||
liveness property that the most updated configuration in a given
|
||||
partition is propagated to all the Sentinels is extended to partitions
|
||||
without reachable instances.
|
||||
* [NEW] Sentinel safety properties are now ensured in a crash-recovery system
|
||||
model since some state is persisted on disk before replying to other
|
||||
nodes, and reloaded at startup.
|
||||
* [NEW] Sentinel now uses CLIENT SETNAME so that it is easy to identify
|
||||
Sentinels using CLIENT LIST among other clients.
|
||||
* [NEW] Sentinel failure detection and reconnection code improved.
|
||||
|
||||
--[ Redis 3.0.0 Beta 2 (version 2.9.51) ] Release date: 11 mar 2014
|
||||
|
||||
This is the second beta of Redis 3.0.0.
|
||||
|
||||
>> General changes
|
||||
|
||||
* [FIX] Sometimes the absolute config file path was obtained in a wrong way.
|
||||
This happened when there was a "dir" directive inside the config file
|
||||
and at the same time the configuration file was given as a relative
|
||||
path to redis-server or redis-sentinel executables.
|
||||
* [FIX] redis-cli: Automatically enter --slave mode when SYNC or PSYNC are
|
||||
called during an interactive session.
|
||||
* [FIX] BITCOUNT: fixed unaligned access causing issues in sparc and other
|
||||
archs not capable of dealing with unaligned accesses. This also makes
|
||||
the code faster in archs where unaligned accesses are allowed.
|
||||
* [FIX] Force INFO used_memory_peak to match peak memory. This generated some
|
||||
confusion among users even if it was not an actual bug.
|
||||
* [FIX] Fixed an critical EVALSHA script cache bug: scripts executed may not
|
||||
propagate to AOF / Slaves correctly under certain conditions.
|
||||
See issue #1549 at Github for more information.
|
||||
* [FIX] Fixed multiple bugs resulting into closing the link with master or slave
|
||||
during replication without good reasons. This will result in useless
|
||||
resynchronizations, or infinite loops where the replication link can't
|
||||
be established.
|
||||
* [FIX] Don't count the time needed to populate the buffers of clients waiting
|
||||
in MONITOR mode when populating the Slow Log entries.
|
||||
|
||||
* [NEW] Redis-cli updated to use SCAN instead of random sampling via
|
||||
RANDOMKEY in order to implement --bigkeys feature. Moreover the
|
||||
implementation now supports pipelining and reports more information
|
||||
at the end of the scan. Much faster, much better. A special thank
|
||||
you to Michael Grunder for this improvement.
|
||||
* [NEW] redis-cli now supports a new --intrinsic-latency mode that is able
|
||||
to meter the latency of a system due to kernel / hypervisor.
|
||||
How to use it is explained at http://redis.io/topics/latency.
|
||||
* [NEW] New command BITPOS: find first bit set or clear in a bitmap.
|
||||
* [NEW] CONFIG REWRITE calls are now logged.
|
||||
* [NEW] AOF write errors (like no space on device) no longer abort Redis if the
|
||||
fsync policy is none or every second. The database enters a read-only
|
||||
mode where every write is refused with an error. Normal operations are
|
||||
restored as soon as Redis is able to append again data to the AOF file.
|
||||
* [NEW] Sentinel now accepts SHUTDOWN command.
|
||||
|
||||
|
||||
>> Cluster changes
|
||||
|
||||
* [FIX] Bind the first interface listed in the "bind" configuration directive
|
||||
if any, in order to perform outgoing connections. This fixes Cluster
|
||||
usage when an address is bound but there are multiple interfaces that
|
||||
may be used to connect with other nodes.
|
||||
* [FIX] When an "Importing" slot is closed via CLUSTER SETSLOT NODE ...
|
||||
increment the configEpoch in the special case it is zero.
|
||||
* [FIX] Current transaction is invalidated on redirection errors.
|
||||
* [FIX] Abort if port does not allow for a valid cluster bus port that is
|
||||
always at fixed +10000 offset.
|
||||
* [FIX] Keys extraction algorithm fixed for ZUNIONSTORE/ZINTERSTORE and SORT.
|
||||
* [FIX] Better failover timeout and retry times: failover should now work
|
||||
reliabily when node-timeout is very small (a few milliseconds).
|
||||
* [FIX] Don't allow SORT GET/BY options in Cluster mode.
|
||||
* [FIX] Clear importing/migrating state when turning from master to slave role.
|
||||
* [FIX] Set slot error if we receive an update for a busy slot.
|
||||
* [FIX] Update node configEpoch on UPDATE messages.
|
||||
|
||||
* [NEW] Support multi-key operations as long as keys resolve to the same
|
||||
hash slot, and the slot is not migrating, or it is migrating but all
|
||||
the mentioned keys are available.
|
||||
* [NEW] New DEBUG command CMDKEYS available to debug / test keys identification
|
||||
in Redis commands.
|
||||
* [NEW] redis-trib: create subcommand is now able to assign spare slaves.
|
||||
* [NEW] redis-trib: new subcommand 'call'. Exec command in all nodes.
|
||||
|
||||
|
||||
>> Sentinel changes
|
||||
|
||||
* [FIX] Sentinel "IDONTKNOW" error removed as it does not made sense with the
|
||||
new Sentinel design. This error was actually a fix for a design error
|
||||
in the first implementation of Sentinel.
|
||||
* [FIX] Sentinel: added a missing exit() call to abort after config file
|
||||
checks at startup. This error was introduced with an improvement in
|
||||
a previous 2.8 release.
|
||||
* [FIX] Sentinel: better nodes fail over start time desynchronization to avoid
|
||||
split-brain during the voting process needed to get authorization to
|
||||
fail over. This means the system is less likely to need to retry
|
||||
and will fail over faster. No changes in behavior / correctness.
|
||||
|
||||
* [NEW] Sentinel unit tests and framework. More tests needed and units must
|
||||
be improved in order to have less false positives, but it is a start
|
||||
and features a debugging console that is useful to fix tests or to
|
||||
inspect bugs causing tests failures.
|
||||
* [NEW] New Sentinel events: +/-monitor and +set used to monitor when an
|
||||
instance to monitor is added or removed, or when a configuration
|
||||
is modified via SENTINEL SET.
|
||||
|
||||
--[ Redis 3.0.0 Beta 1 (version 2.9.50) ] Release date: 11 Feb 2014
|
||||
|
||||
This is the first beta of Redis 3.0.0.
|
||||
|
||||
The following is a list of improvements in Redis 3.0, compared to Redis 2.8.
|
||||
|
||||
* [NEW] Redis Cluster: a distributed implementation of a subset of Redis.
|
||||
* [NEW] New "embedded string" object encoding resulting in less cache
|
||||
misses. Big speed gain under certain work loads.
|
||||
* [NEW] WAIT command to block waiting for a write to be transmitted to
|
||||
the specified number of slaves.
|
||||
* [NEW] MIGRATE connection caching. Much faster keys migraitons.
|
||||
* [NEW] MIGARTE new options COPY and REPLACE.
|
||||
* [NEW] CLIENT PAUSE command: stop processing client requests for a
|
||||
specified amount of time.
|
||||
|
||||
Migrating from 2.8 to 3.0
|
||||
=========================
|
||||
|
||||
Redis 2.4 is mostly a strict subset of 2.6. However there are a few things
|
||||
that you should be aware of:
|
||||
Redis 3.0 is mostly a strict subset of 2.8, you should not have any problem
|
||||
upgrading your application from 2.8 to 3.0. However this is a list of small
|
||||
non-backward compatible changes introduced in the 3.0 release:
|
||||
|
||||
* You can't use .rdb and AOF files generated with 2.6 into a 2.4 instance.
|
||||
* 2.6 slaves can be attached to 2.4 masters, but not the contrary, and only
|
||||
for the time needed to perform the version upgrade.
|
||||
|
||||
There are also a few API differences, that are unlikely to cause problems,
|
||||
but it is better to keep them in mind:
|
||||
|
||||
* SORT now will refuse to sort in numerical mode elements that can't be parsed
|
||||
as numbers.
|
||||
* EXPIREs now all have millisecond resolution (but this is very unlikely to
|
||||
break code that was not conceived exploting the previous resolution error
|
||||
in some way.)
|
||||
* INFO output is a bit different now, and contains empty lines and comments
|
||||
starting with '#'. All the major clients should be already fixed to work
|
||||
with the new INFO format.
|
||||
|
||||
Also the following redis.conf and CONFIG GET / SET parameters changed name:
|
||||
|
||||
* hash-max-zipmap-entries, now replaced by hash-max-ziplist-entries
|
||||
* hash-max-zipmap-value, now replaced by hash-max-ziplist-value
|
||||
* glueoutputbuf was now completely removed as it does not make sense
|
||||
|
||||
---------
|
||||
CHANGELOG
|
||||
---------
|
||||
|
||||
What's new in Redis 2.6.0
|
||||
=========================
|
||||
|
||||
UPGRADE URGENCY: We suggest new users to start with 2.6.0, and old users to
|
||||
upgrade after some testing of the application with the new
|
||||
Redis version.
|
||||
|
||||
* Server side Lua scripting, see http://redis.io/commands/eval
|
||||
* Virtual Memory removed (was deprecated in 2.4)
|
||||
* Hardcoded limits about max number of clients removed.
|
||||
* AOF low level semantics is generally more sane, and especially when used
|
||||
in slaves.
|
||||
* Milliseconds resolution expires, also added new commands with milliseconds
|
||||
precision (PEXPIRE, PTTL, ...).
|
||||
* Clients max output buffer soft and hard limits. You can specifiy different
|
||||
limits for different classes of clients (normal,pubsub,slave).
|
||||
* AOF is now able to rewrite aggregate data types using variadic commands,
|
||||
often producing an AOF that is faster to save, load, and is smaller in size.
|
||||
* Every redis.conf directive is now accepted as a command line option for the
|
||||
redis-server binary, with the same name and number of arguments.
|
||||
* Hash table seed randomization for protection against collisions attacks.
|
||||
* Performances improved when writing large objects to Redis.
|
||||
* Significant parts of the core refactored or rewritten. New internal APIs
|
||||
and core changes allowed to develop Redis Cluster on top of the new code,
|
||||
however for 2.6 all the cluster code was removed, and will be released with
|
||||
Redis 3.0 when it is more complete and stable.
|
||||
* Redis ASCII art logo added at startup.
|
||||
* Crash report on memory violation or failed asserts improved significantly
|
||||
to make debugging of hard to catch bugs simpler.
|
||||
* redis-benchmark improvements: ability to run selected tests,
|
||||
CSV output, faster, better help.
|
||||
* redis-cli improvements: --eval for comfortable development of Lua scripts.
|
||||
* SHUTDOWN now supports two optional arguments: "SAVE" and "NOSAVE".
|
||||
* INFO output split into sections, the command is now able to just show
|
||||
pecific sections.
|
||||
* New statistics about how many time a command was called, and how much
|
||||
execution time it used (INFO commandstats).
|
||||
* More predictable SORT behavior in edge cases.
|
||||
* INCRBYFLOAT and HINCRBYFLOAT commands.
|
||||
* The log format was modified. The prefix of each line included the pid
|
||||
in the following format [1234]. Now instead it is 1234:? Where
|
||||
'?' 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.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Credits: Where not specified the implementation and design are done by
|
||||
Salvatore Sanfilippo and Pieter Noordhuis. Thanks to VMware for making all
|
||||
this possible. Also many thanks to all the other contributors and the amazing
|
||||
community we have.
|
||||
Credits: Where not specified the implementation and design is done by
|
||||
Salvatore Sanfilippo. Thanks to Pivotal for making all this possible.
|
||||
Also many thanks to all the other contributors and the amazing community
|
||||
we have.
|
||||
|
||||
See commit messages for more credits.
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ it the proper way for a production system, we have a script doing this
|
||||
for Ubuntu and Debian systems:
|
||||
|
||||
% cd utils
|
||||
% ./install_server
|
||||
% ./install_server.sh
|
||||
|
||||
The script will ask you a few questions and will setup everything you need
|
||||
to run Redis properly as a background daemon that will start again on
|
||||
|
||||
Vendored
+693
-229
File diff suppressed because it is too large
Load Diff
Vendored
+34
-23
@@ -3,39 +3,44 @@
|
||||
*
|
||||
* See linenoise.c for more information.
|
||||
*
|
||||
* ------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __LINENOISE_H
|
||||
#define __LINENOISE_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct linenoiseCompletions {
|
||||
size_t len;
|
||||
char **cvec;
|
||||
@@ -43,13 +48,19 @@ typedef struct linenoiseCompletions {
|
||||
|
||||
typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);
|
||||
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *);
|
||||
void linenoiseAddCompletion(linenoiseCompletions *, char *);
|
||||
void linenoiseAddCompletion(linenoiseCompletions *, const char *);
|
||||
|
||||
char *linenoise(const char *prompt);
|
||||
int linenoiseHistoryAdd(const char *line);
|
||||
int linenoiseHistorySetMaxLen(int len);
|
||||
int linenoiseHistorySave(char *filename);
|
||||
int linenoiseHistoryLoad(char *filename);
|
||||
int linenoiseHistorySave(const char *filename);
|
||||
int linenoiseHistoryLoad(const char *filename);
|
||||
void linenoiseClearScreen(void);
|
||||
void linenoiseSetMultiLine(int ml);
|
||||
void linenoisePrintKeyCodes(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __LINENOISE_H */
|
||||
|
||||
+68
-6
@@ -416,15 +416,18 @@ slave-priority 100
|
||||
#
|
||||
# The default is:
|
||||
#
|
||||
# maxmemory-policy volatile-lru
|
||||
# maxmemory-policy noeviction
|
||||
|
||||
# LRU and minimal TTL algorithms are not precise algorithms but approximated
|
||||
# algorithms (in order to save memory), so you can select as well the sample
|
||||
# size to check. For instance for default Redis will check three keys and
|
||||
# pick the one that was used less recently, you can change the sample size
|
||||
# using the following configuration directive.
|
||||
# algorithms (in order to save memory), so you can tune it for speed or
|
||||
# accuracy. For default Redis will check five keys and pick the one that was
|
||||
# used less recently, you can change the sample size using the following
|
||||
# configuration directive.
|
||||
#
|
||||
# maxmemory-samples 3
|
||||
# The default of 5 produces good enough results. 10 Approximates very closely
|
||||
# true LRU but costs a bit more CPU. 3 is very fast but not very accurate.
|
||||
#
|
||||
# maxmemory-samples 5
|
||||
|
||||
############################## APPEND ONLY MODE ###############################
|
||||
|
||||
@@ -560,6 +563,51 @@ lua-time-limit 5000
|
||||
#
|
||||
# cluster-node-timeout 15000
|
||||
|
||||
# A slave of a failing master will avoid to start a failover if its data
|
||||
# looks too old.
|
||||
#
|
||||
# There is no simple way for a slave to actually have a exact measure of
|
||||
# its "data age", so the following two checks are performed:
|
||||
#
|
||||
# 1) If there are multiple slaves able to failover, they exchange messages
|
||||
# in order to try to give an advantage to the slave with the best
|
||||
# replication offset (more data from the master processed).
|
||||
# Slaves will try to get their rank by offset, and apply to the start
|
||||
# of the failover a delay proportional to their rank.
|
||||
#
|
||||
# 2) Every single slave computes the time of the last interaction with
|
||||
# its master. This can be the last ping or command received (if the master
|
||||
# is still in the "connected" state), or the time that elapsed since the
|
||||
# disconnection with the master (if the replication link is currently down).
|
||||
# If the last interaction is too old, the slave will not try to failover
|
||||
# at all.
|
||||
#
|
||||
# The point "2" can be tuned by user. Specifically a slave will not perform
|
||||
# the failover if, since the last interaction with the master, the time
|
||||
# elapsed is greater than:
|
||||
#
|
||||
# (node-timeout * slave-validity-factor) + repl-ping-slave-period
|
||||
#
|
||||
# So for example if node-timeout is 30 seconds, and the slave-validity-factor
|
||||
# is 10, and assuming a default repl-ping-slave-period of 10 seconds, the
|
||||
# slave will not try to failover if it was not able to talk with the master
|
||||
# for longer than 310 seconds.
|
||||
#
|
||||
# A large slave-validity-factor may allow slaves with too old data to failover
|
||||
# a master, while a too small value may prevent the cluster from being able to
|
||||
# elect a slave at all.
|
||||
#
|
||||
# For maximum availability, it is possible to set the slave-validity-factor
|
||||
# to a value of 0, which means, that slaves will always try to failover the
|
||||
# master regardless of the last time they interacted with the master.
|
||||
# (However they'll always try to apply a delay proportional to their
|
||||
# offset rank).
|
||||
#
|
||||
# Zero is the only value able to guarantee that when all the partitions heal
|
||||
# the cluster will always be able to continue.
|
||||
#
|
||||
# cluster-slave-validity-factor 10
|
||||
|
||||
# Cluster slaves are able to migrate to orphaned masters, that are masters
|
||||
# that are left without working slaves. This improves the cluster ability
|
||||
# to resist to failures as otherwise an orphaned master can't be failed over
|
||||
@@ -679,6 +727,20 @@ set-max-intset-entries 512
|
||||
zset-max-ziplist-entries 128
|
||||
zset-max-ziplist-value 64
|
||||
|
||||
# HyperLogLog sparse representation bytes limit. The limit includes the
|
||||
# 16 bytes header. When an HyperLogLog using the sparse representation crosses
|
||||
# this limit, it is converted into the dense representation.
|
||||
#
|
||||
# A value greater than 16000 is totally useless, since at that point the
|
||||
# dense representation is more memory efficient.
|
||||
#
|
||||
# The suggested value is ~ 3000 in order to have the benefits of
|
||||
# the space efficient encoding without slowing down too much PFADD,
|
||||
# which is O(N) with the sparse encoding. The value can be raised to
|
||||
# ~ 10000 when CPU is not a concern, but space is, and the data set is
|
||||
# composed of many HyperLogLogs with cardinality in the 0 - 15000 range.
|
||||
hll-sparse-max-bytes 3000
|
||||
|
||||
# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
|
||||
# order to help rehashing the main Redis hash table (the one mapping top-level
|
||||
# keys to values). The hash table implementation Redis uses (see dict.c)
|
||||
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
TCL_VERSIONS="8.5 8.6"
|
||||
TCLSH=""
|
||||
|
||||
for VERSION in $TCL_VERSIONS; do
|
||||
TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL
|
||||
done
|
||||
|
||||
if [ -z $TCLSH ]
|
||||
then
|
||||
echo "You need tcl 8.5 or newer in order to run the Redis Sentinel test"
|
||||
exit 1
|
||||
fi
|
||||
$TCLSH tests/cluster/run.tcl $*
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
TCL_VERSIONS="8.5 8.6"
|
||||
TCLSH=""
|
||||
|
||||
for VERSION in $TCL_VERSIONS; do
|
||||
TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL
|
||||
done
|
||||
|
||||
if [ -z $TCLSH ]
|
||||
then
|
||||
echo "You need tcl 8.5 or newer in order to run the Redis Sentinel test"
|
||||
exit 1
|
||||
fi
|
||||
$TCLSH tests/sentinel/run.tcl $*
|
||||
+15
-2
@@ -4,6 +4,13 @@
|
||||
# The port that this sentinel instance will run on
|
||||
port 26379
|
||||
|
||||
# dir <working-directory>
|
||||
# Every long running process should have a well-defined working directory.
|
||||
# For Redis Sentinel to chdir to /tmp at startup is the simplest thing
|
||||
# for the process to don't interferer with administrative tasks such as
|
||||
# unmounting filesystems.
|
||||
dir /tmp
|
||||
|
||||
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
|
||||
#
|
||||
# Tells Sentinel to monitor this master, and to consider it in O_DOWN
|
||||
@@ -13,6 +20,12 @@ port 26379
|
||||
# be elected by the majority of the known Sentinels in order to
|
||||
# start a failover, so no failover can be performed in minority.
|
||||
#
|
||||
# Slaves are auto-discovered, so you don't need to specify slaves in
|
||||
# any way. Sentinel itself will rewrite this configuration file adding
|
||||
# the slaves using additional configuration options.
|
||||
# Also note that the configuration file is rewritten when a
|
||||
# slave is promoted to master.
|
||||
#
|
||||
# Note: master name should not include special characters or spaces.
|
||||
# The valid charset is A-z 0-9 and the three characters ".-_".
|
||||
sentinel monitor mymaster 127.0.0.1 6379 2
|
||||
@@ -86,10 +99,10 @@ sentinel failover-timeout mymaster 180000
|
||||
# or to reconfigure clients after a failover. The scripts are executed
|
||||
# with the following rules for error handling:
|
||||
#
|
||||
# If script exists with "1" the execution is retried later (up to a maximum
|
||||
# If script exits with "1" the execution is retried later (up to a maximum
|
||||
# number of times currently set to 10).
|
||||
#
|
||||
# If script exists with "2" (or an higher value) the script execution is
|
||||
# If script exits with "2" (or an higher value) the script execution is
|
||||
# not retried.
|
||||
#
|
||||
# If script terminates because it receives a signal the behavior is the same
|
||||
|
||||
+4
-1
@@ -107,7 +107,7 @@ endif
|
||||
|
||||
REDIS_SERVER_NAME=redis-server
|
||||
REDIS_SENTINEL_NAME=redis-sentinel
|
||||
REDIS_SERVER_OBJ=adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o
|
||||
REDIS_SERVER_OBJ=adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o
|
||||
REDIS_CLI_NAME=redis-cli
|
||||
REDIS_CLI_OBJ=anet.o sds.o adlist.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o
|
||||
REDIS_BENCHMARK_NAME=redis-benchmark
|
||||
@@ -204,6 +204,9 @@ distclean: clean
|
||||
test: $(REDIS_SERVER_NAME) $(REDIS_CHECK_AOF_NAME)
|
||||
@(cd ..; ./runtest)
|
||||
|
||||
test-sentinel: $(REDIS_SENTINEL_NAME)
|
||||
@(cd ..; ./runtest-sentinel)
|
||||
|
||||
check: test
|
||||
|
||||
lcov:
|
||||
|
||||
+7
-3
@@ -14,6 +14,9 @@ bio.o: bio.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
bitops.o: bitops.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h
|
||||
blocked.o: blocked.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h
|
||||
cluster.o: cluster.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h cluster.h endianconv.h
|
||||
@@ -32,6 +35,10 @@ debug.o: debug.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h sha1.h crc64.h bio.h
|
||||
dict.o: dict.c fmacros.h dict.h zmalloc.h redisassert.h
|
||||
endianconv.o: endianconv.c
|
||||
hyperloglog.o: hyperloglog.c redis.h fmacros.h config.h \
|
||||
../deps/lua/src/lua.h ../deps/lua/src/luaconf.h ae.h sds.h dict.h \
|
||||
adlist.h zmalloc.h anet.h ziplist.h intset.h version.h util.h rdb.h \
|
||||
rio.h
|
||||
intset.o: intset.c intset.h zmalloc.h endianconv.h config.h
|
||||
lzf_c.o: lzf_c.c lzfP.h
|
||||
lzf_d.o: lzf_d.c lzfP.h
|
||||
@@ -117,6 +124,3 @@ ziplist.o: ziplist.c zmalloc.h util.h sds.h ziplist.h endianconv.h \
|
||||
config.h redisassert.h
|
||||
zipmap.o: zipmap.c zmalloc.h endianconv.h config.h
|
||||
zmalloc.o: zmalloc.c config.h zmalloc.h
|
||||
blocked.o: blocked.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h
|
||||
|
||||
+30
-6
@@ -234,11 +234,12 @@ static int anetCreateSocket(char *err, int domain) {
|
||||
|
||||
#define ANET_CONNECT_NONE 0
|
||||
#define ANET_CONNECT_NONBLOCK 1
|
||||
static int anetTcpGenericConnect(char *err, char *addr, int port, int flags)
|
||||
static int anetTcpGenericConnect(char *err, char *addr, int port,
|
||||
char *source_addr, int flags)
|
||||
{
|
||||
int s = ANET_ERR, rv;
|
||||
char portstr[6]; /* strlen("65535") + 1; */
|
||||
struct addrinfo hints, *servinfo, *p;
|
||||
struct addrinfo hints, *servinfo, *bservinfo, *p, *b;
|
||||
|
||||
snprintf(portstr,sizeof(portstr),"%d",port);
|
||||
memset(&hints,0,sizeof(hints));
|
||||
@@ -258,6 +259,24 @@ static int anetTcpGenericConnect(char *err, char *addr, int port, int flags)
|
||||
if (anetSetReuseAddr(err,s) == ANET_ERR) goto error;
|
||||
if (flags & ANET_CONNECT_NONBLOCK && anetNonBlock(err,s) != ANET_OK)
|
||||
goto error;
|
||||
if (source_addr) {
|
||||
int bound = 0;
|
||||
/* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */
|
||||
if ((rv = getaddrinfo(source_addr, NULL, &hints, &bservinfo)) != 0) {
|
||||
anetSetError(err, "%s", gai_strerror(rv));
|
||||
goto end;
|
||||
}
|
||||
for (b = bservinfo; b != NULL; b = b->ai_next) {
|
||||
if (bind(s,b->ai_addr,b->ai_addrlen) != -1) {
|
||||
bound = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!bound) {
|
||||
anetSetError(err, "bind: %s", strerror(errno));
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
|
||||
/* If the socket is non-blocking, it is ok for connect() to
|
||||
* return an EINPROGRESS error here. */
|
||||
@@ -287,12 +306,17 @@ end:
|
||||
|
||||
int anetTcpConnect(char *err, char *addr, int port)
|
||||
{
|
||||
return anetTcpGenericConnect(err,addr,port,ANET_CONNECT_NONE);
|
||||
return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONE);
|
||||
}
|
||||
|
||||
int anetTcpNonBlockConnect(char *err, char *addr, int port)
|
||||
{
|
||||
return anetTcpGenericConnect(err,addr,port,ANET_CONNECT_NONBLOCK);
|
||||
return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONBLOCK);
|
||||
}
|
||||
|
||||
int anetTcpNonBlockBindConnect(char *err, char *addr, int port, char *source_addr)
|
||||
{
|
||||
return anetTcpGenericConnect(err,addr,port,source_addr,ANET_CONNECT_NONBLOCK);
|
||||
}
|
||||
|
||||
int anetUnixGenericConnect(char *err, char *path, int flags)
|
||||
@@ -472,7 +496,7 @@ int anetTcpAccept(char *err, int s, char *ip, size_t ip_len, int *port) {
|
||||
int fd;
|
||||
struct sockaddr_storage sa;
|
||||
socklen_t salen = sizeof(sa);
|
||||
if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == ANET_ERR)
|
||||
if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == -1)
|
||||
return ANET_ERR;
|
||||
|
||||
if (sa.ss_family == AF_INET) {
|
||||
@@ -491,7 +515,7 @@ int anetUnixAccept(char *err, int s) {
|
||||
int fd;
|
||||
struct sockaddr_un sa;
|
||||
socklen_t salen = sizeof(sa);
|
||||
if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == ANET_ERR)
|
||||
if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == -1)
|
||||
return ANET_ERR;
|
||||
|
||||
return fd;
|
||||
|
||||
@@ -45,6 +45,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 anetUnixConnect(char *err, char *path);
|
||||
int anetUnixNonBlockConnect(char *err, char *path);
|
||||
int anetRead(int fd, char *buf, int count);
|
||||
|
||||
@@ -226,6 +226,7 @@ int startAppendOnly(void) {
|
||||
*
|
||||
* However if force is set to 1 we'll write regardless of the background
|
||||
* fsync. */
|
||||
#define AOF_WRITE_LOG_ERROR_RATE 30 /* Seconds between errors logging. */
|
||||
void flushAppendOnlyFile(int force) {
|
||||
ssize_t nwritten;
|
||||
int sync_in_progress = 0;
|
||||
@@ -267,27 +268,76 @@ void flushAppendOnlyFile(int force) {
|
||||
* or alike */
|
||||
nwritten = write(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
|
||||
if (nwritten != (signed)sdslen(server.aof_buf)) {
|
||||
/* Ooops, we are in troubles. The best thing to do for now is
|
||||
* aborting instead of giving the illusion that everything is
|
||||
* working as expected. */
|
||||
static time_t last_write_error_log = 0;
|
||||
int can_log = 0;
|
||||
|
||||
/* Limit logging rate to 1 line per AOF_WRITE_LOG_ERROR_RATE seconds. */
|
||||
if ((server.unixtime - last_write_error_log) > AOF_WRITE_LOG_ERROR_RATE) {
|
||||
can_log = 1;
|
||||
last_write_error_log = server.unixtime;
|
||||
}
|
||||
|
||||
/* Lof the AOF write error and record the error code. */
|
||||
if (nwritten == -1) {
|
||||
redisLog(REDIS_WARNING,"Exiting on error writing to the append-only file: %s",strerror(errno));
|
||||
if (can_log) {
|
||||
redisLog(REDIS_WARNING,"Error writing to the AOF file: %s",
|
||||
strerror(errno));
|
||||
server.aof_last_write_errno = errno;
|
||||
}
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,"Exiting on short write while writing to "
|
||||
"the append-only file: %s (nwritten=%ld, "
|
||||
"expected=%ld)",
|
||||
strerror(errno),
|
||||
(long)nwritten,
|
||||
(long)sdslen(server.aof_buf));
|
||||
if (can_log) {
|
||||
redisLog(REDIS_WARNING,"Short write while writing to "
|
||||
"the AOF file: (nwritten=%lld, "
|
||||
"expected=%lld)",
|
||||
(long long)nwritten,
|
||||
(long long)sdslen(server.aof_buf));
|
||||
}
|
||||
|
||||
if (ftruncate(server.aof_fd, server.aof_current_size) == -1) {
|
||||
redisLog(REDIS_WARNING, "Could not remove short write "
|
||||
"from the append-only file. Redis may refuse "
|
||||
"to load the AOF the next time it starts. "
|
||||
"ftruncate: %s", strerror(errno));
|
||||
if (can_log) {
|
||||
redisLog(REDIS_WARNING, "Could not remove short write "
|
||||
"from the append-only file. Redis may refuse "
|
||||
"to load the AOF the next time it starts. "
|
||||
"ftruncate: %s", strerror(errno));
|
||||
}
|
||||
} else {
|
||||
/* If the ftrunacate() succeeded we can set nwritten to
|
||||
* -1 since there is no longer partial data into the AOF. */
|
||||
nwritten = -1;
|
||||
}
|
||||
server.aof_last_write_errno = ENOSPC;
|
||||
}
|
||||
|
||||
/* Handle the AOF write error. */
|
||||
if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
|
||||
/* We can't recover when the fsync policy is ALWAYS since the
|
||||
* reply for the client is already in the output buffers, and we
|
||||
* have the contract with the user that on acknowledged write data
|
||||
* is synched on disk. */
|
||||
redisLog(REDIS_WARNING,"Can't recover from AOF write error when the AOF fsync policy is 'always'. Exiting...");
|
||||
exit(1);
|
||||
} else {
|
||||
/* Recover from failed write leaving data into the buffer. However
|
||||
* set an error to stop accepting writes as long as the error
|
||||
* condition is not cleared. */
|
||||
server.aof_last_write_status = REDIS_ERR;
|
||||
|
||||
/* Trim the sds buffer if there was a partial write, and there
|
||||
* was no way to undo it with ftruncate(2). */
|
||||
if (nwritten > 0) {
|
||||
server.aof_current_size += nwritten;
|
||||
sdsrange(server.aof_buf,nwritten,-1);
|
||||
}
|
||||
return; /* We'll try again on the next call... */
|
||||
}
|
||||
} else {
|
||||
/* Successful write(2). If AOF was in error state, restore the
|
||||
* OK state and log the event. */
|
||||
if (server.aof_last_write_status == REDIS_ERR) {
|
||||
redisLog(REDIS_WARNING,
|
||||
"AOF write error looks solved, Redis can write again.");
|
||||
server.aof_last_write_status = REDIS_OK;
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
server.aof_current_size += nwritten;
|
||||
|
||||
@@ -457,6 +507,7 @@ struct redisClient *createFakeClient(void) {
|
||||
c->reply_bytes = 0;
|
||||
c->obuf_soft_limit_reached_time = 0;
|
||||
c->watched_keys = listCreate();
|
||||
c->peerid = NULL;
|
||||
listSetFreeMethod(c->reply,decrRefCountVoid);
|
||||
listSetDupMethod(c->reply,dupClientReplyValue);
|
||||
initClientMultiState(c);
|
||||
@@ -510,7 +561,7 @@ int loadAppendOnlyFile(char *filename) {
|
||||
/* Serve the clients from time to time */
|
||||
if (!(loops++ % 1000)) {
|
||||
loadingProgress(ftello(fp));
|
||||
aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
|
||||
processEventsWhileBlocked();
|
||||
}
|
||||
|
||||
if (fgets(buf,sizeof(buf),fp) == NULL) {
|
||||
@@ -919,9 +970,9 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
}
|
||||
|
||||
/* Make sure data will not remain on the OS's output buffers */
|
||||
fflush(fp);
|
||||
aof_fsync(fileno(fp));
|
||||
fclose(fp);
|
||||
if (fflush(fp) == EOF) goto werr;
|
||||
if (aof_fsync(fileno(fp)) == -1) goto werr;
|
||||
if (fclose(fp) == EOF) goto werr;
|
||||
|
||||
/* Use RENAME to make sure the DB file is changed atomically only
|
||||
* if the generate DB file is ok. */
|
||||
|
||||
+185
-11
@@ -60,11 +60,18 @@ static int getBitOffsetFromArgument(redisClient *c, robj *o, size_t *offset) {
|
||||
* work with a input string length up to 512 MB. */
|
||||
size_t redisPopcount(void *s, long count) {
|
||||
size_t bits = 0;
|
||||
unsigned char *p;
|
||||
uint32_t *p4 = s;
|
||||
unsigned char *p = s;
|
||||
uint32_t *p4;
|
||||
static const unsigned char bitsinbyte[256] = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8};
|
||||
|
||||
/* Count initial bytes not aligned to 32 bit. */
|
||||
while((unsigned long)p & 3 && count) {
|
||||
bits += bitsinbyte[*p++];
|
||||
count--;
|
||||
}
|
||||
|
||||
/* Count bits 16 bytes at a time */
|
||||
p4 = (uint32_t*)p;
|
||||
while(count>=16) {
|
||||
uint32_t aux1, aux2, aux3, aux4;
|
||||
|
||||
@@ -87,12 +94,99 @@ size_t redisPopcount(void *s, long count) {
|
||||
((((aux3 + (aux3 >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24) +
|
||||
((((aux4 + (aux4 >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24);
|
||||
}
|
||||
/* Count the remaining bytes */
|
||||
/* Count the remaining bytes. */
|
||||
p = (unsigned char*)p4;
|
||||
while(count--) bits += bitsinbyte[*p++];
|
||||
return bits;
|
||||
}
|
||||
|
||||
/* Return the position of the first bit set to one (if 'bit' is 1) or
|
||||
* zero (if 'bit' is 0) in the bitmap starting at 's' and long 'count' bytes.
|
||||
*
|
||||
* The function is guaranteed to return a value >= 0 if 'bit' is 0 since if
|
||||
* no zero bit is found, it returns count*8 assuming the string is zero
|
||||
* padded on the right. However if 'bit' is 1 it is possible that there is
|
||||
* not a single set bit in the bitmap. In this special case -1 is returned. */
|
||||
long redisBitpos(void *s, long count, int bit) {
|
||||
unsigned long *l;
|
||||
unsigned char *c;
|
||||
unsigned long skipval, word = 0, one;
|
||||
long pos = 0; /* Position of bit, to return to the caller. */
|
||||
int j;
|
||||
|
||||
/* Process whole words first, seeking for first word that is not
|
||||
* all ones or all zeros respectively if we are lookig for zeros
|
||||
* or ones. This is much faster with large strings having contiguous
|
||||
* blocks of 1 or 0 bits compared to the vanilla bit per bit processing.
|
||||
*
|
||||
* Note that if we start from an address that is not aligned
|
||||
* to sizeof(unsigned long) we consume it byte by byte until it is
|
||||
* aligned. */
|
||||
|
||||
/* Skip initial bits not aligned to sizeof(unsigned long) byte by byte. */
|
||||
skipval = bit ? 0 : UCHAR_MAX;
|
||||
c = (unsigned char*) s;
|
||||
while((unsigned long)c & (sizeof(*l)-1) && count) {
|
||||
if (*c != skipval) break;
|
||||
c++;
|
||||
count--;
|
||||
pos += 8;
|
||||
}
|
||||
|
||||
/* Skip bits with full word step. */
|
||||
skipval = bit ? 0 : ULONG_MAX;
|
||||
l = (unsigned long*) c;
|
||||
while (count >= sizeof(*l)) {
|
||||
if (*l != skipval) break;
|
||||
l++;
|
||||
count -= sizeof(*l);
|
||||
pos += sizeof(*l)*8;
|
||||
}
|
||||
|
||||
/* Load bytes into "word" considering the first byte as the most significant
|
||||
* (we basically consider it as written in big endian, since we consider the
|
||||
* string as a set of bits from left to right, with the first bit at position
|
||||
* zero.
|
||||
*
|
||||
* Note that the loading is designed to work even when the bytes left
|
||||
* (count) are less than a full word. We pad it with zero on the right. */
|
||||
c = (unsigned char*)l;
|
||||
for (j = 0; j < sizeof(*l); j++) {
|
||||
word <<= 8;
|
||||
if (count) {
|
||||
word |= *c;
|
||||
c++;
|
||||
count--;
|
||||
}
|
||||
}
|
||||
|
||||
/* Special case:
|
||||
* If bits in the string are all zero and we are looking for one,
|
||||
* return -1 to signal that there is not a single "1" in the whole
|
||||
* string. This can't happen when we are looking for "0" as we assume
|
||||
* that the right of the string is zero padded. */
|
||||
if (bit == 1 && word == 0) return -1;
|
||||
|
||||
/* Last word left, scan bit by bit. The first thing we need is to
|
||||
* have a single "1" set in the most significant position in an
|
||||
* unsigned long. We don't know the size of the long so we use a
|
||||
* simple trick. */
|
||||
one = ULONG_MAX; /* All bits set to 1.*/
|
||||
one >>= 1; /* All bits set to 1 but the MSB. */
|
||||
one = ~one; /* All bits set to 0 but the MSB. */
|
||||
|
||||
while(one) {
|
||||
if (((one & word) != 0) == bit) return pos;
|
||||
pos++;
|
||||
one >>= 1;
|
||||
}
|
||||
|
||||
/* If we reached this point, there is a bug in the algorithm, since
|
||||
* the case of no match is handled as a special case before. */
|
||||
redisPanic("End of redisBitpos() reached.");
|
||||
return 0; /* Just to avoid warnings. */
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Bits related string commands: GETBIT, SETBIT, BITCOUNT, BITOP.
|
||||
* -------------------------------------------------------------------------- */
|
||||
@@ -129,14 +223,7 @@ void setbitCommand(redisClient *c) {
|
||||
dbAdd(c->db,c->argv[1],o);
|
||||
} else {
|
||||
if (checkType(c,o,REDIS_STRING)) return;
|
||||
|
||||
/* Create a copy when the object is shared or encoded. */
|
||||
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
|
||||
robj *decoded = getDecodedObject(o);
|
||||
o = createRawStringObject(decoded->ptr, sdslen(decoded->ptr));
|
||||
decrRefCount(decoded);
|
||||
dbOverwrite(c->db,c->argv[1],o);
|
||||
}
|
||||
o = dbUnshareStringValue(c->db,c->argv[1],o);
|
||||
}
|
||||
|
||||
/* Grow sds value to the right length if necessary */
|
||||
@@ -410,3 +497,90 @@ void bitcountCommand(redisClient *c) {
|
||||
addReplyLongLong(c,redisPopcount(p+start,bytes));
|
||||
}
|
||||
}
|
||||
|
||||
/* BITPOS key bit [start [end]] */
|
||||
void bitposCommand(redisClient *c) {
|
||||
robj *o;
|
||||
long bit, start, end, strlen;
|
||||
unsigned char *p;
|
||||
char llbuf[32];
|
||||
int end_given = 0;
|
||||
|
||||
/* Parse the bit argument to understand what we are looking for, set
|
||||
* or clear bits. */
|
||||
if (getLongFromObjectOrReply(c,c->argv[2],&bit,NULL) != REDIS_OK)
|
||||
return;
|
||||
if (bit != 0 && bit != 1) {
|
||||
addReplyError(c, "The bit argument must be 1 or 0.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* If the key does not exist, from our point of view it is an infinite
|
||||
* array of 0 bits. If the user is looking for the fist clear bit return 0,
|
||||
* If the user is looking for the first set bit, return -1. */
|
||||
if ((o = lookupKeyRead(c->db,c->argv[1])) == NULL) {
|
||||
addReplyLongLong(c, bit ? -1 : 0);
|
||||
return;
|
||||
}
|
||||
if (checkType(c,o,REDIS_STRING)) return;
|
||||
|
||||
/* Set the 'p' pointer to the string, that can be just a stack allocated
|
||||
* array if our string was integer encoded. */
|
||||
if (o->encoding == REDIS_ENCODING_INT) {
|
||||
p = (unsigned char*) llbuf;
|
||||
strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
|
||||
} else {
|
||||
p = (unsigned char*) o->ptr;
|
||||
strlen = sdslen(o->ptr);
|
||||
}
|
||||
|
||||
/* Parse start/end range if any. */
|
||||
if (c->argc == 4 || c->argc == 5) {
|
||||
if (getLongFromObjectOrReply(c,c->argv[3],&start,NULL) != REDIS_OK)
|
||||
return;
|
||||
if (c->argc == 5) {
|
||||
if (getLongFromObjectOrReply(c,c->argv[4],&end,NULL) != REDIS_OK)
|
||||
return;
|
||||
end_given = 1;
|
||||
} else {
|
||||
end = strlen-1;
|
||||
}
|
||||
/* Convert negative indexes */
|
||||
if (start < 0) start = strlen+start;
|
||||
if (end < 0) end = strlen+end;
|
||||
if (start < 0) start = 0;
|
||||
if (end < 0) end = 0;
|
||||
if (end >= strlen) end = strlen-1;
|
||||
} else if (c->argc == 3) {
|
||||
/* The whole string. */
|
||||
start = 0;
|
||||
end = strlen-1;
|
||||
} else {
|
||||
/* Syntax error. */
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
|
||||
/* For empty ranges (start > end) we return -1 as an empty range does
|
||||
* not contain a 0 nor a 1. */
|
||||
if (start > end) {
|
||||
addReplyLongLong(c, -1);
|
||||
} else {
|
||||
long bytes = end-start+1;
|
||||
long pos = redisBitpos(p+start,bytes,bit);
|
||||
|
||||
/* If we are looking for clear bits, and the user specified an exact
|
||||
* range with start-end, we can't consider the right of the range as
|
||||
* zero padded (as we do when no explicit end is given).
|
||||
*
|
||||
* So if redisBitpos() returns the first bit outside the range,
|
||||
* we return -1 to the caller, to mean, in the specified range there
|
||||
* is not a single "0" bit. */
|
||||
if (end_given && bit == 0 && pos == bytes*8) {
|
||||
addReplyLongLong(c,-1);
|
||||
return;
|
||||
}
|
||||
if (pos != -1) pos += start*8; /* Adjust for the bytes we skipped. */
|
||||
addReplyLongLong(c,pos);
|
||||
}
|
||||
}
|
||||
|
||||
+734
-191
File diff suppressed because it is too large
Load Diff
+13
-5
@@ -10,21 +10,26 @@
|
||||
#define REDIS_CLUSTER_FAIL 1 /* The cluster can't work */
|
||||
#define REDIS_CLUSTER_NAMELEN 40 /* sha1 hex length */
|
||||
#define REDIS_CLUSTER_PORT_INCR 10000 /* Cluster port = baseport + PORT_INCR */
|
||||
#define REDIS_CLUSTER_IPLEN INET6_ADDRSTRLEN /* IPv6 address string length */
|
||||
|
||||
/* The following defines are amunt of time, sometimes expressed as
|
||||
* multiplicators of the node timeout value (when ending with MULT). */
|
||||
#define REDIS_CLUSTER_DEFAULT_NODE_TIMEOUT 15000
|
||||
#define REDIS_CLUSTER_DEFAULT_SLAVE_VALIDITY 10 /* Slave max data age factor. */
|
||||
#define REDIS_CLUSTER_FAIL_REPORT_VALIDITY_MULT 2 /* Fail report validity. */
|
||||
#define REDIS_CLUSTER_FAIL_UNDO_TIME_MULT 2 /* Undo fail if master is back. */
|
||||
#define REDIS_CLUSTER_FAIL_UNDO_TIME_ADD 10 /* Some additional time. */
|
||||
#define REDIS_CLUSTER_SLAVE_VALIDITY_MULT 10 /* Slave data validity. */
|
||||
#define REDIS_CLUSTER_FAILOVER_AUTH_RETRY_MULT 4 /* Auth request retry time. */
|
||||
#define REDIS_CLUSTER_FAILOVER_DELAY 5 /* Seconds */
|
||||
#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. */
|
||||
|
||||
/* Redirection errors returned by getNodeByQuery(). */
|
||||
#define REDIS_CLUSTER_REDIR_NONE 0 /* Node can serve the request. */
|
||||
#define REDIS_CLUSTER_REDIR_CROSS_SLOT 1 /* Keys in different slots. */
|
||||
#define REDIS_CLUSTER_REDIR_UNSTABLE 2 /* Keys in slot resharding. */
|
||||
#define REDIS_CLUSTER_REDIR_ASK 3 /* -ASK redirection required. */
|
||||
#define REDIS_CLUSTER_REDIR_MOVED 4 /* -MOVED redirection required. */
|
||||
|
||||
struct clusterNode;
|
||||
|
||||
/* clusterLink encapsulates everything needed to talk with a remote node. */
|
||||
@@ -113,7 +118,7 @@ typedef struct clusterState {
|
||||
int mf_can_start; /* If non-zero signal that the manual failover
|
||||
can start requesting masters vote. */
|
||||
/* The followign fields are uesd by masters to take state on elections. */
|
||||
uint64_t last_vote_epoch; /* Epoch of the last vote granted. */
|
||||
uint64_t lastVoteEpoch; /* Epoch of the last vote granted. */
|
||||
int todo_before_sleep; /* Things to do in clusterBeforeSleep(). */
|
||||
long long stats_bus_messages_sent; /* Num of msg sent via cluster bus. */
|
||||
long long stats_bus_messages_received; /* Num of msg rcvd via cluster bus.*/
|
||||
@@ -148,7 +153,7 @@ typedef struct {
|
||||
char nodename[REDIS_CLUSTER_NAMELEN];
|
||||
uint32_t ping_sent;
|
||||
uint32_t pong_received;
|
||||
char ip[16]; /* IP address last time it was seen */
|
||||
char ip[REDIS_IP_STR_LEN]; /* IP address last time it was seen */
|
||||
uint16_t port; /* port last time it was seen */
|
||||
uint16_t flags;
|
||||
uint32_t notused; /* for 64 bit alignment */
|
||||
@@ -195,7 +200,10 @@ union clusterMsgData {
|
||||
|
||||
|
||||
typedef struct {
|
||||
char sig[4]; /* Siganture "RCmb" (Redis Cluster message bus). */
|
||||
uint32_t totlen; /* Total length of this message */
|
||||
uint16_t ver; /* Protocol version, currently set to 0. */
|
||||
uint16_t notused0; /* 2 bytes not used. */
|
||||
uint16_t type; /* Message type */
|
||||
uint16_t count; /* Only used for some kind of messages. */
|
||||
uint64_t currentEpoch; /* The epoch accordingly to the sending node. */
|
||||
|
||||
+28
-11
@@ -391,6 +391,8 @@ void loadServerConfigFromString(char *config) {
|
||||
server.zset_max_ziplist_entries = memtoll(argv[1], NULL);
|
||||
} else if (!strcasecmp(argv[0],"zset-max-ziplist-value") && argc == 2) {
|
||||
server.zset_max_ziplist_value = memtoll(argv[1], NULL);
|
||||
} else if (!strcasecmp(argv[0],"hll-sparse-max-bytes") && argc == 2) {
|
||||
server.hll_sparse_max_bytes = memtoll(argv[1], NULL);
|
||||
} else if (!strcasecmp(argv[0],"rename-command") && argc == 3) {
|
||||
struct redisCommand *cmd = lookupCommand(argv[1]);
|
||||
int retval;
|
||||
@@ -432,7 +434,15 @@ void loadServerConfigFromString(char *config) {
|
||||
{
|
||||
server.cluster_migration_barrier = atoi(argv[1]);
|
||||
if (server.cluster_migration_barrier < 0) {
|
||||
err = "cluster migration barrier must be positive";
|
||||
err = "cluster migration barrier must zero or positive";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"cluster-slave-validity-factor")
|
||||
&& argc == 2)
|
||||
{
|
||||
server.cluster_slave_validity_factor = atoi(argv[1]);
|
||||
if (server.cluster_slave_validity_factor < 0) {
|
||||
err = "cluster slave validity factor must be zero or positive";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"lua-time-limit") && argc == 2) {
|
||||
@@ -600,7 +610,7 @@ void configSetCommand(redisClient *c) {
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"maxclients")) {
|
||||
int orig_value = server.maxclients;
|
||||
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 1) goto badfmt;
|
||||
|
||||
/* Try to check if the OS is capable of supporting so many FDs. */
|
||||
server.maxclients = ll;
|
||||
@@ -765,6 +775,9 @@ void configSetCommand(redisClient *c) {
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"zset-max-ziplist-value")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.zset_max_ziplist_value = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"hll-sparse-max-bytes")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.hll_sparse_max_bytes = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"lua-time-limit")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.lua_time_limit = ll;
|
||||
@@ -892,6 +905,10 @@ void configSetCommand(redisClient *c) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll < 0) goto badfmt;
|
||||
server.cluster_migration_barrier = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"cluster-slave-validity-factor")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll < 0) goto badfmt;
|
||||
server.cluster_slave_validity_factor = ll;
|
||||
} else {
|
||||
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
|
||||
(char*)c->argv[2]->ptr);
|
||||
@@ -974,6 +991,8 @@ void configGetCommand(redisClient *c) {
|
||||
server.zset_max_ziplist_entries);
|
||||
config_get_numerical_field("zset-max-ziplist-value",
|
||||
server.zset_max_ziplist_value);
|
||||
config_get_numerical_field("hll-sparse-max-bytes",
|
||||
server.hll_sparse_max_bytes);
|
||||
config_get_numerical_field("lua-time-limit",server.lua_time_limit);
|
||||
config_get_numerical_field("slowlog-log-slower-than",
|
||||
server.slowlog_log_slower_than);
|
||||
@@ -994,6 +1013,7 @@ void configGetCommand(redisClient *c) {
|
||||
config_get_numerical_field("hz",server.hz);
|
||||
config_get_numerical_field("cluster-node-timeout",server.cluster_node_timeout);
|
||||
config_get_numerical_field("cluster-migration-barrier",server.cluster_migration_barrier);
|
||||
config_get_numerical_field("cluster-slave-validity-factor",server.cluster_slave_validity_factor);
|
||||
|
||||
/* Bool (yes/no) values */
|
||||
config_get_bool_field("no-appendfsync-on-rewrite",
|
||||
@@ -1461,7 +1481,7 @@ void rewriteConfigSaveOption(struct rewriteConfigState *state) {
|
||||
* resulting into no RDB persistence as expected. */
|
||||
for (j = 0; j < server.saveparamslen; j++) {
|
||||
line = sdscatprintf(sdsempty(),"save %ld %d",
|
||||
server.saveparams[j].seconds, server.saveparams[j].changes);
|
||||
(long) server.saveparams[j].seconds, server.saveparams[j].changes);
|
||||
rewriteConfigRewriteLine(state,"save",line,1);
|
||||
}
|
||||
/* Mark "save" as processed in case server.saveparamslen is zero. */
|
||||
@@ -1763,6 +1783,7 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigStringOption(state,"cluster-config-file",server.cluster_configfile,REDIS_DEFAULT_CLUSTER_CONFIG_FILE);
|
||||
rewriteConfigNumericalOption(state,"cluster-node-timeout",server.cluster_node_timeout,REDIS_CLUSTER_DEFAULT_NODE_TIMEOUT);
|
||||
rewriteConfigNumericalOption(state,"cluster-migration-barrier",server.cluster_migration_barrier,REDIS_CLUSTER_DEFAULT_MIGRATION_BARRIER);
|
||||
rewriteConfigNumericalOption(state,"cluster-slave-validity-factor",server.cluster_slave_validity_factor,REDIS_CLUSTER_DEFAULT_SLAVE_VALIDITY);
|
||||
rewriteConfigNumericalOption(state,"slowlog-log-slower-than",server.slowlog_log_slower_than,REDIS_SLOWLOG_LOG_SLOWER_THAN);
|
||||
rewriteConfigNumericalOption(state,"slowlog-max-len",server.slowlog_max_len,REDIS_SLOWLOG_MAX_LEN);
|
||||
rewriteConfigNotifykeyspaceeventsOption(state);
|
||||
@@ -1773,6 +1794,7 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigNumericalOption(state,"set-max-intset-entries",server.set_max_intset_entries,REDIS_SET_MAX_INTSET_ENTRIES);
|
||||
rewriteConfigNumericalOption(state,"zset-max-ziplist-entries",server.zset_max_ziplist_entries,REDIS_ZSET_MAX_ZIPLIST_ENTRIES);
|
||||
rewriteConfigNumericalOption(state,"zset-max-ziplist-value",server.zset_max_ziplist_value,REDIS_ZSET_MAX_ZIPLIST_VALUE);
|
||||
rewriteConfigNumericalOption(state,"hll-sparse-max-bytes",server.hll_sparse_max_bytes,REDIS_DEFAULT_HLL_SPARSE_MAX_BYTES);
|
||||
rewriteConfigYesNoOption(state,"activerehashing",server.activerehashing,REDIS_DEFAULT_ACTIVE_REHASHING);
|
||||
rewriteConfigClientoutputbufferlimitOption(state);
|
||||
rewriteConfigNumericalOption(state,"hz",server.hz,REDIS_DEFAULT_HZ);
|
||||
@@ -1807,14 +1829,7 @@ void configCommand(redisClient *c) {
|
||||
configGetCommand(c);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"resetstat")) {
|
||||
if (c->argc != 2) goto badarity;
|
||||
server.stat_keyspace_hits = 0;
|
||||
server.stat_keyspace_misses = 0;
|
||||
server.stat_numcommands = 0;
|
||||
server.stat_numconnections = 0;
|
||||
server.stat_expiredkeys = 0;
|
||||
server.stat_rejected_conn = 0;
|
||||
server.stat_fork_time = 0;
|
||||
server.aof_delayed_fsync = 0;
|
||||
resetServerStats();
|
||||
resetCommandTableStats();
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"rewrite")) {
|
||||
@@ -1824,8 +1839,10 @@ void configCommand(redisClient *c) {
|
||||
return;
|
||||
}
|
||||
if (rewriteConfig(server.configfile) == -1) {
|
||||
redisLog(REDIS_WARNING,"CONFIG REWRITE failed: %s", strerror(errno));
|
||||
addReplyErrorFormat(c,"Rewriting config file: %s", strerror(errno));
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,"CONFIG REWRITE executed with success.");
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
} else {
|
||||
|
||||
+1
-1
@@ -187,7 +187,7 @@ void setproctitle(const char *fmt, ...);
|
||||
|
||||
#if (__i386 || __amd64) && __GNUC__
|
||||
#define GNUC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
|
||||
#if GNUC_VERSION >= 40100
|
||||
#if (GNUC_VERSION >= 40100) || defined(__clang__)
|
||||
#define HAVE_ATOMIC
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -50,7 +50,7 @@ robj *lookupKey(redisDb *db, robj *key) {
|
||||
* Don't do it if we have a saving child, as this will trigger
|
||||
* a copy on write madness. */
|
||||
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1)
|
||||
val->lru = server.lruclock;
|
||||
val->lru = LRU_CLOCK();
|
||||
return val;
|
||||
} else {
|
||||
return NULL;
|
||||
@@ -104,7 +104,7 @@ void dbAdd(redisDb *db, robj *key, robj *val) {
|
||||
*
|
||||
* The program is aborted if the key was not already present. */
|
||||
void dbOverwrite(redisDb *db, robj *key, robj *val) {
|
||||
struct dictEntry *de = dictFind(db->dict,key->ptr);
|
||||
dictEntry *de = dictFind(db->dict,key->ptr);
|
||||
|
||||
redisAssertWithInfo(NULL,key,de != NULL);
|
||||
dictReplace(db->dict, key->ptr, val);
|
||||
@@ -136,7 +136,7 @@ int dbExists(redisDb *db, robj *key) {
|
||||
*
|
||||
* The function makes sure to return keys not already expired. */
|
||||
robj *dbRandomKey(redisDb *db) {
|
||||
struct dictEntry *de;
|
||||
dictEntry *de;
|
||||
|
||||
while(1) {
|
||||
sds key;
|
||||
@@ -170,6 +170,44 @@ int dbDelete(redisDb *db, robj *key) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Prepare the string object stored at 'key' to be modified destructively
|
||||
* to implement commands like SETBIT or APPEND.
|
||||
*
|
||||
* An object is usually ready to be modified unless one of the two conditions
|
||||
* are true:
|
||||
*
|
||||
* 1) The object 'o' is shared (refcount > 1), we don't want to affect
|
||||
* other users.
|
||||
* 2) The object encoding is not "RAW".
|
||||
*
|
||||
* If the object is found in one of the above conditions (or both) by the
|
||||
* function, an unshared / not-encoded copy of the string object is stored
|
||||
* at 'key' in the specified 'db'. Otherwise the object 'o' itself is
|
||||
* returned.
|
||||
*
|
||||
* USAGE:
|
||||
*
|
||||
* The object 'o' is what the caller already obtained by looking up 'key'
|
||||
* in 'db', the usage pattern looks like this:
|
||||
*
|
||||
* o = lookupKeyWrite(db,key);
|
||||
* if (checkType(c,o,REDIS_STRING)) return;
|
||||
* o = dbUnshareStringValue(db,key,o);
|
||||
*
|
||||
* At this point the caller is ready to modify the object, for example
|
||||
* using an sdscat() call to append some data, or anything else.
|
||||
*/
|
||||
robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {
|
||||
redisAssert(o->type == REDIS_STRING);
|
||||
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
|
||||
robj *decoded = getDecodedObject(o);
|
||||
o = createRawStringObject(decoded->ptr, sdslen(decoded->ptr));
|
||||
decrRefCount(decoded);
|
||||
dbOverwrite(db,key,o);
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
long long emptyDb(void(callback)(void*)) {
|
||||
int j;
|
||||
long long removed = 0;
|
||||
@@ -242,6 +280,7 @@ void delCommand(redisClient *c) {
|
||||
int deleted = 0, j;
|
||||
|
||||
for (j = 1; j < c->argc; j++) {
|
||||
expireIfNeeded(c->db,c->argv[j]);
|
||||
if (dbDelete(c->db,c->argv[j])) {
|
||||
signalModifiedKey(c->db,c->argv[j]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,
|
||||
@@ -930,6 +969,8 @@ void persistCommand(redisClient *c) {
|
||||
* API to get key arguments from commands
|
||||
* ---------------------------------------------------------------------------*/
|
||||
|
||||
/* The base case is to use the keys position as given in the command table
|
||||
* (firstkey, lastkey, step). */
|
||||
int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, int *numkeys) {
|
||||
int j, i = 0, last, *keys;
|
||||
REDIS_NOTUSED(argv);
|
||||
@@ -949,42 +990,36 @@ int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, in
|
||||
return keys;
|
||||
}
|
||||
|
||||
int *getKeysFromCommand(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) {
|
||||
/* Return all the arguments that are keys in the command passed via argc / argv.
|
||||
*
|
||||
* The command returns the positions of all the key arguments inside the array,
|
||||
* so the actual return value is an heap allocated array of integers. The
|
||||
* length of the array is returned by reference into *numkeys.
|
||||
*
|
||||
* 'cmd' must be point to the corresponding entry into the redisCommand
|
||||
* table, according to the command name in argv[0].
|
||||
*
|
||||
* This function uses the command table if a command-specific helper function
|
||||
* is not required, otherwise it calls the command-specific function. */
|
||||
int *getKeysFromCommand(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
if (cmd->getkeys_proc) {
|
||||
return cmd->getkeys_proc(cmd,argv,argc,numkeys,flags);
|
||||
return cmd->getkeys_proc(cmd,argv,argc,numkeys);
|
||||
} else {
|
||||
return getKeysUsingCommandTable(cmd,argv,argc,numkeys);
|
||||
}
|
||||
}
|
||||
|
||||
/* Free the result of getKeysFromCommand. */
|
||||
void getKeysFreeResult(int *result) {
|
||||
zfree(result);
|
||||
}
|
||||
|
||||
int *noPreloadGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) {
|
||||
if (flags & REDIS_GETKEYS_PRELOAD) {
|
||||
*numkeys = 0;
|
||||
return NULL;
|
||||
} else {
|
||||
return getKeysUsingCommandTable(cmd,argv,argc,numkeys);
|
||||
}
|
||||
}
|
||||
|
||||
int *renameGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) {
|
||||
if (flags & REDIS_GETKEYS_PRELOAD) {
|
||||
int *keys = zmalloc(sizeof(int));
|
||||
*numkeys = 1;
|
||||
keys[0] = 1;
|
||||
return keys;
|
||||
} else {
|
||||
return getKeysUsingCommandTable(cmd,argv,argc,numkeys);
|
||||
}
|
||||
}
|
||||
|
||||
int *zunionInterGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) {
|
||||
/* Helper function to extract keys from following commands:
|
||||
* ZUNIONSTORE <destkey> <num-keys> <key> <key> ... <key> <options>
|
||||
* ZINTERSTORE <destkey> <num-keys> <key> <key> ... <key> <options> */
|
||||
int *zunionInterGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
int i, num, *keys;
|
||||
REDIS_NOTUSED(cmd);
|
||||
REDIS_NOTUSED(flags);
|
||||
|
||||
num = atoi(argv[2]->ptr);
|
||||
/* Sanity check. Don't return any key if the command is going to
|
||||
@@ -993,8 +1028,89 @@ int *zunionInterGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *num
|
||||
*numkeys = 0;
|
||||
return NULL;
|
||||
}
|
||||
keys = zmalloc(sizeof(int)*num);
|
||||
|
||||
/* Keys in z{union,inter}store come from two places:
|
||||
* argv[1] = storage key,
|
||||
* argv[3...n] = keys to intersect */
|
||||
keys = zmalloc(sizeof(int)*(num+1));
|
||||
|
||||
/* Add all key positions for argv[3...n] to keys[] */
|
||||
for (i = 0; i < num; i++) keys[i] = 3+i;
|
||||
|
||||
/* Finally add the argv[1] key position (the storage key target). */
|
||||
keys[num] = 1;
|
||||
*numkeys = num+1; /* Total keys = {union,inter} keys + storage key */
|
||||
return keys;
|
||||
}
|
||||
|
||||
/* Helper function to extract keys from the following commands:
|
||||
* EVAL <script> <num-keys> <key> <key> ... <key> [more stuff]
|
||||
* EVALSHA <script> <num-keys> <key> <key> ... <key> [more stuff] */
|
||||
int *evalGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
int i, num, *keys;
|
||||
REDIS_NOTUSED(cmd);
|
||||
|
||||
num = atoi(argv[2]->ptr);
|
||||
/* Sanity check. Don't return any key if the command is going to
|
||||
* reply with syntax error. */
|
||||
if (num > (argc-3)) {
|
||||
*numkeys = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
keys = zmalloc(sizeof(int)*num);
|
||||
*numkeys = num;
|
||||
|
||||
/* Add all key positions for argv[3...n] to keys[] */
|
||||
for (i = 0; i < num; i++) keys[i] = 3+i;
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
/* Helper function to extract keys from the SORT command.
|
||||
*
|
||||
* SORT <sort-key> ... STORE <store-key> ...
|
||||
*
|
||||
* The first argument of SORT is always a key, however a list of options
|
||||
* follow in SQL-alike style. Here we parse just the minimum in order to
|
||||
* correctly identify keys in the "STORE" option. */
|
||||
int *sortGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
int i, j, num, *keys;
|
||||
REDIS_NOTUSED(cmd);
|
||||
|
||||
num = 0;
|
||||
keys = zmalloc(sizeof(int)*2); /* Alloc 2 places for the worst case. */
|
||||
|
||||
keys[num++] = 1; /* <sort-key> is always present. */
|
||||
|
||||
/* Search for STORE option. By default we consider options to don't
|
||||
* have arguments, so if we find an unknown option name we scan the
|
||||
* next. However there are options with 1 or 2 arguments, so we
|
||||
* provide a list here in order to skip the right number of args. */
|
||||
struct {
|
||||
char *name;
|
||||
int skip;
|
||||
} skiplist[] = {
|
||||
{"limit", 2},
|
||||
{"get", 1},
|
||||
{"by", 1},
|
||||
{NULL, 0} /* End of elements. */
|
||||
};
|
||||
|
||||
for (i = 2; i < argc; i++) {
|
||||
for (j = 0; skiplist[j].name != NULL; j++) {
|
||||
if (!strcasecmp(argv[i]->ptr,skiplist[j].name)) {
|
||||
i += skiplist[j].skip;
|
||||
break;
|
||||
} else if (!strcasecmp(argv[i]->ptr,"store") && i+1 < argc) {
|
||||
/* Note: we don't increment "num" here and continue the loop
|
||||
* to be sure to process the *last* "STORE" option if multiple
|
||||
* ones are provided. This is same behavior as SORT. */
|
||||
keys[num] = i+1; /* <store-key> */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
*numkeys = num;
|
||||
return keys;
|
||||
}
|
||||
@@ -1027,8 +1143,8 @@ unsigned int getKeysInSlot(unsigned int hashslot, robj **keys, unsigned int coun
|
||||
|
||||
range.min = range.max = hashslot;
|
||||
range.minex = range.maxex = 0;
|
||||
|
||||
n = zslFirstInRange(server.cluster->slots_to_keys, range);
|
||||
|
||||
n = zslFirstInRange(server.cluster->slots_to_keys, &range);
|
||||
while(n && n->score == hashslot && count--) {
|
||||
keys[j++] = n->obj;
|
||||
n = n->level[0].forward;
|
||||
@@ -1036,6 +1152,28 @@ unsigned int getKeysInSlot(unsigned int hashslot, robj **keys, unsigned int coun
|
||||
return j;
|
||||
}
|
||||
|
||||
/* Remove all the keys in the specified hash slot.
|
||||
* The number of removed items is returned. */
|
||||
unsigned int delKeysInSlot(unsigned int hashslot) {
|
||||
zskiplistNode *n;
|
||||
zrangespec range;
|
||||
int j = 0;
|
||||
|
||||
range.min = range.max = hashslot;
|
||||
range.minex = range.maxex = 0;
|
||||
|
||||
n = zslFirstInRange(server.cluster->slots_to_keys, &range);
|
||||
while(n && n->score == hashslot) {
|
||||
robj *key = n->obj;
|
||||
n = n->level[0].forward; /* Go to the next item before freeing it. */
|
||||
incrRefCount(key); /* Protect the object while freeing it. */
|
||||
dbDelete(&server.db[0],key);
|
||||
decrRefCount(key);
|
||||
j++;
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
unsigned int countKeysInSlot(unsigned int hashslot) {
|
||||
zskiplist *zsl = server.cluster->slots_to_keys;
|
||||
zskiplistNode *zn;
|
||||
@@ -1046,7 +1184,7 @@ unsigned int countKeysInSlot(unsigned int hashslot) {
|
||||
range.minex = range.maxex = 0;
|
||||
|
||||
/* Find first element in range */
|
||||
zn = zslFirstInRange(zsl, range);
|
||||
zn = zslFirstInRange(zsl, &range);
|
||||
|
||||
/* Use rank of first element, if any, to determine preliminary count */
|
||||
if (zn != NULL) {
|
||||
@@ -1054,7 +1192,7 @@ unsigned int countKeysInSlot(unsigned int hashslot) {
|
||||
count = (zsl->length - (rank - 1));
|
||||
|
||||
/* Find last element in range */
|
||||
zn = zslLastInRange(zsl, range);
|
||||
zn = zslLastInRange(zsl, &range);
|
||||
|
||||
/* Use rank of last element, if any, to determine the actual count */
|
||||
if (zn != NULL) {
|
||||
|
||||
+29
-3
@@ -292,7 +292,7 @@ void debugCommand(redisClient *c) {
|
||||
addReplyStatusFormat(c,
|
||||
"Value at:%p refcount:%d "
|
||||
"encoding:%s serializedlength:%lld "
|
||||
"lru:%d lru_seconds_idle:%lu",
|
||||
"lru:%d lru_seconds_idle:%llu",
|
||||
(void*)val, val->refcount,
|
||||
strenc, (long long) rdbSavedObjectLen(val),
|
||||
val->lru, estimateObjectIdleTime(val));
|
||||
@@ -326,6 +326,7 @@ void debugCommand(redisClient *c) {
|
||||
|
||||
if (getLongFromObjectOrReply(c, c->argv[2], &keys, NULL) != REDIS_OK)
|
||||
return;
|
||||
dictExpand(c->db->dict,keys);
|
||||
for (j = 0; j < keys; j++) {
|
||||
snprintf(buf,sizeof(buf),"key:%lu",j);
|
||||
key = createStringObject(buf,strlen(buf));
|
||||
@@ -363,6 +364,31 @@ void debugCommand(redisClient *c) {
|
||||
{
|
||||
server.active_expire_enabled = atoi(c->argv[2]->ptr);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"cmdkeys") && c->argc >= 3) {
|
||||
struct redisCommand *cmd = lookupCommand(c->argv[2]->ptr);
|
||||
int *keys, numkeys, j;
|
||||
|
||||
if (!cmd) {
|
||||
addReplyError(c,"Invalid command specified");
|
||||
return;
|
||||
} else if ((cmd->arity > 0 && cmd->arity != c->argc-2) ||
|
||||
((c->argc-2) < -cmd->arity))
|
||||
{
|
||||
addReplyError(c,"Invalid number of arguments specified for command");
|
||||
return;
|
||||
}
|
||||
|
||||
keys = getKeysFromCommand(cmd,c->argv+2,c->argc-2,&numkeys);
|
||||
addReplyMultiBulkLen(c,numkeys);
|
||||
for (j = 0; j < numkeys; j++) addReplyBulk(c,c->argv[keys[j]+2]);
|
||||
getKeysFreeResult(keys);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"error") && c->argc == 3) {
|
||||
sds errstr = sdsnewlen("-",1);
|
||||
|
||||
errstr = sdscatsds(errstr,c->argv[2]->ptr);
|
||||
errstr = sdsmapchars(errstr,"\n\r"," ",2); /* no newlines in errors. */
|
||||
errstr = sdscatlen(errstr,"\r\n",2);
|
||||
addReplySds(c,errstr);
|
||||
} else {
|
||||
addReplyErrorFormat(c, "Unknown DEBUG subcommand or wrong number of arguments for '%s'",
|
||||
(char*)c->argv[1]->ptr);
|
||||
@@ -676,7 +702,7 @@ void logCurrentClient(void) {
|
||||
int j;
|
||||
|
||||
redisLog(REDIS_WARNING, "--- CURRENT CLIENT INFO");
|
||||
client = getClientInfoString(cc);
|
||||
client = catClientInfoString(sdsempty(),cc);
|
||||
redisLog(REDIS_WARNING,"client: %s", client);
|
||||
sdsfree(client);
|
||||
for (j = 0; j < cc->argc; j++) {
|
||||
@@ -921,7 +947,7 @@ void enableWatchdog(int period) {
|
||||
/* Watchdog was actually disabled, so we have to setup the signal
|
||||
* handler. */
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_flags = SA_NODEFER | SA_ONSTACK | SA_SIGINFO;
|
||||
act.sa_flags = SA_ONSTACK | SA_SIGINFO;
|
||||
act.sa_sigaction = watchdogSignalHandler;
|
||||
sigaction(SIGALRM, &act, NULL);
|
||||
}
|
||||
|
||||
+54
-2
@@ -239,7 +239,7 @@ int dictExpand(dict *d, unsigned long size)
|
||||
/* Performs N steps of incremental rehashing. Returns 1 if there are still
|
||||
* keys to move from the old to the new hash table, otherwise 0 is returned.
|
||||
* Note that a rehashing step consists in moving a bucket (that may have more
|
||||
* thank one key as we use chaining) from the old to the new hash table. */
|
||||
* than one key as we use chaining) from the old to the new hash table. */
|
||||
int dictRehash(dict *d, int n) {
|
||||
if (!dictIsRehashing(d)) return 0;
|
||||
|
||||
@@ -649,6 +649,58 @@ dictEntry *dictGetRandomKey(dict *d)
|
||||
return he;
|
||||
}
|
||||
|
||||
/* This is a version of dictGetRandomKey() that is modified in order to
|
||||
* return multiple entries by jumping at a random place of the hash table
|
||||
* and scanning linearly for entries.
|
||||
*
|
||||
* Returned pointers to hash table entries are stored into 'des' that
|
||||
* points to an array of dictEntry pointers. The array must have room for
|
||||
* at least 'count' elements, that is the argument we pass to the function
|
||||
* to tell how many random elements we need.
|
||||
*
|
||||
* The function returns the number of items stored into 'des', that may
|
||||
* be less than 'count' if the hash table has less than 'count' elements
|
||||
* inside.
|
||||
*
|
||||
* Note that this function is not suitable when you need a good distribution
|
||||
* of the returned items, but only when you need to "sample" a given number
|
||||
* of continuous elements to run some kind of algorithm or to produce
|
||||
* statistics. However the function is much faster than dictGetRandomKey()
|
||||
* at producing N elements, and the elements are guaranteed to be non
|
||||
* repeating. */
|
||||
int dictGetRandomKeys(dict *d, dictEntry **des, int count) {
|
||||
int j; /* internal hash table id, 0 or 1. */
|
||||
int stored = 0;
|
||||
|
||||
if (dictSize(d) < count) count = dictSize(d);
|
||||
while(stored < count) {
|
||||
for (j = 0; j < 2; j++) {
|
||||
/* Pick a random point inside the hash table 0 or 1. */
|
||||
unsigned int i = random() & d->ht[j].sizemask;
|
||||
int size = d->ht[j].size;
|
||||
|
||||
/* Make sure to visit every bucket by iterating 'size' times. */
|
||||
while(size--) {
|
||||
dictEntry *he = d->ht[j].table[i];
|
||||
while (he) {
|
||||
/* Collect all the elements of the buckets found non
|
||||
* empty while iterating. */
|
||||
*des = he;
|
||||
des++;
|
||||
he = he->next;
|
||||
stored++;
|
||||
if (stored == count) return stored;
|
||||
}
|
||||
i = (i+1) & d->ht[j].sizemask;
|
||||
}
|
||||
/* If there is only one table and we iterated it all, we should
|
||||
* already have 'count' elements. Assert this condition. */
|
||||
assert(dictIsRehashing(d) != 0);
|
||||
}
|
||||
}
|
||||
return stored; /* Never reached. */
|
||||
}
|
||||
|
||||
/* Function to reverse bits. Algorithm from:
|
||||
* http://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel */
|
||||
static unsigned long rev(unsigned long v) {
|
||||
@@ -695,7 +747,7 @@ static unsigned long rev(unsigned long v) {
|
||||
* (where SIZE-1 is always the mask that is equivalent to taking the rest
|
||||
* of the division between the Hash of the key and SIZE).
|
||||
*
|
||||
* For example if the current hash table size is 64, the mask is
|
||||
* For example if the current hash table size is 16, the mask is
|
||||
* (in binary) 1111. The position of a key in the hash table will be always
|
||||
* the last four bits of the hash output, and so forth.
|
||||
*
|
||||
|
||||
@@ -157,6 +157,7 @@ dictIterator *dictGetSafeIterator(dict *d);
|
||||
dictEntry *dictNext(dictIterator *iter);
|
||||
void dictReleaseIterator(dictIterator *iter);
|
||||
dictEntry *dictGetRandomKey(dict *d);
|
||||
int dictGetRandomKeys(dict *d, dictEntry **des, int count);
|
||||
void dictPrintStats(dict *d);
|
||||
unsigned int dictGenHashFunction(const void *key, int len);
|
||||
unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len);
|
||||
|
||||
+73
-2
@@ -14,7 +14,8 @@ static char *commandGroups[] = {
|
||||
"transactions",
|
||||
"connection",
|
||||
"server",
|
||||
"scripting"
|
||||
"scripting",
|
||||
"hyperloglog"
|
||||
};
|
||||
|
||||
struct commandHelp {
|
||||
@@ -54,6 +55,11 @@ struct commandHelp {
|
||||
"Perform bitwise operations between strings",
|
||||
1,
|
||||
"2.6.0" },
|
||||
{ "BITPOS",
|
||||
"key bit [start] [end]",
|
||||
"Find first bit set or clear in a string",
|
||||
1,
|
||||
"2.8.7" },
|
||||
{ "BLPOP",
|
||||
"key [key ...] timeout",
|
||||
"Remove and get the first element in a list, or block until one is available",
|
||||
@@ -84,6 +90,11 @@ struct commandHelp {
|
||||
"Get the list of client connections",
|
||||
9,
|
||||
"2.4.0" },
|
||||
{ "CLIENT PAUSE",
|
||||
"timeout",
|
||||
"Stop processing commands from clients for some time",
|
||||
9,
|
||||
"2.9.50" },
|
||||
{ "CLIENT SETNAME",
|
||||
"connection-name",
|
||||
"Set the current connection name",
|
||||
@@ -99,6 +110,11 @@ struct commandHelp {
|
||||
"Reset the stats returned by INFO",
|
||||
9,
|
||||
"2.0.0" },
|
||||
{ "CONFIG REWRITE",
|
||||
"-",
|
||||
"Rewrite the configuration file with the in memory configuration",
|
||||
9,
|
||||
"2.8.0" },
|
||||
{ "CONFIG SET",
|
||||
"parameter value",
|
||||
"Set a configuration parameter to the given value",
|
||||
@@ -259,6 +275,11 @@ struct commandHelp {
|
||||
"Set multiple hash fields to multiple values",
|
||||
5,
|
||||
"2.0.0" },
|
||||
{ "HSCAN",
|
||||
"key cursor [MATCH pattern] [COUNT count]",
|
||||
"Incrementally iterate hash fields and associated values",
|
||||
5,
|
||||
"2.8.0" },
|
||||
{ "HSET",
|
||||
"key field value",
|
||||
"Set the string value of a hash field",
|
||||
@@ -360,7 +381,7 @@ struct commandHelp {
|
||||
1,
|
||||
"1.0.0" },
|
||||
{ "MIGRATE",
|
||||
"host port key destination-db timeout",
|
||||
"host port key destination-db timeout [COPY] [REPLACE]",
|
||||
"Atomically transfer a key from a Redis instance to another one.",
|
||||
0,
|
||||
"2.6.0" },
|
||||
@@ -409,6 +430,21 @@ struct commandHelp {
|
||||
"Set the expiration for a key as a UNIX timestamp specified in milliseconds",
|
||||
0,
|
||||
"2.6.0" },
|
||||
{ "PFADD",
|
||||
"key element [element ...]",
|
||||
"Adds the specified elements to the specified HyperLogLog.",
|
||||
11,
|
||||
"2.8.9" },
|
||||
{ "PFCOUNT",
|
||||
"key [key ...]",
|
||||
"Return the approximated cardinality of the set(s) observed by the HyperLogLog at key(s).",
|
||||
11,
|
||||
"2.8.9" },
|
||||
{ "PFMERGE",
|
||||
"destkey sourcekey [sourcekey ...]",
|
||||
"Merge N different HyperLogLogs into a single one.",
|
||||
11,
|
||||
"2.8.9" },
|
||||
{ "PING",
|
||||
"-",
|
||||
"Ping the server",
|
||||
@@ -434,6 +470,11 @@ struct commandHelp {
|
||||
"Post a message to a channel",
|
||||
6,
|
||||
"2.0.0" },
|
||||
{ "PUBSUB",
|
||||
"subcommand [argument [argument ...]]",
|
||||
"Inspect the state of the Pub/Sub subsystem",
|
||||
6,
|
||||
"2.8.0" },
|
||||
{ "PUNSUBSCRIBE",
|
||||
"[pattern [pattern ...]]",
|
||||
"Stop listening for messages posted to channels matching the given patterns",
|
||||
@@ -494,6 +535,11 @@ struct commandHelp {
|
||||
"Synchronously save the dataset to disk",
|
||||
9,
|
||||
"1.0.0" },
|
||||
{ "SCAN",
|
||||
"cursor [MATCH pattern] [COUNT count]",
|
||||
"Incrementally iterate the keys space",
|
||||
0,
|
||||
"2.8.0" },
|
||||
{ "SCARD",
|
||||
"key",
|
||||
"Get the number of members in a set",
|
||||
@@ -619,6 +665,11 @@ struct commandHelp {
|
||||
"Remove one or more members from a set",
|
||||
3,
|
||||
"1.0.0" },
|
||||
{ "SSCAN",
|
||||
"key cursor [MATCH pattern] [COUNT count]",
|
||||
"Incrementally iterate Set elements",
|
||||
3,
|
||||
"2.8.0" },
|
||||
{ "STRLEN",
|
||||
"key",
|
||||
"Get the length of the value stored in a key",
|
||||
@@ -699,11 +750,21 @@ struct commandHelp {
|
||||
"Intersect multiple sorted sets and store the resulting sorted set in a new key",
|
||||
4,
|
||||
"2.0.0" },
|
||||
{ "ZLEXCOUNT",
|
||||
"key min max",
|
||||
"Count the number of members in a sorted set between a given lexicographical range",
|
||||
4,
|
||||
"2.8.9" },
|
||||
{ "ZRANGE",
|
||||
"key start stop [WITHSCORES]",
|
||||
"Return a range of members in a sorted set, by index",
|
||||
4,
|
||||
"1.2.0" },
|
||||
{ "ZRANGEBYLEX",
|
||||
"key min max [LIMIT offset count]",
|
||||
"Return a range of members in a sorted set, by lexicographical range",
|
||||
4,
|
||||
"2.8.9" },
|
||||
{ "ZRANGEBYSCORE",
|
||||
"key min max [WITHSCORES] [LIMIT offset count]",
|
||||
"Return a range of members in a sorted set, by score",
|
||||
@@ -719,6 +780,11 @@ struct commandHelp {
|
||||
"Remove one or more members from a sorted set",
|
||||
4,
|
||||
"1.2.0" },
|
||||
{ "ZREMRANGEBYLEX",
|
||||
"key min max",
|
||||
"Remove all members in a sorted set between the given lexicographical range",
|
||||
4,
|
||||
"2.8.9" },
|
||||
{ "ZREMRANGEBYRANK",
|
||||
"key start stop",
|
||||
"Remove all members in a sorted set within the given indexes",
|
||||
@@ -744,6 +810,11 @@ struct commandHelp {
|
||||
"Determine the index of a member in a sorted set, with scores ordered from high to low",
|
||||
4,
|
||||
"2.0.0" },
|
||||
{ "ZSCAN",
|
||||
"key cursor [MATCH pattern] [COUNT count]",
|
||||
"Incrementally iterate sorted sets elements and associated scores",
|
||||
4,
|
||||
"2.8.0" },
|
||||
{ "ZSCORE",
|
||||
"key member",
|
||||
"Get the score associated with the given member in a sorted set",
|
||||
|
||||
+1548
File diff suppressed because it is too large
Load Diff
+88
-43
@@ -118,6 +118,7 @@ redisClient *createClient(int fd) {
|
||||
c->watched_keys = listCreate();
|
||||
c->pubsub_channels = dictCreate(&setDictType,NULL);
|
||||
c->pubsub_patterns = listCreate();
|
||||
c->peerid = NULL;
|
||||
listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
|
||||
listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
|
||||
if (fd != -1) listAddNodeTail(server.clients,c);
|
||||
@@ -551,6 +552,7 @@ void copyClientOutputBuffer(redisClient *dst, redisClient *src) {
|
||||
dst->reply_bytes = src->reply_bytes;
|
||||
}
|
||||
|
||||
#define MAX_ACCEPTS_PER_CALL 1000
|
||||
static void acceptCommonHandler(int fd, int flags) {
|
||||
redisClient *c;
|
||||
if ((c = createClient(fd)) == NULL) {
|
||||
@@ -580,37 +582,44 @@ static void acceptCommonHandler(int fd, int flags) {
|
||||
}
|
||||
|
||||
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
int cport, cfd;
|
||||
int cport, cfd, max = MAX_ACCEPTS_PER_CALL;
|
||||
char cip[REDIS_IP_STR_LEN];
|
||||
REDIS_NOTUSED(el);
|
||||
REDIS_NOTUSED(mask);
|
||||
REDIS_NOTUSED(privdata);
|
||||
|
||||
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
|
||||
if (cfd == AE_ERR) {
|
||||
redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);
|
||||
return;
|
||||
while(max--) {
|
||||
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
|
||||
if (cfd == ANET_ERR) {
|
||||
if (errno != EWOULDBLOCK)
|
||||
redisLog(REDIS_WARNING,
|
||||
"Accepting client connection: %s", server.neterr);
|
||||
return;
|
||||
}
|
||||
redisLog(REDIS_VERBOSE,"Accepted %s:%d", cip, cport);
|
||||
acceptCommonHandler(cfd,0);
|
||||
}
|
||||
redisLog(REDIS_VERBOSE,"Accepted %s:%d", cip, cport);
|
||||
acceptCommonHandler(cfd,0);
|
||||
}
|
||||
|
||||
void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
int cfd;
|
||||
int cfd, max = MAX_ACCEPTS_PER_CALL;
|
||||
REDIS_NOTUSED(el);
|
||||
REDIS_NOTUSED(mask);
|
||||
REDIS_NOTUSED(privdata);
|
||||
|
||||
cfd = anetUnixAccept(server.neterr, fd);
|
||||
if (cfd == AE_ERR) {
|
||||
redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);
|
||||
return;
|
||||
while(max--) {
|
||||
cfd = anetUnixAccept(server.neterr, fd);
|
||||
if (cfd == ANET_ERR) {
|
||||
if (errno != EWOULDBLOCK)
|
||||
redisLog(REDIS_WARNING,
|
||||
"Accepting client connection: %s", server.neterr);
|
||||
return;
|
||||
}
|
||||
redisLog(REDIS_VERBOSE,"Accepted connection to %s", server.unixsocket);
|
||||
acceptCommonHandler(cfd,REDIS_UNIX_SOCKET);
|
||||
}
|
||||
redisLog(REDIS_VERBOSE,"Accepted connection to %s", server.unixsocket);
|
||||
acceptCommonHandler(cfd,REDIS_UNIX_SOCKET);
|
||||
}
|
||||
|
||||
|
||||
static void freeClientArgv(redisClient *c) {
|
||||
int j;
|
||||
for (j = 0; j < c->argc; j++)
|
||||
@@ -755,6 +764,7 @@ void freeClient(redisClient *c) {
|
||||
if (c->name) decrRefCount(c->name);
|
||||
zfree(c->argv);
|
||||
freeClientMultiState(c);
|
||||
sdsfree(c->peerid);
|
||||
zfree(c);
|
||||
}
|
||||
|
||||
@@ -938,7 +948,7 @@ int processInlineBuffer(redisClient *c) {
|
||||
* multi bulk requests idempotent. */
|
||||
static void setProtocolError(redisClient *c, int pos) {
|
||||
if (server.verbosity >= REDIS_VERBOSE) {
|
||||
sds client = getClientInfoString(c);
|
||||
sds client = catClientInfoString(sdsempty(),c);
|
||||
redisLog(REDIS_VERBOSE,
|
||||
"Protocol error from client: %s", client);
|
||||
sdsfree(client);
|
||||
@@ -1000,8 +1010,10 @@ int processMultibulkBuffer(redisClient *c) {
|
||||
newline = strchr(c->querybuf+pos,'\r');
|
||||
if (newline == NULL) {
|
||||
if (sdslen(c->querybuf) > REDIS_INLINE_MAX_SIZE) {
|
||||
addReplyError(c,"Protocol error: too big bulk count string");
|
||||
addReplyError(c,
|
||||
"Protocol error: too big bulk count string");
|
||||
setProtocolError(c,0);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1174,7 +1186,7 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
return;
|
||||
}
|
||||
if (sdslen(c->querybuf) > server.client_max_querybuf_len) {
|
||||
sds ci = getClientInfoString(c), bytes = sdsempty();
|
||||
sds ci = catClientInfoString(sdsempty(),c), bytes = sdsempty();
|
||||
|
||||
bytes = sdscatrepr(bytes,c->querybuf,64);
|
||||
redisLog(REDIS_WARNING,"Closing client that reached max query buffer length: %s (qbuf initial bytes: %s)", ci, bytes);
|
||||
@@ -1205,7 +1217,7 @@ void getClientsMaxBuffers(unsigned long *longest_output_list,
|
||||
*biggest_input_buffer = bib;
|
||||
}
|
||||
|
||||
/* This is a helper function for getClientPeerId().
|
||||
/* This is a helper function for genClientPeerId().
|
||||
* It writes the specified ip/port to "peerid" as a null termiated string
|
||||
* in the form ip:port if ip does not contain ":" itself, otherwise
|
||||
* [ip]:port format is used (for IPv6 addresses basically). */
|
||||
@@ -1229,7 +1241,7 @@ void formatPeerId(char *peerid, size_t peerid_len, char *ip, int port) {
|
||||
* On failure the function still populates 'peerid' with the "?:0" string
|
||||
* in case you want to relax error checking or need to display something
|
||||
* anyway (see anetPeerToString implementation for more info). */
|
||||
int getClientPeerId(redisClient *client, char *peerid, size_t peerid_len) {
|
||||
int genClientPeerId(redisClient *client, char *peerid, size_t peerid_len) {
|
||||
char ip[REDIS_IP_STR_LEN];
|
||||
int port;
|
||||
|
||||
@@ -1245,12 +1257,26 @@ int getClientPeerId(redisClient *client, char *peerid, size_t peerid_len) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Turn a Redis client into an sds string representing its state. */
|
||||
sds getClientInfoString(redisClient *client) {
|
||||
char peerid[REDIS_PEER_ID_LEN], flags[16], events[3], *p;
|
||||
/* This function returns the client peer id, by creating and caching it
|
||||
* if client->perrid is NULL, otherwise returning the cached value.
|
||||
* The Peer ID never changes during the life of the client, however it
|
||||
* is expensive to compute. */
|
||||
char *getClientPeerId(redisClient *c) {
|
||||
char peerid[REDIS_PEER_ID_LEN];
|
||||
|
||||
if (c->peerid == NULL) {
|
||||
genClientPeerId(c,peerid,sizeof(peerid));
|
||||
c->peerid = sdsnew(peerid);
|
||||
}
|
||||
return c->peerid;
|
||||
}
|
||||
|
||||
/* Concatenate a string representing the state of a client in an human
|
||||
* readable format, into the sds string 's'. */
|
||||
sds catClientInfoString(sds s, redisClient *client) {
|
||||
char flags[16], events[3], *p;
|
||||
int emask;
|
||||
|
||||
getClientPeerId(client,peerid,sizeof(peerid));
|
||||
p = flags;
|
||||
if (client->flags & REDIS_SLAVE) {
|
||||
if (client->flags & REDIS_MONITOR)
|
||||
@@ -1275,23 +1301,23 @@ sds getClientInfoString(redisClient *client) {
|
||||
if (emask & AE_READABLE) *p++ = 'r';
|
||||
if (emask & AE_WRITABLE) *p++ = 'w';
|
||||
*p = '\0';
|
||||
return sdscatprintf(sdsempty(),
|
||||
"addr=%s fd=%d name=%s age=%ld idle=%ld flags=%s db=%d sub=%d psub=%d multi=%d qbuf=%lu qbuf-free=%lu obl=%lu oll=%lu omem=%lu events=%s cmd=%s",
|
||||
peerid,
|
||||
return sdscatfmt(s,
|
||||
"addr=%s fd=%i name=%s age=%I idle=%I flags=%s db=%i sub=%i psub=%i multi=%i qbuf=%U qbuf-free=%U obl=%U oll=%U omem=%U events=%s cmd=%s",
|
||||
getClientPeerId(client),
|
||||
client->fd,
|
||||
client->name ? (char*)client->name->ptr : "",
|
||||
(long)(server.unixtime - client->ctime),
|
||||
(long)(server.unixtime - client->lastinteraction),
|
||||
(long long)(server.unixtime - client->ctime),
|
||||
(long long)(server.unixtime - client->lastinteraction),
|
||||
flags,
|
||||
client->db->id,
|
||||
(int) dictSize(client->pubsub_channels),
|
||||
(int) listLength(client->pubsub_patterns),
|
||||
(client->flags & REDIS_MULTI) ? client->mstate.count : -1,
|
||||
(unsigned long) sdslen(client->querybuf),
|
||||
(unsigned long) sdsavail(client->querybuf),
|
||||
(unsigned long) client->bufpos,
|
||||
(unsigned long) listLength(client->reply),
|
||||
getClientOutputBufferMemoryUsage(client),
|
||||
(unsigned long long) sdslen(client->querybuf),
|
||||
(unsigned long long) sdsavail(client->querybuf),
|
||||
(unsigned long long) client->bufpos,
|
||||
(unsigned long long) listLength(client->reply),
|
||||
(unsigned long long) getClientOutputBufferMemoryUsage(client),
|
||||
events,
|
||||
client->lastcmd ? client->lastcmd->name : "NULL");
|
||||
}
|
||||
@@ -1302,14 +1328,11 @@ sds getAllClientsInfoString(void) {
|
||||
redisClient *client;
|
||||
sds o = sdsempty();
|
||||
|
||||
o = sdsMakeRoomFor(o,200*listLength(server.clients));
|
||||
listRewind(server.clients,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
sds cs;
|
||||
|
||||
client = listNodeValue(ln);
|
||||
cs = getClientInfoString(client);
|
||||
o = sdscatsds(o,cs);
|
||||
sdsfree(cs);
|
||||
o = catClientInfoString(o,client);
|
||||
o = sdscatlen(o,"\n",1);
|
||||
}
|
||||
return o;
|
||||
@@ -1327,11 +1350,10 @@ void clientCommand(redisClient *c) {
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"kill") && c->argc == 3) {
|
||||
listRewind(server.clients,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
char peerid[REDIS_PEER_ID_LEN];
|
||||
char *peerid;
|
||||
|
||||
client = listNodeValue(ln);
|
||||
if (getClientPeerId(client,peerid,sizeof(peerid)) == REDIS_ERR)
|
||||
continue;
|
||||
peerid = getClientPeerId(client);
|
||||
if (strcmp(peerid,c->argv[2]->ptr) == 0) {
|
||||
addReply(c,shared.ok);
|
||||
if (c == client) {
|
||||
@@ -1537,7 +1559,7 @@ void asyncCloseClientOnOutputBufferLimitReached(redisClient *c) {
|
||||
redisAssert(c->reply_bytes < ULONG_MAX-(1024*64));
|
||||
if (c->reply_bytes == 0 || c->flags & REDIS_CLOSE_ASAP) return;
|
||||
if (checkClientOutputBufferLimits(c)) {
|
||||
sds client = getClientInfoString(c);
|
||||
sds client = catClientInfoString(sdsempty(),c);
|
||||
|
||||
freeClientAsync(c);
|
||||
redisLog(REDIS_WARNING,"Client %s scheduled to be closed ASAP for overcoming of output buffer limits.", client);
|
||||
@@ -1611,3 +1633,26 @@ int clientsArePaused(void) {
|
||||
}
|
||||
return server.clients_paused;
|
||||
}
|
||||
|
||||
/* This function is called by Redis in order to process a few events from
|
||||
* time to time while blocked into some not interruptible operation.
|
||||
* This allows to reply to clients with the -LOADING error while loading the
|
||||
* data set at startup or after a full resynchronization with the master
|
||||
* and so forth.
|
||||
*
|
||||
* It calls the event loop in order to process a few events. Specifically we
|
||||
* try to call the event loop for times as long as we receive acknowledge that
|
||||
* some event was processed, in order to go forward with the accept, read,
|
||||
* write, close sequence needed to serve a client.
|
||||
*
|
||||
* The function returns the total number of events processed. */
|
||||
int processEventsWhileBlocked(void) {
|
||||
int iterations = 4; /* See the function top-comment. */
|
||||
int count = 0;
|
||||
while (iterations--) {
|
||||
int events = aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
|
||||
if (!events) break;
|
||||
count += events;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
+70
-54
@@ -40,7 +40,7 @@ robj *createObject(int type, void *ptr) {
|
||||
o->refcount = 1;
|
||||
|
||||
/* Set the LRU to the current lruclock (minutes resolution). */
|
||||
o->lru = server.lruclock;
|
||||
o->lru = LRU_CLOCK();
|
||||
return o;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ robj *createEmbeddedStringObject(char *ptr, size_t len) {
|
||||
o->encoding = REDIS_ENCODING_EMBSTR;
|
||||
o->ptr = sh+1;
|
||||
o->refcount = 1;
|
||||
o->lru = server.lruclock;
|
||||
o->lru = LRU_CLOCK();
|
||||
|
||||
sh->len = len;
|
||||
sh->free = 0;
|
||||
@@ -76,8 +76,11 @@ robj *createEmbeddedStringObject(char *ptr, size_t len) {
|
||||
|
||||
/* Create a string object with EMBSTR encoding if it is smaller than
|
||||
* REIDS_ENCODING_EMBSTR_SIZE_LIMIT, otherwise the RAW encoding is
|
||||
* used. */
|
||||
#define REDIS_ENCODING_EMBSTR_SIZE_LIMIT 32
|
||||
* used.
|
||||
*
|
||||
* The current limit of 39 is chosen so that the biggest string object
|
||||
* we allocate as EMBSTR will still fit into the 64 byte arena of jemalloc. */
|
||||
#define REDIS_ENCODING_EMBSTR_SIZE_LIMIT 39
|
||||
robj *createStringObject(char *ptr, size_t len) {
|
||||
if (len <= REDIS_ENCODING_EMBSTR_SIZE_LIMIT)
|
||||
return createEmbeddedStringObject(ptr,len);
|
||||
@@ -341,64 +344,76 @@ robj *tryObjectEncoding(robj *o) {
|
||||
sds s = o->ptr;
|
||||
size_t len;
|
||||
|
||||
if (o->encoding == REDIS_ENCODING_INT)
|
||||
return o; /* Already encoded */
|
||||
/* Make sure this is a string object, the only type we encode
|
||||
* in this function. Other types use encoded memory efficient
|
||||
* representations but are handled by the commands implementing
|
||||
* the type. */
|
||||
redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
|
||||
|
||||
/* We try some specialized encoding only for objects that are
|
||||
* RAW or EMBSTR encoded, in other words objects that are still
|
||||
* in represented by an actually array of chars. */
|
||||
if (!sdsEncodedObject(o)) return o;
|
||||
|
||||
/* It's not safe to encode shared objects: shared objects can be shared
|
||||
* everywhere in the "object space" of Redis. Encoded objects can only
|
||||
* appear as "values" (and not, for instance, as keys) */
|
||||
* everywhere in the "object space" of Redis and may end in places where
|
||||
* they are not handled. We handle them only as values in the keyspace. */
|
||||
if (o->refcount > 1) return o;
|
||||
|
||||
/* Currently we try to encode only strings */
|
||||
redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
|
||||
|
||||
/* Check if we can represent this string as a long integer.
|
||||
* Note that we are sure that a string larger than 21 chars is not
|
||||
* representable as a 64 bit integer. */
|
||||
* representable as a 32 nor 64 bit integer. */
|
||||
len = sdslen(s);
|
||||
if (len > 21 || !string2l(s,len,&value)) {
|
||||
/* Integer encoding not possible. Check if we can use EMBSTR. */
|
||||
if (sdslen(s) <= REDIS_ENCODING_EMBSTR_SIZE_LIMIT) {
|
||||
robj *emb = createEmbeddedStringObject(s,sdslen(s));
|
||||
if (len <= 21 && string2l(s,len,&value)) {
|
||||
/* This object is encodable as a long. Try to use a shared object.
|
||||
* Note that we avoid using shared integers when maxmemory is used
|
||||
* because every object needs to have a private LRU field for the LRU
|
||||
* algorithm to work well. */
|
||||
if (server.maxmemory == 0 &&
|
||||
value >= 0 &&
|
||||
value < REDIS_SHARED_INTEGERS)
|
||||
{
|
||||
decrRefCount(o);
|
||||
return emb;
|
||||
incrRefCount(shared.integers[value]);
|
||||
return shared.integers[value];
|
||||
} else {
|
||||
/* We can't encode the object...
|
||||
*
|
||||
* Do the last try, and at least optimize the SDS string inside
|
||||
* the string object to require little space, in case there
|
||||
* is more than 10% of free space at the end of the SDS string.
|
||||
*
|
||||
* We do that only for relatively large strings as this branch
|
||||
* is only entered if the length of the string is greater than
|
||||
* REDIS_ENCODING_EMBSTR_SIZE_LIMIT. */
|
||||
if (o->encoding == REDIS_ENCODING_RAW &&
|
||||
sdsavail(s) > len/10)
|
||||
{
|
||||
o->ptr = sdsRemoveFreeSpace(o->ptr);
|
||||
}
|
||||
/* Return the original object. */
|
||||
if (o->encoding == REDIS_ENCODING_RAW) sdsfree(o->ptr);
|
||||
o->encoding = REDIS_ENCODING_INT;
|
||||
o->ptr = (void*) value;
|
||||
return o;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ok, this object can be encoded...
|
||||
*
|
||||
* Can I use a shared object? Only if the object is inside a given range
|
||||
*
|
||||
* Note that we also avoid using shared integers when maxmemory is used
|
||||
* because every object needs to have a private LRU field for the LRU
|
||||
* algorithm to work well. */
|
||||
if (server.maxmemory == 0 && value >= 0 && value < REDIS_SHARED_INTEGERS) {
|
||||
/* If the string is small and is still RAW encoded,
|
||||
* try the EMBSTR encoding which is more efficient.
|
||||
* In this representation the object and the SDS string are allocated
|
||||
* in the same chunk of memory to save space and cache misses. */
|
||||
if (len <= REDIS_ENCODING_EMBSTR_SIZE_LIMIT) {
|
||||
robj *emb;
|
||||
|
||||
if (o->encoding == REDIS_ENCODING_EMBSTR) return o;
|
||||
emb = createEmbeddedStringObject(s,sdslen(s));
|
||||
decrRefCount(o);
|
||||
incrRefCount(shared.integers[value]);
|
||||
return shared.integers[value];
|
||||
} else {
|
||||
if (o->encoding == REDIS_ENCODING_RAW) sdsfree(o->ptr);
|
||||
o->encoding = REDIS_ENCODING_INT;
|
||||
o->ptr = (void*) value;
|
||||
return o;
|
||||
return emb;
|
||||
}
|
||||
|
||||
/* We can't encode the object...
|
||||
*
|
||||
* Do the last try, and at least optimize the SDS string inside
|
||||
* the string object to require little space, in case there
|
||||
* is more than 10% of free space at the end of the SDS string.
|
||||
*
|
||||
* We do that only for relatively large strings as this branch
|
||||
* is only entered if the length of the string is greater than
|
||||
* REDIS_ENCODING_EMBSTR_SIZE_LIMIT. */
|
||||
if (o->encoding == REDIS_ENCODING_RAW &&
|
||||
sdsavail(s) > len/10)
|
||||
{
|
||||
o->ptr = sdsRemoveFreeSpace(o->ptr);
|
||||
}
|
||||
|
||||
/* Return the original object. */
|
||||
return o;
|
||||
}
|
||||
|
||||
/* Get a decoded version of an encoded object (returned as a new object).
|
||||
@@ -648,18 +663,19 @@ char *strEncoding(int encoding) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Given an object returns the min number of seconds the object was never
|
||||
/* Given an object returns the min number of milliseconds the object was never
|
||||
* requested, using an approximated LRU algorithm. */
|
||||
unsigned long estimateObjectIdleTime(robj *o) {
|
||||
if (server.lruclock >= o->lru) {
|
||||
return (server.lruclock - o->lru) * REDIS_LRU_CLOCK_RESOLUTION;
|
||||
unsigned long long estimateObjectIdleTime(robj *o) {
|
||||
unsigned long long lruclock = LRU_CLOCK();
|
||||
if (lruclock >= o->lru) {
|
||||
return (lruclock - o->lru) * REDIS_LRU_CLOCK_RESOLUTION;
|
||||
} else {
|
||||
return ((REDIS_LRU_CLOCK_MAX - o->lru) + server.lruclock) *
|
||||
return (lruclock + (REDIS_LRU_CLOCK_MAX - o->lru)) *
|
||||
REDIS_LRU_CLOCK_RESOLUTION;
|
||||
}
|
||||
}
|
||||
|
||||
/* This is a helper function for the DEBUG command. We need to lookup keys
|
||||
/* This is a helper function for the OBJECT command. We need to lookup keys
|
||||
* without any modification of LRU or other parameters. */
|
||||
robj *objectCommandLookup(redisClient *c, robj *key) {
|
||||
dictEntry *de;
|
||||
@@ -691,7 +707,7 @@ void objectCommand(redisClient *c) {
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"idletime") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
||||
== NULL) return;
|
||||
addReplyLongLong(c,estimateObjectIdleTime(o));
|
||||
addReplyLongLong(c,estimateObjectIdleTime(o)/1000);
|
||||
} else {
|
||||
addReplyError(c,"Syntax error. Try OBJECT (refcount|encoding|idletime)");
|
||||
}
|
||||
|
||||
+7
-5
@@ -50,7 +50,7 @@ int listMatchPubsubPattern(void *a, void *b) {
|
||||
/* Subscribe a client to a channel. Returns 1 if the operation succeeded, or
|
||||
* 0 if the client was already subscribed to that channel. */
|
||||
int pubsubSubscribeChannel(redisClient *c, robj *channel) {
|
||||
struct dictEntry *de;
|
||||
dictEntry *de;
|
||||
list *clients = NULL;
|
||||
int retval = 0;
|
||||
|
||||
@@ -80,7 +80,7 @@ int pubsubSubscribeChannel(redisClient *c, robj *channel) {
|
||||
/* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or
|
||||
* 0 if the client was not subscribed to the specified channel. */
|
||||
int pubsubUnsubscribeChannel(redisClient *c, robj *channel, int notify) {
|
||||
struct dictEntry *de;
|
||||
dictEntry *de;
|
||||
list *clients;
|
||||
listNode *ln;
|
||||
int retval = 0;
|
||||
@@ -218,7 +218,7 @@ int pubsubUnsubscribeAllPatterns(redisClient *c, int notify) {
|
||||
/* Publish a message */
|
||||
int pubsubPublishMessage(robj *channel, robj *message) {
|
||||
int receivers = 0;
|
||||
struct dictEntry *de;
|
||||
dictEntry *de;
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
|
||||
@@ -306,8 +306,10 @@ void punsubscribeCommand(redisClient *c) {
|
||||
|
||||
void publishCommand(redisClient *c) {
|
||||
int receivers = pubsubPublishMessage(c->argv[1],c->argv[2]);
|
||||
if (server.cluster_enabled) clusterPropagatePublish(c->argv[1],c->argv[2]);
|
||||
forceCommandPropagation(c,REDIS_PROPAGATE_REPL);
|
||||
if (server.cluster_enabled)
|
||||
clusterPropagatePublish(c->argv[1],c->argv[2]);
|
||||
else
|
||||
forceCommandPropagation(c,REDIS_PROPAGATE_REPL);
|
||||
addReplyLongLong(c,receivers);
|
||||
}
|
||||
|
||||
|
||||
@@ -398,7 +398,7 @@ int rdbSaveDoubleValue(rio *rdb, double val) {
|
||||
double min = -4503599627370495; /* (2^52)-1 */
|
||||
double max = 4503599627370496; /* -(2^52) */
|
||||
if (val > min && val < max && val == ((double)((long long)val)))
|
||||
ll2string((char*)buf+1,sizeof(buf),(long long)val);
|
||||
ll2string((char*)buf+1,sizeof(buf)-1,(long long)val);
|
||||
else
|
||||
#endif
|
||||
snprintf((char*)buf+1,sizeof(buf)-1,"%.17g",val);
|
||||
@@ -410,7 +410,7 @@ int rdbSaveDoubleValue(rio *rdb, double val) {
|
||||
|
||||
/* For information about double serialization check rdbSaveDoubleValue() */
|
||||
int rdbLoadDoubleValue(rio *rdb, double *val) {
|
||||
char buf[128];
|
||||
char buf[256];
|
||||
unsigned char len;
|
||||
|
||||
if (rioRead(rdb,&len,1) == 0) return -1;
|
||||
@@ -690,9 +690,9 @@ int rdbSave(char *filename) {
|
||||
rioWrite(&rdb,&cksum,8);
|
||||
|
||||
/* Make sure data will not remain on the OS's output buffers */
|
||||
fflush(fp);
|
||||
fsync(fileno(fp));
|
||||
fclose(fp);
|
||||
if (fflush(fp) == EOF) goto werr;
|
||||
if (fsync(fileno(fp)) == -1) goto werr;
|
||||
if (fclose(fp) == EOF) goto werr;
|
||||
|
||||
/* Use RENAME to make sure the DB file is changed atomically only
|
||||
* if the generate DB file is ok. */
|
||||
@@ -1065,10 +1065,14 @@ void rdbLoadProgressCallback(rio *r, const void *buf, size_t len) {
|
||||
if (server.loading_process_events_interval_bytes &&
|
||||
(r->processed_bytes + len)/server.loading_process_events_interval_bytes > r->processed_bytes/server.loading_process_events_interval_bytes)
|
||||
{
|
||||
/* The DB can take some non trivial amount of time to load. Update
|
||||
* our cached time since it is used to create and update the last
|
||||
* interaction time with clients and for other important things. */
|
||||
updateCachedTime();
|
||||
if (server.masterhost && server.repl_state == REDIS_REPL_TRANSFER)
|
||||
replicationSendNewlineToMaster();
|
||||
loadingProgress(r->processed_bytes);
|
||||
aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
|
||||
processEventsWhileBlocked();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+386
-72
@@ -58,7 +58,7 @@
|
||||
#define OUTPUT_RAW 1
|
||||
#define OUTPUT_CSV 2
|
||||
#define REDIS_CLI_KEEPALIVE_INTERVAL 15 /* seconds */
|
||||
#define REDIS_DEFAULT_PIPE_TIMEOUT 30 /* seconds */
|
||||
#define REDIS_CLI_DEFAULT_PIPE_TIMEOUT 30 /* seconds */
|
||||
|
||||
static redisContext *context;
|
||||
static struct config {
|
||||
@@ -82,6 +82,8 @@ static struct config {
|
||||
int getrdb_mode;
|
||||
int stat_mode;
|
||||
int scan_mode;
|
||||
int intrinsic_latency_mode;
|
||||
int intrinsic_latency_duration;
|
||||
char *pattern;
|
||||
char *rdb_filename;
|
||||
int bigkeys;
|
||||
@@ -94,6 +96,7 @@ static struct config {
|
||||
} config;
|
||||
|
||||
static void usage();
|
||||
static void slaveMode(void);
|
||||
char *redisGitSHA1(void);
|
||||
char *redisGitDirty(void);
|
||||
|
||||
@@ -101,14 +104,18 @@ char *redisGitDirty(void);
|
||||
* Utility functions
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
static long long mstime(void) {
|
||||
static long long ustime(void) {
|
||||
struct timeval tv;
|
||||
long long mst;
|
||||
long long ust;
|
||||
|
||||
gettimeofday(&tv, NULL);
|
||||
mst = ((long long)tv.tv_sec)*1000;
|
||||
mst += tv.tv_usec/1000;
|
||||
return mst;
|
||||
ust = ((long long)tv.tv_sec)*1000000;
|
||||
ust += tv.tv_usec;
|
||||
return ust;
|
||||
}
|
||||
|
||||
static long long mstime(void) {
|
||||
return ustime()/1000;
|
||||
}
|
||||
|
||||
static void cliRefreshPrompt(void) {
|
||||
@@ -600,6 +607,8 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
|
||||
if (!strcasecmp(command,"monitor")) config.monitor_mode = 1;
|
||||
if (!strcasecmp(command,"subscribe") ||
|
||||
!strcasecmp(command,"psubscribe")) config.pubsub_mode = 1;
|
||||
if (!strcasecmp(command,"sync") ||
|
||||
!strcasecmp(command,"psync")) config.slave_mode = 1;
|
||||
|
||||
/* Setup argument length */
|
||||
argvlen = malloc(argc*sizeof(size_t));
|
||||
@@ -621,6 +630,13 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
|
||||
}
|
||||
}
|
||||
|
||||
if (config.slave_mode) {
|
||||
printf("Entering slave output mode... (press Ctrl-C to quit)\n");
|
||||
slaveMode();
|
||||
config.slave_mode = 0;
|
||||
return REDIS_ERR; /* Error = slaveMode lost connection to master */
|
||||
}
|
||||
|
||||
if (cliReadReply(output_raw) != REDIS_OK) {
|
||||
free(argvlen);
|
||||
return REDIS_ERR;
|
||||
@@ -716,8 +732,11 @@ static int parseOptions(int argc, char **argv) {
|
||||
config.stat_mode = 1;
|
||||
} else if (!strcmp(argv[i],"--scan")) {
|
||||
config.scan_mode = 1;
|
||||
} else if (!strcmp(argv[i],"--pattern")) {
|
||||
} else if (!strcmp(argv[i],"--pattern") && !lastarg) {
|
||||
config.pattern = argv[++i];
|
||||
} else if (!strcmp(argv[i],"--intrinsic-latency") && !lastarg) {
|
||||
config.intrinsic_latency_mode = 1;
|
||||
config.intrinsic_latency_duration = atoi(argv[++i]);
|
||||
} else if (!strcmp(argv[i],"--rdb") && !lastarg) {
|
||||
config.getrdb_mode = 1;
|
||||
config.rdb_filename = argv[++i];
|
||||
@@ -803,6 +822,8 @@ static void usage() {
|
||||
" --bigkeys Sample Redis keys looking for big keys.\n"
|
||||
" --scan List all keys using the SCAN command.\n"
|
||||
" --pattern <pat> Useful with --scan to specify a SCAN pattern.\n"
|
||||
" --intrinsic-latency <sec> Run a test to measure intrinsic system latency.\n"
|
||||
" The test will run for the specified amount of seconds.\n"
|
||||
" --eval <file> Send an EVAL command using the Lua script at <file>.\n"
|
||||
" --help Output this help and exit.\n"
|
||||
" --version Output version and exit.\n"
|
||||
@@ -820,7 +841,7 @@ static void usage() {
|
||||
"When no command is given, redis-cli starts in interactive mode.\n"
|
||||
"Type \"help\" in interactive mode for information on available commands.\n"
|
||||
"\n",
|
||||
version, REDIS_DEFAULT_PIPE_TIMEOUT);
|
||||
version, REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
|
||||
sdsfree(version);
|
||||
exit(1);
|
||||
}
|
||||
@@ -845,6 +866,7 @@ static void repl() {
|
||||
sds *argv;
|
||||
|
||||
config.interactive = 1;
|
||||
linenoiseSetMultiLine(1);
|
||||
linenoiseSetCompletionCallback(completionCallback);
|
||||
|
||||
/* Only use history when stdin is a tty. */
|
||||
@@ -940,6 +962,10 @@ static int noninteractive(int argc, char **argv) {
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Eval mode
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
static int evalMode(int argc, char **argv) {
|
||||
sds script = sdsempty();
|
||||
FILE *fp;
|
||||
@@ -978,6 +1004,10 @@ static int evalMode(int argc, char **argv) {
|
||||
return cliSendCommand(argc+3-got_comma, argv2, config.repeat);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Latency and latency history modes
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
#define LATENCY_SAMPLE_RATE 10 /* milliseconds. */
|
||||
#define LATENCY_HISTORY_DEFAULT_INTERVAL 15000 /* milliseconds. */
|
||||
static void latencyMode(void) {
|
||||
@@ -1022,6 +1052,10 @@ static void latencyMode(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Slave mode
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
/* Sends SYNC and reads the number of bytes in the payload. Used both by
|
||||
* slaveMode() and getRDB(). */
|
||||
unsigned long long sendSync(int fd) {
|
||||
@@ -1061,6 +1095,7 @@ static void slaveMode(void) {
|
||||
int fd = context->fd;
|
||||
unsigned long long payload = sendSync(fd);
|
||||
char buf[1024];
|
||||
int original_output = config.output;
|
||||
|
||||
fprintf(stderr,"SYNC with master, discarding %llu "
|
||||
"bytes of bulk transfer...\n", payload);
|
||||
@@ -1081,8 +1116,13 @@ static void slaveMode(void) {
|
||||
/* Now we can use hiredis to read the incoming protocol. */
|
||||
config.output = OUTPUT_CSV;
|
||||
while (cliReadReply(0) == REDIS_OK);
|
||||
config.output = original_output;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* RDB transfer mode
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
/* This function implements --rdb, so it uses the replication protocol in order
|
||||
* to fetch the RDB file from a remote server. */
|
||||
static void getRDB(void) {
|
||||
@@ -1128,6 +1168,10 @@ static void getRDB(void) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Bulk import (pipe) mode
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
static void pipeMode(void) {
|
||||
int fd = context->fd;
|
||||
long long errors = 0, replies = 0, obuf_len = 0, obuf_pos = 0;
|
||||
@@ -1279,92 +1323,287 @@ static void pipeMode(void) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Find big keys
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
#define TYPE_STRING 0
|
||||
#define TYPE_LIST 1
|
||||
#define TYPE_SET 2
|
||||
#define TYPE_HASH 3
|
||||
#define TYPE_ZSET 4
|
||||
#define TYPE_NONE 5
|
||||
|
||||
static redisReply *sendScan(unsigned long long *it) {
|
||||
redisReply *reply = redisCommand(context, "SCAN %llu", *it);
|
||||
|
||||
/* Handle any error conditions */
|
||||
if(reply == NULL) {
|
||||
fprintf(stderr, "\nI/O error\n");
|
||||
exit(1);
|
||||
} else if(reply->type == REDIS_REPLY_ERROR) {
|
||||
fprintf(stderr, "SCAN error: %s\n", reply->str);
|
||||
exit(1);
|
||||
} else if(reply->type != REDIS_REPLY_ARRAY) {
|
||||
fprintf(stderr, "Non ARRAY response from SCAN!\n");
|
||||
exit(1);
|
||||
} else if(reply->elements != 2) {
|
||||
fprintf(stderr, "Invalid element count from SCAN!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Validate our types are correct */
|
||||
assert(reply->element[0]->type == REDIS_REPLY_STRING);
|
||||
assert(reply->element[1]->type == REDIS_REPLY_ARRAY);
|
||||
|
||||
/* Update iterator */
|
||||
*it = atoi(reply->element[0]->str);
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
static int getDbSize(void) {
|
||||
redisReply *reply;
|
||||
int size;
|
||||
|
||||
reply = redisCommand(context, "DBSIZE");
|
||||
|
||||
if(reply == NULL || reply->type != REDIS_REPLY_INTEGER) {
|
||||
fprintf(stderr, "Couldn't determine DBSIZE!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Grab the number of keys and free our reply */
|
||||
size = reply->integer;
|
||||
freeReplyObject(reply);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static int toIntType(char *key, char *type) {
|
||||
if(!strcmp(type, "string")) {
|
||||
return TYPE_STRING;
|
||||
} else if(!strcmp(type, "list")) {
|
||||
return TYPE_LIST;
|
||||
} else if(!strcmp(type, "set")) {
|
||||
return TYPE_SET;
|
||||
} else if(!strcmp(type, "hash")) {
|
||||
return TYPE_HASH;
|
||||
} else if(!strcmp(type, "zset")) {
|
||||
return TYPE_ZSET;
|
||||
} else if(!strcmp(type, "none")) {
|
||||
return TYPE_NONE;
|
||||
} else {
|
||||
fprintf(stderr, "Unknown type '%s' for key '%s'\n", type, key);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void getKeyTypes(redisReply *keys, int *types) {
|
||||
redisReply *reply;
|
||||
int i;
|
||||
|
||||
/* Pipeline TYPE commands */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
redisAppendCommand(context, "TYPE %s", keys->element[i]->str);
|
||||
}
|
||||
|
||||
/* Retrieve types */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
if(redisGetReply(context, (void**)&reply)!=REDIS_OK) {
|
||||
fprintf(stderr, "Error getting type for key '%s' (%d: %s)\n",
|
||||
keys->element[i]->str, context->err, context->errstr);
|
||||
exit(1);
|
||||
} else if(reply->type != REDIS_REPLY_STATUS) {
|
||||
fprintf(stderr, "Invalid reply type (%d) for TYPE on key '%s'!\n",
|
||||
reply->type, keys->element[i]->str);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
types[i] = toIntType(keys->element[i]->str, reply->str);
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
}
|
||||
|
||||
static void getKeySizes(redisReply *keys, int *types,
|
||||
unsigned long long *sizes)
|
||||
{
|
||||
redisReply *reply;
|
||||
char *sizecmds[] = {"STRLEN","LLEN","SCARD","HLEN","ZCARD"};
|
||||
int i;
|
||||
|
||||
/* Pipeline size commands */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
/* Skip keys that were deleted */
|
||||
if(types[i]==TYPE_NONE)
|
||||
continue;
|
||||
|
||||
redisAppendCommand(context, "%s %s", sizecmds[types[i]],
|
||||
keys->element[i]->str);
|
||||
}
|
||||
|
||||
/* Retreive sizes */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
/* Skip keys that dissapeared between SCAN and TYPE */
|
||||
if(types[i] == TYPE_NONE) {
|
||||
sizes[i] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Retreive size */
|
||||
if(redisGetReply(context, (void**)&reply)!=REDIS_OK) {
|
||||
fprintf(stderr, "Error getting size for key '%s' (%d: %s)\n",
|
||||
keys->element[i]->str, context->err, context->errstr);
|
||||
exit(1);
|
||||
} else if(reply->type != REDIS_REPLY_INTEGER) {
|
||||
/* Theoretically the key could have been removed and
|
||||
* added as a different type between TYPE and SIZE */
|
||||
fprintf(stderr,
|
||||
"Warning: %s on '%s' failed (may have changed type)\n",
|
||||
sizecmds[types[i]], keys->element[i]->str);
|
||||
sizes[i] = 0;
|
||||
} else {
|
||||
sizes[i] = reply->integer;
|
||||
}
|
||||
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
}
|
||||
|
||||
static void findBigKeys(void) {
|
||||
unsigned long long biggest[5] = {0,0,0,0,0};
|
||||
unsigned long long samples = 0;
|
||||
redisReply *reply1, *reply2, *reply3 = NULL;
|
||||
char *sizecmd, *typename[] = {"string","list","set","hash","zset"};
|
||||
unsigned long long biggest[5] = {0}, counts[5] = {0}, totalsize[5] = {0};
|
||||
unsigned long long sampled = 0, total_keys, totlen=0, *sizes=NULL, it=0;
|
||||
sds maxkeys[5] = {0};
|
||||
char *typename[] = {"string","list","set","hash","zset"};
|
||||
char *typeunit[] = {"bytes","items","members","fields","members"};
|
||||
int type;
|
||||
redisReply *reply, *keys;
|
||||
int type, *types=NULL, arrsize=0, i;
|
||||
double pct;
|
||||
|
||||
printf("\n# Press ctrl+c when you have had enough of it... :)\n");
|
||||
printf("# You can use -i 0.1 to sleep 0.1 sec every 100 sampled keys\n");
|
||||
printf("# in order to reduce server load (usually not needed).\n\n");
|
||||
while(1) {
|
||||
/* Sample with RANDOMKEY */
|
||||
reply1 = redisCommand(context,"RANDOMKEY");
|
||||
if (reply1 == NULL) {
|
||||
fprintf(stderr,"\nI/O error\n");
|
||||
exit(1);
|
||||
} else if (reply1->type == REDIS_REPLY_ERROR) {
|
||||
fprintf(stderr, "RANDOMKEY error: %s\n",
|
||||
reply1->str);
|
||||
exit(1);
|
||||
} else if (reply1->type == REDIS_REPLY_NIL) {
|
||||
fprintf(stderr, "It looks like the database is empty!\n");
|
||||
/* Total keys pre scanning */
|
||||
total_keys = getDbSize();
|
||||
|
||||
/* Status message */
|
||||
printf("\n# Scanning the entire keyspace to find biggest keys as well as\n");
|
||||
printf("# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec\n");
|
||||
printf("# per 100 SCAN commands (not usually needed).\n\n");
|
||||
|
||||
/* New up sds strings to keep track of overall biggest per type */
|
||||
for(i=0;i<TYPE_NONE; i++) {
|
||||
maxkeys[i] = sdsempty();
|
||||
if(!maxkeys[i]) {
|
||||
fprintf(stderr, "Failed to allocate memory for largest key names!");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the key type */
|
||||
reply2 = redisCommand(context,"TYPE %s",reply1->str);
|
||||
assert(reply2 && reply2->type == REDIS_REPLY_STATUS);
|
||||
samples++;
|
||||
/* SCAN loop */
|
||||
do {
|
||||
/* Calculate approximate percentage completion */
|
||||
pct = 100 * (double)sampled/total_keys;
|
||||
|
||||
/* Get the key "size" */
|
||||
if (!strcmp(reply2->str,"string")) {
|
||||
sizecmd = "STRLEN";
|
||||
type = TYPE_STRING;
|
||||
} else if (!strcmp(reply2->str,"list")) {
|
||||
sizecmd = "LLEN";
|
||||
type = TYPE_LIST;
|
||||
} else if (!strcmp(reply2->str,"set")) {
|
||||
sizecmd = "SCARD";
|
||||
type = TYPE_SET;
|
||||
} else if (!strcmp(reply2->str,"hash")) {
|
||||
sizecmd = "HLEN";
|
||||
type = TYPE_HASH;
|
||||
} else if (!strcmp(reply2->str,"zset")) {
|
||||
sizecmd = "ZCARD";
|
||||
type = TYPE_ZSET;
|
||||
} else if (!strcmp(reply2->str,"none")) {
|
||||
freeReplyObject(reply1);
|
||||
freeReplyObject(reply2);
|
||||
continue;
|
||||
} else {
|
||||
fprintf(stderr, "Unknown key type '%s' for key '%s'\n",
|
||||
reply2->str, reply1->str);
|
||||
exit(1);
|
||||
/* Grab some keys and point to the keys array */
|
||||
reply = sendScan(&it);
|
||||
keys = reply->element[1];
|
||||
|
||||
/* Reallocate our type and size array if we need to */
|
||||
if(keys->elements > arrsize) {
|
||||
types = zrealloc(types, sizeof(int)*keys->elements);
|
||||
sizes = zrealloc(sizes, sizeof(unsigned long long)*keys->elements);
|
||||
|
||||
if(!types || !sizes) {
|
||||
fprintf(stderr, "Failed to allocate storage for keys!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
arrsize = keys->elements;
|
||||
}
|
||||
|
||||
reply3 = redisCommand(context,"%s %s", sizecmd, reply1->str);
|
||||
if (reply3 && reply3->type == REDIS_REPLY_INTEGER) {
|
||||
if (biggest[type] < reply3->integer) {
|
||||
printf("Biggest %-6s found so far '%s' with %llu %s.\n",
|
||||
typename[type], reply1->str,
|
||||
(unsigned long long) reply3->integer,
|
||||
typeunit[type]);
|
||||
biggest[type] = reply3->integer;
|
||||
/* Retreive types and then sizes */
|
||||
getKeyTypes(keys, types);
|
||||
getKeySizes(keys, types, sizes);
|
||||
|
||||
/* Now update our stats */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
if((type = types[i]) == TYPE_NONE)
|
||||
continue;
|
||||
|
||||
totalsize[type] += sizes[i];
|
||||
counts[type]++;
|
||||
totlen += keys->element[i]->len;
|
||||
sampled++;
|
||||
|
||||
if(biggest[type]<sizes[i]) {
|
||||
printf(
|
||||
"[%05.2f%%] Biggest %-6s found so far '%s' with %llu %s\n",
|
||||
pct, typename[type], keys->element[i]->str, sizes[i],
|
||||
typeunit[type]);
|
||||
|
||||
/* Keep track of biggest key name for this type */
|
||||
maxkeys[type] = sdscpy(maxkeys[type], keys->element[i]->str);
|
||||
if(!maxkeys[type]) {
|
||||
fprintf(stderr, "Failed to allocate memory for key!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Keep track of the biggest size for this type */
|
||||
biggest[type] = sizes[i];
|
||||
}
|
||||
|
||||
/* Update overall progress */
|
||||
if(sampled % 1000000 == 0) {
|
||||
printf("[%05.2f%%] Sampled %llu keys so far\n", pct, sampled);
|
||||
}
|
||||
}
|
||||
|
||||
if ((samples % 1000000) == 0)
|
||||
printf("(%llu keys sampled)\n", samples);
|
||||
|
||||
if ((samples % 100) == 0 && config.interval)
|
||||
/* Sleep if we've been directed to do so */
|
||||
if(sampled && (sampled %100) == 0 && config.interval) {
|
||||
usleep(config.interval);
|
||||
}
|
||||
|
||||
freeReplyObject(reply);
|
||||
} while(it != 0);
|
||||
|
||||
freeReplyObject(reply1);
|
||||
freeReplyObject(reply2);
|
||||
if (reply3) freeReplyObject(reply3);
|
||||
if(types) zfree(types);
|
||||
if(sizes) zfree(sizes);
|
||||
|
||||
/* We're done */
|
||||
printf("\n-------- summary -------\n\n");
|
||||
|
||||
printf("Sampled %llu keys in the keyspace!\n", sampled);
|
||||
printf("Total key length in bytes is %llu (avg len %.2f)\n\n",
|
||||
totlen, totlen ? (double)totlen/sampled : 0);
|
||||
|
||||
/* Output the biggest keys we found, for types we did find */
|
||||
for(i=0;i<TYPE_NONE;i++) {
|
||||
if(sdslen(maxkeys[i])>0) {
|
||||
printf("Biggest %6s found '%s' has %llu %s\n", typename[i], maxkeys[i],
|
||||
biggest[i], typeunit[i]);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
|
||||
for(i=0;i<TYPE_NONE;i++) {
|
||||
printf("%llu %ss with %llu %s (%05.2f%% of keys, avg size %.2f)\n",
|
||||
counts[i], typename[i], totalsize[i], typeunit[i],
|
||||
sampled ? 100 * (double)counts[i]/sampled : 0,
|
||||
counts[i] ? (double)totalsize[i]/counts[i] : 0);
|
||||
}
|
||||
|
||||
/* Free sds strings containing max keys */
|
||||
for(i=0;i<TYPE_NONE;i++) {
|
||||
sdsfree(maxkeys[i]);
|
||||
}
|
||||
|
||||
/* Success! */
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Stats mode
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
/* Return the specified INFO field from the INFO command output "info".
|
||||
* A new buffer is allocated for the result, that needs to be free'd.
|
||||
* If the field is not found NULL is returned. */
|
||||
@@ -1504,6 +1743,10 @@ static void statMode() {
|
||||
}
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Scan mode
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
static void scanMode() {
|
||||
redisReply *reply;
|
||||
unsigned long long cur = 0;
|
||||
@@ -1533,6 +1776,73 @@ static void scanMode() {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Intrisic latency mode.
|
||||
*
|
||||
* Measure max latency of a running process that does not result from
|
||||
* syscalls. Basically this software should provide an hint about how much
|
||||
* time the kernel leaves the process without a chance to run.
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
/* This is just some computation the compiler can't optimize out.
|
||||
* Should run in less than 100-200 microseconds even using very
|
||||
* slow hardware. Runs in less than 10 microseconds in modern HW. */
|
||||
unsigned long compute_something_fast(void) {
|
||||
unsigned char s[256], i, j, t;
|
||||
int count = 1000, k;
|
||||
unsigned long output = 0;
|
||||
|
||||
for (k = 0; k < 256; k++) s[k] = k;
|
||||
|
||||
i = 0;
|
||||
j = 0;
|
||||
while(count--) {
|
||||
i++;
|
||||
j = j + s[i];
|
||||
t = s[i];
|
||||
s[i] = s[j];
|
||||
s[j] = t;
|
||||
output += s[(s[i]+s[j])&255];
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
static void intrinsicLatencyMode(void) {
|
||||
long long test_end, run_time, max_latency = 0, runs = 0;
|
||||
|
||||
run_time = config.intrinsic_latency_duration*1000000;
|
||||
test_end = ustime() + run_time;
|
||||
|
||||
while(1) {
|
||||
long long start, end, latency;
|
||||
|
||||
start = ustime();
|
||||
compute_something_fast();
|
||||
end = ustime();
|
||||
latency = end-start;
|
||||
runs++;
|
||||
if (latency <= 0) continue;
|
||||
|
||||
/* Reporting */
|
||||
if (latency > max_latency) {
|
||||
max_latency = latency;
|
||||
printf("Max latency so far: %lld microseconds.\n", max_latency);
|
||||
}
|
||||
|
||||
if (end > test_end) {
|
||||
printf("\n%lld total runs (avg %lld microseconds per run).\n",
|
||||
runs, run_time/runs);
|
||||
printf("Worst run took %.02fx times the avarege.\n",
|
||||
(double) max_latency / (run_time/runs));
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Program main()
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int firstarg;
|
||||
|
||||
@@ -1553,10 +1863,11 @@ int main(int argc, char **argv) {
|
||||
config.getrdb_mode = 0;
|
||||
config.stat_mode = 0;
|
||||
config.scan_mode = 0;
|
||||
config.intrinsic_latency_mode = 0;
|
||||
config.pattern = NULL;
|
||||
config.rdb_filename = NULL;
|
||||
config.pipe_mode = 0;
|
||||
config.pipe_timeout = REDIS_DEFAULT_PIPE_TIMEOUT;
|
||||
config.pipe_timeout = REDIS_CLI_DEFAULT_PIPE_TIMEOUT;
|
||||
config.bigkeys = 0;
|
||||
config.stdinarg = 0;
|
||||
config.auth = NULL;
|
||||
@@ -1615,6 +1926,9 @@ int main(int argc, char **argv) {
|
||||
scanMode();
|
||||
}
|
||||
|
||||
/* Intrinsic latency mode */
|
||||
if (config.intrinsic_latency_mode) intrinsicLatencyMode();
|
||||
|
||||
/* Start interactive mode when no command is provided */
|
||||
if (argc == 0 && !config.eval) {
|
||||
/* Note that in repl mode we don't abort on connection error.
|
||||
|
||||
+303
-38
@@ -139,10 +139,10 @@ class ClusterNode
|
||||
if s[0..0] == '['
|
||||
if s.index("->-") # Migrating
|
||||
slot,dst = s[1..-1].split("->-")
|
||||
@info[:migrating][slot] = dst
|
||||
@info[:migrating][slot.to_i] = dst
|
||||
elsif s.index("-<-") # Importing
|
||||
slot,src = s[1..-1].split("-<-")
|
||||
@info[:importing][slot] = src
|
||||
@info[:importing][slot.to_i] = src
|
||||
end
|
||||
elsif s.index("-")
|
||||
start,stop = s.split("-")
|
||||
@@ -359,11 +359,11 @@ class RedisTrib
|
||||
@nodes.each{|n|
|
||||
if n.info[:migrating].size > 0
|
||||
cluster_error \
|
||||
"[WARNING] Node #{n} has slots in migrating state."
|
||||
"[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
|
||||
cluster_error \
|
||||
"[WARNING] Node #{n} has slots in importing state."
|
||||
"[WARNING] Node #{n} has slots in importing state (#{n.info[:importing].keys.join(",")})."
|
||||
open_slots += n.info[:importing].keys
|
||||
end
|
||||
}
|
||||
@@ -445,10 +445,35 @@ class RedisTrib
|
||||
end
|
||||
end
|
||||
|
||||
# Return the owner of the specified slot
|
||||
def get_slot_owner(slot)
|
||||
@nodes.each{|n|
|
||||
n.slots.each{|s,_|
|
||||
return n if s == slot
|
||||
}
|
||||
}
|
||||
nil
|
||||
end
|
||||
|
||||
# Slot 'slot' was found to be in importing or migrating state in one or
|
||||
# more nodes. This function fixes this condition by migrating keys where
|
||||
# it seems more sensible.
|
||||
def fix_open_slot(slot)
|
||||
puts ">>> Fixing open slot #{slot}"
|
||||
|
||||
# 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
|
||||
|
||||
migrating = []
|
||||
importing = []
|
||||
@nodes.each{|n|
|
||||
@@ -457,18 +482,26 @@ class RedisTrib
|
||||
migrating << n
|
||||
elsif n.info[:importing][slot]
|
||||
importing << n
|
||||
elsif n.r.cluster("countkeysinslot",slot) > 0
|
||||
elsif n.r.cluster("countkeysinslot",slot) > 0 && n != owner
|
||||
xputs "*** Found keys about slot #{slot} in node #{n}!"
|
||||
importing << n
|
||||
end
|
||||
}
|
||||
puts ">>> Fixing open slot #{slot}"
|
||||
puts "Set as migrating in: #{migrating.join(",")}"
|
||||
puts "Set as importing in: #{importing.join(",")}"
|
||||
|
||||
# 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)
|
||||
move_slot(migrating[0],importing[0],slot,:verbose=>true,:fix=>true)
|
||||
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)
|
||||
xputs ">>> Setting #{slot} as STABLE in #{node}"
|
||||
node.r.cluster("setslot",slot,"stable")
|
||||
}
|
||||
else
|
||||
xputs "[ERR] Sorry, Redis-trib can't fix this slot yet (work in progress)"
|
||||
end
|
||||
@@ -504,7 +537,6 @@ class RedisTrib
|
||||
def alloc_slots
|
||||
nodes_count = @nodes.length
|
||||
masters_count = @nodes.length / (@replicas+1)
|
||||
slots_per_node = ClusterHashSlots / masters_count
|
||||
masters = []
|
||||
slaves = []
|
||||
|
||||
@@ -535,34 +567,60 @@ class RedisTrib
|
||||
end
|
||||
|
||||
# Alloc slots on masters
|
||||
i = 0
|
||||
slots_per_node = ClusterHashSlots.to_f / masters_count
|
||||
first = 0
|
||||
cursor = 0.0
|
||||
masters.each_with_index{|n,masternum|
|
||||
first = i*slots_per_node
|
||||
last = first+slots_per_node-1
|
||||
last = ClusterHashSlots-1 if masternum == masters.length-1
|
||||
last = (cursor+slots_per_node-1).round
|
||||
if last > ClusterHashSlots || masternum == masters.length-1
|
||||
last = ClusterHashSlots-1
|
||||
end
|
||||
last = first if last < first # Min step is 1.
|
||||
n.add_slots first..last
|
||||
i += 1
|
||||
first = last+1
|
||||
cursor += slots_per_node
|
||||
}
|
||||
|
||||
# Select N replicas for every master.
|
||||
# We try to split the replicas among all the IPs with spare nodes
|
||||
# trying to avoid the host where the master is running, if possible.
|
||||
masters.each{|m|
|
||||
i = 0
|
||||
while i < @replicas
|
||||
ips.each{|ip,nodes_list|
|
||||
next if nodes_list.length == 0
|
||||
# Skip instances with the same IP as the master if we
|
||||
# have some more IPs available.
|
||||
next if ip == m.info[:host] && nodes_count > nodes_list.length
|
||||
slave = nodes_list.shift
|
||||
slave.set_as_replica(m.info[:name])
|
||||
nodes_count -= 1
|
||||
i += 1
|
||||
puts "#{m} replica ##{i} is #{slave}"
|
||||
break if masters.length == masters_count
|
||||
}
|
||||
end
|
||||
#
|
||||
# Note we loop two times. The first loop assigns the requested
|
||||
# number of replicas to each master. The second loop assigns any
|
||||
# remaining instances as extra replicas to masters. Some masters
|
||||
# may end up with more than their requested number of replicas, but
|
||||
# all nodes will be used.
|
||||
assignment_verbose = false
|
||||
|
||||
[:requested,:unused].each{|assign|
|
||||
masters.each{|m|
|
||||
assigned_replicas = 0
|
||||
while assigned_replicas < @replicas
|
||||
break if nodes_count == 0
|
||||
if assignment_verbose
|
||||
if assign == :requested
|
||||
puts "Requesting total of #{@replicas} replicas " \
|
||||
"(#{assigned_replicas} replicas assigned " \
|
||||
"so far with #{nodes_count} total remaining)."
|
||||
elsif assign == :unused
|
||||
puts "Assigning extra instance to replication " \
|
||||
"role too (#{nodes_count} remaining)."
|
||||
end
|
||||
end
|
||||
ips.each{|ip,nodes_list|
|
||||
next if nodes_list.length == 0
|
||||
# Skip instances with the same IP as the master if we
|
||||
# have some more IPs available.
|
||||
next if ip == m.info[:host] && nodes_count > nodes_list.length
|
||||
slave = nodes_list.shift
|
||||
slave.set_as_replica(m.info[:name])
|
||||
nodes_count -= 1
|
||||
assigned_replicas += 1
|
||||
puts "Adding replica #{slave} to #{m}"
|
||||
break
|
||||
}
|
||||
end
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
@@ -578,6 +636,22 @@ class RedisTrib
|
||||
}
|
||||
end
|
||||
|
||||
# Redis Cluster config epoch collision resolution code is able to eventually
|
||||
# set a different epoch to each node after a new cluster is created, but
|
||||
# it is slow compared to assign a progressive config epoch to each node
|
||||
# before joining the cluster. However we do just a best-effort try here
|
||||
# since if we fail is not a problem.
|
||||
def assign_config_epoch
|
||||
config_epoch = 1
|
||||
@nodes.each{|n|
|
||||
begin
|
||||
n.r.cluster("set-config-epoch",config_epoch)
|
||||
rescue
|
||||
end
|
||||
config_epoch += 1
|
||||
}
|
||||
end
|
||||
|
||||
def join_cluster
|
||||
# We use a brute force approach to make sure the node will meet
|
||||
# each other, that is, sending CLUSTER MEET messages to all the nodes
|
||||
@@ -680,29 +754,51 @@ class RedisTrib
|
||||
}
|
||||
end
|
||||
|
||||
# Move slots between source and target nodes using MIGRATE.
|
||||
#
|
||||
# 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.
|
||||
def move_slot(source,target,slot,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
|
||||
target.r.cluster("setslot",slot,"importing",source.info[:name])
|
||||
source.r.cluster("setslot",slot,"migrating",target.info[:name])
|
||||
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)
|
||||
break if keys.length == 0
|
||||
keys.each{|key|
|
||||
source.r.migrate(target.info[:host],target.info[:port],key,0,1000)
|
||||
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
|
||||
end
|
||||
print "." if o[:verbose]
|
||||
STDOUT.flush
|
||||
}
|
||||
end
|
||||
|
||||
puts
|
||||
# Set the new node as the owner of the slot in all the known nodes.
|
||||
@nodes.each{|n|
|
||||
n.r.cluster("setslot",slot,"node",target.info[:name])
|
||||
}
|
||||
if !o[:cold]
|
||||
@nodes.each{|n|
|
||||
n.r.cluster("setslot",slot,"node",target.info[:name])
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# redis-trib subcommands implementations
|
||||
@@ -819,6 +915,8 @@ class RedisTrib
|
||||
yes_or_die "Can I set the above configuration?"
|
||||
flush_nodes_config
|
||||
xputs ">>> Nodes configuration updated"
|
||||
xputs ">>> Assign a different config epoch to each node"
|
||||
assign_config_epoch
|
||||
xputs ">>> Sending CLUSTER MEET messages to join the cluster"
|
||||
join_cluster
|
||||
# Give one second for the join to start, in order to avoid that
|
||||
@@ -898,10 +996,10 @@ class RedisTrib
|
||||
xputs ">>> Sending CLUSTER FORGET messages to the cluster..."
|
||||
@nodes.each{|n|
|
||||
next if n == node
|
||||
if n.info[:replicate] && n.info[:replicate].downcase == node_id
|
||||
if n.info[:replicate] && n.info[:replicate].downcase == id
|
||||
# Reconfigure the slave to replicate with some other node
|
||||
xputs ">>> #{n} as replica of #{master}"
|
||||
master = get_master_with_least_replicas
|
||||
xputs ">>> #{n} as replica of #{master}"
|
||||
n.r.cluster("replicate",master.info[:name])
|
||||
end
|
||||
n.r.cluster("forget",argv[1])
|
||||
@@ -940,6 +1038,71 @@ class RedisTrib
|
||||
xputs ">>> New node timeout set. #{ok_count} OK, #{err_count} ERR."
|
||||
end
|
||||
|
||||
def call_cluster_cmd(argv,opt)
|
||||
cmd = argv[1..-1]
|
||||
cmd[0] = cmd[0].upcase
|
||||
|
||||
# Load cluster information
|
||||
load_cluster_info_from_node(argv[0])
|
||||
xputs ">>> Calling #{cmd.join(" ")}"
|
||||
@nodes.each{|n|
|
||||
begin
|
||||
res = n.r.send(*cmd)
|
||||
puts "#{n}: #{res}"
|
||||
rescue => e
|
||||
puts "#{n}: #{e}"
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def import_cluster_cmd(argv,opt)
|
||||
source_addr = opt['from']
|
||||
xputs ">>> Importing data from #{source_addr} to cluster #{argv[1]}"
|
||||
|
||||
# Check the existing cluster.
|
||||
load_cluster_info_from_node(argv[0])
|
||||
check_cluster
|
||||
|
||||
# Connect to the source node.
|
||||
xputs ">>> Connecting to the source Redis instance"
|
||||
src_host,src_port = source_addr.split(":")
|
||||
source = Redis.new(:host =>src_host, :port =>src_port)
|
||||
if source.info['cluster_enabled'].to_i == 1
|
||||
xputs "[ERR] The source node should not be a cluster node."
|
||||
end
|
||||
xputs "*** Importing #{source.dbsize} keys from DB 0"
|
||||
|
||||
# Build a slot -> node map
|
||||
slots = {}
|
||||
@nodes.each{|n|
|
||||
n.slots.each{|s,_|
|
||||
slots[s] = n
|
||||
}
|
||||
}
|
||||
|
||||
# Use SCAN to iterate over the keys, migrating to the
|
||||
# right node as needed.
|
||||
cursor = nil
|
||||
while cursor != 0
|
||||
cursor,keys = source.scan(cursor,:count,1000)
|
||||
cursor = cursor.to_i
|
||||
keys.each{|k|
|
||||
# Migrate keys using the MIGRATE command.
|
||||
slot = key_to_slot(k)
|
||||
target = slots[slot]
|
||||
print "Migrating #{k} to #{target}: "
|
||||
STDOUT.flush
|
||||
begin
|
||||
source.client.call(["migrate",target.info[:host],target.info[:port],k,0,15000])
|
||||
rescue => e
|
||||
puts e
|
||||
else
|
||||
puts "OK"
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def help_cluster_cmd(argv,opt)
|
||||
show_help
|
||||
exit 0
|
||||
@@ -971,10 +1134,109 @@ class RedisTrib
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
# Enforce mandatory options
|
||||
if ALLOWED_OPTIONS[cmd]
|
||||
ALLOWED_OPTIONS[cmd].each {|option,val|
|
||||
if !options[option] && val == :required
|
||||
puts "Option '--#{option}' is required "+ \
|
||||
"for subcommand '#{cmd}'"
|
||||
exit 1
|
||||
end
|
||||
}
|
||||
end
|
||||
return options,idx
|
||||
end
|
||||
end
|
||||
|
||||
#################################################################################
|
||||
# Libraries
|
||||
#
|
||||
# We try to don't depend on external libs since this is a critical part
|
||||
# of Redis Cluster.
|
||||
#################################################################################
|
||||
|
||||
# This is the CRC16 algorithm used by Redis Cluster to hash keys.
|
||||
# Implementation according to CCITT standards.
|
||||
#
|
||||
# This is actually the XMODEM CRC 16 algorithm, using the
|
||||
# following parameters:
|
||||
#
|
||||
# Name : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN"
|
||||
# Width : 16 bit
|
||||
# Poly : 1021 (That is actually x^16 + x^12 + x^5 + 1)
|
||||
# Initialization : 0000
|
||||
# Reflect Input byte : False
|
||||
# Reflect Output CRC : False
|
||||
# Xor constant to output CRC : 0000
|
||||
# Output for "123456789" : 31C3
|
||||
|
||||
module RedisClusterCRC16
|
||||
def RedisClusterCRC16.crc16(bytes)
|
||||
crc = 0
|
||||
bytes.each_byte{|b|
|
||||
crc = ((crc<<8) & 0xffff) ^ XMODEMCRC16Lookup[((crc>>8)^b) & 0xff]
|
||||
}
|
||||
crc
|
||||
end
|
||||
|
||||
private
|
||||
XMODEMCRC16Lookup = [
|
||||
0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
|
||||
0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
|
||||
0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6,
|
||||
0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de,
|
||||
0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485,
|
||||
0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d,
|
||||
0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4,
|
||||
0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc,
|
||||
0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823,
|
||||
0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b,
|
||||
0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12,
|
||||
0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a,
|
||||
0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41,
|
||||
0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49,
|
||||
0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70,
|
||||
0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78,
|
||||
0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f,
|
||||
0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067,
|
||||
0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e,
|
||||
0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256,
|
||||
0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d,
|
||||
0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405,
|
||||
0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c,
|
||||
0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634,
|
||||
0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab,
|
||||
0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3,
|
||||
0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a,
|
||||
0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92,
|
||||
0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9,
|
||||
0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1,
|
||||
0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8,
|
||||
0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0
|
||||
]
|
||||
end
|
||||
|
||||
# Turn a key name into the corrisponding Redis Cluster slot.
|
||||
def key_to_slot(key)
|
||||
# Only hash what is inside {...} if there is such a pattern in the key.
|
||||
# Note that the specification requires the content that is between
|
||||
# the first { and the first } after the first {. If we found {} without
|
||||
# nothing in the middle, the whole key is hashed as usually.
|
||||
s = key.index "{"
|
||||
if s
|
||||
e = key.index "}",s+1
|
||||
if e && e != s+1
|
||||
key = key[s+1..e-1]
|
||||
end
|
||||
end
|
||||
RedisClusterCRC16.crc16(key) % 16384
|
||||
end
|
||||
|
||||
#################################################################################
|
||||
# Definition of commands
|
||||
#################################################################################
|
||||
|
||||
COMMANDS={
|
||||
"create" => ["create_cluster_cmd", -2, "host1:port1 ... hostN:portN"],
|
||||
"check" => ["check_cluster_cmd", 2, "host:port"],
|
||||
@@ -983,12 +1245,15 @@ COMMANDS={
|
||||
"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"],
|
||||
"call" => ["call_cluster_cmd", -3, "host:port command arg arg .. arg"],
|
||||
"import" => ["import_cluster_cmd", 2, "host:port"],
|
||||
"help" => ["help_cluster_cmd", 1, "(show this help)"]
|
||||
}
|
||||
|
||||
ALLOWED_OPTIONS={
|
||||
"create" => {"replicas" => true},
|
||||
"add-node" => {"slave" => false, "master-id" => true}
|
||||
"add-node" => {"slave" => false, "master-id" => true},
|
||||
"import" => {"from" => :required}
|
||||
}
|
||||
|
||||
def show_help
|
||||
|
||||
+380
-110
@@ -118,13 +118,13 @@ struct redisCommand *commandTable;
|
||||
*/
|
||||
struct redisCommand redisCommandTable[] = {
|
||||
{"get",getCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
{"set",setCommand,-3,"wm",0,noPreloadGetKeys,1,1,1,0,0},
|
||||
{"setnx",setnxCommand,3,"wm",0,noPreloadGetKeys,1,1,1,0,0},
|
||||
{"setex",setexCommand,4,"wm",0,noPreloadGetKeys,1,1,1,0,0},
|
||||
{"psetex",psetexCommand,4,"wm",0,noPreloadGetKeys,1,1,1,0,0},
|
||||
{"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0},
|
||||
{"setnx",setnxCommand,3,"wm",0,NULL,1,1,1,0,0},
|
||||
{"setex",setexCommand,4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"psetex",psetexCommand,4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"append",appendCommand,3,"wm",0,NULL,1,1,1,0,0},
|
||||
{"strlen",strlenCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
{"del",delCommand,-2,"w",0,noPreloadGetKeys,1,-1,1,0,0},
|
||||
{"del",delCommand,-2,"w",0,NULL,1,-1,1,0,0},
|
||||
{"exists",existsCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
{"setbit",setbitCommand,4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"getbit",getbitCommand,3,"r",0,NULL,1,1,1,0,0},
|
||||
@@ -171,12 +171,16 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"zrem",zremCommand,-3,"w",0,NULL,1,1,1,0,0},
|
||||
{"zremrangebyscore",zremrangebyscoreCommand,4,"w",0,NULL,1,1,1,0,0},
|
||||
{"zremrangebyrank",zremrangebyrankCommand,4,"w",0,NULL,1,1,1,0,0},
|
||||
{"zremrangebylex",zremrangebylexCommand,4,"w",0,NULL,1,1,1,0,0},
|
||||
{"zunionstore",zunionstoreCommand,-4,"wm",0,zunionInterGetKeys,0,0,0,0,0},
|
||||
{"zinterstore",zinterstoreCommand,-4,"wm",0,zunionInterGetKeys,0,0,0,0,0},
|
||||
{"zrange",zrangeCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zrangebyscore",zrangebyscoreCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zrevrangebyscore",zrevrangebyscoreCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zrangebylex",zrangebylexCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zrevrangebylex",zrevrangebylexCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zcount",zcountCommand,4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zlexcount",zlexcountCommand,4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zrevrange",zrevrangeCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zcard",zcardCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
{"zscore",zscoreCommand,3,"r",0,NULL,1,1,1,0,0},
|
||||
@@ -206,8 +210,8 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"randomkey",randomkeyCommand,1,"rR",0,NULL,0,0,0,0,0},
|
||||
{"select",selectCommand,2,"rl",0,NULL,0,0,0,0,0},
|
||||
{"move",moveCommand,3,"w",0,NULL,1,1,1,0,0},
|
||||
{"rename",renameCommand,3,"w",0,renameGetKeys,1,2,1,0,0},
|
||||
{"renamenx",renamenxCommand,3,"w",0,renameGetKeys,1,2,1,0,0},
|
||||
{"rename",renameCommand,3,"w",0,NULL,1,2,1,0,0},
|
||||
{"renamenx",renamenxCommand,3,"w",0,NULL,1,2,1,0,0},
|
||||
{"expire",expireCommand,3,"w",0,NULL,1,1,1,0,0},
|
||||
{"expireat",expireatCommand,3,"w",0,NULL,1,1,1,0,0},
|
||||
{"pexpire",pexpireCommand,3,"w",0,NULL,1,1,1,0,0},
|
||||
@@ -232,7 +236,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"replconf",replconfCommand,-1,"arslt",0,NULL,0,0,0,0,0},
|
||||
{"flushdb",flushdbCommand,1,"w",0,NULL,0,0,0,0,0},
|
||||
{"flushall",flushallCommand,1,"w",0,NULL,0,0,0,0,0},
|
||||
{"sort",sortCommand,-2,"wm",0,NULL,1,1,1,0,0},
|
||||
{"sort",sortCommand,-2,"wm",0,sortGetKeys,1,1,1,0,0},
|
||||
{"info",infoCommand,-1,"rlt",0,NULL,0,0,0,0,0},
|
||||
{"monitor",monitorCommand,1,"ars",0,NULL,0,0,0,0,0},
|
||||
{"ttl",ttlCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
@@ -247,7 +251,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"punsubscribe",punsubscribeCommand,-1,"rpslt",0,NULL,0,0,0,0,0},
|
||||
{"publish",publishCommand,3,"pltr",0,NULL,0,0,0,0,0},
|
||||
{"pubsub",pubsubCommand,-2,"pltrR",0,NULL,0,0,0,0,0},
|
||||
{"watch",watchCommand,-2,"rs",0,noPreloadGetKeys,1,-1,1,0,0},
|
||||
{"watch",watchCommand,-2,"rs",0,NULL,1,-1,1,0,0},
|
||||
{"unwatch",unwatchCommand,1,"rs",0,NULL,0,0,0,0,0},
|
||||
{"cluster",clusterCommand,-2,"ar",0,NULL,0,0,0,0,0},
|
||||
{"restore",restoreCommand,-4,"awm",0,NULL,1,1,1,0,0},
|
||||
@@ -259,16 +263,24 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"dump",dumpCommand,2,"ar",0,NULL,1,1,1,0,0},
|
||||
{"object",objectCommand,-2,"r",0,NULL,2,2,2,0,0},
|
||||
{"client",clientCommand,-2,"ar",0,NULL,0,0,0,0,0},
|
||||
{"eval",evalCommand,-3,"s",0,zunionInterGetKeys,0,0,0,0,0},
|
||||
{"evalsha",evalShaCommand,-3,"s",0,zunionInterGetKeys,0,0,0,0,0},
|
||||
{"eval",evalCommand,-3,"s",0,evalGetKeys,0,0,0,0,0},
|
||||
{"evalsha",evalShaCommand,-3,"s",0,evalGetKeys,0,0,0,0,0},
|
||||
{"slowlog",slowlogCommand,-2,"r",0,NULL,0,0,0,0,0},
|
||||
{"script",scriptCommand,-2,"ras",0,NULL,0,0,0,0,0},
|
||||
{"time",timeCommand,1,"rR",0,NULL,0,0,0,0,0},
|
||||
{"bitop",bitopCommand,-4,"wm",0,NULL,2,-1,1,0,0},
|
||||
{"bitcount",bitcountCommand,-2,"r",0,NULL,1,1,1,0,0},
|
||||
{"wait",waitCommand,3,"rs",0,NULL,0,0,0,0,0}
|
||||
{"bitpos",bitposCommand,-3,"r",0,NULL,1,1,1,0,0},
|
||||
{"wait",waitCommand,3,"rs",0,NULL,0,0,0,0,0},
|
||||
{"pfselftest",pfselftestCommand,1,"r",0,NULL,0,0,0,0,0},
|
||||
{"pfadd",pfaddCommand,-2,"wm",0,NULL,1,1,1,0,0},
|
||||
{"pfcount",pfcountCommand,-2,"w",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}
|
||||
};
|
||||
|
||||
struct evictionPoolEntry *evictionPoolAlloc(void);
|
||||
|
||||
/*============================ Utility functions ============================ */
|
||||
|
||||
/* Low level logging. To use only for very big messages, otherwise
|
||||
@@ -292,11 +304,21 @@ void redisLogRaw(int level, const char *msg) {
|
||||
} else {
|
||||
int off;
|
||||
struct timeval tv;
|
||||
int role_char;
|
||||
pid_t pid = getpid();
|
||||
|
||||
gettimeofday(&tv,NULL);
|
||||
off = strftime(buf,sizeof(buf),"%d %b %H:%M:%S.",localtime(&tv.tv_sec));
|
||||
snprintf(buf+off,sizeof(buf)-off,"%03d",(int)tv.tv_usec/1000);
|
||||
fprintf(fp,"[%d] %s %c %s\n",(int)getpid(),buf,c[level],msg);
|
||||
if (server.sentinel_mode) {
|
||||
role_char = 'X'; /* Sentinel. */
|
||||
} else if (pid != server.pid) {
|
||||
role_char = 'C'; /* RDB / AOF writing child. */
|
||||
} else {
|
||||
role_char = (server.masterhost ? 'S':'M'); /* Slave or Master. */
|
||||
}
|
||||
fprintf(fp,"%d:%c %s %c %s\n",
|
||||
(int)getpid(),role_char, buf,c[level],msg);
|
||||
}
|
||||
fflush(fp);
|
||||
|
||||
@@ -337,9 +359,8 @@ void redisLogFromHandler(int level, const char *msg) {
|
||||
open(server.logfile, O_APPEND|O_CREAT|O_WRONLY, 0644);
|
||||
if (fd == -1) return;
|
||||
ll2string(buf,sizeof(buf),getpid());
|
||||
if (write(fd,"[",1) == -1) goto err;
|
||||
if (write(fd,buf,strlen(buf)) == -1) goto err;
|
||||
if (write(fd," | signal handler] (",20) == -1) goto err;
|
||||
if (write(fd,":signal-handler (",17) == -1) goto err;
|
||||
ll2string(buf,sizeof(buf),time(NULL));
|
||||
if (write(fd,buf,strlen(buf)) == -1) goto err;
|
||||
if (write(fd,") ",2) == -1) goto err;
|
||||
@@ -685,7 +706,7 @@ void updateDictResizePolicy(void) {
|
||||
*
|
||||
* The parameter 'now' is the current time in milliseconds as is passed
|
||||
* to the function to avoid too many gettimeofday() syscalls. */
|
||||
int activeExpireCycleTryExpire(redisDb *db, struct dictEntry *de, long long now) {
|
||||
int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
|
||||
long long t = dictGetSignedIntegerVal(de);
|
||||
if (now > t) {
|
||||
sds key = dictGetKey(de);
|
||||
@@ -842,12 +863,10 @@ void activeExpireCycle(int type) {
|
||||
}
|
||||
}
|
||||
|
||||
void updateLRUClock(void) {
|
||||
server.lruclock = (server.unixtime/REDIS_LRU_CLOCK_RESOLUTION) &
|
||||
REDIS_LRU_CLOCK_MAX;
|
||||
unsigned int getLRUClock(void) {
|
||||
return (mstime()/REDIS_LRU_CLOCK_RESOLUTION) & REDIS_LRU_CLOCK_MAX;
|
||||
}
|
||||
|
||||
|
||||
/* Add a sample to the operations per second array of samples. */
|
||||
void trackOperationsPerSecond(void) {
|
||||
long long t = mstime() - server.ops_sec_last_sample_time;
|
||||
@@ -1001,6 +1020,15 @@ void databasesCron(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* We take a cached value of the unix time in the global state because with
|
||||
* virtual memory and aging there is to store the current time in objects at
|
||||
* every object access, and accuracy is not needed. To access a global var is
|
||||
* a lot faster than calling time(NULL) */
|
||||
void updateCachedTime(void) {
|
||||
server.unixtime = time(NULL);
|
||||
server.mstime = mstime();
|
||||
}
|
||||
|
||||
/* This is our timer interrupt, called server.hz times per second.
|
||||
* Here is where we do a number of things that need to be done asynchronously.
|
||||
* For instance:
|
||||
@@ -1030,33 +1058,31 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
* handler if we don't return here fast enough. */
|
||||
if (server.watchdog_period) watchdogScheduleSignal(server.watchdog_period);
|
||||
|
||||
/* We take a cached value of the unix time in the global state because
|
||||
* with virtual memory and aging there is to store the current time
|
||||
* in objects at every object access, and accuracy is not needed.
|
||||
* To access a global var is faster than calling time(NULL) */
|
||||
server.unixtime = time(NULL);
|
||||
server.mstime = mstime();
|
||||
/* Update the time cache. */
|
||||
updateCachedTime();
|
||||
|
||||
run_with_period(100) trackOperationsPerSecond();
|
||||
|
||||
/* We have just 22 bits per object for LRU information.
|
||||
* So we use an (eventually wrapping) LRU clock with 10 seconds resolution.
|
||||
* 2^22 bits with 10 seconds resolution is more or less 1.5 years.
|
||||
/* We have just REDIS_LRU_BITS bits per object for LRU information.
|
||||
* So we use an (eventually wrapping) LRU clock.
|
||||
*
|
||||
* Note that even if this will wrap after 1.5 years it's not a problem,
|
||||
* everything will still work but just some object will appear younger
|
||||
* to Redis. But for this to happen a given object should never be touched
|
||||
* for 1.5 years.
|
||||
* Note that even if the counter wraps it's not a big problem,
|
||||
* everything will still work but some object will appear younger
|
||||
* to Redis. However for this to happen a given object should never be
|
||||
* touched for all the time needed to the counter to wrap, which is
|
||||
* not likely.
|
||||
*
|
||||
* Note that you can change the resolution altering the
|
||||
* REDIS_LRU_CLOCK_RESOLUTION define.
|
||||
*/
|
||||
updateLRUClock();
|
||||
* REDIS_LRU_CLOCK_RESOLUTION define. */
|
||||
server.lruclock = getLRUClock();
|
||||
|
||||
/* Record the max memory used since the server was started. */
|
||||
if (zmalloc_used_memory() > server.stat_peak_memory)
|
||||
server.stat_peak_memory = zmalloc_used_memory();
|
||||
|
||||
/* Sample the RSS here since this is a relatively slow call. */
|
||||
server.resident_set_size = zmalloc_get_rss();
|
||||
|
||||
/* We received a SIGTERM, shutting down here in a safe way, as it is
|
||||
* not ok doing so inside the signal handler. */
|
||||
if (server.shutdown_asap) {
|
||||
@@ -1167,10 +1193,19 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
}
|
||||
|
||||
|
||||
/* If we postponed an AOF buffer flush, let's try to do it every time the
|
||||
* cron function is called. */
|
||||
/* AOF postponed flush: Try at every cron cycle if the slow fsync
|
||||
* completed. */
|
||||
if (server.aof_flush_postponed_start) flushAppendOnlyFile(0);
|
||||
|
||||
/* AOF write errors: in this case we have a buffer to flush as well and
|
||||
* clear the AOF error in case of success to make the DB writable again,
|
||||
* however to try every second is enough in case of 'hz' is set to
|
||||
* an higher frequency. */
|
||||
run_with_period(1000) {
|
||||
if (server.aof_last_write_status == REDIS_ERR)
|
||||
flushAppendOnlyFile(0);
|
||||
}
|
||||
|
||||
/* Close clients that need to be closed asynchronous */
|
||||
freeClientsInAsyncFreeQueue();
|
||||
|
||||
@@ -1290,6 +1325,8 @@ void createSharedObjects(void) {
|
||||
"-EXECABORT Transaction discarded because of previous errors.\r\n"));
|
||||
shared.noreplicaserr = createObject(REDIS_STRING,sdsnew(
|
||||
"-NOREPLICAS Not enough good slaves to write.\r\n"));
|
||||
shared.busykeyerr = createObject(REDIS_STRING,sdsnew(
|
||||
"-BUSYKEY Target key name already exists.\r\n"));
|
||||
shared.space = createObject(REDIS_STRING,sdsnew(" "));
|
||||
shared.colon = createObject(REDIS_STRING,sdsnew(":"));
|
||||
shared.plus = createObject(REDIS_STRING,sdsnew("+"));
|
||||
@@ -1324,6 +1361,12 @@ void createSharedObjects(void) {
|
||||
shared.bulkhdr[j] = createObject(REDIS_STRING,
|
||||
sdscatprintf(sdsempty(),"$%d\r\n",j));
|
||||
}
|
||||
/* The following two shared objects, minstring and maxstrings, are not
|
||||
* actually used for their value but as a special object meaning
|
||||
* respectively the minimum possible string and the maximum possible
|
||||
* string in string comparisons for the ZRANGEBYLEX command. */
|
||||
shared.minstring = createStringObject("minstring",9);
|
||||
shared.maxstring = createStringObject("maxstring",9);
|
||||
}
|
||||
|
||||
void initServerConfig() {
|
||||
@@ -1391,6 +1434,7 @@ void initServerConfig() {
|
||||
server.set_max_intset_entries = REDIS_SET_MAX_INTSET_ENTRIES;
|
||||
server.zset_max_ziplist_entries = REDIS_ZSET_MAX_ZIPLIST_ENTRIES;
|
||||
server.zset_max_ziplist_value = REDIS_ZSET_MAX_ZIPLIST_VALUE;
|
||||
server.hll_sparse_max_bytes = REDIS_DEFAULT_HLL_SPARSE_MAX_BYTES;
|
||||
server.shutdown_asap = 0;
|
||||
server.repl_ping_slave_period = REDIS_REPL_PING_SLAVE_PERIOD;
|
||||
server.repl_timeout = REDIS_REPL_TIMEOUT;
|
||||
@@ -1399,6 +1443,7 @@ void initServerConfig() {
|
||||
server.cluster_enabled = 0;
|
||||
server.cluster_node_timeout = REDIS_CLUSTER_DEFAULT_NODE_TIMEOUT;
|
||||
server.cluster_migration_barrier = REDIS_CLUSTER_DEFAULT_MIGRATION_BARRIER;
|
||||
server.cluster_slave_validity_factor = REDIS_CLUSTER_DEFAULT_SLAVE_VALIDITY;
|
||||
server.cluster_configfile = zstrdup(REDIS_DEFAULT_CLUSTER_CONFIG_FILE);
|
||||
server.lua_caller = NULL;
|
||||
server.lua_time_limit = REDIS_LUA_TIME_LIMIT;
|
||||
@@ -1407,7 +1452,7 @@ void initServerConfig() {
|
||||
server.migrate_cached_sockets = dictCreate(&migrateCacheDictType,NULL);
|
||||
server.loading_process_events_interval_bytes = (1024*1024*2);
|
||||
|
||||
updateLRUClock();
|
||||
server.lruclock = getLRUClock();
|
||||
resetServerSaveParams();
|
||||
|
||||
appendServerSaveParams(60*60,1); /* save after 1 hour and 1 change */
|
||||
@@ -1473,21 +1518,21 @@ void initServerConfig() {
|
||||
}
|
||||
|
||||
/* This function will try to raise the max number of open files accordingly to
|
||||
* the configured max number of clients. It will also account for 32 additional
|
||||
* file descriptors as we need a few more for persistence, listening
|
||||
* sockets, log files and so forth.
|
||||
* the configured max number of clients. It also reserves a number of file
|
||||
* descriptors (REDIS_MIN_RESERVED_FDS) for extra operations of
|
||||
* persistence, listening sockets, log files and so forth.
|
||||
*
|
||||
* If it will not be possible to set the limit accordingly to the configured
|
||||
* max number of clients, the function will do the reverse setting
|
||||
* server.maxclients to the value that we can actually handle. */
|
||||
void adjustOpenFilesLimit(void) {
|
||||
rlim_t maxfiles = server.maxclients+32;
|
||||
rlim_t maxfiles = server.maxclients+REDIS_MIN_RESERVED_FDS;
|
||||
struct rlimit limit;
|
||||
|
||||
if (getrlimit(RLIMIT_NOFILE,&limit) == -1) {
|
||||
redisLog(REDIS_WARNING,"Unable to obtain the current NOFILE limit (%s), assuming 1024 and setting the max clients configuration accordingly.",
|
||||
strerror(errno));
|
||||
server.maxclients = 1024-32;
|
||||
server.maxclients = 1024-REDIS_MIN_RESERVED_FDS;
|
||||
} else {
|
||||
rlim_t oldlimit = limit.rlim_cur;
|
||||
|
||||
@@ -1495,22 +1540,58 @@ void adjustOpenFilesLimit(void) {
|
||||
* for our needs. */
|
||||
if (oldlimit < maxfiles) {
|
||||
rlim_t f;
|
||||
|
||||
int setrlimit_error = 0;
|
||||
|
||||
/* Try to set the file limit to match 'maxfiles' or at least
|
||||
* to the higher value supported less than maxfiles. */
|
||||
f = maxfiles;
|
||||
while(f > oldlimit) {
|
||||
int decr_step = 16;
|
||||
|
||||
limit.rlim_cur = f;
|
||||
limit.rlim_max = f;
|
||||
if (setrlimit(RLIMIT_NOFILE,&limit) != -1) break;
|
||||
f -= 128;
|
||||
setrlimit_error = errno;
|
||||
|
||||
/* We failed to set file limit to 'f'. Try with a
|
||||
* smaller limit decrementing by a few FDs per iteration. */
|
||||
if (f < decr_step) break;
|
||||
f -= decr_step;
|
||||
}
|
||||
|
||||
/* Assume that the limit we get initially is still valid if
|
||||
* our last try was even lower. */
|
||||
if (f < oldlimit) f = oldlimit;
|
||||
|
||||
if (f != maxfiles) {
|
||||
server.maxclients = f-32;
|
||||
redisLog(REDIS_WARNING,"Unable to set the max number of files limit to %d (%s), setting the max clients configuration to %d.",
|
||||
(int) maxfiles, strerror(errno), (int) server.maxclients);
|
||||
int old_maxclients = server.maxclients;
|
||||
server.maxclients = f-REDIS_MIN_RESERVED_FDS;
|
||||
if (server.maxclients < 1) {
|
||||
redisLog(REDIS_WARNING,"Your current 'ulimit -n' "
|
||||
"of %llu is not enough for Redis to start. "
|
||||
"Please increase your open file limit to at least "
|
||||
"%llu. Exiting.",
|
||||
(unsigned long long) oldlimit,
|
||||
(unsigned long long) maxfiles);
|
||||
exit(1);
|
||||
}
|
||||
redisLog(REDIS_WARNING,"You requested maxclients of %d "
|
||||
"requiring at least %llu max file descriptors.",
|
||||
old_maxclients,
|
||||
(unsigned long long) maxfiles);
|
||||
redisLog(REDIS_WARNING,"Redis can't set maximum open files "
|
||||
"to %llu because of OS error: %s.",
|
||||
(unsigned long long) maxfiles, strerror(setrlimit_error));
|
||||
redisLog(REDIS_WARNING,"Current maximum open files is %llu. "
|
||||
"maxclients has been reduced to %d to compensate for "
|
||||
"low ulimit. "
|
||||
"If you need higher maxclients increase 'ulimit -n'.",
|
||||
(unsigned long long) oldlimit, server.maxclients);
|
||||
} else {
|
||||
redisLog(REDIS_NOTICE,"Max number of open files set to %d",
|
||||
(int) maxfiles);
|
||||
redisLog(REDIS_NOTICE,"Increased maximum number of open files "
|
||||
"to %llu (it was originally set to %llu).",
|
||||
(unsigned long long) maxfiles,
|
||||
(unsigned long long) oldlimit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1546,10 +1627,16 @@ int listenToPort(int port, int *fds, int *count) {
|
||||
* server.bindaddr_count == 0. */
|
||||
fds[*count] = anetTcp6Server(server.neterr,port,NULL,
|
||||
server.tcp_backlog);
|
||||
if (fds[*count] != ANET_ERR) (*count)++;
|
||||
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) (*count)++;
|
||||
if (fds[*count] != ANET_ERR) {
|
||||
anetNonBlock(NULL,fds[*count]);
|
||||
(*count)++;
|
||||
}
|
||||
/* Exit the loop if we were able to bind * on IPv4 or IPv6,
|
||||
* otherwise fds[*count] will be ANET_ERR and we'll print an
|
||||
* error and return to the caller with an error. */
|
||||
@@ -1567,14 +1654,36 @@ int listenToPort(int port, int *fds, int *count) {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Creating Server TCP listening socket %s:%d: %s",
|
||||
server.bindaddr[j] ? server.bindaddr[j] : "*",
|
||||
server.port, server.neterr);
|
||||
port, server.neterr);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
anetNonBlock(NULL,fds[*count]);
|
||||
(*count)++;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Resets the stats that we expose via INFO or other means that we want
|
||||
* to reset via CONFIG RESETSTAT. The function is also used in order to
|
||||
* initialize these fields in initServer() at server startup. */
|
||||
void resetServerStats(void) {
|
||||
server.stat_numcommands = 0;
|
||||
server.stat_numconnections = 0;
|
||||
server.stat_expiredkeys = 0;
|
||||
server.stat_evictedkeys = 0;
|
||||
server.stat_keyspace_misses = 0;
|
||||
server.stat_keyspace_hits = 0;
|
||||
server.stat_fork_time = 0;
|
||||
server.stat_rejected_conn = 0;
|
||||
server.stat_sync_full = 0;
|
||||
server.stat_sync_partial_ok = 0;
|
||||
server.stat_sync_partial_err = 0;
|
||||
memset(server.ops_sec_samples,0,sizeof(server.ops_sec_samples));
|
||||
server.ops_sec_idx = 0;
|
||||
server.ops_sec_last_sample_time = mstime();
|
||||
server.ops_sec_last_sample_ops = 0;
|
||||
}
|
||||
|
||||
void initServer() {
|
||||
int j;
|
||||
|
||||
@@ -1587,6 +1696,7 @@ void initServer() {
|
||||
server.syslog_facility);
|
||||
}
|
||||
|
||||
server.pid = getpid();
|
||||
server.current_client = NULL;
|
||||
server.clients = listCreate();
|
||||
server.clients_to_close = listCreate();
|
||||
@@ -1618,6 +1728,7 @@ void initServer() {
|
||||
redisLog(REDIS_WARNING, "Opening socket: %s", server.neterr);
|
||||
exit(1);
|
||||
}
|
||||
anetNonBlock(NULL,server.sofd);
|
||||
}
|
||||
|
||||
/* Abort if there are no listening sockets at all. */
|
||||
@@ -1633,6 +1744,7 @@ void initServer() {
|
||||
server.db[j].blocking_keys = dictCreate(&keylistDictType,NULL);
|
||||
server.db[j].ready_keys = dictCreate(&setDictType,NULL);
|
||||
server.db[j].watched_keys = dictCreate(&keylistDictType,NULL);
|
||||
server.db[j].eviction_pool = evictionPoolAlloc();
|
||||
server.db[j].id = j;
|
||||
server.db[j].avg_ttl = 0;
|
||||
}
|
||||
@@ -1650,27 +1762,16 @@ void initServer() {
|
||||
server.rdb_save_time_last = -1;
|
||||
server.rdb_save_time_start = -1;
|
||||
server.dirty = 0;
|
||||
server.stat_numcommands = 0;
|
||||
server.stat_numconnections = 0;
|
||||
server.stat_expiredkeys = 0;
|
||||
server.stat_evictedkeys = 0;
|
||||
resetServerStats();
|
||||
/* A few stats we don't want to reset: server startup time, and peak mem. */
|
||||
server.stat_starttime = time(NULL);
|
||||
server.stat_keyspace_misses = 0;
|
||||
server.stat_keyspace_hits = 0;
|
||||
server.stat_peak_memory = 0;
|
||||
server.stat_fork_time = 0;
|
||||
server.stat_rejected_conn = 0;
|
||||
server.stat_sync_full = 0;
|
||||
server.stat_sync_partial_ok = 0;
|
||||
server.stat_sync_partial_err = 0;
|
||||
memset(server.ops_sec_samples,0,sizeof(server.ops_sec_samples));
|
||||
server.ops_sec_idx = 0;
|
||||
server.ops_sec_last_sample_time = mstime();
|
||||
server.ops_sec_last_sample_ops = 0;
|
||||
server.unixtime = time(NULL);
|
||||
server.mstime = mstime();
|
||||
server.resident_set_size = 0;
|
||||
server.lastbgsave_status = REDIS_OK;
|
||||
server.aof_last_write_status = REDIS_OK;
|
||||
server.aof_last_write_errno = 0;
|
||||
server.repl_good_slaves_count = 0;
|
||||
updateCachedTime();
|
||||
|
||||
/* Create the serverCron() time event, that's our main way to process
|
||||
* background operations. */
|
||||
@@ -2002,17 +2103,30 @@ int processCommand(redisClient *c) {
|
||||
int hashslot;
|
||||
|
||||
if (server.cluster->state != REDIS_CLUSTER_OK) {
|
||||
flagTransaction(c);
|
||||
addReplySds(c,sdsnew("-CLUSTERDOWN The cluster is down. Use CLUSTER INFO for more information\r\n"));
|
||||
return REDIS_OK;
|
||||
} else {
|
||||
int ask;
|
||||
clusterNode *n = getNodeByQuery(c,c->cmd,c->argv,c->argc,&hashslot,&ask);
|
||||
int error_code;
|
||||
clusterNode *n = getNodeByQuery(c,c->cmd,c->argv,c->argc,&hashslot,&error_code);
|
||||
if (n == NULL) {
|
||||
addReplyError(c,"Multi keys request invalid in cluster");
|
||||
flagTransaction(c);
|
||||
if (error_code == REDIS_CLUSTER_REDIR_CROSS_SLOT) {
|
||||
addReplySds(c,sdsnew("-CROSSSLOT Keys in request don't hash to the same slot\r\n"));
|
||||
} else if (error_code == REDIS_CLUSTER_REDIR_UNSTABLE) {
|
||||
/* The request spawns mutliple keys in the same slot,
|
||||
* but the slot is not "stable" currently as there is
|
||||
* a migration or import in progress. */
|
||||
addReplySds(c,sdsnew("-TRYAGAIN Multiple keys request during rehashing of slot\r\n"));
|
||||
} else {
|
||||
redisPanic("getNodeByQuery() unknown error.");
|
||||
}
|
||||
return REDIS_OK;
|
||||
} else if (n != server.cluster->myself) {
|
||||
flagTransaction(c);
|
||||
addReplySds(c,sdscatprintf(sdsempty(),
|
||||
"-%s %d %s:%d\r\n", ask ? "ASK" : "MOVED",
|
||||
"-%s %d %s:%d\r\n",
|
||||
(error_code == REDIS_CLUSTER_REDIR_ASK) ? "ASK" : "MOVED",
|
||||
hashslot,n->ip,n->port));
|
||||
return REDIS_OK;
|
||||
}
|
||||
@@ -2035,15 +2149,22 @@ int processCommand(redisClient *c) {
|
||||
|
||||
/* Don't accept write commands if there are problems persisting on disk
|
||||
* and if this is a master instance. */
|
||||
if (server.stop_writes_on_bgsave_err &&
|
||||
server.saveparamslen > 0
|
||||
&& server.lastbgsave_status == REDIS_ERR &&
|
||||
if (((server.stop_writes_on_bgsave_err &&
|
||||
server.saveparamslen > 0 &&
|
||||
server.lastbgsave_status == REDIS_ERR) ||
|
||||
server.aof_last_write_status == REDIS_ERR) &&
|
||||
server.masterhost == NULL &&
|
||||
(c->cmd->flags & REDIS_CMD_WRITE ||
|
||||
c->cmd->proc == pingCommand))
|
||||
{
|
||||
flagTransaction(c);
|
||||
addReply(c, shared.bgsaveerr);
|
||||
if (server.aof_last_write_status == REDIS_OK)
|
||||
addReply(c, shared.bgsaveerr);
|
||||
else
|
||||
addReplySds(c,
|
||||
sdscatprintf(sdsempty(),
|
||||
"-MISCONF Errors writing to the AOF file: %s\r\n",
|
||||
strerror(server.aof_last_write_errno)));
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
@@ -2315,7 +2436,8 @@ sds genRedisInfoString(char *section) {
|
||||
|
||||
/* Server */
|
||||
if (allsections || defsections || !strcasecmp(section,"server")) {
|
||||
struct utsname name;
|
||||
static int call_uname = 1;
|
||||
static struct utsname name;
|
||||
char *mode;
|
||||
|
||||
if (server.cluster_enabled) mode = "cluster";
|
||||
@@ -2323,7 +2445,13 @@ sds genRedisInfoString(char *section) {
|
||||
else mode = "standalone";
|
||||
|
||||
if (sections++) info = sdscat(info,"\r\n");
|
||||
uname(&name);
|
||||
|
||||
if (call_uname) {
|
||||
/* Uname can be slow and is always the same output. Cache it. */
|
||||
uname(&name);
|
||||
call_uname = 0;
|
||||
}
|
||||
|
||||
info = sdscatprintf(info,
|
||||
"# Server\r\n"
|
||||
"redis_version:%s\r\n"
|
||||
@@ -2384,8 +2512,16 @@ sds genRedisInfoString(char *section) {
|
||||
if (allsections || defsections || !strcasecmp(section,"memory")) {
|
||||
char hmem[64];
|
||||
char peak_hmem[64];
|
||||
size_t zmalloc_used = zmalloc_used_memory();
|
||||
|
||||
bytesToHuman(hmem,zmalloc_used_memory());
|
||||
/* Peak memory is updated from time to time by serverCron() so it
|
||||
* may happen that the instantaneous value is slightly bigger than
|
||||
* the peak value. This may confuse users, so we update the peak
|
||||
* if found smaller than the current memory usage. */
|
||||
if (zmalloc_used > server.stat_peak_memory)
|
||||
server.stat_peak_memory = zmalloc_used;
|
||||
|
||||
bytesToHuman(hmem,zmalloc_used);
|
||||
bytesToHuman(peak_hmem,server.stat_peak_memory);
|
||||
if (sections++) info = sdscat(info,"\r\n");
|
||||
info = sdscatprintf(info,
|
||||
@@ -2398,13 +2534,13 @@ sds genRedisInfoString(char *section) {
|
||||
"used_memory_lua:%lld\r\n"
|
||||
"mem_fragmentation_ratio:%.2f\r\n"
|
||||
"mem_allocator:%s\r\n",
|
||||
zmalloc_used_memory(),
|
||||
zmalloc_used,
|
||||
hmem,
|
||||
zmalloc_get_rss(),
|
||||
server.resident_set_size,
|
||||
server.stat_peak_memory,
|
||||
peak_hmem,
|
||||
((long long)lua_gc(server.lua,LUA_GCCOUNT,0))*1024LL,
|
||||
zmalloc_get_fragmentation_ratio(),
|
||||
zmalloc_get_fragmentation_ratio(server.resident_set_size),
|
||||
ZMALLOC_LIB
|
||||
);
|
||||
}
|
||||
@@ -2426,7 +2562,8 @@ sds genRedisInfoString(char *section) {
|
||||
"aof_rewrite_scheduled:%d\r\n"
|
||||
"aof_last_rewrite_time_sec:%jd\r\n"
|
||||
"aof_current_rewrite_time_sec:%jd\r\n"
|
||||
"aof_last_bgrewrite_status:%s\r\n",
|
||||
"aof_last_bgrewrite_status:%s\r\n"
|
||||
"aof_last_write_status:%s\r\n",
|
||||
server.loading,
|
||||
server.dirty,
|
||||
server.rdb_child_pid != -1,
|
||||
@@ -2441,7 +2578,8 @@ sds genRedisInfoString(char *section) {
|
||||
(intmax_t)server.aof_rewrite_time_last,
|
||||
(intmax_t)((server.aof_child_pid == -1) ?
|
||||
-1 : time(NULL)-server.aof_rewrite_time_start),
|
||||
(server.aof_lastbgrewrite_status == REDIS_OK) ? "ok" : "err");
|
||||
(server.aof_lastbgrewrite_status == REDIS_OK) ? "ok" : "err",
|
||||
(server.aof_last_write_status == REDIS_OK) ? "ok" : "err");
|
||||
|
||||
if (server.aof_state != REDIS_AOF_OFF) {
|
||||
info = sdscatprintf(info,
|
||||
@@ -2732,8 +2870,9 @@ void monitorCommand(redisClient *c) {
|
||||
|
||||
/* ============================ Maxmemory directive ======================== */
|
||||
|
||||
/* This function gets called when 'maxmemory' is set on the config file to limit
|
||||
* the max memory used by the server, before processing a command.
|
||||
/* freeMemoryIfNeeded() gets called when 'maxmemory' is set on the config
|
||||
* file to limit the max memory used by the server, before processing a
|
||||
* command.
|
||||
*
|
||||
* The goal of the function is to free enough memory to keep Redis under the
|
||||
* configured memory limit.
|
||||
@@ -2746,7 +2885,123 @@ void monitorCommand(redisClient *c) {
|
||||
* function returns REDIS_OK, otherwise REDIS_ERR is returned, and the caller
|
||||
* should block the execution of commands that will result in more memory
|
||||
* used by the server.
|
||||
*/
|
||||
*
|
||||
* ------------------------------------------------------------------------
|
||||
*
|
||||
* LRU approximation algorithm
|
||||
*
|
||||
* Redis uses an approximation of the LRU algorithm that runs in constant
|
||||
* memory. Every time there is a key to expire, we sample N keys (with
|
||||
* N very small, usually in around 5) to populate a pool of best keys to
|
||||
* evict of M keys (the pool size is defined by REDIS_EVICTION_POOL_SIZE).
|
||||
*
|
||||
* The N keys sampled are added in the pool of good keys to expire (the one
|
||||
* with an old access time) if they are better than one of the current keys
|
||||
* in the pool.
|
||||
*
|
||||
* After the pool is populated, the best key we have in the pool is expired.
|
||||
* However note that we don't remove keys from the pool when they are deleted
|
||||
* so the pool may contain keys that no longer exist.
|
||||
*
|
||||
* When we try to evict a key, and all the entries in the pool don't exist
|
||||
* we populate it again. This time we'll be sure that the pool has at least
|
||||
* one key that can be evicted, if there is at least one key that can be
|
||||
* evicted in the whole database. */
|
||||
|
||||
/* Create a new eviction pool. */
|
||||
struct evictionPoolEntry *evictionPoolAlloc(void) {
|
||||
struct evictionPoolEntry *ep;
|
||||
int j;
|
||||
|
||||
ep = zmalloc(sizeof(*ep)*REDIS_EVICTION_POOL_SIZE);
|
||||
for (j = 0; j < REDIS_EVICTION_POOL_SIZE; j++) {
|
||||
ep[j].idle = 0;
|
||||
ep[j].key = NULL;
|
||||
}
|
||||
return ep;
|
||||
}
|
||||
|
||||
/* This is an helper function for freeMemoryIfNeeded(), it is used in order
|
||||
* to populate the evictionPool with a few entries every time we want to
|
||||
* expire a key. Keys with idle time smaller than one of the current
|
||||
* keys are added. Keys are always added if there are free entries.
|
||||
*
|
||||
* We insert keys on place in ascending order, so keys with the smaller
|
||||
* idle time are on the left, and keys with the higher idle time on the
|
||||
* right. */
|
||||
|
||||
#define EVICTION_SAMPLES_ARRAY_SIZE 16
|
||||
void evictionPoolPopulate(dict *sampledict, dict *keydict, struct evictionPoolEntry *pool) {
|
||||
int j, k, count;
|
||||
dictEntry *_samples[EVICTION_SAMPLES_ARRAY_SIZE];
|
||||
dictEntry **samples;
|
||||
|
||||
/* Try to use a static buffer: this function is a big hit...
|
||||
* Note: it was actually measured that this helps. */
|
||||
if (server.maxmemory_samples <= EVICTION_SAMPLES_ARRAY_SIZE) {
|
||||
samples = _samples;
|
||||
} else {
|
||||
samples = zmalloc(sizeof(samples[0])*server.maxmemory_samples);
|
||||
}
|
||||
|
||||
#if 1 /* Use bulk get by default. */
|
||||
count = dictGetRandomKeys(sampledict,samples,server.maxmemory_samples);
|
||||
#else
|
||||
count = server.maxmemory_samples;
|
||||
for (j = 0; j < count; j++) samples[j] = dictGetRandomKey(sampledict);
|
||||
#endif
|
||||
|
||||
for (j = 0; j < count; j++) {
|
||||
unsigned long long idle;
|
||||
sds key;
|
||||
robj *o;
|
||||
dictEntry *de;
|
||||
|
||||
de = samples[j];
|
||||
key = dictGetKey(de);
|
||||
/* If the dictionary we are sampling from is not the main
|
||||
* dictionary (but the expires one) we need to lookup the key
|
||||
* again in the key dictionary to obtain the value object. */
|
||||
if (sampledict != keydict) de = dictFind(keydict, key);
|
||||
o = dictGetVal(de);
|
||||
idle = estimateObjectIdleTime(o);
|
||||
|
||||
/* Insert the element inside the pool.
|
||||
* First, find the first empty bucket or the first populated
|
||||
* bucket that has an idle time smaller than our idle time. */
|
||||
k = 0;
|
||||
while (k < REDIS_EVICTION_POOL_SIZE &&
|
||||
pool[k].key &&
|
||||
pool[k].idle < idle) k++;
|
||||
if (k == 0 && pool[REDIS_EVICTION_POOL_SIZE-1].key != NULL) {
|
||||
/* Can't insert if the element is < the worst element we have
|
||||
* and there are no empty buckets. */
|
||||
continue;
|
||||
} else if (k < REDIS_EVICTION_POOL_SIZE && pool[k].key == NULL) {
|
||||
/* Inserting into empty position. No setup needed before insert. */
|
||||
} else {
|
||||
/* Inserting in the middle. Now k points to the first element
|
||||
* greater than the element to insert. */
|
||||
if (pool[REDIS_EVICTION_POOL_SIZE-1].key == NULL) {
|
||||
/* Free space on the right? Insert at k shifting
|
||||
* all the elements from k to end to the right. */
|
||||
memmove(pool+k+1,pool+k,
|
||||
sizeof(pool[0])*(REDIS_EVICTION_POOL_SIZE-k-1));
|
||||
} else {
|
||||
/* No free space on right? Insert at k-1 */
|
||||
k--;
|
||||
/* Shift all elements on the left of k (included) to the
|
||||
* left, so we discard the element with smaller idle time. */
|
||||
sdsfree(pool[0].key);
|
||||
memmove(pool,pool+1,sizeof(pool[0])*k);
|
||||
}
|
||||
}
|
||||
pool[k].key = sdsdup(key);
|
||||
pool[k].idle = idle;
|
||||
}
|
||||
if (samples != _samples) zfree(samples);
|
||||
}
|
||||
|
||||
int freeMemoryIfNeeded(void) {
|
||||
size_t mem_used, mem_tofree, mem_freed;
|
||||
int slaves = listLength(server.slaves);
|
||||
@@ -2788,7 +3043,7 @@ int freeMemoryIfNeeded(void) {
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
long bestval = 0; /* just to prevent warning */
|
||||
sds bestkey = NULL;
|
||||
struct dictEntry *de;
|
||||
dictEntry *de;
|
||||
redisDb *db = server.db+j;
|
||||
dict *dict;
|
||||
|
||||
@@ -2813,24 +3068,34 @@ int freeMemoryIfNeeded(void) {
|
||||
else if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU ||
|
||||
server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU)
|
||||
{
|
||||
for (k = 0; k < server.maxmemory_samples; k++) {
|
||||
sds thiskey;
|
||||
long thisval;
|
||||
robj *o;
|
||||
struct evictionPoolEntry *pool = db->eviction_pool;
|
||||
|
||||
de = dictGetRandomKey(dict);
|
||||
thiskey = dictGetKey(de);
|
||||
/* When policy is volatile-lru we need an additional lookup
|
||||
* to locate the real key, as dict is set to db->expires. */
|
||||
if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU)
|
||||
de = dictFind(db->dict, thiskey);
|
||||
o = dictGetVal(de);
|
||||
thisval = estimateObjectIdleTime(o);
|
||||
while(bestkey == NULL) {
|
||||
evictionPoolPopulate(dict, db->dict, db->eviction_pool);
|
||||
/* Go backward from best to worst element to evict. */
|
||||
for (k = REDIS_EVICTION_POOL_SIZE-1; k >= 0; k--) {
|
||||
if (pool[k].key == NULL) continue;
|
||||
de = dictFind(dict,pool[k].key);
|
||||
|
||||
/* Higher idle time is better candidate for deletion */
|
||||
if (bestkey == NULL || thisval > bestval) {
|
||||
bestkey = thiskey;
|
||||
bestval = thisval;
|
||||
/* Remove the entry from the pool. */
|
||||
sdsfree(pool[k].key);
|
||||
/* Shift all elements on its right to left. */
|
||||
memmove(pool+k,pool+k+1,
|
||||
sizeof(pool[0])*(REDIS_EVICTION_POOL_SIZE-k-1));
|
||||
/* Clear the element on the right which is empty
|
||||
* since we shifted one position to the left. */
|
||||
pool[REDIS_EVICTION_POOL_SIZE-1].key = NULL;
|
||||
pool[REDIS_EVICTION_POOL_SIZE-1].idle = 0;
|
||||
|
||||
/* If the key exists, is our pick. Otherwise it is
|
||||
* a ghost and we need to try the next element. */
|
||||
if (de) {
|
||||
bestkey = dictGetKey(de);
|
||||
break;
|
||||
} else {
|
||||
/* Ghost... */
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3055,10 +3320,15 @@ void redisOutOfMemoryHandler(size_t allocation_size) {
|
||||
|
||||
void redisSetProcTitle(char *title) {
|
||||
#ifdef USE_SETPROCTITLE
|
||||
setproctitle("%s %s:%d",
|
||||
char *server_mode = "";
|
||||
if (server.cluster_enabled) server_mode = " [cluster]";
|
||||
else if (server.sentinel_mode) server_mode = " [sentinel]";
|
||||
|
||||
setproctitle("%s %s:%d%s",
|
||||
title,
|
||||
server.bindaddr_count ? server.bindaddr[0] : "*",
|
||||
server.port);
|
||||
server.port,
|
||||
server_mode);
|
||||
#else
|
||||
REDIS_NOTUSED(title);
|
||||
#endif
|
||||
@@ -3129,10 +3399,10 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
j++;
|
||||
}
|
||||
if (configfile) server.configfile = getAbsolutePath(configfile);
|
||||
resetServerSaveParams();
|
||||
loadServerConfig(configfile,options);
|
||||
sdsfree(options);
|
||||
if (configfile) server.configfile = getAbsolutePath(configfile);
|
||||
} else {
|
||||
redisLog(REDIS_WARNING, "Warning: no config file specified, using the default config. In order to specify a config file use %s /path/to/%s.conf", argv[0], server.sentinel_mode ? "sentinel" : "redis");
|
||||
}
|
||||
|
||||
+80
-27
@@ -113,7 +113,7 @@
|
||||
#define REDIS_DEFAULT_SLAVE_READ_ONLY 1
|
||||
#define REDIS_DEFAULT_REPL_DISABLE_TCP_NODELAY 0
|
||||
#define REDIS_DEFAULT_MAXMEMORY 0
|
||||
#define REDIS_DEFAULT_MAXMEMORY_SAMPLES 3
|
||||
#define REDIS_DEFAULT_MAXMEMORY_SAMPLES 5
|
||||
#define REDIS_DEFAULT_AOF_FILENAME "appendonly.aof"
|
||||
#define REDIS_DEFAULT_AOF_NO_FSYNC_ON_REWRITE 0
|
||||
#define REDIS_DEFAULT_ACTIVE_REHASHING 1
|
||||
@@ -123,6 +123,7 @@
|
||||
#define REDIS_IP_STR_LEN INET6_ADDRSTRLEN
|
||||
#define REDIS_PEER_ID_LEN (REDIS_IP_STR_LEN+32) /* Must be enough for ip:port */
|
||||
#define REDIS_BINDADDR_MAX 16
|
||||
#define REDIS_MIN_RESERVED_FDS 32
|
||||
|
||||
#define ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 20 /* Loopkups per loop. */
|
||||
#define ACTIVE_EXPIRE_CYCLE_FAST_DURATION 1000 /* Microseconds */
|
||||
@@ -139,9 +140,9 @@
|
||||
#define REDIS_LONGSTR_SIZE 21 /* Bytes needed for long -> str */
|
||||
#define REDIS_AOF_AUTOSYNC_BYTES (1024*1024*32) /* fdatasync every 32MB */
|
||||
/* When configuring the Redis eventloop, we setup it so that the total number
|
||||
* of file descriptors we can handle are server.maxclients + FDSET_INCR
|
||||
* of file descriptors we can handle are server.maxclients + RESERVED_FDS + FDSET_INCR
|
||||
* that is our safety margin. */
|
||||
#define REDIS_EVENTLOOP_FDSET_INCR 128
|
||||
#define REDIS_EVENTLOOP_FDSET_INCR (REDIS_MIN_RESERVED_FDS+96)
|
||||
|
||||
/* Hash table parameters */
|
||||
#define REDIS_HT_MINFILL 10 /* Minimal hash table fill 10% */
|
||||
@@ -311,6 +312,9 @@
|
||||
#define REDIS_ZSET_MAX_ZIPLIST_ENTRIES 128
|
||||
#define REDIS_ZSET_MAX_ZIPLIST_VALUE 64
|
||||
|
||||
/* HyperLogLog defines */
|
||||
#define REDIS_DEFAULT_HLL_SPARSE_MAX_BYTES 3000
|
||||
|
||||
/* Sets operations codes */
|
||||
#define REDIS_OP_UNION 0
|
||||
#define REDIS_OP_DIFF 1
|
||||
@@ -323,7 +327,7 @@
|
||||
#define REDIS_MAXMEMORY_ALLKEYS_LRU 3
|
||||
#define REDIS_MAXMEMORY_ALLKEYS_RANDOM 4
|
||||
#define REDIS_MAXMEMORY_NO_EVICTION 5
|
||||
#define REDIS_DEFAULT_MAXMEMORY_POLICY REDIS_MAXMEMORY_VOLATILE_LRU
|
||||
#define REDIS_DEFAULT_MAXMEMORY_POLICY REDIS_MAXMEMORY_NO_EVICTION
|
||||
|
||||
/* Scripting */
|
||||
#define REDIS_LUA_TIME_LIMIT 5000 /* milliseconds */
|
||||
@@ -382,17 +386,23 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
/* A redis object, that is a type able to hold a string / list / set */
|
||||
|
||||
/* The actual Redis Object */
|
||||
#define REDIS_LRU_CLOCK_MAX ((1<<21)-1) /* Max value of obj->lru */
|
||||
#define REDIS_LRU_CLOCK_RESOLUTION 10 /* LRU clock resolution in seconds */
|
||||
#define REDIS_LRU_BITS 24
|
||||
#define REDIS_LRU_CLOCK_MAX ((1<<REDIS_LRU_BITS)-1) /* Max value of obj->lru */
|
||||
#define REDIS_LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */
|
||||
typedef struct redisObject {
|
||||
unsigned type:4;
|
||||
unsigned notused:2; /* Not used */
|
||||
unsigned encoding:4;
|
||||
unsigned lru:22; /* lru time (relative to server.lruclock) */
|
||||
unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
|
||||
int refcount;
|
||||
void *ptr;
|
||||
} robj;
|
||||
|
||||
/* Macro used to obtain the current LRU clock.
|
||||
* If the current resolution is lower than the frequency we refresh the
|
||||
* LRU clock (as it should be in production servers) we return the
|
||||
* precomputed value, otherwise we need to resort to a function call. */
|
||||
#define LRU_CLOCK() ((1000/server.hz <= REDIS_LRU_CLOCK_RESOLUTION) ? server.lruclock : getLRUClock())
|
||||
|
||||
/* Macro used to initialize a Redis object allocated on the stack.
|
||||
* Note that this macro is taken near the structure definition to make sure
|
||||
* we'll update it when the structure is changed, to avoid bugs like
|
||||
@@ -404,13 +414,30 @@ typedef struct redisObject {
|
||||
_var.ptr = _ptr; \
|
||||
} while(0);
|
||||
|
||||
/* To improve the quality of the LRU approximation we take a set of keys
|
||||
* that are good candidate for eviction across freeMemoryIfNeeded() calls.
|
||||
*
|
||||
* Entries inside the eviciton pool are taken ordered by idle time, putting
|
||||
* greater idle times to the right (ascending order).
|
||||
*
|
||||
* Empty entries have the key pointer set to NULL. */
|
||||
#define REDIS_EVICTION_POOL_SIZE 16
|
||||
struct evictionPoolEntry {
|
||||
unsigned long long idle; /* Object idle time. */
|
||||
sds key; /* Key name. */
|
||||
};
|
||||
|
||||
/* Redis database representation. There are multiple databases identified
|
||||
* by integers from 0 (the default database) up to the max configured
|
||||
* database. The database number is the 'id' field in the structure. */
|
||||
typedef struct redisDb {
|
||||
dict *dict; /* The keyspace for this DB */
|
||||
dict *expires; /* Timeout of keys with a timeout set */
|
||||
dict *blocking_keys; /* Keys with clients waiting for data (BLPOP) */
|
||||
dict *ready_keys; /* Blocked keys that received a PUSH */
|
||||
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
|
||||
int id;
|
||||
struct evictionPoolEntry *eviction_pool; /* Eviction pool of keys */
|
||||
int id; /* Database ID */
|
||||
long long avg_ttl; /* Average TTL, just for stats */
|
||||
} redisDb;
|
||||
|
||||
@@ -503,6 +530,7 @@ typedef struct redisClient {
|
||||
list *watched_keys; /* Keys WATCHED for MULTI/EXEC CAS */
|
||||
dict *pubsub_channels; /* channels a client is interested in (SUBSCRIBE) */
|
||||
list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */
|
||||
sds peerid; /* Cached peer ID. */
|
||||
|
||||
/* Response buffer */
|
||||
int bufpos;
|
||||
@@ -520,9 +548,9 @@ struct sharedObjectsStruct {
|
||||
*emptymultibulk, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr,
|
||||
*outofrangeerr, *noscripterr, *loadingerr, *slowscripterr, *bgsaveerr,
|
||||
*masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr,
|
||||
*oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk,
|
||||
*busykeyerr, *oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk,
|
||||
*unsubscribebulk, *psubscribebulk, *punsubscribebulk, *del, *rpop, *lpop,
|
||||
*lpush, *emptyscan,
|
||||
*lpush, *emptyscan, *minstring, *maxstring,
|
||||
*select[REDIS_SHARED_SELECT_CMDS],
|
||||
*integers[REDIS_SHARED_INTEGERS],
|
||||
*mbulkhdr[REDIS_SHARED_BULKHDR_LEN], /* "*<value>\r\n" */
|
||||
@@ -591,14 +619,14 @@ struct clusterState;
|
||||
|
||||
struct redisServer {
|
||||
/* General */
|
||||
pid_t pid; /* Main process pid. */
|
||||
char *configfile; /* Absolute config file path, or NULL */
|
||||
int hz; /* serverCron() calls frequency in hertz */
|
||||
redisDb *db;
|
||||
dict *commands; /* Command table */
|
||||
dict *orig_commands; /* Command table before command renaming. */
|
||||
aeEventLoop *el;
|
||||
unsigned lruclock:22; /* Clock incrementing every minute, for LRU */
|
||||
unsigned lruclock_padding:10;
|
||||
unsigned lruclock:REDIS_LRU_BITS; /* Clock for LRU eviction */
|
||||
int shutdown_asap; /* SHUTDOWN needed ASAP */
|
||||
int activerehashing; /* Incremental rehash in serverCron() */
|
||||
char *requirepass; /* Pass for AUTH command, or NULL */
|
||||
@@ -654,6 +682,7 @@ struct redisServer {
|
||||
long long slowlog_entry_id; /* SLOWLOG current entry ID */
|
||||
long long slowlog_log_slower_than; /* SLOWLOG time limit (to get logged) */
|
||||
unsigned long slowlog_max_len; /* SLOWLOG max number of items logged */
|
||||
size_t resident_set_size; /* RSS sampled in serverCron(). */
|
||||
/* The following two are used to track instantaneous "load" in terms
|
||||
* of operations per second. */
|
||||
long long ops_sec_last_sample_time; /* Timestamp of last sample (in ms) */
|
||||
@@ -691,6 +720,8 @@ struct redisServer {
|
||||
int aof_lastbgrewrite_status; /* REDIS_OK or REDIS_ERR */
|
||||
unsigned long aof_delayed_fsync; /* delayed AOF fsync() counter */
|
||||
int aof_rewrite_incremental_fsync;/* fsync incrementally while rewriting? */
|
||||
int aof_last_write_status; /* REDIS_OK or REDIS_ERR */
|
||||
int aof_last_write_errno; /* Valid if aof_last_write_status is ERR */
|
||||
/* RDB persistence */
|
||||
long long dirty; /* Changes to DB from the last save */
|
||||
long long dirty_before_bgsave; /* Used to restore dirty on failed BGSAVE */
|
||||
@@ -761,7 +792,7 @@ struct redisServer {
|
||||
list *clients_waiting_acks; /* Clients waiting in WAIT command. */
|
||||
int get_ack_from_slaves; /* If true we send REPLCONF GETACK. */
|
||||
/* Limits */
|
||||
unsigned int maxclients; /* Max number of simultaneous clients */
|
||||
int maxclients; /* Max number of simultaneous clients */
|
||||
unsigned long long maxmemory; /* Max number of memory bytes to use */
|
||||
int maxmemory_policy; /* Policy for key eviction */
|
||||
int maxmemory_samples; /* Pricision of random sampling */
|
||||
@@ -783,6 +814,7 @@ struct redisServer {
|
||||
size_t set_max_intset_entries;
|
||||
size_t zset_max_ziplist_entries;
|
||||
size_t zset_max_ziplist_value;
|
||||
size_t hll_sparse_max_bytes;
|
||||
time_t unixtime; /* Unix time sampled every cron cycle. */
|
||||
long long mstime; /* Like 'unixtime' but with milliseconds resolution. */
|
||||
/* Pubsub */
|
||||
@@ -796,6 +828,7 @@ struct redisServer {
|
||||
char *cluster_configfile; /* Cluster auto-generated config file name. */
|
||||
struct clusterState *cluster; /* State of the cluster */
|
||||
int cluster_migration_barrier; /* Cluster replicas migration barrier. */
|
||||
int cluster_slave_validity_factor; /* Slave max data age for failover. */
|
||||
/* Scripting */
|
||||
lua_State *lua; /* The Lua interpreter. We use just one for all clients */
|
||||
redisClient *lua_client; /* The "fake client" to query Redis from Lua */
|
||||
@@ -824,7 +857,7 @@ typedef struct pubsubPattern {
|
||||
} pubsubPattern;
|
||||
|
||||
typedef void redisCommandProc(redisClient *c);
|
||||
typedef int *redisGetKeysProc(struct redisCommand *cmd, robj **argv, int argc, int *numkeys, int flags);
|
||||
typedef int *redisGetKeysProc(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
struct redisCommand {
|
||||
char *name;
|
||||
redisCommandProc *proc;
|
||||
@@ -961,8 +994,8 @@ void *dupClientReplyValue(void *o);
|
||||
void getClientsMaxBuffers(unsigned long *longest_output_list,
|
||||
unsigned long *biggest_input_buffer);
|
||||
void formatPeerId(char *peerid, size_t peerid_len, char *ip, int port);
|
||||
int getClientPeerId(redisClient *client, char *peerid, size_t peerid_len);
|
||||
sds getClientInfoString(redisClient *client);
|
||||
char *getClientPeerId(redisClient *client);
|
||||
sds catClientInfoString(sds s, redisClient *client);
|
||||
sds getAllClientsInfoString(void);
|
||||
void rewriteClientCommandVector(redisClient *c, int argc, ...);
|
||||
void rewriteClientCommandArgument(redisClient *c, int i, robj *newval);
|
||||
@@ -976,6 +1009,7 @@ void disconnectSlaves(void);
|
||||
int listenToPort(int port, int *fds, int *count);
|
||||
void pauseClients(mstime_t duration);
|
||||
int clientsArePaused(void);
|
||||
int processEventsWhileBlocked(void);
|
||||
|
||||
#ifdef __GNUC__
|
||||
void addReplyErrorFormat(redisClient *c, const char *fmt, ...)
|
||||
@@ -1053,7 +1087,7 @@ char *strEncoding(int encoding);
|
||||
int compareStringObjects(robj *a, robj *b);
|
||||
int collateStringObjects(robj *a, robj *b);
|
||||
int equalStringObjects(robj *a, robj *b);
|
||||
unsigned long estimateObjectIdleTime(robj *o);
|
||||
unsigned long long estimateObjectIdleTime(robj *o);
|
||||
#define sdsEncodedObject(objptr) (objptr->encoding == REDIS_ENCODING_RAW || objptr->encoding == REDIS_ENCODING_EMBSTR)
|
||||
|
||||
/* Synchronous I/O with timeout */
|
||||
@@ -1104,19 +1138,25 @@ unsigned long aofRewriteBufferSize(void);
|
||||
|
||||
/* Sorted sets data type */
|
||||
|
||||
/* Struct to hold a inclusive/exclusive range spec. */
|
||||
/* Struct to hold a inclusive/exclusive range spec by score comparison. */
|
||||
typedef struct {
|
||||
double min, max;
|
||||
int minex, maxex; /* are min or max exclusive? */
|
||||
} zrangespec;
|
||||
|
||||
/* Struct to hold an inclusive/exclusive range spec by lexicographic comparison. */
|
||||
typedef struct {
|
||||
robj *min, *max; /* May be set to shared.(minstring|maxstring) */
|
||||
int minex, maxex; /* are min or max exclusive? */
|
||||
} zlexrangespec;
|
||||
|
||||
zskiplist *zslCreate(void);
|
||||
void zslFree(zskiplist *zsl);
|
||||
zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj);
|
||||
unsigned char *zzlInsert(unsigned char *zl, robj *ele, double score);
|
||||
int zslDelete(zskiplist *zsl, double score, robj *obj);
|
||||
zskiplistNode *zslFirstInRange(zskiplist *zsl, zrangespec range);
|
||||
zskiplistNode *zslLastInRange(zskiplist *zsl, zrangespec range);
|
||||
zskiplistNode *zslFirstInRange(zskiplist *zsl, zrangespec *range);
|
||||
zskiplistNode *zslLastInRange(zskiplist *zsl, zrangespec *range);
|
||||
double zzlGetScore(unsigned char *sptr);
|
||||
void zzlNext(unsigned char *zl, unsigned char **eptr, unsigned char **sptr);
|
||||
void zzlPrev(unsigned char *zl, unsigned char **eptr, unsigned char **sptr);
|
||||
@@ -1152,6 +1192,9 @@ void populateCommandTable(void);
|
||||
void resetCommandTableStats(void);
|
||||
void adjustOpenFilesLimit(void);
|
||||
void closeListeningSockets(int unlink_unix_socket);
|
||||
void updateCachedTime(void);
|
||||
void resetServerStats(void);
|
||||
unsigned int getLRUClock(void);
|
||||
|
||||
/* Set data type */
|
||||
robj *setTypeCreate(robj *value);
|
||||
@@ -1223,24 +1266,24 @@ void setKey(redisDb *db, robj *key, robj *val);
|
||||
int dbExists(redisDb *db, robj *key);
|
||||
robj *dbRandomKey(redisDb *db);
|
||||
int dbDelete(redisDb *db, robj *key);
|
||||
robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o);
|
||||
long long emptyDb(void(callback)(void*));
|
||||
int selectDb(redisClient *c, int id);
|
||||
void signalModifiedKey(redisDb *db, robj *key);
|
||||
void signalFlushedDb(int dbid);
|
||||
unsigned int getKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count);
|
||||
unsigned int countKeysInSlot(unsigned int hashslot);
|
||||
unsigned int delKeysInSlot(unsigned int hashslot);
|
||||
int verifyClusterConfigWithData(void);
|
||||
void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor);
|
||||
int parseScanCursorOrReply(redisClient *c, robj *o, unsigned long *cursor);
|
||||
|
||||
/* API to get key arguments from commands */
|
||||
#define REDIS_GETKEYS_ALL 0
|
||||
#define REDIS_GETKEYS_PRELOAD 1
|
||||
int *getKeysFromCommand(struct redisCommand *cmd, robj **argv, int argc, int *numkeys, int flags);
|
||||
int *getKeysFromCommand(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
void getKeysFreeResult(int *result);
|
||||
int *noPreloadGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags);
|
||||
int *renameGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags);
|
||||
int *zunionInterGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags);
|
||||
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);
|
||||
|
||||
/* Cluster */
|
||||
void clusterInit(void);
|
||||
@@ -1359,12 +1402,16 @@ void zincrbyCommand(redisClient *c);
|
||||
void zrangeCommand(redisClient *c);
|
||||
void zrangebyscoreCommand(redisClient *c);
|
||||
void zrevrangebyscoreCommand(redisClient *c);
|
||||
void zrangebylexCommand(redisClient *c);
|
||||
void zrevrangebylexCommand(redisClient *c);
|
||||
void zcountCommand(redisClient *c);
|
||||
void zlexcountCommand(redisClient *c);
|
||||
void zrevrangeCommand(redisClient *c);
|
||||
void zcardCommand(redisClient *c);
|
||||
void zremCommand(redisClient *c);
|
||||
void zscoreCommand(redisClient *c);
|
||||
void zremrangebyscoreCommand(redisClient *c);
|
||||
void zremrangebylexCommand(redisClient *c);
|
||||
void multiCommand(redisClient *c);
|
||||
void execCommand(redisClient *c);
|
||||
void discardCommand(redisClient *c);
|
||||
@@ -1417,8 +1464,14 @@ void scriptCommand(redisClient *c);
|
||||
void timeCommand(redisClient *c);
|
||||
void bitopCommand(redisClient *c);
|
||||
void bitcountCommand(redisClient *c);
|
||||
void bitposCommand(redisClient *c);
|
||||
void replconfCommand(redisClient *c);
|
||||
void waitCommand(redisClient *c);
|
||||
void pfselftestCommand(redisClient *c);
|
||||
void pfaddCommand(redisClient *c);
|
||||
void pfcountCommand(redisClient *c);
|
||||
void pfmergeCommand(redisClient *c);
|
||||
void pfdebugCommand(redisClient *c);
|
||||
|
||||
#if defined(__GNUC__)
|
||||
void *calloc(size_t count, size_t size) __attribute__ ((deprecated));
|
||||
|
||||
+7
-3
@@ -239,7 +239,6 @@ void replicationFeedMonitors(redisClient *c, list *monitors, int dictid, robj **
|
||||
int j;
|
||||
sds cmdrepr = sdsnew("+");
|
||||
robj *cmdobj;
|
||||
char peerid[REDIS_PEER_ID_LEN];
|
||||
struct timeval tv;
|
||||
|
||||
gettimeofday(&tv,NULL);
|
||||
@@ -249,8 +248,7 @@ void replicationFeedMonitors(redisClient *c, list *monitors, int dictid, robj **
|
||||
} else if (c->flags & REDIS_UNIX_SOCKET) {
|
||||
cmdrepr = sdscatprintf(cmdrepr,"[%d unix:%s] ",dictid,server.unixsocket);
|
||||
} else {
|
||||
getClientPeerId(c,peerid,sizeof(peerid));
|
||||
cmdrepr = sdscatprintf(cmdrepr,"[%d %s] ",dictid,peerid);
|
||||
cmdrepr = sdscatprintf(cmdrepr,"[%d %s] ",dictid,getClientPeerId(c));
|
||||
}
|
||||
|
||||
for (j = 0; j < argc; j++) {
|
||||
@@ -1387,6 +1385,12 @@ void replicationCacheMaster(redisClient *c) {
|
||||
/* Set fd to -1 so that we can safely call freeClient(c) later. */
|
||||
c->fd = -1;
|
||||
|
||||
/* Invalidate the Peer ID cache. */
|
||||
if (c->peerid) {
|
||||
sdsfree(c->peerid);
|
||||
c->peerid = NULL;
|
||||
}
|
||||
|
||||
/* Caching the master happens instead of the actual freeClient() call,
|
||||
* so make sure to adjust the replication state. This function will
|
||||
* also set server.master to NULL. */
|
||||
|
||||
+105
-26
@@ -200,13 +200,20 @@ void luaSortArray(lua_State *lua) {
|
||||
lua_pop(lua,1); /* Stack: array (sorted) */
|
||||
}
|
||||
|
||||
#define LUA_CMD_OBJCACHE_SIZE 32
|
||||
#define LUA_CMD_OBJCACHE_MAX_LEN 64
|
||||
int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
int j, argc = lua_gettop(lua);
|
||||
struct redisCommand *cmd;
|
||||
robj **argv;
|
||||
redisClient *c = server.lua_client;
|
||||
sds reply;
|
||||
|
||||
/* Cached across calls. */
|
||||
static robj **argv = NULL;
|
||||
static int argv_size = 0;
|
||||
static robj *cached_objects[LUA_CMD_OBJCACHE_SIZE];
|
||||
static int cached_objects_len[LUA_CMD_OBJCACHE_SIZE];
|
||||
|
||||
/* Require at least one argument */
|
||||
if (argc == 0) {
|
||||
luaPushError(lua,
|
||||
@@ -215,13 +222,37 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
}
|
||||
|
||||
/* Build the arguments vector */
|
||||
argv = zmalloc(sizeof(robj*)*argc);
|
||||
for (j = 0; j < argc; j++) {
|
||||
if (!lua_isstring(lua,j+1)) break;
|
||||
argv[j] = createStringObject((char*)lua_tostring(lua,j+1),
|
||||
lua_strlen(lua,j+1));
|
||||
if (!argv) {
|
||||
argv = zmalloc(sizeof(robj*)*argc);
|
||||
} else if (argv_size < argc) {
|
||||
argv = zrealloc(argv,sizeof(robj*)*argc);
|
||||
argv_size = argc;
|
||||
}
|
||||
|
||||
|
||||
for (j = 0; j < argc; j++) {
|
||||
char *obj_s;
|
||||
size_t obj_len;
|
||||
|
||||
obj_s = (char*)lua_tolstring(lua,j+1,&obj_len);
|
||||
if (obj_s == NULL) break; /* Not a string. */
|
||||
|
||||
/* Try to use a cached object. */
|
||||
if (j < LUA_CMD_OBJCACHE_SIZE && cached_objects[j] &&
|
||||
cached_objects_len[j] >= obj_len)
|
||||
{
|
||||
char *s = cached_objects[j]->ptr;
|
||||
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
|
||||
|
||||
argv[j] = cached_objects[j];
|
||||
cached_objects[j] = NULL;
|
||||
memcpy(s,obj_s,obj_len+1);
|
||||
sh->free += sh->len - obj_len;
|
||||
sh->len = obj_len;
|
||||
} else {
|
||||
argv[j] = createStringObject(obj_s, obj_len);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if one of the arguments passed by the Lua script
|
||||
* is not a string or an integer (lua_isstring() return true for
|
||||
* integers as well). */
|
||||
@@ -231,7 +262,6 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
decrRefCount(argv[j]);
|
||||
j--;
|
||||
}
|
||||
zfree(argv);
|
||||
luaPushError(lua,
|
||||
"Lua redis() command arguments must be strings or integers");
|
||||
return 1;
|
||||
@@ -306,16 +336,22 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
/* Convert the result of the Redis command into a suitable Lua type.
|
||||
* The first thing we need is to create a single string from the client
|
||||
* output buffers. */
|
||||
reply = sdsempty();
|
||||
if (c->bufpos) {
|
||||
reply = sdscatlen(reply,c->buf,c->bufpos);
|
||||
if (listLength(c->reply) == 0 && c->bufpos < REDIS_REPLY_CHUNK_BYTES) {
|
||||
/* This is a fast path for the common case of a reply inside the
|
||||
* client static buffer. Don't create an SDS string but just use
|
||||
* the client buffer directly. */
|
||||
c->buf[c->bufpos] = '\0';
|
||||
reply = c->buf;
|
||||
c->bufpos = 0;
|
||||
}
|
||||
while(listLength(c->reply)) {
|
||||
robj *o = listNodeValue(listFirst(c->reply));
|
||||
} else {
|
||||
reply = sdsnewlen(c->buf,c->bufpos);
|
||||
c->bufpos = 0;
|
||||
while(listLength(c->reply)) {
|
||||
robj *o = listNodeValue(listFirst(c->reply));
|
||||
|
||||
reply = sdscatlen(reply,o->ptr,sdslen(o->ptr));
|
||||
listDelNode(c->reply,listFirst(c->reply));
|
||||
reply = sdscatlen(reply,o->ptr,sdslen(o->ptr));
|
||||
listDelNode(c->reply,listFirst(c->reply));
|
||||
}
|
||||
}
|
||||
if (raise_error && reply[0] != '-') raise_error = 0;
|
||||
redisProtocolToLuaType(lua,reply);
|
||||
@@ -325,15 +361,38 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
(reply[0] == '*' && reply[1] != '-')) {
|
||||
luaSortArray(lua);
|
||||
}
|
||||
sdsfree(reply);
|
||||
if (reply != c->buf) sdsfree(reply);
|
||||
c->reply_bytes = 0;
|
||||
|
||||
cleanup:
|
||||
/* Clean up. Command code may have changed argv/argc so we use the
|
||||
* argv/argc of the client instead of the local variables. */
|
||||
for (j = 0; j < c->argc; j++)
|
||||
decrRefCount(c->argv[j]);
|
||||
zfree(c->argv);
|
||||
for (j = 0; j < c->argc; j++) {
|
||||
robj *o = c->argv[j];
|
||||
|
||||
/* Try to cache the object in the cached_objects array.
|
||||
* The object must be small, SDS-encoded, and with refcount = 1
|
||||
* (we must be the only owner) for us to cache it. */
|
||||
if (j < LUA_CMD_OBJCACHE_SIZE &&
|
||||
o->refcount == 1 &&
|
||||
(o->encoding == REDIS_ENCODING_RAW ||
|
||||
o->encoding == REDIS_ENCODING_EMBSTR) &&
|
||||
sdslen(o->ptr) <= LUA_CMD_OBJCACHE_MAX_LEN)
|
||||
{
|
||||
struct sdshdr *sh = (void*)(((char*)(o->ptr))-(sizeof(struct sdshdr)));
|
||||
|
||||
if (cached_objects[j]) decrRefCount(cached_objects[j]);
|
||||
cached_objects[j] = o;
|
||||
cached_objects_len[j] = sh->free + sh->len;
|
||||
} else {
|
||||
decrRefCount(o);
|
||||
}
|
||||
}
|
||||
|
||||
if (c->argv != argv) {
|
||||
zfree(c->argv);
|
||||
argv = NULL;
|
||||
}
|
||||
|
||||
if (raise_error) {
|
||||
/* If we are here we should have an error in the stack, in the
|
||||
@@ -452,8 +511,7 @@ void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
|
||||
* here when the EVAL command will return. */
|
||||
aeDeleteFileEvent(server.el, server.lua_caller->fd, AE_READABLE);
|
||||
}
|
||||
if (server.lua_timedout)
|
||||
aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
|
||||
if (server.lua_timedout) processEventsWhileBlocked();
|
||||
if (server.lua_kill) {
|
||||
redisLog(REDIS_WARNING,"Lua script killed by user with SCRIPT KILL.");
|
||||
lua_pushstring(lua,"Script killed by user with SCRIPT KILL...");
|
||||
@@ -476,7 +534,7 @@ void luaLoadLibraries(lua_State *lua) {
|
||||
luaLoadLib(lua, LUA_TABLIBNAME, luaopen_table);
|
||||
luaLoadLib(lua, LUA_STRLIBNAME, luaopen_string);
|
||||
luaLoadLib(lua, LUA_MATHLIBNAME, luaopen_math);
|
||||
luaLoadLib(lua, LUA_DBLIBNAME, luaopen_debug);
|
||||
luaLoadLib(lua, LUA_DBLIBNAME, luaopen_debug);
|
||||
luaLoadLib(lua, "cjson", luaopen_cjson);
|
||||
luaLoadLib(lua, "struct", luaopen_struct);
|
||||
luaLoadLib(lua, "cmsgpack", luaopen_cmsgpack);
|
||||
@@ -856,8 +914,12 @@ void evalGenericCommand(redisClient *c, int evalsha) {
|
||||
int j;
|
||||
char *sha = c->argv[1]->ptr;
|
||||
|
||||
/* Convert to lowercase. We don't use tolower since the function
|
||||
* managed to always show up in the profiler output consuming
|
||||
* a non trivial amount of time. */
|
||||
for (j = 0; j < 40; j++)
|
||||
funcname[j+2] = tolower(sha[j]);
|
||||
funcname[j+2] = (sha[j] >= 'A' && sha[j] <= 'Z') ?
|
||||
sha[j]+('a'-'A') : sha[j];
|
||||
funcname[42] = '\0';
|
||||
}
|
||||
|
||||
@@ -894,7 +956,7 @@ void evalGenericCommand(redisClient *c, int evalsha) {
|
||||
|
||||
/* Select the right DB in the context of the Lua client */
|
||||
selectDb(server.lua_client,c->db->id);
|
||||
|
||||
|
||||
/* Set a hook in order to be able to stop the script execution if it
|
||||
* is running for too much time.
|
||||
* We set the hook only if the time limit is enabled as the hook will
|
||||
@@ -923,7 +985,23 @@ void evalGenericCommand(redisClient *c, int evalsha) {
|
||||
}
|
||||
server.lua_caller = NULL;
|
||||
selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */
|
||||
lua_gc(lua,LUA_GCSTEP,1);
|
||||
|
||||
/* Call the Lua garbage collector from time to time to avoid a
|
||||
* full cycle performed by Lua, which adds too latency.
|
||||
*
|
||||
* The call is performed every LUA_GC_CYCLE_PERIOD executed commands
|
||||
* (and for LUA_GC_CYCLE_PERIOD collection steps) because calling it
|
||||
* for every command uses too much CPU. */
|
||||
#define LUA_GC_CYCLE_PERIOD 50
|
||||
{
|
||||
static long gc_count = 0;
|
||||
|
||||
gc_count++;
|
||||
if (gc_count == LUA_GC_CYCLE_PERIOD) {
|
||||
lua_gc(lua,LUA_GCSTEP,LUA_GC_CYCLE_PERIOD);
|
||||
gc_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (err) {
|
||||
addReplyErrorFormat(c,"Error running script (call to %s): %s\n",
|
||||
@@ -958,6 +1036,7 @@ void evalGenericCommand(redisClient *c, int evalsha) {
|
||||
rewriteClientCommandArgument(c,0,
|
||||
resetRefCount(createStringObject("EVAL",4)));
|
||||
rewriteClientCommandArgument(c,1,script);
|
||||
forceCommandPropagation(c,REDIS_PROPAGATE_REPL|REDIS_PROPAGATE_AOF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,27 +289,118 @@ sds sdscpy(sds s, const char *t) {
|
||||
return sdscpylen(s, t, strlen(t));
|
||||
}
|
||||
|
||||
/* Helper for sdscatlonglong() doing the actual number -> string
|
||||
* conversion. 's' must point to a string with room for at least
|
||||
* SDS_LLSTR_SIZE bytes.
|
||||
*
|
||||
* The function returns the lenght of the null-terminated string
|
||||
* representation stored at 's'. */
|
||||
#define SDS_LLSTR_SIZE 21
|
||||
int sdsll2str(char *s, long long value) {
|
||||
char *p, aux;
|
||||
unsigned long long v;
|
||||
size_t l;
|
||||
|
||||
/* Generate the string representation, this method produces
|
||||
* an reversed string. */
|
||||
v = (value < 0) ? -value : value;
|
||||
p = s;
|
||||
do {
|
||||
*p++ = '0'+(v%10);
|
||||
v /= 10;
|
||||
} while(v);
|
||||
if (value < 0) *p++ = '-';
|
||||
|
||||
/* Compute length and add null term. */
|
||||
l = p-s;
|
||||
*p = '\0';
|
||||
|
||||
/* Reverse the string. */
|
||||
p--;
|
||||
while(s < p) {
|
||||
aux = *s;
|
||||
*s = *p;
|
||||
*p = aux;
|
||||
s++;
|
||||
p--;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
/* Identical sdsll2str(), but for unsigned long long type. */
|
||||
int sdsull2str(char *s, unsigned long long v) {
|
||||
char *p, aux;
|
||||
size_t l;
|
||||
|
||||
/* Generate the string representation, this method produces
|
||||
* an reversed string. */
|
||||
p = s;
|
||||
do {
|
||||
*p++ = '0'+(v%10);
|
||||
v /= 10;
|
||||
} while(v);
|
||||
|
||||
/* Compute length and add null term. */
|
||||
l = p-s;
|
||||
*p = '\0';
|
||||
|
||||
/* Reverse the string. */
|
||||
p--;
|
||||
while(s < p) {
|
||||
aux = *s;
|
||||
*s = *p;
|
||||
*p = aux;
|
||||
s++;
|
||||
p--;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
/* Create an sds string from a long long value. It is much faster than:
|
||||
*
|
||||
* sdscatprintf(sdsempty(),"%lld\n", value);
|
||||
*/
|
||||
sds sdsfromlonglong(long long value) {
|
||||
char buf[SDS_LLSTR_SIZE];
|
||||
int len = sdsll2str(buf,value);
|
||||
|
||||
return sdsnewlen(buf,len);
|
||||
}
|
||||
|
||||
/* Like sdscatpritf() but gets va_list instead of being variadic. */
|
||||
sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
|
||||
va_list cpy;
|
||||
char *buf, *t;
|
||||
size_t buflen = 16;
|
||||
char staticbuf[1024], *buf = staticbuf, *t;
|
||||
size_t buflen = strlen(fmt)*2;
|
||||
|
||||
while(1) {
|
||||
/* We try to start using a static buffer for speed.
|
||||
* If not possible we revert to heap allocation. */
|
||||
if (buflen > sizeof(staticbuf)) {
|
||||
buf = zmalloc(buflen);
|
||||
if (buf == NULL) return NULL;
|
||||
} else {
|
||||
buflen = sizeof(staticbuf);
|
||||
}
|
||||
|
||||
/* Try with buffers two times bigger every time we fail to
|
||||
* fit the string in the current buffer size. */
|
||||
while(1) {
|
||||
buf[buflen-2] = '\0';
|
||||
va_copy(cpy,ap);
|
||||
vsnprintf(buf, buflen, fmt, cpy);
|
||||
if (buf[buflen-2] != '\0') {
|
||||
zfree(buf);
|
||||
if (buf != staticbuf) zfree(buf);
|
||||
buflen *= 2;
|
||||
buf = zmalloc(buflen);
|
||||
if (buf == NULL) return NULL;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Finally concat the obtained string to the SDS string and return it. */
|
||||
t = sdscat(s, buf);
|
||||
zfree(buf);
|
||||
if (buf != staticbuf) zfree(buf);
|
||||
return t;
|
||||
}
|
||||
|
||||
@@ -338,6 +429,122 @@ sds sdscatprintf(sds s, const char *fmt, ...) {
|
||||
return t;
|
||||
}
|
||||
|
||||
/* This function is similar to sdscatprintf, but much faster as it does
|
||||
* not rely on sprintf() family functions implemented by the libc that
|
||||
* are often very slow. Moreover directly handling the sds string as
|
||||
* new data is concatenated provides a performance improvement.
|
||||
*
|
||||
* However this function only handles an incompatible subset of printf-alike
|
||||
* format specifiers:
|
||||
*
|
||||
* %s - C String
|
||||
* %S - SDS string
|
||||
* %i - signed int
|
||||
* %I - 64 bit signed integer (long long, int64_t)
|
||||
* %u - unsigned int
|
||||
* %U - 64 bit unsigned integer (unsigned long long, uint64_t)
|
||||
* %% - Verbatim "%" character.
|
||||
*/
|
||||
sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
size_t initlen = sdslen(s);
|
||||
const char *f = fmt;
|
||||
int i;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap,fmt);
|
||||
f = fmt; /* Next format specifier byte to process. */
|
||||
i = initlen; /* Position of the next byte to write to dest str. */
|
||||
while(*f) {
|
||||
char next, *str;
|
||||
size_t l;
|
||||
long long num;
|
||||
unsigned long long unum;
|
||||
|
||||
/* Make sure there is always space for at least 1 char. */
|
||||
if (sh->free == 0) {
|
||||
s = sdsMakeRoomFor(s,1);
|
||||
sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
}
|
||||
|
||||
switch(*f) {
|
||||
case '%':
|
||||
next = *(f+1);
|
||||
f++;
|
||||
switch(next) {
|
||||
case 's':
|
||||
case 'S':
|
||||
str = va_arg(ap,char*);
|
||||
l = (next == 's') ? strlen(str) : sdslen(str);
|
||||
if (sh->free < l) {
|
||||
s = sdsMakeRoomFor(s,l);
|
||||
sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
}
|
||||
memcpy(s+i,str,l);
|
||||
sh->len += l;
|
||||
sh->free -= l;
|
||||
i += l;
|
||||
break;
|
||||
case 'i':
|
||||
case 'I':
|
||||
if (next == 'i')
|
||||
num = va_arg(ap,int);
|
||||
else
|
||||
num = va_arg(ap,long long);
|
||||
{
|
||||
char buf[SDS_LLSTR_SIZE];
|
||||
l = sdsll2str(buf,num);
|
||||
if (sh->free < l) {
|
||||
s = sdsMakeRoomFor(s,l);
|
||||
sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
}
|
||||
memcpy(s+i,buf,l);
|
||||
sh->len += l;
|
||||
sh->free -= l;
|
||||
i += l;
|
||||
}
|
||||
break;
|
||||
case 'u':
|
||||
case 'U':
|
||||
if (next == 'u')
|
||||
unum = va_arg(ap,unsigned int);
|
||||
else
|
||||
unum = va_arg(ap,unsigned long long);
|
||||
{
|
||||
char buf[SDS_LLSTR_SIZE];
|
||||
l = sdsull2str(buf,unum);
|
||||
if (sh->free < l) {
|
||||
s = sdsMakeRoomFor(s,l);
|
||||
sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
}
|
||||
memcpy(s+i,buf,l);
|
||||
sh->len += l;
|
||||
sh->free -= l;
|
||||
i += l;
|
||||
}
|
||||
break;
|
||||
default: /* Handle %% and generally %<unknown>. */
|
||||
s[i++] = next;
|
||||
sh->len += 1;
|
||||
sh->free -= 1;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
s[i++] = *f;
|
||||
sh->len += 1;
|
||||
sh->free -= 1;
|
||||
break;
|
||||
}
|
||||
f++;
|
||||
}
|
||||
va_end(ap);
|
||||
|
||||
/* Add null-term */
|
||||
s[i] = '\0';
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Remove the part of the string from left and from right composed just of
|
||||
* contiguous characters found in 'cset', that is a null terminted C string.
|
||||
*
|
||||
@@ -525,25 +732,6 @@ void sdsfreesplitres(sds *tokens, int count) {
|
||||
zfree(tokens);
|
||||
}
|
||||
|
||||
/* Create an sds string from a long long value. It is much faster than:
|
||||
*
|
||||
* sdscatprintf(sdsempty(),"%lld\n", value);
|
||||
*/
|
||||
sds sdsfromlonglong(long long value) {
|
||||
char buf[32], *p;
|
||||
unsigned long long v;
|
||||
|
||||
v = (value < 0) ? -value : value;
|
||||
p = buf+31; /* point to the last character */
|
||||
do {
|
||||
*p-- = '0'+(v%10);
|
||||
v /= 10;
|
||||
} while(v);
|
||||
if (value < 0) *p-- = '-';
|
||||
p++;
|
||||
return sdsnewlen(p,32-(p-buf));
|
||||
}
|
||||
|
||||
/* Append to the sds string "s" an escaped string representation where
|
||||
* all the non-printable characters (tested with isprint()) are turned into
|
||||
* escapes in the form "\n\r\a...." or "\x<hex-number>".
|
||||
@@ -774,6 +962,7 @@ sds sdsjoin(char **argv, int argc, char *sep) {
|
||||
#ifdef SDS_TEST_MAIN
|
||||
#include <stdio.h>
|
||||
#include "testhelp.h"
|
||||
#include "limits.h"
|
||||
|
||||
int main(void) {
|
||||
{
|
||||
@@ -804,39 +993,61 @@ int main(void) {
|
||||
sdsfree(x);
|
||||
x = sdscatprintf(sdsempty(),"%d",123);
|
||||
test_cond("sdscatprintf() seems working in the base case",
|
||||
sdslen(x) == 3 && memcmp(x,"123\0",4) ==0)
|
||||
sdslen(x) == 3 && memcmp(x,"123\0",4) == 0)
|
||||
|
||||
sdsfree(x);
|
||||
x = sdstrim(sdsnew("xxciaoyyy"),"xy");
|
||||
x = sdsnew("--");
|
||||
x = sdscatfmt(x, "Hello %s World %I,%I--", "Hi!", LLONG_MIN,LLONG_MAX);
|
||||
test_cond("sdscatfmt() seems working in the base case",
|
||||
sdslen(x) == 60 &&
|
||||
memcmp(x,"--Hello Hi! World -9223372036854775808,"
|
||||
"9223372036854775807--",60) == 0)
|
||||
|
||||
sdsfree(x);
|
||||
x = sdsnew("--");
|
||||
x = sdscatfmt(x, "%u,%U--", UINT_MAX, ULLONG_MAX);
|
||||
test_cond("sdscatfmt() seems working with unsigned numbers",
|
||||
sdslen(x) == 35 &&
|
||||
memcmp(x,"--4294967295,18446744073709551615--",35) == 0)
|
||||
|
||||
sdsfree(x);
|
||||
x = sdsnew("xxciaoyyy");
|
||||
sdstrim(x,"xy");
|
||||
test_cond("sdstrim() correctly trims characters",
|
||||
sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0)
|
||||
|
||||
y = sdsrange(sdsdup(x),1,1);
|
||||
y = sdsdup(x);
|
||||
sdsrange(y,1,1);
|
||||
test_cond("sdsrange(...,1,1)",
|
||||
sdslen(y) == 1 && memcmp(y,"i\0",2) == 0)
|
||||
|
||||
sdsfree(y);
|
||||
y = sdsrange(sdsdup(x),1,-1);
|
||||
y = sdsdup(x);
|
||||
sdsrange(y,1,-1);
|
||||
test_cond("sdsrange(...,1,-1)",
|
||||
sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0)
|
||||
|
||||
sdsfree(y);
|
||||
y = sdsrange(sdsdup(x),-2,-1);
|
||||
y = sdsdup(x);
|
||||
sdsrange(y,-2,-1);
|
||||
test_cond("sdsrange(...,-2,-1)",
|
||||
sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0)
|
||||
|
||||
sdsfree(y);
|
||||
y = sdsrange(sdsdup(x),2,1);
|
||||
y = sdsdup(x);
|
||||
sdsrange(y,2,1);
|
||||
test_cond("sdsrange(...,2,1)",
|
||||
sdslen(y) == 0 && memcmp(y,"\0",1) == 0)
|
||||
|
||||
sdsfree(y);
|
||||
y = sdsrange(sdsdup(x),1,100);
|
||||
y = sdsdup(x);
|
||||
sdsrange(y,1,100);
|
||||
test_cond("sdsrange(...,1,100)",
|
||||
sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0)
|
||||
|
||||
sdsfree(y);
|
||||
y = sdsrange(sdsdup(x),100,100);
|
||||
y = sdsdup(x);
|
||||
sdsrange(y,100,100);
|
||||
test_cond("sdsrange(...,100,100)",
|
||||
sdslen(y) == 0 && memcmp(y,"\0",1) == 0)
|
||||
|
||||
@@ -858,6 +1069,13 @@ int main(void) {
|
||||
y = sdsnew("bar");
|
||||
test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0)
|
||||
|
||||
sdsfree(y);
|
||||
sdsfree(x);
|
||||
x = sdsnewlen("\a\n\0foo\r",7);
|
||||
y = sdscatrepr(sdsempty(),x,sdslen(x));
|
||||
test_cond("sdscatrepr(...data...)",
|
||||
memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0)
|
||||
|
||||
{
|
||||
int oldfree;
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ sds sdscatprintf(sds s, const char *fmt, ...)
|
||||
sds sdscatprintf(sds s, const char *fmt, ...);
|
||||
#endif
|
||||
|
||||
sds sdscatfmt(sds s, char const *fmt, ...);
|
||||
sds sdstrim(sds s, const char *cset);
|
||||
void sdsrange(sds s, int start, int end);
|
||||
void sdsupdatelen(sds s);
|
||||
|
||||
+350
-150
@@ -78,12 +78,13 @@ typedef struct sentinelAddr {
|
||||
#define SENTINEL_TILT_TRIGGER 2000
|
||||
#define SENTINEL_TILT_PERIOD (SENTINEL_PING_PERIOD*30)
|
||||
#define SENTINEL_DEFAULT_SLAVE_PRIORITY 100
|
||||
#define SENTINEL_SLAVE_RECONF_RETRY_PERIOD 10000
|
||||
#define SENTINEL_SLAVE_RECONF_TIMEOUT 10000
|
||||
#define SENTINEL_DEFAULT_PARALLEL_SYNCS 1
|
||||
#define SENTINEL_MIN_LINK_RECONNECT_PERIOD 15000
|
||||
#define SENTINEL_DEFAULT_FAILOVER_TIMEOUT (60*3*1000)
|
||||
#define SENTINEL_MAX_PENDING_COMMANDS 100
|
||||
#define SENTINEL_ELECTION_TIMEOUT 10000
|
||||
#define SENTINEL_MAX_DESYNC 1000
|
||||
|
||||
/* Failover machine different states. */
|
||||
#define SENTINEL_FAILOVER_STATE_NONE 0 /* No failover in progress. */
|
||||
@@ -128,6 +129,10 @@ typedef struct sentinelRedisInstance {
|
||||
mstime_t pc_last_activity; /* Last time we received any message. */
|
||||
mstime_t last_avail_time; /* Last time the instance replied to ping with
|
||||
a reply we consider valid. */
|
||||
mstime_t last_ping_time; /* Last time a pending ping was sent in the
|
||||
context of the current command connection
|
||||
with the instance. 0 if still not sent or
|
||||
if pong already received. */
|
||||
mstime_t last_pong_time; /* Last time the instance replied to ping,
|
||||
whatever the reply was. That's used to check
|
||||
if the link is idle and must be reconnected. */
|
||||
@@ -178,6 +183,8 @@ typedef struct sentinelRedisInstance {
|
||||
mstime_t failover_state_change_time;
|
||||
mstime_t failover_start_time; /* Last failover attempt start time. */
|
||||
mstime_t failover_timeout; /* Max time to refresh failover state. */
|
||||
mstime_t failover_delay_logged; /* For what failover_start_time value we
|
||||
logged the failover delay. */
|
||||
struct sentinelRedisInstance *promoted_slave; /* Promoted slave instance. */
|
||||
/* Scripts executed to notify admin or reconfigure clients: when they
|
||||
* are set to NULL no script is executed. */
|
||||
@@ -327,6 +334,8 @@ void sentinelDiscardReplyCallback(redisAsyncContext *c, void *reply, void *privd
|
||||
int sentinelSendSlaveOf(sentinelRedisInstance *ri, char *host, int port);
|
||||
char *sentinelVoteLeader(sentinelRedisInstance *master, uint64_t req_epoch, char *req_runid, uint64_t *leader_epoch);
|
||||
void sentinelFlushConfig(void);
|
||||
void sentinelGenerateInitialMonitorEvents(void);
|
||||
int sentinelSendPing(sentinelRedisInstance *ri);
|
||||
|
||||
/* ========================= Dictionary types =============================== */
|
||||
|
||||
@@ -369,6 +378,7 @@ dictType leaderVotesDictType = {
|
||||
void sentinelCommand(redisClient *c);
|
||||
void sentinelInfoCommand(redisClient *c);
|
||||
void sentinelSetCommand(redisClient *c);
|
||||
void sentinelPublishCommand(redisClient *c);
|
||||
|
||||
struct redisCommand sentinelcmds[] = {
|
||||
{"ping",pingCommand,1,"",0,NULL,0,0,0,0,0},
|
||||
@@ -377,6 +387,7 @@ struct redisCommand sentinelcmds[] = {
|
||||
{"unsubscribe",unsubscribeCommand,-1,"",0,NULL,0,0,0,0,0},
|
||||
{"psubscribe",psubscribeCommand,-2,"",0,NULL,0,0,0,0,0},
|
||||
{"punsubscribe",punsubscribeCommand,-1,"",0,NULL,0,0,0,0,0},
|
||||
{"publish",sentinelPublishCommand,3,"",0,NULL,0,0,0,0,0},
|
||||
{"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0},
|
||||
{"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0}
|
||||
};
|
||||
@@ -417,10 +428,20 @@ void initSentinel(void) {
|
||||
void sentinelIsRunning(void) {
|
||||
redisLog(REDIS_WARNING,"Sentinel runid is %s", server.runid);
|
||||
|
||||
if (server.configfile == NULL || access(server.configfile,W_OK) == -1) {
|
||||
redisLog(REDIS_WARNING,"Sentinel started without a config file, or config file not writable. Exiting...");
|
||||
if (server.configfile == NULL) {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Sentinel started without a config file. Exiting...");
|
||||
exit(1);
|
||||
} else if (access(server.configfile,W_OK) == -1) {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Sentinel config file %s is not writable: %s. Exiting...",
|
||||
server.configfile,strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* We want to generate a +monitor event for every configured master
|
||||
* at startup. */
|
||||
sentinelGenerateInitialMonitorEvents();
|
||||
}
|
||||
|
||||
/* ============================== sentinelAddr ============================== */
|
||||
@@ -472,7 +493,7 @@ int sentinelAddrIsEqual(sentinelAddr *a, sentinelAddr *b) {
|
||||
/* =========================== Events notification ========================== */
|
||||
|
||||
/* Send an event to log, pub/sub, user notification script.
|
||||
*
|
||||
*
|
||||
* 'level' is the log level for logging. Only REDIS_WARNING events will trigger
|
||||
* the execution of the user notification script.
|
||||
*
|
||||
@@ -552,6 +573,22 @@ void sentinelEvent(int level, char *type, sentinelRedisInstance *ri,
|
||||
}
|
||||
}
|
||||
|
||||
/* This function is called only at startup and is used to generate a
|
||||
* +monitor event for every configured master. The same events are also
|
||||
* generated when a master to monitor is added at runtime via the
|
||||
* SENTINEL MONITOR command. */
|
||||
void sentinelGenerateInitialMonitorEvents(void) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
|
||||
di = dictGetIterator(sentinel.masters);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sentinelRedisInstance *ri = dictGetVal(de);
|
||||
sentinelEvent(REDIS_WARNING,"+monitor",ri,"%@ quorum %d",ri->quorum);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
/* ============================ script execution ============================ */
|
||||
|
||||
/* Release a script job structure and all the associated data. */
|
||||
@@ -579,7 +616,7 @@ void sentinelScheduleScriptExecution(char *path, ...) {
|
||||
}
|
||||
va_end(ap);
|
||||
argv[0] = sdsnew(path);
|
||||
|
||||
|
||||
sj = zmalloc(sizeof(*sj));
|
||||
sj->flags = SENTINEL_SCRIPT_NONE;
|
||||
sj->retry_num = 0;
|
||||
@@ -705,7 +742,7 @@ void sentinelCollectTerminatedScripts(void) {
|
||||
if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);
|
||||
sentinelEvent(REDIS_DEBUG,"-script-child",NULL,"%ld %d %d",
|
||||
(long)pid, exitcode, bysignal);
|
||||
|
||||
|
||||
ln = sentinelGetScriptListNodeByPid(pid);
|
||||
if (ln == NULL) {
|
||||
redisLog(REDIS_WARNING,"wait3() returned a pid (%ld) we can't find in our scripts execution queue!", (long)pid);
|
||||
@@ -895,6 +932,11 @@ sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *
|
||||
ri->cc_conn_time = 0;
|
||||
ri->pc_conn_time = 0;
|
||||
ri->pc_last_activity = 0;
|
||||
/* We set the last_ping_time to "now" even if we actually don't have yet
|
||||
* a connection with the node, nor we sent a ping.
|
||||
* This is useful to detect a timeout in case we'll not be able to connect
|
||||
* with the node at all. */
|
||||
ri->last_ping_time = mstime();
|
||||
ri->last_avail_time = mstime();
|
||||
ri->last_pong_time = mstime();
|
||||
ri->last_pub_time = mstime();
|
||||
@@ -927,6 +969,7 @@ sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *
|
||||
ri->failover_state_change_time = 0;
|
||||
ri->failover_start_time = 0;
|
||||
ri->failover_timeout = SENTINEL_DEFAULT_FAILOVER_TIMEOUT;
|
||||
ri->failover_delay_logged = 0;
|
||||
ri->promoted_slave = NULL;
|
||||
ri->notification_script = NULL;
|
||||
ri->client_reconfig_script = NULL;
|
||||
@@ -977,7 +1020,7 @@ sentinelRedisInstance *sentinelRedisInstanceLookupSlave(
|
||||
{
|
||||
sds key;
|
||||
sentinelRedisInstance *slave;
|
||||
|
||||
|
||||
redisAssert(ri->flags & SRI_MASTER);
|
||||
key = sdscatprintf(sdsempty(),
|
||||
strchr(ip,':') ? "[%s]:%d" : "%s:%d",
|
||||
@@ -997,7 +1040,7 @@ const char *sentinelRedisInstanceTypeStr(sentinelRedisInstance *ri) {
|
||||
|
||||
/* This function removes all the instances found in the dictionary of
|
||||
* sentinels in the specified 'master', having either:
|
||||
*
|
||||
*
|
||||
* 1) The same ip/port as specified.
|
||||
* 2) The same runid.
|
||||
*
|
||||
@@ -1131,6 +1174,7 @@ void sentinelResetMaster(sentinelRedisInstance *ri, int flags) {
|
||||
sdsfree(ri->slave_master_host);
|
||||
ri->runid = NULL;
|
||||
ri->slave_master_host = NULL;
|
||||
ri->last_ping_time = mstime();
|
||||
ri->last_avail_time = mstime();
|
||||
ri->last_pong_time = mstime();
|
||||
ri->role_reported_time = mstime();
|
||||
@@ -1190,7 +1234,7 @@ int sentinelResetMasterAndChangeAddress(sentinelRedisInstance *master, char *ip,
|
||||
slave->addr->port);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
|
||||
|
||||
/* If we are switching to a different address, include the old address
|
||||
* as a slave as well, so that we'll be able to sense / reconfigure
|
||||
* the old master. */
|
||||
@@ -1257,6 +1301,24 @@ sentinelAddr *sentinelGetCurrentMasterAddress(sentinelRedisInstance *master) {
|
||||
}
|
||||
}
|
||||
|
||||
/* This function sets the down_after_period field value in 'master' to all
|
||||
* the slaves and sentinel instances connected to this master. */
|
||||
void sentinelPropagateDownAfterPeriod(sentinelRedisInstance *master) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
int j;
|
||||
dict *d[] = {master->slaves, master->sentinels, NULL};
|
||||
|
||||
for (j = 0; d[j]; j++) {
|
||||
di = dictGetIterator(d[j]);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sentinelRedisInstance *ri = dictGetVal(de);
|
||||
ri->down_after_period = master->down_after_period;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================ Config handling ============================= */
|
||||
char *sentinelHandleConfiguration(char **argv, int argc) {
|
||||
sentinelRedisInstance *ri;
|
||||
@@ -1282,6 +1344,7 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
|
||||
ri->down_after_period = atoi(argv[2]);
|
||||
if (ri->down_after_period <= 0)
|
||||
return "negative or zero time parameter.";
|
||||
sentinelPropagateDownAfterPeriod(ri);
|
||||
} else if (!strcasecmp(argv[0],"failover-timeout") && argc == 3) {
|
||||
/* failover-timeout <name> <milliseconds> */
|
||||
ri = sentinelGetMasterByName(argv[1]);
|
||||
@@ -1314,13 +1377,26 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
|
||||
ri = sentinelGetMasterByName(argv[1]);
|
||||
if (!ri) return "No such master with specified name.";
|
||||
ri->auth_pass = sdsnew(argv[2]);
|
||||
} else if (!strcasecmp(argv[0],"current-epoch") && argc == 2) {
|
||||
/* current-epoch <epoch> */
|
||||
unsigned long long current_epoch = strtoull(argv[1],NULL,10);
|
||||
if (current_epoch > sentinel.current_epoch)
|
||||
sentinel.current_epoch = current_epoch;
|
||||
} else if (!strcasecmp(argv[0],"config-epoch") && argc == 3) {
|
||||
/* config-epoch <name> <epoch> */
|
||||
ri = sentinelGetMasterByName(argv[1]);
|
||||
if (!ri) return "No such master with specified name.";
|
||||
ri->config_epoch = strtoull(argv[2],NULL,10);
|
||||
/* The following update of current_epoch is not really useful as
|
||||
* now the current epoch is persisted on the config file, but
|
||||
* we leave this check here for redundancy. */
|
||||
if (ri->config_epoch > sentinel.current_epoch)
|
||||
sentinel.current_epoch = ri->config_epoch;
|
||||
} else if (!strcasecmp(argv[0],"leader-epoch") && argc == 3) {
|
||||
/* leader-epoch <name> <epoch> */
|
||||
ri = sentinelGetMasterByName(argv[1]);
|
||||
if (!ri) return "No such master with specified name.";
|
||||
ri->leader_epoch = strtoull(argv[2],NULL,10);
|
||||
} else if (!strcasecmp(argv[0],"known-slave") && argc == 4) {
|
||||
sentinelRedisInstance *slave;
|
||||
|
||||
@@ -1359,13 +1435,13 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
|
||||
void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
|
||||
dictIterator *di, *di2;
|
||||
dictEntry *de;
|
||||
sds line;
|
||||
|
||||
/* For every master emit a "sentinel monitor" config entry. */
|
||||
di = dictGetIterator(sentinel.masters);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sentinelRedisInstance *master, *ri;
|
||||
sentinelAddr *master_addr;
|
||||
sds line;
|
||||
|
||||
/* sentinel monitor */
|
||||
master = dictGetVal(de);
|
||||
@@ -1429,6 +1505,12 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
|
||||
master->name, (unsigned long long) master->config_epoch);
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,1);
|
||||
|
||||
/* sentinel leader-epoch */
|
||||
line = sdscatprintf(sdsempty(),
|
||||
"sentinel leader-epoch %s %llu",
|
||||
master->name, (unsigned long long) master->leader_epoch);
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,1);
|
||||
|
||||
/* sentinel known-slave */
|
||||
di2 = dictGetIterator(master->slaves);
|
||||
while((de = dictNext(di2)) != NULL) {
|
||||
@@ -1464,6 +1546,12 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
|
||||
}
|
||||
dictReleaseIterator(di2);
|
||||
}
|
||||
|
||||
/* sentinel current-epoch is a global state valid for all the masters. */
|
||||
line = sdscatprintf(sdsempty(),
|
||||
"sentinel current-epoch %llu", (unsigned long long) sentinel.current_epoch);
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,1);
|
||||
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
@@ -1475,21 +1563,23 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
|
||||
*
|
||||
* On failure the function logs a warning on the Redis log. */
|
||||
void sentinelFlushConfig(void) {
|
||||
int fd;
|
||||
int fd = -1;
|
||||
int saved_hz = server.hz;
|
||||
int rewrite_status;
|
||||
|
||||
server.hz = REDIS_DEFAULT_HZ;
|
||||
if (rewriteConfig(server.configfile) != -1) {
|
||||
/* Rewrite succeded, fsync it. */
|
||||
if ((fd = open(server.configfile,O_RDONLY)) != -1) {
|
||||
fsync(fd);
|
||||
close(fd);
|
||||
}
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,"WARNING: Senitnel was not able to save the new configuration on disk!!!: %s", strerror(errno));
|
||||
}
|
||||
rewrite_status = rewriteConfig(server.configfile);
|
||||
server.hz = saved_hz;
|
||||
|
||||
if (rewrite_status == -1) goto werr;
|
||||
if ((fd = open(server.configfile,O_RDONLY)) == -1) goto werr;
|
||||
if (fsync(fd) == -1) goto werr;
|
||||
if (close(fd) == EOF) goto werr;
|
||||
return;
|
||||
|
||||
werr:
|
||||
if (fd != -1) close(fd);
|
||||
redisLog(REDIS_WARNING,"WARNING: Sentinel was not able to save the new configuration on disk!!!: %s", strerror(errno));
|
||||
}
|
||||
|
||||
/* ====================== hiredis connection handling ======================= */
|
||||
@@ -1560,6 +1650,23 @@ void sentinelSendAuthIfNeeded(sentinelRedisInstance *ri, redisAsyncContext *c) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Use CLIENT SETNAME to name the connection in the Redis instance as
|
||||
* sentinel-<first_8_chars_of_runid>-<connection_type>
|
||||
* The connection type is "cmd" or "pubsub" as specified by 'type'.
|
||||
*
|
||||
* This makes it possible to list all the sentinel instances connected
|
||||
* to a Redis servewr with CLIENT LIST, grepping for a specific name format. */
|
||||
void sentinelSetClientName(sentinelRedisInstance *ri, redisAsyncContext *c, char *type) {
|
||||
char name[64];
|
||||
|
||||
snprintf(name,sizeof(name),"sentinel-%.8s-%s",server.runid,type);
|
||||
if (redisAsyncCommand(c, sentinelDiscardReplyCallback, NULL,
|
||||
"CLIENT SETNAME %s", name) == REDIS_OK)
|
||||
{
|
||||
ri->pending_commands++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Create the async connections for the specified instance if the instance
|
||||
* is disconnected. Note that the SRI_DISCONNECTED flag is set even if just
|
||||
* one of the two links (commands and pub/sub) is missing. */
|
||||
@@ -1582,6 +1689,10 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
|
||||
redisAsyncSetDisconnectCallback(ri->cc,
|
||||
sentinelDisconnectCallback);
|
||||
sentinelSendAuthIfNeeded(ri,ri->cc);
|
||||
sentinelSetClientName(ri,ri->cc,"cmd");
|
||||
|
||||
/* Send a PING ASAP when reconnecting. */
|
||||
sentinelSendPing(ri);
|
||||
}
|
||||
}
|
||||
/* Pub / Sub */
|
||||
@@ -1602,6 +1713,7 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
|
||||
redisAsyncSetDisconnectCallback(ri->pc,
|
||||
sentinelDisconnectCallback);
|
||||
sentinelSendAuthIfNeeded(ri,ri->pc);
|
||||
sentinelSetClientName(ri,ri->pc,"pubsub");
|
||||
/* Now we subscribe to the Sentinels "Hello" channel. */
|
||||
retval = redisAsyncCommand(ri->pc,
|
||||
sentinelReceiveHelloMessages, NULL, "SUBSCRIBE %s",
|
||||
@@ -1735,7 +1847,7 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
|
||||
ri->slave_conf_change_time = mstime();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* master_link_status:<status> */
|
||||
if (sdslen(l) >= 19 && !memcmp(l,"master_link_status:",19)) {
|
||||
ri->slave_master_link_status =
|
||||
@@ -1765,8 +1877,20 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
|
||||
ri->role_reported_time = mstime();
|
||||
ri->role_reported = role;
|
||||
if (role == SRI_SLAVE) ri->slave_conf_change_time = mstime();
|
||||
/* Log the event with +role-change if the new role is coherent or
|
||||
* with -role-change if there is a mismatch with the current config. */
|
||||
sentinelEvent(REDIS_VERBOSE,
|
||||
((ri->flags & (SRI_MASTER|SRI_SLAVE)) == role) ?
|
||||
"+role-change" : "-role-change",
|
||||
ri, "%@ new reported role is %s",
|
||||
role == SRI_MASTER ? "master" : "slave",
|
||||
ri->flags & SRI_MASTER ? "master" : "slave");
|
||||
}
|
||||
|
||||
/* None of the following conditions are processed when in tilt mode, so
|
||||
* return asap. */
|
||||
if (sentinel.tilt) return;
|
||||
|
||||
/* Handle master -> slave role switch. */
|
||||
if ((ri->flags & SRI_MASTER) && role == SRI_SLAVE) {
|
||||
/* Nothing to do, but masters claiming to be slaves are
|
||||
@@ -1778,8 +1902,7 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
|
||||
if ((ri->flags & SRI_SLAVE) && role == SRI_MASTER) {
|
||||
/* If this is a promoted slave we can change state to the
|
||||
* failover state machine. */
|
||||
if (!sentinel.tilt &&
|
||||
(ri->master->flags & SRI_FAILOVER_IN_PROGRESS) &&
|
||||
if ((ri->master->flags & SRI_FAILOVER_IN_PROGRESS) &&
|
||||
(ri->master->failover_state ==
|
||||
SENTINEL_FAILOVER_STATE_WAIT_PROMOTION))
|
||||
{
|
||||
@@ -1797,7 +1920,7 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
|
||||
ri->master,"%@");
|
||||
sentinelCallClientReconfScript(ri->master,SENTINEL_LEADER,
|
||||
"start",ri->master->addr,ri->addr);
|
||||
} else if (!sentinel.tilt) {
|
||||
} else {
|
||||
/* A slave turned into a master. We want to force our view and
|
||||
* reconfigure as slave. Wait some time after the change before
|
||||
* going forward, to receive new configs if any. */
|
||||
@@ -1817,7 +1940,7 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
|
||||
}
|
||||
|
||||
/* Handle slaves replicating to a different master address. */
|
||||
if ((ri->flags & SRI_SLAVE) && !sentinel.tilt &&
|
||||
if ((ri->flags & SRI_SLAVE) &&
|
||||
role == SRI_SLAVE &&
|
||||
(ri->slave_master_port != ri->master->addr->port ||
|
||||
strcasecmp(ri->slave_master_host,ri->master->addr->ip)))
|
||||
@@ -1838,10 +1961,6 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
|
||||
}
|
||||
}
|
||||
|
||||
/* None of the following conditions are processed when in tilt mode, so
|
||||
* return asap. */
|
||||
if (sentinel.tilt) return;
|
||||
|
||||
/* Detect if the slave that is in the process of being reconfigured
|
||||
* changed state. */
|
||||
if ((ri->flags & SRI_SLAVE) && role == SRI_SLAVE &&
|
||||
@@ -1908,6 +2027,7 @@ void sentinelPingReplyCallback(redisAsyncContext *c, void *reply, void *privdata
|
||||
strncmp(r->str,"MASTERDOWN",10) == 0)
|
||||
{
|
||||
ri->last_avail_time = mstime();
|
||||
ri->last_ping_time = 0; /* Flag the pong as received. */
|
||||
} else {
|
||||
/* Send a SCRIPT KILL command if the instance appears to be
|
||||
* down because of a busy script. */
|
||||
@@ -1942,22 +2062,113 @@ void sentinelPublishReplyCallback(redisAsyncContext *c, void *reply, void *privd
|
||||
ri->last_pub_time = mstime();
|
||||
}
|
||||
|
||||
/* Process an hello message received via Pub/Sub in master or slave instance,
|
||||
* or sent directly to this sentinel via the (fake) PUBLISH command of Sentinel.
|
||||
*
|
||||
* If the master name specified in the message is not known, the message is
|
||||
* discareded. */
|
||||
void sentinelProcessHelloMessage(char *hello, int hello_len) {
|
||||
/* Format is composed of 8 tokens:
|
||||
* 0=ip,1=port,2=runid,3=current_epoch,4=master_name,
|
||||
* 5=master_ip,6=master_port,7=master_config_epoch. */
|
||||
int numtokens, port, removed, master_port;
|
||||
uint64_t current_epoch, master_config_epoch;
|
||||
char **token = sdssplitlen(hello, hello_len, ",", 1, &numtokens);
|
||||
sentinelRedisInstance *si, *master;
|
||||
|
||||
if (numtokens == 8) {
|
||||
/* Obtain a reference to the master this hello message is about */
|
||||
master = sentinelGetMasterByName(token[4]);
|
||||
if (!master) goto cleanup; /* Unknown master, skip the message. */
|
||||
|
||||
/* First, try to see if we already have this sentinel. */
|
||||
port = atoi(token[1]);
|
||||
master_port = atoi(token[6]);
|
||||
si = getSentinelRedisInstanceByAddrAndRunID(
|
||||
master->sentinels,token[0],port,token[2]);
|
||||
current_epoch = strtoull(token[3],NULL,10);
|
||||
master_config_epoch = strtoull(token[7],NULL,10);
|
||||
|
||||
if (!si) {
|
||||
/* If not, remove all the sentinels that have the same runid
|
||||
* OR the same ip/port, because it's either a restart or a
|
||||
* network topology change. */
|
||||
removed = removeMatchingSentinelsFromMaster(master,token[0],port,
|
||||
token[2]);
|
||||
if (removed) {
|
||||
sentinelEvent(REDIS_NOTICE,"-dup-sentinel",master,
|
||||
"%@ #duplicate of %s:%d or %s",
|
||||
token[0],port,token[2]);
|
||||
}
|
||||
|
||||
/* Add the new sentinel. */
|
||||
si = createSentinelRedisInstance(NULL,SRI_SENTINEL,
|
||||
token[0],port,master->quorum,master);
|
||||
if (si) {
|
||||
sentinelEvent(REDIS_NOTICE,"+sentinel",si,"%@");
|
||||
/* The runid is NULL after a new instance creation and
|
||||
* for Sentinels we don't have a later chance to fill it,
|
||||
* so do it now. */
|
||||
si->runid = sdsnew(token[2]);
|
||||
sentinelFlushConfig();
|
||||
}
|
||||
}
|
||||
|
||||
/* Update local current_epoch if received current_epoch is greater.*/
|
||||
if (current_epoch > sentinel.current_epoch) {
|
||||
sentinel.current_epoch = current_epoch;
|
||||
sentinelFlushConfig();
|
||||
sentinelEvent(REDIS_WARNING,"+new-epoch",master,"%llu",
|
||||
(unsigned long long) sentinel.current_epoch);
|
||||
}
|
||||
|
||||
/* Update master info if received configuration is newer. */
|
||||
if (master->config_epoch < master_config_epoch) {
|
||||
master->config_epoch = master_config_epoch;
|
||||
if (master_port != master->addr->port ||
|
||||
strcmp(master->addr->ip, token[5]))
|
||||
{
|
||||
sentinelAddr *old_addr;
|
||||
|
||||
sentinelEvent(REDIS_WARNING,"+config-update-from",si,"%@");
|
||||
sentinelEvent(REDIS_WARNING,"+switch-master",
|
||||
master,"%s %s %d %s %d",
|
||||
master->name,
|
||||
master->addr->ip, master->addr->port,
|
||||
token[5], master_port);
|
||||
|
||||
old_addr = dupSentinelAddr(master->addr);
|
||||
sentinelResetMasterAndChangeAddress(master, token[5], master_port);
|
||||
sentinelCallClientReconfScript(master,
|
||||
SENTINEL_OBSERVER,"start",
|
||||
old_addr,master->addr);
|
||||
releaseSentinelAddr(old_addr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Update the state of the Sentinel. */
|
||||
if (si) si->last_hello_time = mstime();
|
||||
}
|
||||
|
||||
cleanup:
|
||||
sdsfreesplitres(token,numtokens);
|
||||
}
|
||||
|
||||
|
||||
/* This is our Pub/Sub callback for the Hello channel. It's useful in order
|
||||
* to discover other sentinels attached at the same master. */
|
||||
void sentinelReceiveHelloMessages(redisAsyncContext *c, void *reply, void *privdata) {
|
||||
sentinelRedisInstance *ri = c->data, *master;
|
||||
sentinelRedisInstance *ri = c->data;
|
||||
redisReply *r;
|
||||
|
||||
if (!reply || !ri) return;
|
||||
r = reply;
|
||||
|
||||
master = (ri->flags & SRI_MASTER) ? ri : ri->master;
|
||||
|
||||
/* Update the last activity in the pubsub channel. Note that since we
|
||||
* receive our messages as well this timestamp can be used to detect
|
||||
* if the link is probably disconnected even if it seems otherwise. */
|
||||
ri->pc_last_activity = mstime();
|
||||
|
||||
|
||||
/* Sanity check in the reply we expect, so that the code that follows
|
||||
* can avoid to check for details. */
|
||||
if (r->type != REDIS_REPLY_ARRAY ||
|
||||
@@ -1970,90 +2181,7 @@ void sentinelReceiveHelloMessages(redisAsyncContext *c, void *reply, void *privd
|
||||
/* We are not interested in meeting ourselves */
|
||||
if (strstr(r->element[2]->str,server.runid) != NULL) return;
|
||||
|
||||
{
|
||||
/* Format is composed of 8 tokens:
|
||||
* 0=ip,1=port,2=runid,3=current_epoch,4=master_name,
|
||||
* 5=master_ip,6=master_port,7=master_config_epoch. */
|
||||
int numtokens, port, removed, master_port;
|
||||
uint64_t current_epoch, master_config_epoch;
|
||||
char **token = sdssplitlen(r->element[2]->str,
|
||||
r->element[2]->len,
|
||||
",",1,&numtokens);
|
||||
sentinelRedisInstance *si;
|
||||
|
||||
if (numtokens == 8) {
|
||||
/* First, try to see if we already have this sentinel. */
|
||||
port = atoi(token[1]);
|
||||
master_port = atoi(token[6]);
|
||||
si = getSentinelRedisInstanceByAddrAndRunID(
|
||||
master->sentinels,token[0],port,token[2]);
|
||||
current_epoch = strtoull(token[3],NULL,10);
|
||||
master_config_epoch = strtoull(token[7],NULL,10);
|
||||
sentinelRedisInstance *msgmaster;
|
||||
|
||||
if (!si) {
|
||||
/* If not, remove all the sentinels that have the same runid
|
||||
* OR the same ip/port, because it's either a restart or a
|
||||
* network topology change. */
|
||||
removed = removeMatchingSentinelsFromMaster(master,token[0],port,
|
||||
token[2]);
|
||||
if (removed) {
|
||||
sentinelEvent(REDIS_NOTICE,"-dup-sentinel",master,
|
||||
"%@ #duplicate of %s:%d or %s",
|
||||
token[0],port,token[2]);
|
||||
}
|
||||
|
||||
/* Add the new sentinel. */
|
||||
si = createSentinelRedisInstance(NULL,SRI_SENTINEL,
|
||||
token[0],port,master->quorum,master);
|
||||
if (si) {
|
||||
sentinelEvent(REDIS_NOTICE,"+sentinel",si,"%@");
|
||||
/* The runid is NULL after a new instance creation and
|
||||
* for Sentinels we don't have a later chance to fill it,
|
||||
* so do it now. */
|
||||
si->runid = sdsnew(token[2]);
|
||||
sentinelFlushConfig();
|
||||
}
|
||||
}
|
||||
|
||||
/* Update local current_epoch if received current_epoch is greater.*/
|
||||
if (current_epoch > sentinel.current_epoch) {
|
||||
sentinel.current_epoch = current_epoch;
|
||||
sentinelEvent(REDIS_WARNING,"+new-epoch",ri,"%llu",
|
||||
(unsigned long long) sentinel.current_epoch);
|
||||
}
|
||||
|
||||
/* Update master info if received configuration is newer. */
|
||||
if ((msgmaster = sentinelGetMasterByName(token[4])) != NULL) {
|
||||
if (msgmaster->config_epoch < master_config_epoch) {
|
||||
msgmaster->config_epoch = master_config_epoch;
|
||||
if (master_port != msgmaster->addr->port ||
|
||||
strcmp(msgmaster->addr->ip, token[5]))
|
||||
{
|
||||
sentinelAddr *old_addr;
|
||||
|
||||
sentinelEvent(REDIS_WARNING,"+switch-master",
|
||||
msgmaster,"%s %s %d %s %d",
|
||||
msgmaster->name,
|
||||
msgmaster->addr->ip, msgmaster->addr->port,
|
||||
token[5], master_port);
|
||||
|
||||
old_addr = dupSentinelAddr(msgmaster->addr);
|
||||
sentinelResetMasterAndChangeAddress(msgmaster,
|
||||
token[5], master_port);
|
||||
sentinelCallClientReconfScript(msgmaster,
|
||||
SENTINEL_OBSERVER,"start",
|
||||
old_addr,msgmaster->addr);
|
||||
releaseSentinelAddr(old_addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Update the state of the Sentinel. */
|
||||
if (si) si->last_hello_time = mstime();
|
||||
}
|
||||
sdsfreesplitres(token,numtokens);
|
||||
}
|
||||
sentinelProcessHelloMessage(r->element[2]->str, r->element[2]->len);
|
||||
}
|
||||
|
||||
/* Send an "Hello" message via Pub/Sub to the specified 'ri' Redis
|
||||
@@ -2095,11 +2223,31 @@ int sentinelSendHello(sentinelRedisInstance *ri) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Send a PING to the specified instance and refresh the last_ping_time
|
||||
* if it is zero (that is, if we received a pong for the previous ping).
|
||||
*
|
||||
* On error zero is returned, and we can't consider the PING command
|
||||
* queued in the connection. */
|
||||
int sentinelSendPing(sentinelRedisInstance *ri) {
|
||||
int retval = redisAsyncCommand(ri->cc,
|
||||
sentinelPingReplyCallback, NULL, "PING");
|
||||
if (retval == REDIS_OK) {
|
||||
ri->pending_commands++;
|
||||
/* We update the ping time only if we received the pong for
|
||||
* the previous ping, otherwise we are technically waiting
|
||||
* since the first ping that did not received a reply. */
|
||||
if (ri->last_ping_time == 0) ri->last_ping_time = mstime();
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Send periodic PING, INFO, and PUBLISH to the Hello channel to
|
||||
* the specified master or slave instance. */
|
||||
void sentinelPingInstance(sentinelRedisInstance *ri) {
|
||||
void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) {
|
||||
mstime_t now = mstime();
|
||||
mstime_t info_period;
|
||||
mstime_t info_period, ping_period;
|
||||
int retval;
|
||||
|
||||
/* Return ASAP if we have already a PING or INFO already pending, or
|
||||
@@ -2125,6 +2273,12 @@ void sentinelPingInstance(sentinelRedisInstance *ri) {
|
||||
info_period = SENTINEL_INFO_PERIOD;
|
||||
}
|
||||
|
||||
/* We ping instances every time the last received pong is older than
|
||||
* the configured 'down-after-milliseconds' time, but every second
|
||||
* anyway if 'down-after-milliseconds' is greater than 1 second. */
|
||||
ping_period = ri->down_after_period;
|
||||
if (ping_period > SENTINEL_PING_PERIOD) ping_period = SENTINEL_PING_PERIOD;
|
||||
|
||||
if ((ri->flags & SRI_SENTINEL) == 0 &&
|
||||
(ri->info_refresh == 0 ||
|
||||
(now - ri->info_refresh) > info_period))
|
||||
@@ -2132,18 +2286,12 @@ void sentinelPingInstance(sentinelRedisInstance *ri) {
|
||||
/* Send INFO to masters and slaves, not sentinels. */
|
||||
retval = redisAsyncCommand(ri->cc,
|
||||
sentinelInfoReplyCallback, NULL, "INFO");
|
||||
if (retval != REDIS_OK) return;
|
||||
ri->pending_commands++;
|
||||
} else if ((now - ri->last_pong_time) > SENTINEL_PING_PERIOD) {
|
||||
if (retval == REDIS_OK) ri->pending_commands++;
|
||||
} else if ((now - ri->last_pong_time) > ping_period) {
|
||||
/* Send PING to all the three kinds of instances. */
|
||||
retval = redisAsyncCommand(ri->cc,
|
||||
sentinelPingReplyCallback, NULL, "PING");
|
||||
if (retval != REDIS_OK) return;
|
||||
ri->pending_commands++;
|
||||
} else if ((ri->flags & SRI_SENTINEL) == 0 &&
|
||||
(now - ri->last_pub_time) > SENTINEL_PUBLISH_PERIOD)
|
||||
{
|
||||
/* PUBLISH hello messages to masters and slaves. */
|
||||
sentinelSendPing(ri);
|
||||
} else if ((now - ri->last_pub_time) > SENTINEL_PUBLISH_PERIOD) {
|
||||
/* PUBLISH hello messages to all the three kinds of instances. */
|
||||
sentinelSendHello(ri);
|
||||
}
|
||||
}
|
||||
@@ -2217,6 +2365,11 @@ void addReplySentinelRedisInstance(redisClient *c, sentinelRedisInstance *ri) {
|
||||
fields++;
|
||||
}
|
||||
|
||||
addReplyBulkCString(c,"last-ping-sent");
|
||||
addReplyBulkLongLong(c,
|
||||
ri->last_ping_time ? (mstime() - ri->last_ping_time) : 0);
|
||||
fields++;
|
||||
|
||||
addReplyBulkCString(c,"last-ok-ping-reply");
|
||||
addReplyBulkLongLong(c,mstime() - ri->last_avail_time);
|
||||
fields++;
|
||||
@@ -2237,6 +2390,10 @@ void addReplySentinelRedisInstance(redisClient *c, sentinelRedisInstance *ri) {
|
||||
fields++;
|
||||
}
|
||||
|
||||
addReplyBulkCString(c,"down-after-milliseconds");
|
||||
addReplyBulkLongLong(c,ri->down_after_period);
|
||||
fields++;
|
||||
|
||||
/* Masters and Slaves */
|
||||
if (ri->flags & (SRI_MASTER|SRI_SLAVE)) {
|
||||
addReplyBulkCString(c,"info-refresh");
|
||||
@@ -2271,10 +2428,6 @@ void addReplySentinelRedisInstance(redisClient *c, sentinelRedisInstance *ri) {
|
||||
addReplyBulkLongLong(c,ri->quorum);
|
||||
fields++;
|
||||
|
||||
addReplyBulkCString(c,"down-after-milliseconds");
|
||||
addReplyBulkLongLong(c,ri->down_after_period);
|
||||
fields++;
|
||||
|
||||
addReplyBulkCString(c,"failover-timeout");
|
||||
addReplyBulkLongLong(c,ri->failover_timeout);
|
||||
fields++;
|
||||
@@ -2455,8 +2608,6 @@ void sentinelCommand(redisClient *c) {
|
||||
ri = sentinelGetMasterByName(c->argv[2]->ptr);
|
||||
if (ri == NULL) {
|
||||
addReply(c,shared.nullmultibulk);
|
||||
} else if (ri->info_refresh == 0) {
|
||||
addReplySds(c,sdsnew("-IDONTKNOW I have not enough information to reply. Please ask another Sentinel.\r\n"));
|
||||
} else {
|
||||
sentinelAddr *addr = sentinelGetCurrentMasterAddress(ri);
|
||||
|
||||
@@ -2491,6 +2642,7 @@ void sentinelCommand(redisClient *c) {
|
||||
sentinelPendingScriptsCommand(c);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"monitor")) {
|
||||
/* SENTINEL MONITOR <name> <ip> <port> <quorum> */
|
||||
sentinelRedisInstance *ri;
|
||||
long quorum, port;
|
||||
char buf[32];
|
||||
|
||||
@@ -2506,9 +2658,11 @@ void sentinelCommand(redisClient *c) {
|
||||
addReplyError(c,"Invalid IP address specified");
|
||||
return;
|
||||
}
|
||||
if (createSentinelRedisInstance(c->argv[2]->ptr,SRI_MASTER,
|
||||
c->argv[3]->ptr,port,quorum,NULL) == NULL)
|
||||
{
|
||||
|
||||
/* Parameters are valid. Try to create the master instance. */
|
||||
ri = createSentinelRedisInstance(c->argv[2]->ptr,SRI_MASTER,
|
||||
c->argv[3]->ptr,port,quorum,NULL);
|
||||
if (ri == NULL) {
|
||||
switch(errno) {
|
||||
case EBUSY:
|
||||
addReplyError(c,"Duplicated master name");
|
||||
@@ -2522,6 +2676,7 @@ void sentinelCommand(redisClient *c) {
|
||||
}
|
||||
} else {
|
||||
sentinelFlushConfig();
|
||||
sentinelEvent(REDIS_WARNING,"+monitor",ri,"%@ quorum %d",ri->quorum);
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"remove")) {
|
||||
@@ -2530,6 +2685,7 @@ void sentinelCommand(redisClient *c) {
|
||||
|
||||
if ((ri = sentinelGetMasterByNameOrReplyError(c,c->argv[2]))
|
||||
== NULL) return;
|
||||
sentinelEvent(REDIS_WARNING,"-monitor",ri,"%@");
|
||||
dictDelete(sentinel.masters,c->argv[2]->ptr);
|
||||
sentinelFlushConfig();
|
||||
addReply(c,shared.ok);
|
||||
@@ -2628,6 +2784,7 @@ void sentinelSetCommand(redisClient *c) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll <= 0)
|
||||
goto badfmt;
|
||||
ri->down_after_period = ll;
|
||||
sentinelPropagateDownAfterPeriod(ri);
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"failover-timeout")) {
|
||||
/* failover-timeout <milliseconds> */
|
||||
@@ -2681,6 +2838,7 @@ void sentinelSetCommand(redisClient *c) {
|
||||
if (changes) sentinelFlushConfig();
|
||||
return;
|
||||
}
|
||||
sentinelEvent(REDIS_WARNING,"+set",ri,"%@ %s %s",option,value);
|
||||
}
|
||||
|
||||
if (changes) sentinelFlushConfig();
|
||||
@@ -2693,20 +2851,42 @@ badfmt: /* Bad format errors */
|
||||
value, option);
|
||||
}
|
||||
|
||||
/* Our fake PUBLISH command: it is actually useful only to receive hello messages
|
||||
* from the other sentinel instances, and publishing to a channel other than
|
||||
* SENTINEL_HELLO_CHANNEL is forbidden.
|
||||
*
|
||||
* Because we have a Sentinel PUBLISH, the code to send hello messages is the same
|
||||
* for all the three kind of instances: masters, slaves, sentinels. */
|
||||
void sentinelPublishCommand(redisClient *c) {
|
||||
if (strcmp(c->argv[1]->ptr,SENTINEL_HELLO_CHANNEL)) {
|
||||
addReplyError(c, "Only HELLO messages are accepted by Sentinel instances.");
|
||||
return;
|
||||
}
|
||||
sentinelProcessHelloMessage(c->argv[2]->ptr,sdslen(c->argv[2]->ptr));
|
||||
addReplyLongLong(c,1);
|
||||
}
|
||||
|
||||
/* ===================== SENTINEL availability checks ======================= */
|
||||
|
||||
/* Is this instance down from our point of view? */
|
||||
void sentinelCheckSubjectivelyDown(sentinelRedisInstance *ri) {
|
||||
mstime_t elapsed = mstime() - ri->last_avail_time;
|
||||
mstime_t elapsed = 0;
|
||||
|
||||
if (ri->last_ping_time)
|
||||
elapsed = mstime() - ri->last_ping_time;
|
||||
|
||||
/* Check if we are in need for a reconnection of one of the
|
||||
* links, because we are detecting low activity.
|
||||
*
|
||||
* 1) Check if the command link seems connected, was connected not less
|
||||
* than SENTINEL_MIN_LINK_RECONNECT_PERIOD, but still we have an
|
||||
* idle time that is greater than down_after_period / 2 seconds. */
|
||||
* than SENTINEL_MIN_LINK_RECONNECT_PERIOD, but still we have a
|
||||
* pending ping for more than half the timeout. */
|
||||
if (ri->cc &&
|
||||
(mstime() - ri->cc_conn_time) > SENTINEL_MIN_LINK_RECONNECT_PERIOD &&
|
||||
ri->last_ping_time != 0 && /* Ther is a pending ping... */
|
||||
/* The pending ping is delayed, and we did not received
|
||||
* error replies as well. */
|
||||
(mstime() - ri->last_ping_time) > (ri->down_after_period/2) &&
|
||||
(mstime() - ri->last_pong_time) > (ri->down_after_period/2))
|
||||
{
|
||||
sentinelKillLink(ri,ri->cc);
|
||||
@@ -2889,6 +3069,7 @@ void sentinelAskMasterStateToOtherSentinels(sentinelRedisInstance *master, int f
|
||||
char *sentinelVoteLeader(sentinelRedisInstance *master, uint64_t req_epoch, char *req_runid, uint64_t *leader_epoch) {
|
||||
if (req_epoch > sentinel.current_epoch) {
|
||||
sentinel.current_epoch = req_epoch;
|
||||
sentinelFlushConfig();
|
||||
sentinelEvent(REDIS_WARNING,"+new-epoch",master,"%llu",
|
||||
(unsigned long long) sentinel.current_epoch);
|
||||
}
|
||||
@@ -2898,13 +3079,14 @@ char *sentinelVoteLeader(sentinelRedisInstance *master, uint64_t req_epoch, char
|
||||
sdsfree(master->leader);
|
||||
master->leader = sdsnew(req_runid);
|
||||
master->leader_epoch = sentinel.current_epoch;
|
||||
sentinelFlushConfig();
|
||||
sentinelEvent(REDIS_WARNING,"+vote-for-leader",master,"%s %llu",
|
||||
master->leader, (unsigned long long) master->leader_epoch);
|
||||
/* If we did not voted for ourselves, set the master failover start
|
||||
* time to now, in order to force a delay before we can start a
|
||||
* failover for the same master. */
|
||||
if (strcasecmp(master->leader,server.runid))
|
||||
master->failover_start_time = mstime();
|
||||
master->failover_start_time = mstime()+rand()%SENTINEL_MAX_DESYNC;
|
||||
}
|
||||
|
||||
*leader_epoch = master->leader_epoch;
|
||||
@@ -3049,7 +3231,7 @@ void sentinelStartFailover(sentinelRedisInstance *master) {
|
||||
sentinelEvent(REDIS_WARNING,"+new-epoch",master,"%llu",
|
||||
(unsigned long long) sentinel.current_epoch);
|
||||
sentinelEvent(REDIS_WARNING,"+try-failover",master,"%@");
|
||||
master->failover_start_time = mstime();
|
||||
master->failover_start_time = mstime()+rand()%SENTINEL_MAX_DESYNC;
|
||||
master->failover_state_change_time = mstime();
|
||||
}
|
||||
|
||||
@@ -3059,7 +3241,7 @@ void sentinelStartFailover(sentinelRedisInstance *master) {
|
||||
* 1) Master must be in ODOWN condition.
|
||||
* 2) No failover already in progress.
|
||||
* 3) No failover already attempted recently.
|
||||
*
|
||||
*
|
||||
* We still don't know if we'll win the election so it is possible that we
|
||||
* start the failover but that we'll not be able to act.
|
||||
*
|
||||
@@ -3073,7 +3255,22 @@ int sentinelStartFailoverIfNeeded(sentinelRedisInstance *master) {
|
||||
|
||||
/* Last failover attempt started too little time ago? */
|
||||
if (mstime() - master->failover_start_time <
|
||||
master->failover_timeout*2) return 0;
|
||||
master->failover_timeout*2)
|
||||
{
|
||||
if (master->failover_delay_logged != master->failover_start_time) {
|
||||
time_t clock = (master->failover_start_time +
|
||||
master->failover_timeout*2) / 1000;
|
||||
char ctimebuf[26];
|
||||
|
||||
ctime_r(&clock,ctimebuf);
|
||||
ctimebuf[24] = '\0'; /* Remove newline. */
|
||||
master->failover_delay_logged = master->failover_start_time;
|
||||
redisLog(REDIS_WARNING,
|
||||
"Next failover delay: I will not start a failover before %s",
|
||||
ctimebuf);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
sentinelStartFailover(master);
|
||||
return 1;
|
||||
@@ -3359,14 +3556,17 @@ void sentinelFailoverReconfNextSlave(sentinelRedisInstance *master) {
|
||||
/* Skip the promoted slave, and already configured slaves. */
|
||||
if (slave->flags & (SRI_PROMOTED|SRI_RECONF_DONE)) continue;
|
||||
|
||||
/* Clear the SRI_RECONF_SENT flag if too much time elapsed without
|
||||
* the slave moving forward to the next state. */
|
||||
/* If too much time elapsed without the slave moving forward to
|
||||
* the next state, consider it reconfigured even if it is not.
|
||||
* Sentinels will detect the slave as misconfigured and fix its
|
||||
* configuration later. */
|
||||
if ((slave->flags & SRI_RECONF_SENT) &&
|
||||
(mstime() - slave->slave_reconf_sent_time) >
|
||||
SENTINEL_SLAVE_RECONF_RETRY_PERIOD)
|
||||
SENTINEL_SLAVE_RECONF_TIMEOUT)
|
||||
{
|
||||
sentinelEvent(REDIS_NOTICE,"-slave-reconf-sent-timeout",slave,"%@");
|
||||
slave->flags &= ~SRI_RECONF_SENT;
|
||||
slave->flags |= SRI_RECONF_DONE;
|
||||
}
|
||||
|
||||
/* Nothing to do for instances that are disconnected or already
|
||||
@@ -3457,7 +3657,7 @@ void sentinelHandleRedisInstance(sentinelRedisInstance *ri) {
|
||||
/* ========== MONITORING HALF ============ */
|
||||
/* Every kind of instance */
|
||||
sentinelReconnectInstance(ri);
|
||||
sentinelPingInstance(ri);
|
||||
sentinelSendPeriodicCommands(ri);
|
||||
|
||||
/* ============== ACTING HALF ============= */
|
||||
/* We don't proceed with the acting half if we are in TILT mode.
|
||||
|
||||
+34
-6
@@ -194,6 +194,7 @@ void sortCommand(redisClient *c) {
|
||||
int j, dontsort = 0, vectorlen;
|
||||
int getop = 0; /* GET operation counter */
|
||||
int int_convertion_error = 0;
|
||||
int syntax_error = 0;
|
||||
robj *sortval, *sortby = NULL, *storekey = NULL;
|
||||
redisSortObject *vector; /* Resulting vector to sort */
|
||||
|
||||
@@ -231,8 +232,14 @@ void sortCommand(redisClient *c) {
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"alpha")) {
|
||||
alpha = 1;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"limit") && leftargs >= 2) {
|
||||
if ((getLongFromObjectOrReply(c, c->argv[j+1], &limit_start, NULL) != REDIS_OK) ||
|
||||
(getLongFromObjectOrReply(c, c->argv[j+2], &limit_count, NULL) != REDIS_OK)) return;
|
||||
if ((getLongFromObjectOrReply(c, c->argv[j+1], &limit_start, NULL)
|
||||
!= REDIS_OK) ||
|
||||
(getLongFromObjectOrReply(c, c->argv[j+2], &limit_count, NULL)
|
||||
!= REDIS_OK))
|
||||
{
|
||||
syntax_error++;
|
||||
break;
|
||||
}
|
||||
j+=2;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"store") && leftargs >= 1) {
|
||||
storekey = c->argv[j+1];
|
||||
@@ -241,22 +248,43 @@ void sortCommand(redisClient *c) {
|
||||
sortby = c->argv[j+1];
|
||||
/* If the BY pattern does not contain '*', i.e. it is constant,
|
||||
* we don't need to sort nor to lookup the weight keys. */
|
||||
if (strchr(c->argv[j+1]->ptr,'*') == NULL) dontsort = 1;
|
||||
if (strchr(c->argv[j+1]->ptr,'*') == NULL) {
|
||||
dontsort = 1;
|
||||
} else {
|
||||
/* If BY is specified with a real patter, we can't accept
|
||||
* it in cluster mode. */
|
||||
if (server.cluster_enabled) {
|
||||
addReplyError(c,"BY option of SORT denied in Cluster mode.");
|
||||
syntax_error++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
j++;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"get") && leftargs >= 1) {
|
||||
if (server.cluster_enabled) {
|
||||
addReplyError(c,"GET option of SORT denied in Cluster mode.");
|
||||
syntax_error++;
|
||||
break;
|
||||
}
|
||||
listAddNodeTail(operations,createSortOperation(
|
||||
REDIS_SORT_GET,c->argv[j+1]));
|
||||
getop++;
|
||||
j++;
|
||||
} else {
|
||||
decrRefCount(sortval);
|
||||
listRelease(operations);
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
syntax_error++;
|
||||
break;
|
||||
}
|
||||
j++;
|
||||
}
|
||||
|
||||
/* Handle syntax errors set during options parsing. */
|
||||
if (syntax_error) {
|
||||
decrRefCount(sortval);
|
||||
listRelease(operations);
|
||||
return;
|
||||
}
|
||||
|
||||
/* For the STORE option, or when SORT is called from a Lua script,
|
||||
* we want to force a specific ordering even when no explicit ordering
|
||||
* was asked (SORT BY nosort). This guarantees that replication / AOF
|
||||
|
||||
+2
-14
@@ -215,12 +215,7 @@ void setrangeCommand(redisClient *c) {
|
||||
return;
|
||||
|
||||
/* Create a copy when the object is shared or encoded. */
|
||||
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
|
||||
robj *decoded = getDecodedObject(o);
|
||||
o = createRawStringObject(decoded->ptr, sdslen(decoded->ptr));
|
||||
decrRefCount(decoded);
|
||||
dbOverwrite(c->db,c->argv[1],o);
|
||||
}
|
||||
o = dbUnshareStringValue(c->db,c->argv[1],o);
|
||||
}
|
||||
|
||||
if (sdslen(value) > 0) {
|
||||
@@ -433,15 +428,8 @@ void appendCommand(redisClient *c) {
|
||||
if (checkStringLength(c,totlen) != REDIS_OK)
|
||||
return;
|
||||
|
||||
/* If the object is shared or encoded, we have to make a copy */
|
||||
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
|
||||
robj *decoded = getDecodedObject(o);
|
||||
o = createRawStringObject(decoded->ptr, sdslen(decoded->ptr));
|
||||
decrRefCount(decoded);
|
||||
dbOverwrite(c->db,c->argv[1],o);
|
||||
}
|
||||
|
||||
/* Append the value */
|
||||
o = dbUnshareStringValue(c->db,c->argv[1],o);
|
||||
o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
|
||||
totlen = sdslen(o->ptr);
|
||||
}
|
||||
|
||||
+706
-95
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -402,7 +402,7 @@ void getRandomHexChars(char *p, unsigned int len) {
|
||||
/* Turn it into hex digits taking just 4 bits out of 8 for every byte. */
|
||||
for (j = 0; j < len; j++)
|
||||
p[j] = charset[p[j] & 0x0F];
|
||||
fclose(fp);
|
||||
if (fp) fclose(fp);
|
||||
}
|
||||
|
||||
/* Given the filename, return the absolute path as an SDS string, or NULL
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
#define REDIS_VERSION "2.9.11"
|
||||
#define REDIS_VERSION "2.9.54"
|
||||
|
||||
+2
-2
@@ -321,8 +321,8 @@ size_t zmalloc_get_rss(void) {
|
||||
#endif
|
||||
|
||||
/* Fragmentation = RSS / allocated-bytes */
|
||||
float zmalloc_get_fragmentation_ratio(void) {
|
||||
return (float)zmalloc_get_rss()/zmalloc_used_memory();
|
||||
float zmalloc_get_fragmentation_ratio(size_t rss) {
|
||||
return (float)rss/zmalloc_used_memory();
|
||||
}
|
||||
|
||||
#if defined(HAVE_PROC_SMAPS)
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ char *zstrdup(const char *s);
|
||||
size_t zmalloc_used_memory(void);
|
||||
void zmalloc_enable_thread_safeness(void);
|
||||
void zmalloc_set_oom_handler(void (*oom_handler)(size_t));
|
||||
float zmalloc_get_fragmentation_ratio(void);
|
||||
float zmalloc_get_fragmentation_ratio(size_t rss);
|
||||
size_t zmalloc_get_rss(void);
|
||||
size_t zmalloc_get_private_dirty(void);
|
||||
void zlibc_free(void *ptr);
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
# Cluster-specific test functions.
|
||||
#
|
||||
# Copyright (C) 2014 Salvatore Sanfilippo antirez@gmail.com
|
||||
# This softare is released under the BSD License. See the COPYING file for
|
||||
# more information.
|
||||
|
||||
# Returns a parsed CLUSTER NODES output as a list of dictionaries.
|
||||
proc get_cluster_nodes id {
|
||||
set lines [split [R $id cluster nodes] "\r\n"]
|
||||
set nodes {}
|
||||
foreach l $lines {
|
||||
set l [string trim $l]
|
||||
if {$l eq {}} continue
|
||||
set args [split $l]
|
||||
set node [dict create \
|
||||
id [lindex $args 0] \
|
||||
addr [lindex $args 1] \
|
||||
flags [split [lindex $args 2] ,] \
|
||||
slaveof [lindex $args 3] \
|
||||
ping_sent [lindex $args 4] \
|
||||
pong_recv [lindex $args 5] \
|
||||
config_epoch [lindex $args 6] \
|
||||
linkstate [lindex $args 7] \
|
||||
slots [lrange $args 8 -1] \
|
||||
]
|
||||
lappend nodes $node
|
||||
}
|
||||
return $nodes
|
||||
}
|
||||
|
||||
# Test node for flag.
|
||||
proc has_flag {node flag} {
|
||||
expr {[lsearch -exact [dict get $node flags] $flag] != -1}
|
||||
}
|
||||
|
||||
# Returns the parsed myself node entry as a dictionary.
|
||||
proc get_myself id {
|
||||
set nodes [get_cluster_nodes $id]
|
||||
foreach n $nodes {
|
||||
if {[has_flag $n myself]} {return $n}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
# Return the value of the specified CLUSTER INFO field.
|
||||
proc CI {n field} {
|
||||
get_info_field [R $n cluster info] $field
|
||||
}
|
||||
|
||||
# Assuming nodes are reest, this function performs slots allocation.
|
||||
# Only the first 'n' nodes are used.
|
||||
proc cluster_allocate_slots {n} {
|
||||
set slot 16383
|
||||
while {$slot >= 0} {
|
||||
# Allocate successive slots to random nodes.
|
||||
set node [randomInt $n]
|
||||
lappend slots_$node $slot
|
||||
incr slot -1
|
||||
}
|
||||
for {set j 0} {$j < $n} {incr j} {
|
||||
R $j cluster addslots {*}[set slots_${j}]
|
||||
}
|
||||
}
|
||||
|
||||
# Check that cluster nodes agree about "state", or raise an error.
|
||||
proc assert_cluster_state {state} {
|
||||
foreach_redis_id id {
|
||||
if {[instance_is_killed redis $id]} continue
|
||||
wait_for_condition 1000 50 {
|
||||
[CI $id cluster_state] eq $state
|
||||
} else {
|
||||
fail "Cluster node $id cluster_state:[CI $id cluster_state]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Search the first node starting from ID $first that is not
|
||||
# already configured as a slave.
|
||||
proc cluster_find_available_slave {first} {
|
||||
foreach_redis_id id {
|
||||
if {$id < $first} continue
|
||||
if {[instance_is_killed redis $id]} continue
|
||||
set me [get_myself $id]
|
||||
if {[dict get $me slaveof] eq {-}} {return $id}
|
||||
}
|
||||
fail "No available slaves"
|
||||
}
|
||||
|
||||
# Add 'slaves' slaves to a cluster composed of 'masters' masters.
|
||||
# It assumes that masters are allocated sequentially from instance ID 0
|
||||
# to N-1.
|
||||
proc cluster_allocate_slaves {masters slaves} {
|
||||
for {set j 0} {$j < $slaves} {incr j} {
|
||||
set master_id [expr {$j % $masters}]
|
||||
set slave_id [cluster_find_available_slave $masters]
|
||||
set master_myself [get_myself $master_id]
|
||||
R $slave_id cluster replicate [dict get $master_myself id]
|
||||
}
|
||||
}
|
||||
|
||||
# Create a cluster composed of the specified number of masters and slaves.
|
||||
proc create_cluster {masters slaves} {
|
||||
cluster_allocate_slots $masters
|
||||
if {$slaves} {
|
||||
cluster_allocate_slaves $masters $slaves
|
||||
}
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
# Set the cluster node-timeout to all the reachalbe nodes.
|
||||
proc set_cluster_node_timeout {to} {
|
||||
foreach_redis_id id {
|
||||
catch {R $id CONFIG SET cluster-node-timeout $to}
|
||||
}
|
||||
}
|
||||
|
||||
# Check if the cluster is writable and readable. Use node "id"
|
||||
# as a starting point to talk with the cluster.
|
||||
proc cluster_write_test {id} {
|
||||
set prefix [randstring 20 20 alpha]
|
||||
set port [get_instance_attrib redis $id port]
|
||||
set cluster [redis_cluster 127.0.0.1:$port]
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
$cluster set key.$j $prefix.$j
|
||||
}
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
assert {[$cluster get key.$j] eq "$prefix.$j"}
|
||||
}
|
||||
$cluster close
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# Cluster test suite. Copyright (C) 2014 Salvatore Sanfilippo antirez@gmail.com
|
||||
# This softare is released under the BSD License. See the COPYING file for
|
||||
# more information.
|
||||
|
||||
cd tests/cluster
|
||||
source cluster.tcl
|
||||
source ../instances.tcl
|
||||
source ../../support/cluster.tcl ; # Redis Cluster client.
|
||||
|
||||
set ::instances_count 20 ; # How many instances we use at max.
|
||||
|
||||
proc main {} {
|
||||
parse_options
|
||||
spawn_instance redis $::redis_base_port $::instances_count {
|
||||
"cluster-enabled yes"
|
||||
"appendonly yes"
|
||||
}
|
||||
run_tests
|
||||
cleanup
|
||||
}
|
||||
|
||||
if {[catch main e]} {
|
||||
puts $::errorInfo
|
||||
cleanup
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
# Check the basic monitoring and failover capabilities.
|
||||
|
||||
source "../tests/includes/init-tests.tcl"
|
||||
|
||||
if {$::simulate_error} {
|
||||
test "This test will fail" {
|
||||
fail "Simulated error"
|
||||
}
|
||||
}
|
||||
|
||||
test "Different nodes have different IDs" {
|
||||
set ids {}
|
||||
set numnodes 0
|
||||
foreach_redis_id id {
|
||||
incr numnodes
|
||||
# Every node should just know itself.
|
||||
set nodeid [dict get [get_myself $id] id]
|
||||
assert {$nodeid ne {}}
|
||||
lappend ids $nodeid
|
||||
}
|
||||
set numids [llength [lsort -unique $ids]]
|
||||
assert {$numids == $numnodes}
|
||||
}
|
||||
|
||||
test "It is possible to perform slot allocation" {
|
||||
cluster_allocate_slots 5
|
||||
}
|
||||
|
||||
test "After the join, every node gets a different config epoch" {
|
||||
set trynum 60
|
||||
while {[incr trynum -1] != 0} {
|
||||
# We check that this condition is true for *all* the nodes.
|
||||
set ok 1 ; # Will be set to 0 every time a node is not ok.
|
||||
foreach_redis_id id {
|
||||
set epochs {}
|
||||
foreach n [get_cluster_nodes $id] {
|
||||
lappend epochs [dict get $n config_epoch]
|
||||
}
|
||||
if {[lsort $epochs] != [lsort -unique $epochs]} {
|
||||
set ok 0 ; # At least one collision!
|
||||
}
|
||||
}
|
||||
if {$ok} break
|
||||
after 1000
|
||||
puts -nonewline .
|
||||
flush stdout
|
||||
}
|
||||
if {$trynum == 0} {
|
||||
fail "Config epoch conflict resolution is not working."
|
||||
}
|
||||
}
|
||||
|
||||
test "Nodes should report cluster_state is ok now" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "It is possible to write and read from the cluster" {
|
||||
cluster_write_test 0
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
# Check the basic monitoring and failover capabilities.
|
||||
|
||||
source "../tests/includes/init-tests.tcl"
|
||||
|
||||
test "Create a 5 nodes cluster" {
|
||||
create_cluster 5 5
|
||||
}
|
||||
|
||||
test "Cluster should start ok" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "Killing two slave nodes" {
|
||||
kill_instance redis 5
|
||||
kill_instance redis 6
|
||||
}
|
||||
|
||||
test "Cluster should be still up" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "Killing one master node" {
|
||||
kill_instance redis 0
|
||||
}
|
||||
|
||||
# Note: the only slave of instance 0 is already down so no
|
||||
# failover is possible, that would change the state back to ok.
|
||||
test "Cluster should be down now" {
|
||||
assert_cluster_state fail
|
||||
}
|
||||
|
||||
test "Restarting master node" {
|
||||
restart_instance redis 0
|
||||
}
|
||||
|
||||
test "Cluster should be up again" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
# Check the basic monitoring and failover capabilities.
|
||||
|
||||
source "../tests/includes/init-tests.tcl"
|
||||
|
||||
test "Create a 5 nodes cluster" {
|
||||
create_cluster 5 5
|
||||
}
|
||||
|
||||
test "Cluster is up" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "Cluster is writable" {
|
||||
cluster_write_test 0
|
||||
}
|
||||
|
||||
test "Instance #5 is a slave" {
|
||||
assert {[RI 5 role] eq {slave}}
|
||||
}
|
||||
|
||||
test "Killing one master node" {
|
||||
kill_instance redis 0
|
||||
}
|
||||
|
||||
test "Cluster should eventually be up again" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "Cluster is writable" {
|
||||
cluster_write_test 1
|
||||
}
|
||||
|
||||
test "Instance #5 is now a master" {
|
||||
assert {[RI 5 role] eq {master}}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
# Initialization tests -- most units will start including this.
|
||||
|
||||
test "(init) Restart killed instances" {
|
||||
foreach type {redis} {
|
||||
foreach_${type}_id id {
|
||||
if {[get_instance_attrib $type $id pid] == -1} {
|
||||
puts -nonewline "$type/$id "
|
||||
flush stdout
|
||||
restart_instance $type $id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "Cluster nodes are reachable" {
|
||||
foreach_redis_id id {
|
||||
# Every node should just know itself.
|
||||
assert {[R $id ping] eq {PONG}}
|
||||
}
|
||||
}
|
||||
|
||||
test "Cluster nodes hard reset" {
|
||||
foreach_redis_id id {
|
||||
catch {R $id flushall} ; # May fail for readonly slaves.
|
||||
R $id cluster reset hard
|
||||
R $id config set cluster-node-timeout 3000
|
||||
}
|
||||
}
|
||||
|
||||
test "Cluster Join and auto-discovery test" {
|
||||
# Join node 0 with 1, 1 with 2, ... and so forth.
|
||||
# If auto-discovery works all nodes will know every other node
|
||||
# eventually.
|
||||
set ids {}
|
||||
foreach_redis_id id {lappend ids $id}
|
||||
for {set j 0} {$j < [expr [llength $ids]-1]} {incr j} {
|
||||
set a [lindex $ids $j]
|
||||
set b [lindex $ids [expr $j+1]]
|
||||
set b_port [get_instance_attrib redis $b port]
|
||||
R $a cluster meet 127.0.0.1 $b_port
|
||||
}
|
||||
|
||||
foreach_redis_id id {
|
||||
wait_for_condition 1000 50 {
|
||||
[llength [get_cluster_nodes $id]] == [llength $ids]
|
||||
} else {
|
||||
fail "Cluster failed to join into a full mesh."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "Before slots allocation, all nodes report cluster failure" {
|
||||
assert_cluster_state fail
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
redis_*
|
||||
sentinel_*
|
||||
@@ -0,0 +1,394 @@
|
||||
# Multi-instance test framework.
|
||||
# This is used in order to test Sentinel and Redis Cluster, and provides
|
||||
# basic capabilities for spawning and handling N parallel Redis / Sentinel
|
||||
# instances.
|
||||
#
|
||||
# Copyright (C) 2014 Salvatore Sanfilippo antirez@gmail.com
|
||||
# This softare is released under the BSD License. See the COPYING file for
|
||||
# more information.
|
||||
|
||||
package require Tcl 8.5
|
||||
|
||||
set tcl_precision 17
|
||||
source ../support/redis.tcl
|
||||
source ../support/util.tcl
|
||||
source ../support/server.tcl
|
||||
source ../support/test.tcl
|
||||
|
||||
set ::verbose 0
|
||||
set ::pause_on_error 0
|
||||
set ::simulate_error 0
|
||||
set ::sentinel_instances {}
|
||||
set ::redis_instances {}
|
||||
set ::sentinel_base_port 20000
|
||||
set ::redis_base_port 30000
|
||||
set ::pids {} ; # We kill everything at exit
|
||||
set ::dirs {} ; # We remove all the temp dirs at exit
|
||||
set ::run_matching {} ; # If non empty, only tests matching pattern are run.
|
||||
|
||||
if {[catch {cd tmp}]} {
|
||||
puts "tmp directory not found."
|
||||
puts "Please run this test from the Redis source root."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Spawn a redis or sentinel instance, depending on 'type'.
|
||||
proc spawn_instance {type base_port count {conf {}}} {
|
||||
for {set j 0} {$j < $count} {incr j} {
|
||||
set port [find_available_port $base_port]
|
||||
incr base_port
|
||||
puts "Starting $type #$j at port $port"
|
||||
|
||||
# Create a directory for this instance.
|
||||
set dirname "${type}_${j}"
|
||||
lappend ::dirs $dirname
|
||||
catch {exec rm -rf $dirname}
|
||||
file mkdir $dirname
|
||||
|
||||
# Write the instance config file.
|
||||
set cfgfile [file join $dirname $type.conf]
|
||||
set cfg [open $cfgfile w]
|
||||
puts $cfg "port $port"
|
||||
puts $cfg "dir ./$dirname"
|
||||
puts $cfg "logfile log.txt"
|
||||
# Add additional config files
|
||||
foreach directive $conf {
|
||||
puts $cfg $directive
|
||||
}
|
||||
close $cfg
|
||||
|
||||
# Finally exec it and remember the pid for later cleanup.
|
||||
if {$type eq "redis"} {
|
||||
set prgname redis-server
|
||||
} elseif {$type eq "sentinel"} {
|
||||
set prgname redis-sentinel
|
||||
} else {
|
||||
error "Unknown instance type."
|
||||
}
|
||||
set pid [exec ../../../src/${prgname} $cfgfile &]
|
||||
lappend ::pids $pid
|
||||
|
||||
# Check availability
|
||||
if {[server_is_up 127.0.0.1 $port 100] == 0} {
|
||||
abort_sentinel_test "Problems starting $type #$j: ping timeout"
|
||||
}
|
||||
|
||||
# Push the instance into the right list
|
||||
lappend ::${type}_instances [list \
|
||||
pid $pid \
|
||||
host 127.0.0.1 \
|
||||
port $port \
|
||||
link [redis 127.0.0.1 $port] \
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
proc cleanup {} {
|
||||
puts "Cleaning up..."
|
||||
foreach pid $::pids {
|
||||
catch {exec kill -9 $pid}
|
||||
}
|
||||
foreach dir $::dirs {
|
||||
catch {exec rm -rf $dir}
|
||||
}
|
||||
}
|
||||
|
||||
proc abort_sentinel_test msg {
|
||||
puts "WARNING: Aborting the test."
|
||||
puts ">>>>>>>> $msg"
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
proc parse_options {} {
|
||||
for {set j 0} {$j < [llength $::argv]} {incr j} {
|
||||
set opt [lindex $::argv $j]
|
||||
set val [lindex $::argv [expr $j+1]]
|
||||
if {$opt eq "--single"} {
|
||||
incr j
|
||||
set ::run_matching "*${val}*"
|
||||
} elseif {$opt eq "--pause-on-error"} {
|
||||
set ::pause_on_error 1
|
||||
} elseif {$opt eq "--fail"} {
|
||||
set ::simulate_error 1
|
||||
} elseif {$opt eq "--help"} {
|
||||
puts "Hello, I'm sentinel.tcl and I run Sentinel unit tests."
|
||||
puts "\nOptions:"
|
||||
puts "--single <pattern> Only runs tests specified by pattern."
|
||||
puts "--pause-on-error Pause for manual inspection on error."
|
||||
puts "--fail Simulate a test failure."
|
||||
puts "--help Shows this help."
|
||||
exit 0
|
||||
} else {
|
||||
puts "Unknown option $opt"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# If --pause-on-error option was passed at startup this function is called
|
||||
# on error in order to give the developer a chance to understand more about
|
||||
# the error condition while the instances are still running.
|
||||
proc pause_on_error {} {
|
||||
puts ""
|
||||
puts [colorstr yellow "*** Please inspect the error now ***"]
|
||||
puts "\nType \"continue\" to resume the test, \"help\" for help screen.\n"
|
||||
while 1 {
|
||||
puts -nonewline "> "
|
||||
flush stdout
|
||||
set line [gets stdin]
|
||||
set argv [split $line " "]
|
||||
set cmd [lindex $argv 0]
|
||||
if {$cmd eq {continue}} {
|
||||
break
|
||||
} elseif {$cmd eq {show-sentinel-logs}} {
|
||||
set count 10
|
||||
if {[lindex $argv 1] ne {}} {set count [lindex $argv 1]}
|
||||
foreach_sentinel_id id {
|
||||
puts "=== SENTINEL $id ===="
|
||||
puts [exec tail -$count sentinel_$id/log.txt]
|
||||
puts "---------------------\n"
|
||||
}
|
||||
} elseif {$cmd eq {ls}} {
|
||||
foreach_redis_id id {
|
||||
puts -nonewline "Redis $id"
|
||||
set errcode [catch {
|
||||
set str {}
|
||||
append str "@[RI $id tcp_port]: "
|
||||
append str "[RI $id role] "
|
||||
if {[RI $id role] eq {slave}} {
|
||||
append str "[RI $id master_host]:[RI $id master_port]"
|
||||
}
|
||||
set str
|
||||
} retval]
|
||||
if {$errcode} {
|
||||
puts " -- $retval"
|
||||
} else {
|
||||
puts $retval
|
||||
}
|
||||
}
|
||||
foreach_sentinel_id id {
|
||||
puts -nonewline "Sentinel $id"
|
||||
set errcode [catch {
|
||||
set str {}
|
||||
append str "@[SI $id tcp_port]: "
|
||||
append str "[join [S $id sentinel get-master-addr-by-name mymaster]]"
|
||||
set str
|
||||
} retval]
|
||||
if {$errcode} {
|
||||
puts " -- $retval"
|
||||
} else {
|
||||
puts $retval
|
||||
}
|
||||
}
|
||||
} elseif {$cmd eq {help}} {
|
||||
puts "ls List Sentinel and Redis instances."
|
||||
puts "show-sentinel-logs \[N\] Show latest N lines of logs."
|
||||
puts "S <id> cmd ... arg Call command in Sentinel <id>."
|
||||
puts "R <id> cmd ... arg Call command in Redis <id>."
|
||||
puts "SI <id> <field> Show Sentinel <id> INFO <field>."
|
||||
puts "RI <id> <field> Show Sentinel <id> INFO <field>."
|
||||
puts "continue Resume test."
|
||||
} else {
|
||||
set errcode [catch {eval $line} retval]
|
||||
if {$retval ne {}} {puts "$retval"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# We redefine 'test' as for Sentinel we don't use the server-client
|
||||
# architecture for the test, everything is sequential.
|
||||
proc test {descr code} {
|
||||
set ts [clock format [clock seconds] -format %H:%M:%S]
|
||||
puts -nonewline "$ts> $descr: "
|
||||
flush stdout
|
||||
|
||||
if {[catch {set retval [uplevel 1 $code]} error]} {
|
||||
if {[string match "assertion:*" $error]} {
|
||||
set msg [string range $error 10 end]
|
||||
puts [colorstr red $msg]
|
||||
if {$::pause_on_error} pause_on_error
|
||||
puts "(Jumping to next unit after error)"
|
||||
return -code continue
|
||||
} else {
|
||||
# Re-raise, let handler up the stack take care of this.
|
||||
error $error $::errorInfo
|
||||
}
|
||||
} else {
|
||||
puts [colorstr green OK]
|
||||
}
|
||||
}
|
||||
|
||||
proc run_tests {} {
|
||||
set tests [lsort [glob ../tests/*]]
|
||||
foreach test $tests {
|
||||
if {$::run_matching ne {} && [string match $::run_matching $test] == 0} {
|
||||
continue
|
||||
}
|
||||
if {[file isdirectory $test]} continue
|
||||
puts [colorstr yellow "Testing unit: [lindex [file split $test] end]"]
|
||||
source $test
|
||||
}
|
||||
}
|
||||
|
||||
# The "S" command is used to interact with the N-th Sentinel.
|
||||
# The general form is:
|
||||
#
|
||||
# S <sentinel-id> command arg arg arg ...
|
||||
#
|
||||
# Example to ping the Sentinel 0 (first instance): S 0 PING
|
||||
proc S {n args} {
|
||||
set s [lindex $::sentinel_instances $n]
|
||||
[dict get $s link] {*}$args
|
||||
}
|
||||
|
||||
# Like R but to chat with Redis instances.
|
||||
proc R {n args} {
|
||||
set r [lindex $::redis_instances $n]
|
||||
[dict get $r link] {*}$args
|
||||
}
|
||||
|
||||
proc get_info_field {info field} {
|
||||
set fl [string length $field]
|
||||
append field :
|
||||
foreach line [split $info "\n"] {
|
||||
set line [string trim $line "\r\n "]
|
||||
if {[string range $line 0 $fl] eq $field} {
|
||||
return [string range $line [expr {$fl+1}] end]
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
proc SI {n field} {
|
||||
get_info_field [S $n info] $field
|
||||
}
|
||||
|
||||
proc RI {n field} {
|
||||
get_info_field [R $n info] $field
|
||||
}
|
||||
|
||||
# Iterate over IDs of sentinel or redis instances.
|
||||
proc foreach_instance_id {instances idvar code} {
|
||||
upvar 1 $idvar id
|
||||
for {set id 0} {$id < [llength $instances]} {incr id} {
|
||||
set errcode [catch {uplevel 1 $code} result]
|
||||
if {$errcode == 1} {
|
||||
error $result $::errorInfo $::errorCode
|
||||
} elseif {$errcode == 4} {
|
||||
continue
|
||||
} elseif {$errcode == 3} {
|
||||
break
|
||||
} elseif {$errcode != 0} {
|
||||
return -code $errcode $result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc foreach_sentinel_id {idvar code} {
|
||||
set errcode [catch {uplevel 1 [list foreach_instance_id $::sentinel_instances $idvar $code]} result]
|
||||
return -code $errcode $result
|
||||
}
|
||||
|
||||
proc foreach_redis_id {idvar code} {
|
||||
set errcode [catch {uplevel 1 [list foreach_instance_id $::redis_instances $idvar $code]} result]
|
||||
return -code $errcode $result
|
||||
}
|
||||
|
||||
# Get the specific attribute of the specified instance type, id.
|
||||
proc get_instance_attrib {type id attrib} {
|
||||
dict get [lindex [set ::${type}_instances] $id] $attrib
|
||||
}
|
||||
|
||||
# Set the specific attribute of the specified instance type, id.
|
||||
proc set_instance_attrib {type id attrib newval} {
|
||||
set d [lindex [set ::${type}_instances] $id]
|
||||
dict set d $attrib $newval
|
||||
lset ::${type}_instances $id $d
|
||||
}
|
||||
|
||||
# Create a master-slave cluster of the given number of total instances.
|
||||
# The first instance "0" is the master, all others are configured as
|
||||
# slaves.
|
||||
proc create_redis_master_slave_cluster n {
|
||||
foreach_redis_id id {
|
||||
if {$id == 0} {
|
||||
# Our master.
|
||||
R $id slaveof no one
|
||||
R $id flushall
|
||||
} elseif {$id < $n} {
|
||||
R $id slaveof [get_instance_attrib redis 0 host] \
|
||||
[get_instance_attrib redis 0 port]
|
||||
} else {
|
||||
# Instances not part of the cluster.
|
||||
R $id slaveof no one
|
||||
}
|
||||
}
|
||||
# Wait for all the slaves to sync.
|
||||
wait_for_condition 1000 50 {
|
||||
[RI 0 connected_slaves] == ($n-1)
|
||||
} else {
|
||||
fail "Unable to create a master-slaves cluster."
|
||||
}
|
||||
}
|
||||
|
||||
proc get_instance_id_by_port {type port} {
|
||||
foreach_${type}_id id {
|
||||
if {[get_instance_attrib $type $id port] == $port} {
|
||||
return $id
|
||||
}
|
||||
}
|
||||
fail "Instance $type port $port not found."
|
||||
}
|
||||
|
||||
# Kill an instance of the specified type/id with SIGKILL.
|
||||
# This function will mark the instance PID as -1 to remember that this instance
|
||||
# is no longer running and will remove its PID from the list of pids that
|
||||
# we kill at cleanup.
|
||||
#
|
||||
# The instance can be restarted with restart-instance.
|
||||
proc kill_instance {type id} {
|
||||
set pid [get_instance_attrib $type $id pid]
|
||||
if {$pid == -1} {
|
||||
error "You tried to kill $type $id twice."
|
||||
}
|
||||
exec kill -9 $pid
|
||||
set_instance_attrib $type $id pid -1
|
||||
set_instance_attrib $type $id link you_tried_to_talk_with_killed_instance
|
||||
|
||||
# Remove the PID from the list of pids to kill at exit.
|
||||
set ::pids [lsearch -all -inline -not -exact $::pids $pid]
|
||||
}
|
||||
|
||||
# Return true of the instance of the specified type/id is killed.
|
||||
proc instance_is_killed {type id} {
|
||||
set pid [get_instance_attrib $type $id pid]
|
||||
expr {$pid == -1}
|
||||
}
|
||||
|
||||
# Restart an instance previously killed by kill_instance
|
||||
proc restart_instance {type id} {
|
||||
set dirname "${type}_${id}"
|
||||
set cfgfile [file join $dirname $type.conf]
|
||||
set port [get_instance_attrib $type $id port]
|
||||
|
||||
# Execute the instance with its old setup and append the new pid
|
||||
# file for cleanup.
|
||||
if {$type eq "redis"} {
|
||||
set prgname redis-server
|
||||
} else {
|
||||
set prgname redis-sentinel
|
||||
}
|
||||
set pid [exec ../../../src/${prgname} $cfgfile &]
|
||||
set_instance_attrib $type $id pid $pid
|
||||
lappend ::pids $pid
|
||||
|
||||
# Check that the instance is running
|
||||
if {[server_is_up 127.0.0.1 $port 100] == 0} {
|
||||
abort_sentinel_test "Problems starting $type #$j: ping timeout"
|
||||
}
|
||||
|
||||
# Connect with it with a fresh link
|
||||
set_instance_attrib $type $id link [redis 127.0.0.1 $port]
|
||||
}
|
||||
|
||||
@@ -91,8 +91,13 @@ tags {"aof"} {
|
||||
assert_equal 1 [is_alive $srv]
|
||||
}
|
||||
|
||||
test "Fixed AOF: Keyspace should contain values that were parsable" {
|
||||
test "Fixed AOF: Keyspace should contain values that were parsable" {
|
||||
set client [redis [dict get $srv host] [dict get $srv port]]
|
||||
wait_for_condition 50 100 {
|
||||
[catch {$client ping} e] == 0
|
||||
} else {
|
||||
fail "Loading DB is taking too much time."
|
||||
}
|
||||
assert_equal "hello" [$client get foo]
|
||||
assert_equal "" [$client get bar]
|
||||
}
|
||||
@@ -112,6 +117,11 @@ tags {"aof"} {
|
||||
|
||||
test "AOF+SPOP: Set should have 1 member" {
|
||||
set client [redis [dict get $srv host] [dict get $srv port]]
|
||||
wait_for_condition 50 100 {
|
||||
[catch {$client ping} e] == 0
|
||||
} else {
|
||||
fail "Loading DB is taking too much time."
|
||||
}
|
||||
assert_equal 1 [$client scard set]
|
||||
}
|
||||
}
|
||||
@@ -130,6 +140,11 @@ tags {"aof"} {
|
||||
|
||||
test "AOF+EXPIRE: List should be empty" {
|
||||
set client [redis [dict get $srv host] [dict get $srv port]]
|
||||
wait_for_condition 50 100 {
|
||||
[catch {$client ping} e] == 0
|
||||
} else {
|
||||
fail "Loading DB is taking too much time."
|
||||
}
|
||||
assert_equal 0 [$client llen list]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# Sentinel test suite. Copyright (C) 2014 Salvatore Sanfilippo antirez@gmail.com
|
||||
# This softare is released under the BSD License. See the COPYING file for
|
||||
# more information.
|
||||
|
||||
cd tests/sentinel
|
||||
source ../instances.tcl
|
||||
|
||||
set ::instances_count 5 ; # How many instances we use at max.
|
||||
|
||||
proc main {} {
|
||||
parse_options
|
||||
spawn_instance sentinel $::sentinel_base_port $::instances_count
|
||||
spawn_instance redis $::redis_base_port $::instances_count
|
||||
run_tests
|
||||
cleanup
|
||||
}
|
||||
|
||||
if {[catch main e]} {
|
||||
puts $::errorInfo
|
||||
cleanup
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
# Check the basic monitoring and failover capabilities.
|
||||
|
||||
source "../tests/includes/init-tests.tcl"
|
||||
|
||||
if {$::simulate_error} {
|
||||
test "This test will fail" {
|
||||
fail "Simulated error"
|
||||
}
|
||||
}
|
||||
|
||||
test "Basic failover works if the master is down" {
|
||||
set old_port [RI $master_id tcp_port]
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
assert {[lindex $addr 1] == $old_port}
|
||||
kill_instance redis $master_id
|
||||
foreach_sentinel_id id {
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "At least one Sentinel did not received failover info"
|
||||
}
|
||||
}
|
||||
restart_instance redis $master_id
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
set master_id [get_instance_id_by_port redis [lindex $addr 1]]
|
||||
}
|
||||
|
||||
test "New master [join $addr {:}] role matches" {
|
||||
assert {[RI $master_id role] eq {master}}
|
||||
}
|
||||
|
||||
test "All the other slaves now point to the new master" {
|
||||
foreach_redis_id id {
|
||||
if {$id != $master_id && $id != 0} {
|
||||
wait_for_condition 1000 50 {
|
||||
[RI $id master_port] == [lindex $addr 1]
|
||||
} else {
|
||||
fail "Redis ID $id not configured to replicate with new master"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "The old master eventually gets reconfigured as a slave" {
|
||||
wait_for_condition 1000 50 {
|
||||
[RI 0 master_port] == [lindex $addr 1]
|
||||
} else {
|
||||
fail "Old master not reconfigured as slave of new master"
|
||||
}
|
||||
}
|
||||
|
||||
test "ODOWN is not possible without N (quorum) Sentinels reports" {
|
||||
foreach_sentinel_id id {
|
||||
S $id SENTINEL SET mymaster quorum [expr $sentinels+1]
|
||||
}
|
||||
set old_port [RI $master_id tcp_port]
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
assert {[lindex $addr 1] == $old_port}
|
||||
kill_instance redis $master_id
|
||||
|
||||
# Make sure failover did not happened.
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
assert {[lindex $addr 1] == $old_port}
|
||||
restart_instance redis $master_id
|
||||
}
|
||||
|
||||
test "Failover is not possible without majority agreement" {
|
||||
foreach_sentinel_id id {
|
||||
S $id SENTINEL SET mymaster quorum $quorum
|
||||
}
|
||||
|
||||
# Crash majority of sentinels
|
||||
for {set id 0} {$id < $quorum} {incr id} {
|
||||
kill_instance sentinel $id
|
||||
}
|
||||
|
||||
# Kill the current master
|
||||
kill_instance redis $master_id
|
||||
|
||||
# Make sure failover did not happened.
|
||||
set addr [S $quorum SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
assert {[lindex $addr 1] == $old_port}
|
||||
restart_instance redis $master_id
|
||||
|
||||
# Cleanup: restart Sentinels to monitor the master.
|
||||
for {set id 0} {$id < $quorum} {incr id} {
|
||||
restart_instance sentinel $id
|
||||
}
|
||||
}
|
||||
|
||||
test "Failover works if we configure for absolute agreement" {
|
||||
foreach_sentinel_id id {
|
||||
S $id SENTINEL SET mymaster quorum $sentinels
|
||||
}
|
||||
|
||||
# Wait for Sentinels to monitor the master again
|
||||
foreach_sentinel_id id {
|
||||
wait_for_condition 1000 50 {
|
||||
[dict get [S $id SENTINEL MASTER mymaster] info-refresh] < 100000
|
||||
} else {
|
||||
fail "At least one Sentinel is not monitoring the master"
|
||||
}
|
||||
}
|
||||
|
||||
kill_instance redis $master_id
|
||||
|
||||
foreach_sentinel_id id {
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "At least one Sentinel did not received failover info"
|
||||
}
|
||||
}
|
||||
restart_instance redis $master_id
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
set master_id [get_instance_id_by_port redis [lindex $addr 1]]
|
||||
|
||||
# Set the min ODOWN agreement back to strict majority.
|
||||
foreach_sentinel_id id {
|
||||
S $id SENTINEL SET mymaster quorum $quorum
|
||||
}
|
||||
}
|
||||
|
||||
test "New master [join $addr {:}] role matches" {
|
||||
assert {[RI $master_id role] eq {master}}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
# Test Sentinel configuration consistency after partitions heal.
|
||||
|
||||
source "../tests/includes/init-tests.tcl"
|
||||
|
||||
test "We can failover with Sentinel 1 crashed" {
|
||||
set old_port [RI $master_id tcp_port]
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
assert {[lindex $addr 1] == $old_port}
|
||||
|
||||
# Crash Sentinel 1
|
||||
kill_instance sentinel 1
|
||||
|
||||
kill_instance redis $master_id
|
||||
foreach_sentinel_id id {
|
||||
if {$id != 1} {
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "Sentinel $id did not received failover info"
|
||||
}
|
||||
}
|
||||
}
|
||||
restart_instance redis $master_id
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
set master_id [get_instance_id_by_port redis [lindex $addr 1]]
|
||||
}
|
||||
|
||||
test "After Sentinel 1 is restarted, its config gets updated" {
|
||||
restart_instance sentinel 1
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S 1 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "Restarted Sentinel did not received failover info"
|
||||
}
|
||||
}
|
||||
|
||||
test "New master [join $addr {:}] role matches" {
|
||||
assert {[RI $master_id role] eq {master}}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
# Check that slaves are reconfigured at a latter time if they are partitioned.
|
||||
#
|
||||
# Here we should test:
|
||||
# 1) That slaves point to the new master after failover.
|
||||
# 2) That partitioned slaves point to new master when they are partitioned
|
||||
# away during failover and return at a latter time.
|
||||
|
||||
source "../tests/includes/init-tests.tcl"
|
||||
|
||||
proc 03_test_slaves_replication {} {
|
||||
uplevel 1 {
|
||||
test "Check that slaves replicate from current master" {
|
||||
set master_port [RI $master_id tcp_port]
|
||||
foreach_redis_id id {
|
||||
if {$id == $master_id} continue
|
||||
if {[instance_is_killed redis $id]} continue
|
||||
wait_for_condition 1000 50 {
|
||||
[RI $id master_port] == $master_port
|
||||
} else {
|
||||
fail "Redis slave $id is replicating from wrong master"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc 03_crash_and_failover {} {
|
||||
uplevel 1 {
|
||||
test "Crash the master and force a failover" {
|
||||
set old_port [RI $master_id tcp_port]
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
assert {[lindex $addr 1] == $old_port}
|
||||
kill_instance redis $master_id
|
||||
foreach_sentinel_id id {
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "At least one Sentinel did not received failover info"
|
||||
}
|
||||
}
|
||||
restart_instance redis $master_id
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
set master_id [get_instance_id_by_port redis [lindex $addr 1]]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
03_test_slaves_replication
|
||||
03_crash_and_failover
|
||||
03_test_slaves_replication
|
||||
|
||||
test "Kill a slave instance" {
|
||||
foreach_redis_id id {
|
||||
if {$id == $master_id} continue
|
||||
set killed_slave_id $id
|
||||
kill_instance redis $id
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
03_crash_and_failover
|
||||
03_test_slaves_replication
|
||||
|
||||
test "Wait for failover to end" {
|
||||
set inprogress 1
|
||||
while {$inprogress} {
|
||||
set inprogress 0
|
||||
foreach_sentinel_id id {
|
||||
if {[dict exists [S $id SENTINEL MASTER mymaster] failover-state]} {
|
||||
incr inprogress
|
||||
}
|
||||
}
|
||||
if {$inprogress} {after 100}
|
||||
}
|
||||
}
|
||||
|
||||
test "Restart killed slave and test replication of slaves again..." {
|
||||
restart_instance redis $killed_slave_id
|
||||
}
|
||||
|
||||
# Now we check if the slave rejoining the partition is reconfigured even
|
||||
# if the failover finished.
|
||||
03_test_slaves_replication
|
||||
@@ -0,0 +1 @@
|
||||
# Test runtime reconfiguration command SENTINEL SET.
|
||||
@@ -0,0 +1,5 @@
|
||||
# Test slave selection algorithm.
|
||||
#
|
||||
# This unit should test:
|
||||
# 1) That when there are no suitable slaves no failover is performed.
|
||||
# 2) That among the available slaves, the one with better offset is picked.
|
||||
@@ -0,0 +1,67 @@
|
||||
# Initialization tests -- most units will start including this.
|
||||
|
||||
test "(init) Restart killed instances" {
|
||||
foreach type {redis sentinel} {
|
||||
foreach_${type}_id id {
|
||||
if {[get_instance_attrib $type $id pid] == -1} {
|
||||
puts -nonewline "$type/$id "
|
||||
flush stdout
|
||||
restart_instance $type $id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set redis_slaves 4
|
||||
test "(init) Create a master-slaves cluster of [expr $redis_slaves+1] instances" {
|
||||
create_redis_master_slave_cluster [expr {$redis_slaves+1}]
|
||||
}
|
||||
set master_id 0
|
||||
|
||||
test "(init) Sentinels can start monitoring a master" {
|
||||
set sentinels [llength $::sentinel_instances]
|
||||
set quorum [expr {$sentinels/2+1}]
|
||||
foreach_sentinel_id id {
|
||||
catch {S $id SENTINEL REMOVE mymaster}
|
||||
S $id SENTINEL MONITOR mymaster \
|
||||
[get_instance_attrib redis $master_id host] \
|
||||
[get_instance_attrib redis $master_id port] $quorum
|
||||
}
|
||||
foreach_sentinel_id id {
|
||||
assert {[S $id sentinel master mymaster] ne {}}
|
||||
S $id SENTINEL SET mymaster down-after-milliseconds 2000
|
||||
S $id SENTINEL SET mymaster failover-timeout 20000
|
||||
S $id SENTINEL SET mymaster parallel-syncs 10
|
||||
}
|
||||
}
|
||||
|
||||
test "(init) Sentinels can talk with the master" {
|
||||
foreach_sentinel_id id {
|
||||
wait_for_condition 100 50 {
|
||||
[catch {S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster}] == 0
|
||||
} else {
|
||||
fail "Sentinel $id can't talk with the master."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "(init) Sentinels are able to auto-discover other sentinels" {
|
||||
set sentinels [llength $::sentinel_instances]
|
||||
foreach_sentinel_id id {
|
||||
wait_for_condition 100 50 {
|
||||
[dict get [S $id SENTINEL MASTER mymaster] num-other-sentinels] == ($sentinels-1)
|
||||
} else {
|
||||
fail "At least some sentinel can't detect some other sentinel"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "(init) Sentinels are able to auto-discover slaves" {
|
||||
foreach_sentinel_id id {
|
||||
wait_for_condition 100 50 {
|
||||
[dict get [S $id SENTINEL MASTER mymaster] num-slaves] == $redis_slaves
|
||||
} else {
|
||||
fail "At least some sentinel can't detect some slave"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
redis_*
|
||||
sentinel_*
|
||||
@@ -0,0 +1,303 @@
|
||||
# Tcl redis cluster client as a wrapper of redis.rb.
|
||||
# Copyright (C) 2014 Salvatore Sanfilippo
|
||||
# Released under the BSD license like Redis itself
|
||||
#
|
||||
# Example usage:
|
||||
#
|
||||
# set c [redis_cluster 127.0.0.1 6379 127.0.0.1 6380]
|
||||
# $c set foo
|
||||
# $c get foo
|
||||
# $c close
|
||||
|
||||
package require Tcl 8.5
|
||||
package provide redis_cluster 0.1
|
||||
|
||||
namespace eval redis_cluster {}
|
||||
set ::redis_cluster::id 0
|
||||
array set ::redis_cluster::startup_nodes {}
|
||||
array set ::redis_cluster::nodes {}
|
||||
array set ::redis_cluster::slots {}
|
||||
|
||||
# List of "plain" commands, which are commands where the sole key is always
|
||||
# the first argument.
|
||||
set ::redis_cluster::plain_commands {
|
||||
get set setnx setex psetex append strlen exists setbit getbit
|
||||
setrange getrange substr incr decr rpush lpush rpushx lpushx
|
||||
linsert rpop lpop brpop llen lindex lset lrange ltrim lrem
|
||||
sadd srem sismember scard spop srandmember smembers sscan zadd
|
||||
zincrby zrem zremrangebyscore zremrangebyrank zremrangebylex zrange
|
||||
zrangebyscore zrevrangebyscore zrangebylex zrevrangebylex zcount
|
||||
zlexcount zrevrange zcard zscore zrank zrevrank zscan hset hsetnx
|
||||
hget hmset hmget hincrby hincrbyfloat hdel hlen hkeys hvals
|
||||
hgetall hexists hscan incrby decrby incrbyfloat getset move
|
||||
expire expireat pexpire pexpireat type ttl pttl persist restore
|
||||
dump bitcount bitpos pfadd pfcount
|
||||
}
|
||||
|
||||
proc redis_cluster {nodes} {
|
||||
set id [incr ::redis_cluster::id]
|
||||
set ::redis_cluster::startup_nodes($id) $nodes
|
||||
set ::redis_cluster::nodes($id) {}
|
||||
set ::redis_cluster::slots($id) {}
|
||||
set handle [interp alias {} ::redis_cluster::instance$id {} ::redis_cluster::__dispatch__ $id]
|
||||
$handle refresh_nodes_map
|
||||
return $handle
|
||||
}
|
||||
|
||||
# Totally reset the slots / nodes state for the client, calls
|
||||
# CLUSTER NODES in the first startup node available, populates the
|
||||
# list of nodes ::redis_cluster::nodes($id) with an hash mapping node
|
||||
# ip:port to a representation of the node (another hash), and finally
|
||||
# maps ::redis_cluster::slots($id) with an hash mapping slot numbers
|
||||
# to node IDs.
|
||||
#
|
||||
# This function is called when a new Redis Cluster client is initialized
|
||||
# and every time we get a -MOVED redirection error.
|
||||
proc ::redis_cluster::__method__refresh_nodes_map {id} {
|
||||
# Contact the first responding startup node.
|
||||
set idx 0; # Index of the node that will respond.
|
||||
set errmsg {}
|
||||
foreach start_node $::redis_cluster::startup_nodes($id) {
|
||||
lassign [split $start_node :] start_host start_port
|
||||
if {[catch {
|
||||
set r {}
|
||||
set r [redis $start_host $start_port]
|
||||
set nodes_descr [$r cluster nodes]
|
||||
$r close
|
||||
} e]} {
|
||||
if {$r ne {}} {catch {$r close}}
|
||||
incr idx
|
||||
if {[string length $errmsg] < 200} {
|
||||
append errmsg " $start_node: $e"
|
||||
}
|
||||
continue ; # Try next.
|
||||
} else {
|
||||
break; # Good node found.
|
||||
}
|
||||
}
|
||||
|
||||
if {$idx == [llength $::redis_cluster::startup_nodes($id)]} {
|
||||
error "No good startup node found. $errmsg"
|
||||
}
|
||||
|
||||
# Put the node that responded as first in the list if it is not
|
||||
# already the first.
|
||||
if {$idx != 0} {
|
||||
set l $::redis_cluster::startup_nodes($id)
|
||||
set left [lrange $l 0 [expr {$idx-1}]]
|
||||
set right [lrange $l [expr {$idx+1}] end]
|
||||
set l [concat [lindex $l $idx] $left $right]
|
||||
set ::redis_cluster::startup_nodes($id) $l
|
||||
}
|
||||
|
||||
# Parse CLUSTER NODES output to populate the nodes description.
|
||||
set nodes {} ; # addr -> node description hash.
|
||||
foreach line [split $nodes_descr "\n"] {
|
||||
set line [string trim $line]
|
||||
if {$line eq {}} continue
|
||||
set args [split $line " "]
|
||||
lassign $args nodeid addr flags slaveof pingsent pongrecv configepoch linkstate
|
||||
set slots [lrange $args 8 end]
|
||||
if {$addr eq {:0}} {
|
||||
set addr $start_host:$start_port
|
||||
}
|
||||
lassign [split $addr :] host port
|
||||
|
||||
# Connect to the node
|
||||
set link {}
|
||||
catch {set link [redis $host $port]}
|
||||
|
||||
# Build this node description as an hash.
|
||||
set node [dict create \
|
||||
id $nodeid \
|
||||
addr $addr \
|
||||
host $host \
|
||||
port $port \
|
||||
flags $flags \
|
||||
slaveof $slaveof \
|
||||
slots $slots \
|
||||
link $link \
|
||||
]
|
||||
dict set nodes $addr $node
|
||||
lappend ::redis_cluster::startup_nodes($id) $addr
|
||||
}
|
||||
|
||||
# Close all the existing links in the old nodes map, and set the new
|
||||
# map as current.
|
||||
foreach n $::redis_cluster::nodes($id) {
|
||||
catch {
|
||||
[dict get $n link] close
|
||||
}
|
||||
}
|
||||
set ::redis_cluster::nodes($id) $nodes
|
||||
|
||||
# Populates the slots -> nodes map.
|
||||
dict for {addr node} $nodes {
|
||||
foreach slotrange [dict get $node slots] {
|
||||
lassign [split $slotrange -] start end
|
||||
if {$end == {}} {set end $start}
|
||||
for {set j $start} {$j <= $end} {incr j} {
|
||||
dict set ::redis_cluster::slots($id) $j $addr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Only retain unique entries in the startup nodes list
|
||||
set ::redis_cluster::startup_nodes($id) [lsort -unique $::redis_cluster::startup_nodes($id)]
|
||||
}
|
||||
|
||||
# Free a redis_cluster handle.
|
||||
proc ::redis_cluster::__method__close {id} {
|
||||
catch {
|
||||
set nodes $::redis_cluster::nodes($id)
|
||||
dict for {addr node} $nodes {
|
||||
catch {
|
||||
[dict get $node link] close
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {unset ::redis_cluster::startup_nodes($id)}
|
||||
catch {unset ::redis_cluster::nodes($id)}
|
||||
catch {unset ::redis_cluster::slots($id)}
|
||||
catch {interp alias {} ::redis_cluster::instance$id {}}
|
||||
}
|
||||
|
||||
proc ::redis_cluster::__dispatch__ {id method args} {
|
||||
if {[info command ::redis_cluster::__method__$method] eq {}} {
|
||||
# Get the keys from the command.
|
||||
set keys [::redis_cluster::get_keys_from_command $method $args]
|
||||
if {$keys eq {}} {
|
||||
error "Redis command '$method' is not supported by redis_cluster."
|
||||
}
|
||||
|
||||
# Resolve the keys in the corresponding hash slot they hash to.
|
||||
set slot [::redis_cluster::get_slot_from_keys $keys]
|
||||
if {$slot eq {}} {
|
||||
error "Invalid command: multiple keys not hashing to the same slot."
|
||||
}
|
||||
|
||||
# Get the node mapped to this slot.
|
||||
set node_addr [dict get $::redis_cluster::slots($id) $slot]
|
||||
if {$node_addr eq {}} {
|
||||
error "No mapped node for slot $slot."
|
||||
}
|
||||
|
||||
# Execute the command in the node we think is the slot owner.
|
||||
set retry 100
|
||||
while {[incr retry -1]} {
|
||||
if {$retry < 5} {after 100}
|
||||
set node [dict get $::redis_cluster::nodes($id) $node_addr]
|
||||
set link [dict get $node link]
|
||||
if {[catch {$link $method {*}$args} e]} {
|
||||
if {$link eq {} || \
|
||||
[string range $e 0 4] eq {MOVED} || \
|
||||
[string range $e 0 2] eq {I/O} \
|
||||
} {
|
||||
# MOVED redirection.
|
||||
::redis_cluster::__method__refresh_nodes_map $id
|
||||
set node_addr [dict get $::redis_cluster::slots($id) $slot]
|
||||
continue
|
||||
} elseif {[string range $e 0 2] eq {ASK}} {
|
||||
# ASK redirection.
|
||||
set node_addr [lindex $e 2]
|
||||
continue
|
||||
} else {
|
||||
# Non redirecting error.
|
||||
error $e $::errorInfo $::errorCode
|
||||
}
|
||||
} else {
|
||||
# OK query went fine
|
||||
return $e
|
||||
}
|
||||
}
|
||||
error "Too many redirections or failures contacting Redis Cluster."
|
||||
} else {
|
||||
uplevel 1 [list ::redis_cluster::__method__$method $id] $args
|
||||
}
|
||||
}
|
||||
|
||||
proc ::redis_cluster::get_keys_from_command {cmd argv} {
|
||||
set cmd [string tolower $cmd]
|
||||
# Most Redis commands get just one key as first argument.
|
||||
if {[lsearch -exact $::redis_cluster::plain_commands $cmd] != -1} {
|
||||
return [list [lindex $argv 0]]
|
||||
}
|
||||
|
||||
# Special handling for other commands
|
||||
switch -exact $cmd {
|
||||
mget {return $argv}
|
||||
}
|
||||
|
||||
# All the remaining commands are not handled.
|
||||
return {}
|
||||
}
|
||||
|
||||
# Returns the CRC16 of the specified string.
|
||||
# The CRC parameters are described in the Redis Cluster specification.
|
||||
set ::redis_cluster::XMODEMCRC16Lookup {
|
||||
0x0000 0x1021 0x2042 0x3063 0x4084 0x50a5 0x60c6 0x70e7
|
||||
0x8108 0x9129 0xa14a 0xb16b 0xc18c 0xd1ad 0xe1ce 0xf1ef
|
||||
0x1231 0x0210 0x3273 0x2252 0x52b5 0x4294 0x72f7 0x62d6
|
||||
0x9339 0x8318 0xb37b 0xa35a 0xd3bd 0xc39c 0xf3ff 0xe3de
|
||||
0x2462 0x3443 0x0420 0x1401 0x64e6 0x74c7 0x44a4 0x5485
|
||||
0xa56a 0xb54b 0x8528 0x9509 0xe5ee 0xf5cf 0xc5ac 0xd58d
|
||||
0x3653 0x2672 0x1611 0x0630 0x76d7 0x66f6 0x5695 0x46b4
|
||||
0xb75b 0xa77a 0x9719 0x8738 0xf7df 0xe7fe 0xd79d 0xc7bc
|
||||
0x48c4 0x58e5 0x6886 0x78a7 0x0840 0x1861 0x2802 0x3823
|
||||
0xc9cc 0xd9ed 0xe98e 0xf9af 0x8948 0x9969 0xa90a 0xb92b
|
||||
0x5af5 0x4ad4 0x7ab7 0x6a96 0x1a71 0x0a50 0x3a33 0x2a12
|
||||
0xdbfd 0xcbdc 0xfbbf 0xeb9e 0x9b79 0x8b58 0xbb3b 0xab1a
|
||||
0x6ca6 0x7c87 0x4ce4 0x5cc5 0x2c22 0x3c03 0x0c60 0x1c41
|
||||
0xedae 0xfd8f 0xcdec 0xddcd 0xad2a 0xbd0b 0x8d68 0x9d49
|
||||
0x7e97 0x6eb6 0x5ed5 0x4ef4 0x3e13 0x2e32 0x1e51 0x0e70
|
||||
0xff9f 0xefbe 0xdfdd 0xcffc 0xbf1b 0xaf3a 0x9f59 0x8f78
|
||||
0x9188 0x81a9 0xb1ca 0xa1eb 0xd10c 0xc12d 0xf14e 0xe16f
|
||||
0x1080 0x00a1 0x30c2 0x20e3 0x5004 0x4025 0x7046 0x6067
|
||||
0x83b9 0x9398 0xa3fb 0xb3da 0xc33d 0xd31c 0xe37f 0xf35e
|
||||
0x02b1 0x1290 0x22f3 0x32d2 0x4235 0x5214 0x6277 0x7256
|
||||
0xb5ea 0xa5cb 0x95a8 0x8589 0xf56e 0xe54f 0xd52c 0xc50d
|
||||
0x34e2 0x24c3 0x14a0 0x0481 0x7466 0x6447 0x5424 0x4405
|
||||
0xa7db 0xb7fa 0x8799 0x97b8 0xe75f 0xf77e 0xc71d 0xd73c
|
||||
0x26d3 0x36f2 0x0691 0x16b0 0x6657 0x7676 0x4615 0x5634
|
||||
0xd94c 0xc96d 0xf90e 0xe92f 0x99c8 0x89e9 0xb98a 0xa9ab
|
||||
0x5844 0x4865 0x7806 0x6827 0x18c0 0x08e1 0x3882 0x28a3
|
||||
0xcb7d 0xdb5c 0xeb3f 0xfb1e 0x8bf9 0x9bd8 0xabbb 0xbb9a
|
||||
0x4a75 0x5a54 0x6a37 0x7a16 0x0af1 0x1ad0 0x2ab3 0x3a92
|
||||
0xfd2e 0xed0f 0xdd6c 0xcd4d 0xbdaa 0xad8b 0x9de8 0x8dc9
|
||||
0x7c26 0x6c07 0x5c64 0x4c45 0x3ca2 0x2c83 0x1ce0 0x0cc1
|
||||
0xef1f 0xff3e 0xcf5d 0xdf7c 0xaf9b 0xbfba 0x8fd9 0x9ff8
|
||||
0x6e17 0x7e36 0x4e55 0x5e74 0x2e93 0x3eb2 0x0ed1 0x1ef0
|
||||
}
|
||||
|
||||
proc ::redis_cluster::crc16 {s} {
|
||||
set s [encoding convertto ascii $s]
|
||||
set crc 0
|
||||
foreach char [split $s {}] {
|
||||
scan $char %c byte
|
||||
set crc [expr {(($crc<<8)&0xffff) ^ [lindex $::redis_cluster::XMODEMCRC16Lookup [expr {(($crc>>8)^$byte) & 0xff}]]}]
|
||||
}
|
||||
return $crc
|
||||
}
|
||||
|
||||
# Hash a single key returning the slot it belongs to, Implemented hash
|
||||
# tags as described in the Redis Cluster specification.
|
||||
proc ::redis_cluster::hash {key} {
|
||||
# TODO: Handle hash slots.
|
||||
expr {[::redis_cluster::crc16 $key] & 16383}
|
||||
}
|
||||
|
||||
# Return the slot the specified keys hash to.
|
||||
# If the keys hash to multiple slots, an empty string is returned to
|
||||
# signal that the command can't be run in Redis Cluster.
|
||||
proc ::redis_cluster::get_slot_from_keys {keys} {
|
||||
set slot {}
|
||||
foreach k $keys {
|
||||
set s [::redis_cluster::hash $k]
|
||||
if {$slot eq {}} {
|
||||
set slot $s
|
||||
} elseif {$slot != $s} {
|
||||
return {} ; # Error
|
||||
}
|
||||
}
|
||||
return $slot
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
# Tcl clinet library - used by test-redis.tcl script for now
|
||||
# Copyright (C) 2009 Salvatore Sanfilippo
|
||||
# Tcl client library - used by the Redis test
|
||||
# Copyright (C) 2009-2014 Salvatore Sanfilippo
|
||||
# Released under the BSD license like Redis itself
|
||||
#
|
||||
# Example usage:
|
||||
@@ -170,7 +170,10 @@ proc ::redis::redis_read_reply fd {
|
||||
- {return -code error [redis_read_line $fd]}
|
||||
$ {redis_bulk_read $fd}
|
||||
* {redis_multi_bulk_read $fd}
|
||||
default {return -code error "Bad protocol, '$type' as reply type byte"}
|
||||
default {
|
||||
if {$type eq {}} {return -code error "I/O error reading reply"}
|
||||
return -code error "Bad protocol, '$type' as reply type byte"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+24
-13
@@ -40,6 +40,11 @@ proc kill_server config {
|
||||
test "Check for memory leaks (pid $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"
|
||||
}
|
||||
set output
|
||||
} {*0 leaks*}
|
||||
}
|
||||
@@ -79,7 +84,7 @@ proc is_alive config {
|
||||
proc ping_server {host port} {
|
||||
set retval 0
|
||||
if {[catch {
|
||||
set fd [socket $::host $::port]
|
||||
set fd [socket $host $port]
|
||||
fconfigure $fd -translation binary
|
||||
puts $fd "PING\r\n"
|
||||
flush $fd
|
||||
@@ -101,6 +106,22 @@ proc ping_server {host port} {
|
||||
return $retval
|
||||
}
|
||||
|
||||
# Return 1 if the server at the specified addr is reachable by PING, otherwise
|
||||
# returns 0. Performs a try every 50 milliseconds for the specified number
|
||||
# of retries.
|
||||
proc server_is_up {host port retrynum} {
|
||||
after 10 ;# Use a small delay to make likely a first-try success.
|
||||
set retval 0
|
||||
while {[incr retrynum -1]} {
|
||||
if {[catch {ping_server $host $port} ping]} {
|
||||
set ping 0
|
||||
}
|
||||
if {$ping} {return 1}
|
||||
after 50
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
# doesn't really belong here, but highly coupled to code in start_server
|
||||
proc tags {tags code} {
|
||||
set ::tags [concat $::tags $tags]
|
||||
@@ -191,23 +212,13 @@ proc start_server {options {code undefined}} {
|
||||
# check that the server actually started
|
||||
# ugly but tries to be as fast as possible...
|
||||
if {$::valgrind} {set retrynum 1000} else {set retrynum 100}
|
||||
set serverisup 0
|
||||
|
||||
if {$::verbose} {
|
||||
puts -nonewline "=== ($tags) Starting server ${::host}:${::port} "
|
||||
}
|
||||
|
||||
after 10
|
||||
if {$code ne "undefined"} {
|
||||
while {[incr retrynum -1]} {
|
||||
catch {
|
||||
if {[ping_server $::host $::port]} {
|
||||
set serverisup 1
|
||||
}
|
||||
}
|
||||
if {$serverisup} break
|
||||
after 50
|
||||
}
|
||||
set serverisup [server_is_up $::host $::port $retrynum]
|
||||
} else {
|
||||
set serverisup 1
|
||||
}
|
||||
@@ -225,7 +236,7 @@ proc start_server {options {code undefined}} {
|
||||
|
||||
# find out the pid
|
||||
while {![info exists pid]} {
|
||||
regexp {\[(\d+)\]} [exec cat $stdout] _ pid
|
||||
regexp {PID:\s(\d+)} [exec cat $stdout] _ pid
|
||||
after 100
|
||||
}
|
||||
|
||||
|
||||
+8
-32
@@ -53,41 +53,17 @@ proc assert_type {type key} {
|
||||
# executed.
|
||||
proc wait_for_condition {maxtries delay e _else_ elsescript} {
|
||||
while {[incr maxtries -1] >= 0} {
|
||||
if {[uplevel 1 [list expr $e]]} break
|
||||
set errcode [catch {uplevel 1 [list expr $e]} result]
|
||||
if {$errcode == 0} {
|
||||
if {$result} break
|
||||
} else {
|
||||
return -code $errcode $result
|
||||
}
|
||||
after $delay
|
||||
}
|
||||
if {$maxtries == -1} {
|
||||
uplevel 1 $elsescript
|
||||
}
|
||||
}
|
||||
|
||||
# Test if TERM looks like to support colors
|
||||
proc color_term {} {
|
||||
expr {[info exists ::env(TERM)] && [string match *xterm* $::env(TERM)]}
|
||||
}
|
||||
|
||||
proc colorstr {color str} {
|
||||
if {[color_term]} {
|
||||
set b 0
|
||||
if {[string range $color 0 4] eq {bold-}} {
|
||||
set b 1
|
||||
set color [string range $color 5 end]
|
||||
}
|
||||
switch $color {
|
||||
red {set colorcode {31}}
|
||||
green {set colorcode {32}}
|
||||
yellow {set colorcode {33}}
|
||||
blue {set colorcode {34}}
|
||||
magenta {set colorcode {35}}
|
||||
cyan {set colorcode {36}}
|
||||
white {set colorcode {37}}
|
||||
default {set colorcode {37}}
|
||||
}
|
||||
if {$colorcode ne {}} {
|
||||
return "\033\[$b;${colorcode};40m$str\033\[0m"
|
||||
}
|
||||
} else {
|
||||
return $str
|
||||
set errcode [catch [uplevel 1 $elsescript] result]
|
||||
return -code $errcode $result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -312,3 +312,48 @@ proc csvstring s {
|
||||
proc roundFloat f {
|
||||
format "%.10g" $f
|
||||
}
|
||||
|
||||
proc find_available_port start {
|
||||
for {set j $start} {$j < $start+1024} {incr j} {
|
||||
if {[catch {
|
||||
set fd [socket 127.0.0.1 $j]
|
||||
}]} {
|
||||
return $j
|
||||
} else {
|
||||
close $fd
|
||||
}
|
||||
}
|
||||
if {$j == $start+1024} {
|
||||
error "Can't find a non busy port in the $start-[expr {$start+1023}] range."
|
||||
}
|
||||
}
|
||||
|
||||
# Test if TERM looks like to support colors
|
||||
proc color_term {} {
|
||||
expr {[info exists ::env(TERM)] && [string match *xterm* $::env(TERM)]}
|
||||
}
|
||||
|
||||
proc colorstr {color str} {
|
||||
if {[color_term]} {
|
||||
set b 0
|
||||
if {[string range $color 0 4] eq {bold-}} {
|
||||
set b 1
|
||||
set color [string range $color 5 end]
|
||||
}
|
||||
switch $color {
|
||||
red {set colorcode {31}}
|
||||
green {set colorcode {32}}
|
||||
yellow {set colorcode {33}}
|
||||
blue {set colorcode {34}}
|
||||
magenta {set colorcode {35}}
|
||||
cyan {set colorcode {36}}
|
||||
white {set colorcode {37}}
|
||||
default {set colorcode {37}}
|
||||
}
|
||||
if {$colorcode ne {}} {
|
||||
return "\033\[$b;${colorcode};40m$str\033\[0m"
|
||||
}
|
||||
} else {
|
||||
return $str
|
||||
}
|
||||
}
|
||||
|
||||
+1
-15
@@ -47,6 +47,7 @@ set ::all_tests {
|
||||
unit/obuf-limits
|
||||
unit/bitops
|
||||
unit/memefficiency
|
||||
unit/hyperloglog
|
||||
}
|
||||
# Index to the next test to run in the ::all_tests list.
|
||||
set ::next_test 0
|
||||
@@ -164,21 +165,6 @@ proc cleanup {} {
|
||||
if {!$::quiet} {puts "OK"}
|
||||
}
|
||||
|
||||
proc find_available_port start {
|
||||
for {set j $start} {$j < $start+1024} {incr j} {
|
||||
if {[catch {
|
||||
set fd [socket 127.0.0.1 $j]
|
||||
}]} {
|
||||
return $j
|
||||
} else {
|
||||
close $fd
|
||||
}
|
||||
}
|
||||
if {$j == $start+1024} {
|
||||
error "Can't find a non busy port in the $start-[expr {$start+1023}] range."
|
||||
}
|
||||
}
|
||||
|
||||
proc test_server_main {} {
|
||||
cleanup
|
||||
set tclsh [info nameofexecutable]
|
||||
|
||||
@@ -261,6 +261,14 @@ start_server {tags {"basic"}} {
|
||||
assert_equal 20 [r get x]
|
||||
}
|
||||
|
||||
test "DEL against expired key" {
|
||||
r debug set-active-expire 0
|
||||
r setex keyExpire 1 valExpire
|
||||
after 1100
|
||||
assert_equal 0 [r del keyExpire]
|
||||
r debug set-active-expire 1
|
||||
}
|
||||
|
||||
test {EXISTS} {
|
||||
set res {}
|
||||
r set newkey test
|
||||
|
||||
+162
-1
@@ -52,7 +52,7 @@ start_server {tags {"bitops"}} {
|
||||
}
|
||||
}
|
||||
|
||||
test {BITCOUNT fuzzing} {
|
||||
test {BITCOUNT fuzzing without start/end} {
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
set str [randstring 0 3000]
|
||||
r set str $str
|
||||
@@ -60,6 +60,20 @@ start_server {tags {"bitops"}} {
|
||||
}
|
||||
}
|
||||
|
||||
test {BITCOUNT fuzzing with start/end} {
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
set str [randstring 0 3000]
|
||||
r set str $str
|
||||
set l [string length $str]
|
||||
set start [randomInt $l]
|
||||
set end [randomInt $l]
|
||||
if {$start > $end} {
|
||||
lassign [list $end $start] start end
|
||||
}
|
||||
assert {[r bitcount str $start $end] == [count_bits [string range $str $start $end]]}
|
||||
}
|
||||
}
|
||||
|
||||
test {BITCOUNT with start, end} {
|
||||
r set s "foobar"
|
||||
assert_equal [r bitcount s 0 -1] [count_bits "foobar"]
|
||||
@@ -84,6 +98,18 @@ start_server {tags {"bitops"}} {
|
||||
}
|
||||
} {1}
|
||||
|
||||
test {BITCOUNT misaligned prefix} {
|
||||
r del str
|
||||
r set str ab
|
||||
r bitcount str 1 -1
|
||||
} {3}
|
||||
|
||||
test {BITCOUNT misaligned prefix + full words + remainder} {
|
||||
r del str
|
||||
r set str __PPxxxxxxxxxxxxxxxxRR__
|
||||
r bitcount str 2 -3
|
||||
} {74}
|
||||
|
||||
test {BITOP NOT (empty string)} {
|
||||
r set s ""
|
||||
r bitop not dest s
|
||||
@@ -177,4 +203,139 @@ start_server {tags {"bitops"}} {
|
||||
r set a "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
r bitop or x a b
|
||||
} {32}
|
||||
|
||||
test {BITPOS bit=0 with empty key returns 0} {
|
||||
r del str
|
||||
r bitpos str 0
|
||||
} {0}
|
||||
|
||||
test {BITPOS bit=1 with empty key returns -1} {
|
||||
r del str
|
||||
r bitpos str 1
|
||||
} {-1}
|
||||
|
||||
test {BITPOS bit=0 with string less than 1 word works} {
|
||||
r set str "\xff\xf0\x00"
|
||||
r bitpos str 0
|
||||
} {12}
|
||||
|
||||
test {BITPOS bit=1 with string less than 1 word works} {
|
||||
r set str "\x00\x0f\x00"
|
||||
r bitpos str 1
|
||||
} {12}
|
||||
|
||||
test {BITPOS bit=0 starting at unaligned address} {
|
||||
r set str "\xff\xf0\x00"
|
||||
r bitpos str 0 1
|
||||
} {12}
|
||||
|
||||
test {BITPOS bit=1 starting at unaligned address} {
|
||||
r set str "\x00\x0f\xff"
|
||||
r bitpos str 1 1
|
||||
} {12}
|
||||
|
||||
test {BITPOS bit=0 unaligned+full word+reminder} {
|
||||
r del str
|
||||
r set str "\xff\xff\xff" ; # Prefix
|
||||
# Followed by two (or four in 32 bit systems) full words
|
||||
r append str "\xff\xff\xff\xff\xff\xff\xff\xff"
|
||||
r append str "\xff\xff\xff\xff\xff\xff\xff\xff"
|
||||
r append str "\xff\xff\xff\xff\xff\xff\xff\xff"
|
||||
# First zero bit.
|
||||
r append str "\x0f"
|
||||
assert {[r bitpos str 0] == 216}
|
||||
assert {[r bitpos str 0 1] == 216}
|
||||
assert {[r bitpos str 0 2] == 216}
|
||||
assert {[r bitpos str 0 3] == 216}
|
||||
assert {[r bitpos str 0 4] == 216}
|
||||
assert {[r bitpos str 0 5] == 216}
|
||||
assert {[r bitpos str 0 6] == 216}
|
||||
assert {[r bitpos str 0 7] == 216}
|
||||
assert {[r bitpos str 0 8] == 216}
|
||||
}
|
||||
|
||||
test {BITPOS bit=1 unaligned+full word+reminder} {
|
||||
r del str
|
||||
r set str "\x00\x00\x00" ; # Prefix
|
||||
# Followed by two (or four in 32 bit systems) full words
|
||||
r append str "\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
r append str "\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
r append str "\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
# First zero bit.
|
||||
r append str "\xf0"
|
||||
assert {[r bitpos str 1] == 216}
|
||||
assert {[r bitpos str 1 1] == 216}
|
||||
assert {[r bitpos str 1 2] == 216}
|
||||
assert {[r bitpos str 1 3] == 216}
|
||||
assert {[r bitpos str 1 4] == 216}
|
||||
assert {[r bitpos str 1 5] == 216}
|
||||
assert {[r bitpos str 1 6] == 216}
|
||||
assert {[r bitpos str 1 7] == 216}
|
||||
assert {[r bitpos str 1 8] == 216}
|
||||
}
|
||||
|
||||
test {BITPOS bit=1 returns -1 if string is all 0 bits} {
|
||||
r set str ""
|
||||
for {set j 0} {$j < 20} {incr j} {
|
||||
assert {[r bitpos str 1] == -1}
|
||||
r append str "\x00"
|
||||
}
|
||||
}
|
||||
|
||||
test {BITPOS bit=0 works with intervals} {
|
||||
r set str "\x00\xff\x00"
|
||||
assert {[r bitpos str 0 0 -1] == 0}
|
||||
assert {[r bitpos str 0 1 -1] == 16}
|
||||
assert {[r bitpos str 0 2 -1] == 16}
|
||||
assert {[r bitpos str 0 2 200] == 16}
|
||||
assert {[r bitpos str 0 1 1] == -1}
|
||||
}
|
||||
|
||||
test {BITPOS bit=1 works with intervals} {
|
||||
r set str "\x00\xff\x00"
|
||||
assert {[r bitpos str 1 0 -1] == 8}
|
||||
assert {[r bitpos str 1 1 -1] == 8}
|
||||
assert {[r bitpos str 1 2 -1] == -1}
|
||||
assert {[r bitpos str 1 2 200] == -1}
|
||||
assert {[r bitpos str 1 1 1] == 8}
|
||||
}
|
||||
|
||||
test {BITPOS bit=0 changes behavior if end is given} {
|
||||
r set str "\xff\xff\xff"
|
||||
assert {[r bitpos str 0] == 24}
|
||||
assert {[r bitpos str 0 0] == 24}
|
||||
assert {[r bitpos str 0 0 -1] == -1}
|
||||
}
|
||||
|
||||
test {BITPOS bit=1 fuzzy testing using SETBIT} {
|
||||
r del str
|
||||
set max 524288; # 64k
|
||||
set first_one_pos -1
|
||||
for {set j 0} {$j < 1000} {incr j} {
|
||||
assert {[r bitpos str 1] == $first_one_pos}
|
||||
set pos [randomInt $max]
|
||||
r setbit str $pos 1
|
||||
if {$first_one_pos == -1 || $first_one_pos > $pos} {
|
||||
# Update the position of the first 1 bit in the array
|
||||
# if the bit we set is on the left of the previous one.
|
||||
set first_one_pos $pos
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {BITPOS bit=0 fuzzy testing using SETBIT} {
|
||||
set max 524288; # 64k
|
||||
set first_zero_pos $max
|
||||
r set str [string repeat "\xff" [expr $max/8]]
|
||||
for {set j 0} {$j < 1000} {incr j} {
|
||||
assert {[r bitpos str 0] == $first_zero_pos}
|
||||
set pos [randomInt $max]
|
||||
r setbit str $pos 0
|
||||
if {$first_zero_pos > $pos} {
|
||||
# Update the position of the first 0 bit in the array
|
||||
# if the bit we clear is on the left of the previous one.
|
||||
set first_zero_pos $pos
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ start_server {tags {"dump"}} {
|
||||
set e {}
|
||||
catch {r restore foo 0 "..."} e
|
||||
set e
|
||||
} {*is busy*}
|
||||
} {*BUSYKEY*}
|
||||
|
||||
test {RESTORE can overwrite an existing key with REPLACE} {
|
||||
r set foo bar1
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
start_server {tags {"hll"}} {
|
||||
test {HyperLogLog self test passes} {
|
||||
catch {r pfselftest} e
|
||||
set e
|
||||
} {OK}
|
||||
|
||||
test {PFADD without arguments creates an HLL value} {
|
||||
r pfadd hll
|
||||
r exists hll
|
||||
} {1}
|
||||
|
||||
test {Approximated cardinality after creation is zero} {
|
||||
r pfcount hll
|
||||
} {0}
|
||||
|
||||
test {PFADD returns 1 when at least 1 reg was modified} {
|
||||
r pfadd hll a b c
|
||||
} {1}
|
||||
|
||||
test {PFADD returns 0 when no reg was modified} {
|
||||
r pfadd hll a b c
|
||||
} {0}
|
||||
|
||||
test {PFADD works with empty string (regression)} {
|
||||
r pfadd hll ""
|
||||
}
|
||||
|
||||
# Note that the self test stresses much better the
|
||||
# cardinality estimation error. We are testing just the
|
||||
# command implementation itself here.
|
||||
test {PFCOUNT returns approximated cardinality of set} {
|
||||
r del hll
|
||||
set res {}
|
||||
r pfadd hll 1 2 3 4 5
|
||||
lappend res [r pfcount hll]
|
||||
# Call it again to test cached value invalidation.
|
||||
r pfadd hll 6 7 8 8 9 10
|
||||
lappend res [r pfcount hll]
|
||||
set res
|
||||
} {5 10}
|
||||
|
||||
test {HyperLogLogs are promote from sparse to dense} {
|
||||
r del hll
|
||||
r config set hll-sparse-max-bytes 3000
|
||||
set n 0
|
||||
while {$n < 100000} {
|
||||
set elements {}
|
||||
for {set j 0} {$j < 100} {incr j} {lappend elements [expr rand()]}
|
||||
incr n 100
|
||||
r pfadd hll {*}$elements
|
||||
set card [r pfcount hll]
|
||||
set err [expr {abs($card-$n)}]
|
||||
assert {$err < (double($card)/100)*5}
|
||||
if {$n < 1000} {
|
||||
assert {[r pfdebug encoding hll] eq {sparse}}
|
||||
} elseif {$n > 10000} {
|
||||
assert {[r pfdebug encoding hll] eq {dense}}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {HyperLogLog sparse encoding stress test} {
|
||||
for {set x 0} {$x < 1000} {incr x} {
|
||||
r del hll1 hll2
|
||||
set numele [randomInt 100]
|
||||
set elements {}
|
||||
for {set j 0} {$j < $numele} {incr j} {
|
||||
lappend elements [expr rand()]
|
||||
}
|
||||
# Force dense representation of hll2
|
||||
r pfadd hll2
|
||||
r pfdebug todense hll2
|
||||
r pfadd hll1 {*}$elements
|
||||
r pfadd hll2 {*}$elements
|
||||
assert {[r pfdebug encoding hll1] eq {sparse}}
|
||||
assert {[r pfdebug encoding hll2] eq {dense}}
|
||||
# Cardinality estimated should match exactly.
|
||||
assert {[r pfcount hll1] eq [r pfcount hll2]}
|
||||
}
|
||||
}
|
||||
|
||||
test {Corrupted sparse HyperLogLogs are detected: Additionl at tail} {
|
||||
r del hll
|
||||
r pfadd hll a b c
|
||||
r append hll "hello"
|
||||
set e {}
|
||||
catch {r pfcount hll} e
|
||||
set e
|
||||
} {*INVALIDOBJ*}
|
||||
|
||||
test {Corrupted sparse HyperLogLogs are detected: Broken magic} {
|
||||
r del hll
|
||||
r pfadd hll a b c
|
||||
r setrange hll 0 "0123"
|
||||
set e {}
|
||||
catch {r pfcount hll} e
|
||||
set e
|
||||
} {*WRONGTYPE*}
|
||||
|
||||
test {Corrupted sparse HyperLogLogs are detected: Invalid encoding} {
|
||||
r del hll
|
||||
r pfadd hll a b c
|
||||
r setrange hll 4 "x"
|
||||
set e {}
|
||||
catch {r pfcount hll} e
|
||||
set e
|
||||
} {*WRONGTYPE*}
|
||||
|
||||
test {Corrupted dense HyperLogLogs are detected: Wrong length} {
|
||||
r del hll
|
||||
r pfadd hll a b c
|
||||
r setrange hll 4 "\x00"
|
||||
set e {}
|
||||
catch {r pfcount hll} e
|
||||
set e
|
||||
} {*WRONGTYPE*}
|
||||
|
||||
test {PFADD, PFCOUNT, PFMERGE type checking works} {
|
||||
r set foo bar
|
||||
catch {r pfadd foo 1} e
|
||||
assert_match {*WRONGTYPE*} $e
|
||||
catch {r pfcount foo} e
|
||||
assert_match {*WRONGTYPE*} $e
|
||||
catch {r pfmerge bar foo} e
|
||||
assert_match {*WRONGTYPE*} $e
|
||||
catch {r pfmerge foo bar} e
|
||||
assert_match {*WRONGTYPE*} $e
|
||||
}
|
||||
|
||||
test {PFMERGE results on the cardinality of union of sets} {
|
||||
r del hll hll1 hll2 hll3
|
||||
r pfadd hll1 a b c
|
||||
r pfadd hll2 b c d
|
||||
r pfadd hll3 c d e
|
||||
r pfmerge hll hll1 hll2 hll3
|
||||
r pfcount hll
|
||||
} {5}
|
||||
|
||||
test {PFCOUNT multiple-keys merge returns cardinality of union} {
|
||||
r del hll1 hll2 hll3
|
||||
for {set x 1} {$x < 10000} {incr x} {
|
||||
# Force dense representation of hll2
|
||||
r pfadd hll1 "foo-$x"
|
||||
r pfadd hll2 "bar-$x"
|
||||
r pfadd hll3 "zap-$x"
|
||||
|
||||
set card [r pfcount hll1 hll2 hll3]
|
||||
set realcard [expr {$x*3}]
|
||||
set err [expr {abs($card-$realcard)}]
|
||||
assert {$err < (double($card)/100)*5}
|
||||
}
|
||||
}
|
||||
|
||||
test {PFDEBUG GETREG returns the HyperLogLog raw registers} {
|
||||
r del hll
|
||||
r pfadd hll 1 2 3
|
||||
llength [r pfdebug getreg hll]
|
||||
} {16384}
|
||||
|
||||
test {PFADD / PFCOUNT cache invalidation works} {
|
||||
r del hll
|
||||
r pfadd hll a b c
|
||||
r pfcount hll
|
||||
assert {[r getrange hll 15 15] eq "\x00"}
|
||||
r pfadd hll a b c
|
||||
assert {[r getrange hll 15 15] eq "\x00"}
|
||||
r pfadd hll 1 2 3
|
||||
assert {[r getrange hll 15 15] eq "\x80"}
|
||||
}
|
||||
}
|
||||
@@ -311,8 +311,34 @@ start_server {tags {"scripting"}} {
|
||||
r config set slave-read-only yes
|
||||
r slaveof 127.0.0.1 0
|
||||
r debug loadaof
|
||||
r get foo
|
||||
set res [r get foo]
|
||||
r slaveof no one
|
||||
set res
|
||||
} {102}
|
||||
|
||||
test {We can call scripts rewriting client->argv from Lua} {
|
||||
r del myset
|
||||
r sadd myset a b c
|
||||
r mset a 1 b 2 c 3 d 4
|
||||
assert {[r spop myset] ne {}}
|
||||
assert {[r spop myset] ne {}}
|
||||
assert {[r spop myset] ne {}}
|
||||
assert {[r mget a b c d] eq {1 2 3 4}}
|
||||
assert {[r spop myset] eq {}}
|
||||
}
|
||||
|
||||
test {Call Redis command with many args from Lua (issue #1764)} {
|
||||
r eval {
|
||||
local i
|
||||
local x={}
|
||||
redis.call('del','mylist')
|
||||
for i=1,100 do
|
||||
table.insert(x,i)
|
||||
end
|
||||
redis.call('rpush','mylist',unpack(x))
|
||||
return redis.call('lrange','mylist',0,-1)
|
||||
} 0
|
||||
} {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100}
|
||||
}
|
||||
|
||||
# Start a new server since the last test in this stanza will kill the
|
||||
@@ -326,6 +352,7 @@ start_server {tags {"scripting"}} {
|
||||
catch {r ping} e
|
||||
assert_match {BUSY*} $e
|
||||
r script kill
|
||||
after 200 ; # Give some time to Lua to call the hook again...
|
||||
assert_equal [r ping] "PONG"
|
||||
}
|
||||
|
||||
@@ -417,5 +444,17 @@ start_server {tags {"scripting repl"}} {
|
||||
}
|
||||
set res
|
||||
} {a 1}
|
||||
|
||||
test {EVALSHA replication when first call is readonly} {
|
||||
r del x
|
||||
r eval {if tonumber(KEYS[1]) > 0 then redis.call('incr', 'x') end} 1 0
|
||||
r evalsha 38fe3ddf5284a1d48f37f824b4c4e826879f3cb9 1 0
|
||||
r evalsha 38fe3ddf5284a1d48f37f824b4c4e826879f3cb9 1 1
|
||||
wait_for_condition 50 100 {
|
||||
[r -1 get x] eq {1}
|
||||
} else {
|
||||
fail "Expected 1 in x, but value is '[r -1 get x]'"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,6 +296,62 @@ start_server {tags {"zset"}} {
|
||||
assert_error "*not*float*" {r zrangebyscore fooz 1 NaN}
|
||||
}
|
||||
|
||||
proc create_default_lex_zset {} {
|
||||
create_zset zset {0 alpha 0 bar 0 cool 0 down
|
||||
0 elephant 0 foo 0 great 0 hill
|
||||
0 omega}
|
||||
}
|
||||
|
||||
test "ZRANGEBYLEX/ZREVRANGEBYLEX/ZCOUNT basics" {
|
||||
create_default_lex_zset
|
||||
|
||||
# inclusive range
|
||||
assert_equal {alpha bar cool} [r zrangebylex zset - \[cool]
|
||||
assert_equal {bar cool down} [r zrangebylex zset \[bar \[down]
|
||||
assert_equal {great hill omega} [r zrangebylex zset \[g +]
|
||||
assert_equal {cool bar alpha} [r zrevrangebylex zset \[cool -]
|
||||
assert_equal {down cool bar} [r zrevrangebylex zset \[down \[bar]
|
||||
assert_equal {omega hill great foo elephant down} [r zrevrangebylex zset + \[d]
|
||||
assert_equal 3 [r zlexcount zset \[ele \[h]
|
||||
|
||||
# exclusive range
|
||||
assert_equal {alpha bar} [r zrangebylex zset - (cool]
|
||||
assert_equal {cool} [r zrangebylex zset (bar (down]
|
||||
assert_equal {hill omega} [r zrangebylex zset (great +]
|
||||
assert_equal {bar alpha} [r zrevrangebylex zset (cool -]
|
||||
assert_equal {cool} [r zrevrangebylex zset (down (bar]
|
||||
assert_equal {omega hill} [r zrevrangebylex zset + (great]
|
||||
assert_equal 2 [r zlexcount zset (ele (great]
|
||||
|
||||
# inclusive and exclusive
|
||||
assert_equal {} [r zrangebylex zset (az (b]
|
||||
assert_equal {} [r zrangebylex zset (z +]
|
||||
assert_equal {} [r zrangebylex zset - \[aaaa]
|
||||
assert_equal {} [r zrevrangebylex zset \[elez \[elex]
|
||||
assert_equal {} [r zrevrangebylex zset (hill (omega]
|
||||
}
|
||||
|
||||
test "ZRANGEBYSLEX with LIMIT" {
|
||||
create_default_lex_zset
|
||||
assert_equal {alpha bar} [r zrangebylex zset - \[cool LIMIT 0 2]
|
||||
assert_equal {bar cool} [r zrangebylex zset - \[cool LIMIT 1 2]
|
||||
assert_equal {} [r zrangebylex zset \[bar \[down LIMIT 0 0]
|
||||
assert_equal {} [r zrangebylex zset \[bar \[down LIMIT 2 0]
|
||||
assert_equal {bar} [r zrangebylex zset \[bar \[down LIMIT 0 1]
|
||||
assert_equal {cool} [r zrangebylex zset \[bar \[down LIMIT 1 1]
|
||||
assert_equal {bar cool down} [r zrangebylex zset \[bar \[down LIMIT 0 100]
|
||||
assert_equal {omega hill great foo elephant} [r zrevrangebylex zset + \[d LIMIT 0 5]
|
||||
assert_equal {omega hill great foo} [r zrevrangebylex zset + \[d LIMIT 0 4]
|
||||
}
|
||||
|
||||
test "ZRANGEBYLEX with invalid lex range specifiers" {
|
||||
assert_error "*not*string*" {r zrangebylex fooz foo bar}
|
||||
assert_error "*not*string*" {r zrangebylex fooz \[foo bar}
|
||||
assert_error "*not*string*" {r zrangebylex fooz foo \[bar}
|
||||
assert_error "*not*string*" {r zrangebylex fooz +x \[bar}
|
||||
assert_error "*not*string*" {r zrangebylex fooz -x \[bar}
|
||||
}
|
||||
|
||||
test "ZREMRANGEBYSCORE basics" {
|
||||
proc remrangebyscore {min max} {
|
||||
create_zset zset {1 a 2 b 3 c 4 d 5 e}
|
||||
@@ -708,6 +764,111 @@ start_server {tags {"zset"}} {
|
||||
assert_equal {} $err
|
||||
}
|
||||
|
||||
test "ZRANGEBYLEX fuzzy test, 100 ranges in $elements element sorted set - $encoding" {
|
||||
set lexset {}
|
||||
r del zset
|
||||
for {set j 0} {$j < $elements} {incr j} {
|
||||
set e [randstring 0 30 alpha]
|
||||
lappend lexset $e
|
||||
r zadd zset 0 $e
|
||||
}
|
||||
set lexset [lsort -unique $lexset]
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
set min [randstring 0 30 alpha]
|
||||
set max [randstring 0 30 alpha]
|
||||
set mininc [randomInt 2]
|
||||
set maxinc [randomInt 2]
|
||||
if {$mininc} {set cmin "\[$min"} else {set cmin "($min"}
|
||||
if {$maxinc} {set cmax "\[$max"} else {set cmax "($max"}
|
||||
set rev [randomInt 2]
|
||||
if {$rev} {
|
||||
set cmd zrevrangebylex
|
||||
} else {
|
||||
set cmd zrangebylex
|
||||
}
|
||||
|
||||
# Make sure data is the same in both sides
|
||||
assert {[r zrange zset 0 -1] eq $lexset}
|
||||
|
||||
# Get the Redis output
|
||||
set output [r $cmd zset $cmin $cmax]
|
||||
if {$rev} {
|
||||
set outlen [r zlexcount zset $cmax $cmin]
|
||||
} else {
|
||||
set outlen [r zlexcount zset $cmin $cmax]
|
||||
}
|
||||
|
||||
# Compute the same output via Tcl
|
||||
set o {}
|
||||
set copy $lexset
|
||||
if {(!$rev && [string compare $min $max] > 0) ||
|
||||
($rev && [string compare $max $min] > 0)} {
|
||||
# Empty output when ranges are inverted.
|
||||
} else {
|
||||
if {$rev} {
|
||||
# Invert the Tcl array using Redis itself.
|
||||
set copy [r zrevrange zset 0 -1]
|
||||
# Invert min / max as well
|
||||
lassign [list $min $max $mininc $maxinc] \
|
||||
max min maxinc mininc
|
||||
}
|
||||
foreach e $copy {
|
||||
set mincmp [string compare $e $min]
|
||||
set maxcmp [string compare $e $max]
|
||||
if {
|
||||
($mininc && $mincmp >= 0 || !$mininc && $mincmp > 0)
|
||||
&&
|
||||
($maxinc && $maxcmp <= 0 || !$maxinc && $maxcmp < 0)
|
||||
} {
|
||||
lappend o $e
|
||||
}
|
||||
}
|
||||
}
|
||||
assert {$o eq $output}
|
||||
assert {$outlen eq [llength $output]}
|
||||
}
|
||||
}
|
||||
|
||||
test "ZREMRANGEBYLEX fuzzy test, 100 ranges in $elements element sorted set - $encoding" {
|
||||
set lexset {}
|
||||
r del zset zsetcopy
|
||||
for {set j 0} {$j < $elements} {incr j} {
|
||||
set e [randstring 0 30 alpha]
|
||||
lappend lexset $e
|
||||
r zadd zset 0 $e
|
||||
}
|
||||
set lexset [lsort -unique $lexset]
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
# Copy...
|
||||
r zunionstore zsetcopy 1 zset
|
||||
set lexsetcopy $lexset
|
||||
|
||||
set min [randstring 0 30 alpha]
|
||||
set max [randstring 0 30 alpha]
|
||||
set mininc [randomInt 2]
|
||||
set maxinc [randomInt 2]
|
||||
if {$mininc} {set cmin "\[$min"} else {set cmin "($min"}
|
||||
if {$maxinc} {set cmax "\[$max"} else {set cmax "($max"}
|
||||
|
||||
# Make sure data is the same in both sides
|
||||
assert {[r zrange zset 0 -1] eq $lexset}
|
||||
|
||||
# Get the range we are going to remove
|
||||
set torem [r zrangebylex zset $cmin $cmax]
|
||||
set toremlen [r zlexcount zset $cmin $cmax]
|
||||
r zremrangebylex zsetcopy $cmin $cmax
|
||||
set output [r zrange zsetcopy 0 -1]
|
||||
|
||||
# Remove the range with Tcl from the original list
|
||||
if {$toremlen} {
|
||||
set first [lsearch -exact $lexsetcopy [lindex $torem 0]]
|
||||
set last [expr {$first+$toremlen-1}]
|
||||
set lexsetcopy [lreplace $lexsetcopy $first $last]
|
||||
}
|
||||
assert {$lexsetcopy eq $output}
|
||||
}
|
||||
}
|
||||
|
||||
test "ZSETs skiplist implementation backlink consistency test - $encoding" {
|
||||
set diff 0
|
||||
for {set j 0} {$j < $elements} {incr j} {
|
||||
|
||||
@@ -11,7 +11,8 @@ GROUPS = [
|
||||
"transactions",
|
||||
"connection",
|
||||
"server",
|
||||
"scripting"
|
||||
"scripting",
|
||||
"hyperloglog"
|
||||
].freeze
|
||||
|
||||
GROUPS_BY_NAME = Hash[*
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
*.txt
|
||||
@@ -0,0 +1,27 @@
|
||||
# hll-err.rb - Copyright (C) 2014 Salvatore Sanfilippo
|
||||
# BSD license, See the COPYING file for more information.
|
||||
#
|
||||
# Check error of HyperLogLog Redis implementation for different set sizes.
|
||||
|
||||
require 'rubygems'
|
||||
require 'redis'
|
||||
require 'digest/sha1'
|
||||
|
||||
r = Redis.new
|
||||
r.del('hll')
|
||||
i = 0
|
||||
while true do
|
||||
100.times {
|
||||
elements = []
|
||||
1000.times {
|
||||
ele = Digest::SHA1.hexdigest(i.to_s)
|
||||
elements << ele
|
||||
i += 1
|
||||
}
|
||||
r.pfadd('hll',*elements)
|
||||
}
|
||||
approx = r.pfcount('hll')
|
||||
abs_err = (approx-i).abs
|
||||
rel_err = 100.to_f*abs_err/i
|
||||
puts "#{i} vs #{approx}: #{rel_err}%"
|
||||
end
|
||||
@@ -0,0 +1,88 @@
|
||||
# hll-err.rb - Copyright (C) 2014 Salvatore Sanfilippo
|
||||
# BSD license, See the COPYING file for more information.
|
||||
#
|
||||
# This program is suited to output average and maximum errors of
|
||||
# the Redis HyperLogLog implementation in a format suitable to print
|
||||
# graphs using gnuplot.
|
||||
|
||||
require 'rubygems'
|
||||
require 'redis'
|
||||
require 'digest/sha1'
|
||||
|
||||
# Generate an array of [cardinality,relative_error] pairs
|
||||
# in the 0 - max range, with the specified step.
|
||||
#
|
||||
# 'r' is the Redis object used to perform the queries.
|
||||
# 'seed' must be different every time you want a test performed
|
||||
# with a different set. The function guarantees that if 'seed' is the
|
||||
# same, exactly the same dataset is used, and when it is different,
|
||||
# a totally unrelated different data set is used (without any common
|
||||
# element in practice).
|
||||
def run_experiment(r,seed,max,step)
|
||||
r.del('hll')
|
||||
i = 0
|
||||
samples = []
|
||||
step = 1000 if step > 1000
|
||||
while i < max do
|
||||
elements = []
|
||||
step.times {
|
||||
ele = Digest::SHA1.hexdigest(i.to_s+seed.to_s)
|
||||
elements << ele
|
||||
i += 1
|
||||
}
|
||||
r.pfadd('hll',*elements)
|
||||
approx = r.pfcount('hll')
|
||||
err = approx-i
|
||||
rel_err = 100.to_f*err/i
|
||||
samples << [i,rel_err]
|
||||
end
|
||||
samples
|
||||
end
|
||||
|
||||
def filter_samples(numsets,max,step,filter)
|
||||
r = Redis.new
|
||||
dataset = {}
|
||||
(0...numsets).each{|i|
|
||||
dataset[i] = run_experiment(r,i,max,step)
|
||||
STDERR.puts "Set #{i}"
|
||||
}
|
||||
dataset[0].each_with_index{|ele,index|
|
||||
if filter == :max
|
||||
card=ele[0]
|
||||
err=ele[1].abs
|
||||
(1...numsets).each{|i|
|
||||
err = dataset[i][index][1] if err < dataset[i][index][1]
|
||||
}
|
||||
puts "#{card} #{err}"
|
||||
elsif filter == :avg
|
||||
card=ele[0]
|
||||
err = 0
|
||||
(0...numsets).each{|i|
|
||||
err += dataset[i][index][1]
|
||||
}
|
||||
err /= numsets
|
||||
puts "#{card} #{err}"
|
||||
elsif filter == :absavg
|
||||
card=ele[0]
|
||||
err = 0
|
||||
(0...numsets).each{|i|
|
||||
err += dataset[i][index][1].abs
|
||||
}
|
||||
err /= numsets
|
||||
puts "#{card} #{err}"
|
||||
elsif filter == :all
|
||||
(0...numsets).each{|i|
|
||||
card,err = dataset[i][index]
|
||||
puts "#{card} #{err}"
|
||||
}
|
||||
else
|
||||
raise "Unknown filter #{filter}"
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
if ARGV.length != 4
|
||||
puts "Usage: hll-gnuplot-graph <samples> <max> <step> (max|avg|absavg|all)"
|
||||
exit 1
|
||||
end
|
||||
filter_samples(ARGV[0].to_i,ARGV[1].to_i,ARGV[2].to_i,ARGV[3].to_sym)
|
||||
+116
-61
@@ -30,46 +30,48 @@
|
||||
# this scripts should be run as root
|
||||
|
||||
die () {
|
||||
echo "ERROR: $1. Aborting!"
|
||||
echo "ERROR: $1. Aborting!"
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
||||
#Absolute path to this script
|
||||
SCRIPT=$(readlink -f $0)
|
||||
#Absolute path this script is in
|
||||
SCRIPTPATH=$(dirname $SCRIPT)
|
||||
|
||||
#Initial defaults
|
||||
_REDIS_PORT=6379
|
||||
|
||||
echo "Welcome to the redis service installer"
|
||||
echo "This script will help you easily set up a running redis server
|
||||
echo "This script will help you easily set up a running redis server"
|
||||
echo
|
||||
|
||||
"
|
||||
|
||||
#check for root user TODO: replace this with a call to "id"
|
||||
if [ `whoami` != "root" ] ; then
|
||||
#check for root user
|
||||
if [ "$(id -u)" -ne 0 ] ; then
|
||||
echo "You must run this script as root. Sorry!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
#Read the redis port
|
||||
read -p "Please select the redis port for this instance: [$_REDIS_PORT] " REDIS_PORT
|
||||
if [ ! `echo $REDIS_PORT | egrep "^[0-9]+\$"` ] ; then
|
||||
read -p "Please select the redis port for this instance: [$_REDIS_PORT] " REDIS_PORT
|
||||
if ! echo $REDIS_PORT | egrep -q '^[0-9]+$' ; then
|
||||
echo "Selecting default: $_REDIS_PORT"
|
||||
REDIS_PORT=$_REDIS_PORT
|
||||
REDIS_PORT=$_REDIS_PORT
|
||||
fi
|
||||
|
||||
#read the redis config file
|
||||
_REDIS_CONFIG_FILE="/etc/redis/$REDIS_PORT.conf"
|
||||
read -p "Please select the redis config file name [$_REDIS_CONFIG_FILE] " REDIS_CONFIG_FILE
|
||||
if [ !"$REDIS_CONFIG_FILE" ] ; then
|
||||
if [ -z "$REDIS_CONFIG_FILE" ] ; then
|
||||
REDIS_CONFIG_FILE=$_REDIS_CONFIG_FILE
|
||||
echo "Selected default - $REDIS_CONFIG_FILE"
|
||||
fi
|
||||
#try and create it
|
||||
mkdir -p `dirname "$REDIS_CONFIG_FILE"` || die "Could not create redis config directory"
|
||||
|
||||
#read the redis log file path
|
||||
_REDIS_LOG_FILE="/var/log/redis_$REDIS_PORT.log"
|
||||
read -p "Please select the redis log file name [$_REDIS_LOG_FILE] " REDIS_LOG_FILE
|
||||
if [ !"$REDIS_LOG_FILE" ] ; then
|
||||
if [ -z "$REDIS_LOG_FILE" ] ; then
|
||||
REDIS_LOG_FILE=$_REDIS_LOG_FILE
|
||||
echo "Selected default - $REDIS_LOG_FILE"
|
||||
fi
|
||||
@@ -78,55 +80,71 @@ fi
|
||||
#get the redis data directory
|
||||
_REDIS_DATA_DIR="/var/lib/redis/$REDIS_PORT"
|
||||
read -p "Please select the data directory for this instance [$_REDIS_DATA_DIR] " REDIS_DATA_DIR
|
||||
if [ !"$REDIS_DATA_DIR" ] ; then
|
||||
if [ -z "$REDIS_DATA_DIR" ] ; then
|
||||
REDIS_DATA_DIR=$_REDIS_DATA_DIR
|
||||
echo "Selected default - $REDIS_DATA_DIR"
|
||||
fi
|
||||
mkdir -p $REDIS_DATA_DIR || die "Could not create redis data directory"
|
||||
|
||||
#get the redis executable path
|
||||
_REDIS_EXECUTABLE=`which redis-server`
|
||||
_REDIS_EXECUTABLE=`command -v redis-server`
|
||||
read -p "Please select the redis executable path [$_REDIS_EXECUTABLE] " REDIS_EXECUTABLE
|
||||
if [ ! -f "$REDIS_EXECUTABLE" ] ; then
|
||||
if [ ! -x "$REDIS_EXECUTABLE" ] ; then
|
||||
REDIS_EXECUTABLE=$_REDIS_EXECUTABLE
|
||||
|
||||
if [ ! -f "$REDIS_EXECUTABLE" ] ; then
|
||||
|
||||
if [ ! -x "$REDIS_EXECUTABLE" ] ; then
|
||||
echo "Mmmmm... it seems like you don't have a redis executable. Did you run make install yet?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
|
||||
#render the tmplates
|
||||
TMP_FILE="/tmp/$REDIS_PORT.conf"
|
||||
DEFAULT_CONFIG="../redis.conf"
|
||||
INIT_TPL_FILE="./redis_init_script.tpl"
|
||||
INIT_SCRIPT_DEST="/etc/init.d/redis_$REDIS_PORT"
|
||||
PIDFILE="/var/run/redis_$REDIS_PORT.pid"
|
||||
|
||||
|
||||
|
||||
#check the default for redis cli
|
||||
CLI_EXEC=`which redis-cli`
|
||||
if [ ! "$CLI_EXEC" ] ; then
|
||||
CLI_EXEC=`command -v redis-cli`
|
||||
if [ -z "$CLI_EXEC" ] ; then
|
||||
CLI_EXEC=`dirname $REDIS_EXECUTABLE`"/redis-cli"
|
||||
fi
|
||||
|
||||
echo "Selected config:"
|
||||
|
||||
echo "Port : $REDIS_PORT"
|
||||
echo "Config file : $REDIS_CONFIG_FILE"
|
||||
echo "Log file : $REDIS_LOG_FILE"
|
||||
echo "Data dir : $REDIS_DATA_DIR"
|
||||
echo "Executable : $REDIS_EXECUTABLE"
|
||||
echo "Cli Executable : $CLI_EXEC"
|
||||
|
||||
read -p "Is this ok? Then press ENTER to go on or Ctrl-C to abort." _UNUSED_
|
||||
|
||||
mkdir -p `dirname "$REDIS_CONFIG_FILE"` || die "Could not create redis config directory"
|
||||
mkdir -p `dirname "$REDIS_LOG_FILE"` || die "Could not create redis log dir"
|
||||
mkdir -p "$REDIS_DATA_DIR" || die "Could not create redis data directory"
|
||||
|
||||
#render the templates
|
||||
TMP_FILE="/tmp/${REDIS_PORT}.conf"
|
||||
DEFAULT_CONFIG="${SCRIPTPATH}/../redis.conf"
|
||||
INIT_TPL_FILE="${SCRIPTPATH}/redis_init_script.tpl"
|
||||
INIT_SCRIPT_DEST="/etc/init.d/redis_${REDIS_PORT}"
|
||||
PIDFILE="/var/run/redis_${REDIS_PORT}.pid"
|
||||
|
||||
if [ ! -f "$DEFAULT_CONFIG" ]; then
|
||||
echo "Mmmmm... the default config is missing. Did you switch to the utils directory?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
#Generate config file from the default config file as template
|
||||
#changing only the stuff we're controlling from this script
|
||||
echo "## Generated by install_server.sh ##" > $TMP_FILE
|
||||
|
||||
SED_EXPR="s#^port [0-9]{4}\$#port ${REDIS_PORT}#;\
|
||||
s#^logfile .+\$#logfile ${REDIS_LOG_FILE}#;\
|
||||
s#^dir .+\$#dir ${REDIS_DATA_DIR}#;\
|
||||
s#^pidfile .+\$#pidfile ${PIDFILE}#;\
|
||||
s#^daemonize no\$#daemonize yes#;"
|
||||
echo $SED_EXPR
|
||||
read -r SED_EXPR <<-EOF
|
||||
s#^port [0-9]{4}\$#port ${REDIS_PORT}#; \
|
||||
s#^logfile .+\$#logfile ${REDIS_LOG_FILE}#; \
|
||||
s#^dir .+\$#dir ${REDIS_DATA_DIR}#; \
|
||||
s#^pidfile .+\$#pidfile ${PIDFILE}#; \
|
||||
s#^daemonize no\$#daemonize yes#;
|
||||
EOF
|
||||
sed -r "$SED_EXPR" $DEFAULT_CONFIG >> $TMP_FILE
|
||||
|
||||
#cat $TPL_FILE | while read line; do eval "echo \"$line\"" >> $TMP_FILE; done
|
||||
cp -f $TMP_FILE $REDIS_CONFIG_FILE || exit 1
|
||||
cp $TMP_FILE $REDIS_CONFIG_FILE || die "Could not write redis config file $REDIS_CONFIG_FILE"
|
||||
|
||||
#Generate sample script from template file
|
||||
rm -f $TMP_FILE
|
||||
@@ -138,7 +156,7 @@ REDIS_INIT_HEADER=\
|
||||
#Configurations injected by install_server below....\n\n
|
||||
EXEC=$REDIS_EXECUTABLE\n
|
||||
CLIEXEC=$CLI_EXEC\n
|
||||
PIDFILE=$PIDFILE\n
|
||||
PIDFILE=\"$PIDFILE\"\n
|
||||
CONF=\"$REDIS_CONFIG_FILE\"\n\n
|
||||
REDISPORT=\"$REDIS_PORT\"\n\n
|
||||
###############\n\n"
|
||||
@@ -146,45 +164,82 @@ REDISPORT=\"$REDIS_PORT\"\n\n
|
||||
REDIS_CHKCONFIG_INFO=\
|
||||
"# REDHAT chkconfig header\n\n
|
||||
# chkconfig: - 58 74\n
|
||||
# description: redis_6379 is the redis daemon.\n
|
||||
# description: redis_${REDIS_PORT} is the redis daemon.\n
|
||||
### BEGIN INIT INFO\n
|
||||
# Provides: redis_6379\n
|
||||
# Required-Start: $network $local_fs $remote_fs\n
|
||||
# Required-Stop: $network $local_fs $remote_fs\n
|
||||
# Required-Start: \$network \$local_fs \$remote_fs\n
|
||||
# Required-Stop: \$network \$local_fs \$remote_fs\n
|
||||
# Default-Start: 2 3 4 5\n
|
||||
# Default-Stop: 0 1 6\n
|
||||
# Should-Start: $syslog $named\n
|
||||
# Should-Stop: $syslog $named\n
|
||||
# Short-Description: start and stop redis_6379\n
|
||||
# Should-Start: \$syslog \$named\n
|
||||
# Should-Stop: \$syslog \$named\n
|
||||
# Short-Description: start and stop redis_${REDIS_PORT}\n
|
||||
# Description: Redis daemon\n
|
||||
### END INIT INFO\n\n"
|
||||
|
||||
if [ !`which chkconfig` ] ; then
|
||||
#combine the header and the template (which is actually a static footer)
|
||||
echo $REDIS_INIT_HEADER > $TMP_FILE && cat $INIT_TPL_FILE >> $TMP_FILE || die "Could not write init script to $TMP_FILE"
|
||||
else
|
||||
if command -v chkconfig >/dev/null; then
|
||||
#if we're a box with chkconfig on it we want to include info for chkconfig
|
||||
echo -e $REDIS_INIT_HEADER $REDIS_CHKCONFIG_INFO > $TMP_FILE && cat $INIT_TPL_FILE >> $TMP_FILE || die "Could not write init script to $TMP_FILE"
|
||||
echo "$REDIS_INIT_HEADER" "$REDIS_CHKCONFIG_INFO" > $TMP_FILE && cat $INIT_TPL_FILE >> $TMP_FILE || die "Could not write init script to $TMP_FILE"
|
||||
else
|
||||
#combine the header and the template (which is actually a static footer)
|
||||
echo "$REDIS_INIT_HEADER" > $TMP_FILE && cat $INIT_TPL_FILE >> $TMP_FILE || die "Could not write init script to $TMP_FILE"
|
||||
fi
|
||||
|
||||
###
|
||||
# Generate sample script from template file
|
||||
# - No need to check which system we are on. The init info are comments and
|
||||
# do not interfere with update_rc.d systems. Additionally:
|
||||
# Ubuntu/debian by default does not come with chkconfig, but does issue a
|
||||
# warning if init info is not available.
|
||||
|
||||
cat > ${TMP_FILE} <<EOT
|
||||
#/bin/sh
|
||||
#Configurations injected by install_server below....
|
||||
|
||||
EXEC=$REDIS_EXECUTABLE
|
||||
CLIEXEC=$CLI_EXEC
|
||||
PIDFILE=$PIDFILE
|
||||
CONF="$REDIS_CONFIG_FILE"
|
||||
REDISPORT="$REDIS_PORT"
|
||||
###############
|
||||
# SysV Init Information
|
||||
# chkconfig: - 58 74
|
||||
# description: redis_${REDIS_PORT} is the redis daemon.
|
||||
### BEGIN INIT INFO
|
||||
# Provides: redis_${REDIS_PORT}
|
||||
# Required-Start: \$network \$local_fs \$remote_fs
|
||||
# Required-Stop: \$network \$local_fs \$remote_fs
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Should-Start: \$syslog \$named
|
||||
# Should-Stop: \$syslog \$named
|
||||
# Short-Description: start and stop redis_${REDIS_PORT}
|
||||
# Description: Redis daemon
|
||||
### END INIT INFO
|
||||
|
||||
EOT
|
||||
cat ${INIT_TPL_FILE} >> ${TMP_FILE}
|
||||
|
||||
#copy to /etc/init.d
|
||||
cp -f $TMP_FILE $INIT_SCRIPT_DEST && chmod +x $INIT_SCRIPT_DEST || die "Could not copy redis init script to $INIT_SCRIPT_DEST"
|
||||
cp $TMP_FILE $INIT_SCRIPT_DEST && \
|
||||
chmod +x $INIT_SCRIPT_DEST || die "Could not copy redis init script to $INIT_SCRIPT_DEST"
|
||||
echo "Copied $TMP_FILE => $INIT_SCRIPT_DEST"
|
||||
|
||||
#Install the service
|
||||
echo "Installing service..."
|
||||
if [ !`which chkconfig` ] ; then
|
||||
#if we're not a chkconfig box assume we're able to use update-rc.d
|
||||
update-rc.d redis_$REDIS_PORT defaults && echo "Success!"
|
||||
else
|
||||
if command -v chkconfig >/dev/null 2>&1; then
|
||||
# we're chkconfig, so lets add to chkconfig and put in runlevel 345
|
||||
chkconfig --add redis_$REDIS_PORT && echo "Successfully added to chkconfig!"
|
||||
chkconfig --level 345 redis_$REDIS_PORT on && echo "Successfully added to runlevels 345!"
|
||||
chkconfig --add redis_${REDIS_PORT} && echo "Successfully added to chkconfig!"
|
||||
chkconfig --level 345 redis_${REDIS_PORT} on && echo "Successfully added to runlevels 345!"
|
||||
elif command -v update-rc.d >/dev/null 2>&1; then
|
||||
#if we're not a chkconfig box assume we're able to use update-rc.d
|
||||
update-rc.d redis_${REDIS_PORT} defaults && echo "Success!"
|
||||
else
|
||||
echo "No supported init tool found."
|
||||
fi
|
||||
|
||||
|
||||
/etc/init.d/redis_$REDIS_PORT start || die "Failed starting service..."
|
||||
|
||||
#tada
|
||||
echo "Installation successful!"
|
||||
exit 0
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
The test-lru.rb program can be used in order to check the behavior of the
|
||||
Redis approximated LRU algorithm against the theoretical output of true
|
||||
LRU algorithm.
|
||||
|
||||
In order to use the program you need to recompile Redis setting the define
|
||||
REDIS_LRU_CLOCK_RESOLUTION to 1, by editing redis.h.
|
||||
This allows to execute the program in a fast way since the 1 ms resolution
|
||||
is enough for all the objects to have a different enough time stamp during
|
||||
the test.
|
||||
|
||||
The program is executed like this:
|
||||
|
||||
ruby test-lru.rb > /tmp/lru.html
|
||||
@@ -0,0 +1,112 @@
|
||||
require 'rubygems'
|
||||
require 'redis'
|
||||
|
||||
r = Redis.new
|
||||
r.config("SET","maxmemory","2000000")
|
||||
r.config("SET","maxmemory-policy","allkeys-lru")
|
||||
r.config("SET","maxmemory-samples",5)
|
||||
r.config("RESETSTAT")
|
||||
r.flushall
|
||||
|
||||
puts <<EOF
|
||||
<html>
|
||||
<body>
|
||||
<style>
|
||||
.box {
|
||||
width:5px;
|
||||
height:5px;
|
||||
float:left;
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
.old {
|
||||
border: 1px black solid;
|
||||
}
|
||||
|
||||
.new {
|
||||
border: 1px green solid;
|
||||
}
|
||||
|
||||
.ex {
|
||||
background-color: #666;
|
||||
}
|
||||
</style>
|
||||
<pre>
|
||||
EOF
|
||||
|
||||
# Fill
|
||||
oldsize = r.dbsize
|
||||
id = 0
|
||||
while true
|
||||
id += 1
|
||||
r.set(id,"foo")
|
||||
newsize = r.dbsize
|
||||
break if newsize == oldsize
|
||||
oldsize = newsize
|
||||
end
|
||||
|
||||
inserted = r.dbsize
|
||||
first_set_max_id = id
|
||||
puts "#{r.dbsize} keys inserted"
|
||||
|
||||
# Access keys sequencially
|
||||
|
||||
puts "Access keys sequencially"
|
||||
(1..first_set_max_id).each{|id|
|
||||
r.get(id)
|
||||
# sleep 0.001
|
||||
}
|
||||
|
||||
# Insert more 50% keys. We expect that the new keys
|
||||
half = inserted/2
|
||||
puts "Insert enough keys to evict half the keys we inserted"
|
||||
add = 0
|
||||
while true
|
||||
add += 1
|
||||
id += 1
|
||||
r.set(id,"foo")
|
||||
break if r.info['evicted_keys'].to_i >= half
|
||||
end
|
||||
|
||||
puts "#{add} additional keys added."
|
||||
puts "#{r.dbsize} keys in DB"
|
||||
|
||||
# Check if evicted keys respect LRU
|
||||
# We consider errors from 1 to N progressively more serious as they violate
|
||||
# more the access pattern.
|
||||
|
||||
errors = 0
|
||||
e = 1
|
||||
edecr = 1.0/(first_set_max_id/2)
|
||||
(1..(first_set_max_id/2)).each{|id|
|
||||
e -= edecr if e > 0
|
||||
e = 0 if e < 0
|
||||
if r.exists(id)
|
||||
errors += e
|
||||
end
|
||||
}
|
||||
|
||||
puts "#{errors} errors!"
|
||||
puts "</pre>"
|
||||
|
||||
# Generate the graphical representation
|
||||
(1..id).each{|id|
|
||||
# Mark first set and added items in a different way.
|
||||
c = "box"
|
||||
if id <= first_set_max_id
|
||||
c << " old"
|
||||
else
|
||||
c << " new"
|
||||
end
|
||||
|
||||
# Add class if exists
|
||||
c << " ex" if r.exists(id)
|
||||
puts "<div class=\"#{c}\"></div>"
|
||||
}
|
||||
|
||||
# Close HTML page
|
||||
|
||||
puts <<EOF
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
+26
-14
@@ -3,29 +3,41 @@ case "$1" in
|
||||
start)
|
||||
if [ -f $PIDFILE ]
|
||||
then
|
||||
echo "$PIDFILE exists, process is already running or crashed"
|
||||
echo "$PIDFILE exists, process is already running or crashed"
|
||||
else
|
||||
echo "Starting Redis server..."
|
||||
$EXEC $CONF
|
||||
echo "Starting Redis server..."
|
||||
$EXEC $CONF
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
if [ ! -f $PIDFILE ]
|
||||
then
|
||||
echo "$PIDFILE does not exist, process is not running"
|
||||
echo "$PIDFILE does not exist, process is not running"
|
||||
else
|
||||
PID=$(cat $PIDFILE)
|
||||
echo "Stopping ..."
|
||||
$CLIEXEC -p $REDISPORT shutdown
|
||||
while [ -x /proc/${PID} ]
|
||||
do
|
||||
echo "Waiting for Redis to shutdown ..."
|
||||
sleep 1
|
||||
done
|
||||
echo "Redis stopped"
|
||||
PID=$(cat $PIDFILE)
|
||||
echo "Stopping ..."
|
||||
$CLIEXEC -p $REDISPORT shutdown
|
||||
while [ -x /proc/${PID} ]
|
||||
do
|
||||
echo "Waiting for Redis to shutdown ..."
|
||||
sleep 1
|
||||
done
|
||||
echo "Redis stopped"
|
||||
fi
|
||||
;;
|
||||
status)
|
||||
if [ ! -f $PIDFILE ]
|
||||
then
|
||||
echo 'Redis is not running'
|
||||
else
|
||||
echo "Redis is running ($(<$PIDFILE))"
|
||||
fi
|
||||
;;
|
||||
restart)
|
||||
$0 stop
|
||||
$0 start
|
||||
;;
|
||||
*)
|
||||
echo "Please use start or stop as first argument"
|
||||
echo "Please use start, stop, restart or status as first argument"
|
||||
;;
|
||||
esac
|
||||
|
||||
Reference in New Issue
Block a user