无题
在前面的文章中,我们已经介绍完了 Redis 中的 String、List、Hash、Set 这四种核心数据结构的命令以及应用场景。本讲作为本模块的最后一节,我们将介绍 Redis 中独有的一种数据结构 —— Sorted Set(也被称为 ZSet) 的核心命令以及实战应用。
下图对 Sorted Set 相关的命令进行了一个简单的分类,本节也将按照这个分类进行介绍。
Sorted Set 命令详解如上图所示,按照命令执行效果,我们可以将 Sorted Set 的相关命令划分为基础命令、弹出命令、范围查询、集合操作四大类。
下面我们先来介绍基础命令这一个分类。
1. 基础命令Sorted Set 中最基础的命令就是 ZADD 命令,它会向 Sorted Set 集合里面添加新元素,ZADD 命令后面可以跟多个元素。
在下面的示例中,就是用 ZADD 命令往 myzset 这个集合里面添加 zhangsan、lisi 两个字符串,同时 zhangsan 这个元素对应的 score 值为 100, lisi 对应的 score 值为 90 ,score 值也被称为“评分”,Sorted ...
无题
在上一个模块中,我和小伙伴们一起学习了 Redis 最核心的命令,主要涉及 String、List、Hash、Set、Sorted Set 五种数据结构的命令,同时,我们还介绍了每种数据结构的实战场景,并带领小伙伴们使用 Java 语言中的 Lettuce 客户端,实现了每种实践场景的核心代码。
经过上一模块的学习之后,相信小伙伴们已经知道如何结合实际需求使用 Redis 了。如果只是达到 **应用的层次**,在进行面试或者做架构设计的时候,是远远不够的,我们需要更进一步,了解 Redis 中这五种数据结构的底层实现原理,才能达到 **用好的层次**,从而让我们在面试和工作中脱颖而出。
因此,这一模块我们将从源码级别抽丝剥茧,介绍 Redis 五大数据结构的底层原理,读完本模块之后,小伙伴们就会对 Redis 五大数据结构的原理有透彻清晰的理解。
下面我们就开始看 Redis 的字符串实现了,Redis 并没有直接用 C 语言的字符串,而是自己搞了一个 sds 的结构体来表示字符串,这个 sds 的全称是 Simple Dynamic String,翻译过来就是“简单的动态字符串”。
...
无题
在上一节课程中,我们详细介绍了 Redis sds 的设计和优化,我们可以将这些 sds 结构体理解为存储的字符串数据的静态结构,Redis 如何使用这些 sds 结构体,完成字符串的基本功能,是我们接下来要关注的重点,也是本节课重点要介绍的内容。
本节我们将 Redis 字符串相关的函数分成了四大类,分别是:初始化函数、扩容函数、缩容函数以及其他字符串相关的函数。
在 sds.h 文件里面,可以看到非常多的函数定义:
具体的实现在 sds.c 这个文件里面,我们点这个红绿箭头就可以跳转过去。
初始化字符串首先,我们来看创建 sds 实例的方法实现,这几个(sdstrynewlen()、sdsnew()、sdsempty()、sdsnewlen() )方法都是常用的创建 sds 的方法,它们底层都是依赖 _sdsnewlen() 方法实现的。
1. 分配内存空间的位置前面在第 3 讲《先导基础篇:10 分钟 C 语言入门》中我们提到, C 语言里面,创建一个结构体实例的时候,是这么写的:
12345678struct student { char name[13]; ...
无题
我们前面第 6 讲《实战应用篇:List 命令详解与实战(上)》中说过,Redis List 的底层逻辑类似于 Java 里面的 LinkedList。C 语言里面呢,并没有现成的链表结构, 所以 Redis 自己做了一个 quicklist,用来表示双端链表。
quicklist 的结构大概就是下图这样:
可以看到,quicklist 有头指针和尾指针,分别指向了链表的首节点和尾节点;在每个节点里面,都有 next、prev 两个指针,next 指针指向下一个节点,prev 指针指向前一个节点。除此之外,每个 Node 里面还有一个 Value 指针,指向这个节点存储的具体数据。
如果单纯使用双端链表的话,会出现一些问题。
如果每个 Node 节点里面 Value 指针指向的数据很小,比如只存储了一个 int 值,那 next、prev 指针占了 Node 节点的绝大部分空间,真正存储数据的有效负载就比较低。
链表节点分配很多的话,就会出现很多不连续的内存碎片。
还有就是,链表查询数据的时候,需要顺着 next 或者 prev 指针链表的一端开始查找,不像数组那样 ...
无题
在上一节中,我们详细分析了 Redis 引入 ziplist 结构的目的以及 ziplist 的核心结构,但是 ziplist 本身是个静态结构,Redis 需要通过增删改查的相关逻辑,才能真正发挥 ziplist 这个结构的作用。
所以,从本节开始,我们就来深入分析 ziplist 的相关函数,其中最复杂、也是最重要的就是 ziplist 的写入逻辑,这里会用一节的篇幅来展开分析。
打开 ziplist.h 这个头文件,这里可以看到操作 ziplist 的全部函数:
要用 ziplist,第一步肯定是要创建一个 ziplist 实例,对应的就是这里的 ziplistNew() 函数,它里面会创建一个空 ziplist 实例,返回的这个 char* 指针呢,指向的就是这个空 ziplist 实例的首地址。
新建的 ziplist 不包含任何 entry,只有队首和队尾这两个部分,就是下图的状态:
创建好 ziplist 之后,我们就要开始往里添加元素了。下面这两个函数都涉及到了往 ziplist 里添加元素的功能,我们一个个来说。
ziplistPush() 函数,其完整的函数签 ...
无题
在前面两节中,我们详细介绍了 ziplist 结构以及 ziplist 写入新元素的核心逻辑。除了插入新元素的操作之外,我们还需要学习一下 ziplist 查询、删除以及更新元素的操作,这样整个 ziplist 结构才算掌握完整。
查询操作首先来看 ziplist 提供的查询函数,如下图所示,我们可以在 ziplist.h 文件中看到红色方框圈出来的这些查询函数:
这里先简单列一下这些函数的功能,其实通过函数的名字,也猜个差不多。
ziplistIndex() 函数的功能就是查找 ziplist 中指定 index 索引值上的 entry。要是传入的 index 值是个负数,会从 ziplist 的尾部向前查找。相信你也能猜到,《实战应用篇:List 命令详解与实战(上)》一文中介绍的 LINDEX 命令,就是依赖 ziplistIndex() 函数实现的。
ziplistNext() 和 ziplistPrev() 函数,就是返回 ziplist 中指定 entry 节点的前一个和后一个 entry。
其他的这些方法就不再一个个说了,你可以直接参考下面这个表:
函 ...
无题
在前面三讲中,我们已经详细介绍了 ziplist 的核心结构以及核心操作,你会发现 ziplist 是一种比较复杂的结构,所以在 Redis 5.0 就引入了 listpack 这个更简单的结构来对标 ziplist。在前面介绍 Redis 7.0 Release Note 说过,从 Redis 7.0 开始,完全使用 listpack 替换 ziplist 了。
这一节我们就来说说 listpack。
listpack 核心结构listpack 说白了,跟 ziplist 差不多,也是一块连续的内存空间,但是呢,listpack 没有 ziplist 那么复杂的内部结构。
listpack 之所以会出现,是因为用户上报的一个 Redis 崩溃问题,但是 Redis 的代码维护者并没有找到崩溃的明确原因,猜测可能是 ziplist 复杂的连锁更新操作导致的,希望设计一种简单点的、能够替换 ziplist 的紧凑型数据结构。
下面这张图是 listpack 结构,它和 ziplist 的结构很类似,也是分为头、中、尾三部分。先来看头、尾两部分:头部里面的 tot-bytes 占了 4 个 ...
无题
常用的 Docker Compose 配置项与 Dockerfile 一样,编写 Docker Compose 的配置文件是掌握和使用好 Docker Compose 的前提。编写 Docker Compose 配置文件,其本质就是根据我们所设计的应用架构,对不同应用容器进行配置并加以组合。在这一节中,我们就来谈谈如何编写 Docker Compose 的配置文件,了解其中常见配置项的使用方法。
定义服务为了理解在开发中常用的 Docker Compose 配置,我们通过一个在开发中使用的 Docker Compose 文件来进行下面的讲解。
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758version: "3"services: redis: image: redis:3.2 networks: - backend volumes: - ./redis/red ...
无题
应用于服务化开发上一节里我们谈到了小型的独立项目如何使用 Docker Compose 来搭建程序的运行环境,对于由多人或多部门参与的中大型服务化架构的项目,仅由一个 Docker Compose 项目来管理它们的运行环境显然是不切实际的。在这一小节里,我们就谈谈如何在服务化开发中合理利用 Docker 来搭建环境。
服务开发环境在开始之前,我们依然来设定一个场景。在这里,假定我们处于一个 Dubbo 治下的微服务系统,而工作是开发系统中某一项微服务。
微服务开发与上一节里我们提到的小型项目开发在环境搭建上有一定的区别,我们要合理地调整 Docker 的使用方法和策略,就必须先了解这些区别。
在微服务开发中,我们所开发的功能都不是完整的系统,很多功能需要与其他服务之间配合才能正常运转,而我们开发所使用的机器时常无法满足我们在一台机器上将这些相关服务同时运行起来。
我们仅仅是开发某一部分服务的内容,既对其他服务的运转机制不太了解,又完全没有必要在自己的机器上运行其他的服务。所以我们最佳的实践自然就是让参与系统中服务开发的同事,各自维护自己开发服务的环境,而直接提供给我们对应的连接地址使用 ...
无题
编写 Docker Compose 项目通过阅读之前的小节,相信大家对 Docker 在开发中的应用已经有了一定的了解。作为一款实用的软件,我们必须回归到实践中来,这样才能更好地理解 Docker 的实用逻辑和背后的原理。在这一小节里,我们就举一个完整的例子,让大家跟随这个项目的脉络,熟悉如何通过 Docker 和 Docker Compose 来搭建应用开发环境。
设计项目的目录结构在这一小节里,我们以一个由 MySQL、Redis、PHP-FPM 和 Nginx 组成的小型 PHP 网站为例,介绍通过 Docker 搭建运行这套程序运行环境的方法。
既然我们说到这个小型网站是由 MySQL、Redis、PHP-FPM 和 Nginx 四款软件所组成的,那么自然在 Docker 里,我们要准备四个容器分别来运行它们。而为了更好地管理这四个容器所组成的环境,我们这里还会使用到 Docker Compose。
与搭建一个软件开发项目类似,我们提倡将 Docker Compose 项目的组成内容聚集到一个文件目录中,这样更利于我们进行管理和迁移。
这里我已经建立好了一个目录结构,虽然我们在 ...
