从零单排学Redis【白银】

9次阅读

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

前言
只有光头才能变强
今天继续来学习 Redis,上一篇从零单排学 Redis【青铜】已经将 Redis 常用的数据结构过了一遍了。如果还没看的同学可以先去看一遍再回来~
这篇主要讲的内容有:

Redis 服务器的数据库
Redis 对过期键的处理
Redis 持久化策略(RDB 和 AOF)

本文力求简单讲清每个知识点,希望大家看完能有所收获
一、Redis 服务器中的数据库
我们应该都用过 MySQL,MySQL 我们可以在里边创建好几个库:

同样地,Redis 服务器中也有数据库这么一个概念。如果不指定具体的数量,默认会有 16 个数据库。

上面的命令我们也可以发现:当切换到 15 号数据库,存进 15 号库的数据,再切换到 0 号数据库时,是获取不到的!
这说明,数据库与数据库之间的数据是隔离的。
1.1Redis 数据库的原理
Redis 服务器用 redisServer 结构体来表示,其中 redisDb 是一个数组,用来保存所有的数据库,dbnum 代表数据库的数量(这个可以配置,默认是 16)

struct redisServer{

//redisDb 数组, 表示服务器中所有的数据库
redisDb *db;

// 服务器中数据库的数量
int dbnum;

};

我们知道 Redis 是 C / S 结构,Redis 客户端通过 redisClient 结构体来表示:

typedef struct redisClient{

// 客户端当前所选数据库
redisDb *db;

}redisClient;

Redis 客户端连接 Redis 服务端时的示例图:

Redis 中对每个数据库用 redisDb 结构体来表示:

typedef struct redisDb {
int id; // 数据库 ID 标识
dict *dict; // 键空间,存放着所有的键值对
dict *expires; // 过期哈希表,保存着键的过期时间
dict *watched_keys; // 被 watch 命令监控的 key 和相应 client
long long avg_ttl; // 数据库内所有键的平均 TTL(生存时间)
} redisDb;

从代码上我们可以发现最重要的应该是 dict *dict,它用来存放着所有的键值对。对于 dict 数据结构 (哈希表) 我们在上一篇也已经详细说了。一般我们将存储所有键值对的 dict 称为键空间。
键空间示意图:

Redis 的数据库就是使用字典 (哈希表) 来作为底层实现的,对数据库的增删改查都是构建在字典 (哈希表) 的操作之上的。
例如:

redis > GET message

“hello world”

1.2 键的过期时间
Redis 是基于内存,内存是比较昂贵的,容量肯定比不上硬盘的。就我们现在一台普通的机子,可能就 8G 内存,但硬盘随随便便都 1T 了。
因为我们的内存是有限的。所以我们会干掉不常用的数据,保留常用的数据。这就需要我们设置一下键的过期 (生存) 时间了。

设置键的生存时间可以通过 EXPIRE 或者 PEXPIRE 命令。
设置键的过期时间可以通过 EXPIREAT 或者 PEXPIREAT 命令。

其实 EXPIRE、PEXPIRE、EXPIREAT 这三个命令都是通过 PEXPIREAT 命令来实现的。
我们在 redisDb 结构体中还发现了 dict *expires; 属性,存放所有键过期的时间。
举个例子基本就可以理解了:

redis > PEXPIREAT message 1391234400000
(integer) 1

设置了 message 键的过期时间为 1391234400000

既然有设置过期 (生存) 时间的命令,那肯定也有移除过期时间,查看剩余生存时间的命令了:

PERSIST(移除过期时间)
TTL(Time To Live)返回剩余生存时间,以秒为单位
PTTL 以毫秒为单位返回键的剩余生存时间

1.2.1 过期策略
上面我们已经能够了解到:过期键是保存在哈希表中了。那这些过期键到了过期的时间,就会立马被删除掉吗??
要回答上面的问题,需要我们了解一下删除策略的知识,删除策略可分为三种

定时删除(对内存友好,对 CPU 不友好)
到时间点上就把所有过期的键删除了。

惰性删除(对 CPU 极度友好,对内存极度不友好)
每次从键空间取键的时候,判断一下该键是否过期了,如果过期了就删除。

定期删除(折中)

每隔一段时间去删除过期键,限制删除的执行时长和频率。

Redis 采用的是惰性删除 + 定期删除两种策略,所以说,在 Redis 里边如果过期键到了过期的时间了,未必被立马删除的!
1.2.2 内存淘汰机制
如果定期删除漏掉了很多过期 key,也没及时去查(没走惰性删除),大量过期 key 堆积在内存里,导致 redis 内存块耗尽了,咋整?
我们可以设置内存最大使用量,当内存使用量超出时,会施行数据淘汰策略。
Redis 的内存淘汰机制有以下几种:

一般场景:
使用 Redis 缓存数据时,为了提高缓存命中率,需要保证缓存数据都是热点数据。可以将内存最大使用量设置为热点数据占用的内存量,然后启用 allkeys-lru 淘汰策略,将最近最少使用的数据淘汰
二、Redis 持久化
Redis 是基于内存的,如果不想办法将数据保存在硬盘上,一旦 Redis 重启(退出 / 故障),内存的数据将会全部丢失。
我们肯定不想 Redis 里头的数据由于某些故障全部丢失(导致所有请求都走 MySQL),即便发生了故障也希望可以将 Redis 原有的数据恢复过来,这就是持久化的作用。
Redis 提供了两种不同的持久化方法来讲数据存储到硬盘里边:

RDB(基于快照),将某一时刻的所有数据保存到一个 RDB 文件中。
AOF(append-only-file),当 Redis 服务器执行写命令的时候,将执行的写命令保存到 AOF 文件中。

2.1RDB(快照持久化)
RDB 持久化可以手动执行,也可以根据服务器配置定期执行。RDB 持久化所生成的 RDB 文件是一个经过压缩的二进制文件,Redis 可以通过这个文件还原数据库的数据。

有两个命令可以生成 RDB 文件:

SAVE 会阻塞 Redis 服务器进程,服务器不能接收任何请求,直到 RDB 文件创建完毕为止。

BGSAVE 创建出一个子进程,由子进程来负责创建 RDB 文件,服务器进程可以继续接收请求。

Redis 服务器在启动的时候,如果发现有 RDB 文件,就会自动载入 RDB 文件(不需要人工干预)
服务器在载入 RDB 文件期间,会处于阻塞状态,直到载入工作完成。
除了手动调用 SAVE 或者 BGSAVE 命令生成 RDB 文件之外,我们可以使用配置的方式来定期执行:
在默认的配置下,如果以下的条件被触发,就会执行 BGSAVE 命令
save 900 1 #在 900 秒 (15 分钟) 之后,至少有 1 个 key 发生变化,
save 300 10 #在 300 秒 (5 分钟) 之后,至少有 10 个 key 发生变化
save 60 10000 #在 60 秒 (1 分钟) 之后,至少有 10000 个 key 发生变化
原理大概就是这样子的(结合上面的配置来看):

struct redisServer{
// 修改计数器
long long dirty;

// 上一次执行保存的时间
time_t lastsave;

// 参数的配置
struct saveparam *saveparams;
};

遍历参数数组,判断修改次数和时间是否符合,如果符合则调用 besave()来生成 RDB 文件

总结:通过手动调用 SAVE 或者 BGSAVE 命令或者配置条件触发,将数据库某一时刻的数据快照,生成 RDB 文件实现持久化。
2.2AOF(文件追加)
上面已经介绍了 RDB 持久化是通过将某一时刻数据库的数据“快照”来实现的,下面我们来看看 AOF 是怎么实现的。
AOF 是通过保存 Redis 服务器所执行的写命令来记录数据库的数据的。

比如说我们对空白的数据库执行以下写命令:

redis> SET meg “hello”
OK

redis> SADD fruits “apple” “banana” “cherry”
(integer) 3

redis> RPUSH numbers 128 256 512
(integer) 3

Redis 会产生以下内容的 AOF 文件:

这些都是以 Redis 的命令请求协议格式保存的。Redis 协议规范 (RESP) 参考资料:
https://www.cnblogs.com/tommy-huang/p/6051577.html
AOF 持久化功能的实现可以分为 3 个步骤:

命令追加:命令写入 aof_buf 缓冲区
文件写入:调用 flushAppendOnlyFile 函数,考虑是否要将 aof_buf 缓冲区写入 AOF 文件中
文件同步:考虑是否将内存缓冲区的数据真正写入到硬盘

flushAppendOnlyFile 函数的行为由服务器配置的 appendfsyn 选项来决定的:

appendfsync always # 每次有数据修改发生时都会写入 AOF 文件。
appendfsync everysec # 每秒钟同步一次,该策略为 AOF 的默认策略。
appendfsync no # 从不同步。高效但是数据不会被持久化。
从字面上应该就更好理解了,这里我就不细说了 …
下面来看一下 AOF 是如何载入与数据还原的:
创建一个伪客户端 (本地) 来执行 AOF 的命令,直到 AOF 命令被全部执行完毕。

2.2.1AOF 重写
从前面的示例看出,我们写了三条命令,AOF 文件就保存了三条命令。如果我们的命令是这样子的:

redis > RPUSH list “Java” “3y”
(integer)2

redis > RPUSH list “Java3y”
integer(3)

redis > RPUSH list “yyy”
integer(4)

同样地,AOF 也会保存 3 条命令。我们会发现一个问题:上面的命令是可以合并起来成为 1 条命令的,并不需要 3 条。这样就可以让 AOF 文件的体积变得更小。
AOF 重写由 Redis 自行触发(参数配置),也可以用 BGREWRITEAOF 命令手动触发重写操作。
要值得说明的是:AOF 重写不需要对现有的 AOF 文件进行任何的读取、分析。AOF 重写是通过读取服务器当前数据库的数据来实现的!
比如说现在有一个 Redis 数据库的数据如下:

新的 AOF 文件的命令如下,没有一条是多余的!

2.2.2AOF 后台重写
Redis 将 AOF 重写程序放到子进程里执行(BGREWRITEAOF 命令),像 BGSAVE 命令一样 fork 出一个子进程来完成重写 AOF 的操作,从而不会影响到主进程。
AOF 后台重写是不会阻塞主进程接收请求的,新的写命令请求可能会导致当前数据库和重写后的 AOF 文件的数据不一致!
为了解决数据不一致的问题,Redis 服务器设置了一个 AOF 重写缓冲区,这个缓存区会在服务器创建出子进程之后使用。

2.3RDB 和 AOF 对过期键的策略
RDB 持久化对过期键的策略:

执行 SAVE 或者 BGSAVE 命令创建出的 RDB 文件,程序会对数据库中的过期键检查,已过期的键不会保存在 RDB 文件中。
载入 RDB 文件时,程序同样会对 RDB 文件中的键进行检查,过期的键会被忽略。

RDB 持久化对过期键的策略:

如果数据库的键已过期,但还没被惰性 / 定期删除,AOF 文件不会因为这个过期键产生任何影响(也就说会保留),当过期的键被删除了以后,会追加一条 DEL 命令来显示记录该键被删除了
重写 AOF 文件时,程序会对 RDB 文件中的键进行检查,过期的键会被忽略。

复制模式:

主服务器来控制从服务器统一删除过期键(保证主从服务器数据的一致性)
2.4RDB 和 AOF 用哪个?
RDB 和 AOF 并不互斥,它俩可以同时使用。

RDB 的优点:载入时恢复数据快、文件体积小。
RDB 的缺点:会一定程度上丢失数据(因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。)
AOF 的优点:丢失数据少(默认配置只丢失一秒的数据)。
AOF 的缺点:恢复数据相对较慢,文件体积大

如果 Redis 服务器同时开启了 RDB 和 AOF 持久化,服务器会优先使用 AOF 文件来还原数据(因为 AOF 更新频率比 RDB 更新频率要高,还原的数据更完善)
可能涉及到 RDB 和 AOF 的配置:

redis 持久化,两种方式
1、rdb 快照方式
2、aof 日志方式

———-rdb 快照 ————
save 900 1
save 300 10
save 60 10000

stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir /var/rdb/

———–Aof 的配置 ———–
appendonly no # 是否打开 aof 日志功能

appendfsync always #每一个命令都立即同步到 aof,安全速度慢
appendfsync everysec
appendfsync no 写入工作交给操作系统,由操作系统判断缓冲区大小,统一写入到 aof 同步频率低,速度快

no-appendfsync-on-rewrite yes 正在导出 rdb 快照的时候不要写 aof
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

./bin/redis-benchmark -n 20000
官网文档:
https://redis.io/topics/persistence#rdb-advantages
三、最后
现在临近双十一买阿里云服务器就特别省钱!之前我买学生机也要 9.8 块钱一个月,现在最低价只需要 8.3 一个月!
无论是 Nginx/Elasticsearch/Redis 这些技术都是在 Linux 下完美运行的,如果还是程序员新手,买一个学习 Linux 基础命令,学习搭建环境也是不错的选择。
如果有要买服务器的同学可通过我的链接直接享受最低价:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.pfn5xpli

如果大家有更好的理解方式或者文章有错误的地方还请大家不吝在评论区留言,大家互相学习交流~~~
参考资料:

《Redis 设计与实现》
《Redis 实战》

一个坚持原创的 Java 技术公众号:Java3y,欢迎大家关注
3y 所有的原创文章:

文章的目录导航(脑图 + 海量视频资源):https://github.com/ZhongFuCheng3y/3y

正文完
 0