在Github标星50k+的文章告诉你,Redis怎么玩

什么是Redis?

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

在Redis中实现的数据结构有几个特殊的属性:

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

另一个很好的例子是将Redis看作是memcached的一个更复杂的版本,其中的操作不仅仅是set和get,还包括与复杂数据类型(如列表、集合、有序数据结构等)一起工作的操作。

如果希望了解更多信息,请列出选定的起点:

建筑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.cserver.h文件名redis.credis.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字段是客户端套接字文件描述符。
  • argcargv由客户端正在执行的命令填充,以便实现给定Redis命令的函数可以读取参数。
  • querybuf累积来自客户端的请求,这些请求由Redis服务器根据Redis协议解析,并通过调用客户端正在执行的命令的实现来执行。
  • replybuf是动态和静态缓冲区,用于累积服务器发送给客户端的答复。一旦文件描述符可写,这些缓冲区就会增量地写入套接字。

正如您在上面的客户端结构中所看到的,命令中的参数被描述为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()启动事件循环,该循环侦听新连接。

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

  1. serverCron()定期调用(根据server.hz),并执行必须不时执行的任务,如检查超时客户端。
  2. 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命令对特定的数据类型进行操作;其他命令则是通用的。泛型命令的示例包括DELEXPIRE。它们操作的是键,而不是特定的值。所有这些通用命令都是在内部定义的。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()它将命令写入表示连接到我们的主服务器的副本实例的客户端,以便副本能够得到客户端执行的写入:这样,它们的数据集将与主服务器中的数据集保持同步。

此文件还实现了两个SYNCPSYNC用于在主程序和副本之间执行第一次同步或在断开连接后继续复制的命令。

其他C文件

  • t_hash.c, t_list.c, t_set.c, t_string.c, t_zset.ct_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代码库中找到自己的方法:-)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • 表情是什么,我认为表情就是表现出来的情绪。表情可以传达很多信息。高兴了当然就笑了,难过就哭了。两者是相互影响密不可...
    Persistenc_6aea阅读 124,103评论 2 7
  • 16宿命:用概率思维提高你的胜算 以前的我是风险厌恶者,不喜欢去冒险,但是人生放弃了冒险,也就放弃了无数的可能。 ...
    yichen大刀阅读 6,032评论 0 4