Redis source code analysis (30th) --- pubsub publish and subscribe Mode
Today I learned the term "Publish and subscribe mode" in Redis, which was first introduced to JMS (Java Message Service) java Message Service. I subscribe to these types of messages. Only when these types of messages are broadcast and sent Will I filter other messages directly, this ensures an efficient transmission efficiency. Next, let's take a look at how Redis achieves this publishing and subscription model. Let's take a look at the Simple API construction in it;
/* Publish * Pubsub low level API * publish */void freePubsubPattern (void * p)/* release publish/subscribe mode */int listMatchPubsubPattern (void * a, void * B) /* Whether the publishing and subscription modes match */int clientSubscriptionsCount (redisClient * c)/* return the number of subscribed clients, including channels + patterns pipelines and modes */int pubsubSubscribeChannel (redisClient * c, robj * channel)/* Client subscribe to a Channel Pipeline */int pubsubUnsubscribeChannel (redisClient * c, robj * channel, int publish y)/* cancel the Channel */int pubsubSubscribePattern (redisClient * c, robj * pattern)/* Client subscription mode */int pubsubUnsubscribePattern (redisClient * c, robj * pattern, int regular y)/* Client cancels subscription pattern mode */int pubsubUnsubscribeAllChannels (redisClient * c, int regular y) /* All channels subscribed by the client */int pubsubUnsubscribeAllPatterns (redisClient * c, int publish y)/* The client unsubscribes to all pattern modes */int pubsubPublishMessage (robj * Channel, robj * message)/* sends a message to all clients that have subscribed to the Channel * // * ------------ PUB/sub api ---------------- */void subscribeCommand (redisClient * c) /* subscribe to the Channel command */void unsubscribeCommand (redisClient * c)/* unsubscribe to the Channel command */void psubscribeCommand (redisClient * c) /* subscription MODE Command */void punsubscribeCommand (redisClient * c)/* cancel subscription MODE Command */void publishCommand (redisClient * c) /* publish message command */void pubsubCommand (redisClient * c)/* publish subscription command */
The Pattern and Channel (channels, called pipelines) frequently appear here. That is to say, all subsequent releases and subscriptions are based on the two. The following describes how to implement the mode in Redis:
1. Maintains a pubsub_channels Channel list in the RedisClient and records the Channel subscribed by the client.
2. on the Server, a variable similar to pubsub_channels is maintained, which is a dict dictionary variable. Each Channel corresponds to a batch of clients subscribed to this Channel, that is, Channel --> list of Clients
3. When a Client publish and a message are sent, the pubsub_channels on the server will first find the corresponding Channel, traverse the Client in it, and then send a notification to complete the entire publishing and subscription mode.
Let's take a simple look at the implementation of Redis's method of subscribing to a Channel;
/* Subscribe a client to a channel. returns 1 if the operation succeeded, or * 0 if the client was already subscribed to that channel. * // * The Client subscribes to a Channel Pipeline */int pubsubSubscribeChannel (redisClient * c, robj * channel) {struct dictEntry * de; list * clients = NULL; int retval = 0; /* Add the channel to the client-> channels hash table * // Add Channel if (dictAdd (c-> pubsub_channels, channel, NULL) to the Client dictionary pubsub_channels) = DICT_ OK) {retval = 1; incrRefCount (channel);/* Add the client to the channel-> list of clients hash table * // Add the Clietn to the pubsub_channels in the server, in the corresponding list, de = dictFind (server. pubsub_channels, channel); if (de = NULL) {// if the Client list of this channel is empty, create a new list and add clients = listCreate (); dictAdd (server. pubsub_channels, channel, clients); incrRefCount (channel) ;}else {// otherwise, obtain the client list for this channel and add a new client clients = dictGetVal (de) At the end );} listAddNodeTail (clients, c);}/* handle y the client * // Add it to the reply client addReply (c, shared. mbulkhdr [3]); addReply (c, shared. subscribebulk); addReplyBulk (c, channel); addReplyLongLong (c, clientSubscriptionsCount (c); return retval ;}
The add operation is mainly divided into two parts. The pubsub_channels maintained internally by the Client itself is added as a dict dictionary object, and then the client list added in the pubsub_channels maintained by the server. When deleting a Channel, perform the following two steps:
/* Unsubscribe a client from a channel. returns 1 if the operation succeeded, or * 0 if the client was not subscribed to the specified channel. * // * cancel the Channel */int pubsubUnsubscribeChannel (redisClient * c, robj * channel, int publish y) {struct dictEntry * de; list * clients; listNode * ln; int retval = 0;/* Remove the channel from the client-> channels hash table */incrRefCount (channel );/* Channel may be just a pointer to the same object we have in the hash tables. protect it... * // Delete the Channel if (dictDelete (c-> pubsub_channels, channel) = DICT_ OK) {retval = 1; /* Remove the client from the channel-> clients list hash table * // then Remove the Client list corresponding to the Channel de = dictFind (server. pubsub_channels, channel); redisAssertWithInfo (c, NULL, de! = NULL); clients = dictGetVal (de); ln = listSearchKey (clients, c); redisAssertWithInfo (c, NULL, ln! = NULL); listDelNode (clients, ln); if (listLength (clients) = 0) {/* Free the list and associated hash entry at all if this was * the latest client, so that it will be possible to abuse * Redis PUBSUB creating millions of channels. */dictDelete (server. pubsub_channels, channel) ;}}/* sort y the client */if (addrey) {addReply (c, shared. mbulkhdr [3]); addReply (c, shared. unsubscribebulk); addReplyBulk (c, channel); addReplyLongLong (c, dictSize (c-> pubsub_channels) + listLength (c-> pubsub_patterns);} decrRefCount (channel ); /* it is finally safe to release it */return retval ;}
There is also the corresponding mode subscription and cancel subscription operations, the principle is exactly the same as the channel, the difference between the two is that pattern is used to match the Channel, what does this mean. The answer will be provided later. Finally, let's take a look at the most core method. The client sends a step message method:
/* Publish a message * // * sends a message to all clients that have subscribed to the Channel */int pubsubPublishMessage (robj * channel, robj * message) {int receivers = 0; struct dictEntry * de; listNode * ln; listIter li;/* Send to clients listening for that channel * // find the dictEntry de = dictFind (server. pubsub_channels, channel); if (de) {// obtain the list of customer tickets corresponding to this Channel * list = dictGetVal (de); listNode * ln; listIter li; listRewi Nd (list, & li); while (ln = listNext (& li ))! = NULL) {// retrieve the customer orders in the List in sequence, and add the message reply redisClient * c = ln-> value; addReply (c, shared. mbulkhdr [3]); addReply (c, shared. messagebulk); addReplyBulk (c, channel); // Add the message reply addReplyBulk (c, message); receivers ++ ;}} /* Send to clients listening to matching channels * // * Send the message to the client trying to match the Channel */if (listLength (server. pubsub_patterns) {listRewind (server. pubsub_patterns, & li); channel = getDecodedObject (channel ); While (ln = listNext (& li ))! = NULL) {pubsubPattern * pat = ln-> value; // if the client mode matches the Channel, the message if (stringmatchlen (char *) pat-> pattern-> ptr, sdslen (pat-> pattern-> ptr), (char *) channel-> ptr, sdslen (channel-> ptr), 0 )) {addReply (pat-> client, shared. mbulkhdr [4]); addReply (pat-> client, shared. pmessagebulk); addReplyBulk (pat-> client, pat-> pattern); addReplyBulk (pat-> client, channel); addReplyBulk (pat-> client, message ); receivers ++ ;}} decrRefCount (channel) ;}return receivers ;}
The role of pattern is shown above. If a pattern matches the Channel, the client in the pattern will also receive messages. In server-> pubsub_patterns, pubsub_patterns is a list. Each pattern in it corresponds to only one Client, that is, the above pat-> client, this is essentially different from the Channel. After talking about the basic operations of the publish/subscribe mode, I will also talk about the related notify y notification classes. There are only three methods for notification,
/* ----------------- API ----------------- */int keyspaceEventsStringToFlags (char * classes)/* convert the key-value character type to the corresponding Class type */sds keyspaceEventsFlagsToString (int flags) /* convert the input flag value class to the character type */void policykeyspaceevent (int type, char * event, robj * key, int dbid)/* release notification method, there are two types: keySpace notifications and keyEvent notifications */
The conversion between string To flag and flag To String is involved, and I do not know where this will be used;
/* Turn a string representing notification classes into an integer * representing notification classes flags xored. ** The function returns-1 if the input contains characters not mapping to * any class. * // * convert the key-value character type to the corresponding Class type */int keyspaceEventsStringToFlags (char * classes) {char * p = classes; int c, flags = 0; while (c = * p ++ )! = '\ 0') {switch (c) {case 'A': flags | = redis_policy_all; break; case 'G': flags | = redis_policy_generic; break; case '$': flags | = redis_policy_string; break; case 'l': flags | = redis_policy_list; break; case 's': flags | = redis_policy_set; break; case 'H': flags | = redis_policy_hash; break; case 'Z': flags | = redis_policy_zset; break; case 'X': flags | = redis_policy_expired; break; case 'E': flags | = redis_policy_evicted; break; case 'K': flags | = redis_policy_keyspace; break; case 'E': flags | = redis_policy_keyevent; break; default: return-1 ;}} return flags ;}
It should be a conversion between the type of the response keyboard input and the Redis type. The notify method also has an event Notification method:
/* The API provided to the rest of the Redis core is a simple function: ** policykeyspaceevent (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. * // * release notification method, which can be divided into two types: keySpace notification and keyEvent notification */void policykeyspaceevent (int type, char * event, robj * key, int dbid) {Sds chan; robj * chanobj, * eventobj; int len =-1; char buf [24];/* If events for this class of events are off, return ASAP. */if (! (Server. notify_keyspace_events & type) return; eventobj = createStringObject (event, strlen (event); // two notification forms, slightly different/* _ keyspace @
__:
Communications. */if (server. policy_keyspace_events & redis_policy_keyspace) {chan = sdsnewlen ("_ keyspace @", 11); len = ll2string (buf, sizeof (buf), dbid); chan = sdscatlen (chan, buf, len); chan = sdscatlen (chan, "__:", 3); chan = sdscatsds (chan, key-> ptr); chanobj = createObject (REDIS_STRING, chan ); // The preceding operations are performed in the component format string and the message is finally published. The following keyEvent notifications are the same as pubsubPublishMessage (chanobj, eventobj); decrRefCount (chanobj);}/* _ keyevente @
__:
Communications. */if (server. policy_keyspace_events & redis_policy_keyevent) {chan = sdsnewlen ("_ keyevent @", 11); if (len =-1) len = ll2string (buf, sizeof (buf ), dbid); chan = sdscatlen (chan, buf, len); chan = sdscatlen (chan, "__:", 3); chan = sdscatsds (chan, eventobj-> ptr ); chanobj = createObject (REDIS_STRING, chan); pubsubPublishMessage (chanobj, key); decrRefCount (chanobj);} decrRefCount (eventobj );}
There are two types of Event Notifications: keySpace and keyEvent. How to use it? Let's take a look at it later.