Skip to content

Commit 6b5daa2

Browse files
committed
First implementation of Redis Sentinel.
This commit implements the first, beta quality implementation of Redis Sentinel, a distributed monitoring system for Redis with notification and automatic failover capabilities. More info at http://redis.io/topics/sentinel
1 parent 03f412d commit 6b5daa2

File tree

8 files changed

+2614
-44
lines changed

8 files changed

+2614
-44
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*.log
55
redis-cli
66
redis-server
7+
redis-sentinel
78
redis-benchmark
89
redis-check-dump
910
redis-check-aof
@@ -24,3 +25,4 @@ deps/lua/src/luac
2425
deps/lua/src/liblua.a
2526
.make-*
2627
.prerequisites
28+
*.dSYM

sentinel.conf

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Example sentienl.conf
2+
3+
# sentinel monitor <name> <ip> <port> quorum. Tells Sentinel to monitor this
4+
# slave, and to consider it in O_DOWN (Objectively Down) state only if at
5+
# least two sentinels agree.
6+
#
7+
# Note: master name should not include special characters or spaces.
8+
# The valid charset is A-z 0-9 and the three characters ".-_".
9+
sentinel monitor mymaster 127.0.0.1 6379 2
10+
11+
# Number of milliseconds the master (or any attached slave or sentinel) should
12+
# be unreachable (as in, not acceptable reply to PING, continuously, for the
13+
# specified period) in order to consider it in S_DOWN state (Subjectively
14+
# Down).
15+
#
16+
# Default is 30 seconds.
17+
sentinel down-after-milliseconds mymaster 30000
18+
19+
# Specify if this Sentinel can start the failover for this master.
20+
sentinel can-failover mymaster yes
21+
22+
# How many slaves we can reconfigure to point to the new slave simultaneously
23+
# during the failover. Use a low number if you use the slaves to serve query
24+
# to avoid that all the slaves will be unreachable at about the same
25+
# time while performing the synchronization with the master.
26+
sentinel parallel-syncs mymaster 1
27+
28+
# Specifies the failover timeout in milliseconds. When this time has elapsed
29+
# without any progress in the failover process, it is considered concluded by
30+
# the sentinel even if not all the attached slaves were correctly configured
31+
# to replicate with the new master (however a "best effort" SLAVEOF command
32+
# is sent to all the slaves before).
33+
#
34+
# Also when 25% of this time has elapsed without any advancement, and there
35+
# is a leader switch (the sentinel did not started the failover but is now
36+
# elected as leader), the sentinel will continue the failover doing a
37+
# "takeover".
38+
#
39+
# Default is 15 minutes.
40+
sentinel failover-timeout mymaster 900000
41+

src/Makefile

+16-9
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ endif
7878

7979
REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS)
8080
REDIS_LD=$(QUIET_LINK)$(CC) $(FINAL_LDFLAGS)
81+
REDIS_INSTALL=$(QUIET_INSTALL)$(INSTALL)
8182

8283
PREFIX?=/usr/local
8384
INSTALL_BIN= $(PREFIX)/bin
@@ -93,10 +94,12 @@ ENDCOLOR="\033[0m"
9394
ifndef V
9495
QUIET_CC = @printf ' %b %b\n' $(CCCOLOR)CC$(ENDCOLOR) $(SRCCOLOR)$@$(ENDCOLOR) 1>&2;
9596
QUIET_LINK = @printf ' %b %b\n' $(LINKCOLOR)LINK$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) 1>&2;
97+
QUIET_INSTALL = @printf ' %b %b\n' $(LINKCOLOR)INSTALL$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) 1>&2;
9698
endif
9799

98100
REDIS_SERVER_NAME= redis-server
99-
REDIS_SERVER_OBJ= adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o
101+
REDIS_SENTINEL_NAME= redis-sentinel
102+
REDIS_SERVER_OBJ= adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o
100103
REDIS_CLI_NAME= redis-cli
101104
REDIS_CLI_OBJ= anet.o sds.o adlist.o redis-cli.o zmalloc.o release.o anet.o ae.o
102105
REDIS_BENCHMARK_NAME= redis-benchmark
@@ -106,7 +109,7 @@ REDIS_CHECK_DUMP_OBJ= redis-check-dump.o lzf_c.o lzf_d.o crc64.o
106109
REDIS_CHECK_AOF_NAME= redis-check-aof
107110
REDIS_CHECK_AOF_OBJ= redis-check-aof.o
108111

109-
all: $(REDIS_SERVER_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_DUMP_NAME) $(REDIS_CHECK_AOF_NAME)
112+
all: $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_DUMP_NAME) $(REDIS_CHECK_AOF_NAME)
110113
@echo ""
111114
@echo "Hint: To run 'make test' is a good idea ;)"
112115
@echo ""
@@ -151,7 +154,11 @@ endif
151154

152155
# redis-server
153156
$(REDIS_SERVER_NAME): $(REDIS_SERVER_OBJ)
154-
$(REDIS_LD) -o $@ $^ ../deps/lua/src/liblua.a $(FINAL_LIBS)
157+
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a $(FINAL_LIBS)
158+
159+
# redis-sentinel
160+
$(REDIS_SENTINEL_NAME): $(REDIS_SERVER_NAME)
161+
$(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME)
155162

156163
# redis-cli
157164
$(REDIS_CLI_NAME): $(REDIS_CLI_OBJ)
@@ -176,7 +183,7 @@ $(REDIS_CHECK_AOF_NAME): $(REDIS_CHECK_AOF_OBJ)
176183
$(REDIS_CC) -c $<
177184

178185
clean:
179-
rm -rf $(REDIS_SERVER_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_DUMP_NAME) $(REDIS_CHECK_AOF_NAME) *.o *.gcda *.gcno *.gcov redis.info lcov-html
186+
rm -rf $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_DUMP_NAME) $(REDIS_CHECK_AOF_NAME) *.o *.gcda *.gcno *.gcov redis.info lcov-html
180187

181188
.PHONY: clean
182189

@@ -217,8 +224,8 @@ src/help.h:
217224

218225
install: all
219226
mkdir -p $(INSTALL_BIN)
220-
$(INSTALL) $(REDIS_SERVER_NAME) $(INSTALL_BIN)
221-
$(INSTALL) $(REDIS_BENCHMARK_NAME) $(INSTALL_BIN)
222-
$(INSTALL) $(REDIS_CLI_NAME) $(INSTALL_BIN)
223-
$(INSTALL) $(REDIS_CHECK_DUMP_NAME) $(INSTALL_BIN)
224-
$(INSTALL) $(REDIS_CHECK_AOF_NAME) $(INSTALL_BIN)
227+
$(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(INSTALL_BIN)
228+
$(REDIS_INSTALL) $(REDIS_BENCHMARK_NAME) $(INSTALL_BIN)
229+
$(REDIS_INSTALL) $(REDIS_CLI_NAME) $(INSTALL_BIN)
230+
$(REDIS_INSTALL) $(REDIS_CHECK_DUMP_NAME) $(INSTALL_BIN)
231+
$(REDIS_INSTALL) $(REDIS_CHECK_AOF_NAME) $(INSTALL_BIN)

src/anet.c

+15
Original file line numberDiff line numberDiff line change
@@ -367,3 +367,18 @@ int anetPeerToString(int fd, char *ip, int *port) {
367367
if (port) *port = ntohs(sa.sin_port);
368368
return 0;
369369
}
370+
371+
int anetSockName(int fd, char *ip, int *port) {
372+
struct sockaddr_in sa;
373+
socklen_t salen = sizeof(sa);
374+
375+
if (getsockname(fd,(struct sockaddr*)&sa,&salen) == -1) {
376+
*port = 0;
377+
ip[0] = '?';
378+
ip[1] = '\0';
379+
return -1;
380+
}
381+
if (ip) strcpy(ip,inet_ntoa(sa.sin_addr));
382+
if (port) *port = ntohs(sa.sin_port);
383+
return 0;
384+
}

src/config.c

+11
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,17 @@ void loadServerConfigFromString(char *config) {
354354
if ((server.stop_writes_on_bgsave_err = yesnotoi(argv[1])) == -1) {
355355
err = "argument must be 'yes' or 'no'"; goto loaderr;
356356
}
357+
} else if (!strcasecmp(argv[0],"sentinel")) {
358+
/* argc == 1 is handled by main() as we need to enter the sentinel
359+
* mode ASAP. */
360+
if (argc != 1) {
361+
if (!server.sentinel_mode) {
362+
err = "sentinel directive while not in sentinel mode";
363+
goto loaderr;
364+
}
365+
err = sentinelHandleConfiguration(argv+1,argc-1);
366+
if (err) goto loaderr;
367+
}
357368
} else {
358369
err = "Bad directive or wrong number of arguments"; goto loaderr;
359370
}

src/redis.c

+72-35
Original file line numberDiff line numberDiff line change
@@ -821,13 +821,8 @@ void clientsCron(void) {
821821
* a macro is used: run_with_period(milliseconds) { .... }
822822
*/
823823

824-
/* Using the following macro you can run code inside serverCron() with the
825-
* specified period, specified in milliseconds.
826-
* The actual resolution depends on REDIS_HZ. */
827-
#define run_with_period(_ms_) if (!(loops % ((_ms_)/(1000/REDIS_HZ))))
828-
829824
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
830-
int j, loops = server.cronloops;
825+
int j;
831826
REDIS_NOTUSED(eventLoop);
832827
REDIS_NOTUSED(id);
833828
REDIS_NOTUSED(clientData);
@@ -896,11 +891,14 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
896891
}
897892

898893
/* Show information about connected clients */
899-
run_with_period(5000) {
900-
redisLog(REDIS_VERBOSE,"%d clients connected (%d slaves), %zu bytes in use",
901-
listLength(server.clients)-listLength(server.slaves),
902-
listLength(server.slaves),
903-
zmalloc_used_memory());
894+
if (!server.sentinel_mode) {
895+
run_with_period(5000) {
896+
redisLog(REDIS_VERBOSE,
897+
"%d clients connected (%d slaves), %zu bytes in use",
898+
listLength(server.clients)-listLength(server.slaves),
899+
listLength(server.slaves),
900+
zmalloc_used_memory());
901+
}
904902
}
905903

906904
/* We need to do a few operations on clients asynchronously. */
@@ -985,6 +983,11 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
985983
if (server.cluster_enabled) clusterCron();
986984
}
987985

986+
/* Run the sentinel timer if we are in sentinel mode. */
987+
run_with_period(100) {
988+
if (server.sentinel_mode) sentinelTimer();
989+
}
990+
988991
server.cronloops++;
989992
return 1000/REDIS_HZ;
990993
}
@@ -2444,21 +2447,26 @@ void usage() {
24442447
fprintf(stderr," ./redis-server /etc/redis/6379.conf\n");
24452448
fprintf(stderr," ./redis-server --port 7777\n");
24462449
fprintf(stderr," ./redis-server --port 7777 --slaveof 127.0.0.1 8888\n");
2447-
fprintf(stderr," ./redis-server /etc/myredis.conf --loglevel verbose\n");
2450+
fprintf(stderr," ./redis-server /etc/myredis.conf --loglevel verbose\n\n");
2451+
fprintf(stderr,"Sentinel mode:\n");
2452+
fprintf(stderr," ./redis-server /etc/sentinel.conf --sentinel\n");
24482453
exit(1);
24492454
}
24502455

24512456
void redisAsciiArt(void) {
24522457
#include "asciilogo.h"
24532458
char *buf = zmalloc(1024*16);
2459+
char *mode = "stand alone";
2460+
2461+
if (server.cluster_enabled) mode = "cluster";
2462+
else if (server.sentinel_mode) mode = "sentinel";
24542463

24552464
snprintf(buf,1024*16,ascii_logo,
24562465
REDIS_VERSION,
24572466
redisGitSHA1(),
24582467
strtol(redisGitDirty(),NULL,10) > 0,
24592468
(sizeof(long) == 8) ? "64" : "32",
2460-
server.cluster_enabled ? "cluster" : "stand alone",
2461-
server.port,
2469+
mode, server.port,
24622470
(long) getpid()
24632471
);
24642472
redisLogRaw(REDIS_NOTICE|REDIS_LOG_RAW,buf);
@@ -2496,17 +2504,53 @@ void setupSignalHandlers(void) {
24962504

24972505
void memtest(size_t megabytes, int passes);
24982506

2507+
/* Returns 1 if there is --sentinel among the arguments or if
2508+
* argv[0] is exactly "redis-sentinel". */
2509+
int checkForSentinelMode(int argc, char **argv) {
2510+
int j;
2511+
2512+
if (strstr(argv[0],"redis-sentinel") != NULL) return 1;
2513+
for (j = 1; j < argc; j++)
2514+
if (!strcmp(argv[j],"--sentinel")) return 1;
2515+
return 0;
2516+
}
2517+
2518+
/* Function called at startup to load RDB or AOF file in memory. */
2519+
void loadDataFromDisk(void) {
2520+
long long start = ustime();
2521+
if (server.aof_state == REDIS_AOF_ON) {
2522+
if (loadAppendOnlyFile(server.aof_filename) == REDIS_OK)
2523+
redisLog(REDIS_NOTICE,"DB loaded from append only file: %.3f seconds",(float)(ustime()-start)/1000000);
2524+
} else {
2525+
if (rdbLoad(server.rdb_filename) == REDIS_OK) {
2526+
redisLog(REDIS_NOTICE,"DB loaded from disk: %.3f seconds",
2527+
(float)(ustime()-start)/1000000);
2528+
} else if (errno != ENOENT) {
2529+
redisLog(REDIS_WARNING,"Fatal error loading the DB. Exiting.");
2530+
exit(1);
2531+
}
2532+
}
2533+
}
2534+
24992535
int main(int argc, char **argv) {
2500-
long long start;
25012536
struct timeval tv;
25022537

25032538
/* We need to initialize our libraries, and the server configuration. */
25042539
zmalloc_enable_thread_safeness();
25052540
srand(time(NULL)^getpid());
25062541
gettimeofday(&tv,NULL);
25072542
dictSetHashFunctionSeed(tv.tv_sec^tv.tv_usec^getpid());
2543+
server.sentinel_mode = checkForSentinelMode(argc,argv);
25082544
initServerConfig();
25092545

2546+
/* We need to init sentinel right now as parsing the configuration file
2547+
* in sentinel mode will have the effect of populating the sentinel
2548+
* data structures with master nodes to monitor. */
2549+
if (server.sentinel_mode) {
2550+
initSentinelConfig();
2551+
initSentinel();
2552+
}
2553+
25102554
if (argc >= 2) {
25112555
int j = 1; /* First option to parse in argv[] */
25122556
sds options = sdsempty();
@@ -2558,27 +2602,20 @@ int main(int argc, char **argv) {
25582602
initServer();
25592603
if (server.daemonize) createPidFile();
25602604
redisAsciiArt();
2561-
redisLog(REDIS_WARNING,"Server started, Redis version " REDIS_VERSION);
2562-
#ifdef __linux__
2563-
linuxOvercommitMemoryWarning();
2564-
#endif
2565-
start = ustime();
2566-
if (server.aof_state == REDIS_AOF_ON) {
2567-
if (loadAppendOnlyFile(server.aof_filename) == REDIS_OK)
2568-
redisLog(REDIS_NOTICE,"DB loaded from append only file: %.3f seconds",(float)(ustime()-start)/1000000);
2569-
} else {
2570-
if (rdbLoad(server.rdb_filename) == REDIS_OK) {
2571-
redisLog(REDIS_NOTICE,"DB loaded from disk: %.3f seconds",
2572-
(float)(ustime()-start)/1000000);
2573-
} else if (errno != ENOENT) {
2574-
redisLog(REDIS_WARNING,"Fatal error loading the DB. Exiting.");
2575-
exit(1);
2576-
}
2605+
2606+
if (!server.sentinel_mode) {
2607+
/* Things only needed when not runnign in Sentinel mode. */
2608+
redisLog(REDIS_WARNING,"Server started, Redis version " REDIS_VERSION);
2609+
#ifdef __linux__
2610+
linuxOvercommitMemoryWarning();
2611+
#endif
2612+
loadDataFromDisk();
2613+
if (server.ipfd > 0)
2614+
redisLog(REDIS_NOTICE,"The server is now ready to accept connections on port %d", server.port);
2615+
if (server.sofd > 0)
2616+
redisLog(REDIS_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
25772617
}
2578-
if (server.ipfd > 0)
2579-
redisLog(REDIS_NOTICE,"The server is now ready to accept connections on port %d", server.port);
2580-
if (server.sofd > 0)
2581-
redisLog(REDIS_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
2618+
25822619
aeSetBeforeSleepProc(server.el,beforeSleep);
25832620
aeMain(server.el);
25842621
aeDeleteEventLoop(server.el);

src/redis.h

+18
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,11 @@
257257
#define REDIS_PROPAGATE_AOF 1
258258
#define REDIS_PROPAGATE_REPL 2
259259

260+
/* Using the following macro you can run code inside serverCron() with the
261+
* specified period, specified in milliseconds.
262+
* The actual resolution depends on REDIS_HZ. */
263+
#define run_with_period(_ms_) if (!(server.cronloops%((_ms_)/(1000/REDIS_HZ))))
264+
260265
/* We can print the stacktrace, so our assert is defined this way: */
261266
#define redisAssertWithInfo(_c,_o,_e) ((_e)?(void)0 : (_redisAssertWithInfo(_c,_o,#_e,__FILE__,__LINE__),_exit(1)))
262267
#define redisAssert(_e) ((_e)?(void)0 : (_redisAssert(#_e,__FILE__,__LINE__),_exit(1)))
@@ -579,6 +584,7 @@ struct redisServer {
579584
int arch_bits; /* 32 or 64 depending on sizeof(long) */
580585
int cronloops; /* Number of times the cron function run */
581586
char runid[REDIS_RUN_ID_SIZE+1]; /* ID always different at every exec. */
587+
int sentinel_mode; /* True if this instance is a Sentinel. */
582588
/* Networking */
583589
int port; /* TCP listening port */
584590
char *bindaddr; /* Bind address or NULL */
@@ -1115,6 +1121,12 @@ void clusterCron(void);
11151121
clusterNode *getNodeByQuery(redisClient *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot, int *ask);
11161122
void clusterPropagatePublish(robj *channel, robj *message);
11171123

1124+
/* Sentinel */
1125+
void initSentinelConfig(void);
1126+
void initSentinel(void);
1127+
void sentinelTimer(void);
1128+
char *sentinelHandleConfiguration(char **argv, int argc);
1129+
11181130
/* Scripting */
11191131
void scriptingInit(void);
11201132

@@ -1280,4 +1292,10 @@ void enableWatchdog(int period);
12801292
void disableWatchdog(void);
12811293
void watchdogScheduleSignal(int period);
12821294
void redisLogHexDump(int level, char *descr, void *value, size_t len);
1295+
1296+
#define redisDebug(fmt, ...) \
1297+
printf("DEBUG %s:%d > " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
1298+
#define redisDebugMark() \
1299+
printf("-- MARK %s:%d --\n", __FILE__, __LINE__)
1300+
12831301
#endif

0 commit comments

Comments
 (0)