本自述文件只是一份快速入门的文档。您可以在[redis.io]上找到更详细的文档(https://redis.io)

什么是Redis?

Redis通常被称为“数据结构”服务器。这意味着Redis通过一组命令提供对可变数据结构的访问,这些命令使用带有TCP套接字和简单协议的服务器-客户端模型发送。因此,不同的进程可以以共享的方式查询和修改相同的数据结构。

Redis中实现的数据结构有几个特殊的财产:

  • Redis愿意将它们存储在磁盘上,即使它们总是被提供并修改到服务器内存中。这意味着Redis速度很快,但也是非易失性的。
  • 数据结构的实现强调内存效率,因此与使用高级编程语言建模的相同数据结构相比,Redis内部的数据结构可能使用更少的内存。
  • Redis提供了许多在数据库中很容易找到的功能,如复制、可调的耐用性、集群、高可用性。

另一个很好的例子是将Redis视为memcached的一个更复杂的版本,其中的操作不仅仅是SET和GET,而是用于处理复杂数据类型(如列表、集合、有序数据结构等)的操作。

如果您想了解更多信息,以下是所选起点的列表:

构建Redis

Redis可以在Linux、OSX、OpenBSD、NetBSD、FreeBSD上编译和使用。我们支持big-endian和little-endian架构,并且都支持32位和64位系统。

它可能在Solaris派生系统(例如SmartOS)上编译,但我们对该平台的支持是“尽最大努力”,Redis不能保证在Linux、OSX和BSD中也能很好地工作。

简单命令:

% make

您可以使用以下方法运行32位Redis二进制文件:

% make 32bit

在构建Redis之后,最好使用以下方法对其进行测试:

% make test

修复依赖项或缓存生成选项的生成问题

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-i386(也可以尝试g++-multlib)。
  • 请尝试使用以下命令行,而不是 make 32bit: make CFLAGS="-m32 -march=native" LDFLAGS="-m32"

分配器

在构建Redis时选择非默认内存分配器是通过设置MALLOC环境变量。Redis是根据libc编译和链接的默认情况下为malloc,但jemalloc是Linux上的默认值系统。之所以选择此默认值,是因为事实证明jemalloc的与libc-malloc相比,存在碎片问题。

要强制编译libc-malloc,请使用:

% make MALLOC=libc

要在Mac OS X系统上针对jemalloc进行编译,请使用:

% make MALLOC=jemalloc

详细生成

默认情况下,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中的所有选项也支持作为使用命令的选项行,名称完全相同。

使用Redis

您可以使用redis-cli来播放redis。启动一个redis服务器实例,则在另一个终端中尝试以下操作:

% 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>

您可以在以下位置找到所有可用命令的列表:http://redis.io/commands.

安装Redis

要将Redis二进制文件安装到/usr/local/bin中,只需使用:

% make install

您可以使用 make PREFIX=/some/other/directory install 如果您希望使用不同的安装目录。

Make-install只会在系统中安装二进制文件,但不会配置在适当的位置初始化脚本和配置文件。这不是如果你只想玩一点Redis,但如果你正在安装这是生产系统的正确方式,我们有一个脚本来做这件事对于Ubuntu和Debian系统:

% cd utils
% ./install_server.sh

脚本会问你几个问题,并会设置你需要的一切将Redis作为后台守护程序正确运行,该后台守护程序将在系统重新启动。

您将能够使用名为 /etc/init.d/redis_<portnumber>, 例如 /etc/init.d/redis_6379.

代码贡献

注意:通过以任何形式向Redis项目贡献代码,包括发送通过Github的拉取请求,通过私人电子邮件的代码片段或补丁,或公共讨论组,您同意根据条款发布您的代码您可以在Redis中包含的COPYING文件中找到BSD许可证的来源分布。

有关详细信息,请参阅此源发行版中的CONTRIBUTING文件信息

Redis内部

如果你正在阅读这个自述文件,你很可能在Github页面前面或者你只是解开了Redis分发的焦油球。在这两种情况下你基本上离源代码只有一步之遥,所以在这里我们解释Redis源代码布局,每个文件中的总体思想是什么Redis服务器内部最重要的功能和结构等等。我们把所有的讨论都保持在高水平,而不深入细节因为如果不是这样,这个文档将是巨大的,并且我们的代码库也会发生变化持续,但一个总体想法应该是一个很好的起点了解更多。此外,大多数代码都有大量注释,而且很容易紧随其后。

源代码布局

Redis根目录只包含这个README,即Makefile调用“src”目录中的真实Makefile和一个示例Redis和Sentinel的配置。你可以找到一些贝壳用于执行Redis、Redis Cluster和Redis Sentinel单元测试,在tests目录

根目录中有以下重要目录:

  • src: 包含用C编写的Redis实现。
  • tests: 包含在Tcl中实现的单元测试。
  • deps: 包含Redis使用的库。编译Redis所需的一切都在这个目录中;您的系统只需要提供libc,一个与POSIX兼容的接口和一个C编译器。值得注意的是,deps包含一个jemalloc的副本,它是Linux下Redis的默认分配器。请注意,在“deps”下,也有一些东西是从Redis项目开始的,但其主存储库不是antirez/Redis。这个规则的一个例外是deps/geohash-int,这是Redis使用的低级别地理编码库:它起源于另一个项目,但在这一点上,它分歧太大,以至于直接在Redis存储库中作为一个独立的实体开发。

还有一些目录,但它们对我们的目标不是很重要在这里我们将主要关注src,其中包含Redis实现,探索每个文件中的内容。文件的顺序公开是为了公开不同的层而遵循的逻辑复杂性逐渐增加。

注意:最近Redis被重构了很多。函数名称和文件名称已经更改,因此您可能会发现此文档反映了unstable 分支更紧密。例如,在Redis 3.0中,server.cserver.h文件分别命名为redis.credis.h结构是一样的。请记住,所有新的发展和拉动应针对 unstable 分支执行请求。

server.h

理解程序如何工作的最简单方法是理解它使用的数据结构。因此,我们将从Redis,即server.h.

所有服务器配置以及通常所有共享状态都是在名为server的全局结构中定义,类型为struct redisServer。该结构中的几个重要领域是:

  • server.db是存储数据的Redis数据库阵列。
  • server.commands 是命令表。
  • server.clients 是连接到服务器的客户端的链接列表。
  • server.master是一个特殊的客户端,即主机,如果实例是一个复制副本。

还有很多其他领域。大多数字段直接在内部进行注释结构定义。

Redis的另一个重要数据结构是定义客户端的数据结构。过去它被称为rediClient,现在只是client。结构有许多字段,在这里我们只显示主要字段:

struct client {
    int fd;
    sds querybuf;
    int argc;
    robj **argv;
    redisDb *db;
    int flags;
    list *reply;
    char buf[PROTO_REPLY_CHUNK_BYTES];
    ... many other fields ...
}

客户端结构定义了一个连接的客户端

  • fd字段是客户端套接字文件描述符。
  • argcargv用客户端正在执行的命令填充,这样实现给定Redis命令的函数就可以读取参数。
  • querybuf累积来自客户端的请求,这些请求由Redis服务器根据Redis协议解析,并通过调用客户端正在执行的命令的实现来执行。
  • replybuf是动态和静态缓冲区,用于累积服务器发送给客户端的回复。一旦文件描述符是可写的,这些缓冲区就会增量写入套接字。

正如您在上面的客户端结构中看到的,命令中的参数被描述为robj结构。以下是完整的robj结构,它定义了一个Redis对象

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数据类型,比如字符串、列表、集合、排序集合等等。有趣的是它有一个“类型”字段,因此可以知道给定的类型对象有,和一个refcount,以便可以引用同一个对象在多个位置,而无需多次分配。最后是ptr字段指向对象的实际表示形式,可能会有所不同即使对于相同的类型也是如此,这取决于所使用的”编码“。

Redis对象在Redis内部被广泛使用,但是按照顺序为了避免间接访问的开销,最近在许多地方我们只是使用没有封装在Redis对象中的普通动态字符串。

server.c

这是Redis服务器的入口点,其中的main() 函数定义。以下是启动最重要的步骤Redis服务器。

  • initServerConfig() 设置“服务器”结构的默认值。
  • initServer() 分配操作所需的数据结构、设置侦听套接字等等。
  • aeMain() 启动侦听新连接的事件循环。

事件循环定期调用两个特殊函数:

  1. serverCron() 定期调用(根据server.hz频率),并执行必须不时执行的任务,如检查timedout客户端。
  2. beforeSleep() 每次触发事件循环时都会调用,Redis会提供一些请求,并返回到事件循环中。

在server.c中,您可以找到处理Redis服务器其他重要事务的代码:

  • call() 用于在给定客户端的上下文中调用给定命令。
  • activeExpireCycle() 处理通过EXPIRE命令设置的具有生存时间的密钥的验证。
  • freeMemoryIfNeeded() 根据maxmemory指令,当应该执行新的写入命令但Redis内存不足时调用。
  • 全局变量redisCommandTable定义了所有Redis命令,指定了命令的名称、实现命令的函数、所需参数的数量以及每个命令的其他财产。

networking.c

此文件定义了客户端、主机和副本的所有I/O功能(在Redis中只是特殊的客户端):

  • createClient() 分配并初始化一个新的客户端。
  • the addReply*() 命令实现使用函数族,以便将数据附加到客户端结构中,这些数据将作为对所执行的给定命令的答复传输到客户端。
  • writeToClient() 将输出缓冲区中挂起的数据传输到客户端,并由可写事件处理程序sendReplyToClient()调用。
  • readQueryFromClient()可读的事件处理程序,并将从客户端读取的数据累积到查询缓冲区中。
  • processInputBuffer() 是根据Redis协议解析客户端查询缓冲区的入口点。一旦命令准备好处理,它就会调processCommand(),该命令在server.c中定义,以便实际执行命令。
  • freeClient() 解除分配、断开连接和删除客户端。

aof.c and rdb.c

从名称中可以猜到,这些文件实现了RDB和AOFRedis的持久性。Redis使用基于fork()的持久性模型系统调用,以便创建具有相同(共享)内存的线程Redis主线程的内容。此辅助线程转储内容磁盘上内存的大小。这是rdb.c用来创建快照的当仅追加文件太大。

aof.c内部的实现具有附加功能,以便实现一个API,该API允许命令将新命令附加到AOF中文件,而客户端执行它们。

server.c中定义的call()函数负责调用依次将命令写入AOF的函数。

db.c

某些Redis命令对特定的数据类型进行操作,其他命令则是通用的。通用命令的示例有DELEXPIRE。他们用钥匙操作而不是具体取决于它们的价值。所有这些通用命令都是在db.c中定义。

此外,db.c实现了一个API,以便执行某些操作在Redis数据集上,而无需直接访问内部数据结构。

在许多命令实现中使用的“db.c”中最重要的函数如下:

  • lookupKeyRead()lookupKeyWrite()用于获取指向与给定键关联的值的指针,如果该键不存在,则为NULL。
  • dbAdd()和其更高级别的对应项setKey()`在Redis数据库中创建一个新密钥。
  • dbDelete() 删除键及其关联值。
  • emptyDb()删除整个单个数据库或所有定义的数据库。

文件的其余部分实现了向客户端公开的通用命令。

object.c

已经描述了定义Redis对象的robj结构。在…内object.c有所有与Redis对象一起操作的函数一个基本级别,就像分配新对象的函数一样,处理引用计数等等。此文件中值得注意的功能:

  • incrRefcount()decrRefCount() 用于递增或递减对象引用计数。当它降到0时,对象最终被释放。
  • createObject() 分配一个新对象。还有一些专门的函数来分配具有特定内容的字符串对象,如createStringObjectFromLongLong()和类似的函数。

此文件还实现了OBJECT命令。

replication.c

这是Redis中最复杂的文件之一,建议只有在稍微熟悉了代码库的其余部分之后,才能使用它。在该文件中,同时实现了主角色和副本角色Redis的。

该文件中最重要的函数之一是replicationFeedSlaves(),它将命令写入表示连接的副本实例的客户端到我们的主机,以便副本可以获得客户端执行的写入:这样,它们的数据集将保持与主数据集中的数据集同步。

该文件还实现了SYNCPSYNC命令用于在主设备和副本,或在断开连接后继续复制。

其他C文件

  • t_hash.c, t_list.c, t_set.c, t_string.c and t_zset.c 包含Redis数据类型的实现。它们既实现了访问给定数据类型的API,也实现了这些数据类型的客户端命令实现。
  • ae.c实现Redis事件循环,这是一个自包含的库,易于阅读和理解。
  • sds.c 是Redis字符串库,请检查http://github.com/antirez/sds了解更多信息。
  • anet.c 是一个库,与内核公开的原始接口相比,它可以以更简单的方式使用POSIX网络。
  • dict.c 是一个非阻塞哈希表的实现,它以增量方式重新哈希。
  • scripting.c实现Lua脚本。它与Redis的其他实现完全独立,如果您熟悉Lua API,它也足够简单。
  • cluster.c 实现Redis集群。在非常熟悉Redis的其余代码库之后,这可能是一本很好的读物。如果您想阅读cluster.c,请确保阅读RRedis cluster规范

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代码库中找到自己的方法:-)

玩的开心!