作者:京东科技 周新智

一、Redis

家喻户晓,Redis = Remote Dictionary Server,即近程字典服务。

是一个开源的应用ANSI C语言编写、反对网络、可基于内存亦可长久化的日志型、Key-Value数据库,并提供多种语言的API。

二、当你对 redis 说你中意的女孩是 Mia 时

1、set myLove Mia

redis 会将 key:myLove value:Mia

包装成一个 dictEntry 对象、一个 redisObject 对象,如下图所示:

dictEntry:家喻户晓,Redis是Key-Value数据库,因而对每个键值对都会有一个dictEntry,外面存储了指向Key和Value的指针;next指向下一个dictEntry,与本Key-Value无关。

Key:图中右上角可见,Key("myLove")并不是间接以字符串存储,而是存储在SDS构造中。

redisObject:Value("Mia")既不是间接以字符串存储,也不是像Key一样间接存储在SDS中,而是存储在redisObject中。实际上,不管Value是5种类型的哪一种,都是通过redisObject来存储的;而redisObject中的type字段指明了Value对象的类型,ptr字段则指向对象所在的地址。不过能够看出,字符串对象尽管通过了redisObject的包装,但依然须要通过SDS存储。

1.1、对 myLove 进行对象封装

1.1.1、dictEntry

redis外部整体的存储构造是一个大的hashmap,外部是数组实现的hash,key抵触通过挂链表去实现,每个dictEntry为一个key/value对象,value为定义的redisObject。

结构图如下:

dictEntry是存储key->value的中央,再让咱们看一下dictEntry构造体

/* * 字典 */typedef struct dictEntry {    // 键    void *key;    // 值    union {        // 指向具体redisObject        void *val;        //         uint64_t u64;        int64_t s64;    } v;    // 指向下个哈希表节点,造成链表    struct dictEntry *next;} dictEntry;
1.1.2、对象封装 redisObject

咱们接着再往下看redisObject到底是什么构造的

/* * Redis 对象 */typedef struct redisObject {    // 类型 4bits    unsigned type:4;    // 编码方式 4bits    unsigned encoding:4;    // LRU 工夫(绝对于 server.lruclock) 24bits    unsigned lru:22;    // 援用计数 Redis外面的数据能够通过援用计数进行共享 32bits    int refcount;    // 指向对象的值 64-bit    void *ptr;} robj;

*ptr指向具体的数据结构的地址;type示意该对象的类型,即String,List,Hash,Set,Zset中的一个,但为了进步存储效率与程序执行效率,每种对象的底层数据结构实现都可能不止一种,encoding 示意对象底层所应用的编码。

redis对象底层的八种数据结构:

 REDIS_ENCODING_INT(long 类型的整数) REDIS_ENCODING_EMBSTR embstr (编码的简略动静字符串) REDIS_ENCODING_RAW (简略动静字符串) REDIS_ENCODING_HT (字典) REDIS_ENCODING_LINKEDLIST (双端链表) REDIS_ENCODING_ZIPLIST (压缩列表) REDIS_ENCODING_INTSET (整数汇合) REDIS_ENCODING_SKIPLIST (跳跃表和字典)

查看 redisObject 详细信息 :

# 查看 key对应value的 redisObject 类型type key    type myLove    # 查看 key对应value的redisObject 详细信息debug object key     debug object myLove

value 为 string 、int 类型是 redisObject 中的 type、encoding 不同表现形式

Value 为 string 类型时:

Value 为 int类型时:

以上两种不同 value 类型,type 雷同,encoding 不同

1.2、对 myLove 进行长久化

1.2.1、rdb 文件写入

1.2.2、aof 缓存写入 文件保留

默认状况下 没有开启 AOF ( append only file)

开启 AOF 长久化后,每执行一条会更改 redis 数据的命令,redis就会将写入、批改、删除命令写入到硬盘中的 AOF 文件(当然并不是立刻写入文件,而是立刻写入aof缓存中,再依据aof配置的数据长久化条件进行写入),这一过程显然会升高 redis 的性能,但大部分状况下这个影响是可能承受的,

另外应用快的硬盘能够进步 AOF 的性能。

配置 redis.conf

# 能够通过批改redis.conf配置文件中的appendonly参数开启     appendonly yes# AOF文件的保留地位和RDB文件的地位雷同,都是通过dir参数设置的。 dir ./# 默认的文件名是appendonly.aof,能够通过appendfilename参数批改 appendfilename appendonly.aof

AOF文件中存储的是redis的命令 原理

Redis 将所有对数据库进行过写入的命令(及其参数)记录到 AOF 文件, 以此达到记录数据库状态的 目标, 为了不便起见, 咱们称说这种记录过程为同步。

同步命令到 AOF 文件的整个过程能够分为三个阶段:

命令流传:Redis 将执行完的命令、命令的参数、命令的参数个数等信息发送到 AOF 程序中。 缓存追 加:AOF 程序依据接管到的命令数据,将命令转换为网络通讯协定 RESP 的格局,而后将协定内容追加到服务器的 AOF 缓存中。 文件写入和保留: AOF 缓存中的内容被写入到 AOF 文件开端,如果设定的 AOF 保留条件被满足的话, fsync 函数或者 fdatasync 函数会被调用,将写入的内容真正地保留到磁盘中。

命令流传:

当一个 Redis 客户端须要执行命令时, 它通过网络连接, 将协定文本发送给 Redis 服务器。服务器在 接到客户端的申请之后, 它会依据协定文本的内容, 抉择适当的命令函数, 并将各个参数从字符串文 本转换为 Redis 字符串对象( StringObject )。每当命令函数胜利执行之后, 命令参数都会被流传到 AOF 程序。

缓存追加:

当命令被流传到 AOF 程序之后, 程序会依据命令以及命令的参数, 将命令从字符串对象转换回原来的 协定文本。协定文本生成之后, 它会被追加到 redis.h/redisServer 构造的 aof_buf 开端。

redisServer 构造维持着 Redis 服务器的状态, aof_buf 域则保留着所有期待写入到 AOF 文件的协 议文本。

RESP 协定:

Redis客户端应用RESP(Redis的序列化协定)协定与Redis的服务器端进行通信。 尽管该协定是专门为 Redis设计的,然而该协定也能够用于其余 客户端-服务器 (Client-Server)软件我的项目。

能够通过特殊符号来辨别出数据的类型:

单行回复:以+号结尾。

谬误回复:以-号结尾。

整数回复:以:号结尾。

批量回复:以$号结尾。

多条批量回复:以*号结尾。

1、距离符号,在Linux下是\r\n,在Windows下是\n

2、简略字符串 Simple Strings, 以 "+"加号 结尾

3、谬误 Errors, 以"-"减号 结尾

4、整数型 Integer, 以 ":" 冒号结尾

5、大字符串类型 Bulk Strings, 以 "$"美元符号结尾,长度限度512M 6、数组类型 Arrays,以 "*"星号结尾 用SET命令来举例说明RESP协定的格局。

理论发送的申请数据:

redis> SET myLove "Mia""OK"*3\r\n$3\r\nSET\r\n$6\r\nmyLove\r\n$3\r\nMia\r\n*3$3SET$5mykey$5Hello

理论收到的响应数据:

+OK\r\n

文件写入和保留:

每当服务器惯例工作函数被执行、 或者事件处理器被执行时, aof.c/flushAppendOnlyFile 函数都会被 调用, 这个函数执行以下两个工作:

WRITE:依据条件,将 aof_buf 中的缓存写入到 AOF 文件。 SAVE:依据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保留到磁盘中。

2、给你的爱一个期限 expire myLove 999999999

从图可知,在redis的数据库中,redisDb构造中的expires字典中保留了数据库中所有键的过期工夫,所以叫过期字典。

过期字典的key是一个指针,指向键空间的某个键对象(就是数据库键)

过期字典的value是一个long类型的整数,这个整数保留了键所指向的数据库键的过期工夫,一个毫秒精度的UNIX工夫戳

过期键断定

通过过期字典,咱们能够失去一个key是否过期:

判断key是否存在于过期字典中

通过过期字典拿到key的过期工夫,判断以后UNIX工夫戳是否大于key工夫

过期key如何删除

惰性删除策略

过期键的惰性删除策略由db.c/expireIfNeeded函数实现,所有读写数据库的Redis命令在执行之前都会调用expireIfNeeded函数对输出键进行查看:

如果输出键曾经过期,那么expireIfNeeded函数将输出键从数据库中删除。

如果输出键未过期,那么expireIfNeeded函数不做动作。

expireIfNeeded函数就像一个过滤器,它能够在命令真正执行之前,过滤掉过期的输出键,从而防止命令接触到过期键。

另外,因为每个被拜访的键都可能因为过期而被expireIfNeeded函数删除,所以每个命令的实现函数都必须能同时解决键存在以及键不存在这两种状况:

当键存在时,命令依照键存在的状况执行。

当键不存在或者键因为过期而被expireIfNeeded函数删除时,命令依照键不存在的状况执行。

定期删除策略的实现

过期键的定期删除策略由redis.c/activeExpireCycle函数实现,每当Redis的服务器周期性操作redis.c/serverCron函数执行时,activeExpireCycle函数就会被调用,它在规定的工夫内,分屡次遍历服务器中的各个数据库,从数据库的expires字典中随机查看一部分键的过期工夫,并删除其中的过期键。

3、del myLove

不好意思,哥们的爱无奈删除!