diff --git a/redis.conf b/redis.conf index 797347c69ea611f2efd5d17b93c01f2307b4628a..d7bdcfd53d400e9d15b9934af4d43115e8792a73 100644 --- a/redis.conf +++ b/redis.conf @@ -445,6 +445,23 @@ slowlog-log-slower-than 10000 # You can reclaim memory used by the slow log with SLOWLOG RESET. slowlog-max-len 128 +############################# Event notification ############################## + +# Redis can notify Pub/Sub clients about events happening in the key space. +# This feature is documented at http://redis.io/topics/keyspace-events +# +# For instance if keyspace events notification is enabled, and a client +# performs a DEL operation on key "foo" stored in the Database 0, two +# messages will be published via Pub/Sub: +# +# PUBLISH __keyspace@0__:foo del +# PUBLISH __keyevent@0__:del foo +# +# While the overhead of this feature is relatively small most users don't +# need it so it is disabled by default. You can enable it setting the +# following configuration option to yes. +notify-keyspace-events no + ############################### ADVANCED CONFIG ############################### # Hashes are encoded using a memory efficient data structure when they have a diff --git a/src/Makefile b/src/Makefile index 9d0ec4eb85c46978e322d0f27b48948bab037462..242cf099fcdfe9719755f9077a9426c5a973e5bb 100644 --- a/src/Makefile +++ b/src/Makefile @@ -99,7 +99,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 +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 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 diff --git a/src/Makefile.dep b/src/Makefile.dep index 4e07a563d78de95fe7a410adb44374474438f876..35025070c7e84d94002b4e9854f6c5c3b00663f8 100644 --- a/src/Makefile.dep +++ b/src/Makefile.dep @@ -40,6 +40,9 @@ networking.o: networking.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 +notify.o: notify.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 object.o: object.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 diff --git a/src/config.c b/src/config.c index 1fa498d3c8c519f60a6f3b6052e6f175d5df6520..bfe73482fc761c288901da0b58decd9b22c41f3c 100644 --- a/src/config.c +++ b/src/config.c @@ -384,6 +384,10 @@ void loadServerConfigFromString(char *config) { } } else if (!strcasecmp(argv[0],"slave-priority") && argc == 2) { server.slave_priority = atoi(argv[1]); + } else if (!strcasecmp(argv[0],"notify-keyspace-events") && argc == 2) { + if ((server.notify_keyspace_events = yesnotoi(argv[1])) == -1) { + err = "argument must be 'yes' or 'no'"; goto loaderr; + } } else if (!strcasecmp(argv[0],"sentinel")) { /* argc == 1 is handled by main() as we need to enter the sentinel * mode ASAP. */ @@ -702,11 +706,11 @@ void configSetCommand(redisClient *c) { if (yn == -1) goto badfmt; server.rdb_compression = yn; - } else if (!strcasecmp(c->argv[2]->ptr,"rdbchecksum")) { + } else if (!strcasecmp(c->argv[2]->ptr,"notify-keyspace-events")) { int yn = yesnotoi(o->ptr); if (yn == -1) goto badfmt; - server.rdb_checksum = yn; + server.notify_keyspace_events = yn; } else if (!strcasecmp(c->argv[2]->ptr,"slave-priority")) { if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll <= 0) goto badfmt; @@ -816,6 +820,7 @@ void configGetCommand(redisClient *c) { config_get_bool_field("rdbcompression", server.rdb_compression); config_get_bool_field("rdbchecksum", server.rdb_checksum); config_get_bool_field("activerehashing", server.activerehashing); + config_get_bool_field("notify-keyspace-events", server.notify_keyspace_events); /* Everything we can't handle with macros follows. */ diff --git a/src/notify.c b/src/notify.c new file mode 100644 index 0000000000000000000000000000000000000000..70c96566389ba53808b58a4682351fef01ee03b1 --- /dev/null +++ b/src/notify.c @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2013, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * 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 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. + */ + +#include "redis.h" + +/* This file implements keyspace events notification via Pub/Sub ad + * described at http://redis.io/topics/keyspace-events. + * + * The API provided to the rest of the Redis core is a simple function: + * + * notifyKeyspaceEvent(char *event, robj *key, int dbid); + * + * 'event' is a C string representing the event name. + * 'key' is a Redis object representing the key name. + * 'dbid' is the database ID where the key lives. + */ + +void notifyKeyspaceEvent(char *event, robj *key, int dbid) { + sds keyspace_chan, keyevent_chan; + int len; + char buf[24]; + robj *chan1, *chan2, *eventobj; + + /* The prefix of the two channels is identical if not for + * 'keyspace' that is 'keyevent' in the event channel name, so + * we build a single prefix and overwrite 'event' with 'space'. */ + keyspace_chan = sdsnewlen("__keyspace@",11); + len = ll2string(buf,sizeof(buf),dbid); + keyspace_chan = sdscatlen(keyspace_chan, buf, len); + keyspace_chan = sdscatlen(keyspace_chan, "__:", 3); + keyevent_chan = sdsdup(keyspace_chan); /* Dup the prefix. */ + memcpy(keyevent_chan+5,"event",5); /* Fix it. */ + + eventobj = createStringObject(event,strlen(event)); + + /* The keyspace channel name has a trailing key name, while + * the keyevent channel name has a trailing event name. */ + keyspace_chan = sdscatsds(keyspace_chan, key->ptr); + keyevent_chan = sdscatsds(keyspace_chan, eventobj->ptr); + chan1 = createObject(REDIS_STRING, keyspace_chan); + chan2 = createObject(REDIS_STRING, keyevent_chan); + + /* Finally publish the two notifications. */ + pubsubPublishMessage(chan1, eventobj); + pubsubPublishMessage(chan2, key); + + /* Release objects. */ + decrRefCount(eventobj); + decrRefCount(keyspace_chan); + decrRefCount(keyevent_chan); +} diff --git a/src/redis.c b/src/redis.c index cb78de7ed75ed8e817753982a34e8542ef23dc72..ce72dd93d7e5e14df43e9619762268db63e9e26f 100644 --- a/src/redis.c +++ b/src/redis.c @@ -1125,6 +1125,7 @@ void initServerConfig() { server.rdb_compression = 1; server.rdb_checksum = 1; server.activerehashing = 1; + server.notify_keyspace_events = 0; server.maxclients = REDIS_MAX_CLIENTS; server.bpop_blocked_clients = 0; server.maxmemory = 0; diff --git a/src/redis.h b/src/redis.h index 334d7b65d1086440048cacfbafaef0c667d712d7..601725a335129a867882136dd8cfeb7fa3fe6edd 100644 --- a/src/redis.h +++ b/src/redis.h @@ -646,6 +646,7 @@ struct redisServer { /* Pubsub */ dict *pubsub_channels; /* Map channels to list of subscribed clients */ list *pubsub_patterns; /* A list of pubsub_patterns */ + int notify_keyspace_events; /* Propagate keyspace events via Pub/Sub. */ /* Scripting */ lua_State *lua; /* The Lua interpreter. We use just one for all clients */ redisClient *lua_client; /* The "fake client" to query Redis from Lua */ @@ -999,6 +1000,7 @@ int pubsubUnsubscribeAllPatterns(redisClient *c, int notify); void freePubsubPattern(void *p); int listMatchPubsubPattern(void *a, void *b); int pubsubPublishMessage(robj *channel, robj *message); +void notifyKeyspaceEvent(char *event, robj *key, int dbid); /* Configuration */ void loadServerConfig(char *filename, char *options);