Objective
Redis linked lists are often used for Message Queuing services to complete message exchange between multiple programs. Personally, the benefit of Redis Message Queuing is that it can be distributed and shared, just like the memcache cache as MySQL and MySQL comes with it.
Link list Implementation Message Queuing
Redis linked list support before and after insertion and before and after removal, so if the end of the insertion element to the head to remove elements, this is a message queue, can be said to be a consumer/producer model. Can be implemented using Lpush and Rpop. But there is a problem, if there is no data in the list, then the consumer will be in the while loop to invoke Rpop, which is a waste of CPU resources, fortunately Redis provides a blocking version of the pop command brpop or blpop, use brpop/blpop list timeout
, when the list is empty, brpop/ The BLPOP will block until the timeout period is set or the list inserts an element.
Usage is as follows:
charles@charles-aspire-4741:~/mydir/mylib/redis$./src/redis-cli
127.0.0.1:6379> lpush list Hello
( Integer) 1
127.0.0.1:6379> brpop list 0
1) "List"
2) "Hello"
127.0.0.1:6379> brpop list 0
Blocking here
/*----------------------------------------------------/
//When I lpush an element on another client, the client outputs as
127.0.0.1:6379> Brpop list 0
1 "list"
2) "World"
(50.60s)/blocked time
When the list is empty, the Brpop is blocked, waits for the timeout time, or another client lpush an element. Next, look at the source code is how to achieve blocking Brpop command. To implement client blocking, the server simply does not send a message to the client, and the client blocks in the read call, waiting for the message to arrive. This is a good implementation, the key is how to determine the client blocked list of data arrival and notify the client to unblock? Redis's approach is to store the blocked key and the client list blocked on this key in a dictionary, then whenever a linked list is inserted into the database, the newly inserted list is judged to be blocked by the client, and if so, the blocked client is removed and the newly inserted list element is sent to the client. The client is thus unblocked.
Look at the data structure and server and client-related properties first
Blocking state typedef struct BLOCKINGSTATE {/* Generic fields. */mstime_t timeout; /* Timeout Time * * * redis_block_list/* dict *keys; /* The keys we are waiting to terminate a blocking * operation such as Blpop. otherwise NULL. * * RobJ *target; /* The key that is should receive the element, * for Brpoplpush. */////* redis_block_wait/int numreplicas; /* Number of replicas we are waiting for ACK. */Long long reploffset; /* Replication offset to reach.
*/} Blockingstate;
Continue list typedef struct READYLIST {REDISDB *db;//ready key database RobJ *key;//ready key} readylist; Client-related properties typedef struct Redisclient {int btype; /* Type of blocking op if redis_blocked. * * Blockingstate Bpop; /* Blocking state////server related properties struct Redisserver {/* Blocked clients/unsigned int bpop_blocked_clients; * Numbe R of clients blocked by lists/list *unblocked_clients; /* List of clients to unblock before next loop */list *ready_keys; /* List of readylist structures for Blpop &amP CO///database related properties typedef struct REDISDB {//keys->redisclient mapping dict *blocking_keys; /* Keys with clients waiting for data (blpop) * * Dict *ready_keys; /* Blocked keys that received a PUSH */}redisdb
The
must have a good understanding of the data structures mentioned above, otherwise it is difficult to understand the following code because the code needs to manipulate the data structure above. Start parsing from the Brpop command execution function, Brpop command execution function is
void Brpopcommand (Redisclient *c) {Blockingpopgenericcommand (c,redis_tail);}//+++++++++++++++++++++++++++++++++++
+++++++++++++++ void Blockingpopgenericcommand (redisclient *c, int where) {robj *o;
mstime_t timeout;
Int J; if (gettimeoutfromobjectorreply (c,c->argv[c->argc-1],&timeout,unit_seconds)!= REDIS_OK) return;// Save timeout in timeout for (j = 1; J < c->argc-1; J + +) {o = Lookupkeywrite (c->db,c->argv[j]);//Find an operation in the database linked list if (o!= NULL)
{//If not NULL if (O->type!= redis_list) {//Not linked list type addreply (C,SHARED.WRONGTYPEERR);//error return; else {if (Listtypelength (o)!= 0) {//list is NOT NULL/* Non empty list, this is like a Non normal [lr]pop./Char *event = (where = = Redis_head)?
"Lpop": "Rpop";
RobJ *value = Listtypepop (o,where);//Pop out an element Redisassert (value!= NULL) from the list;
To send the client the pop out of the element information Addreplymultibulklen (c,2);
Addreplybulk (C,c->argv[j]);
Addreplybulk (C,value);
Decrrefcount (value); NotiFykeyspaceevent (Redis_notify_list,event, c->argv[j],c->db->id);
if (Listtypelength (o) = = 0) {//If the list is empty, delete the list from the database Dbdelete (C->db,c->argv[j]);
Notifykeyspaceevent (Redis_notify_generic, "Del", C->argv[j],c->db->id); }/* Omit part of */}}}/* If the list is empty, block the client * * Blockforkeys (c, c->argv + 1, c->argc-2, timeout, NULL
); }
From the source, you can see that Brpop can manipulate multiple linked list variables, such as Brpop list1 list2 0
, but only the first linked list with elements is exported. If List1 has no elements, and List2 has elements, it outputs list2 elements, and if all two have elements, the List1 element is exported, and if there are no elements, wait for one of the linked lists to insert an element and then return at 2. The last call to the Blockforykeys block
void Blockforkeys (Redisclient *c, robj **keys, int numkeys, mstime_t timeout, robj *target) {dictentry *de;
List *l;
Int J; C->bpop.timeout = timeout;//Timeout assignment to client blockingstate property C->bpop.target = target;//This property applies to the input object of the Brpoplpush command. If it is brpop,//target is an empty if (target!= null) Incrrefcount (target);//is not NULL, increment reference count for (j = 0; J < Numkeys; J +) {/* will block
Key is deposited in C.bpop.keys dictionary/if (Dictadd (c->bpop.keys,keys[j],null)!= DICT_OK) continue;
Incrrefcount (Keys[j]); /* and in the ' side ', to map keys-> clients///will block key and client added to C->db->blocking_keys de = Dictfind (C-&G
T;DB->BLOCKING_KEYS,KEYS[J]);
if (de = = NULL) {int retval;
/* For every key we take a list of clients blocked for it/L = listcreate ();
retval = Dictadd (c->db->blocking_keys,keys[j],l);
Incrrefcount (Keys[j]);
Redisassertwithinfo (C,keys[j],retval = = DICT_OK);
else {L = dictgetval (DE); Listaddnodetail (l,c);//Add to the customer point list in the blocking key} blockclient (c,redis_blocked_list);/Set client blocking flag}
The Blockclient function simply sets the client properties, as follows
void Blockclient (redisclient *c, int btype) {
c->flags |= redis_blocked;//setting flag
C->btype = btype;//blocking operation type c3/>server.bpop_blocked_clients++;
}
Because of this function, the Brpop command execution function ends, and the client blocks in the read call because no messages are sent to the client. So how to unlock the client blocking it?
Insert an element to solve blocking
The executive function of any instruction is to invoke the call function in the ProcessCommand function, and then invoke the command execution function in the call function, as does Lpush. When the Lpush is finished, the list is not empty, back to the ProcessCommand call, execute the following statement
if (Listlength (Server.ready_keys))
handleclientsblockedonlists ();
The two lines of code is to check whether the Server.ready_keys is empty, if not NULL, there are already some ready linked list, at this point can determine whether there is a client blocking the key value, if any, then wake up; now the question is here again, this server.ready_ Where did the keys update the list?
Originally in the Dbadd function, when the value type added to the database is redis-list, the Signallistasready function is called to add the list pointer to the Server.ready_keys:
DB.C void Dbadd (Redisdb *db, RobJ *key, RobJ *val) {SDS copy = Sdsdup (key->ptr);
int retval = Dictadd (db->dict, Copy, Val);//Add Data to Database Redisassertwithinfo (null,key,retval = REDIS_OK);
To determine if it is a linked list type, and if so, call a linked list already ready function if (Val->type = = redis_list) signallistasready (DB, key);
if (server.cluster_enabled) Slottokeyadd (key);
}//t_list.c void Signallistasready (Redisdb *db, RobJ *key) {readylist *rl; /* No client is blocked on this key, then return directly.
*/if (Dictfind (db->blocking_keys,key) = NULL) return;
/* This key has been nearly awakened, so there is no need to re-enter the team */if (Dictfind (db->ready_keys,key)!= NULL) return;
* Ok, in addition to the above two cases, put this key into the server.ready_keys * * RL = Zmalloc (sizeof (*RL));
Rl->key = key;
RL->DB = db;
Incrrefcount (key); Listaddnodetail (SERVER.READY_KEYS,RL)//Add the end of the list/* We also add the key in the Db->ready_keys dictionary into order * Avoid adding it multiple times into a-list with a simple O (1) * check.
* * Incrrefcount (key); At the same time put this blocking key into Db->ready_keys Redisassert (Dictadd (Db->ready_keys,key,null) = = DICT_OK); }
OK, when the ready key is already on the Server.ready_keys, the handleclientsblockedonlists ()
function in the ProcessCommand function is called to handle the blocking client. In this function, the
void handleclientsblockedonlists (void) {while (Listlength (Server.ready_keys)!= 0) {list *l;
/* Will server.ready_keys to a new list, and then Server.ready_keys empty/L = Server.ready_keys;
Server.ready_keys = Listcreate (); /* Iterate every readylist/while (Listlength (L)!= 0) {ListNode *ln = Listfirst (l);//Get First ready readylist readylist *r
L = ln->value;
/* from the database to the RL to delete the RL * * * DICTDELETE (Rl->db->ready_keys,rl->key);
* Query RL belongs to the database to find Rl->key, to block the client reply Rl->key the first element of the list/RobJ *o = Lookupkeywrite (Rl->db,rl->key);
if (o!= NULL && o->type = = redis_list) {dictentry *de;
/* In Rl->db->blocking_keys find blocked client chain in Rl->key/de = Dictfind (Rl->db->blocking_keys,rl->key);
if (DE) {List *clients = Dictgetval (DE);//Convert to client list int numclients = listlength (clients);
while (numclients--) {//sends messages to each client ListNode *clientnode = Listfirst (clients); Redisclient *receiver = clientnode->value;//Blocked client robj *dstkey = receiver->bpop.target;//brpoplpush command intent list int where = (Receiver->lastcmd && rec
Eiver->lastcmd->proc = = Blpopcommand)? redis_head:redis_tail;//gets the direction of the fetch robj *value = Listtypepop (o,where);//Remove the element if (value) {/* Protec of the Ready list T Receiver->bpop.target, that would be * freed by the next unblockclient () * call.
*/if (Dstkey) Incrrefcount (Dstkey); Unblockclient (receiver);/set the client to a non-blocking state if (serveclientblockedonlist receiver, Rl->key,dstkey,rl->db,va Lue, where) = = Redis_err) {/* If we failed serving the client we need * to also undo the POP operation.
* * Listtypepush (O,value,where);
}//the element content in the client reply list if (Dstkey) Decrrefcount (Dstkey);
Decrrefcount (value);
} else {break;
If the list is empty, delete the IF (Listtypelength (o) = = 0) dbdelete (rl->db,rl->key) from the database; /* We don ' t calL Signalmodifiedkey () as it is already called * when a element is pushed on the list.
*/}/* Recycle RL/Decrrefcount (Rl->key);
Zfree (RL);
Listdelnode (L,LN); } listrelease (L); /* We have the new list on */
}
}
From this source can be seen, if there are two clients, while blocking the top of a linked list, then if the list inserted an element, only the first blocked client received the message, the back of the blocked client continues to block, this is the first blocking the first service idea. The Handleclientsblockedonlists function unblockClient(receiver)
is called, which functions as a contact-client blocking flag and finds the client-side list of DB blocking on key and deletes the blocked client from the linked list. Then call Serveclientblockonlist to the client to reply to the element that was just inserted in the list.
int Serveclientblockedonlist (redisclient *receiver, RobJ *key, RobJ *dstkey, Redisdb *db, robj *value, int where)
{
robj *argv[3];
if (Dstkey = = NULL) {/
* Propagate the [lr]pop operation. *
/argv[0] = (where = = Redis_head)? Shared.lpop:
S Hared.rpop;
ARGV[1] = key;
Propagate (where = = Redis_head)?
Server.lpopCommand:server.rpopCommand,
db->id,argv,2,redis_propagate_aof| REDIS_PROPAGATE_REPL);
/* Brpop/blpop * *
Addreplymultibulklen (receiver,2);
Addreplybulk (Receiver,key);
Addreplybulk (Receiver,value);
} else {/
* Brpoplpush */////
}
The propagate function mainly sends command information to AOF and slave. The omitted part of the function is brpoplpush list list1 0
the purpose of the command list List1 Non-empty, the elements that pop out of the list chain are inserted into the List1. When a message is sent to the client, the client returns from the Read function call and becomes unblocked.
Unblocking by timeout time
If the linked list has not been data inserted, then the client will continue to block, this is certainly not the case, so Brpop also supports timeout blocking, that is, after blocking time exceeded a certain value, the server returned a null value, so that the client freed up blocking.
For time timeouts, it is placed in a time event of 100ms execution; Timeout relief the blocking function is also in Servercron;serverCron->clientsCron->clientsCronHandleTimeout
int Clientscronhandletimeout (redisclient *c, mstime_t now_ms) {
time_t now = now_ms/1000;
//..........
else if (C->flags & redis_blocked) {/
* BLOCKED OPS timeout is handled with milliseconds resolution.
* However Note This actual resolution is limited by
* server.hz.
/If c->bpop.timeout!= 0 && c-& Gt;bpop.timeout < Now_ms) {
/* Handle blocking operation specific timeout. * * Replytoblockedclienttimedout
(c );
Unblockclient (c);
}
}
//.............
Delete This function irrelevant code, the main part of the first to determine whether the client is blocked, if it is, the time-out expires, if it is, then call Replytoblockedclienttimedout to the client to reply to an empty reply, and contact client blocking.
Summarize
Link List Message Queue implementation Temporary analysis to this, everyone has learned? I hope this article can bring some help, if you have questions can be exchanged message.