`

redis之列表命令源码解析

阅读更多

形象化设计模式实战             HELLO!架构                     redis命令源码解析

 

一、Lpush,Rpush

t_list.c

 

void lpushCommand(redisClient *c) {
    pushGenericCommand(c,REDIS_HEAD);
}

void rpushCommand(redisClient *c) {
    pushGenericCommand(c,REDIS_TAIL);
}

lpush插入列表头部,rpush插入列表尾部

void pushGenericCommand(redisClient *c, int where) {

    int j, waiting = 0, pushed = 0;

    // 取出列表对象
    robj *lobj = lookupKeyWrite(c->db,c->argv[1]);

    // 如果列表对象不存在,那么可能有客户端在等待这个键的出现
    int may_have_waiting_clients = (lobj == NULL);

    if (lobj && lobj->type != REDIS_LIST) {
        addReply(c,shared.wrongtypeerr);
        return;
    }

    // 将列表状态设置为就绪
    if (may_have_waiting_clients) signalListAsReady(c,c->argv[1]);

    // 遍历所有输入值,并将它们添加到列表中
    for (j = 2; j < c->argc; j++) {

        // 编码值
        c->argv[j] = tryObjectEncoding(c->argv[j]);

        // 如果列表对象不存在,那么创建一个,并关联到数据库
        if (!lobj) {
            lobj = createZiplistObject();
            dbAdd(c->db,c->argv[1],lobj);
        }

        // 将值推入到列表
        listTypePush(lobj,c->argv[j],where);

        pushed++;
    }

    // 返回添加的节点数量
    addReplyLongLong(c, waiting + (lobj ? listTypeLength(lobj) : 0));

    // 如果至少有一个元素被成功推入,那么执行以下代码
    if (pushed) {
        char *event = (where == REDIS_HEAD) ? "lpush" : "rpush";

        // 发送键修改信号
        signalModifiedKey(c->db,c->argv[1]);

        // 发送事件通知
        notifyKeyspaceEvent(REDIS_NOTIFY_LIST,event,c->argv[1],c->db->id);
    }

    server.dirty += pushed;
}

1、从这代码可以看出,可以一次插入多个数据,lpush test 1 2 3 4....。

 

官网解释:

It is possible to push multiple elements using a single command call just specifying multiple arguments at the end of the command. Elements are inserted one after the other to the head of the list, from the leftmost element to the rightmost element. So for instance the command LPUSH mylist a b c will result into a list containing c as first element, b as second element and a as third element.

 

 

2、最终实现插入列表的函数是listTypePush

 

二、listTypePush

 

void listTypePush(robj *subject, robj *value, int where) {

    // 是否需要转换编码?
    listTypeTryConversion(subject,value);

    if (subject->encoding == REDIS_ENCODING_ZIPLIST &&
        ziplistLen(subject->ptr) >= server.list_max_ziplist_entries)//注意这里,类似哈希表的类型转换
            listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);

    // ZIPLIST
    if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
        int pos = (where == REDIS_HEAD) ? ZIPLIST_HEAD : ZIPLIST_TAIL;
        // 取出对象的值,因为 ZIPLIST 只能保存字符串或整数
        value = getDecodedObject(value);
        subject->ptr = ziplistPush(subject->ptr,value->ptr,sdslen(value->ptr),pos);
        decrRefCount(value);

    // 双端链表
    } else if (subject->encoding == REDIS_ENCODING_LINKEDLIST) {
        if (where == REDIS_HEAD) {
            listAddNodeHead(subject->ptr,value);
        } else {
            listAddNodeTail(subject->ptr,value);
        }
        incrRefCount(value);

    // 未知编码
    } else {
        redisPanic("Unknown list encoding");
    }
}

 

 

 

void listTypeTryConversion(robj *subject, robj *value) {

    // 确保 subject 为 ZIPLIST 编码
    if (subject->encoding != REDIS_ENCODING_ZIPLIST) return;

    if (sdsEncodedObject(value) &&
        // 看字符串是否过长
        sdslen(value->ptr) > server.list_max_ziplist_value)
            // 将编码转换为双端链表
            listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
}

1、当插入的数据的长度大于list_max_ziplist_value时,转换类型为REDIS_ENCODING_LINKEDLIST(双端列表)。

 

2、当压缩列表的节点数大于list_max_ziplist_entries时,转换类型为REDIS_ENCODING_LINKEDLIST(双端列表)。

双端列表是很常见的数据结构,具体实现请点击

 

三、list_max_ziplist_value与list_max_ziplist_entries

在redis.conf中找到相关设置

 


如果你还记得哈希表的话,会发现这两值与哈希表从压缩列表转换为字典的限制值是相等的,这也不足为奇,因为它们都是由压缩列表转换。

 

 

四、列表阻塞命令:blpop、brpop、brpoplpush

这命令可能用的比较少,就是当列表有元素时就pop,没有就阻塞客户端,直到timeout。下面来试试看:

停在这里不动了,阻塞了!

(0表示无限时阻塞)

不管你再输入什么命令,客户端都没有反应,哪怕你直接ctrl+c。

那我再开一个客户端,往test里lpush一个数据,lpush test 1,再回看阻塞的客户端

返回了值,并且阻塞解除了。

这里注意的是,

1、如里在阻塞的客户端运行lpush会解除阻塞吗?答案是否,就是自己不能解除自己的阻塞。

2、blpop可以同时对多个列表进行阻塞,只要有一个列表有返回值,阻塞即解除。

3、如果有其他客户端访问阻塞的列表,只要不是阻塞命令都不会被阻塞。

 

在这里需要介绍一下客户端的几个参数:(因为参数数量很多,此处不全列出,具体可参看源码)

1、redisDb *db;当前正在使用的数据库

 

typedef struct redisDb {

    // 数据库键空间,保存着数据库中的所有键值对
    dict *dict;                 /* The keyspace for this DB */

    // 键的过期时间,字典的键为键,字典的值为过期事件 UNIX 时间戳。之前在讲字典结构时说道过期时间结构也是一个字典。
    dict *expires;              /* Timeout of keys with a timeout set */

    // 正处于阻塞状态的键(注意这个参数)
    dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP) */

    // 可以解除阻塞的键<span style="font-family: Arial, Helvetica, sans-serif;">(注意这个参数)</span>
    dict *ready_keys;           /* Blocked keys that received a PUSH */

    // 正在被 WATCH 命令监视的键
    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */

    struct evictionPoolEntry *eviction_pool;    /* Eviction pool of keys */

    // 数据库号码
    int id;                     /* Database ID */

    // 数据库的键的平均 TTL ,统计信息
    long long avg_ttl;          /* Average TTL, just for stats */

} redisDb;

标红处是本文所要关注的

 

2、blockingState bpop;     /* blocking state */

 

typedef struct blockingState {

    /* Generic fields. */
    // 阻塞时限
    mstime_t timeout;       /* Blocking operation timeout. If UNIX current time
                             * is > timeout then the operation timed out. */

    /* REDIS_BLOCK_LIST */
    // 造成阻塞的键
    dict *keys;             /* The keys we are waiting to terminate a blocking
                             * operation such as BLPOP. Otherwise NULL. *
    // 在被阻塞的键有新元素进入时,需要将这些新元素添加到哪里的目标键
    // 用于 BRPOPLPUSH 命令
    robj *target;           /* The key that should receive the element,
                             * for BRPOPLPUSH. */

    /* REDIS_BLOCK_WAIT */
    // 等待 ACK 的复制节点数量
    int numreplicas;        /* Number of replicas we are waiting for ACK. */
    // 复制偏移量
    long long reploffset;   /* Replication offset to reach. */

} blockingState;



 

五、阻塞的内部实现

 

1. 将客户端的状态设为“正在阻塞” ,并记录阻塞这个客户端的各个键(记录到bpop.keys中),以及阻塞的最长时限(bpop.timeout)等数据。
2. 将客户端的信息记录到server.db[i]->blocking_keys 中(其中i 为客户端所使用的数据库号码)。
3. 继续维持客户端和服务器之间的网络连接,但不再向客户端传送任何信息,造成客户端阻塞。

 

步骤2 是将来解除阻塞的关键,server.db[i]->blocking_keys 是一个字典,字典的键是那些造成客户端阻塞的键,而字典的值是一个链表,链表里保存了所有因为这个键而被阻塞的客户端(被同一个键所阻塞的客户端可能不止一个)。

 

六、解除阻塞的内部实现

脱离阻塞状态有以下三种方法:
1. 被动脱离:有其他客户端为造成阻塞的键推入了新元素。
2. 主动脱离:到达执行阻塞原语时设定的最大阻塞时间。
3. 强制脱离:客户端强制终止和服务器的连接,或者服务器停机。
 
这里看下lpush是如何解除阻塞的。
1. 检查这个键是否存在于前面提到的server.db[i]->blocking_keys 字典里,如果是的话,那么说明有至少一个客户端因为这个key 而被阻塞,程序会为这个键创建一个
redis.h/readyList 结构,并将它添加到server.ready_keys 链表中。(在signalListAsReady函数中实现)
2. 将给定的值添加到列表键中。
好像并没有解除阻塞啊,只是添加到了server.ready_keys 链表中。Redis 的主进程在执行完pushGenericCommand 函数之后,会继续调用handleClientsBlockedOnLists 函数来完成解除阻塞,大致图解如下:
 
解除阻塞依据FBFS策略(先阻塞先服务)
 
最大阻塞时间解除在redis.c中的clientsCronHandleTimeout中有:
        // 检查被 BLPOP 等命令阻塞的客户端的阻塞时间是否已经到达
        // 如果是的话,取消客户端的阻塞
        if (c->bpop.timeout != 0 && c->bpop.timeout < now_ms) {
            // 向客户端返回空回复
            replyToBlockedClientTimedOut(c);
            // 取消客户端的阻塞状态
            unblockClient(c);
        }
有兴趣可以深入看下源码,这里不作分析。

4
3
分享到:
评论

相关推荐

    Redis源码解析

    源码解析有助于深入理解其工作原理,提高在实际应用中的优化能力。本篇文章将聚焦Redis的源码,探讨其核心组件、数据结构以及内部运行机制。 1. **Redis的数据结构** Redis的核心数据结构包括字符串(String)、...

    redis3源码及解析

    Redis源码解析是一项深入学习数据库系统、内存管理以及并发控制等核心计算机科学概念的宝贵资源。 首先,我们来看Redis中的核心数据结构。Redis支持多种数据类型,包括字符串(String)、哈希表(Hash)、列表...

    Redis实战中文版及源码下载

    同时,源码分析部分将帮助你理解Redis内部工作机制,比如命令处理流程、内存管理策略等,这对于开发自定义模块或者调试Redis问题极其有用。 源码阅读的过程中,你可能会接触到以下概念: - **Redis服务器启动流程**...

    redis源码日志(源码分析)

    源码日志中可能会详细探讨这些机制的实现细节,包括但不限于事件循环的处理、数据结构的定义、命令解析与执行流程、持久化过程、网络通信模块等。通过对源码的阅读和理解,开发者能够学习到如何优化内存管理、错误...

    redis-5.0.14.源码

    10. **性能监控**:Redis 提供了各种性能指标,如 `INFO` 命令输出的信息,源码中 `info.c` 文件定义了相关信息的收集和输出。 通过对 Redis 5.0.14 的源码阅读和分析,我们可以深入了解 Redis 的设计思想、内部...

    Redis Windows源码

    以下是对"Redis Windows源码"的详细解析: 1. Redis核心架构: Redis基于单线程模型,通过事件驱动机制处理客户端请求。它使用I/O多路复用(例如epoll或kqueue)来高效地处理大量并发连接,确保系统性能不受连接数...

    Redis_redis_源码

    1. **Redis的数据结构**:Redis支持多种数据结构,如字符串(Strings)、哈希表(Hashes)、列表(Lists)、集合(Sets)和有序集合(Sorted Sets)。这些数据结构的设计使得Redis在处理复杂数据操作时非常高效。 2...

    redis 学习 基础知识 源码

    本文将深入探讨 Redis 的基础知识,包括其核心概念、数据类型、命令操作以及源码解析。 ### 1. Redis 核心概念 Redis 作为内存数据库,所有数据存储在内存中,通过持久化策略将数据保存到磁盘以防止数据丢失。其...

    C# Redis秒杀活动系统源码

    本文将深入解析基于C#开发、利用Redis作为缓存数据库的秒杀活动系统源码。Redis因其高效、轻量级以及丰富的数据结构特性,成为构建秒杀系统理想的中间件。 首先,我们来理解Redis的基本概念。Redis是一个开源的内存...

    redis-unstable_Redis数据库源码_

    以上只是Redis源码中的一部分关键知识点,深入研究源码还能了解到更多关于错误处理、内存管理、命令执行流程等方面的内容。对于想要深入了解Redis或者进行定制化开发的开发者来说,阅读和理解源码是非常有价值的。

    redis in action 源码

    源码的阅读可以帮助我们深入理解Redis如何处理数据结构(如字符串、哈希表、列表、集合、有序集合等)、命令执行、持久化机制(RDB和AOF)、网络I/O模型(基于epoll的事件驱动)以及复制和集群功能。 首先,Redis的...

    phpredis linux源码

    这些函数实现了客户端与 Redis 服务器的连接、命令发送和响应解析。 3. **PHP Wrappers** - `phpredis.cpp` 文件是 PHP 函数的包装器,将 C++ 实现的 Redis 操作转换为 PHP 可以调用的函数。 4. **Resource ...

    Java项目源码 - 仿Redis服务端的命令解析处理.zip

    使用redis构建简单的社交网站

    labview redis通讯源码及实例

    2. **Redis命令**:Redis支持多种命令,如SET(设置键值)、GET(获取键值)、PUSH(在列表中添加元素)、SUBSCRIBE(订阅主题)等。LabVIEW程序需要正确构建这些命令的字符串格式并发送到Redis服务器。 3. **数据...

    redis 分布式锁实现案例和源码解析备注

    redis 分布式锁实现案例和源码解析备注 * 多线程 * 使用redis事务的方法 * 加事务 乐观锁 * watch命令监控key有没有更改 * multi命令开启事务

    RedisDesktopManager0.9源码

    - Command模块:实现Redis命令的封装和执行,如GET、SET、DEL等。 源码阅读和理解可以帮助我们了解RDM如何与Redis服务器交互,以及它是如何将复杂的Redis操作转化为直观的图形化界面的。对于开发者来说,这有助于...

    go语言学习 - 封装redis常用基本命令.zip

    在封装Redis命令时,一般会创建一个客户端库,它包含了一系列方法,如Set、Get、Del等,对应Redis的基本命令。例如,`Set`方法用于设置键值对,`Get`用于获取键对应的值,而`Del`用于删除指定键的数据。这些方法内部...

    redis delphi实施数据库操作源码

    2. **执行Redis命令**:连接建立后,你可以通过客户端对象发送各种Redis命令。Redis命令通常以大写字母表示,如`SET`、`GET`、`DEL`等。例如,设置一个键值对: ```pascal RedisClient.SendCommand('SET', ['mykey'...

    redis-4.0.14源码压缩包

    Redis通过命令处理器接收客户端发送的命令,解析并执行。每个命令都有一个对应的处理函数,如`lpush`、`get`等。 3. **网络事件处理**: Redis采用单线程模型,通过I/O多路复用(通常使用epoll)处理客户端连接。...

Global site tag (gtag.js) - Google Analytics