# Redis基础知识

# Redis的数据类型(5+4)

名称 描述 常见场景
String 字符串,key-value格式 一般应用于:需要存储常规数据的场景,需要计数的场景,还可以使用SETNX key value 实现简易分布式锁
List 双向链表,key-好多个value格式 一般应用于:最新数据展示,也可以实现简易消息队列
Hash 哈希,key-子key-子value格式 一般应用于:存储对象的场景例如用户信息
Set 无序唯一集合,key-好多个value格式 一般应用于:不可重复数据、集合多交集差集等操作场景
Zset 由于唯一集合,key-分数-value格式 一般应用于:根据数据中权重排名,例如微信运动步数排行
名称 描述 常见场景
BitMap(2.2 版新增) 存储的是连续的二进制数字(0 和 1) 一般应用于需要保存0,1的场景,例如用户签到。
HyperLogLog(2.8 版新增) 基数计数概率算法 大量数据的统计,例如活跃网站的访问量统计
GEO(3.2 版新增) 主要用于存储地理位置信息,基于 Sorted Set 实现 需要管理使用地理空间数据的场景
Stream(5.0 版新增) 消息队列,相比于基于 List 类型实现的消息队列,有这两个特有的特性:自动生成全局唯一消息ID,支持以消费组形式消费数据 消息队列

# Redis常见三种问题

# 缓存穿透

  • 问题描述

用户访问缓存和数据库中都不存在的数据,持续的访问导致数据库崩溃。

  • 解决方案
  1. 业务校验,例如系统中id为8位以上数字,传的id是7位的就该拦截。
  2. 为查询不存在的数据设置为null,并设置短短过期时间。
  3. 利用布隆过滤器判断数据 一定不存在或可能存在

# 缓存击穿

  • 问题描述

缓存中的一个热点key,在某个高流量时间段失效,导致大量请求压到数据库,是数据库崩溃。

  • 解决方案
  1. 设置热点数据不过期。
  2. 定时去刷新热点数据,例如有效时间是30分钟,那么应该有个定时任务在29分钟的时候去刷新。
  3. 使用互斥锁,如果根据key获取的值为null,那么先锁住,从数据库加载完成到缓存之后释放锁,如果同时有其他请求访问,可以让其等待一小会儿重试。

# 缓存雪崩

  • 问题描述

缓存中大量数据同时失效,大量请求压垮数据库。

  • 解决方案
  1. 设置均匀分布的有效期。
  2. redis集群、哨兵,使redis高可用

# Redis缓存和数据库数据不一致问题

# Cache Aside Pattern(旁路缓存模式)【主要使用】

此策略适合读请求比较多的场景。

特点:

  1. 先更新DB,再直接删除缓存。
  2. 先从缓存中读数据,读到返回。未读到的话,从DB查询数据返回,再把数据放入缓存。

常见问题:

  • 在写数据的过程中,可以先删除 cache ,后更新 db 么?

不可以,因为会出现数据不一致问题,例如:

  1. 请求A 删除了缓存,更新DB还没完成
  2. 请求B查询缓存,发现不存在,从数据库查询了老数据返回了,并把老数据放进了缓存。
  3. 此时请求A的更新DB操作才完成。

那此时,缓存是老数据,数据库是新数据,数据不一致。

可以采用延迟双删解决该问题。

延迟双删:在删除缓存,更新DB后,等待一段时间后再次删除缓存。

  • 那接着上述问题,先更新DB,再删除缓存就不会出问题了吗?

还是会出现问题,但是概率较小,因为缓存写入速率比数据库写入速率高得多。出问题的情况例如:

  1. 请求A从DB读取数据,还没来得及将数据写入缓存。
  2. 请求B更新了DB数据,此时缓存中还没该数据。
  3. 请求A这时候才把数据写入缓存。

那此时,DB是新数据,缓存是老数据。但是,由于缓存的写入速率很快,实际情况下很难出现,请求B更新了DB数据,删除了缓存,A才写入的情况。如果难以出现,那场景就变成了:

  1. 请求A从DB读取数据,并快速写入了缓存。
  2. 请求B更新DB数据,删除旧缓存。

更进一步还可以给缓存加上过期时间,就算不一致也只有一段时间的不一致。

  • 可不可以先更新DB,后更新缓存呢?

这是不行的,因为很容易就存在并发问题。例如:

  1. 请求A更新了数据为1,请求A还没更新缓存。
  2. 请求B更新了数据为2,请求B还没更新缓存。
  3. 接下来的 请求 A 和请求B更新缓存操作,不管谁成功,那肯定数据不一致了。
  • 旁路缓存模式的缺点
  1. 首次请求,数据一定不在缓存中问题

可以先将热点数据刷入缓存

  1. 写操作频繁,导致缓存数据频繁被删除,缓存命中率就变的极低了。
    • 可以采用“更新DB的同时更新缓存”,但需要为该操作加上锁。此操作可以保证强一致性。
    • 也是同时的去更新,但可以给缓存加一个较短的过期时间,可以减少删除次数。

补充:对于**”先更新数据库,后删除缓存“操作,如果删除缓存操作失败**产生问题的思考与解决方案:

  1. 重试机制。

可以引入消息队列,将第二个操作(删除缓存)要操作的数据加入到消息队列,由消费者来操作数据。

如果删除缓存失败,可以从消息队列中重新读取数据重试删除。如果删除成功就可以将数据从消息队列移除。

  1. 订阅 MySQL binlog,再操作缓存。

如果更新数据库操作成功,那么肯定产生binlog日志,订阅该日志再执行删除缓存操作。阿里巴巴开源的 Canal 中间件就是基于这个实现的。

# Read/Write Through Pattern(读写穿透)

在该模式下,缓存被认为是主要数据存储,从中读取数据和写入数据。缓存服务负责将数据写入DB,减轻应用程序的职责。

先查询缓存,缓存不存在,直接更新DB。缓存中存在,则先更新缓存,再由缓存自己更新数据库。(同步更新缓存和DB)

从缓存中读取数据,读取到了直接返回。未读取到,先从DB加载,写入缓存后才返回。

Read-Through Pattern 实际只是在 Cache-Aside Pattern 之上进行了封装。在 Cache-Aside Pattern 下,发生读请求的时候,如果 cache 中不存在对应的数据,是由客户端自己负责把数据写入 cache,而 Read Through Pattern 则是 cache 服务自己来写入缓存的,这对客户端是透明的。

和 Cache Aside Pattern 一样, Read-Through Pattern 也有首次请求数据一定不在缓存的问题,对于热点数据可以提前放入缓存中。

# Write Behind Pattern(异步缓存写入)

该模式在更新数据的时候,只更新缓存,同时将缓存数据设置为脏的,然后立马返回,并不会更新数据库。对于数据库的更新,会通过批量异步更新的方式进行。

实际上该模式也不能应用到我们常用的数据库和缓存的场景中,因为 Redis 并没有异步更新数据库的功能。

这种模式在我们平时开发过程中也非常非常少见,但是不代表它的应用场景少,比如消息队列中消息的异步写入磁盘、MySQL 的 Innodb Buffer Pool 机制都用到了这种策略。

# 持久化

# AOF 日志

每执行一条写操作命令,就把该命令以追加的方式写入到一个文件里;

Redis 在执行完一条写操作命令后,就会把该命令以追加的方式写入到一个文件里,然后 Redis 重启时,会读取该文件记录的命令,然后逐一执行命令的方式来进行数据恢复。

# 如何看待:Redis先执行写操作命令,再把数据写入日志?
  • 好处
  1. 避免额外的检查开销,因为如果先写入日志,那肯定避免不了语法检查,肯定不能将错误的语句写入日志文件,因为恢复文件时会报错。
  2. 不会阻塞当前写操作命令
  • 风险
  1. 服务器宕机,本次操作还没写如日志,所以重启后也无法恢复,存在数据丢失风险
  2. 由于写成功后才会记录AOF日志,但记录日志的操作也是在主线程中执行,会阻塞其他的写操作。
# AOF的三种写硬盘策略

在 Redis.conf 配置文件中的 appendfsync 配置项可以有以下 3 种参数可填:

  • Always,这个单词的意思是「总是」,所以它的意思是每次写操作命令执行完后,同步将 AOF 日志数据写回硬盘;
  • Everysec,这个单词的意思是「每秒」,所以它的意思是每次写操作命令执行完后,先将命令写入到 AOF 文件的内核缓冲区,然后每隔一秒将缓冲区里的内容写回到硬盘;
  • No,意味着不由 Redis 控制写回硬盘的时机,转交给操作系统控制写回的时机,也就是每次写操作命令执行完后,先将命令写入到 AOF 文件的内核缓冲区,再由操作系统决定何时将缓冲区内容写回硬盘。
# AOF 日志过大,会触发什么机制?

AOF重写机制,AOF 重写机制是在重写时,读取当前数据库中的所有键值对,然后将每一个键值对用一条命令记录到「新的 AOF 文件」,等到全部记录完后,就将新的 AOF 文件替换掉现有的 AOF 文件。它会丢弃无意义的语句,例如在string类型,你先设置一个值,在设置一个值,那么最后key对应的肯定是最后的值,前面的set语句毫无意义。

重写AOF的操作是由后台子进程完成的,不会阻塞主线程。对于子进程执行期间,主进程产生的数据,redis会设置一个AOF重写缓冲区,在重写AOF子进程执行期间,主进程每次执行命令,都会将命令写入AOF缓冲区AOF重写缓冲区

在子进程完成后,会异步向主进程发送信号量,主进程受到信号量后会调用信号处理函数,将AOF重写缓冲区的内容追加进新的AOF文件中,再将新的AOF文件重命名后覆盖老的文件。(信号函数执行完后主进程才能处理命令。)

# RDB 快照

将某一时刻的内存数据,以二进制的方式写入磁盘;

AOF都是操作命令,恢复时如果数据较多,速度会缓慢。为了解决这个问题,Redis增加了RDB快照,记录的是某一时刻的实际内存数据,恢复时不必像AOF那样执行命令,速度会提升。

# RDB做快照会阻塞主线程吗?

Redis提供了两个命令,save && bgsabve。一个会阻塞,一个不会阻塞。

在配置文件中的:

save 900 1     # 900 秒之内,对数据库进行了至少 1 次修改;
save 300 10    # 300 秒之内,对数据库进行了至少 10 次修改;
save 60 10000  # 60 秒之内,对数据库进行了至少 10000 次修改。
1
2
3

可以配置bgsave 的执行频率。

Redis此种方式做的的全量快照,频繁执行影响性能,不频繁执行丢失的数据会多。

# RDB执行快照时,数据修改怎么办?

redis使用 写时复制技术解决该问题。写时复制执行时,如果父进程发生了写操作,那么执行快照的子进程便会将原数据的物理内存复制一份,主进程在复制的那一份上进行写操作,子进程继续进行快照操作。

这种情况下对于在快照期间的数据,只能等到下次bgsave时才能被快照了。

另外,最坏的情况下,执行时内存占用可能是原来的两倍。

# 混合持久化方式

Redis 4.0 新增的方式,集成了 AOF 和 RBD 的优点;

RDB 优点是数据恢复速度快,但是快照的频率不好把握。频率太低,丢失的数据就会比较多,频率太高,就会影响性能。

AOF 优点是丢失数据少,但是数据恢复不快。

使用了混合持久化,最终AOF 文件的前半部分是 RDB 格式的全量数据,后半部分是 AOF 格式的增量数据

最后更新时间: 2/8/2023, 7:43:03 AM