Redis知识点

认识 Redis

1. 什么是Redis

Redis是一种基于内存的数据库,对数据的读写操作都是在内存中完成的,因此读写速度非常快,常用于缓存、消息队列、分布式锁等场景。

Redis 提供了多种数据类型来支持不同的业务场景

比如String、Hash、set、zset、Bitmaps、HyperLoglog(基数统计)、GEO(地理信息)、Stream(流),对数据类型的操作都是原子性的,因为执行命令是由单线程负责的。

2. Redis 和 Memcached 有什么区别?

共同点

  1. 都是基于内存的数据库,一般都用于当作缓存来使用。
  2. 都有过期策略
  3. 性能都非常高

区别

  1. Redis支持的数据类型更加丰富,而Memcached只支持最简单的key-value数据类型。
  2. Redis支持数据的持久化,可以将内存的数据保存在磁盘中,而Memcached没有持久化功能。
  3. Redis原生支持集群模式,而Memcached没有原生集群模式。
  4. Redis支持发布订阅模型、Lua脚本、事务等功能,而Memcached不支持。

3. 为什么用 Redis 作为 MySQL 的缓存?

  1. Redis 具备高性能

    假如用户第一次访问 MySQL 中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据缓存在 Redis 中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了,操作 Redis 缓存就是直接操作内存,所以速度相当快。

  2. Redis 具备高并发

    单台设备的 Redis 的 QPS是 MySQL 的 10 倍,所以,直接访问 Redis 能够承受的请求是远远大于直接访问 MySQL 的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。

Redis 数据结构

4. Redis 数据类型以及使用场景分别是什么?

常见的有五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合)

后面又支持了四种数据类型: BitMap(2.2 版新增)、HyperLogLog(2.8 版新增)、GEO(3.2 版新增)、Stream(5.0 版新增)

使用场景

  • String:缓存对象、常规计数、分布式锁、共享session信息等。
  • List:消息队列等
  • Hash:缓存对象、购物车等。
  • Set:聚合计算(交并差集)场景、比如点赞、共同关注、抽奖活动等。
  • Zset:排序场景。比如排行榜、电话和姓名的排序。

  • BitMap:二值状态统计的场景,比如签到、判断用户登录状态、连续签到用户总数等。

  • HyperLogLog:海量数据基数统计场景,比如百万级网页UV计数。
  • GEO:存储地理信息的场景。
  • Stream:消息队列。

5. 五种常见的 Redis 数据类型是怎么实现?

String类型的内部实现

底层的数据结构主要是SDS(简单动态字符串),是改进c语言的原生字符串类型,优点:

  • 既可以存储文本数据、又可以存储二进制数据,因为SDS使用的len属性的值不是用\n判断字符串是否结束,并且SDS的所有API都会以二进制的方式处理SDS存放在buf[]数组里面的数据。
  • SDS获取字符串长度的时间复杂度是O(1),因为SDS结构里面len属性记录了字符串长度。
  • Redis的SDS的API是安全的,拼接字符串不会造成缓冲区的溢出,因为SDS在拼接字符串前会检测SDS空海军是否满足需求,如果不满足会自动扩容。

List类型的内部实现

List类型底层数据结构是用双向链表和压缩列表实现的

  • 列表元素<512 Redis选择压缩列表做List类型的底层数据结构。
  • 其余情况选择双向链表

但在Redis3.2版本之后,List底层数据结构只由quicklist实现。

Hash类型内部实现

Hash类型底层数据结构是用哈希表和压缩列表实现的

  • 列表元素<512 Redis选择压缩列表做List类型的底层数据结构。
  • 其余情况 512 选择hash表

但在Redis7.0版本之后,List底层数据结构只由listpack实现。

Set类型内部实现

Set类型底层数据结构是用哈希表和整数集合实现的

  • 列表元素<512 Redis选择整数集合做List类型的底层数据结构。
  • 其余情况 512 选择hash表

但在Redis7.0版本之后,List底层数据结构只由listpack实现。

Redis 线程模型

6. Redis 是单线程吗?

Redis 单线程指的是「接收客户端请求->解析请求 ->进行数据读写等操作->发送数据给客户端」这个过程是由一个线程(主线程)来完成的,这也是我们常说 Redis 是单线程的原因

但是,Redis 程序并不是单线程的,Redis 在启动的时候,是会启动后台线程(BIO)的:

  • Redis在2.6版本,会启动两个后台线程,分别处理关闭文件、AOF刷盘。
  • Redis在4.0版本之后,新增了一个后台线程,用来异步释放Redis 内存。

之所以 Redis 为「关闭文件、AOF 刷盘、释放内存」这些任务创建单独的线程来处理,是因为这些任务的操作都是很耗时的,如果把这些任务都放在主线程来处理,那么 Redis 主线程就很容易发生阻塞,这样就无法处理后续的请求了。

关闭文件、AOF 刷盘、释放内存这三个任务都有各自的任务队列:

7. Redis 采用单线程为什么还这么快?

  • Redis的操作大部分都在内存中完成,并且采用了高效的数据结构。
  • Redis采用单线程模型避免了多线程之间的竞争,节省了多线程上下文切换带来的开销。
  • Redis采用了I/O多路复用机制处理大量的客户端Socket请求,IO 多路复用机制是指一个线程处理多个 IO 流,就是我们经常听到的 select/epoll 机制。简单来说,在 Redis 只运行单线程的情况下,该机制允许内核中,同时存在多个监听 Socket 和已连接 Socket。内核会一直监听这些 Socket 上的连接请求或数据请求。一旦有请求到达,就会交给 Redis 线程处理,这就实现了一个 Redis 线程处理多个 IO 流的效果。

8. Redis 6.0 之前为什么使用单线程?

CPU 并不是制约 Redis 性能表现的瓶颈所在,更多情况下是受到内存大小和网络I/O的限制,所以 Redis 核心网络模型使用单线程并没有什么问题,如果你想要使用服务的多核CPU,可以在一台服务器上启动多个节点或者采用分片集群的方式。

9.Redis 6.0 之后为什么引入了多线程?

在 Redis 6.0 版本之后,也采用了多个 I/O 线程来处理网络请求这是因为随着网络硬件的性能提升,Redis 的性能瓶颈有时会出现在网络 I/O 的处理上

所以为了提高网络I/O的并行度,Redis6.0对于网络I/O采用多线程来处理。但对于命令的执行Redis仍然采用单线程处理。

Redis 6.0 版本引入的多线程 I/O 特性对性能提升至少是一倍以上

Redis 持久化

10. Redis 如何实现数据不丢失?

  • AOF日志:每执行一条写操作命令,就把该命令以追加的方式写入到一个文件里;
  • RDB快照:将某一时刻的内存数据,以二进制的方式写入磁盘;
  • 混合持久化方式:集合AOF和RDB的优点

11. AOF 日志是如何实现的?

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

为什么先执行命令,再把数据写入日志?

  • 避免额外的开销:因为如果先将写操作命令记录到 AOF 日志里,再执行该命令的话,如果当前的命令语法有问题,那么如果不进行命令语法检查,该错误的命令记录到 AOF 日志里后,Redis 在使用日志恢复数据时,就可能会出错。
  • 不会阻塞当前写操作命令的执行:因为当写操作命令执行成功后,才会将命令记录到AOF日志。

缺点:

  • 数据可能会丢失:执行写操作命令和记录日志是两个过程,那当 Redis 在还没来得及将命令写入到硬盘时,服务器发生宕机了,这个数据就会有丢失的风险。
  • 可能阻塞其它操作:由于写操作命令执行成功后才记录到 AOF 日志,所以不会阻塞当前命令的执行,但因为 AOF 日志也是在主线程中执行,所以当 Redis 把日志文件写入磁盘的时候,还是会阻塞后续的操作无法执行。

AOF写回策略有几种?

写回策略 写回实际 优点 缺点
Always 同步写回 可靠性高,最大程度保证数据不会丢失 性能开销大
Eversec 每秒写回 性能适中 会丢失1s内的数据
No 由操作系统写回 性能好 可能会丢失大量数据

AOF日志过大,会触发什么机制?

如果当 AOF 日志文件过大就会带来性能问题,比如重启 Redis 后,需要读 AOF 文件的内容以恢复数据,如果文件过大,整个恢复的过程就会很慢。

提供了 AOF 重写机制,当 AOF 文件的大小超过所设定的阈值后,Redis 就会启用 AOF 重写机制,来压缩 AOF 文件。

AOF 重写机制是在重写时,读取当前数据库中的所有键值对,然后将每一个键值对用一条命令记录到「新的 AOF 文件」,等到全部记录完后,就将新的 AOF 文件替换掉现有的 AOF 文件。

12. RDB 快照是如何实现的呢?

所谓的快照,就是记录某一个瞬间东西,比如当我们给风景拍照时,那一个瞬间的画面和信息就记录到了一张照片。

所以,RDB 快照就是记录某一个瞬间的内存数据,记录的是实际数据,而 AOF 文件记录的是命令操作的日志,而不是实际的数据。

因此在 Redis 恢复数据时, RDB 恢复数据的效率会比 AOF 高些,因为直接将 RDB 文件读入内存就可以,不需要像 AOF 那样还需要额外执行操作命令的步骤才能恢复数据。

RDB 做快照时会阻塞线程吗?

Redis 提供了两个命令来生成 RDB 文件,分别是 save 和 bgsave,他们的区别就在于是否在「主线程」里执行:

  • 执行了 save 命令,就会在主线程生成 RDB 文件,由于和执行操作命令在同一个线程,所以如果写入 RDB 文件的时间太长,会阻塞主线程
  • 执行了 bgsave 命令,会创建一个子进程来生成 RDB 文件,这样可以避免主线程的阻塞

Redis 的快照是全量快照,也就是说每次执行快照,都是把内存中的「所有数据」都记录到磁盘中。所以执行快照是一个比较重的操作,如果频率太频繁,可能会对 Redis 性能产生影响。如果频率太低,服务器故障时,丢失的数据会更多。

RDB在执行快照的时候,数据能修改吗?

可以修改,因为执行bgsave过程中,Redis依然可以继续处理操作命令,因为有写时复制技术

13. 为什么会有混合持久化?

因为RDB的优点是:数据恢复速度快,但频率不好保证。

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

为了集成两者的优点,Redis4.0采用了混合使用AOF日志和内存快照,既保证了Redis重启速度,又降低数据呢丢失风险。

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

好处:重启Redis加载数据的时候,由于前半部分是RDB内容,这样加载的时候速度会很快

加载完RDB内容后,才会加载后半部分的AOF内容。

混合持久化优点:

  • 结合了RDB和AOF持久化的优点,既可以使得Redis可以更快的启动,同时又减少了大量数据丢失的风险。

缺点:

  • AOF文件中添加了RDB格式的内容,使得AOF文件的可读性变得很差。
  • 兼容性差。

Redis 集群

14. Redis 如何实现服务高可用?

主从复制

主从复制是Redis高可用服务的最基础保证,实现方案是将从前的一台Redis服务器,同步数据到多台Redis服务器上,即一主多从的模式,且主从服务器之间采用的是[读写分离]的方式。

主从服务器之间的命令复制是异步进行的。

所以,无法实现强一致性保证(主从数据时时刻刻保持一致),数据不一致是难以避免的。

哨兵模式

在使用 Redis 主从服务的时候,会有一个问题,就是当 Redis 的主从服务器出现故障宕机时,需要手动进行恢复。

为了解决这个问题,Redis 增加了哨兵模式,因为哨兵模式做到了可以监控主从服务器,并且提供主从节点故障转移的功能。

切片集群模式

当 Redis 缓存数据量大到一台服务器无法缓存时,就需要使用 Redis 切片集群方案,它将数据分布在不同的服务器上,以此来降低系统对单主节点的依赖,从而提高 Redis 服务的读写性能。

采用哈希槽,来处理数据和节点之间的映射关系。在 Redis Cluster 方案中,一个切片集群共有 16384 个哈希槽

15. 集群脑裂导致数据丢失怎么办?

什么是脑裂

脑裂就是同时出现了两个主节点。

由于网络问题,集群节点之间失去联系。主从数据不同步;重新平衡选举,产生两个主服务。等网络恢复,旧主节点会降级为从节点,再与新主节点进行同步复制的时候,由于会从节点会清空自己的缓冲区,所以导致之前客户端写入的数据丢失了。

Redis 过期删除与内存淘汰

16. Redis 使用的过期删除策略是什么?

每当我们对一个 key 设置了过期时间时,Redis 会把该 key 带上过期时间存储到一个过期字典中,也就是说「过期字典」保存了数据库中所有 key 的过期时间。

当我们查询一个 key 时,Redis 首先检查该 key 是否存在于过期字典中:

  • 如果不在,则正常读取键值;
  • 如果存在,则会获取该 key 的过期时间,然后与当前系统时间进行比对,如果比系统时间大,那就没有过期,否则判定该 key 已过期。

Redis 使用的过期删除策略是「惰性删除+定期删除」这两种策略配和使用。

什么是惰性删除策略?

不主动删除过期键,每次从数据库访问 key 时,都检测 key 是否过期,如果过期则删除该 key。

优点:

  • 因为每次访问时,才会检测过期,所以占用资源很少,对CPU时间最友好。

缺点:

  • 这个key已过期,如果这个过期key一直没有访问,所占用的内存就不会释放。会造成内存浪费。

什么是定期删除策略?

每隔一段时间「随机」从数据库中取出一定数量的 key 进行检查,并删除其中的过期key。

Redis 的定期删除的流程:

  1. 从过期字典中随机抽取 20 个 key;
  2. 检查这 20 个 key 是否过期,并删除已过期的 key;
  3. 如果本轮检查的已过期 key 的数量,超过 5 个(20/4),也就是「已过期 key 的数量」占比「随机抽取 key 的数量」大于 25%,则继续重复步骤 1;如果已过期的 key 比例小于 25%,则停止继续删除过期 key,然后等待下一轮再检查。

可以看到,定期删除是一个循环的流程。那 Redis 为了保证定期删除不会出现循环过度,导致线程卡死现象,为此增加了定期删除循环流程的时间上限,默认不会超过 25ms。

优点:

  • 通过限制删除操作执行的时长和频率,来减少删除操作对 CPU 的影响,同时也能删除一部分过期的数据减少了过期键对空间的无效占用。

缺点:

  • 难以确定删除操作执行的时长和频率。如果执行的太频繁,就会对 CPU 不友好;如果执行的太少,那又和惰性删除一样了,过期 key 占用的内存不会及时得到释放。

17. Redis 持久化时,对过期键会如何处理的?

RDB 文件分为两个阶段,RDB 文件生成阶段和加载阶段。

  • RDB 文件生成阶段:从内存状态持久化成 RDB(文件)的时候,会对 key 进行过期检查,过期的键「不会」被保存到新的 RDB 文件中,因此 Redis 中的过期键不会对生成新 RDB 文件产生任何影响。

  • RDB 加载阶段

    :RDB 加载阶段时,要看服务器是主服务器还是从服务器,分别对应以下两种情况:

    • 如果 Redis 是「主服务器」运行模式的话,在载入 RDB 文件时,程序会对文件中保存的键进行检查,过期键「不会」被载入到数据库中。所以过期键不会对载入 RDB 文件的主服务器造成影响;
    • 如果 Redis 是「从服务器」运行模式的话,在载入 RDB 文件时,不论键是否过期都会被载入到数据库中。但由于主从服务器在进行数据同步时,从服务器的数据会被清空。所以一般来说,过期键对载入 RDB 文件的从服务器也不会造成影响。

AOF 文件分为两个阶段,AOF 文件写入阶段和 AOF 重写阶段。

  • AOF 文件写入阶段:当 Redis 以 AOF 模式持久化时,如果数据库某个过期键还没被删除,那么 AOF 文件会保留此过期键,当此过期键被删除后,Redis 会向 AOF 文件追加一条 DEL 命令来显式地删除该键值
  • AOF 重写阶段:执行 AOF 重写时,会对 Redis 中的键值对进行检查,已过期的键不会被保存到重写后的 AOF 文件中,因此不会对 AOF 重写造成任何影响。

18. Redis 主从模式中,对过期键会如何处理?

主从模式下,从库不会进行过期扫描,从库对过期的处理是被动的。也就是即使从库中的 key 过期了,如果有客户端访问从库时,依然可以得到 key 对应的值,像未过期的键值对一样返回。

从库的过期键处理依靠主服务器控制,主库在 key 到期时,会在 AOF 文件里增加一条 del 指令,同步到所有的从库,从库通过执行这条 del 指令来删除过期的 key。

19. Redis 内存满了,会发生什么?

达到阈值时,会触发内存淘汰机制,阈值就是最大运行内存。

20. Redis 内存淘汰策略有哪些?

总体分为[不进行数据淘汰]和[进行数据淘汰]两类。

不进行数据淘汰的策略

noeviction:当运行内存超过最大设置内存时,不淘汰任何数据,而是不再提供服务,直接返回错误。

进行数据淘汰的策略

可细分为[在设置了过期时间的数据进行淘汰]和[在所有数据范围内进行淘汰]

  • volatile-random:随机淘汰过期时间
  • volatile-ttl:淘汰更早过期的键值。
  • volatile-lru:淘汰最久未使用的。
  • volatile-lfu:淘汰使用次数最少的键值。

在所有数据范围内进行淘汰

  • allkeys-random:随机淘汰
  • allkeys-lru:最久未使用键值。
  • allkeys-lfu:淘汰使用次数最少的。

21. LRU 算法和 LFU 算法有什么区别?

什么是LRU算法?

LRU是指最近最少使用,会选择淘汰最近最少使用的数据。

传统LRU算法是基于链表实现的,元素按照顺序从前往后排列,最新操作的键会被移动到表头,当需要内存淘汰时,只需要删除链表尾部的元素即可,因为链表尾部的元素就代表最久未被使用的元素。

Redis 并没有使用这样的方式实现 LRU 算法,因为传统的 LRU 算法存在两个问题:

  • 需要用链表管理所有的缓存数据,这会带来额外的空间开销
  • 当有数据被访问时,需要在链表上把该数据移动到头端,如果有大量数据被访问,就会带来很多链表移动操作,会很耗时,进而会降低 Redis 缓存性能

Redis是如何实现LRU算法的?

Redis 实现的是一种近似 LRU 算法,目的是为了更好的节约内存,它的实现方式是在 Redis 的对象结构体中添加一个额外的字段,用于记录此数据的最后一次访问时间

当 Redis 进行内存淘汰时,会使用随机采样的方式来淘汰数据,它是随机取 5 个值(此值可配置),然后淘汰最久没有使用的那个

优点

  • 不用维护一个链表,节省了空间占用
  • 不用每次数据访问时都移动链表项,提升了缓存的性能。

问题:无法解决缓存污染问题,比如读取了大量数据,这些数据只会被读取一次,这会留存在Redis缓存很长时间,造成缓存污染。

因此Redis引入了LFU算法解决

什么是LFU算法?

LFU是最近最不常用的,LFU是根据数据访问次数来淘汰数据的,它的核心思想是“如果数据过去被访问多次,那么将来被访问的频率也更高”。

所以, LFU 算法会记录每个数据的访问次数。当一个数据被再次访问时,就会增加该数据的访问次数。这样就解决了偶尔被访问一次之后,数据留存在缓存中很长一段时间的问题,相比于 LRU 算法也更合理一些。

Redis是如何实现LFU算法的?

LFU 算法相比于 LRU 算法的实现,多记录了「数据的访问频次」的信息。

Redis 缓存设计

22. 如何避免缓存雪崩、缓存击穿、缓存穿透?

如何避免缓存雪崩?

大量缓存数据在同一时间过期(失效)时,如果此时有大量的用户请求,都无法在 Redis 中处理,于是全部请求都直接访问数据库,从而导致数据库的压力骤增,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃,这就是缓存雪崩的问题。

解决方案

  • 将缓存失效时间随机打散: 我们可以在原有的失效时间基础上增加一个随机值(比如 1 到 10 分钟)这样每个缓存的过期时间都不重复了,也就降低了缓存集体失效的概率。
  • 设置缓存不过期: 我们可以通过后台服务来更新缓存数据,从而避免因为缓存失效造成的缓存雪崩,也可以在一定程度上避免缓存并发问题。

如何避免缓存击穿?

如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是缓存击穿的问题。

解决方案

  • 互斥锁方案:保证同一时间只有一个业务线程请求缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。
  • 不给热点数据设置过期时间:由后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间;

如何避免缓存穿透?

当用户访问的数据,既不在缓存中,也不在数据库中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增,这就是缓存穿透的问题。

解决方案

  • 非法请求的限制:当有大量恶意请求访问不存在的数据的时候,也会发生缓存穿透,因此在 API 入口处我们要判断求请求参数是否合理,请求参数是否含有非法值、请求字段是否存在,如果判断出是恶意请求就直接返回错误,避免进一步访问缓存和数据库。
  • 设置空值或者默认值:可以针对查询的数据,在缓存中设置一个空值或者默认值,这样后续请求就可以从缓存中读取到空值或者默认值,返回给应用,而不会继续查询数据库。
  • 布隆过滤器:快速判断数据是否存在,避免通过查询数据库来判断数据是否存在

23. 如何设计一个缓存策略,可以动态缓存热点数据呢?

由于数据存储受限,系统并不是将所有数据都需要存放到缓存中的,而只是将其中一部分热点数据缓存起来,所以我们要设计一个热点数据动态缓存的策略。

通过数据最新访问时间来做排名,并过滤掉不常访问的数据,只留下经常访问的数据

以电商平台场景中的例子,现在要求只缓存用户经常访问的 Top 1000 的商品。具体细节如下:

  • 先通过缓存系统做一个排序队列(比如存放 1000 个商品),系统会根据商品的访问时间,更新队列信息,越是最近访问的商品排名越靠前;
  • 同时系统会定期过滤掉队列中排名最后的 200 个商品,然后再从数据库中随机读取出 200 个商品加入队列中;
  • 这样当请求每次到达的时候,会先从队列中获取商品 ID,如果命中,就根据 ID 再从另一个缓存数据结构中读取实际的商品信息,并返回。

24. 说说常见的缓存更新策略?

  • Cache Aside(旁路缓存)
  • Read/Write Through(读穿/写穿)策略
  • Write Back(写回)策略

实际开发中,Redis 和 MySQL 的更新策略用的是 Cache Aside

Cache Aside策略

旁路策略最常用的,应用程序直接与[数据库,缓存]交互,并负责对缓存的维护,该策略可细分为[读策略],[写策略]。

写策略的步骤:

  • 先更新数据库中的数据,再删除缓存中的数据。(不能先删除缓存再更新数据库,因为可能会出现缓存与数据库的不一致问题)

读策略的步骤:

  • 如果读取的数据命中了缓存,则直接返回数据;
  • 如果读取的数据没有命中缓存,则从数据库中读取数据,然后将数据写入到缓存,并且返回给用户。

为什么「先更新数据库再删除缓存」不会有数据不一致的问题?

因为缓存的写入通常要远远快于数据库的写入

Read/Wrtie Through(读穿/写穿)策略

原则是应用程序只和缓存交互,不再和数据库交互,而是由缓存和数据库交互,相当于更新数据库的操作由缓存自己代理了。

1、Read Through 策略

先查询缓存中数据是否存在,如果存在则直接返回,如果不存在,则由缓存组件负责从数据库查询数据,并将结果写入到缓存组件,最后缓存组件将数据返回给应用。

2、Write Through 策略

当有数据更新的时候,先查询要写入的数据在缓存中是否已经存在:

  • 如果缓存中数据已经存在,则更新缓存中的数据,并且由缓存组件同步更新到数据库中,然后缓存组件告知应用程序更新完成。
  • 如果缓存中数据不存在,直接更新数据库,然后返回;

Write Back(写回)策略

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

Write Back 策略特别适合写多的场景

但是带来的问题是,数据不是强一致性的,而且会有数据丢失的风险,因为缓存一般使用内存,而内存是非持久化的,所以一旦缓存机器掉电,就会造成原本缓存中的脏数据丢失。

25. 数据库和缓存如何保证一致性?

所以,如果我们的业务对缓存命中率有很高的要求,我们可以采用「更新数据库 + 更新缓存」的方案,因为更新缓存并不会出现缓存未命中的情况

所以我们得增加一些手段来解决这个问题,这里提供两种做法:

  • 在更新缓存前先加个分布式锁,保证同一时间只运行一个请求更新缓存,就会不会产生并发问题了,当然引入了锁后,对于写入的性能就会带来影响。
  • 在更新完缓存时,给缓存加上较短的过期时间,这样即时出现缓存不一致的情况,缓存的数据也会很快过期,对业务还是能接受的。

Redis 实战

26. Redis 的大 key 如何处理?

什么是 Redis 大 key?

大 key 并不是指 key 的值很大,而是 key 对应的 value 很大。

一般而言,下面这两种情况被称为大 key:

  • String 类型的值大于 10 KB;
  • Hash、List、Set、ZSet 类型的元素的个数超过 5000个;

大 key 会造成什么问题?

  • 客户端超时阻塞
  • 引发网络阻塞
  • 阻塞工作线程
  • 内存分布不均

如何找到大 key ?

1、redis-cli —bigkeys 查找大key

该方式的不足之处:

  • 这个方法只能返回每种类型中最大的那个 bigkey
  • 对于集合类型来说,这个方法只统计集合元素个数的多少,而不是实际占用的内存量。

2、使用 SCAN 命令查找大 key

3、使用 RdbTools 工具查找大 key

如何删除大Key?

1、分批次删除

对于删除大 Hash,使用 hscan 命令,每次获取 100 个字段,再用 hdel 命令,每次删除 1 个字段。

2、异步删除

从 Redis 4.0 版本开始,可以采用异步删除法,用 unlink 命令代替 del 来删除

这样 Redis 会将这个 key 放入到一个异步线程中进行删除,这样不会阻塞主线程。

27. Redis 管道有什么用?

管道技术(Pipeline)是客户端提供的一种批处理技术,用于一次处理多个 Redis 命令,从而提高整个交互的性能。

使用管道技术可以解决多个命令执行时的网络等待,它是把多个命令整合到一起发送给服务器端处理之后统一返回给客户端,这样就免去了每条命令执行后都要等待的情况,从而有效地提高了程序的执行效率。

28. Redis 事务支持回滚吗?

Redis 中并没有提供回滚机制

为什么Redis 不支持事务回滚?

  • 他认为 Redis 事务的执行时,错误通常都是编程错误造成的,这种错误通常只会出现在开发环境中,而很少会在实际的生产环境中出现,所以他认为没有必要为 Redis 开发事务回滚功能;
  • 不支持事务回滚是因为这种复杂的功能和 Redis 追求的简单高效的设计主旨不符合

文章结束!