The principle analysis of Redis Publish/subscribe mechanism

Source: Internet
Author: User

Redis implements publishing and subscription functionality through commands such as PUBLISH, SUBSCRIBE, and Psubscribe.

These commands are widely used to build instant messaging applications such as network chat rooms (chatroom) and live broadcasts, real-time reminders, and so on.

This article is to deepen our understanding of Redis by analyzing the PUBSUB.C files in the Redis source code to understand the underlying implementation of the publish and subscribe mechanism.

Subscribe, Post and Unsubscribe

Before you start studying the source code, you might want to review how several related commands are used.

The PUBLISH command is used to send information to a given channel, and the return value is the number of subscribers who receive the message:

Redis> PUBLISH treehole "Top secret here ..."
(integer) 0
redis> PUBLISH chatroom "Hi?"
(integer) 1

The SUBSCRIBE command subscribes to one or more channels given:

Redis> SUBSCRIBE Chatroom
Reading messages ... (Press Ctrl-c to quit)
1) "Subscribe" # Subscription Feedback
2) "Chatroom" # subscribed Channel
3) (integer) 1 # The number of channels/modes currently subscribed to by the client
1) "Message" # Information
2) "Chatroom" # Channel to send messages
3) "Hi?" # Information Content

The return value of SUBSCRIBE, 1) for SUBSCRIBE is the feedback of the subscription, and 1 for the message is the information sent by the subscribed channel.

SUBSCRIBE can also subscribe to multiple channels so that the information it receives may come from multiple channels:

Redis> SUBSCRIBE Chatroom Talk-to-jack
Reading messages ... (Press Ctrl-c to quit)
1) "Subscribe" # Subscribe to chatroom feedback
2) "Chatroom"
3) (integer) 1
1) "Subscribe" # Subscribe to Talk-to-jack Feedback
2) "Talk-to-jack"
3) (integer) 2
1) "Message" # Messages from chatroom
2) "Chatroom"
3) "Yahoo!"
1) "Message" # Messages from Talk-to-peter
2) "Talk-to-jack"
3) "GoodMorning, Peter."

Psubscribe provides a way to subscribe to all channels that match a given pattern, for example, by using it.* as input, you can subscribe to all it. The beginning of the channel, such as It.news, It.blog, it.tweets, etc.:

Redis> Psubscribe it.*
Reading messages ... (Press Ctrl-c to quit)
1) "Psubscribe"
2) "it.*"
3) (integer) 1
1) "Pmessage"
2) "it.*" # matching pattern
3) "It.news" # source channel of the message
4) "Redis 2.6RC5 release" # Message content
1) "Pmessage"
2) "it.*"
3) "It.blog"
4) "Why NoSQL Matters"
1) "Pmessage"
2) "it.*"
3) "It.tweet"
4) "@redis: When would the 2.6 stable release?"

Of course, Psubscribe can also accept multiple parameters to match multiple patterns.

Finally, the unsubscribe command and Punsubscribe are responsible for retiring the given channel or mode.

Publishing and subscription mechanisms

When a client sends a message to a subscriber via the PUBLISH command, we call the client Publisher.

When a client uses the SUBSCRIBE or Psubscribe command to receive information, we call the client the Subscriber (Subscriber).

To understand the relationship between a decoupled publisher (publisher) and a Subscriber (subscriber), Redis uses the channel as an intermediary for both-the publisher publishes the information directly to the channel, and the channel is responsible for sending the information to the appropriate subscribers. There is no correlation between the publisher and the Subscriber, nor does it know the other person's presence:

  

Click to enlarge

Now that we know the mechanism for publishing and subscribing, we can begin to look at the specific implementation, starting with the Redis subscription command.

Implementation of the SUBSCRIBE command

As mentioned earlier, Redis takes all the tasks of receiving and transmitting information to the channel, and all channel information is stored in the REDISSERVER structure:

struct Redisserver {
Omitted...
Dict *pubsub_channels; Map channels to list of subscribed clients
Omitted...
};

Pubsub_channels is a dictionary, the key of the dictionary is a channel, and the dictionary value is a linked list, the list of all subscriptions to the channel to save the client.

For example, if you have a channel called news in a Redisserver instance, and the channel is subscribed to by both Client_123 and client_456 two clients, the redisserver structure should look like this:

  

Click to enlarge

As you can see, the key to implementing the SUBSCRIBE command is to add the client to the subscription list for a given channel.

The function pubsubsubscribechannel is the underlying implementation of the SUBSCRIBE command, which completes the task of adding the client to the subscription list:

Subscribe to designated channels
The subscription successfully returns 1, if already subscribed, returns 0
int Pubsubsubscribechannel (redisclient *c, RobJ *channel) {
struct Dictentry *de;
List *clients = NULL;
int retval = 0;
/* ADD the channel to the client, channels hash Table */
Dictadd returns DICT_OK when a new element is added successfully
So this sentence indicates that if the new subscription channel succeeds, then ...
if (Dictadd (c->pubsub_channels,channel,null) = = DICT_OK) {
retval = 1;
Incrrefcount (channel);
/* Add the client to the channel-list of clients hash table */
Adding a client to a linked list that subscribes to a given channel
This list is the value of a hash table, the key of the hash table is the given channel
This hash table is stored in the server.pubsub_channels.
de = Dictfind (Server.pubsub_channels,channel);
if (de = = NULL) {
If the de equals NULL
Indicates that the client is the first client to subscribe to this channel
Then create a new list and add it to the hash table
Clients = Listcreate ();
Dictadd (server.pubsub_channels,channel,clients);
Incrrefcount (channel);
} else {
If the de is not empty, remove the clients list
Clients = Dictgetval (DE);
}
Joining a client to a linked list
Listaddnodetail (CLIENTS,C);
}
/* Notify the client */
Addreply (C,shared.mbulkhdr[3]);
Addreply (C,shared.subscribebulk);
Back to subscribed channels
Addreplybulk (C,channel);
Returns the sum of the number of channels and modes currently subscribed by the client
Addreplylonglong (C,dictsize (c->pubsub_channels) +listlength (c->pubsub_patterns));
return retval;
}

Implementation of the Psubscribe command

In addition to subscribing directly to a given channel, you can subscribe to a pattern using psubscribe, and subscribe to a pattern that is equivalent to subscribing to all channel matching this pattern.

Like the Redisserver.pubsub_channels property, the Redisserver.pubsub_patterns property is used to save all subscribed patterns, unlike Pubsub_channels, which pubsub_ Patterns is a linked list (not a dictionary):

struct Redisserver {
Omitted...
List *pubsub_patterns; A List of Pubsub_patterns
Omitted...
};

Each node of the pubsub_patterns is an instance of a Pubsubpattern structure that holds the subscribed schema and the client clients that subscribe to the pattern:

typedef struct PUBSUBPATTERN {
Redisclient *client;
RobJ *pattern;
} Pubsubpattern;

For example, suppose that in a redisserver instance, a pattern called news.* is subscribed by both the client client_789 and client_999, then this redisserver structure should look like this:

  

Click to enlarge

It is now possible to know that the key to implementing the Psubscribe command is to add the client and subscription patterns to the redisserver.pubsub_patterns.

Pubsubsubscribepattern is the underlying implementation of Psubscribe, which adds the client and the subscribed schema to the Redisserver.pubsub_patterns:

Subscribe to a specified pattern
The subscription successfully returns 1, if already subscribed, returns 0
int Pubsubsubscribepattern (redisclient *c, RobJ *pattern) {
int retval = 0;
To find the specified pattern in C->pubsub_patterns
If the return value is NULL, it means that the pattern has not been subscribed to by this client.
if (Listsearchkey (c->pubsub_patterns,pattern) = = NULL) {
retval = 1;
Add pattern to client Pubsub_patterns
Listaddnodetail (C->pubsub_patterns,pattern);
Incrrefcount (pattern);
Adding the pattern to the server
Pubsubpattern *pat;
Pat = Zmalloc (sizeof (*PAT));
Pat->pattern = Getdecodedobject (pattern);
Pat->client = C;
Listaddnodetail (SERVER.PUBSUB_PATTERNS,PAT);
}
/* Notify the client */
Addreply (C,shared.mbulkhdr[3]);
Addreply (C,shared.psubscribebulk);
Returns the subscribed mode
Addreplybulk (C,pattern);
Returns the sum of the number of channels and modes currently subscribed by the client
Addreplylonglong (C,dictsize (c->pubsub_channels) +listlength (c->pubsub_patterns));
return retval;
}

Implementation of the PUBLISH command

To use the PUBLISH command to send a message to subscribers, you need to perform the following two steps:

1) using the given channel as the key, find the list of all the clients that subscribed to the channel in the Redisserver.pubsub_channels dictionary, traverse the list, and publish the message to all Subscribers.

2) traverse the redisserver.pubsub_patterns linked list, match the schema in the list with the given channel, and if the match succeeds, publish the message to the client in the appropriate mode.

For example, if you have two clients subscribing to It.news Channel and it.* mode respectively, when executing the command publish it.news "Hello Moto", the subscriber of It.news channel will receive the message at Step 1, and when publish proceed to step 2 , subscribers to the it.* mode will also receive information.

The actual implementation of the PUBLISH command is done by the Pubsubpublishmessage function, which is fully defined as follows:

Send Message
int Pubsubpublishmessage (RobJ *channel, RobJ *message) {
int receivers = 0;
struct Dictentry *de;
ListNode *ln;
Listiter Li;
/* Send to clients listening for that channel */
Send a message to subscribers on all channels
de = Dictfind (Server.pubsub_channels,channel);
if (DE) {
List *list = Dictgetval (DE); Remove all Subscribers
ListNode *ln;
Listiter Li;
Traverse all Subscribers and send messages to them
Listrewind (List,&li);
while ((ln = listnext (&li)) = NULL) {
Redisclient *c = ln->value;
Addreply (C,shared.mbulkhdr[3]);
Addreply (C,shared.messagebulk);
Addreplybulk (C,channel); Print Channel name
Addreplybulk (C,message); Print messages
receivers++; Update the number of recipients
}
}
/* Send to clients listening to matching channels */
Send a message to all subscribers who are matched to the pattern
if (Listlength (server.pubsub_patterns)) {
Listrewind (Server.pubsub_patterns,&li); Remove all modes
Channel = Getdecodedobject (channel);
while ((ln = listnext (&li)) = NULL) {
Pubsubpattern *pat = ln->value; Remove mode
If the pattern matches the channel,
Send a message to subscribers in this mode
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); Print the pattern that is matched
Addreplybulk (Pat->client,channel); Print Channel name
Addreplybulk (Pat->client,message); Print messages
receivers++; Update the number of recipients
}
}
Decrrefcount (channel); Releasing The Used channel
}
return receivers; Returns the number of recipients
}

Implementation of UNSUBSCRIBE and Punsubscribe

Unsubscribe and Punsubscribe respectively are SUBSCRIBE and psubscribe anti-operation, if you understand the SUBSCRIBE and psubscribe working mechanism, it should not be difficult to understand the two anti-operation principle, So here we omit the detailed analysis, interested can directly see the code.

Section

The analysis of the pubsub mechanism of Redis is over, as usual, the full PUBSUB.C file with comments can be found on my GITHUB: Https://github.com/huangz1990/reading_redis_source

The principle analysis of Redis Publish/subscribe mechanism

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.