Redis 源码剖析与实战
深入源码底层实现,轻松通关 Redis 面试
蒋德钧  中科院计算所副研究员
专栏
已完结·共 47 讲
|
1.8w 人已学
|
收藏
Kaito
很荣幸,能在第二季继续给大家带来加餐文章,分享我学习Redis的经验。 这篇文章和大家分享我阅读源码的经验和心得,当然,文章里提到的方法是「通用」的,不仅限于读Redis代码,读任何项目的源码都可以按这个思路来,希望我的分享能够帮助到大家!
2021-09-18
Milittle
这节课我学到了什么: 1. 第一、redis不是单线程的,而是一个主线程,处理IO,另外有三个线程分别处理关闭fd、异步AOF刷盘、延迟释放。 2. 我们从bio文件中可以看到函数之间的配合。 回答一下课后题目: 可以看到最新版的代码,redis开发人员已经把这个重构了: bio_job里面包含了fd(给关闭文件和异步刷盘用的),lazy_free_fn、free_args给延迟释放用的,一个是函数,一个是函数所需要用的参数。 备注:结合老师讲的源码版本和自己结合最新的版本看,你会在这个过程里面学到不少,有一些时候问题,就被解决了,你也可以从中学习到一些。
2021-08-23
曾轼麟
回答老师的提问,ziplist 能支持的最大整数是多大? 分析步骤: 1、按照问题范围,首先我去查看了zipTryEncoding的实现(ziplist.c),其中在给encoding赋值的时候划分了:极小值,int8,int16,int24,int32和int64 2、此外发现当value大于int32最大值的时候,会统一使用ZIP_INT_64B去编码 3、但是在zipTryEncoding开头处发现了一个判断,entrylen >= 32的时候是不允许编码,意思就是传入的数据如果entrylen大于32直接跳过不编码为int 4、最后调用string2ll尝试将string value编码为long long(转换成功为1失败为0) 5、若转换成功,编码类型放到encoding中,值放到v中 根据第三步个人判断,传入的int值大小不会超过32位,那么最大值应该就是int32的最大值
2021-08-08
Kaito
1、Redis 中的 dict 数据结构,采用「链式哈希」的方式存储,当哈希冲突严重时,会开辟一个新的哈希表,翻倍扩容,并采用「渐进式 rehash」的方式迁移数据 2、所谓「渐进式 rehash」是指,把很大块迁移数据的开销,平摊到多次小的操作中,目的是降低主线程的性能影响 3、Redis 中凡是需要 O(1) 时间获取 k-v 数据的场景,都使用了 dict 这个数据结构,也就是说 dict 是 Redis 中重中之重的「底层数据结构」 4、dict 封装好了友好的「增删改查」API,并在适当时机「自动扩容、缩容」,这给上层数据类型(Hash/Set/Sorted Set)、全局哈希表的实现提供了非常大的便利 5、例如,Redis 中每个 DB 存放数据的「全局哈希表、过期key」都用到了 dict: // server.h typedef struct redisDb { dict *dict; // 全局哈希表,数据键值对存在这 dict *expires; // 过期 key + 过期时间 存在这 ... } 6、「全局哈希表」在触发渐进式 rehash 的情况有 2 个: - 增删改查哈希表时:每次迁移 1 个哈希桶(文章提到的 dict.c 中的 _dictRehashStep 函数) - 定时 rehash:如果 dict 一直没有操作,无法渐进式迁移数据,那主线程会默认每间隔 100ms 执行一次迁移操作。这里一次会以 100 个桶为基本单位迁移数据,并限制如果一次操作耗时超时 1ms 就结束本次任务,待下次再次触发迁移(文章没提到这个,详见 dict.c 的 dictRehashMilliseconds 函数) (注意:定时 rehash 只会迁移全局哈希表中的数据,不会定时迁移 Hash/Set/Sorted Set 下的哈希表的数据,这些哈希表只会在操作数据时做实时的渐进式 rehash) 7、dict 在负载因子超过 1 时(used: bucket size >= 1),会触发 rehash。但如果 Redis 正在 RDB 或 AOF rewrite,为避免父进程大量写时复制,会暂时关闭触发 rehash。但这里有个例外,如果负载因子超过了 5(哈希冲突已非常严重),依旧会强制做 rehash(重点) 8、dict 在 rehash 期间,查询旧哈希表找不到结果,还需要在新哈希表查询一次 课后题:Hash 函数会影响 Hash 表的查询效率及哈希冲突情况,那么,你能从 Redis 的源码中,找到 Hash 表使用的是哪一种 Hash 函数吗? 找到 dict.c 的 dictFind 函数,可以看到查询一个 key 在哈希表的位置时,调用了 dictHashKey 计算 key 的哈希值: dictEntry *dictFind(dict *d, const void *key) { // 计算 key 的哈希值 h = dictHashKey(d, key); ... } 继续跟代码可以看到 dictHashKey 调用了 struct dict 下 dictType 的 hashFunction 函数: // dict.h dictHashKey(d, key) (d)->type->hashFunction(key) 而这个 hashFunction 是在初始化一个 dict 时,才会指定使用哪个哈希函数的。 当 Redis Server 在启动时会创建「全局哈希表」: // 初始化 db 下的全局哈希表 for (j = 0; j < server.dbnum; j++) { // dbDictType 中指定了哈希函数 server.db[j].dict = dictCreate(&dbDictType,NULL); ... } 这个 dbDictType struct 指定了具体的哈希函数,跟代码进去能看到,使用了 SipHash 算法,具体实现逻辑在 siphash.c。 (SipHash 哈希算法是在 Redis 4.0 才开始使用的,3.0-4.0 使用的是 MurmurHash2 哈希算法,3.0 之前是 DJBX33A 哈希算法)
2021-07-31
Ethan New
评论区也太强了吧,瑟瑟发抖
2021-07-28
Kaito
重新看了一下源码目录,结合这篇文章的内容,整理了一下代码分类(忽略.h头文件),这也更清晰一些: 数据类型: - String(t_string.c、sds.c、bitops.c) - List(t_list.c、ziplist.c) - Hash(t_hash.c、ziplist.c、dict.c) - Set(t_set.c、intset.c) - Sorted Set(t_zset.c、ziplist.c、dict.c) - HyperLogLog(hyperloglog.c) - Geo(geo.c、geohash.c、geohash_helper.c) - Stream(t_stream.c、rax.c、listpack.c) 全局: - Server(server.c、anet.c) - Object(object.c) - 键值对(db.c) - 事件驱动(ae.c、ae_epoll.c、ae_kqueue.c、ae_evport.c、ae_select.c、networking.c) - 内存回收(expire.c、lazyfree.c) - 数据替换(evict.c) - 后台线程(bio.c) - 事务(multi.c) - PubSub(pubsub.c) - 内存分配(zmalloc.c) - 双向链表(adlist.c) 高可用&集群: - 持久化:RDB(rdb.c、redis-check-rdb.c)、AOF(aof.c、redis-check-aof.c) - 主从复制(replication.c) - 哨兵(sentinel.c) - 集群(cluster.c) 辅助功能: - 延迟统计(latency.c) - 慢日志(slowlog.c) - 通知(notify.c) - 基准性能(redis-benchmark.c) 下面解答课后问题: Redis 从 4.0 版本开始,能够支持后台异步执行任务,比如异步删除数据,你能在 Redis 功能源码中,找到实现后台任务的代码文件么? 后台任务的代码在 bio.c 中。 Redis Server 在启动时,会在 server.c 中调用 bioInit 函数,这个函数会创建 3 类后台任务(类型定义在 bio.h 中): #define BIO_CLOSE_FILE 0 // 后台线程关闭 fd #define BIO_AOF_FSYNC 1 // AOF 配置为 everysec,后台线程刷盘 #define BIO_LAZY_FREE 2 // 后台线程释放 key 内存 这 3 类后台任务,已经注册好了执行固定的函数(消费者): - BIO_CLOSE_FILE 对应执行 close(fd) - BIO_AOF_FSYNC 对应执行 fsync(fd) - BIO_LAZY_FREE 根据参数不同,对应 3 个函数(freeObject/freeDatabase/freeSlowsMap) 之后,主线程凡是需要把一个任务交给后台线程处理时,就会调用 bio.c 的 bioCreateBackgroundJob 函数(相当于发布异步任务的函数),并指定该任务是上面 3 个的哪一类,把任务挂到对应类型的「链表」下(bio_jobs[type]),任务即发布成功(生产者任务完成)。 消费者从链表中拿到生产者发过来的「任务类型 + 参数」,执行上面任务对应的方法即可。当然,由于是「多线程」读写链表数据,这个过程是需要「加锁」操作的。 如果要找「异步删除数据」的逻辑,可以从 server.c 的 unlink 命令为起点,一路跟代码进去,就会看到调用了 lazyfree.c 的 dbAsyncDelete 函数,这个函数最终会调到上面提到的发布异步任务函数 bioCreateBackgroundJob,整个链条就串起来了。
2021-07-28
Kaito
看过第一季 Redis 专栏的朋友,应该都认识我了,去年在第一季 Redis 专栏被大家叫做「课代表」,在评论区持续输出了 4 个月之久,解答了很多 Redis 问题,另外还参与了专栏的「审稿、勘误」等工作。 当时很多人问我是怎么学习 Redis 的?我的回答中肯定少不了:「看源码」,想要进阶 Redis,我认为源码是必读的。 从去年到现在,这一晃,时间过得真快,现在 Redis 源码课来了,这次和大家一起,再次进阶 Redis!
2021-07-27
黄海峰
也买了上一门课程,学完获益良多,10星好评,那时评论区还有个牛人分享很多经验,而当时我正好在某大厂负责一个分布式存储系统的开发维护,工作轻松舒适无需加班,还时刻可以摸鱼在外面边喝咖啡边收听极客时间,那种工作轻松又能摸鱼学习充实自己的时光真美好。。。后面疫情公司裁员一批人,从此沦落到小厂加班加点各种不适应,工作换了几次,年纪又大,技术也不是特别牛,游走于被行业淘汰边缘,浑浑噩噩了一阵,产生了转行的念头并打算开始设法实施,现在又看到老师新课,熟悉的声音熟悉的知识点让我回忆反思了很多,虽然境况已经大不相同,但肯定还是要购买学习的,对我的意义已经不只是增强技术顺利面试了,还是一种精神寄托啊。
2021-07-26
可怜大灰狼
记得自己是从2020年8月4日跟着老师学习《Redis核心技术和施展》。虽然我很菜,对比当时评论区大神Kaito的精彩回答,自己当时留言并不多。但是这一年来我一直在学习Redis,专栏也看了好几遍,源码也针对3.0版本看了好几遍,然后根据之前学习,都针对每章整理了相应xmind脑图。现在每天晚上为同为后端开发的朋友讲解Redis源码。真的非常感谢老师把我领进门。这次看到老师的第二个专栏,迫不及待购买,知道自己又可以补充脑图和源码讲解课。哈哈。
作者回复:加油,我们一起努力! 有问题也欢迎多多交流。
2021-07-26
木几丶
期待已久的redis第二季,果断第一时间订阅,希望跟着老师有所收获
作者回复:希望能给你带来些收获 :)
2021-07-26
讲师

蒋德钧

中科院计算所副研究员

蒋德钧,中科院计算所副研究员,长期致力于研究 Redis,有 15 年云计算系统、存储系统和键值数据库的研发经验和技术积累。他还和蚂蚁金服、百度、华为、中兴等公司,围绕键值数据库开展过多种研制与优化项目合作,具有非常丰富的 Redis 实战经验。 另外,他在键值数据库方面,还先后开...查看更多
编辑推荐
讲师的其他课程
Redis 核心技术与实战
蒋德钧
中科院计算所副研究员

53讲 | 81730 人已学习

¥68¥199
包含这门课的学习路径

Java工程师

29门课程 154.7w人学习
看过的人还看了
Redis 核心技术与实战
蒋德钧
中科院计算所副研究员

53讲 | 81730 人已学习

¥68¥199
MySQL 实战 45 讲
林晓斌
网名丁奇,前腾讯云数据库负责人

49讲 | 224925 人已学习

¥68¥199
数据结构与算法之美
王争
前 Google 工程师

81讲 | 283791 人已学习

¥68¥199
设计模式之美
王争
前 Google 工程师,《数据结构与算法之美》专栏作者

113讲 | 123455 人已学习

¥98¥299
左耳听风
陈皓
网名“左耳朵耗子”,资深技术专家

119讲 | 180992 人已学习

¥98¥399
从 0 开始学架构
李运华
网名“华仔”,前阿里资深技术专家(P9)

66讲 | 152610 人已学习

¥68¥199