网站建设优化扬州,套模板网站,专业手机移动网站建设,珠海手机网站建设1 Redis 的简介
Redis 实际上是简称#xff0c;全称为 Remote Dictionary Server (远程字典服务器)#xff0c;由 Salvatore Sanfilippo 写的高性能 key-value 存储系统#xff0c;其完全开源免费#xff0c;遵守 BSD 协议。Redis 与其他 key-value 缓存产品#xff08;如…1 Redis 的简介
Redis 实际上是简称全称为 Remote Dictionary Server (远程字典服务器)由 Salvatore Sanfilippo 写的高性能 key-value 存储系统其完全开源免费遵守 BSD 协议。Redis 与其他 key-value 缓存产品如 memcache有以下几个特点。
数据持久化可以将内存中的数据保存在磁盘中重启的时候可以再次加载进行使用。
数据结构简单丰富既有简单的 key-value 类型的数据同时还提供 listsetzsethash 等数据结构的存储。
高可用支持主从、哨兵、集群等模式可以有效提高可用性。
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.0File New CMake Project from Sources, 打开源码项目, 会自动生成根目录下的 CMakeList.txt 文件: Clion 导入项目的时候选择已有的 MakeFile 文件如果有是否 clean 项目选择 clean 即可之后可以点开 MakeFile 文件 如果需要禁止编译器优化可以使用下面命令
make CFLAGS-g -O0 MALLOCjemalloc运行完之后 Src 文件下就会出现可运行文件
然后可以看到这些可运行的选项继而配置Edit configuration 运行配置 选择 debug 进行启动启动成功然后可以进行调试了
可以使用 Redis Desktop Manager 来进行连接
或者命令行连接没有密码就可以不需要 -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-malloclibc-malloc 性能不高且碎片化严重。linenoise一种读线替换。它由 Redis 的 同一作者开发但作为一个单独的项目进行管理并根据需要进行更新。lualua 脚本相关的功能。 src源代码 commons都是 json 文件放着每个指令的原信息。modules实现 Redis Module 的示例代码。其他文件均是源码 test测试代码 clusterRedis Cluster 功能测试。sentinel哨兵集群功能测试。unit单元测试。integration主从复制功能测试。 utils工具类Makefile编译文件redis.conf : redis 启动的配置文件sentinel.conf哨兵配置
3.2 Redis 源码阅读顺序
网上的源码阅读顺序引自网上
自底向上从耦合关系最小的模块开始读然后逐渐过度到关系紧密的模块。就好像写程序的测试一样先从单元测试开始然后才到功能测试。从功能入手通过文件名模块名和函数名快速定位到一个功能的具体实现然后追踪整个实现的运作流程从而了解该功能的实现方式。自顶向下从程序的 main() 函数或者某个特别大的调用者函数为入口以深度优先或者广度优先的方式阅读它的源码。
从大方向来说学习 Redis 会有两种路径
先从数据机构入手直接手撕数据结构 好处学着踏实知根知底坏处容易从入门到放弃 先从启动 Redis 开始跟着启动顺序读源码跟着具体的操作读源码 好处比较符合人的认知路线知道 Redis 启动做了哪些操作执行命令时做了哪些操作。坏处容易迷路前期看哪一句都不知道在干嘛毕竟 RDBAOF集群哨兵这些源码如果实操过才相对容易理解一点。
个人建议是先学习如何启动 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( 23, 12 );此处预期的结果是15但是实际的结果却是7这是因为该段代码在编译进行了简单的符号替换而得到的实际表达式是 A 23 * 12;
根据运算符的优先级先进行乘法运算然后才是加法这就导致了错误。 而如果使用
#define AREA(x,y) (x)*(y)
...
A AREA( 23, 12 );则在编译时替换的结果是
A (23) * (12);#includestdio.h
#define AREA(x,y) (x)*(y)
int main()
{ int a AREA(23, 12); 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 里面的 接口 public 和 private 的概念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(ab,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 也不会被解析多次有且只有一次。
解析多次的坏处是什么
如果在.h 文件里面定义了全局变量会导致变量重复定义。这个基本不太会公司编码规范一般都会禁止这样写是不人道的。浪费编译时间。
既然禁止了在 .h 文件里面定义全局变量那全局变量在哪里定义呢当然是 .c 文件比如 Redis 里面的全局变量
那其他的文件怎么使用这个 sever 可是全局唯一的维护了 redis 的全部状态数据那当然是暴露出去在哪里暴露出去在 .h 文件使用关键字 extern
5 小结一下
阅读源码是一件长期的事情但是我们每次跟读代码的时候一定要带着问题去阅读否则效率会下降挺多。前期了解数据结构模型的时候可以在网上找一些简单易懂的博客最好是有图片的书籍比较推荐《Redis 设计与实现》。有一定了解之后会有些疑问不用担心此时再通过读源代码去验证我们的想法可能不少小伙伴没学过 c 语言也不必担心语言之间都是相通的其次即使有关键字不会可以通过搜索也可以快速了解其作用。 希望我们都能从全局看功能 -- 实践 -- 抓大放小 -- 带疑问看源码 -- 重构知识图谱 -- 关联知识 -- 跳出细节俯瞰全局最终完成 Redis 相关的知识学习并形成一套自己的方法论。
作者秦怀