Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
987a47b823 | ||
|
|
430bec3461 | ||
|
|
3a30be51aa | ||
|
|
42504169c7 | ||
|
|
26359d570b | ||
|
|
768994b683 | ||
|
|
1c0c42e668 | ||
|
|
75266018ee | ||
|
|
edc41baca5 | ||
|
|
1a14eba2d5 | ||
|
|
f482349707 | ||
|
|
f3d3c606f1 | ||
|
|
4113473997 | ||
|
|
e8669ca8a7 | ||
|
|
d491a479bc | ||
|
|
d575f7a147 | ||
|
|
6d2fddd2a3 | ||
|
|
ad935769ae | ||
|
|
43278af63e | ||
|
|
3203e2dcdd | ||
|
|
6e478ec890 | ||
|
|
c0eaac9572 | ||
|
|
23011dea1f | ||
|
|
6ae4c1fcbc | ||
|
|
13d8b2b096 | ||
|
|
909d1883e7 | ||
|
|
80b96bc4a7 | ||
|
|
093eab56de | ||
|
|
32971f6251 | ||
|
|
87516f1a4d | ||
|
|
1b5524f697 | ||
|
|
8237d88f48 | ||
|
|
6938a3d58f | ||
|
|
08ea98c9e9 | ||
|
|
3a1a0c3240 | ||
|
|
d70281575c | ||
|
|
67e350367d | ||
|
|
b06de30dd7 | ||
|
|
e9d0d86dbf | ||
|
|
96b5dc0b98 | ||
|
|
3d2af57974 | ||
|
|
35252037fd | ||
|
|
662f505727 | ||
|
|
de7ebf83e1 | ||
|
|
91dcc5b12f | ||
|
|
26375709c5 | ||
|
|
c5ee48a9d9 | ||
|
|
be145453c6 | ||
|
|
115782b1d7 | ||
|
|
2cd20d3c4c | ||
|
|
3f5cd0c660 | ||
|
|
f0eca148ff | ||
|
|
d25fd8be64 | ||
|
|
4a26648a22 | ||
|
|
3fc22561e7 | ||
|
|
ea0af9ace3 | ||
|
|
019e6f1889 | ||
|
|
9c9aef24a2 | ||
|
|
11f89497b4 | ||
|
|
d975bb587f | ||
|
|
71dfb87f76 | ||
|
|
f2e59eae44 | ||
|
|
469b7c5168 | ||
|
|
614fcd491e | ||
|
|
08da2b79fc | ||
|
|
5a0bab2a8f | ||
|
|
05563310f4 | ||
|
|
e77a3e40f2 | ||
|
|
c4b2a7a7f4 | ||
|
|
ee682c4c48 | ||
|
|
9ef8ef410b | ||
|
|
91e05e1618 | ||
|
|
68a4bf70e0 | ||
|
|
21e328a6d7 | ||
|
|
4244aa29f9 | ||
|
|
4cd11077d2 | ||
|
|
08183cfef5 | ||
|
|
d428569d13 | ||
|
|
2d1e35a58a | ||
|
|
29c7d1974b | ||
|
|
023f2f7a10 | ||
|
|
91391760ca | ||
|
|
5d87160f8a | ||
|
|
a18e7afbe8 | ||
|
|
5c86d9e7b0 | ||
|
|
0e0ddcc4fa | ||
|
|
533235f7e3 | ||
|
|
f12fb49bb2 | ||
|
|
a3e2adc0ae | ||
|
|
eaa73ef73a | ||
|
|
95de3eaea0 | ||
|
|
c277169ed7 | ||
|
|
b6ca71a40a | ||
|
|
8b39fee4c8 | ||
|
|
c1ddead779 | ||
|
|
0a7ba6faef | ||
|
|
8e889728bf | ||
|
|
aadd513160 | ||
|
|
e5e5facba1 | ||
|
|
88847a216f | ||
|
|
21c97e71cd | ||
|
|
ed96e115d0 | ||
|
|
93c75f1bf4 | ||
|
|
2b1385b9ec | ||
|
|
12d1d18b59 | ||
|
|
a10bfded15 | ||
|
|
ee764b0f8d | ||
|
|
091f5677a0 | ||
|
|
a97675923d | ||
|
|
faa7f259cc | ||
|
|
8ae6887091 | ||
|
|
bdd8701c60 | ||
|
|
b3baa51403 | ||
|
|
348d5ea246 | ||
|
|
ba0dc0be2c | ||
|
|
c2cd48718d | ||
|
|
ff551fad24 | ||
|
|
e08100a9ba | ||
|
|
23acae6e1c | ||
|
|
e3773b0a0e | ||
|
|
badf23f57b | ||
|
|
6e71459c73 | ||
|
|
2ee2bec2d6 | ||
|
|
1e90c0066c | ||
|
|
be7eef911b | ||
|
|
f2e707b679 | ||
|
|
be7fe2b92b | ||
|
|
39dd1f648d | ||
|
|
f07b1514bb | ||
|
|
32e9996846 | ||
|
|
9faa80ee8c | ||
|
|
aad5959ddc | ||
|
|
d6ce659ffd | ||
|
|
c3df5965f3 | ||
|
|
9e178afae9 | ||
|
|
1c795db9d0 | ||
|
|
7f30998432 | ||
|
|
430d0ade75 | ||
|
|
83650a72a1 | ||
|
|
3afd25ecc7 | ||
|
|
0ffd5e4a30 | ||
|
|
ca6973d3b6 | ||
|
|
2500f2a334 | ||
|
|
42592a7faa | ||
|
|
3035075b2c | ||
|
|
959d0f012a | ||
|
|
ac29bb2ae4 | ||
|
|
2a91f548c4 | ||
|
|
3f159bbd44 | ||
|
|
a18e880bf1 | ||
|
|
00409d8742 | ||
|
|
de3f821af6 | ||
|
|
02d88fb201 | ||
|
|
ddbdd3b0df | ||
|
|
d2e59c2715 | ||
|
|
99fc582f8c | ||
|
|
3580bb485a | ||
|
|
c3510af1c0 | ||
|
|
771f8ad0e7 | ||
|
|
3ce742d1d5 | ||
|
|
b59585396b | ||
|
|
6826af1b50 | ||
|
|
611372fa97 | ||
|
|
3bd3240660 | ||
|
|
79349affd5 | ||
|
|
80dec5e4df | ||
|
|
a2ec9a9068 | ||
|
|
88c6c669c3 | ||
|
|
6182dad37c | ||
|
|
4ebc7e3738 | ||
|
|
72257f4b87 | ||
|
|
7054f2a029 | ||
|
|
7a20f09647 | ||
|
|
001775f5fb | ||
|
|
c68189a19f | ||
|
|
ff8c81873a | ||
|
|
e3b71a1c02 | ||
|
|
0937377aa2 | ||
|
|
686839b477 | ||
|
|
6d0e408a27 | ||
|
|
ba2edc4191 | ||
|
|
9c2063fb8f | ||
|
|
ffa8f479a5 | ||
|
|
42091a79bb | ||
|
|
38241c4b1e | ||
|
|
9de0755869 | ||
|
|
a31a0b4336 | ||
|
|
6b0e36ffc6 | ||
|
|
bd48ff69b0 | ||
|
|
3703112671 | ||
|
|
ccd2c18c1c | ||
|
|
daa5d2a6c9 | ||
|
|
ca0720b694 | ||
|
|
ffe742f92a | ||
|
|
6fb9e2d717 | ||
|
|
eb9e1526e6 | ||
|
|
a6970570e3 | ||
|
|
fe0ab7d234 |
@@ -14,6 +14,79 @@ HIGH: There is a critical bug that may affect a subset of users. Upgrade!
|
||||
CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--[ Redis 2.8.10 ] Release date: 5 Jun 2014
|
||||
|
||||
# UPGRADE URGENCY: HIGH if you use min-slaves-to-write option.
|
||||
|
||||
* [FIX] IMPORTANT! A min-slaves-to-write option active in a slave totally
|
||||
prevented the slave from acception the master stream of commands.
|
||||
This release includes testes for min-slaves-to-write, and a fix
|
||||
for this issue.
|
||||
* [FIX] Sometimes DEL returned 1 for already expired keys. Fixed.
|
||||
* [FIX] Fix test false positive because new osx 'leaks' output.
|
||||
* [FIX] PFCOUNT HLL cache invalidation fixed: no wrong value was reported
|
||||
but the cache was not used at all, leading to lower performances.
|
||||
* [FIX] Accept(2) multiple clients per readable-event invocation, and better
|
||||
processing of I/O while loading or busy running a timedout script.
|
||||
Basically now the LOADING / BUSY errors are reported at a decent
|
||||
speed.
|
||||
* [FIX] A softwaer watchdog crash fixed.
|
||||
* [FIX] Fixed a Lua -> Redis numerical precision loss.
|
||||
|
||||
* [NEW] Lua scripting engine speed improved.
|
||||
* [NEW] Sentinel generates one new event for humans to understand better
|
||||
what is happening during a failover: +config-update-from.
|
||||
Also the time at which a failover will be re-attempted is logged.
|
||||
|
||||
--[ Redis 2.8.9 ] Release date: 22 Apr 2014
|
||||
|
||||
# UPGRADE URGENCY: LOW, only new features introduced, no bugs fixed.
|
||||
|
||||
* [NEW] The HyperLogLog data structure. You can read more about it
|
||||
in this blog post. http://antirez.com/news/75
|
||||
* [NEW] The Sorted Set data type has now support for lexicographic range
|
||||
queries, check the new commands ZRANGEBYLEX, ZLEXCOUNT and
|
||||
ZREMRANGEBYLEX, which are documented at http://redis.io.
|
||||
|
||||
--[ Redis 2.8.8 ] Release date: 25 Mar 2014
|
||||
|
||||
# UPGRADE URGENCY: HIGH for Redis, LOW for Sentinel. There is a potentially
|
||||
critical bug fix causing data loss in Redis but it requires
|
||||
a combination of disk full and the use of the
|
||||
SHUTDOWN command.
|
||||
|
||||
* [FIX] Fixed data loss when SHUTDOWN was used with a disk full condition.
|
||||
* [FIX] Fixed a memory leak in the SORT syntax error processing.
|
||||
* [FIX] When Sentinel down-after-milliseconds parameter is modified at runtime
|
||||
now it gets propagated to all the slaves and sentinel instances
|
||||
of the master.
|
||||
* [FIX] `install_server.sh` script finally fixed.
|
||||
* [FIX] Different fixes to maxclients handling.
|
||||
|
||||
* [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.
|
||||
* [NEW] Use all 24 bits (instead of 22) for the Redis objects LRU field.
|
||||
Note that the new LRU algorithm using eviction pools was not backported
|
||||
from unstable for safery / code maturity concerns.
|
||||
* [NEW] Majory speedup for the INFO command (it is now 6 times faster).
|
||||
* [NEW] More Sentinel unit tests.
|
||||
* [NEW] New command DEBUG ERROR returns the specified error. Example:
|
||||
DEBUG ERROR "LOADING database". This is handy to write Redis client
|
||||
libraries unit tests.
|
||||
* [NEW] redis-cli now supports multi-line editing via updated linenoise lib.
|
||||
|
||||
Thanks to Matt Stancliff and Jan-Erik Rediger for the work done in the context
|
||||
of this release.
|
||||
|
||||
--[ Redis 2.8.7 ] Release date: 5 Mar 2014
|
||||
|
||||
# UPGRADE URGENCY: LOW for Redis, LOW for Sentinel. However this release adds
|
||||
@@ -269,6 +342,9 @@ that you should be aware of:
|
||||
|
||||
The following commands changed behavior:
|
||||
|
||||
* The TTL and PTTL commands now return -2 if the key does not exist and
|
||||
-1 if it exists but has no associated expire. Redis 2.6 and previous
|
||||
versions used to return -1 for both the conditions.
|
||||
* SORT with ALPHA now sorts according to local collation locale if no STORE
|
||||
option is used.
|
||||
* ZADD/ZINCRBY are now able to accept a bigger range of values as valid
|
||||
|
||||
@@ -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 */
|
||||
|
||||
+14
@@ -635,6 +635,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)
|
||||
|
||||
+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
|
||||
|
||||
+1
-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 migrate.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
|
||||
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 migrate.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 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
|
||||
|
||||
@@ -26,6 +26,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
|
||||
|
||||
+2
-2
@@ -472,7 +472,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 +491,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;
|
||||
|
||||
@@ -559,7 +559,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) {
|
||||
@@ -968,9 +968,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. */
|
||||
|
||||
+1
-8
@@ -223,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 = createStringObject(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 */
|
||||
|
||||
+10
-9
@@ -388,6 +388,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;
|
||||
@@ -568,7 +570,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;
|
||||
@@ -733,6 +735,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;
|
||||
@@ -934,6 +939,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);
|
||||
@@ -1726,6 +1733,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);
|
||||
@@ -1760,14 +1768,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")) {
|
||||
|
||||
+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
|
||||
|
||||
@@ -166,6 +166,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 = createStringObject(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;
|
||||
@@ -236,6 +274,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,
|
||||
|
||||
+9
-1
@@ -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,13 @@ void debugCommand(redisClient *c) {
|
||||
{
|
||||
server.active_expire_enabled = atoi(c->argv[2]->ptr);
|
||||
addReply(c,shared.ok);
|
||||
} 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);
|
||||
@@ -923,7 +931,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);
|
||||
}
|
||||
|
||||
+2
-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;
|
||||
|
||||
@@ -695,7 +695,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.
|
||||
*
|
||||
|
||||
+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
+49
-16
@@ -534,6 +534,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) {
|
||||
@@ -563,37 +564,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++)
|
||||
@@ -977,8 +985,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;
|
||||
}
|
||||
@@ -1531,3 +1541,26 @@ void flushSlavesOutputBuffers(void) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
+1
-1
@@ -587,7 +587,7 @@ unsigned long estimateObjectIdleTime(robj *o) {
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
@@ -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. */
|
||||
@@ -1073,7 +1073,7 @@ void rdbLoadProgressCallback(rio *r, const void *buf, size_t len) {
|
||||
if (server.masterhost && server.repl_state == REDIS_REPL_TRANSFER)
|
||||
replicationSendNewlineToMaster();
|
||||
loadingProgress(r->processed_bytes);
|
||||
aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
|
||||
processEventsWhileBlocked();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -865,6 +865,7 @@ static void repl() {
|
||||
sds *argv;
|
||||
|
||||
config.interactive = 1;
|
||||
linenoiseSetMultiLine(1);
|
||||
linenoiseSetCompletionCallback(completionCallback);
|
||||
|
||||
/* Only use history when stdin is a tty. */
|
||||
|
||||
+115
-35
@@ -168,12 +168,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},
|
||||
@@ -258,7 +262,12 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"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},
|
||||
{"bitpos",bitposCommand,-3,"r",0,NULL,1,1,1,0,0}
|
||||
{"bitpos",bitposCommand,-3,"r",0,NULL,1,1,1,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}
|
||||
};
|
||||
|
||||
/*============================ Utility functions ============================ */
|
||||
@@ -1016,6 +1025,9 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
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) {
|
||||
@@ -1270,6 +1282,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() {
|
||||
@@ -1337,6 +1355,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;
|
||||
@@ -1414,21 +1433,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;
|
||||
|
||||
@@ -1436,22 +1455,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1487,10 +1542,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. */
|
||||
@@ -1511,11 +1572,33 @@ int listenToPort(int port, int *fds, int *count) {
|
||||
server.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;
|
||||
|
||||
@@ -1556,6 +1639,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. */
|
||||
@@ -1588,23 +1672,11 @@ 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.resident_set_size = 0;
|
||||
server.lastbgsave_status = REDIS_OK;
|
||||
server.aof_last_write_status = REDIS_OK;
|
||||
server.aof_last_write_errno = 0;
|
||||
@@ -1965,7 +2037,8 @@ int processCommand(redisClient *c) {
|
||||
|
||||
/* Don't accept write commands if there are not enough good slaves and
|
||||
* user configured the min-slaves-to-write option. */
|
||||
if (server.repl_min_slaves_to_write &&
|
||||
if (server.masterhost == NULL &&
|
||||
server.repl_min_slaves_to_write &&
|
||||
server.repl_min_slaves_max_lag &&
|
||||
c->cmd->flags & REDIS_CMD_WRITE &&
|
||||
server.repl_good_slaves_count < server.repl_min_slaves_to_write)
|
||||
@@ -2228,14 +2301,21 @@ 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.sentinel_mode) mode = "sentinel";
|
||||
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"
|
||||
@@ -2320,11 +2400,11 @@ sds genRedisInfoString(char *section) {
|
||||
"mem_allocator:%s\r\n",
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
+36
-13
@@ -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% */
|
||||
@@ -302,6 +303,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
|
||||
@@ -373,13 +377,13 @@ 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 1 /* LRU clock resolution in seconds */
|
||||
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;
|
||||
@@ -501,7 +505,7 @@ struct sharedObjectsStruct {
|
||||
*masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr,
|
||||
*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" */
|
||||
@@ -574,8 +578,7 @@ struct redisServer {
|
||||
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 */
|
||||
@@ -626,6 +629,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) */
|
||||
@@ -732,7 +736,7 @@ struct redisServer {
|
||||
list *repl_scriptcache_fifo; /* First in, first out LRU eviction. */
|
||||
int repl_scriptcache_size; /* Max number of elements. */
|
||||
/* 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 */
|
||||
@@ -754,6 +758,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 */
|
||||
@@ -935,6 +940,7 @@ int getClientLimitClassByName(char *name);
|
||||
char *getClientLimitClassName(int class);
|
||||
void flushSlavesOutputBuffers(void);
|
||||
void disconnectSlaves(void);
|
||||
int processEventsWhileBlocked(void);
|
||||
|
||||
#ifdef __GNUC__
|
||||
void addReplyErrorFormat(redisClient *c, const char *fmt, ...)
|
||||
@@ -1056,18 +1062,24 @@ 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 *zslFirstInRange(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);
|
||||
@@ -1102,7 +1114,8 @@ void populateCommandTable(void);
|
||||
void resetCommandTableStats(void);
|
||||
void adjustOpenFilesLimit(void);
|
||||
void closeListeningSockets(int unlink_unix_socket);
|
||||
void updateCachedTime();
|
||||
void updateCachedTime(void);
|
||||
void resetServerStats(void);
|
||||
|
||||
/* Set data type */
|
||||
robj *setTypeCreate(robj *value);
|
||||
@@ -1174,6 +1187,7 @@ 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);
|
||||
@@ -1292,12 +1306,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);
|
||||
@@ -1348,6 +1366,11 @@ void bitopCommand(redisClient *c);
|
||||
void bitcountCommand(redisClient *c);
|
||||
void bitposCommand(redisClient *c);
|
||||
void replconfCommand(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));
|
||||
|
||||
+113
-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,47 @@ 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;
|
||||
char dbuf[64];
|
||||
|
||||
if (lua_isnumber(lua,j+1)) {
|
||||
/* We can't use lua_tolstring() for number -> string conversion
|
||||
* since Lua uses a format specifier that loses precision. */
|
||||
lua_Number num = lua_tonumber(lua,j+1);
|
||||
|
||||
obj_len = snprintf(dbuf,sizeof(dbuf),"%.17g",(double)num);
|
||||
obj_s = dbuf;
|
||||
} else {
|
||||
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 +272,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 +346,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 +371,37 @@ 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 &&
|
||||
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 +520,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 +543,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 +923,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 +965,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 +994,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",
|
||||
|
||||
@@ -292,24 +292,37 @@ sds sdscpy(sds s, const char *t) {
|
||||
/* 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;
|
||||
}
|
||||
|
||||
|
||||
+296
-139
@@ -88,7 +88,7 @@ typedef struct sentinelAddr {
|
||||
|
||||
/* Failover machine different states. */
|
||||
#define SENTINEL_FAILOVER_STATE_NONE 0 /* No failover in progress. */
|
||||
#define SENTINEL_FAILOVER_STATE_WAIT_START 1 /* Wait for failover_start_time*/
|
||||
#define SENTINEL_FAILOVER_STATE_WAIT_START 1 /* Wait for failover_start_time*/
|
||||
#define SENTINEL_FAILOVER_STATE_SELECT_SLAVE 2 /* Select slave to promote */
|
||||
#define SENTINEL_FAILOVER_STATE_SEND_SLAVEOF_NOONE 3 /* Slave -> Master */
|
||||
#define SENTINEL_FAILOVER_STATE_WAIT_PROMOTION 4 /* Wait slave to change role */
|
||||
@@ -129,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. */
|
||||
@@ -179,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. */
|
||||
@@ -329,6 +335,7 @@ 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 =============================== */
|
||||
|
||||
@@ -371,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},
|
||||
@@ -379,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}
|
||||
};
|
||||
@@ -484,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.
|
||||
*
|
||||
@@ -607,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;
|
||||
@@ -733,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);
|
||||
@@ -923,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();
|
||||
@@ -955,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;
|
||||
@@ -1005,7 +1020,7 @@ sentinelRedisInstance *sentinelRedisInstanceLookupSlave(
|
||||
{
|
||||
sds key;
|
||||
sentinelRedisInstance *slave;
|
||||
|
||||
|
||||
redisAssert(ri->flags & SRI_MASTER);
|
||||
key = sdscatprintf(sdsempty(),
|
||||
strchr(ip,':') ? "[%s]:%d" : "%s:%d",
|
||||
@@ -1025,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.
|
||||
*
|
||||
@@ -1159,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();
|
||||
@@ -1218,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. */
|
||||
@@ -1285,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;
|
||||
@@ -1310,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]);
|
||||
@@ -1342,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;
|
||||
|
||||
@@ -1387,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);
|
||||
@@ -1457,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) {
|
||||
@@ -1492,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);
|
||||
}
|
||||
|
||||
@@ -1503,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 ======================= */
|
||||
@@ -1588,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. */
|
||||
@@ -1610,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 */
|
||||
@@ -1630,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",
|
||||
@@ -1763,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 =
|
||||
@@ -1803,6 +1887,10 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
|
||||
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
|
||||
@@ -1814,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))
|
||||
{
|
||||
@@ -1833,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. */
|
||||
@@ -1853,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)))
|
||||
@@ -1874,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 &&
|
||||
@@ -1944,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. */
|
||||
@@ -1978,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 ||
|
||||
@@ -2006,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
|
||||
@@ -2131,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
|
||||
@@ -2161,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))
|
||||
@@ -2168,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);
|
||||
}
|
||||
}
|
||||
@@ -2253,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++;
|
||||
@@ -2273,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");
|
||||
@@ -2307,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++;
|
||||
@@ -2667,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> */
|
||||
@@ -2733,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;
|
||||
|
||||
/* Check if we are in need for a reconnection of one of the
|
||||
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);
|
||||
@@ -2929,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);
|
||||
}
|
||||
@@ -2938,6 +3079,7 @@ 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
|
||||
@@ -3099,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.
|
||||
*
|
||||
@@ -3113,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;
|
||||
@@ -3500,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.
|
||||
|
||||
+9
-2
@@ -231,8 +231,15 @@ 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))
|
||||
{
|
||||
decrRefCount(sortval);
|
||||
listRelease(operations);
|
||||
return;
|
||||
}
|
||||
j+=2;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"store") && leftargs >= 1) {
|
||||
storekey = c->argv[j+1];
|
||||
|
||||
+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 = createStringObject(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 = createStringObject(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.8.7"
|
||||
#define REDIS_VERSION "2.8.10"
|
||||
|
||||
+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);
|
||||
|
||||
@@ -6,6 +6,66 @@ start_server {tags {"repl"}} {
|
||||
s -1 role
|
||||
} {slave}
|
||||
|
||||
test {If min-slaves-to-write is honored, write is accepted} {
|
||||
r config set min-slaves-to-write 1
|
||||
r config set min-slaves-max-lag 10
|
||||
r set foo 12345
|
||||
wait_for_condition 50 100 {
|
||||
[r -1 get foo] eq {12345}
|
||||
} else {
|
||||
fail "Write did not reached slave"
|
||||
}
|
||||
}
|
||||
|
||||
test {No write if min-slaves-to-write is < attached slaves} {
|
||||
r config set min-slaves-to-write 2
|
||||
r config set min-slaves-max-lag 10
|
||||
catch {r set foo 12345} err
|
||||
set err
|
||||
} {NOREPLICAS*}
|
||||
|
||||
test {If min-slaves-to-write is honored, write is accepted (again)} {
|
||||
r config set min-slaves-to-write 1
|
||||
r config set min-slaves-max-lag 10
|
||||
r set foo 12345
|
||||
wait_for_condition 50 100 {
|
||||
[r -1 get foo] eq {12345}
|
||||
} else {
|
||||
fail "Write did not reached slave"
|
||||
}
|
||||
}
|
||||
|
||||
test {No write if min-slaves-max-lag is > of the slave lag} {
|
||||
r -1 deferred 1
|
||||
r config set min-slaves-to-write 1
|
||||
r config set min-slaves-max-lag 2
|
||||
r -1 debug sleep 6
|
||||
assert {[r set foo 12345] eq {OK}}
|
||||
after 4000
|
||||
catch {r set foo 12345} err
|
||||
assert {[r -1 read] eq {OK}}
|
||||
r -1 deferred 0
|
||||
set err
|
||||
} {NOREPLICAS*}
|
||||
|
||||
test {min-slaves-to-write is ignored by slaves} {
|
||||
r config set min-slaves-to-write 1
|
||||
r config set min-slaves-max-lag 10
|
||||
r -1 config set min-slaves-to-write 1
|
||||
r -1 config set min-slaves-max-lag 10
|
||||
r set foo aaabbb
|
||||
wait_for_condition 50 100 {
|
||||
[r -1 get foo] eq {aaabbb}
|
||||
} else {
|
||||
fail "Write did not reached slave"
|
||||
}
|
||||
}
|
||||
|
||||
# Fix parameters for the next test to work
|
||||
r config set min-slaves-to-write 0
|
||||
r -1 config set min-slaves-to-write 0
|
||||
r flushall
|
||||
|
||||
test {MASTER and SLAVE dataset should be identical after complex ops} {
|
||||
createComplexDataset r 10000
|
||||
after 500
|
||||
|
||||
@@ -13,6 +13,7 @@ proc 03_test_slaves_replication {} {
|
||||
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 {
|
||||
@@ -23,23 +24,60 @@ proc 03_test_slaves_replication {} {
|
||||
}
|
||||
}
|
||||
|
||||
03_test_slaves_replication
|
||||
|
||||
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"
|
||||
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]]
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
@@ -275,6 +275,8 @@ proc foreach_instance_id {instances idvar code} {
|
||||
error $result $::errorInfo $::errorCode
|
||||
} elseif {$errcode == 4} {
|
||||
continue
|
||||
} elseif {$errcode == 3} {
|
||||
break
|
||||
} elseif {$errcode != 0} {
|
||||
return -code $errcode $result
|
||||
}
|
||||
@@ -356,6 +358,12 @@ proc kill_instance {type id} {
|
||||
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]
|
||||
return $pid == -1
|
||||
}
|
||||
|
||||
# Restart an instance previously killed by kill_instance
|
||||
proc restart_instance {type id} {
|
||||
set dirname "${type}_${id}"
|
||||
|
||||
@@ -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*}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ set ::all_tests {
|
||||
unit/dump
|
||||
unit/bitops
|
||||
unit/memefficiency
|
||||
unit/hyperloglog
|
||||
}
|
||||
# Index to the next test to run in the ::all_tests list.
|
||||
set ::next_test 0
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,42 @@ 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}
|
||||
|
||||
test {Number conversion precision test (issue #1118)} {
|
||||
r eval {
|
||||
local value = 9007199254740991
|
||||
redis.call("set","foo",value)
|
||||
return redis.call("get","foo")
|
||||
} 0
|
||||
} {9007199254740991}
|
||||
}
|
||||
|
||||
# Start a new server since the last test in this stanza will kill the
|
||||
@@ -326,6 +360,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"
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+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