Redis

211 阅读26分钟

该节笔记目录如下:

  1. 为什么需要Redis以及实际业务场景
  2. 基本原理
    • 单线程
    • 持久化机制
      • RDB
      • AOF
      • 混合型
  3. Redis集群
  4. Redis分布式锁
  5. Redis如何实现非阻塞IO
  6. 大key:定义、危害及消除方法。
  7. 热key:定义、危害及消除方法。
  8. 慢查询场景
  9. 缓存穿透和缓存雪崩:定义、危害及消除方法

1. 为什么需要Redis

在MySQL中,随着数据量的增长和读写压力的不断增加,数据从单表演进出了分库分表,MySQL从单机演进出了集群。
当数据量越来越大时,MySQL查询速度会变慢,在高QPS下做实时查询是支撑不住的。读写MySQL实际上是要做磁盘IO,如果我们从内存中读写数据,速度会大大提升。Redis使用内存作为缓存存储数据库。

QPS:每秒查询率,=并发量/平均响应时间

在实际的业务场景中,Redis 一般和其他数据库搭配使用,用来减轻后端数据库的压力,比如和关系型数据库 MySQL 配合使用。具体使用如下:

  • 在读取数据库场景下, 服务器接收到读取请求后首先在Redis缓存中查找,若找不到再到MySQL中查找。 同时将数据分冷热,Redis缓存存储热数据,MySQL存储冷数据,进一步减轻MySQL压力。 当数据由冷转热,MySQL将数据回写到Redis。

    热数据:经常被访问到的数据。

    ---------------------1676613845097.png

  • 在写入数据到数据库场景下,服务器接收到写入请求后,将数据写入MySQL中。同时通过监听binlog对Redis做同步更新。

    binlog会记录到MySQL数据变更。

    ---------------------1676614374351.png

2. Redis基本工作原理

Redis有以下三个重要特点:

  • 数据从内存读写
  • 单线程处理所有操作命令
  • Redis持久化机制
  • Redis缓存的清理策略

2.1 单线程

1676622029976.png
Redis是基于内存的操作,所以CPU不是Redis的瓶颈,又由于单线程容易实现,于是顺理成章地采用单线程的方案了。

Redis客户端对服务端的每次调用都经历了三个过程:发送命令、执行命令、返回结果。 其中执行命令阶段,由于Redis是单线程处理命令,所以到达服务端的命令不会立刻执行。 所有的命令都会进入一个队列中,逐个执行,并且多个客户端发送的命令的进入队列顺序是不确定的。但是可以确定的是不会有两条命令被同时执行而产生并发问题。

优点在于:

  1. 不需要各自锁的性能消耗 不用考虑锁的问题(不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗)
  2. CPU消耗 不存在多进程或者多线程导致的切换而消耗CPU,避免了不必要的上下文切换和竞争条件。

缺点:

  1. 无法发挥多核CPU性能,可以通过在单机开多个Redis进程实例来完善。
  2. 耗时的命令会导致并发的下降,不只是读并发,写并发也会下降。

2.2 持久化机制

在使用内存存储时,关机后重启服务器数据就会丢失。而Redis的一个重要特点就是,使用内存存储但还可以保持一定程度上的持久化。

  • 为什么要做持久化机制?
    在企业级redis集群架构中,需要考虑到海量数据、高并发、高可用三个方面,持久化主要是做灾难恢复,数据恢复,可以归类到高可用的一个环节里面去,即使redis故障了,也可以通过备份数据,快速恢复,一旦恢复立即对外提供服务。

  • 持久化机制怎么实现? 通过RDB文件和AOF文件

    • RDB持久化机制
    • AOF持久化机制
    • 混合型机制
      1676624044153.png

RDB持久化机制:

  • RDB持久化:是把内存中当前进程的数据生成快照(.rdb)文件保存到硬盘的过程。
  • 实现原理:在某个时间点将当前Redis实例的所有数据信息写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,重启时加载这个文件达到数据恢复。对redis中的数据执行周期性的持久化。
  • 缺点:它会出现数据丢失,无法做到实时持久化。
    (因为它是间隔一段时间进行持久化,也就是生成快照保存到硬盘,如果在间隔时间服务器出现故障宕机,会丢失一部分数据。)

AOF持久化机制:
AOF持久化方式能很好的解决RDB持久化方式造成的数据丢失,

  • AOF持久化到硬盘中的并不是内存中的数据快照,而是和MySQL的binlog日志一样记录写入命令
  • 实现原理:客户端读写Redis数据,走RESP协议,在读写之前会产生一个日志AOF(Append Of File)文件,A对每条写入命令作为日志写到磁盘。服务器宕机Redis重启时会通过回放AOF日志中的写入指令来重新构建整个数据集。
  • 缺点:文件体积大,恢复缓慢时间长。
    因为保存的是所有的命令,随着运行时间拉长,AOF文件会无限大下去。
    当AOF文件达到一定程度大小后,重新执行这些命令是异常缓慢。

混合型持久化机制:
因为RDB虽然加载快但是存在数据丢失,AOF数据安全但是加载缓慢,Redis为了解决这个问题,带来了一个新的持久化选项——混合持久化。

  • 原理:RDB文件存某个时间点的全部数据,AOF日志只保存自持久化开始到持久化结束的这段时间发生的增量。Redis重新恢复启动时,先读取RDB,与AOF文件对比有无没有执行的命令,如果有就会加载进去,保证了Redis启动后和上一次的状态是一致的。

2.3 Redis的缓存清理策略

默认是用LRU算法实现,,因为内存是有限的,但是如果你不断地往redis里面写入数据,那肯定是没法存放下所有的数据在内存的

所以redis默认情况下,当内存中写入的数据很满之后,就会使用LRU算法清理掉部分内存中的数据,腾出一些空间来,然后让新的数据写入redis缓存中

LRU:Least Recently Used,最近最少使用算法 将最近一段时间内,最少使用的一些数据给移除掉;

  • LRU算法:磁盘数据肯定比内存大很多.源源不断访问时内存大小肯定是不足的,有时候需要从磁盘访问数据,内存放不下肯定会涉及一个淘汰的机制,通过LRU算法实现,把最近最常使用的数据保留下来,很少使用的数据淘汰掉.管理内存空间.

3. Redis集群

Redis面试题,集群_redis集群面试_Java菜鸟尹先生的博客-CSDN博客

  • Redis集群故障转移的整个流程 1696952647233.png

  • 故障转移需要人工干预吗 1696952819628.png

  • Redis哨兵模式中哨兵如何检测主节点是否宕机的:心跳检测机制

    1. 每个哨兵节点定期向它所知道的主节点和从节点发送心跳包,检查节点可达性

      心跳包通常是一种小型的网络消息,用于检测节点的可达性。在Redis Sentinel(哨兵)中,心跳包就是一种PING命令。每个哨兵节点定期(默认每10秒)向它所知道的主节点和从节点发送PING命令。主节点和从节点在接收到PING命令后,会回复PONG。如果在一定时间内没有收到PONG回复,哨兵就会认为节点不可达,可能发生了故障。

    2. 如果哨兵在指定时间内未收到来自主节点的回应,则该哨兵将该节点标记为主观下线
    3. 询问其他哨兵节点是否也观察到了同样的情况,如果超过一定数量的哨兵同意主节点不可达,则主节点被标记为客观下线。
    4. 主节点被观察道为客观下线后,哨兵就会开始一个故障转移过程。
  • 哨兵模式下,主动下线和异常宕机该怎么区分?

    1. 日志分析:主动关闭Redis节点会有明确关闭记录;异常宕机情况下日志可能会显示其他故障的信息
    2. 系统级的监控和日志:
      • 系统的监控工具(如dmesg/var/log/syslog查询系统日志文件等)可能会显示导致Redis异常宕机的底层故障,比如硬件故障或系统故障、系统OOM(Out of Memory)等。
      • 内存使用情况:监控工具如tophtop可以帮助你确定系统资源是否已经耗尽,这可能导致Redis异常宕机。
      • Redis监控和日志:通过Redis的INFO命令获取Redis服务器的各种统计信息,了解内存使用、连接数、命中率等。
      • 查网络监控:监控系统的网络流量和连接数。异常的网络活动或异常的连接数可能暗示着网络攻击或其他问题。工具如netstat和网络流量监控工具可以帮助你检测异常网络活动。
    3. 外部事件:考虑是否有外部事件可能影响Redis服务,如数据中心的断电、网络故障或硬件维护等。
  • 主从节点不一致怎么处理

    1. 当发现主从节点的数据不一致时,你可以手动将数据从主节点同步到从节点,确保从节点的数据和主节点一致。可以使用Redis的BGSAVE命令创建RDB快照,将快照文件拷贝到从节点并加载,或者使用SYNC命令进行数据同步。
    2. 在主从节点之间建立一个全新的复制连接,这会导致从节点上的数据被主节点上的数据完全替换。在从节点上执行SLAVEOF NO ONE命令,断开与当前主节点的连接,然后再执行SLAVEOF <master_ip> <master_port>命令,连接到主节点并开始复制数据。

    不一致原因:网络延迟、主节点故障(哨兵监控)、数据损坏(定期校验CHECK一下完整性)

4. Redis分布式锁

线程锁:主要用来给方法、代码块加锁。当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段。线程锁只在同一JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如synchronized是共享对象头,显示锁Lock是共享某个变量(state)。

进程锁:为了控制同一操作系统中多个进程访问某个共享资源,因为进程具有独立性,各个进程无法访问其他进程的资源,因此无法通过synchronized等线程锁实现进程锁。

分布式锁:当多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问。分布式锁就是分布式场景下的锁,比如多台不同机器上的进程,去竞争同一项资源,就是分布式锁。

为什么需要分布式锁:
线程间并发问题和进程间并发问题都是可以通过分布式锁解决的,但是强烈不建议这样做!因为采用分布式锁解决这些小问题是非常消耗资源的!分布式锁应该用来解决分布式情况下的多进程并发问题才是最合适的。 有这样一个情境,线程A和线程B都共享某个变量X。
如果是单机情况下(单JVM),线程之间共享内存,只要使用线程锁就可以解决并发问题。
如果是分布式情况下(多JVM),线程A和线程B很可能不是在同一JVM中,这样线程锁就无法起到作用了,这时候就要用到分布式锁来解决。

并发请求,不论哪一条都必须要处理的场景(即:不允许丢数据) 比如:一个订单,客户正在前台修改地址,管理员在后台同时修改备注。地址和备注字段的修改,都必须正确更新,这二个请求同时到达的话,如果不借助db的事务,很容易造成行锁竞争,但用事务的话,db的性能显然比不上redis轻量。 解决思路:A,B二个请求,谁先抢到分布式锁(假设A先抢到锁),谁先处理,抢不到的那个(即:B),丢弃。

image.png

分布式锁的实现:

分布式锁实现的关键是在分布式的应用服务器外,搭建一个存储服务器,存储锁信息,这时候我们很容易就想到了Redis。首先我们要搭建一个Redis服务器,用Redis服务器来存储锁信息。

最简单的实现方法:通过redis的setnx 加锁,加锁之后其他服务无法加锁,用完之后,再通过 delete 解锁,深藏功与名。 image.png

  • 互斥性保证:setnx的key设置
  • 安全性保证:防止redis挂机锁永远释放不了:
    传入超时时间,setnx和expire的原子性:set key value nx ex seconds
  • 锁的可重入性保证:加锁和解锁必须是同一个竞争者,避免竞争者释放了其他人的锁:
    • 情景:超时时间<业务流程执行时间(网络延迟、GC卡顿),则导致A未执行完任务就释放了锁、B接着执行任务获取锁,A执行结束删除锁,删掉了B获取的锁:
    • 方案一:分布式锁需要满足谁申请谁释放原则,不能释放别人的锁,也就是说,分布式锁,是要有归属的:为了避免这种情况,在del锁之前可以做一个判断,验证key对应的value是不是自己线程的ID.如果要考虑原子性问题,可以使用Lua脚本来实现,保证验证和删除的原子性。
    • 方案二:我们可以让获得锁的线程开启一个守护线程,用来给快要过期的锁加长超时时间。当系统A中的线程执行完任务,再显式关掉守护线程。
  • 可靠性保证:异常处理能力(Redis挂掉了、业务执行时间过长、网络波动)、容灾能力: 主从容灾:主节点挂了用从节点顶包、主从切换用哨兵模式灵活自动切换。缺点在于主从同步有时延,从节点可能会损失部分数据导致分布式锁失效。
    多机部署:大概思路就是多个机器、一半以上同意才算加锁成功。Redlock算法通过在多个Redis实例上获取锁,以提高分布式锁的可用性和容错性。 1694873532900.png

在使用的时候要注意的几个注意点: 1、锁信息必须是会过期超时的,不能让一个线程长期占有一个锁而导致死锁; 2、同一时刻只能有一个线程获取到锁。 3、在锁的设计时,需要考虑两点: 锁的颗粒度要尽量小。比如你要通过锁来减库存,那这个锁的名称你可以设置成是商品的ID,而不是任取名称。这样这个锁只对当前商品有效,锁的颗粒度小。 锁的范围尽量要小。比如只要锁2行代码就可以解决问题的,那就不要去锁10行代码了。

5. Redis如何实现非阻塞IO

Redis的非阻塞I/O是通过使用底层操作系统提供的I/O多路复用机制来实现的,而在Linux系统上,通常会使用epoll机制来支持非阻塞I/O。

以下是Redis非阻塞I/O的一般实现方式:

  1. 非阻塞文件描述符:Redis将其所使用的文件描述符(File Descriptor,通常用于表示网络套接字或文件)设置为非阻塞模式。这意味着当Redis执行I/O操作时,不会被阻塞,即使数据尚未准备好或不能立即写入。
  2. 事件驱动模型:Redis使用事件驱动模型来管理I/O操作。它将I/O操作封装为事件,并使用事件循环来监听这些事件的状态变化。
  3. 事件循环:Redis的事件循环是一个核心组件,它负责检查所有已注册的文件描述符,以查看它们是否可读、可写或出现错误。事件循环会根据文件描述符的状态触发相应的事件。
  4. 多路复用机制:Redis使用多路复用技术(Multiplexing)来同时监听多个文件描述符上的事件。这使得Redis能够高效地管理大量并发的网络连接,而不需要为每个连接创建一个线程或进程。
  5. epoll(Linux特有) :在Linux系统上,Redis通常使用epoll机制来实现事件循环。epoll是一种高效的I/O多路复用机制,它允许Redis同时监听大量文件描述符,以确定哪些文件描述符上发生了可读、可写或错误事件。
  6. 回调函数:当某个事件发生时,Redis会调用相应的回调函数来处理事件。例如,当一个客户端的数据到达时,Redis会触发可读事件,并调用相应的回调函数来处理客户端请求。

总之,Redis的非阻塞I/O是通过将文件描述符设置为非阻塞模式、使用事件驱动模型、事件循环和底层的多路复用机制(如epoll)来实现的。这种非阻塞I/O模型使得Redis能够高效地处理大量并发的网络请求,从而实现了高性能的数据存储和缓存功能。不过需要注意,具体的实现方式可能会因Redis版本和配置而异。

Redis在不同操作系统上使用不同的底层多路复用机制来实现非阻塞I/O,具体取决于操作系统的特性和支持。在Linux下,常见的多路复用机制是epoll,而在Windows下,通常使用I/O完成端口(IOCP)来实现非阻塞I/O。

  • 在Linux下,Redis使用epoll作为底层多路复用机制,因为epoll在Linux上提供了高效的I/O多路复用支持。它允许Redis同时监听多个文件描述符,并根据这些文件描述符的状态变化来触发相应的事件,以实现非阻塞I/O。
  • 在Windows下,Redis通常会使用I/O完成端口(IOCP)来实现非阻塞I/O。IOCP是Windows操作系统提供的一种高性能I/O多路复用机制,特别适用于高并发的异步I/O操作。Redis在Windows环境下使用IOCP来管理非阻塞的文件描述符,以实现高性能的操作。

Redis常见命令

以下是Redis常见命令及用法的简要介绍:
SET key value:设置一个键值对。如果键已经存在,会覆盖原有的值。
GET key:获取指定键的值
DEL key:删除指定键的值。
EXISTS key:判断指定键是否存在。
KEYS pattern:列出所有符合给定模式的键。
TTL key:获取指定键的剩余生存时间。
EXPIRE key seconds:设置指定键的生存时间(单位为秒)。
INCR key:将指定键的值加1。
DECR key:将指定键的值减1。
HSET key field value:设置指定哈希表中的字段值。
HGET key field:获取指定哈希表中的字段值。
HDEL key field:删除指定哈希表中的字段。
HMSET key field1 value1 field2 value2 ...:设置指定哈希表中的多个字段值。
HMGET key field1 field2 ...:获取指定哈希表中的多个字段值。
LPUSH key value1 value2 ...:向列表左侧添加一个或多个值。
RPUSH key value1 value2 ...:向列表右侧添加一个或多个值。
LPOP key:从列表左侧弹出一个值。
RPOP key:从列表右侧弹出一个值。
LRANGE key start stop:获取列表中指定范围的值。
SADD key member1 member2 ...:向集合中添加一个或多个成员。
SMEMBERS key:获取集合中的所有成员。
SREM key member1 member2 ...:从集合中删除一个或多个成员。
SCARD key:获取集合中成员的数量。

redis原子性

redis由于单线程特性,单个命令的执行是具备原子性的, 一组命令的原子性可以通过:

  1. 单命令:如incr
  2. redis+Lua脚本:redis会将lua脚本当来一条命令执行
    ​ Redis 会把整个 Lua 脚本作为一个整体执行,在执行的过程中不会被其他命令打断,从而保证了 Lua 脚本中操作的原子性。但是如果把很多操作都放在 Lua 脚本中原子执行,会导致 Redis 执行脚本的时间增加,同样也会降低 Redis 的并发性能。所以在编写Lua脚本时需要避免把不需要做并发控制的操作写入脚本中。

redis订阅发布、过期通知

订阅发布: 1701264942188.png

过期通知: 1701264581183.png

1. 大key

1.1 大Key定义:
  • string类型:value的字节数>10KB
  • Hash/Set/Zset/list等复杂数据结构:元素个数>5000 或 总value字节数>10MB
1.2 大key危害:

先从Redis数据发到Redis服务器上处理过程来说

  • 读取成本高
  • 容易导致慢查询(过期、删除)
    Redis单线程处理,见下图。当key为正常大小时,T时间内可以处理两条命令,但当Key变大时,T时间内就只够处理一条命令。可能会导致设置了过期时间的key到达过期时间被删除。

    1677475737969.png

  • 主从复制异常,服务阻塞
    当我们删除一个大key的时候,会造成时间过长,会引发主从切换或者同步中断 Redis的工作线程是单线程的,如果一个command堵塞了,那所有请求都会超时

    主从复制:集群模式的一种。包含一个主数据库实例(master)与多个从数据库实例(slave)。客户端可对主数据库进行读写操作,对从数据库进行读操作,主数据库写入的数据会实时自动同步给从数据库。可以进行读写分离将读压力分担一部分到slave上。

业务侧使用大key的表现:请求Redis超时报错。业务设计的时候尽量不要用大key,

1.3 消除大key的方法:
  • string类型

    1. 拆分: 将大key拆分为小key。例如一个string拆分为多个string。 1677476182545.png
      缺点:业务逻辑复杂需要去维护多个key and 解析字符串消耗时间

    2. 压缩:将value压缩后写入redis,读取时解压后再使用。
      实际过程中怎么选择压缩算法:压缩算法可以是gzip、snappy、lz4等。通常情况下,一个压缩算法压缩率高则解压耗时就长,需要对实际数据测试后选择合适算法(根据场景去压力测试,写入100个压缩后的key,然后给它反解出来。解压一个key的时间=解压100个key需要时间/100),考虑到压缩时间和解压时间的平衡,如果是写少读多场景,则重点考虑解压时间。

    通常情形下,压缩解决不了才拆分。

  • Hash/Set/Zset/list等集合类结构

    1. 拆分:可以用hash取余、位掩码的方式决定放在哪个key中
    2. 区分冷热:如榜单列表场景使用zset,只缓存前10页数据,后续数据走db

    比较成熟的处理方式:区分冷热
    绝大多数用户都能通过zset提供高速访问,同时也没有形成大key

2. 热key

2.1 热key定义

用户访问一个Key的QPS特别高,导致Server实例出现CPU负载突增或者不均的情况。热Key没有明确的标准,QPS超过500就有可能被识别为热Key。

场景:
Redis分区为多个slot,每个slot对应一个redis实例,虽然做了很多分片,但是所有请求都会访问第一台机器,全站流量都会访问它。
1677502255898.png

热key风险:
CPU负载高,速度慢,机器承受不住,网络拥塞。机器响应能力下降,所有请求实现不了,网站卡住。

2.2 解决热key的方法
  1. 设置Localcache
    在访问Redis前,在业务服务侧设置Localcache,降低Redis的QPS。LocalCache中缓存过期或未命中,则从Redis中将数据更新到LocalCache。Golang的Bigcache就是这类LocalCache。

    LocalCache介绍:
    MySQL数据量增大时为了提高访问速度使用了Redis,而本机LocalCache会比Redis更快,无需走网络缓存。LocalCache核心要做到:1. 让请求更快,从本机内存取 2. 让缓存相对有效

    缓存过期管理:用了LocalCache后Server上存的数据很有可能和Redis上存的数据不一致。所有需要让缓存的数据短暂有效,一旦过期需要从Redis更新。比如过期时间设置为2s,Redis压力会得到极大的降低。

    1677502545448.png

  2. 拆分
    实现:将key:value这一个热Key复制写入多份(例如key1:value, key2:value),访问的时候访问多个key,但value是同一个,以此将QPS分散到不同实例上,降低负载。
    缺点:更新时需要更新多个Key,存在数据短暂不一致的风险。
    关键点:更新key的时候要做很多工作保证所有slot数据一致
    1677503426496.png

  3. 使用Redis代理的热Key承载能力
    LocalCache可行但是需要每个程序写一个LocalCache,由代理实现该能力,代理具备后端路由热key发现能力,设计思想还是LocalCache,但是由Proxy来做。 1677503599846.png

3. 慢查询场景

1677503712831.png

4. 缓存穿透、雪崩、击穿

缓存穿透:

缓存和数据库中都没有的数据,可用户还是源源不断的发起请求,导致每次请求都会到数据库,从而压垮数据库。eg:Redis宕机,大量请求打到数据库上,数据库本身很慢。
比如客户查询一个根本不存在的东西,首先从Redis中查不到,然后会去数据库中查询,数据库中也查询不到,那么就不会将数据放入到缓存中,后面如果还有类似源源不断的请求,最后都会压到数据库来处理,从而给数据库造成巨大的压力。

缓存穿透的危害:

  1. 查询一个一定不存在的数据
    由于通常不会缓存不存在数据,缓存不会命中,于是这类查询请求都会直接达到数据库,如果有系统bug或者人为攻击,那么容易导致数据库响应慢甚至宕机。
  2. 缓存过期时
    在高并发场景下,一个热key如果过期,会有大量请求同时击穿至数据库,影响数据库性能和稳定。
    同一时间有大量key集中过期时,也会导致大量请求落到数据库上,导致查询变慢,甚至出现数据库无法响应新的查询。

如何减少缓存穿透:

  1. 缓存空值:避免用户查询到不存在数据,下次用户查缓存直接返回空值。
  2. 布隆过滤器:通过布隆算法来告诉Key是否存在,存储合法Key。(该算法拥有超高的压缩率,只需极小的空间就能存储大量Key值。) 。对于缓存击穿,我们可以将查询的数据条件都哈希到一个足够大的布隆过滤器中,用户发送的请求会先被布隆过滤器拦截,一定不存在的数据就直接拦截返回了,从而避免下一步对数据库的压力。
  3. 针对缓存穿透的请求,可以在缓存层做拦截,防止直接访问底层存储系统。
缓存击穿

缓存中没有但数据库中有的数据,并且某一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间(一般是缓存时间到期),持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。导致请求直接穿透缓存,直接访问底层存储系统。

解决方案:

  1. 热点数据永不过期
  2. 互斥锁:简单来说就是在Redis中根据key获得的value值为空时,先锁上,然后从数据库加载,加载完毕,释放锁。若其他线程也在请求该key时,发现获取锁失败,则睡眠一段时间(比如100ms)后重试。
  3. 在缓存失效的时候,采用异步方式重新加载缓存,而不是同步方式。
缓存雪崩

缓存雪崩: 缓存中大批量数据到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

如何避免缓存雪崩:

  1. 将缓存失效时间分散开,比如在原有的失效时间基础上增加一个随机值。(eg: 不同key过期时间设置为10分1秒,10分23秒,10分8秒。单位秒部分就是随机时间。)
    对于热点数据,过期时间尽量设置长,冷门数据可以相对设置过期时间短一些。
  2. 如果缓存数据库是分布式部署,将 热点数据均匀分布在不同的缓存数据库中。
  3. 热点数据永不过期

参考

redis分布式锁两种应用场景_redis分布式锁使用场景_xiaolege_的博客-CSDN博客

  1. 对单线程的理解 www.jianshu.com/p/bc6904abc…
  2. Redis重点六:怎么保证redis挂掉之后重启数据可以恢复?_Coding Now的博客-CSDN博客
  3. Redis综述篇 juejin.cn/post/709752…