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.

114 lines
12 KiB
Markdown

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 用户故事 | 曾轼麟:世上无难事,只怕有心人
你好,我叫曾轼麟,目前在广州一家互联网公司,从事互动社交业务领域中的服务端开发工作。
我从《Redis核心技术与实战》的时候就开始跟随蒋老师的脚步一直在学习Redis相关的知识。在这个过程中我收获了很多新的认知也对Redis的使用及其底层实现有了不一样的理解所以很高兴能在这里跟你分享我学习课程的心得与体会、学习思路与方法。
## 为什么学习Redis源码
其实早在老师开设这门课程之前我就已经坚持阅读Redis源码一段时间了。
一开始我有想去阅读Redis源码的念头时应该是在2020年初那时候Redis 6.0版本新增了IO多线程的这个特性。而一直以单线程设计为主推的Redis突然高调引入了多线程的设计就让我对其新的设计思路感到非常好奇。
在随后的一段时间里Redis社区也发生了重大的变化安迪斯AntirezRedis之父不再维护Redis而是全权交给了社区进行维护。由此Redis开源的方式就由专制模式转变为了社区自治模式。老实说我其实挺期待这种变化会给Redis的未来带来什么样的影响。也是因为这种种原因就促使我开展了源码阅读之旅。
到了2021年的1月份公司上市后为了进一步抢占市场领导和我们提出市场今年需要发力本年度的OKR可能会是5倍、10倍乃至30倍的DAU增长而面临这样的增速我认为对系统性能的优化要求就迫在眉睫。并且年初的时候由于DAU的增长几乎每隔一天晚上我都会被告警叫起来处理线上问题我们做的是海外业务在经历了半年的重构和优化后目前已经趋于稳定。而在这半年的时间里**无论是研制新的缓存组件、改造现有缓存方案还是新业务的发展几乎都离不开Redis**甚至连运营和产品同学都会问我们这次设计方案咱用Redis吗
所以除了我个人对于Redis的新特性和底层实现原理的关注之外在工作上的各种变化和挑战也驱动我想要快速提升阅读Redis源码的能力并能通过读源码的设计思路帮助指导和解决我在优化高性能接口上遇到的各种难题。
## 我是怎么学习Redis源码的
那么,我是怎么学习源码的呢?
其实在一开始阅读Redis源码的时候我也走了一些弯路无论是从IDE的选择还是确定代码阅读的切入点我都花费了大量的时间但最后我发现花费了时间却没达到理想的效果。所以这里我先来分享一些我踩坑后总结的经验/建议,希望能通过这些经验/建议,帮助你更好地阅读源码。
* **Linux**
首先就是一定要有一套Linux的环境它能自行编译打包源码从而可以方便我们做调试Windows的同学可以使用虚拟机这里可以参考Redis在GitHub上面的[文档](https://github.com/redis/redis)。我曾经尝试在Windows去编译Redis但是最后发现成本比想象中的要高而且Windows对编译环境不太友好导致编译出来验证后的效果不一定是和实际服务器的效果一致的。
* **Clion**
为了方便阅读源码我曾经选择过一些IDE工具来作为辅助比如VS Code、Visual Studio甚至是文本工具但是最后为了保留Java开发者的快捷键习惯我最后是选择使用了CLion这款软件。所以如果你也是Java开发者这里我建议你可以考虑把源码阅读环境和调试环境分离开这样就可以避免很多不必要的环境冲突。比如在Windows下阅读代码然后提交到虚拟机中的Linux进行断点调试。
* **GDB**
关于Redis的调试我非常推荐这款调试工具它本身在Linux上就已经是兼容的了每次我需要分析某个指针对象或者缓冲区空间内容的时候都可以使用它它甚至还能伪造一些阻塞的场景比如下图是我在getGenericCommand方法入口进行断点并输出Client结构体里面querybuf的内容图中是RESP协议的字符串
![图片](https://static001.geekbang.org/resource/image/31/16/31fcb1e8dc693f11bb2c54ff29622d16.jpg?wh=1920x399)
* **不要忽略注释**
在Redis的源码中存在着很多注释尤其是那些写在某一行代码上面的注释这些注释很多时候能最直接代表作者的想法和思路所以我认为这些注释是一定要翻译细读的可以减少很多我们在阅读源码过程中的疑惑。比如在[《23 | 从哨兵Leader选举学习Raft协议实现》](https://time.geekbang.org/column/article/421736)的每课一问中,蒋老师提出的问题就能在如下的注释中得到解答:
```plain
void sentinelTimer(void) {
sentinelCheckTiltCondition();
sentinelHandleDictOfRedisInstances(sentinel.masters);
sentinelRunPendingScripts();
sentinelCollectTerminatedScripts();
sentinelKillTimedoutScripts();
//这里就是 server.hz 需要动态调整作者为此做出的解释
/* We continuously change the frequency of the Redis "timer interrupt"
* in order to desynchronize every Sentinel from every other.
* This non-determinism avoids that Sentinels started at the same time
* exactly continue to stay synchronized asking to be voted at the
* same time again and again (resulting in nobody likely winning the
* election because of split brain voting). */
server.hz = CONFIG_DEFAULT_HZ + rand() % CONFIG_DEFAULT_HZ;
}
```
* **结合官网文档和GitHub的文档**
很多疑问和设计思路其实在这些地方都可以找到相应的解答,比如[GitHub](https://github.com/redis/redis)上面我们可以找到Redis编译、配置相关的文档。而在[官网](https://redis.io/documentation)我们可以了解到Redis的各种设计思想等。在学习课程的过程中我认为还可以回到社区去时常关注Redis正在发生的变化这样来结合源码的解读和学习就能帮助我们获得更好的学习效果。
而更重要的就是,刚刚开始阅读的同学,一定要跟随老师的脚步,因为老师会按照源码结构和知识点的内容划分模块,并通过课程讲述的逐渐推进,帮我们找到每个功能模块合适的切入点,这样能避免走很多弯路。我在自己阅读源码的时候,其实就是像瞎子过河一样,导致走了弯路,直到看完老师的课程才豁然开朗。
### 基础知识很重要
在学习和阅读Redis源码期间我能很明显地感受到基础知识的重要性。**万丈高楼平地起**无论是Redis中的跳表、Hash表的实现还是Redis中多线程与多进程的协作几乎都离不开基础知识。这里我来推荐两本书和一个课程这些内容对我理解Redis设计原理和阅读Redis源码帮助很大。
* [《深入理解计算机系统》](https://book.douban.com/subject/26912767/)
CSAPP想必很多人都有所接触这里我以第三版为例书中的第八章到第十二章节中就涵盖了Redis实现相关的基础知识内容比如进程、虚拟内存、系统I/O、网络、并发编程等等。比如在Redis源码中使用的fork()在书中《进程》章节就有专门说明,还有写时复制形成原因,在《虚拟内存》的章节中也有。
* [《算法导论》](https://book.douban.com/subject/20432061/)
可能很多人听到这个名字就感到害怕,但这本其实并不是每个章节都是晦涩难懂的,就以第三版为例,书中第三部分“数据结构-散列表”,以及第五部分“高级数据结构-B树”在我们的这门Redis源码课的学习过程中也时有提及。
* 徐文浩老师的课程[《深入浅出计算机组成原理》](https://time.geekbang.org/column/intro/100026001)
在这门课程中老师提到了内存伪共享、CPU缓存行和虚拟内存等相关知识也有各种计算机结构相关的知识点。读完也许你就能明白为什么Redis会一直坚持单线程的设计思路以及为什么以前我们经常会建议在部署Redis的时候进行绑核操作
当然我这里也不是说我们需要全部读完而是可以结合着来学习当我们遇到某些不太容易掌握的知识点的时候可以尝试着去这些书籍课程中寻找相关的补充资料这样更有助于深入理解Redis源码的设计初衷。
### 实践和回馈也很重要
读万卷书不如行万里路对Redis的学习也一样我们不能只阅读源码还需要经常去做尝试当然这里我不建议直接在线上尝试。**只有不断地试错,才能得到成长。**
我曾经就在线下使用过不同的内存分配机制来编译Redis最后通过压测工具去对比效果。或者是在Redis中通过断点去观察querybuf中RESP协议的解析过程。在每次的尝试过程中我都能有所收获都能感受到动手实操所带来的愉悦感并且愈发有深入学习下去的动力。
所以我建议你可以结合一些场景去进行实践,可以是对源码断点进行调试,也可以是针对源码的改造,哪怕只是一个日志输出,再或者是结合实际的业务场景做出一些改进,做出点成果,然后用这些成果来回馈自己。
而回到Redis源码的课程中对于**每课一问**我也是非常期待的因为我总能看到一些同学有不一样的想法和观点或者提出一些新颖的提问。在这个交流的过程中其实也是一种学习的回馈。老师的提问有助于引导我们去思考Redis这样设计的价值和意义。
### 死磕精神和知识沉淀
阅读源码的过程一定会伴随着枯燥,尤其是当遇到一个知识盲区,或者是自身无法理解的事物的时候,这种知识上的挑战所带来的焦虑的情绪,就会导致我们失去了继续看下去的动力。而每到这种时候,我就会让自己休息一下,换个心情,说不定第二天就想通了。
我还记得当时在阅读Redis生命周期相关代码的时候我就整整花了一周的时间去画出整个生命周期的时序图最终才把整个流程和思路走通。后来我渐渐发现每当我想起一个Redis的功能特性几乎都能有办法迅速定位到源码的位置而这就是我通过转换心情和坚持做事的死磕精神给我带来的学习收益。
同时在阅读Redis源码的过程中我也会输出自己的知识文档/博客,每次阅读完某个功能的代码后,都会记录下当时的理解过程和思路。然后,每当我重新阅读这部分代码的时候,就会回来作对比,结果往往就会发现,随着我的知识面的拓展和对代码的深入理解,很多细节其实和当时的理解是有偏差的,那么这个时候我就会去修正文档,而这样对我自己来说,其实也是一种知识的沉淀和积累。
比如,下面展示的就是我之前所做的辅助理解图例。
![图片](https://static001.geekbang.org/resource/image/66/8c/66a4e8d1a96d605e72a5e36c01c8798c.jpg?wh=1920x941 "字典数据结构图")
## 写在最后
最后很高兴能和大家一起学习Redis源码并且能有机会分享自己的学习心得。
我觉得订阅这门课程的同学本身就是对技术有更高追求的对技术都是有前瞻视野的。虽然Redis源码的学习是一个持续且漫长的过程需要付出时间去坚持我们可能一开始会备受打击但是一旦学有所成它带给我们的收获一定是非常有价值的。
世上无难事,只怕有心人,祝大家在学习完课程后,自身的能力都能更上一层楼。也期待各位的留言,我们一起交流和讨论,一起学习,共同进步。