本文正在参与「金石方案」

作者:京东科技 周新智

一、Redis

众所周知,Redis = Remote Dictionary Server,即长途字典服务。

是一个开源的运用ANSI C言语编写、支持网络、可依据内存亦可耐久化的日志型、Key-Value数据库,并提供多种言语的API。

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

1、set myLove Mia

redis 会将 key:myLove value:Mia

包装成一个 dictEntry 目标、一个 redisObject 目标,如下图所示:

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

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。

结构图如下:

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

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 类型时:

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

Value 为 int类型时:

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

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

1.2、对 myLove 进行耐久化

1.2.1、rdb 文件写入

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

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
$3
SET
$5
mykey
$5
Hello

实践收到的呼应数据:

+OK\r\n

文件写入和保存:

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

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

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

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

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

从图可知,在redis的数据库中,redisDb结构中的expires字典中保存了数据库中一切键的过期时刻,所以叫过期字典。

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

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

过期键判定

经过过期字典,咱们能够得到一个key是否过期:

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

经过过期字典拿到key的过期时刻,判别当时UNIX时刻戳是否大于key时刻

过期key如何删去

慵懒删去战略

过期键的慵懒删去战略由db.c/expireIfNeeded函数完成,一切读写数据库的Redis指令在履行之前都会调用expireIfNeeded函数对输入键进行检查:

如果输入键现已过期,那么expireIfNeeded函数将输入键从数据库中删去。

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

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

expireIfNeeded函数就像一个过滤器,它能够在指令真正履行之前,过滤掉过期的输入键,然后防止指令接触到过期键。

别的,由于每个被访问的键都或许由于过期而被expireIfNeeded函数删去,所以每个指令的完成函数都必须能一起处理键存在以及键不存在这两种状况:

当键存在时,指令依照键存在的状况履行。

当键不存在或许键由于过期而被expireIfNeeded函数删去时,指令依照键不存在的状况履行。

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

定期删去战略的完成

过期键的定期删去战略由redis.c/activeExpireCycle函数完成,每当Redis的服务器周期性操作redis.c/serverCron函数履行时,activeExpireCycle函数就会被调用,它在规则的时刻内,分多次遍历服务器中的各个数据库,从数据库的expires字典中随机检查一部分键的过期时刻,并删去其中的过期键。

3、del myLove

不好意思,哥们的爱无法删去!