diff --git a/redis.conf b/redis.conf index 258c0554b8317601ea37700982c02977c9c101b3..10560eb7606232e880dd039662c605d2e226aae9 100644 --- a/redis.conf +++ b/redis.conf @@ -281,7 +281,13 @@ slave-priority 100 # # For example to require at least 3 slaves with a lag <= 10 seconds use: # -# repl-min-slaves-to-write 3 10 +# min-slaves-to-write 3 +# min-slaves-max-lag 10 +# +# Setting one or the other to 0 disables the feature. +# +# By default min-slaves-to-write is set to 0 (feature disabled) and +# min-slaves-max-lag is set to 10. ################################## SECURITY ################################### diff --git a/src/config.c b/src/config.c index 6dabb43ef24888c80ee28cd8b502ec310d455ae1..41b00b63a1e1070f8c91f23b22b6cde838ccf3e2 100644 --- a/src/config.c +++ b/src/config.c @@ -440,6 +440,16 @@ void loadServerConfigFromString(char *config) { } } else if (!strcasecmp(argv[0],"slave-priority") && argc == 2) { server.slave_priority = atoi(argv[1]); + } else if (!strcasecmp(argv[0],"min-slaves-to-write") && argc == 2) { + server.repl_min_slaves_to_write = atoi(argv[1]); + if (server.repl_min_slaves_to_write < 0) { + err = "Invalid value for min-slaves-to-write."; goto loaderr; + } + } else if (!strcasecmp(argv[0],"min-slaves-max-lag") && argc == 2) { + server.repl_min_slaves_max_lag = atoi(argv[1]); + if (server.repl_min_slaves_max_lag < 0) { + err = "Invalid value for min-slaves-max-lag."; goto loaderr; + } } else if (!strcasecmp(argv[0],"notify-keyspace-events") && argc == 2) { int flags = keyspaceEventsStringToFlags(argv[1]); @@ -801,6 +811,14 @@ void configSetCommand(redisClient *c) { if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll <= 0) goto badfmt; server.slave_priority = ll; + } else if (!strcasecmp(c->argv[2]->ptr,"min-slaves-to-write")) { + if (getLongLongFromObject(o,&ll) == REDIS_ERR || + ll < 0) goto badfmt; + server.repl_min_slaves_to_write = ll; + } else if (!strcasecmp(c->argv[2]->ptr,"min-slaves-max-lag")) { + if (getLongLongFromObject(o,&ll) == REDIS_ERR || + ll < 0) goto badfmt; + server.repl_min_slaves_max_lag = ll; } else if (!strcasecmp(c->argv[2]->ptr,"cluster-node-timeout")) { if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll <= 0) goto badfmt; @@ -902,6 +920,8 @@ void configGetCommand(redisClient *c) { config_get_numerical_field("maxclients",server.maxclients); config_get_numerical_field("watchdog-period",server.watchdog_period); config_get_numerical_field("slave-priority",server.slave_priority); + config_get_numerical_field("min-slaves-to-write",server.repl_min_slaves_to_write); + config_get_numerical_field("min-slaves-max-lag",server.repl_min_slaves_max_lag); config_get_numerical_field("hz",server.hz); config_get_numerical_field("cluster-node-timeout",server.cluster_node_timeout); diff --git a/src/networking.c b/src/networking.c index ceef89f358b7e6ce4cbc5f99c53bc2e8671eb6c1..192ca5e0ce893a16029655b95f7e48c50ed7291c 100644 --- a/src/networking.c +++ b/src/networking.c @@ -692,6 +692,7 @@ void freeClient(redisClient *c) { * backlog. */ if (c->flags & REDIS_SLAVE && listLength(server.slaves) == 0) server.repl_no_slaves_since = server.unixtime; + refreshGoodSlavesCount(); } /* Case 2: we lost the connection with the master. */ diff --git a/src/redis.c b/src/redis.c index 0ac46ccf340dbb9fb558ecc7e8481f311ba89fb3..329314234dc1aabbcb23ed93caa83e3df13cb710 100644 --- a/src/redis.c +++ b/src/redis.c @@ -1161,6 +1161,8 @@ void createSharedObjects(void) { "-OOM command not allowed when used memory > 'maxmemory'.\r\n")); shared.execaborterr = createObject(REDIS_STRING,sdsnew( "-EXECABORT Transaction discarded because of previous errors.\r\n")); + shared.noreplicaserr = createObject(REDIS_STRING,sdsnew( + "-NOREPLICAS Not enough good slaves to write.\r\n")); shared.space = createObject(REDIS_STRING,sdsnew(" ")); shared.colon = createObject(REDIS_STRING,sdsnew(":")); shared.plus = createObject(REDIS_STRING,sdsnew("+")); @@ -1264,6 +1266,8 @@ void initServerConfig() { server.shutdown_asap = 0; server.repl_ping_slave_period = REDIS_REPL_PING_SLAVE_PERIOD; server.repl_timeout = REDIS_REPL_TIMEOUT; + server.repl_min_slaves_to_write = REDIS_DEFAULT_MIN_SLAVES_TO_WRITE; + server.repl_min_slaves_max_lag = REDIS_DEFAULT_MIN_SLAVES_MAX_LAG; server.cluster_enabled = 0; server.cluster_node_timeout = REDIS_CLUSTER_DEFAULT_NODE_TIMEOUT; server.cluster_configfile = zstrdup(REDIS_DEFAULT_CLUSTER_CONFIG_FILE); @@ -1469,6 +1473,7 @@ void initServer() { server.ops_sec_last_sample_ops = 0; server.unixtime = time(NULL); server.lastbgsave_status = REDIS_OK; + server.repl_good_slaves_count = 0; if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) { redisPanic("Can't create the serverCron time event."); exit(1); @@ -1803,6 +1808,16 @@ int processCommand(redisClient *c) { return REDIS_OK; } + /* Don't accept write commands if there are not enough good slaves and + * used configured the min-slaves-to-write option. */ + if (server.repl_min_slaves_to_write && server.repl_min_slaves_max_lag && + server.repl_good_slaves_count < server.repl_min_slaves_to_write) + { + flagTransaction(c); + addReply(c, shared.noreplicaserr); + return REDIS_OK; + } + /* Don't accept write commands if this is a read only slave. But * accept write commands if this is our master. */ if (server.masterhost && server.repl_slave_ro && diff --git a/src/redis.h b/src/redis.h index 88522533e8b4c63c81e5defc18f7d8ccabc74eb5..dd0c9181d463ec642275bc485292b5632e3cd268 100644 --- a/src/redis.h +++ b/src/redis.h @@ -118,6 +118,8 @@ #define REDIS_DEFAULT_AOF_NO_FSYNC_ON_REWRITE 0 #define REDIS_DEFAULT_ACTIVE_REHASHING 1 #define REDIS_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC 1 +#define REDIS_DEFAULT_MIN_SLAVES_TO_WRITE 0 +#define REDIS_DEFAULT_MIN_SLAVES_MAX_LAG 10 /* Protocol and I/O related defines */ #define REDIS_MAX_QUERYBUF_LEN (1024*1024*1024) /* 1GB max query buffer. */ @@ -480,7 +482,7 @@ struct sharedObjectsStruct { *colon, *nullbulk, *nullmultibulk, *queued, *emptymultibulk, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr, *outofrangeerr, *noscripterr, *loadingerr, *slowscripterr, *bgsaveerr, - *masterdownerr, *roslaveerr, *execaborterr, *noautherr, + *masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr, *oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk, *unsubscribebulk, *psubscribebulk, *punsubscribebulk, *del, *rpop, *lpop, *lpush, @@ -831,6 +833,9 @@ struct redisServer { gets released. */ time_t repl_no_slaves_since; /* We have no slaves since that time. Only valid if server.slaves len is 0. */ + int repl_min_slaves_to_write; /* Min number of slaves to write. */ + int repl_min_slaves_max_lag; /* Max lag of slaves to write. */ + int repl_good_slaves_count; /* Number of slaves with lag <= max_lag. */ /* Replication (slave) */ char *masterauth; /* AUTH with this password with master */ char *masterhost; /* Hostname of master */ @@ -1151,6 +1156,7 @@ void replicationCacheMaster(redisClient *c); void resizeReplicationBacklog(long long newsize); void replicationSetMaster(char *ip, int port); void replicationUnsetMaster(void); +void refreshGoodSlavesCount(void); /* Generic persistence functions */ void startLoading(FILE *fp); diff --git a/src/replication.c b/src/replication.c index 3157c57ad1256c3d4dc5a0981fed4479bdc30f1d..93740058e05634cd9323a87e8bceeadf45f60c82 100644 --- a/src/replication.c +++ b/src/replication.c @@ -1406,6 +1406,30 @@ void replicationResurrectCachedMaster(int newfd) { } } +/* ------------------------- MIN-SLAVES-TO-WRITE --------------------------- */ + +/* This function counts the number of slaves with lag <= min-slaves-max-lag. + * If the option is active, the server will prevent writes if there are not + * enough connected slaves with the specified lag (or less). */ +void refreshGoodSlavesCount(void) { + listIter li; + listNode *ln; + int good = 0; + + if (!server.repl_min_slaves_to_write || + !server.repl_min_slaves_max_lag) return; + + listRewind(server.slaves,&li); + while((ln = listNext(&li))) { + redisClient *slave = ln->value; + time_t lag = server.unixtime - slave->repl_ack_time; + + if (slave->replstate == REDIS_REPL_ONLINE && + lag <= server.repl_min_slaves_max_lag) good++; + } + server.repl_good_slaves_count = good; +} + /* --------------------------- REPLICATION CRON ---------------------------- */ /* Replication cron funciton, called 1 time per second. */ @@ -1519,4 +1543,7 @@ void replicationCron(void) { (int) server.repl_backlog_time_limit); } } + + /* Refresh the number of slaves with lag <= min-slaves-max-lag. */ + refreshGoodSlavesCount(); }