关于redis:Reids-源码导读

36次阅读

共计 7999 个字符,预计需要花费 20 分钟才能阅读完成。

本 README 只是一个疾速入门文档。你能够在 redis.io 找到更具体的文档。

什么是 Redis?

Redis 通常被称为数据结构服务器。这意味着 Redis 通过一组命令提供对可变数据结构的拜访,这些命令应用带有 TCP 套接字和简略协定的服务器 - 客户端模型发送。因而不同的过程能够以共享的形式查问和批改雷同的数据结构。
Redis 中实现的数据结构有一些非凡的属性:

  • 尽管 Redis 通常被当做内存数据库,读写操作都是在内存中进行的,然而 Redis 也提供了将数据同步到磁盘的性能。这意味着 Redis 速度很快,但它也是非易失的。
  • Redis 数据结构的实现强调内存效率(工夫 & 空间),因而与应用高级编程语言实现的雷同数据结构相比,Redis 外部的数据结构可能会应用更少的内存。
  • Redis 提供了许多数据库软件常见个性,例如复制、可调整级别的持久性、集群和高可用性。

另一个很好的例子是将 Redis 视为更简单的 memcached 版本,其中的操作不仅是 SET GET,而且还实用于简单数据类型(如 ListSet、有序数据结构等)的操作。

如果您想理解更多信息,能够参阅上面链接:

  • Redis 数据类型简介。https://redis.io/topics/data-…
  • 间接在浏览器中试用 Redis。https://try.redis.io
  • Redis 命令的残缺列表。https://redis.io/commands

Redis 官网文档中还有更多内容。https://redis.io/documentation

构建 Redis

Redis 能够在 Linux、OSX、OpenBSD、NetBSD、FreeBSD 上编译和应用。咱们反对大端和小端架构,以及 32 位和 64 位零碎。
Redis 能够在 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 disclean

这将清理:jemallocluahiredislinenoise

此外,如果您强制应用某些构建选项,如 32 位包、无 C 编译器优化(用于调试目标)以及其余相似的构建工夫选项,这些选项将无限期缓存,直到您收回 make distclean 命令。

修复构建 32 位二进制文件的问题

如果在应用 32 位指标构建 Redis 后,又须要应用 64 位指标从新构建它,或者相同,您须要在 Redis 发行版的根目录中执行 make distclean

如果在尝试构建 Redis 的 32 位二进制文件时呈现构建谬误,请尝试以下步骤:

  • 装置软件包 libc6-dev-i386(也能够尝试 g++-multilib)。

尝试应用以下命令行而不是 make 32bitmake 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 中的所有选项也反对作为应用命令行的选项,名称完全相同。

应用 TLS 运行 Redis

无关如何将 Redis 与 TLS 联合应用的更多信息,请参阅 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
redis>

您能够在 https://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

留神:install_server.sh 不能在 Mac OSX 上运行;它仅实用于 Linux。

该脚本将询问几个问题,并实现相干配置设置,而后作为守护过程启动 Redis。该后盾守护程序将在零碎重新启动时重新启动。

能够应用名为 /etc/init.d/redis_<portnumber> 的脚本来进行和启动 Redis,例如 /etc/init.d/redis_6379

代码奉献

留神:通过以任何模式向 Redis 我的项目奉献代码,包含通过 Github 发送拉取申请、通过私人电子邮件或公共讨论组发送代码片段或补丁,即示意您批准依据 BSD 许可条款(您能够在 Redis 源代码散发包中的 COPYING 文件中找到)公布您的代码。

无关详细信息,请参阅此源散发中的 CONTRIBUTING 文件。

Redis 内部结构

如果您正在浏览此自述文件,您可能在 Github 页面后面,或者您只是解压了 Redis 散发 tar 包。在这两种状况下,你基本上离源代码只有一步之遥,所以在这里咱们解释一下 Redis 源代码的布局、每个文件中的大抵概念、Redis 服务器外部最重要的性能和构造等等。这里将所有探讨放弃在高层次,而不深刻细节,因为否则该文档会很大,而且代码库会一直变动,但总体思路应该是理解更多内容的良好终点。此外,大部分代码都通过大量正文并且易于了解。

源代码布局

Redis 根目录只蕴含这个 README、调用 src 目录中真正的 MakefileMakefile 以及 Redis 和 Sentinel 的示例配置。以及一些用于执行 Redis、Redis Cluster 和 Redis Sentinel 单元测试的 shell 脚本,它们在 tests 目录中实现。

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

  • src:蕴含 Redis 实现,用 C 编写。
  • tests:蕴含单元测试,在 Tcl 中实现。
  • deps:蕴含 Redis 应用的库。编译 Redis 所需的所有依赖都在这个目录中;零碎只须要提供 libc、一个 POSIX 兼容的接口和一个 C 编译器。值得注意的是 deps 蕴含一个 jemalloc 的正本,它是 Linux 下 Redis 的默认分配器。请留神,在 deps 下还有一些货色是从 Redis 我的项目开始的,但主存储库不是 redis/redis

还有一些目录,但它们对于咱们在这里的指标并不是很重要。咱们将次要关注蕴含 Redis 实现的 src,摸索每个文件中的内容。文件展现的程序是为了逐渐揭示不同档次的复杂性而遵循的逻辑程序。

留神:最近 Redis 被重构了很多。函数名和文件名已更改,因而您可能会发现此文档更靠近地反映了不稳固的分支。例如,在 Redis 3.0 中,server.c server.h 文件被命名为 redis.credis.h。然而整体构造是一样的。请记住,所有新的开发和拉取申请都应该针对不稳固的分支执行。

server.h

理解程序如何工作的最简略办法是理解它应用的数据结构。因而,咱们将从 Redis 的主头文件 server.h 开始。

所有的服务器配置和通常所有的共享状态都定义在一个名为 server 的全局构造中,类型为 struct redisServer。此构造中的几个重要字段是:

  • server.db 是存储数据的 Redis 数据库数组。
  • server.commands 是命令表。
  • server.clients 是连贯到服务器的客户端的链表。
  • server.master 是一个非凡的客户端,如果实例是正本,则为 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];
  //... many other fields ...
}

客户端构造定义了一个连贯的客户端:

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

正如您在下面的客户端构造中所见,命令中的参数被形容为 robj 构造。上面是残缺的 robj 构造,它定义了一个 Redis object

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() 设置服务器构造的默认值。
  • initServer() 调配操作、设置侦听套接字等所需的数据结构。
  • aeMain() 启动监听新连贯的事件循环。

事件循环会定期调用两个非凡函数:

  • serverCron() 定期调用(依据 server.hz 频率),并执行必须定期执行的工作,例如查看客户端是否超时。
  • 每次触发事件循环时都会调用 beforeSleep(),Redis 服务了一些申请,而后返回到事件循环中。

server.c 中,您能够找到解决 Redis 服务器其余重要事项的代码:

  • call() 用于在给定客户端的上下文中调用给定命令。
  • activeExpireCycle() 通过 EXPIRE 命令解决具备生存工夫的键的删除。
  • freeMemoryIfNeeded() 在执行新的写入命令,但依据 maxmemory 指令,判断 Redis 内存不足时调用。
  • 全局变量 redisCommandTable 定义了所有 Redis 命令,指定命令的名称、实现命令的函数、所需的参数数量以及每个命令的其余属性。

network.c

该文件定义了所有带有客户端、主服务器和正本(在 Redis 中只是非凡客户端)的 I/O 性能:

  • createClient() 调配并初始化一个新客户端。
  • 命令实现逻辑调用 addReply*() 系列函数来将数据附加到客户端构造,这些数据将作为对执行给定命令的回复传输给客户端。
  • writeToClient() 可写事件处理程序 sendReplyToClient() 调用, 将输入缓冲区中数据传输到客户端。
  • readQueryFromClient() 可读的事件处理程序,将从客户端读取的数据缓存到查问缓冲区中。
  • processInputBuffer() 是依据 Redis 协定解析客户端查问缓冲区的入口点。一旦筹备好解决命令,它就会调用在 server.c 中定义的 processCommand() 以理论执行命令。
  • freeClient() 开释、断开和删除客户端。

aof.c 和 rdb.c

从名称中您能够猜到,这些文件实现了 Redis 的 RDBAOF 持久性。Redis 应用基于 fork() 零碎调用的持久性模型,以便创立与主 Redis 线程具备雷同(共享)内存内容的线程。此辅助线程将内存内容转储到磁盘。rdb.c 应用它在磁盘上创立快照,aof.c 应用它来在仅追加文件变得太大时执行 AOF 重写。

aof.c 中的实现具备附加性能,以便实现一个 API,该 API 容许命令在客户端执行它们时将新命令附加到 AOF 文件中。

在 server.c 中定义的 call() 函数负责调用这些函数,这些函数又会将命令写入 AOF。

db.c

某些 Redis 命令对特定数据类型进行操作;其余都是通用命令。通用命令的示例是 DEL 和 EXPIRE。它们作用于键而不是专门作用于它们的值。所有这些通用命令都在 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 的 masterreplica 角色的实现。

  • 该文件中最重要的函数之一是 replicationFeedSlaves(),它向代表连贯到主节点的正本实例的客户端写入命令,以便正本能够获取客户端执行的写入操作:这样他们的数据集将与 master 保持一致。
  • 该文件还实现了 SYNCPSYNC 命令,这些命令用于在主服务器和正本之间执行第一次同步,或者在断开连接后持续复制。

其余 C 文件

  • t_hash.ct_list.ct_set.ct_string.ct_zset.ct_stream.c 蕴含 Redis 数据类型的实现。它们既实现了拜访给定数据类型的 API,又实现了这些数据类型的客户端命令实现。
  • ae.c 实现了 Redis 事件循环,它是一个易于浏览和了解的自蕴含库。
  • sds.c 是 Redis 字符串库,查看 http://github.com/antirez/sds 理解更多信息。
  • anet.c 网络库,与内核公开的原始接口相比,它以更简略的形式应用 POSIX 网络。
  • dict.c 是一个非阻塞哈希表的实现,它以增量形式rehash
  • scripting.c 实现 Lua 脚本。它是齐全独立的,并且与 Redis 实现的其余部分隔离开来,如果您相熟 Lua API,就很容易了解。
  • cluster.c 实现了 Redis 集群。可能只有在十分相熟 Redis 代码库的其余部分之后能力浏览。如果你想浏览 cluster.c 确保浏览 Redis 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 代码库 :-)

Enjoy!

正文完
 0