文章目录
- Redis是什么?
- 什么是Redis社区版?
- 构建Redis
- 修复依赖项或缓存构建选项的构建问题
- 修复构建32位二进制文件的问题
- 分配器
- 单调时钟
- 详细构建
- 运行Redis
- 运行支持TLS的Redis
- 玩转Redis
- 安装Redis
- 代码贡献
- Redis商标
- Redis内部结构
- 源代码布局
- server.h
- server.c
- commands.c
- networking.c
- aof.c和rdb.c
- db.c
- object.c
- replication.c
- 脚本
- 其他C文件
- Redis命令的剖析
这个README只是一个快速的入门文档。您可以在redis.io找到更详细的文档。
Redis是什么?
Redis通常被称为一个数据结构服务器。这意味着Redis通过一组命令提供对可变数据结构的访问,这些命令使用TCP套接字和简单协议的服务器-客户端模型发送。因此,不同的进程可以以共享方式查询和修改相同的数据结构。
Redis中实现的数据结构有一些特殊属性:
- Redis会将它们存储在磁盘上,即使它们总是在服务器内存中被服务和修改。这意味着Redis速度快,但同时也是非易失性的。
- 数据结构的实现强调内存效率,因此Redis中的数据结构可能比使用高级编程语言建模的相同数据结构使用更少的内存。
- Redis提供了一些在数据库中自然可以找到的特性,如复制、可调级别的持久性、集群和高可用性。
另一个好的例子是将Redis视为memcached的一个更复杂的版本,其中的操作不仅仅是SET和GET,还包括与列表、集合、有序数据结构等复杂数据类型一起工作的操作。
如果您想了解更多,这是一个精选的起点列表:
- Redis数据类型介绍。https://redis.io/topics/data-types-intro
- 在浏览器中直接尝试Redis。https://try.redis.io
- Redis命令的完整列表。https://redis.io/commands
- 官方Redis文档中有更多的内容。https://redis.io/documentation
什么是Redis社区版?
Redis OSS在7.4版本发布时更名为Redis社区版(CE)。
Redis Ltd.还提供了Redis软件,这是一个自管理软件,为企业扩展提供了额外的合规性、可靠性和弹性,以及Redis云,这是一个与Google Cloud、Azure和AWS集成的完全托管服务,适用于生产就绪的应用程序。
更多关于Redis社区版和Redis之间差异的信息,请点击这里。
构建Redis
Redis可以在Linux、OSX、OpenBSD、NetBSD、FreeBSD上编译和使用。我们支持大端和小端架构,以及32位和64位系统。
它可能可以在基于Solaris的系统(例如SmartOS)上编译,但我们对这一平台的支持是尽力而为,Redis不能保证在Linux、OSX和*BSD上工作得一样好。
构建过程非常简单:
```
% make
```
要构建支持TLS的Redis,您需要OpenSSL开发库(例如Debian/Ubuntu上的libssl-dev),然后运行:
```
% make BUILD_TLS=yes
```
要构建支持systemd的Redis,您需要systemd开发库(例如Debian/Ubuntu上的libsystemd-dev或CentOS上的systemd-devel),然后运行:
```
% make USE_SYSTEMD=yes
```
要在Redis程序名后追加后缀,请使用:
```
% make PROG_SUFFIX="-alt"
```
您可以使用以下命令构建32位Redis二进制文件:
```
% make 32bit
```
构建Redis后,最好使用以下命令进行测试:
```
% make test
```
如果构建了TLS,可以在启用TLS的情况下运行测试(您需要安装tcl-tls
):
```
% ./utils/gen-test-certs.sh
% ./runtest --tls
```
修复依赖项或缓存构建选项的构建问题
Redis有一些依赖项,这些依赖项包含在deps
目录中。make
命令不会自动重新构建依赖项,即使依赖项的源代码发生了变化。
当您使用git pull
更新源代码或以其他方式修改依赖项树中的代码时,请确保使用以下命令真正清理一切并从头开始重建:
```
% make distclean
```
这将清理:jemalloc、lua、hiredis、linenoise等依赖项。
此外,如果您强制某些构建选项,如32位目标、无C编译器优化(用于调试目的)等类似的构建时选项,这些选项会无限期地缓存,直到您发出make distclean
命令。
修复构建32位二进制文件的问题
如果您在以32位目标构建Redis后需要用64位目标重新构建它,或者反过来,您需要在Redis发行版的根目录执行make distclean
。
在尝试构建Redis的32位二进制文件时,如果遇到构建错误,请尝试以下步骤:
- 安装libc6-dev-i386软件包(也尝试g+±multilib)。
- 尝试使用以下命令行代替
make 32bit
:make CFLAGS="-m32 -march=native" LDFLAGS="-m32"
分配器
在构建Redis时选择非默认内存分配器是通过设置MALLOC
环境变量来完成的。Redis默认使用libc malloc进行编译和链接,但在Linux系统上,默认使用jemalloc。选择这个默认值是因为jemalloc被证明比libc malloc有更少的碎片问题。
要强制使用libc malloc进行编译,请使用:
```
% make MALLOC=libc
```
在Mac OS X系统上使用jemalloc进行编译,请使用:
```
% make MALLOC=jemalloc
```
单调时钟
默认情况下,Redis将使用POSIX clock_gettime函数作为单调时钟源。在大多数现代系统上,可以使用内部处理器时钟来提高性能。警告信息可以在这里找到。
要构建支持处理器内部指令时钟的Redis,请使用:
```
% make CFLAGS="-DUSE_PROCESSOR_CLOCK"
```
详细构建
Redis默认会以用户友好的彩色输出进行构建。如果您想看到更详细的输出,请使用以下命令:
```
% make V=1
```
运行Redis
要使用默认配置运行Redis,只需输入:
```
% cd src
% ./redis-server
```
如果您想提供自己的redis.conf,您必须使用额外的参数(配置文件的路径)来运行它:
```
% cd src
% ./redis-server /path/to/redis.conf
```
可以通过直接将参数作为选项使用命令行来更改Redis配置。示例:
```
% ./redis-server --port 9999 --replicaof 127.0.0.1 6379
% ./redis-server /etc/redis/6379.conf --loglevel debug
```
redis.conf中的所有选项也支持作为命令行选项使用,名称完全相同。
运行支持TLS的Redis
有关如何使用TLS运行Redis的更多信息,请参考TLS.md文件。
玩转Redis
您可以使用redis-cli来玩转Redis。启动一个redis-server实例,然后在另一个终端尝试以下操作:
```
% cd src
% ./redis-cli
redis> ping
PONG
redis> set foo bar
OK
redis> get foo
"bar"
redis> incr mycounter
(integer) 1
redis> incr mycounter
(integer) 2
```
您可以在https://redis.io/commands找到所有可用命令的列表。
安装Redis
要将Redis二进制文件安装到/usr/local/bin,只需使用:
```
% make install
```
如果您希望使用不同的目的地,可以使用make PREFIX=/some/other/directory install
。
make install
只会在系统中安装二进制文件,但不会在适当位置配置init脚本和配置文件。如果您只是想尝试一下Redis,这并不需要,但如果您要为生产系统正确安装它,我们有一个脚本来为Ubuntu和Debian系统执行此操作:
```
% cd utils
% ./install_server.sh
```
注意:install_server.sh
在Mac OSX上不起作用;它仅适用于Linux。
该脚本会问您几个问题,并为您设置一切所需,以便Redis可以作为后台守护进程正确运行,并在系统重启时再次启动。
您将能够使用名为/etc/init.d/redis_<portnumber>
的脚本停止和启动Redis,例如/etc/init.d/redis_6379
。
代码贡献
通过以任何形式向Redis项目贡献代码,包括通过GitHub发送拉取请求、通过私人电子邮件或公共讨论组发送代码片段或补丁,您同意根据Redis软件授权和贡献者许可协议发布您的代码。Redis软件包含对原始Redis核心项目的贡献,这些贡献归其贡献者所有,并在3BSD许可下授权。此存储库中的任何该许可的副本仅适用于这些贡献。Redis从7.4.x版本及以后的所有Redis社区版版本均在LICENSE.txt文件中描述的RSALv2/SSPL双许可下发布。
有关更多信息,请参见此源分发中的CONTRIBUTING.md文件。有关安全漏洞和漏洞,请参阅SECURITY.md。
Redis商标
商标的目的是标识个人或公司的货物和服务,以免造成混淆。作为其名称和标志的注册所有者,Redis接受其商标的某些有限使用,但必须遵循其在商标指南中描述的要求。
Redis内部结构
如果您正在阅读此README,您可能正面对一个GitHub页面,或者您刚刚解压缩了Redis发行版tar球。在这两种情况下,您基本上离源代码只有一步之遥,因此这里我们解释Redis源代码布局、每个文件中的内容以及Redis服务器中的最重要功能和结构等。我们保持所有讨论都在高层次上,不深入细节,因为否则这份文件会非常大,而且我们的代码库不断变化,但一个总体概念应该是一个良好的起点,以便更多地了解。此外,大部分代码都有详细的注释,易于理解。
源代码布局
Redis根目录仅包含此README、调用src
目录中真实Makefile的Makefile以及Redis和Redis Sentinel的示例配置。您可以找到一些用于执行Redis、Redis集群和Redis Sentinel单元测试的shell脚本,这些测试在tests
目录中实现。
根目录中包含以下重要目录:
src
:包含用C语言编写的Redis实现。tests
:包含用Tcl实现的单元测试。deps
:包含Redis使用的库。编译Redis所需的一切都在这个目录中;您的系统只需要提供libc
、POSIX兼容接口和C编译器。值得注意的是,deps
包含一份jemalloc
的副本,这是Linux下Redis的默认分配器。请注意,在deps
下还有一些从Redis项目开始但主存储库不是redis/redis
的东西。
还有一些其他的目录,但它们对我们的目标来说并不重要。我们将主要关注src
,Redis实现包含在其中,探索每个文件中的内容。文件暴露的顺序是逻辑上的,以便逐步揭示不同层次的复杂性。
注意:最近Redis被重构了很多。函数名和文件名已经更改,所以您可能会发现这份文档更接近unstable
分支。例如,在Redis 3.0中,server.c
和server.h
文件被命名为redis.c
和redis.h
。但总体结构是相同的。请记住,所有的新开发和拉取请求都应该针对unstable
分支。
server.h
理解程序如何工作最简单的方法是理解它使用的数据结构。因此,我们将从Redis的主头文件server.h
开始。
所有服务器配置和一般所有共享状态都定义在一个名为server
的全局结构中,类型为struct redisServer
。这个结构中的一些重要字段是:
server.db
是Redis数据库的数组,数据存储在这里。server.commands
是命令表。server.clients
是连接到服务器的客户端的链表。server.master
是一个特殊的客户端,主节点,如果实例是副本。
还有很多其他字段。大多数字段都在结构定义中直接注释。
另一个重要的Redis数据结构是定义客户端的结构。
过去它被称为redisClient
,现在只是client
。该结构有很多字段,这里我们只展示主要的:
struct client {int fd;sds querybuf;int argc;robj **argv;redisDb *db;int flags;list *reply;// ...很多其他字段...char buf[PROTO_REPLY_CHUNK_BYTES];
}
客户端结构定义了一个连接的客户端:
fd
字段是客户端套接字文件描述符。argc
和argv
用客户端正在执行的命令填充,以便实现特定Redis命令的函数可以读取参数。querybuf
累积来自客户端的请求,这些请求由Redis服务器根据Redis协议解析并执行客户端正在执行的命令。reply
和buf
是动态和静态缓冲区,累积服务器发送给客户端的回复。这些缓冲区一旦文件描述符可写,就会逐步写入套接字。
正如您在上述客户端结构中看到的,命令中的参数被描述为robj
结构。以下是完整的robj
结构,它定义了一个Redis对象:
struct redisObject {unsigned type:4;unsigned encoding:4;unsigned lru:LRU_BITS; /* LRU时间(相对于全局lru_clock)或* LFU数据(最低有效8位频率* 和最高有效16位访问时间)。 */int refcount;void *ptr;
};
基本上,这个结构可以表示所有基本的Redis数据类型,如字符串、列表、集合、有序集合等。有趣的事情是它有一个type
字段,这样我们就可以知道给定对象的类型,还有一个refcount
,这样同一个对象就可以在多个地方被引用,而不需要多次分配。最后,ptr
字段指向对象的实际表示,这可能即使对于同一类型,也会根据使用的encoding
而异。
Redis对象在Redis内部广泛使用,但为了避免间接访问的开销,最近在许多地方我们只是使用没有包装在Redis对象中的普通动态字符串。
server.c
这是Redis服务器的入口点,定义了main()
函数。以下是启动Redis服务器的最重要步骤。
initServerConfig()
设置server
结构的默认值。initServer()
分配所需的数据结构,设置监听套接字等。aeMain()
启动事件循环,监听新连接。
事件循环会定期调用两个特殊函数:
serverCron()
定期调用(根据server.hz
频率),执行必须不时执行的任务,如检查超时的客户端。beforeSleep()
每次事件循环触发,Redis服务了一些请求,并返回到事件循环时调用。
在server.c中,您可以找到处理Redis服务器其他重要事项的代码:
call()
用于在给定客户端的上下文中调用给定命令。activeExpireCycle()
处理通过EXPIRE
命令设置的具有生存时间的键的驱逐。performEvictions()
在应该执行新的写命令但Redis根据maxmemory
指令内存不足时调用。- 全局变量
redisCommandTable
定义了所有Redis命令,指定了命令的名称、实现命令的函数、所需的参数数量以及每个命令的其他属性。
commands.c
这个文件是由utils/generate-command-code.py自动生成的,内容基于src/commands文件夹中的JSON文件。
这些应该是关于Redis命令的唯一真相来源,以及它们的所有元数据。
这些JSON文件不打算被任何人直接使用,而是可以通过COMMAND
命令获得元数据。
networking.c
这个文件定义了与客户端、主节点和副本(在Redis中只是特殊客户端)的所有I/O函数:
createClient()
分配并初始化一个新客户端。addReply*()
系列函数由命令实现用来向客户端结构追加数据,这些数据将作为对给定执行命令的回复传输给客户端。writeToClient()
将输出缓冲区中的数据传输给客户端,并由可写事件处理器sendReplyToClient()
调用。readQueryFromClient()
是可读事件处理器,并将从客户端读取的数据累积到查询缓冲区。processInputBuffer()
是解析客户端查询缓冲区的入口点,根据Redis协议。一旦命令准备好被处理,它调用定义在server.c
中的processCommand()
来实际执行命令。freeClient()
释放、断开连接并移除客户端。
aof.c和rdb.c
正如您从名称中猜测的,这些文件实现了Redis的RDB和AOF持久性。Redis使用基于fork()
系统调用的持久性模型,以创建一个与主Redis进程具有相同(共享)内存内容的进程。这个辅助进程将内存内容转储到磁盘上。这被rdb.c
用来在磁盘上创建快照,以及aof.c
用来在追加文件变得太大时执行AOF重写。
aof.c
中的实现还有额外的函数,以便实现一个API,允许命令在客户端执行时将新命令追加到AOF文件中。
在server.c
中定义的call()
函数负责调用函数,这些函数反过来将命令写入AOF。
解释:
这段话描述的是Redis数据库的持久性机制,具体来说,是如何通过操作系统的fork()
系统调用来实现数据的持久化。下面是对这段话的详细解释:
- 基于
fork()
系统调用的持久性模型:- Redis使用
fork()
系统调用来创建一个新的进程,这个新进程是主Redis进程的一个副本,它们共享相同的内存空间。这种机制允许Redis在不干扰主进程处理客户端请求的情况下,进行数据持久化操作。 fork()
是一个在 Unix 和类 Unix 操作系统中使用的系统调用,它用于创建一个新的进程,称为子进程,这个子进程是调用fork()
的原始进程(父进程)的一个副本。这个系统调用的特点是它在父进程中返回子进程的进程ID,在子进程中返回0。
- Redis使用
简单来说,fork()
函数的作用是复制当前进程,创建一个新的进程,这个新进程几乎与原进程完全相同,包括代码段、数据段和堆栈等。这使得 fork()
在多进程编程中非常有用,尤其是在需要并行处理任务或者创建守护进程时。
在 fork()
调用之后,父进程和子进程将从 fork()
调用之后的代码点继续执行,但它们将有不同的进程ID,并且可以独立地执行不同的代码路径。这允许父进程和子进程执行不同的任务,或者子进程可以执行特定的任务而不影响父进程。
-
创建具有相同(共享)内存内容的进程:
- 当Redis执行
fork()
操作时,它会创建一个子进程,这个子进程拥有与父进程(主Redis进程)相同的内存内容。这意味着子进程可以访问父进程的所有数据,包括内存中的数据结构。
- 当Redis执行
-
辅助进程将内存内容转储到磁盘上:
- 这个子进程(辅助进程)的职责是将内存中的数据转储到磁盘上。这样做的目的是为了在系统发生故障时能够恢复数据,确保数据的持久性。
-
rdb.c用来在磁盘上创建快照:
- Redis的RDB持久性机制是通过
rdb.c
这个文件实现的。RDB(Redis Database)是一种将Redis内存中的数据以快照的形式保存到磁盘上的机制。这个快照文件包含了某一时刻Redis数据库的状态,可以在Redis重启时用来恢复数据。
- Redis的RDB持久性机制是通过
-
aof.c用来在追加文件变得太大时执行AOF重写:
- AOF(Append Only File)是Redis的另一种持久性机制,它通过记录每次写操作来保存数据变更。随着时间的推移,AOF文件可能会变得非常大,这可能会影响Redis的性能。
aof.c
文件实现了AOF重写功能,它可以创建一个新的AOF文件,只包含恢复数据所需的最小命令集,从而减小AOF文件的大小。
- AOF(Append Only File)是Redis的另一种持久性机制,它通过记录每次写操作来保存数据变更。随着时间的推移,AOF文件可能会变得非常大,这可能会影响Redis的性能。
总结来说,这段话描述了Redis如何通过fork()
系统调用来创建一个辅助进程,这个进程负责将内存中的数据持久化到磁盘上,以实现数据的快照保存(RDB)和AOF文件的重写,从而保证数据的持久性和一致性。
“堆栈”(stack)是计算机内存中的一个区域,用于存储程序运行时的临时数据。它是一种特殊的数据结构,遵循后进先出(LIFO,Last In First Out)的原则,即最后添加到堆栈的数据会最先被移除。堆栈在程序执行过程中扮演着重要的角色,主要功能包括:
-
函数调用和返回:当程序调用一个函数时,相关的返回地址和局部变量会被压入堆栈中。当函数执行完毕后,堆栈会弹出这些信息,以便程序能够返回到正确的位置继续执行。
-
局部变量存储:函数内部定义的局部变量通常存储在堆栈中。
-
表达式求值:在计算表达式时,中间结果可能会被存储在堆栈中。
-
异常处理:当程序抛出异常时,异常处理相关的信息也会被存储在堆栈中。
在多进程编程中,fork()
函数创建的新进程会复制父进程的堆栈,这意味着新进程会继承父进程在堆栈中的所有数据,包括局部变量和函数调用的上下文信息。这对于进程间的信息传递和任务的并行处理非常重要。
“堆栈”这个术语在计算机科学中有两种不同的含义,它们分别对应于数据结构和内存管理两个领域:
-
数据结构中的栈(Stack):
- 在数据结构中,栈是一种线性数据结构,遵循后进先出(LIFO)的原则。这意味着最后添加的元素将是第一个被移除的元素。
- 栈的基本操作通常包括
push
(入栈,添加元素到栈顶)和pop
(出栈,移除栈顶元素)。 - 栈常用于解决需要回溯的问题,如函数调用栈、括号匹配、表达式求值等。
-
内存管理中的堆栈(Heap and Stack):
- 在内存管理中,"堆栈"通常指的是两个不同的区域:堆(Heap)和栈(Stack)。
- 栈(Stack):如前所述,用于存储程序运行时的临时数据,如函数调用的上下文信息、局部变量等,遵循LIFO原则。
- 堆(Heap):用于动态内存分配,程序可以在运行时请求任意大小的内存块,并在使用完毕后释放。堆不遵循特定的顺序原则,它允许在任何位置添加和移除元素,但通常需要手动管理内存(分配和释放)。
所以,当提到“堆栈”时,如果是在讨论数据结构,那么它指的是栈;如果是在讨论内存管理,那么它可能指的是堆和栈两个不同的概念。在上下文中,需要根据具体语境来确定“堆栈”指的是哪一个。
db.c
某些Redis命令操作特定数据类型;其他是通用的。
通用命令的例子是DEL
和EXPIRE
。它们操作键而不是具体值。所有这些通用命令都定义在db.c
中。
此外,db.c
实现了一个API,以便在不直接访问内部数据结构的情况下对Redis数据集执行某些操作。
db.c
中许多命令实现中使用的最重要的函数如下:
lookupKeyRead()
和lookupKeyWrite()
用于获取与给定键关联的值的指针,如果键不存在则为NULL
。dbAdd()
及其高级对应setKey()
在Redis数据库中创建一个新键。dbDelete()
移除一个键及其关联的值。emptyData()
移除一个整个数据库或所有定义的数据库。
文件的其余部分实现了暴露给客户端的通用命令。
object.c
定义Redis对象的robj
结构已经被描述过。在object.c
中,有所有在基本层面上操作Redis对象的函数,如分配新对象、处理引用计数等。此文件中的著名函数:
incrRefCount()
和decrRefCount()
用于增加或减少对象的引用计数。当它降到0时,对象最终被释放。createObject()
分配一个新对象。还有一些专门分配具有特定内容的字符串对象的专门函数,如createStringObjectFromLongLong()
等。
此文件还实现了OBJECT
命令。
replication.c
这是Redis中最复杂的文件之一,建议在熟悉代码库的其余部分后再接触它。
在这个文件中,有Redis的主节点和副本角色的实现。
此文件中最重要的函数之一是replicationFeedSlaves()
,它将命令写入代表连接到我们主节点的副本实例的客户端,以便副本可以获得客户端执行的写入:
这样它们的数据集将与主节点中的数据集保持同步。
此文件还实现了SYNC
和PSYNC
命令,这些命令用于在主节点和副本之间执行首次同步,或在断开连接后继续复制。
脚本
脚本单元由3个单元组成:
script.c
- 脚本与Redis的集成(命令执行,设置复制/resp,…)script_lua.c
- 负责执行Lua代码,使用script.c
在Lua代码内与Redis交互。function_lua.c
- 包含Lua引擎实现,使用script_lua.c
执行Lua代码。functions.c
- 包含Redis函数实现(FUNCTION
命令),如果它想要调用的函数需要Lua引擎,则使用functions_lua.c
。eval.c
- 包含使用script_lua.c
调用Lua代码的eval
实现。
其他C文件
t_hash.c
、t_list.c
、t_set.c
、t_string.c
、t_zset.c
和t_stream.c
包含Redis数据类型的实现。它们实现了访问给定数据类型的API,并实现了这些数据类型的客户端命令。ae.c
实现了Redis事件循环,它是一个自包含的库,简单易懂。sds.c
是Redis字符串库,更多信息请访问https://github.com/antirez/sds。anet.c
是一个库,用于以比内核暴露的原始接口更简单的方式使用POSIX网络。dict.c
是一个非阻塞哈希表的实现,它增量地重新哈希。cluster.c
实现了Redis集群。可能只有在熟悉了Redis代码库的其余部分之后才值得一读。如果您想阅读cluster.c
,请确保阅读Redis Cluster规格。
Redis命令的剖析
所有Redis命令都以以下方式定义:
void foobarCommand(client *c) {printf("%s",c->argv[1]->ptr); /* 对参数进行一些操作。 */addReply(c,shared.ok); /* 向客户端回复一些内容。 */
}
命令函数由JSON文件引用,连同其元数据,详见上述的commands.c
。
命令标志在server.h
中的struct redisCommand
上面的注释中记录。
有关其他详细信息,请参阅COMMAND
命令。https://redis.io/commands/command/
命令以某种方式操作后,它通常会使用addReply()
或networking.c
中定义的类似函数向客户端返回回复。
在Redis源代码中有大量的命令实现可以作为实际命令实现的示例(例如pingCommand)。编写一些玩具命令可以是熟悉代码库的好练习。
这里没有描述的文件还有很多,但涵盖一切是无用的。我们只想帮助您迈出第一步。
最终,您会找到进入Redis代码库的路:-)
享受吧!
这是翻译后的内容。如果您需要将这个内容保存为新文件,请告知我文件的名称和格式,我将为您生成。