redis中blpop可以实现链表的阻塞操作,客户端连接在list没有数据的情况下会进行阻塞。这让我产生了一个疑问,redis本身是一个单线程服务,如果阻塞客户端一直保持着跟服务器的链接,会不会阻塞其他命令的执行呢?
答案显然是不会,这就涉及到redis阻塞命令的实现原理。我们知道,在redis server中有两个循环:IO循环和定时事件。在IO循环中,redis完成客户端连接应答、命令请求处理和命令处理结果回复等,在定时循环中,redis完成过期key的检测等。redis一次连接处理的过程包含几个重要的步骤:IO多路复用检测套接字状态,套接字事件分派和请求事件处理。
redis在blpop命令处理过程时,首先会去查找key对应的list,如果存在,则pop出数据响应给客户端。否则将对应的key push到blocking_keys数据结构当中,对应的value是被阻塞的client。当下次push命令发出时,服务器检查blocking_keys当中是否存在对应的key,如果存在,则将key添加到ready_keys链表当中,同时将value插入链表当中并响应客户端。
服务端在每次的事件循环当中处理完客户端请求之后,会遍历ready_keys链表,并从blocking_keys链表当中找到对应的client,进行响应,整个过程并不会阻塞事件循环的执行。所以, 总的来说,redis server是通过ready_keys和blocking_keys两个链表和事件循环来处理阻塞事件的。
第一步: 在处理push命令时将key加入到ready_keys中
如果一个list中的元素为null, 则其key也是不存在的, 所以当向一个key中添加元素时一定是新增一个key, 会调用dbAdd()函数, 所以在该函数中, 如果判断value是一个list类型, 则执行signalListAsReady()函数
void dbAdd(redisDb *db, robj *key, robj *val) {
sds copy = sdsdup(key->ptr); //复制key字符串
int retval = dictAdd(db->dict, copy, val); //将key-val添加到键值对字典
serverAssertWithInfo(NULL,key,retval == DICT_OK);
// 如果值对象是列表类型,有阻塞的命令,因此将key加入ready_keys字典中
if (val->type == OBJ_LIST) signalListAsReady(db, key);
// 如果开启了集群模式,则讲key添加到槽中
if (server.cluster_enabled) slotToKeyAdd(key);
}
该数据将key添加到ready_keys链表当中
//如果有client因为等待一个key被push而被阻塞,那么将这个key放入ready_keys,key哈希表中
void signalListAsReady(redisDb *db, robj *key) {
readyList *rl;
/* No clients blocking for this key? No need to queue it. */
//如果在key不是正处于阻塞状态的键则返回
if (dictFind(db->blocking_keys,key) == NULL) return;
/* Key was already signaled? No need to queue it again. */
//key已经是ready_keys链表里的键,则返回
if (dictFind(db->ready_keys,key) != NULL) return;
/* Ok, we need to queue this key into server.ready_keys. */
//接下来需要将key添加到ready_keys中
//分配一个readyList结构的空间,该结构记录要解除client的阻塞状态的键
rl = zmalloc(sizeof(*rl));
rl->key = key; //设置要解除的键
rl->db = db; //设置所在数据库
incrRefCount(key);
//将rl添加到server.ready_keys的末尾
listAddNodeTail(server.ready_keys,rl);
/* We also add the key in the db->ready_keys dictionary in order
* to avoid adding it multiple times into a list with a simple O(1)
* check. */
//再讲key添加到ready_keys哈希表中,防止重复添加
incrRefCount(key);
serverAssert(dictAdd(db->ready_keys,key,NULL) == DICT_OK);
}
第二部: 在处理任一客户端的命令请求后, 都会依次遍历ready_keys链表, 将其中对应的客户端和对应的key做出响应
readQueryFromClient -> processCommand- > handleClientsBlockedOnLists -> serveClientBlockedOnList
https://www.jianshu.com/p/xsMzfn