You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

14 KiB

开篇词 | 阅读Redis源码能给你带来什么

你好我是蒋德钧目前在中科院计算所任职副研究员。在2015年的时候我和团队开始设计实现一个高性能键值数据库。为了实现这一目标我们调研了业界常用的多种键值数据库并选择Redis作为重点研究对象。在学习Redis的过程中我就通读了Redis的源码尤其是Redis的数据结构、主从复制、RDB/AOF等关键功能。

也正是通过阅读Redis源码我发现自己对Redis的关键设计原理和机制有了更加直接和深刻的理解。更重要的是Redis的代码设计和实现教给了我很多计算机系统的设计思路让我受益匪浅。

2020年我在极客时间上开设了一门《Redis核心技术与实战》课程来帮助同学们掌握Redis的核心原理和实战应用技术。在课程的更新和学习过程中也有不少同学说想要了解和学习Redis源码但是又苦于无从下手。因此时隔一年我又带来了一个源码课程。

这门课程会从Redis源码阅读的角度出发一方面会给你介绍Redis关键技术的代码实现以便你能更加彻底地理解和掌握该项关键技术。另外更重要的一方面就是我希望通过这门课程把我当时在阅读Redis源码时体会和掌握到的计算机单机系统和分布式系统常见的设计思想分享给你让你也可以把这些设计思想应用到自身的项目开发中。

好了那么接下来我就先和你聊聊阅读Redis源码能给我们带来什么也就是为什么要学习Redis源码。

会用Redis不就行了为啥要读源码呢

平常我们在基于Redis做应用开发时可能只是将Redis作为一个缓存系统或是数据库来存取数据并不会接触到源码层面的东西。比如我们在做社交应用开发时会将用户数据、关注信息等缓存在Redis中在开发存储系统软件时也会用Redis保存系统元数据。

不过我遇到过不少做开发或是运维的团队他们在使用或运维Redis时经常会面临Redis性能变差、Redis实例故障等问题而这些问题都会影响到业务应用的运行。再者经历过大厂面试的人也知道很多互联网公司在招聘资深技术岗时都会问一些跟Redis相关的考点问题。

也就是说如果你不了解Redis源码层面的实现原理那不管你是在实际开发中排查问题故障点还是在技术面试中快速拆解问题的套路都可能会受到阻碍。

我就举个简单的例子。Redis在运行过程中随着保存数据的增加会进行rehash操作而rehash操作会对Redis的性能造成一定影响。如果我们想定位当前性能问题是否由rehash引起我们就需要了解rehash的具体触发时机这就包括rehash的触发条件有哪些以及在哪些操作过程中会对这些触发条件进行判断。

可是当我们只是了解rehash的基本原理时我们就只是知道当哈希表的负载因子大于预设阈值后就会开始执行rehash。但是具体到Redis来说我们还需要进一步了解

  • 哈希表的负载因子是怎么算的知道了这一点我们可以推算Redis的负载压力。
  • 除了负载因子这一条件是否还有其他触发条件了解这一点可以帮助我们结合Redis运行情况推断当前是否发生rehash。
  • rehash触发条件的判断会在哪些函数中进行调用了解这一点很有用可以让我们知道在哪些操作执行过程中会判断rehash触发条件进而执行rehash。

你看虽然从原理上说这是一个rehash操作但一旦落到实际的性能问题排查时我们却会面临很多的具体问题。

那么要想解答这些问题最好的办法就是阅读和学习Redis源码。通过学习源码我们能进一步掌握Redis的实现细节这带来的最明显收益就是能了解Redis运行过程中要判断和处理的各种条件。这些细节正对应了我们在排查Redis性能、故障问题时的排查思路可以帮助我们有章法、高效地解决问题。

另外从我的经验来看学习源码除了能帮助我们掌握Redis的设计细节还能带来以下三点收获。

第一,从原理到源码,学习源码阅读方法,培养源码习惯,掌握学习主动权。

阅读源码本身是一个辛苦的过程尤其是面对像Redis这样的系统软件。但是你一旦掌握了阅读方法进而养成了阅读习惯后你就能从源码中掌握Redis的各种实现细节建立对Redis的全面认识。这样一来你就能成为一名Redis专家。

除此之外一旦我们养成阅读源码的习惯再遇到问题时我们就会“条件反射”式地从源码中去寻找答案。而且Redis的代码一直在不断迭代更新因此更新代码所对应的工作原理有时也会发生一些变化但是又没有材料可以及时介绍代码更新带来的变化。此时如果我们已经习惯从代码层去理解Redis的工作机制的话那么我们就能在第一时间掌握Redis的新发展和新变化并可以将其应用到实际工作中。

比如Redis在2020年5月份推出了6.0版本在该版本中Redis实现了多IO线程机制。如果我们养成了阅读Redis源码的习惯就可以尽早地了解Redis 6.0中多IO线程的具体实现并评估其可用性。

第二,学习良好的编程规范和技巧,写出高质量的代码。

学习Redis源码给我们带来的第二个收获是它提供了一个经典的、使用C语言开发的软件系统示例可以让我们学习掌握良好的C语言编码规范和技巧。

Redis的稳定版包括2、3、4、5以及2020年发布的6.0版本这些版本在实际业务中都有部署使用其代码稳定性和健壮性也都经过了考验。因此Redis的源码是一份优秀的C语言编程学习素材。无论你是C语言的初学者还是有经验的C语言开发者通过学习Redis源码都可以帮助你掌握编码规范和技巧。

比如我们可以从Redis源码中学习功能模块单元测试的编程方法下面的代码就显示了Redis SDS数据类型的单元测试通过定义测试函数以及宏定义开关就可以实现针对SDS类型的各种操作测试。

int sdsTest() {
    ...
}

#ifdef SDS_TEST_MAIN
int main(void) {
    return sdsTest();
}
#endif

第三,举一反三,学习计算机系统设计思想,实现职业能力进阶。

最后学习Redis源码还有一个大收获就是跟着Redis学习计算机系统的关键设计思想。Redis是一个非常经典的内存数据库它的设计与实现涉及两类计算机系统的关键技术。

一是单机键值数据库的关键技术,包括支持高性能访问的数据结构、支持高效空间利用率的数据结构、网络服务器高并发通信、高效线程执行模型、内存管理、日志机制等。这些技术是设计和实现一个单机键值数据库时都需要考虑的问题。

二是分布式系统的关键技术,包括分布式系统主从库复制机制、可扩展集群数据切片与放置技术、可扩展集群通信机制等。

Redis在开发时就针对上述问题进行了合理的设计和优化。因此你通过阅读Redis源码就可以充分学习到这些计算机系统的设计思想并把它们应用到自身的项目开发中这样进一步也能提升你的职业竞争力。

我画了下面这张图显示了通过阅读Redis源码可以学习和掌握到的计算机系统设计思想你可以看下。

好了到这里你就可以发现阅读和学习Redis源码无论是对掌握Redis细节成为Redis达人还是养成源码阅读习惯主动跟进Redis最新发展或者是跟着Redis学习编程规范和设计思想都大有裨益。

如何正确学习Redis源码

但是你在尝试阅读Redis源码的时候有没有感到无从下手或是无所适从比如说

  • Redis源码中的功能模块很多不清楚它们之间的逻辑关系或是某个模块中的内容很多很难厘清一条清晰的调用路径
  • 花费了很多时间阅读代码,但总是抓不住重点,或者是在阅读一个函数代码时,很容易陷入细节之中,无法快速抓住代码的关键部分。

其实,你之所以“无从下手”的原因,是缺少了代码结构的全景图,而出现“无所适从”的问题,是缺少阅读目标的牵引和基本原理的支撑。简单来说,就是没有掌握科学、高效的代码阅读方法。

根据我阅读Redis这种大型系统源码的经验下面我就来给你提供三个锦囊妙计。

高效阅读代码的第一个要点,是要先从整体上掌握源码的结构。

这是因为如果一开始就盯着一个代码文件看这样就很容易陷入到细节中无法从全局上了解到Redis源码的组成也不容易分清主次。

所以对于阅读Redis源码来说我们就需要先形成一幅Redis源码的全景图如下所示。

有了这张图以后我们就可以根据自己的学习需求查找到所要学习的代码文件。然后我们再根据Redis不同的功能特性分线条学习每个功能特性上涉及的关键技术和设计思想。

高效阅读代码的第二个要点,是一定要有目标牵引和原理支撑。

Redis的功能模块很多每个功能模块的实现也比较复杂我们在阅读代码前一定要明确想要了解的目标比如是想了解某个数据结构还是想要了解主从复制的流程。

在确定目标后我们还需要对相应的原理有所了解然后再开始阅读源码。这是因为源码是原理的体现如果对Redis功能的基本原理不了解直接阅读源码就难于理解代码逻辑增加了代码阅读的难度。

高效阅读代码的第三个要点,是要做到先主线逻辑再分支细节。

虽然说源码是原理的体现,但是和原理相比,源码通常会考虑系统运行时的各种情况和细节。我看到有些开发人员在阅读源码时,一上来就阅读代码中的每个分支,然后在每个分支上又追到每个函数中细看。而不同分支上的函数往往又涉及其他处理细节,这样一来,就会导致自己既不容易理解代码的主要逻辑,又会感到代码不好读,容易气馁。

其实,我们在阅读代码时一定要先把功能模块的主线逻辑梳理出来,具体来说,就是先把代码执行路径了解清楚,其中的分支做好标记,不用一开始就逐行阅读。等主线逻辑清楚后,我们再学习不同分支的处理。

比如我们在阅读Redis事件驱动处理框架代码时就需要在代码中先把事件处理流程的主要步骤梳理出来包括创建事件、监听事件、启动事件处理循环。然后我们再去了解事件创建、监听和处理的各种细节。这样一来代码阅读就能更加高效了。

好了在了解了代码学习方法之后我们可以开始深入Redis具体的源码模块当中去学习不同功能特性的设计与实现了。

这门课程是怎样设计的?

说到Redis的功能特性Redis提供了String、List、Hash、Set、Sorted Set等丰富的数据类型同时Redis的访问性能高还能构建成主从集群、切片集群来分别提升Redis使用的可靠性和可扩展性。

因此针对Redis的上述功能特性我把这门课程分成五大模块具体如下。

  • 数据结构你将学习到Redis主要数据结构的设计思想和实现包括字符串的实现方法、内存紧凑型结构的设计、哈希表性能优化设计以及ziplist、quicklist、listpack、跳表的设计与实现等。
  • 网络通信与执行模型你将掌握Redis server的启动流程、高性能网络通信设计与实现、事件驱动框架的设计与实现、Redis线程类型的设计和优化等。
  • 缓存:你将了解常见缓存替换算法如何从原理转变为代码。
  • 可靠性保证你将掌握RDB、AOF的具体实现分布式系统中Raft一致性协议的设计实现故障切换的关键代码实现等。
  • 切片集群你将学习到Redis切片集群中关键机制的设计与实现包括Gossip通信协议、请求重定向、数据迁移等。

并且在学习这五类模块中的关键源码的同时我还会给你介绍对应的计算机系统设计思想以便你把这些设计思想应用到自己的系统开发中。最后我还会向你介绍Redis源码中使用的一些编程技巧,以便你学习掌握后,应用到自己的程序开发中。

写在最后

万事开头难对于阅读源码来说尤其是这样。Redis有上百个源码文件源码文件中的代码动辄上千行。如果想彻底掌握Redis源码的确需要花大量的精力和时间。

但是掌握一个好方法是成功做好一件事的关键。所以在跟随学习Redis源码的过程中希望你能掌握好我给出的三个学习要点

  1. 获得代码全景图;
  2. 在阅读代码前确定具体学习目标,并做好原理准备;
  3. 在阅读代码时,先梳理出代码的主线逻辑,再详细学习分支细节。

最后我还想正式认识一下你。你可以在留言区做个自我介绍和我聊聊你目前使用Redis或阅读Redis的源码时都存在哪些困难或是都有哪些独特的思考和体验我们一起交流讨论。

好了让我们一起努力开始Redis代码之旅吧。