本文章基于Redis 6.0.9版本,对Redis配置进行说明
AC: Actual Combat 实战
目录
1.什么是Redis?
Redis通常被称为数据结构服务器。 这意味着Redis通过一组命令提供对可变数据结构的访问,这些命令使用带有TCP套接字和简单协议的 server-client 模型发送。 因此,不同的进程可以以共享的方式查询和修改相同的数据结构。
在Redis中实现的数据结构具有一些特殊属性:
- 即使始终为它们提供服务并将它们修改到服务器内存中,Redis也会将它们存储在磁盘上。 这意味着Redis速度很快,但它也是非易失性的。
- 数据结构的实现强调内存效率,因此与使用高级编程语言建模的相同数据结构相比,Redis内部的数据结构将使用较少的内存。
- Redis提供了许多自然可以在数据库中找到的功能,例如复制,持久性的可调级别,集群和高可用性。
另一个很好的例子是将Redis视为memcached的一个更复杂的版本,其中的操作不仅是SET和GET,而且还适用于诸如列表,集合,有序数据结构等复杂数据类型的操作。
2.编译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
2.1.修复依赖项或缓存的构建选项的构建问题
Redis有一些依赖关系,包含在`deps`目录中。
即使依赖关系的源代码中的某些内容发生更改,`make`也不自动重建依赖关系。
当你使用 `git pull`来更新源代码或以其他方式修改依赖关系树中的代码时,请确保使用以下命令来真正清理所有内容并从头开始重建:
make distclean
这将清除: jemalloc, lua, hiredis, linenoise
同样,如果你强制使用某些构建选项,例如32位目标,没有C编译器优化(用于调试目的)以及其他类似的构建时间选项,则这些选项将无限期缓存,直到发出`make distclean`命令为止。
2.2.解决构建32位二进制文件的问题
如果在使用32位目标构建Redis之后,你需要使用64位目标进行重建,或者相反,你需要在Redis发行版的根目录中执行`make distclean`。
如果尝试构建32位二进制Redis时发生构建错误,请尝试以下步骤:
- 安装软件包libc6-dev-i386(也尝试使用g++-multilib)。
- 尝试使用`make CFLAGS="-m32 -march=native" LDFLAGS="-m32"`命令行而不是`make 32bit`:
2.3.分配器(allocator)
通过设置`MALLOC`环境变量可以在构建Redis时选择一个非默认的内存分配器。 默认情况下,Redis是针对libc malloc编译和链接的,但jemalloc是Linux系统上的默认值。 选择设置该默认值,是由于事实证明,与libc malloc相比,jemalloc的碎片问题更少。
要强制针对libc malloc进行编译,请使用:
make MALLOC=libc
要在Mac OS X系统上针对jemalloc进行编译,请使用:
make MALLOC=jemalloc
2.4.详细构建
默认情况下,Redis将使用用户友好的彩色输出进行构建。
如果要查看更详细的输出,请使用以下命令:
make V=1
7.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中的所有选项也都支持使用命令行(名称完全相同)作为选项。
7.2.使用TLS运行Redis
7.2.1.入门
7.2.1.1.编译
要使用TLS支持进行构建,你将需要OpenSSL开发库(例如Debian/Ubuntu上的libssl-dev)。
运行
make BUILD_TLS=yes
7.2.1.2.测试
要使用TLS运行Redis测试套件,你需要TCL的TLS支持(即Debian/Ubuntu上的`tcl-tls`软件包)。
- 运行 `./utils/gen-test-certs.sh`生成根CA和服务器证书。
- 运行 `./runtest --tls` 或者 `./runtest-cluster --tls`以TLS模式运行Redis和Redis Cluster测试。
7.2.1.3.手动运行
要以TLS模式手动运行Redis服务器(假设调用了`gen-test-certs.sh`,因此示例证书/键(certificates/keys)可用):
./src/redis-server --tls-port 6379 --port 0 \
--tls-cert-file ./tests/tls/redis.crt \
--tls-key-file ./tests/tls/redis.key \
--tls-ca-cert-file ./tests/tls/ca.crt
要使用`redis-cli`连接到该Redis服务器:
./src/redis-cli --tls \
--cert ./tests/tls/redis.crt \
--key ./tests/tls/redis.key \
--cacert ./tests/tls/ca.crt
这将禁用TCP并在端口6379上启用TLS。也可以同时使用TCP和TLS,但是你需要分配其他端口。
要使副本服务器使用TLS连接到主服务器,请使用`--tls-replication yes`,并使Redis Cluster在节点间使用TLS时使用`--tls-cluster yes`。
7.2.2.入门连接数
现在,所有套接字操作都经过连接抽象层,该层对调用者隐藏I/O和read/write事件处理。
TLS当前不支持多线程I/O,因为TLS连接需要自己处理不是线程安全的AE事件。 该解决方案可能是为I/O线程管理独立的AE循环以及与线程的连接的长期关联。 这也可能会改善整体性能。
TLS的Sync IO当前以一种骇人听闻的方式实现,即进行套接字阻止和配置套接字级别的超时。 这意味着超时值可能不太准确,并且会产生大量的系统调用开销。但是,我认为完全摆脱syncio以支持纯异步工作可能比尝试解决此问题更好。 对于复制来说,可能并不难。 对于群集键迁移,可能会更困难,但是无论如何,可能还有其他充分的理由来改进该部分。
7.2.3.待办事项清单(未来)
- Redis基准支持。 当前的实现是混合使用hiredi进行解析和基本联网(建立连接),但直接操作大多数操作的套接字。 为了适当的TLS支持,需要对其进行清理。 最好的方法可能是迁移到hireddis异步模式。
- redis-cli `--slave`和`--rdb`支持。
7.2.4.多端口
考虑允许将TLS配置在单独的端口上,使Redis侦听多个端口的含义:
- 启动横幅端口通知
- 标题(Proctitle)
- 复制节点如何宣布自己
- 集群总线端口计算
8.使用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>
9.安装Redis
要将Redis二进制文件安装到/usr/local/bin中,只需使用:
make install
如果你想使用其他路径,可以使用 `make PREFIX=/some/other/directory 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`
10.代码贡献
- https://github.com/redis/redis/blob/unstable/COPYING
- https://github.com/redis/redis/blob/unstable/CONTRIBUTING
11.Redis内部
详细请参考github或者Redis官网
11.1.源代码布局
在`src`目录中调用实际Makefile以及Redis和Sentinel的示例配置。 你可以找到一些用于执行Redis,Redis Cluster和Redis Sentinel单元测试的shell脚本,这些脚本在`tests'目录中实现。
根目录内有以下重要目录:
- `src`: 包含用C编写的Redis实现。
- `tests`: 包含在Tcl中实现的单元测试。
- `deps`: 包含Redis使用的库。 编译Redis所需的所有文件都在此目录中; 你的系统只需要提供`libc`,一个POSIX兼容接口和一个C编译器。 值得注意的是,`deps`包含`jemalloc`的副本,它是Linux下Redis的默认分配器。 请注意,在`deps'下还有一些以Redis项目开始的东西,但是主仓库不是`redis/redis`
还有更多目录,但是对于我们的目标而言,它们并不是很重要。 我们将主要关注包含Redis实现的`src`,探索每个文件中包含的内容。 公开文件的顺序是合乎逻辑的顺序,以便逐步披露不同层次的复杂性。
注意:Redis最近被大量重构。 函数名称和文件名已更改,因此你可能会发现本文档更紧密地反映了“unstable”分支。 例如,在Redis 3.0中,`server.c`和`server.h`文件分别命名为`redis.c`和`redis.h`。 但是总体结构是相同的。 请记住,所有新的开发和请求请求都应在“unstable”分支上执行。
11.2.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];
... many other fields ...
}
客户端结构定义了一个“连接的客户端(connected client)”:
- `fd`字段是客户端套接字(socket)文件描述符。
- `argc`和`argv`填充有客户端正在执行的命令,因此实现给定Redis命令的函数可以读取参数。
- `querybuf`累积来自客户端的请求,这些请求由Redis服务器根据Redis协议进行解析,并通过调用客户端正在执行的命令的实现来执行。
- `reply` and `buf` 是动态和静态缓冲区,用于累积服务器发送给客户端的回复。 文件描述符可写后,这些缓冲区就会递增地写入套接字(socket)。
如你在上面的客户端结构中所看到的,命令中的参数被描述为`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数据类型,例如strings, lists, sets, sorted sets等。 有趣的是,它具有一个 `type` 字段,以便可以知道给定对象具有什么类型,并具有一个 `refcount`,以便可以在多个位置引用同一对象而无需多次分配它。 最后,`ptr`字段指向对象的实际表示形式,即使使用相同的类型,它也可能有所不同,具体取决于所使用的 `encoding`。
Redis objects在Redis内部广泛使用,但是为了避免间接访问的开销,最近在许多地方,我们只是使用未包装在Redis object内部的纯动态字符串。
11.3.server.c
这是Redis服务器的入口点,其中定义了 `main()` 函数。 以下是启动Redis服务器的最重要步骤。
- `initServerConfig()` 设置 `server` 结构的默认值。
- `initServer()` 分配所需操作的数据结构,设置监听套接字(socket)等。
- `aeMain()` 启动侦听新连接的事件循环。
事件循环会定期调用两个特殊功能:
- 定期调用`serverCron()`(根据`server.hz`的频率),并执行必须不时地执行的任务,例如检查超时的客户端。
- 每次触发事件循环时,都会调用`beforeSleep()` ,Redis处理了一些请求,并返回到事件循环中。
在server.c内,你可以找到处理Redis服务器其他重要内容的代码:
- 使用`call()`以便在给定客户端的上下文中调用给定命令。
- `activeExpireCycle()`通过`EXPIRE`命令处理具有有效生存时间的键的收回。
- 当应该执行新的写命令,但根据maxmemory指令Redis内存不足时,将调用`freeMemoryIfNeeded()` 。
- 全局变量 `redisCommandTable`定义了所有Redis命令,指定了命令的名称,实现命令的函数,所需参数的数量以及每个命令的其他属性。
11.4.networking.c
该文件使用客户端,主节点和复制节点(在Redis中只是特殊客户端)定义了所有 I/O 功能:
- `createClient()` 分配并初始化一个新客户端。
- 命令实现使用`addReply*()` 系列函数将数据追加到客户端结构,该数据将作为对给定命令执行的回复传输到客户端。
- `writeToClient()`将输出缓冲区中待处理的数据传输到客户端,并由可写事件处理程序的`sendReplyToClient()`调用。
- `readQueryFromClient()`是可读事件处理程序,它将从客户端读取的数据累积到查询缓冲区中。
- `processInputBuffer()`是用于根据Redis协议解析客户端查询缓冲区的入口点。 一旦准备好处理命令,它将调用在server.c内部定义的 `processCommand()`以便实际执行命令。
- `freeClient()` 取消分配,断开连接和删除客户端。
11.5.aof.c and rdb.c
从名称中可以猜到,这些文件实现了Redis的RDB和AOF持久性。 Redis使用基于`fork()`系统调用的持久性模型来创建一个线程,该线程具有与Redis主线程相同(共享)的内存内容。 该辅助线程转储磁盘上内存的内容。 rdb.c使用此文件在磁盘上创建快照,而aof.c使用此文件,以便在仅追加文件太大时,执行AOF重写。
`aof.c`内部的实现具有其他功能,以实现API,该API允许命令在客户端执行命令时将新命令追加到AOF文件中。
在server.c内部定义的`call()`函数负责调用这些函数,这些函数又会将命令写入AOF。
11.6.db.c
某些Redis命令对特定的数据类型起作用。 通用命令的例子有`DEL`和`EXPIRE`。 它们对键进行操作,而不是针对其值进行操作。 所有这些通用命令都在`db.c`内部定义。
此外,`db.c`实现API是为了在Redis数据集上执行某些操作而无需直接访问内部数据结构。
在`db.c`内部最重要的功能是在许多命令实现中使用的:
- `lookupKeyRead()`和`lookupKeyWrite()` 用于获取指向与给定键关联的值的指针,如果键不存在,则使用`NULL`。
- `dbAdd()` 及其更高级别的`setKey()`在Redis数据库中创建新键。
- `dbDelete()` 删除键及其关联的值。
- `emptyDb()` 删除整个单个数据库或定义的所有数据库。
文件的其余部分实现了公开给客户端的通用命令。
11.7.object.c
已经描述了定义Redis objects的`robj` 结构。 在 `object.c` 内部,有所有在基本级别上与Redis objects一起运行的函数,例如分配新对象,处理引用计数等功能。 该文件中的重要功能:
- `incrRefCount()`和`decrRefCount()`用于增加或减少对象引用计数。 当它降为0时,对象最终被释放。
- `createObject()`分配一个新对象。还有一些专门的函数可以分配具有特定内容的字符串对象,例如`createStringObjectFromLongLong()`和类似的函数。
该文件还实现了`OBJECT`命令。
11.8.replication.c
这是Redis内部最复杂的文件之一,建议仅在对其余代码库有所了解后再进行处理。 在此文件中,实现了Redis的主节点角色和复制节点角色。
该文件中最重要的功能之一是`replicationFeedSlaves()`,它可以将命令写入表示被连接到主节点的复制节点实例的客户端,以便复制节点可以获取客户端执行的写操作:这样,他们的数据集将与主节点中的数据集保持同步。
该文件还实现了`SYNC`和`PSYNC`命令,这些命令用于执行主节点和复制节点之间的首次同步,或在断开连接后继续复制。
11.9.其他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字符串库,了解更多信息。
- `anet.c`是一个与内核公开的原始接口相比,以更简单的方式使用POSIX网络的库。
- `dict.c` 是非阻塞哈希表的实现,该哈希表将逐步进行哈希刷新。
- `scripting.c` 实现Lua脚本。 它是完全独立的,并且与其余Redis实现隔离,并且足够简单,如果你熟悉Lua API,就可以理解。
- `cluster.c` 实现Redis集群。 仅在非常熟悉Redis代码库的其余部分之后,才可能是一篇不错的文章。 如果你想阅读`cluster.c`,请务必阅读Redis群集规范。
11.10.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源代码中有大量的命令实现,可以用作实际命令实现的示例。 编写一些命令可能是熟悉代码库的好习惯。
还有许多其他文件在这里没有描述,但是覆盖所有内容是没有用的。