什么是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可以在Linux、OSX、OpenBSD、NetBSD、FreeBSD上编译和使用。我们支持大终端和小终端架构,以及32位和64位系统。
它可以在Solaris派生的系统(例如SmartOS)上编译,但是我们对这个平台的支持是尽最大努力而且Redis不能保证在Linux、OSX和*BSD中工作得很好。
它很简单,就像:
% make
要使用TLS支持进行构建,您需要OpenSSL开发库(例如,Debian/Ubuntu上的libssl-dev)并运行:
% make BUILD_TLS=yes
要使用systemd支持进行构建,您将需要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位目标重新构建它,或者反过来,则需要执行make distclean
在Redis发行版的根目录中。
如果在尝试构建Redis的32位二进制文件时出现生成错误,请尝试以下步骤:
- 安装libc6-dev-i 386包(也尝试g++-multilib)。
- 尝试使用以下命令行代替
make 32bit
:make CFLAGS="-m32 -march=native" LDFLAGS="-m32"
分配器
生成Redis时,通过设置MALLOC
环境变量默认情况下,redis是针对libcmalloc编译和链接的,但jemalloc是Linux系统上的缺省值。之所以选择这个默认值,是因为jemalloc比libcmalloc有更少的碎片问题。
若要强制针对libc malloc进行编译,请使用:
% make MALLOC=libc
若要在MacOSX系统上针对jemalloc进行编译,请使用:
% make MALLOC=jemalloc
单调时钟
默认情况下,Redis将使用POSIX Clock_gettime函数作为单调时钟源进行构建。在大多数现代系统中,内部处理器时钟可以用来提高性能。请注意:Http://oliveryang.net/2015/09/pitfalls-of-TSC-usage/
若要在支持处理器内部指令时钟的情况下进行构建,请使用:
% 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.md有关如何在TLS中使用Redis的更多信息,请参见文件。
和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
redis>
您可以在Https://redis.io/commands.
安装Redis
为了将Redis二进制文件安装到/usr/local/bin中,只需使用:
% make install
你可以用make PREFIX=/some/other/directory install
如果你想使用不同的目的地。
Makeinstall将只在系统中安装二进制文件,但不会在适当的位置配置init脚本和配置文件。如果您只想使用Redis,就不需要这样做,但是如果您是以适合生产系统的方式安装它,那么我们有一个脚本,用于Ubuntu和Debian系统:
% cd utils
% ./install_server.sh
注: install_server.sh
不会在MacOSX上工作;它只为Linux构建。
脚本将问您几个问题,并将正确地将运行Redis所需的一切设置为后台守护进程,在系统重新启动时重新启动。
您可以使用名为/etc/init.d/redis_<portnumber>
例如,/etc/init.d/redis_6379
.
源代码布局
Redis根目录只包含这个自述文件,Makefile在src
目录以及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
分支更近。例如,在Redis3.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;
// ... many other fields ...
char buf[PROTO_REPLY_CHUNK_BYTES];
}
客户端结构定义了连接客户端:
- 这个
fd
字段是客户端套接字文件描述符。 -
argc
和argv
由客户端正在执行的命令填充,以便实现给定Redis命令的函数可以读取参数。 -
querybuf
累积来自客户端的请求,这些请求由Redis服务器根据Redis协议解析,并通过调用客户端正在执行的命令的实现来执行。 -
reply
和buf
是动态和静态缓冲区,用于累积服务器发送给客户端的答复。一旦文件描述符可写,这些缓冲区就会增量地写入套接字。
正如您在上面的客户端结构中所看到的,命令中的参数被描述为robj
结构。以下是全文robj
结构,它定义了红系物体:
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */
int refcount;
void *ptr;
} robj;
基本上,这个结构可以表示所有基本的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()
当执行新的写命令时调用,但根据maxmemory
指令。 - 全局变量
redisCommandTable
定义所有Redis命令,指定命令的名称、实现命令的函数、所需参数的数量以及每个命令的其他属性。
Networking.c
该文件定义了所有带有客户端、主服务器和副本的I/O函数(在Redis中只是特殊的客户端):
-
createClient()
分配和初始化新客户端。 - 这个
addReply*()
命令实现使用一系列函数,以便将数据附加到客户端结构中,这些数据将作为执行的给定命令的应答发送到客户端。 -
writeToClient()
将输出缓冲区中挂起的数据传输到客户端,并由可写事件处理程序sendReplyToClient()
. -
readQueryFromClient()
是可读事件处理程序并将从客户端读取的数据积累到查询缓冲区中。 -
processInputBuffer()
是根据Redis协议解析客户端查询缓冲区的入口点。一旦准备好处理命令,它就会调用processCommand()
里面定义的server.c
以便实际执行命令。 -
freeClient()
释放、断开和移除客户端。
Aof.c和rdb.c
从名称中可以猜到,这些文件为Redis实现了RDB和AOF持久性。的持久化模型。fork()
系统调用,以创建具有主Redis进程相同(共享)内存内容的进程。此辅助进程转储磁盘上内存的内容。这是由rdb.c
若要在磁盘上创建快照,请执行以下操作aof.c
为了执行AOF重写时,只追加的文件变得太大。
内部实施aof.c
具有额外的功能,以便实现一个API,该API允许命令在客户端执行时将新命令附加到AOF文件中。
这个call()
内部定义的函数server.c
负责调用函数,然后将命令写入AOF。
Db.c
某些Redis命令对特定的数据类型进行操作;其他命令则是通用的。泛型命令的示例包括DEL
和EXPIRE
。它们操作的是键,而不是特定的值。所有这些通用命令都是在内部定义的。db.c
.
而且db.c
实现API,以便在不直接访问内部数据结构的情况下对Redis数据集执行某些操作。
最重要的功能db.c
在许多命令实现中使用的命令如下:
-
lookupKeyRead()
和lookupKeyWrite()
用于获取指向与给定键关联的值的指针,或NULL
如果密钥不存在。 -
dbAdd()
及其高级对应方setKey()
在Redis数据库中创建一个新键。 -
dbDelete()
移除键及其关联值。 -
emptyDb()
移除整个单个数据库或定义的所有数据库。
文件的其余部分实现了向客户端公开的通用命令。
Object.c
这个robj
已经描述了定义Redis对象的结构。内object.c
在基本级别上,有所有与Redis对象一起操作的函数,比如分配新对象、处理引用计数等的函数。此文件中值得注意的功能:
-
incrRefCount()
和decrRefCount()
用于增加或减少对象引用计数。当它下降到0时,对象最终被释放。 -
createObject()
分配一个新对象。还有一些特殊的函数来分配具有特定内容的字符串对象,如createStringObjectFromLongLong()
以及类似的功能。
此文件还实现了OBJECT
命令。
Replication.c
这是Redis中最复杂的文件之一,只有在熟悉了其他代码库之后才建议使用它。在这个文件中,实现了Redis的主角色和副本角色。
该文件中最重要的功能之一是replicationFeedSlaves()
它将命令写入表示连接到我们的主服务器的副本实例的客户端,以便副本能够得到客户端执行的写入:这样,它们的数据集将与主服务器中的数据集保持同步。
此文件还实现了两个SYNC
和PSYNC
用于在主程序和副本之间执行第一次同步或在断开连接后继续复制的命令。
其他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
是一个非阻塞哈希表的实现,它递增地重新散列。 -
scripting.c
实现Lua脚本。它是完全独立的,独立于Redis实现的其余部分,如果您熟悉LuaAPI,那么它就足够简单了。 -
cluster.c
实现Redis群集。在非常熟悉Redis代码库的其余部分之后,可能是一次很好的阅读。如果你想读cluster.c
确保阅读Redis集群规范.
Redis命令剖析
所有Redis命令的定义方式如下:
void foobarCommand(client *c) {
printf("%s",c->argv[1]->ptr); /* Do something with the argument. */
addReply(c,shared.ok); /* Reply something to the client. */
}
然后在内部引用该命令。server.c
在命令表中:
{"foobar",foobarCommand,2,"rtF",0,NULL,0,0,0,0,0},
在上面的例子中2
命令使用的参数数,而"rtF"
是否有命令标志,如命令表顶部注释中所记录的server.c
.
命令以某种方式操作后,它会向客户端返回一个答复,通常使用addReply()
或内部定义的类似函数networking.c
.
Redis源代码中有大量的命令实现可以作为实际命令实现的示例。编写一些玩具命令对于熟悉代码库来说是一个很好的练习。
还有许多其他文件没有在这里描述,但它是无用的涵盖一切。我们只是想帮你迈出第一步。最终,您将在Redis代码库中找到自己的方法:-)