redis--list阻塞详解

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

你可能感兴趣的:(redis-3.2,源码)