Redis源码学习之列表对象

列表对象的底层实现可以是【压缩列表】或者【双端链表】,Redis会通过用户对于压缩列表单个节点值长度(list_max_ziplist_value)和键值对个数(list_max_ziplist_entries)的配置进行选择。

一.压缩列表编码

当Redis创建列表对象时,默认选择的实现方式是压缩列表结构,如push操作的底层实现方法:

可以看到lobj通过createZiplistObject方法创建一个指向空压缩列表的对象:

当我们执行命令:

127.0.0.1:6379> lpush test x

在createZiplistObject方法后打印断点可以观察lobj如下图所示:

可以看到,初始化后的lobj编码为压缩列表(5),此时lobj在内存中如下示意图所示(空压缩列表):

二.双端链表编码

前文中说到,列表对象在初始化时默认使用压缩列表作为底层实现,那么什么时候才会用到双端链表实现呢?这需要下列条件:

这里会有一个疑问,为什么对于INT编码的字符串对象不做长度检查,看了之前文章的同学应该了解,INT编码的字符串对象本身已经保证其长度不会太大,因此也不需要再检验了。

底层的插入操作通过listTypePush方法实现:

当我们实现如下命令时:

lpush test alsjflkasdljf9328904124jljlkajsdfjalskjdflajsf902839084234232234234

我们在listTypush前后打印断点可以看到编码从压缩列表(4)转换为双端链表(5)

具体的转换代码实现如下图所示,底层实现listTypeConvert方法:

这里需要强调一点,列表对象编码的转换是单向的,即只能有压缩列表->双端链表,而不会逆向操作,比如我们将刚才超长的字符串pop出来,再push进去y、z两个字符串,而列表对象依然使用双端链表编码:

三.阻塞操作

列表对象有几个阻塞操作,如blpop\brpop\brpoplpush。以blpop为例,其在官网的描述如下图所示:

这里主要是两点:

  1. 如果队列中有元素,则直接pop返回(同lpop效果)
  2. 如果队列中没有元素,则一直阻塞,直到队列中有元素或者达到超时时间

对于生产者-消费者模式来说,这种方式可以提高消费者获取消息的速度。但是我们都知道Redis是单进程单线程实现的,那么它是如何实现这种阻塞操作的呢?

我们首先来看blockForKeys方法,当客户端使用blpop调用某个空队列(或不存在的队列)时,就会触发该方法:

Redis数据库会记录该链表key作为键,阻塞的客户端链表作为值存到blocking_keys字典中。并且在blockClient方法中设置阻塞状态:

当客户端有Push行为时,会触发signalListAsReady方法:

当有客户端正在因为该队列而阻塞时,就把这个链表key放到server.ready_keys链表中,同时也放到ready_keys字典中防止重复操作。

而在Redis的处理命令的方法processCommand中(这里涉及到Redis的事件处理模型,后面还会细说):

会通过检测sever.ready_keys列表来决定是否需要处理阻塞客户端,而之后的操作就很明了了,从ready_keys中取出就绪列表,从blocking_keys中取出阻塞客户端,以“先阻塞先服务”的顺序依次执行阻塞客户端请求,并释放客户端阻塞状态,没有获得响应的客户端依旧阻塞。Redis将就绪列表分摊到多次EventLoop事件中去,从而实现命令的快速响应。

四.总结(思维导图)

欢迎关注我的公众账号

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java帮帮-微信公众号-技术文章全总结

03.线程安全/同步/线程通讯

03.线程安全/同步/线程通讯 一.一个典型的Java线程安全例子 ? ? 上面例子很容易理解,有一张银行卡,里面有1000的余额,程序模拟你和你老婆同时在取款...

4347
来自专栏大内老A

.NET Core采用的全新配置系统[3]: “Options模式”下的配置是如何绑定为Options对象

配置的原子结构就是单纯的键值对,并且键和值都是字符串,但是在真正的项目开发中我们一般不会单纯地以键值对的形式来使用配置。值得推荐的做法就是采用《.NET Cor...

19110
来自专栏JAVA技术zhai

干货:Java多线程详解(内附源码)

2824
来自专栏H2Cloud

TCPDUMP 抓包

  写了个脚本, 用于调试服务器消息传输, 代码如下: #!/bin/bash if [ $# -eq 0 ] ; then echo "usage local...

2965
来自专栏java 成神之路

JVM 类加载机制深入浅出

26711
来自专栏Java帮帮-微信公众号-技术文章全总结

03.线程安全/同步/线程通讯

03.线程安全/同步/线程通讯 一.一个典型的Java线程安全例子 ? ? 上面例子很容易理解,有一张银行卡,里面有1000的余额,程序模拟你和你老婆同时在取款...

3477
来自专栏好好学java的技术栈

深入线程Thread类的start()方法和run()方法

java的线程是通过java.lang.Thread类来实现的。VM启动时会有一个由主方法所定义的线程。可以通过创建Thread的实例来创建新的线程。每个线程都...

970
来自专栏老马说编程

(65) 线程的基本概念 / 计算机程序的思维逻辑

在之前的章节中,我们都是假设程序中只有一条执行流,程序从main方法的第一条语句逐条执行直到结束。从本节开始,我们讨论并发,在程序中创建线程来启动多条执行流,并...

2217
来自专栏linux运维学习

linux学习第六十六篇:shell中的函数,shell中的数组,告警系统需求分析

shell中的函数 函数就是把一段代码整理到了一个小单元中,并给这个小单元起一个名字,当用到这段代码时直接调用这个小单元的名字即可。 格式: function ...

3058
来自专栏好好学java的技术栈

「附数据结构资源」玩转java并发(六):深入线程Thread类的start()方法和run()方法

java的线程是通过java.lang.Thread类来实现的。VM启动时会有一个由主方法所定义的线程。可以通过创建Thread的实例来创建新的线程。每个线程都...

1062

扫码关注云+社区

领取腾讯云代金券