阅读源代码是软件工程师进阶的必备技能,但是现目前通过IDE阅读源代码的方式却令人痛苦不已。单行的代码大家都认识,初级的语法大多都不构成真正的障碍,然而阅读一个成熟或者开源项目,却往往令人倍感头疼。
复杂的嵌套关系,冗余的依赖结构,往往使只希望研究项目核心机制的我们,迷失在代码的汪洋之中。
单行的代码大家都认识,但是复杂的项目为什么就看不懂了呢?其中的关键在于阅读源代码的过程中,我们的大脑做了太多的无用功,在现有的ide中需要记忆大量无关紧要的中间函数、路径,甚至所在代码的行数,而通过Codemap,我们能很好地解决这个问题。
Codemap 摒弃了tab页的打开方式,通过平铺布局,以及连线、高亮、标注等一系列手段,辅助用户阅读源代码,告别了为了研究代码的执行机制,而不停切换文件,不断展开、折叠源代码的历史,为用户提供了一种可以清晰展示代码逻辑结构、添加高亮备注的方式。
Codemap阅读Redis源代码
如果是作为初中级入门,想通过项目更加深刻地掌握c语言语法,了解生产级项目的架构设计,则一定不能贪多。为了最大限度地学习项目的架构设计、以及减少bug,推荐选择第一个主版本的最后一个稳定小版本。
Redis作为2009年就发布的基于内存的键值数据库,虽然结构简洁、功能强大,但他最新版本的代码库也一定不会简单。通过 cloc 命令可以简单的统计出Redis各个主要版本的代码行数。
版本 | c代码行数 |
---|---|
1.3.6 | 10892 |
2.8.4 | 64061 |
3.2.13 | 78612 |
6.2.13 | 129308 |
7.2.1 | 170691 |
作为初中级入门,10w+的代码行数是不可接受的,而1.3.6就刚刚好,1w+的代码,删繁就简,重构出最小系统的 mini-redis ,代码总量应该也可以控制在小千范围内。
梳理项目的核心逻辑结构
通过Codemap,现在梳理项目的核心逻辑结构已经不再那么困难。
1️⃣核心初始化流程
- 系统通过 main() 函数启动,分别运行 initServer() 和 aeMain() 函数,initServer() 执行服务器的初始化,aeMain() 当 !eventLoop->stop 就一直执行 aeProcessEvents() 处理事件的死循环
- 初始化 server.el 和 server.fd,然后通过 aeCreateFileEvent() 函数配置事件循环监听网络事件,当产生可读事件,则通过 acceptHandler 处理
- 产生网络连接后,首先创建 redisClient 结构体,然后又通过 aeCreateFileEvent() 函数,配置 eventLoop 和 c->fd ,当继续产生可读事件,则由 readQueryFromClient() 处理
- 处理 client 传来的数据,在 cwdTable 中查找相应的处理方法。cmdTable 中存储了 redis 支持的所有命令,以及相应的处理方法、以及在虚拟内存中的相关配置信息等
- 最后执行相应命令,并返回
2️⃣get命令执行流程
- cmdTable 中存储了 redis 支持的所有命令,redisCommand 结构体包括了命令名、处理函数、参数数量、标志位、以及虚拟内存相关的配置
- getCommand 函数通过 getGenericCommand() 主要是执行了 lookupKey() 和 addReply() 两步
- redis 的 key、value 键值对存储在 redisDb 的 dict 元素中,dict 结构体是一个 hash 表,table 元素是 dictEntry 指针的数组,dictEntry 是一个链表,其中存储了键值对的真实数据 key 和 value 的指针,dict 相关的操作和头文件都在 dict.c 和 dict.h 中
- 当查找 key 对应的 value 时,首先通过 dictHashKey() 算出当前 key 的索引值 h,然后通过 ht->table[h] 找到链表的第一个 dictEntry,通过对比 dictEntry 的 key 和当前 key 是否完全一致,查找相应的 dictEntry
- 根据找到的 dictEntry 的具体类型,添加相应的回复