当前位置: 首页 > news >正文

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

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 相关的知识学习,并形成一套自己的方法论。

作者:秦怀

相关文章:

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

1 Redis 的简介 Redis 实际上是简称&#xff0c;全称为 Remote Dictionary Server (远程字典服务器)&#xff0c;由 Salvatore Sanfilippo 写的高性能 key-value 存储系统&#xff0c;其完全开源免费&#xff0c;遵守 BSD 协议。Redis 与其他 key-value 缓存产品&#xff08;如…...

shell查看服务器的内存和CPU,实时使用情况

要查看服务器的内存和 CPU 实时使用情况&#xff0c;可以使用以下方法和命令&#xff1a; 1. 使用 top 运行 top 命令以显示实时的系统性能信息&#xff0c;包括 CPU 和内存使用情况。 top按 q 退出。输出内容包括&#xff1a; CPU 使用率&#xff1a;位于顶部&#xff0c;标…...

软件/游戏提示:mfc42u.dll没有被指定在windows上运行如何解决?多种有效解决方法汇总分享

遇到“mfc42u.dll 没有被指定在 Windows 上运行”的错误提示&#xff0c;通常是因为系统缺少必要的运行库文件或文件损坏。以下是多种有效的解决方法&#xff0c;可以帮助你解决这个问题&#xff1a; 原因分析 出现这个错误的原因是Windows无法找到或加载MFC42u.dll文件。这可…...

《Python基础》之函数、模块与库

目录 简介 一、函数 1、数学类函数 2、聚合类函数 3、和进制相关的函数 4、字符类函数 5、类型转换相关函数 6、获取输出类函数 二、模块与库的使用方法 1、模块和库的导入方法 2、第三方模块的下载 下载方法 简介 在Python编程的世界中&#xff0c;函数、模块和库是…...

selinux和防火墙实验

1 、 selinux 的说明 SELinux 是 Security-Enhanced Linux 的缩写&#xff0c;意思是安全强化的 linux 。 SELinux 主要由美国国家安全局&#xff08; NSA &#xff09;开发&#xff0c;当初开发的目的是为了避免资源的误用。 系统资源都是通过程序进行访问的&#xff0c;如…...

k8s Init:ImagePullBackOff 的解决方法

kubectl describe po (pod名字) -n kube-system 可查看pod所在的节点信息 例如&#xff1a; kubectl describe po calico-node-2lcxx -n kube-system 执行拉取前先把用到的节点的源换了 sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-EOF {"re…...

Spring AOP相关知识详解

难 文章目录 1.AOP介绍1.1 面向切面编程 - Aspect Oriented Programming (AOP)1.2 优点 2.AOP的概念2.1 连接点、切入点、通知、切面&#xff1a;2.2 注解2.2.1 通知类型2.2.1.1 通知的优先级排序 2.2.2 其他重要注解2.2.3 示例代码&#xff08;四种通知&#xff09; 3.Spring …...

selinux和防火墙

第七章 selinux 一、selinux的说明 SELinux&#xff1a;安全强化的 linux&#xff0c;Security-Enhanced Linux的缩写 SELinux &#xff1a; 由美国国家安全局&#xff08; NSA &#xff09;开发&#xff0c;目的是为了避免资源的误用 SELinux&#xff1a; 是对程序、文件等权…...

【vue for beginner】Composition API 和 Options API 的区别

&#x1f308;Don’t worry , just coding! 内耗与overthinking只会削弱你的精力&#xff0c;虚度你的光阴&#xff0c;每天迈出一小步&#xff0c;回头时发现已经走了很远。 &#x1f4d7;概念 vue2中的方式叫Options API &#xff0c;vue3中叫Composition API。 Composition…...

jmeter5.6.3安装教程

一、官网下载 需要提前配置好jdk的环境变量 jmeter官网&#xff1a;https://jmeter.apache.org/download_jmeter.cgi 选择点击二进制的zip文件 下载成功后&#xff0c;默认解压下一步&#xff0c;更改安装路径就行(我安装在D盘) 实用jmeter的bin目录作为系统变量 然后把这…...

关于Spring基础了解

Spring简介 Spring框架是一个开源的Java应用框架&#xff0c;旨在简化企业级应用程序的开发。它提供了一系列强大的工具和服务&#xff0c;帮助开发者构建高质量的Java应用程序。Spring框架的核心理念是使开发过程更加模块化、可测试和可维护。 主要特性 依赖注入&#xff08…...

输入json 达到预览效果

下载 npm i vue-json-pretty2.4.0 <template><div class"newBranchesDialog"><t-base-dialogv-if"addDialogShow"title"Json数据配置"closeDialog"closeDialog":dialogVisible"addDialogShow":center"…...

DataLoade类与list ,iterator ,yield的用法

1 问题 探索DataLoader的属性&#xff0c;方法 Vscode中图标含意 list 与 iterator 的区别&#xff0c;尤其yield的用法 2 方法 知乎搜索DataLoader的属性&#xff0c;方法 pytorch基础的dataloader类是 from torch.utils.data.dataloader import Dataloader 其主要的参数如下&…...

model_selection.train_test_split函数介绍

目录 model_selection.train_test_split函数实战 model_selection.train_test_split函数 model_selection.train_test_split 是 Scikit-Learn 中用于将数据集拆分为训练集和测试集的函数。这个函数非常有用&#xff0c;因为在机器学习中&#xff0c;我们通常需要将数据集分为训…...

Springboot 读取 resource 目录下的Excel文件并下载

代码示例: GetMapping("/download") public void download(HttpServletResponse response) {try {String filename "测试.xls";OutputStream outputStream response.getOutputStream();// 获取springboot resource 路径下的文件InputStream inputStream…...

SQL EXISTS 子句的深入解析

SQL EXISTS 子句的深入解析 引言 SQL&#xff08;Structured Query Language&#xff09;作为一种强大的数据库查询语言&#xff0c;广泛应用于各种数据库管理系统中。在SQL查询中&#xff0c;EXISTS子句是一种非常实用的工具&#xff0c;用于检查子查询中是否存在至少一行数…...

33.Java冒泡排序

冒泡排序&#xff1a; 一种排序的方式&#xff0c;对要进行排序的数据中相邻的数据进行两两比较&#xff0c;将较大的数据放在后面&#xff0c;依次对所有的数据进行操作&#xff0c;直至所有数据按要求完成排序. package Javase;import sun.security.util.ByteArrayTagOrder…...

Docker容器ping不通外网问题排查及解决

Docker容器ping不通外网问题排查及解决 解决方案在最下面&#xff0c;不看过程的可直接拉到最下面。 一台虚拟机里突然遇到docker容器一直访问外网失败&#xff0c;网上看到这个解决方案&#xff0c;这边记录一下。 首先需要明确docker的网桥模式&#xff0c;网桥工作在二层…...

JavaScript 库 number-precision 如何使用?

number-precision 是一个 JavaScript 库&#xff0c;主要用于处理 JavaScript 中的数字精度问题。它提供了一些方法&#xff0c;帮助你进行数字运算时保持精度&#xff0c;尤其是在涉及到浮点数运算时&#xff0c;它能够避免传统 JavaScript 中精度丢失的问题。 例如&#xff…...

faiss库中ivf-sq(ScalarQuantizer,标量量化)代码解读-2

文件ScalarQuantizer.h 主要介绍这里面的枚举以及一些函数内容&#xff1a;QuantizerType、RangeStat、ScalarQuantizer、train、compute_codes、decode、SQuantizer、FlatCodesDistanceComputer、get_distance_computer、select_InvertedListScanner QuantizerType 量化类型…...

Linux云原生安全:零信任架构与机密计算

Linux云原生安全&#xff1a;零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言&#xff1a;云原生安全的范式革命 随着云原生技术的普及&#xff0c;安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测&#xff0c;到2025年&#xff0c;零信任架构将成为超…...

Spring是如何解决Bean的循环依赖:三级缓存机制

1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间‌互相持有对方引用‌,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

【深尚想】TPS54618CQRTERQ1汽车级同步降压转换器电源芯片全面解析

1. 元器件定义与技术特点 TPS54618CQRTERQ1 是德州仪器&#xff08;TI&#xff09;推出的一款 汽车级同步降压转换器&#xff08;DC-DC开关稳压器&#xff09;&#xff0c;属于高性能电源管理芯片。核心特性包括&#xff1a; 输入电压范围&#xff1a;2.95V–6V&#xff0c;输…...

如何把工业通信协议转换成http websocket

1.现状 工业通信协议多数工作在边缘设备上&#xff0c;比如&#xff1a;PLC、IOT盒子等。上层业务系统需要根据不同的工业协议做对应开发&#xff0c;当设备上用的是modbus从站时&#xff0c;采集设备数据需要开发modbus主站&#xff1b;当设备上用的是西门子PN协议时&#xf…...

深度解析云存储:概念、架构与应用实践

在数据爆炸式增长的时代&#xff0c;传统本地存储因容量限制、管理复杂等问题&#xff0c;已难以满足企业和个人的需求。云存储凭借灵活扩展、便捷访问等特性&#xff0c;成为数据存储领域的主流解决方案。从个人照片备份到企业核心数据管理&#xff0c;云存储正重塑数据存储与…...

Docker、Wsl 打包迁移环境

电脑需要开启wsl2 可以使用wsl -v 查看当前的版本 wsl -v WSL 版本&#xff1a; 2.2.4.0 内核版本&#xff1a; 5.15.153.1-2 WSLg 版本&#xff1a; 1.0.61 MSRDC 版本&#xff1a; 1.2.5326 Direct3D 版本&#xff1a; 1.611.1-81528511 DXCore 版本&#xff1a; 10.0.2609…...

Element-Plus:popconfirm与tooltip一起使用不生效?

你们好&#xff0c;我是金金金。 场景 我正在使用Element-plus组件库当中的el-popconfirm和el-tooltip&#xff0c;产品要求是两个需要结合一起使用&#xff0c;也就是鼠标悬浮上去有提示文字&#xff0c;并且点击之后需要出现气泡确认框 代码 <el-popconfirm title"是…...

华为云Flexus+DeepSeek征文 | 基于Dify构建具备联网搜索能力的知识库问答助手

华为云FlexusDeepSeek征文 | 基于Dify构建具备联网搜索能力的知识库问答助手 一、构建知识库问答助手引言二、构建知识库问答助手环境2.1 基于FlexusX实例的Dify平台2.2 基于MaaS的模型API商用服务 三、构建知识库问答助手实战3.1 配置Dify环境3.2 创建知识库问答助手3.3 使用知…...

构建Docker镜像的Dockerfile文件详解

文章目录 前言Dockerfile 案例docker build1. 基本构建2. 指定 Dockerfile 路径3. 设置构建时变量4. 不使用缓存5. 删除中间容器6. 拉取最新基础镜像7. 静默输出完整示例 docker runDockerFile 入门syntax指定构造器FROM基础镜像RUN命令注释COPY复制ENV设置环境变量EXPOSE暴露端…...

RFID推动新能源汽车零部件生产系统管理应用案例

RFID推动新能源汽车零部件生产系统管理应用案例 一、项目背景 新能源汽车零部件场景 在新能源汽车零部件生产领域&#xff0c;电子冷却水泵等关键部件的装配溯源需求日益增长。传统 RFID 溯源方案采用 “网关 RFID 读写头” 模式&#xff0c;存在单点位单独头溯源、网关布线…...