Redis【1】- 如何阅读Redis 源码

news2024/11/30 16:57:16

1 Redis 的简介

Redis 实际上是简称,全称为 Remote Dictionary Server (远程字典服务器),由 Salvatore Sanfilippo 写的高性能 key-value 存储系统,其完全开源免费,遵守 BSD 协议。Redis 与其他 key-value 缓存产品(如 memcache)有以下几个特点。

  • 数据持久化:可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
  • 数据结构简单丰富:既有简单的 key-value 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。
  • 高可用:支持主从、哨兵、集群等模式,可以有效提高可用性。

Redis 也是一种 分布式缓存 [[1. 从缓存到分布式缓存]],其代码是 c 语言写的,那我们该如何阅读呢?

2 环境搭建

环境依赖,先看看 gcc 、cc、g++ 有没有安装

whereis gcc
whereis cc
whereis g++

安装gcc

xcode-select --install  
brew install gcc  
brew install pkg-config

查看 gcc 的版本:

$ gcc --version
Apple clang version 14.0.0 (clang-1400.0.29.202)
Target: x86_64-apple-darwin22.1.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin

我使用 CLion 2022.3.1 ,这个版本可以支持 Makefile 的项目,我们可以检查一下环境是不是有问题, 如果有问题,这里会有错误信息,我的之前报错是因为 Clion 的版本版本太低了,升级之后就好了。

下载Redis源码:

git clone https://github.com/redis/redis.git

切换到指定的版本

git checkout 7.0

File => New CMake Project from Sources, 打开源码项目, 会自动生成根目录下的 CMakeList.txt 文件:
Clion 导入项目的时候选择已有的 MakeFile 文件,如果有是否 clean 项目,选择 clean 即可,之后可以点开 MakeFile 文件:

如果需要禁止编译器优化,可以使用下面命令:

make CFLAGS="-g -O0" MALLOC=jemalloc

运行完之后, Src 文件下就会出现可运行文件:

然后可以看到这些可运行的选项,继而配置Edit configuration 运行配置:

选择 debug 进行启动,启动成功,然后可以进行调试了:

可以使用 Redis Desktop Manager 来进行连接:
image.png

或者命令行连接(没有密码就可以不需要 -a 12345):

redis-cli -h 127.0.0.1 -p 6379 -a 12345

如果头文件引入报红色下划线,那就试试重新加载一下

3 Redis源码阅读技巧

3.1 Redis 的目录结构

Redis 的目录:

  • deps: Redis 所依赖的第三方代码库
    • hdr_histogram:用于生成命令的延迟追踪直方图
    • hiredis:官方c语言客户端
    • Jemalloc:内存分配器,默认情况下选择该内存分配器来代替 Linux 系统的 libc-malloc,libc-malloc 性能不高,且碎片化严重。
    • linenoise:一种读线替换。它由 Redis 的 同一作者开发,但作为一个单独的项目进行管理,并根据需要进行更新。
    • lua:lua 脚本相关的功能。
  • src:源代码
    • commons:都是 json 文件,放着每个指令的原信息。
    • modules:实现 Redis Module 的示例代码。
    • 其他文件均是源码
  • test:测试代码
    • cluster,Redis Cluster 功能测试。
    • sentinel,哨兵集群功能测试。
    • unit,单元测试。
    • integration,主从复制功能测试。
  • utils:工具类
  • Makefile:编译文件
  • redis.conf : redis 启动的配置文件
  • sentinel.conf:哨兵配置

3.2 Redis 源码阅读顺序

网上的源码阅读顺序(引自网上):

  • 自底向上:从耦合关系最小的模块开始读,然后逐渐过度到关系紧密的模块。就好像写程序的测试一样,先从单元测试开始,然后才到功能测试。
  • 从功能入手:通过文件名(模块名)和函数名,快速定位到一个功能的具体实现,然后追踪整个实现的运作流程,从而了解该功能的实现方式。
  • 自顶向下:从程序的 main() 函数,或者某个特别大的调用者函数为入口,以深度优先或者广度优先的方式阅读它的源码。

从大方向来说,学习 Redis 会有两种路径:

  • 先从数据机构入手,直接手撕数据结构
    • 好处:学着踏实,知根知底
    • 坏处:容易从入门到放弃
  • 先从启动 Redis 开始,跟着启动顺序读源码,跟着具体的操作读源码
    • 好处:比较符合人的认知路线,知道 Redis 启动做了哪些操作,执行命令时做了哪些操作。
    • 坏处:容易迷路,前期看哪一句,都不知道在干嘛,毕竟 RDB,AOF,集群,哨兵这些源码,如果实操过才相对容易理解一点。

个人建议是先学习如何启动 Redis,抓大放小(大致知道哪个类启动,读那些配置文件,大概是做什么用的),学习 Redis 到底能干什么,大致知道 Redis 的一些用法之后,再去了解 Redis 的常用的数据结构,到底怎么实现的,这个时候对 Redis 的一些数据结构大致有印象,之后可以跟着 Redis 启动,执行命令去看具体功能执行的路径。
在 Debug 的过程中,可以加深影响,更加了解数据结构的设计,代码的调用关系。

4 C语言的知识

4.1 #define的基本用法

在C语言中,常量是使用频率很高的一个量。常量是指在程序运行过程中,其值不能被改变的量。常量常使用 #define来定义。
使用#define定义的常量也称为符号常量,可以提高程序的运行效率,Redis 的源代码中有比较多的地方都使用该方式。

一般有以下两种用法:

#define 宏名 宏值
#define 宏名(参数列表) 表达式

第一种就是定义常量,比如:

#define N 100

此后直到 #undef N之前, N的值都是100。当遇到#undef N,其后如果再出现 N,则 N 需要重新定义之后才可以使用。

第二种语法常用来定义符号函数。
例如:

#define AREA(x,y) (x)*(y)

表示用来求长和宽分别是x和y的矩形的面积。
需要注意的是,在表达式(x) * (y)中,x和y都要使用“()”括起来,这是因为符号函数在编译时时进行符号形式替换。如果不加()则可能会发生意想不到的错误,例如:

#define AREA(x,y)  x*y
...
A = AREA( 2+3, 1+2 );

此处预期的结果是15,但是实际的结果却是7,这是因为该段代码在编译进行了简单的符号替换而得到的实际表达式是:
A = 2+3 * 1+2;

根据运算符的优先级,先进行乘法运算,然后才是加法,这就导致了错误。
而如果使用

#define AREA(x,y)  (x)*(y)
...
A = AREA( 2+3, 1+2 );

则在编译时替换的结果是:

A = (2+3) * (1+2);
#include"stdio.h"  
#define AREA(x,y)  (x)*(y)  
int main()  
{  
    int a = AREA(2+3, 1+2);  
   printf( " %d\n", a);  
   return 0;  
}

4.2 头文件

Redis 是使用 c 语言写的,里面有很多头文件:

#include "server.h"  
#include "monotonic.h"  
#include "cluster.h"  
#include "slowlog.h"  
#include "bio.h"  
#include "latency.h"  
#include "atomicvar.h"  
#include "mt19937-64.h"  
#include "functions.h"  
#include "syscheck.h"  
  
#include <time.h>

< 开头的,比如 #include <time.h> 是标准库的头文件,会在系统指定路径下查找,对应到 Java里面可以理解为 官方的 jdk 里面的类,而类似 #include "server.h" 则是工程里面自定义的。

我没怎么写过 c 语言的代码, 一般 .c 文件是写实现的代码逻辑的,那如何在 a 文件里面写一个方法,让 b 文件也能用呢?

通过头文件的机制,类似 Java 里面的 接口, publicprivate 的概念,Java 中 一般希望对外暴露的方法,会设置为 public ,,如果不希望暴露,则设置为private。c 语言里面如果希望暴露,则可以在头文件里面定义,否则不用定义。(虽然c语言是面向过程的,但是Redis确实在里面实践一些面向对象的思想)。

比如计算两数之和 与 两数之差 的乘积 test.c

long long mul(int a,int b) {  
    return a*b;  
}  
  
  
long long calculate(int a,int b) {  
    return mul(a+b,a-b);  
}

暴露出去的头文件test.h

long long calculate(int a,int b);

运行的代码 main.c ,可以正常计算结果为 -3:

#include "stdio.h"  
#include "test.h"  
int main(){  
    printf("结果:%lld",calculate(1,2));  
    return 0;  
}

但是如果直接引用 sum() 方法,则会报错,无法使用:

如果我们多次引用头文件会怎么样?结果是正常运行:

4.3 ifndef

Redis 里面有挺多的地方定义头文件的时候总是来一句 #isdef 或者 ifndef

#ifdef __linux__  
#include <sys/mman.h>  
#endif
#ifndef __ADLIST_H__  
#define __ADLIST_H__
...
#endif /* __ADLIST_H__ */

如果加了 #ifndef ,则会判断只有没有定义这个宏的时候,才会定义它,第二次再次遇到 include 的时候,发现这个宏已经被定义过了,就会直接跳过,这样可以保证多次 include 也不会被解析多次,有且只有一次。

解析多次的坏处是什么?

  1. 如果在.h 文件里面定义了全局变量,会导致变量重复定义。这个基本不太会,公司编码规范一般都会禁止,这样写是不人道的。
  2. 浪费编译时间。

既然禁止了在 .h 文件里面定义全局变量,那全局变量在哪里定义呢?当然是 .c 文件,比如 Redis 里面的全局变量:

那其他的文件怎么使用?这个 sever 可是全局唯一的,维护了 redis 的全部状态数据,那当然是暴露出去,在哪里暴露出去,在 .h 文件,使用关键字 extern

5 小结一下

阅读源码,是一件长期的事情,但是我们每次跟读代码的时候,一定要带着问题去阅读,否则效率会下降挺多。前期了解数据结构模型的时候,可以在网上找一些简单易懂的博客,最好是有图片的,书籍比较推荐《Redis 设计与实现》。有一定了解之后,会有些疑问,不用担心,此时再通过读源代码去验证我们的想法,可能不少小伙伴没学过 c 语言,也不必担心,语言之间都是相通的,其次即使有关键字不会,可以通过搜索也可以快速了解其作用。
希望我们都能从全局看功能 --> 实践 --> 抓大放小 --> 带疑问看源码 --> 重构知识图谱 --> 关联知识 --> 跳出细节俯瞰全局,最终完成 Redis 相关的知识学习,并形成一套自己的方法论。

作者:秦怀

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2250557.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

鸿蒙修饰符

文章目录 一、引言1.1 什么是修饰符1.2 修饰符在鸿蒙开发中的重要性1.3 修饰符的作用机制 二、UI装饰类修饰符2.1 Styles修饰符2.1.1 基本概念和使用场景2.1.2 使用示例2.1.3 最佳实践 2.2 Extend修饰符2.2.1 基本概念2.2.2 使用示例2.2.3 Extend vs Styles 对比2.2.4 使用建议…

nginx安装和负载均衡

1. nginx安装 &#xff08;1&#xff09;安装依赖项&#xff1a; yum -y install gcc gcc-c make libtool zlib zlib-devel openssl openssl-devel pcre pcre-devel&#xff08;2&#xff09;下载Nginx源代码&#xff1a; http://nginx.org/en/download.html https://nginx.o…

部署 Prometheus

实验环境 IP地址服务192.168.88.10Prometheus服务端, Consul, Grafana, Node-Exporter192.168.88.77MySQL, Node-Exporter192.168.88.30Nginx&#xff0c;Node-Exporter 一、Prometheus Server 端安装和相关配置 【Prometheus1.sh】 &#xff08;1&#xff09;上传 prometh…

通过深度点图表示的隐式场实现肺树结构的高效解剖标注文献速递-生成式模型与transformer在医学影像中的应用

Title 题目 Efficient anatomical labeling of pulmonary tree structures via deeppoint-graph representation-based implicit fields 通过深度点图表示的隐式场实现肺树结构的高效解剖标注 01 文献速递介绍 近年来&#xff0c;肺部疾病&#xff08;Decramer等&#xff…

# 22_ Python基础到实战一飞冲天(二)-python基础(二十二)--名片管理系统案例:cards_tools.py文件

22_ Python基础到实战一飞冲天&#xff08;二&#xff09;-python基础&#xff08;二十二&#xff09;–名片管理系统案例&#xff1a;cards_tools.py文件 一、框架搭建-09-准备名片操作函数修改主文件中函数调用 1、名片管理系统 案例&#xff1a;框架搭建 — cards_tools.p…

Python 和 Pyecharts 对Taptap相关数据可视化分析

结果展示&#xff1a; 数据来源&#xff1a; Python爬取TapTap 热门游戏信息并存储到数据库&#xff08;详细版&#xff09; 目录 结果展示&#xff1a; 数据来源&#xff1a; Python爬取TapTap 热门游戏信息并存储到数据库&#xff08;详细版 一、引言 二、准备工作 三、…

泷羽sec-shell (3)脚本参数传递与数学运算

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&a…

重塑视频新语言,让每一帧都焕发新生——Video-Retalking,开启数字人沉浸式交流新纪元!

模型简介 Video-Retalking 模型是一种基于深度学习的视频再谈话技术&#xff0c;它通过分析视频中的音频和图像信息&#xff0c;实现视频角色口型、表情乃至肢体动作的精准控制与合成。这一技术的实现依赖于强大的技术架构和核心算法&#xff0c;特别是生成对抗网络&#xff0…

Mybatis Plus 增删改查方法(一、增)

先定义一个简单的测试表&#xff0c;执行脚本如下&#xff1a; create table user(id bigint primary key auto_increment,name varchar(255) not null,age int not null default 0 check (age > 0) ); 根据Spingbootmybatisplus的结构根据表自行构建结构&#xff0c;大致…

依赖倒置原则:Java实践篇

在软件开发的世界里&#xff0c;设计原则如同指南针&#xff0c;指引着我们构建更加健壮、可维护和可扩展的系统。其中&#xff0c;依赖倒置原则&#xff08;Dependency Inversion Principle&#xff0c;DIP&#xff09;是面向对象设计&#xff08;OOD&#xff09;中的一个重要…

【MySQL】库和表的基本操作

目录 库 库的增删查改 字符集与校验集 库的备份与恢复 表 表的创建和删除 用不同的存储引擎创建表的区别 查看表 修改表 添加删除属性 修改改变属性 上篇博客我们讲了数据库的基本理解&#xff0c;对数据库有了一个大致的概念&#xff0c;下面我们来介绍一下库和表的…

大数据新视界 -- 大数据大厂之 Hive 函数库:丰富函数助力数据处理(上)(11/ 30)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

03.ES7 04.ES8

3.1.Array.includes Includes 方法用来检测数组中是否包含某个元素&#xff0c;返回布尔类型值 <script>// includes const mingzhu [王二,张三,李四,王五];//判断console.log(mingzhu.includes(张三));//trueconsole.log(mingzhu.includes(周六));//false//indexOf …

中国科学院大学研究生学术英语读写教程 Unit7 Materials Science TextA 原文和翻译

中国科学院大学研究生学术英语读写教程 Unit7 Materials Science TextA 原文和翻译 Why Is the Story of Materials Really the Story of Civilisation? 为什么材料的故事实际上就是文明的故事&#xff1f; Mark Miodownik 1 Everything is made of something. Take away co…

下载安装Android Studio

&#xff08;一&#xff09;Android Studio下载地址 https://developer.android.google.cn/studio 滑动到 点击下载文档 打开新网页 切换到english ![](https://i-blog.csdnimg.cn/direct/b7052b434f9d4418b9d56c66cdd59fae.png 等待一会&#xff0c;出现 点同意后&#xff0…

【解决方案】pycharm出现 为项目选择的Python解释器无效

文章目录 1.问题重述2.解决方案END 1.问题重述 第二次启动项目的时候出现 2.解决方案 右下角点 先选无解释器&#xff0c;然后在用项目配置好的解释器&#xff0c;然后就好了&#xff0c;估计是第二次启动的时候没有识别到&#xff0c;UI的信号设置的问题 END

浏览器的数据六种存储方法比较 :LocalStorage vs. IndexedDB vs. Cookies vs. OPFS vs. WASM-SQLite

在构建该 Web 应用程序&#xff0c;并且希望将数据存储在用户浏览器中。也许您只需要存储一些小标志&#xff0c;或者甚至需要一个成熟的数据库。 我们构建的 Web 应用程序类型发生了显着变化。在网络发展的早期&#xff0c;我们提供静态 html 文件。然后我们提供动态渲染的 h…

linux一键部署apache脚本

分享一下自己制作的一键部署apache脚本&#xff1a; 脚本已和当前文章绑定&#xff0c;请移步下载&#xff08;免费&#xff01;免费&#xff01;免费&#xff01;&#xff09; &#xff08;单纯的分享&#xff01;&#xff09; 步骤&#xff1a; 将文件/内容上传到终端中 …

Java ConcurrentHashMap

Java Map本质不是线程安全的&#xff0c;HashTable和Collections同步包装器&#xff08;Synchronized Wrapper&#xff09;在并发场景下性能低。Java还为实现 Map 的线程安全提供了并发包&#xff0c;保证线程安全的方式从synchronize简单方式到精细化&#xff0c;比如Concurre…

redis下载、基础数据类型、操作讲解说明,持久化、springboot整合等

1 Redis是什么 官网&#xff1a;https://redis.io 开发者&#xff1a;Antirez Redis诞生于2009年全称是Remote Dictionary Server 远程词典服务器&#xff0c;是一个基于内存的键值型NoSQL数据库。 Redis是一个开源的、高性能的键值对存储系统&#xff0c;它支持多种数据结构&…