Redis理解

目录

Redis介绍

常用数据类型

类型常用命令

字符串 string

哈希 hash

列表 list

集合 set 

有序集合 zset 

通用命令 

持久化机制

RDB持久化

AOF持久化 

AOF工作流程

AOF的重写

二者优缺点

redis线程模型

redis后台线程

redis内存管理 

设置缓存过期时间

删除策略

每次随机抽查数量是多少?

如何控制定期删除的执行频率?

为什么定期删除不是把所有过期 key 都删除呢?

为什么 key 过期之后不立马把它删掉呢?这样不是会浪费很多内存空间吗?

大量key集中过期

内存淘汰策略

redis事务

redis优化 

原生批量操作命令

pipeline

Lua脚本

大量key集中过期

redis bigkey

如何发现bigkey

如何处理bigkey

redis hotkey 

hotkey危害

如何发现hotkey 

如何处理hotkey

慢查询命令

如何找到慢查询命令

redis生产问题 

缓存穿透 

解决方法

缓存击穿

解决方法

缓存雪崩 

解决方法

如何保证缓存与数据库数据的一致性

Redis常见阻塞原因总结 

redis集群

redis使用规范 


Redis介绍

Redis基于内存的key-value的结构数据库,(mysql是磁盘存储,二维表)。

适合存储热点数据

常用数据类型

key:字符串类型

value :

字符串string:普通字符串,基本数据类型

哈希hash:散列

列表list:有序,可重复

集合set:无序,不重复

有序集合sorted set/zset:每个元素关联一个分数,根据分数升序排序,不重复,(排行榜,热点榜可用)

类型常用命令

字符串 string

set key value                          设置指定key的值

get key                                    获取指定key的值

setex key seconds value        设置指定key的值,并将key的过期时间设为second秒

setnx key value                       只有在key不存在时设置key的值

哈希 hash

Redis hash

key对应的value是一个哈希表, 

hset key field value       将哈希表中key中的字段field的值设为value   

hget key field                获取哈希表中指定字段的值

hdel key field                删除储存在哈希表中的指定字段

hkeys key                     获取哈希表中所有字段

hvals key                      获取哈希表中所有值

列表 list

Redis列表是一个简单的字符串列表,按照插入顺序排序

lpush key value1 [value2]        将一个或多个值插入到列表头部(l=left)

lrange key start stop                获取列表指定范围内的元素

rpop key                                  移除并获取列表最后一个元素

llen key                                    获取列表长度

集合 set 

集合是String类型的无序集合,不重复

sadd key member1 [member2]        向集合添加一个或多个成员

smembers key                                 返回集合中的所有成员

scard key                                          获取集合的成员数

sinter key1 [key2]                             返回给定所有集合的交集

sunion key1 [key2]                           返回所有给定集合的并集

srem key member1 [member2]        删除集合中一个或多个成员

有序集合 zset 

有序集合是String类型的集合,不重复,每个元素关联一个double类型的分数

zadd key score1 member1 [score2 member2]        像有序集合添加一个或多个成员

zrange key start stop [withscores]                 通过索引区间返回有序集合中指定区间内的成员

zincrby key increment member        有序集合中对指定成员的分数加上增量 increment

zrem key member [member...]        移除有序集合中的一个或多个成员

通用命令 

keys pattern        查找所有符合给定模式的key

exists key        检查给定key是否存在

type key        返回key所储存的值的类型

del key         该命令用于在key存在时删除key

持久化机制

Redis 支持 3 种持久化方式:

  • 快照(snapshotting,RDB)
  • 只追加文件(append-only file, AOF)
  • RDB 和 AOF 的混合持久化(Redis 4.0 新增)

RDB持久化

Redis 可以通过创建快照来获得存储在内存里面的数据在 某个时间点 上的副本。

Redis 提供了两个命令来生成 RDB 快照文件:

  • save : 同步保存操作,会阻塞 Redis 主线程;
  • bgsave : fork 出一个子进程,子进程执行,不会阻塞 Redis 主线程,默认选项。

AOF持久化 

        AOF 持久化的实时性更好。默认情况下 Redis 没有开启 AOF(append only file)方式的持久化(Redis 6.0 之后已经默认是开启了),

        每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到 AOF 缓冲区server.aof_buf 中,然后再写入到 AOF 文件中(此时还在系统内核缓存区未同步到磁盘),最后再根据持久化方式( fsync策略)的配置来决定何时将系统内核缓存区的数据同步到硬盘中的。只有同步到磁盘中才算持久化保存了。

AOF工作流程

AOF 持久化功能的实现可以简单分为 5 步:

  1. 命令追加(append):所有的写命令会追加到 AOF 缓冲区中。
  2. 文件写入(write):将 AOF 缓冲区的数据写入到 AOF 文件中。
  3. 文件同步(fsync):AOF 缓冲区根据对应的持久化方式( fsync 策略)向硬盘做同步操作。
  4. 文件重写(rewrite):随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的。
  5. 重启加载(load):当 Redis 重启时,可以加载 AOF 文件进行数据恢复。
AOF的重写

        当 AOF 变得太大时,Redis 能够在后台自动重写 AOF 产生一个新的 AOF 文件,这个新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小。

        AOF 文件重写期间,Redis 还会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新 AOF 文件期间,记录服务器执行的所有写命令。当子进程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新的 AOF 文件保存的数据库状态与现有的数据库状态一致。最后,服务器用新的 AOF 文件替换旧的 AOF 文件,以此来完成 AOF 文件重写操作。

二者优缺点

        RDB 文件存储的内容是经过压缩的二进制数据, 保存着某个时间点的数据集,文件很小,适合做数据的备份,灾难恢复。使用 RDB 文件恢复数据,直接解析还原数据即可,不需要一条一条地执行命令,速度非常快。

        AOF数据安全性高,AOF 支持秒级数据丢失,仅仅是追加命令到 AOF 文件,操作轻量。易于理解和解析的格式包含所有操作的日志,可以轻松地导出 AOF 文件进行分析。

redis线程模型

        Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型。Redis 是单线程模型。Redis 通过 IO 多路复用程序 来监听来自客户端的大量连接。

        Redis 在 4.0 之后的版本中就已经加入了对多线程的支持。 Redis 4.0 增加的多线程主要是针对一些大键值对的删除操作的命令,使用这些命令就会使用主线程之外的其他线程来“异步处理”,从而减少对主线程的影响。

Redis 4.0 之后新增了几个异步命令:

  • UNLINK:可以看作是 DEL 命令的异步版本。
  • FLUSHALL ASYNC:用于清空所有数据库的所有键,不限于当前 SELECT 的数据库。
  • FLUSHDB ASYNC:用于清空当前 SELECT 数据库中的所有键。

        Redis6.0 引入多线程主要是为了提高网络 IO 读写性能,因为这个算是 Redis 中的一个性能瓶颈(Redis 的瓶颈主要受限于内存和网络)。虽然,Redis6.0 引入了多线程,但是 Redis 的多线程只是在网络数据的读写这类耗时操作上使用了,执行命令仍然是单线程顺序执行。因此,你也不需要担心线程安全问题。

redis后台线程

redis内存管理 

设置缓存过期时间

        内存是有限且珍贵的,如果不对缓存数据设置过期时间,那内存占用就会一直增长,最终可能会导致 OOM 问题。通过设置合理的过期时间,Redis 会自动删除暂时不需要的数据,为新的缓存数据腾出空间。 

        Redis 通过一个叫做过期字典(可以看作是 hash 表)来保存数据过期的时间。过期字典的键指向 Redis 数据库中的某个 key(键),过期字典的值是一个 long long 类型的整数,这个整数保存了 key 所指向的数据库键的过期时间(毫秒精度的 UNIX 时间戳)。

删除策略

常用的过期数据的删除策略就下面这几种:

  1. 惰性删除:只会在取出/查询 key 的时候才对数据进行过期检查。这种方式对 CPU 最友好,但是可能会造成太多过期 key 没有被删除。
  2. 定期删除:周期性地随机从设置了过期时间的 key 中抽查一批,然后逐个检查这些 key 是否过期,过期就删除 key。相比于惰性删除,定期删除对内存更友好,对 CPU 不太友好。
  3. 延迟队列:把设置过期时间的 key 放到一个延迟队列里,到期之后就删除 key。这种方式可以保证每个过期 key 都能被删除,但维护延迟队列太麻烦,队列本身也要占用资源。
  4. 定时删除:每个设置了过期时间的 key 都会在设置的时间到达时立即被删除。这种方法可以确保内存中不会有过期的键,但是它对 CPU 的压力最大,因为它需要为每个键都设置一个定时器。

        Redis 采用的是 定期删除+惰性/懒汉式删除 结合的策略,这也是大部分缓存框架的选择。定期删除对内存更加友好,惰性删除对 CPU 更加友好。

        Redis 的定期删除过程是随机的(周期性地随机从设置了过期时间的 key 中抽查一批),所以并不保证所有过期键都会被立即删除。这也就解释了为什么有的 key 过期了,并没有被删除。并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。

另外,定期删除还会受到执行时间和过期 key 的比例的影响:

  • 执行时间已经超过了阈值,那么就中断这一次定期删除循环,以避免使用过多的 CPU 时间。
  • 如果这一批过期的 key 比例超过一个比例,就会重复执行此删除流程,以更积极地清理过期 key。相应地,如果过期的 key 比例低于这个比例,就会中断这一次定期删除循环,避免做过多的工作而获得很少的内存回收。
每次随机抽查数量是多少?

        Redis 7.2 版本为 20 ,也就是说每次会随机选择 20 个设置了过期时间的 key 判断是否过期。

如何控制定期删除的执行频率?

        在 Redis 中,定期删除的频率是由 hz 参数控制的。hz 默认为 10,代表每秒执行 10 次,也就是每秒钟进行 10 次尝试来查找并删除过期的 key。

        hz 的取值范围为 1~500。增大 hz 参数的值会提升定期删除的频率。如果你想要更频繁地执行定期删除任务,可以适当增加 hz 的值,但这会加 CPU 的使用率。根据 Redis 官方建议,hz 的值不建议超过 100,对于大部分用户使用默认的 10 就足够了。

为什么定期删除不是把所有过期 key 都删除呢?

        这样会对性能造成太大的影响。如果我们 key 数量非常庞大的话,挨个遍历检查是非常耗时的,会严重影响性能。Redis 设计这种策略的目的是为了平衡内存和性能。

为什么 key 过期之后不立马把它删掉呢?这样不是会浪费很多内存空间吗?

        因为不太好办到,或者说这种删除方式的成本太高了。假如我们使用延迟队列作为删除策略,这样存在下面这些问题:

  1. 队列本身的开销可能很大:key 多的情况下,一个延迟队列可能无法容纳。
  2. 维护延迟队列太麻烦:修改 key 的过期时间就需要调整期在延迟队列中的位置,并且,还需要引入并发控制。
大量key集中过期

        如果存在大量 key 集中过期的问题,可能会使 Redis 的请求延迟变高。可以采用下面的可选方案来应对:

  1. 尽量避免 key 集中过期,在设置键的过期时间时尽量随机一点。
  2. 对过期的 key 开启 lazyfree 机制(修改 redis.conf 中的 lazyfree-lazy-expire参数即可),这样会在后台异步删除过期的 key,不会阻塞主线程的运行。

内存淘汰策略

        Redis 提供了 6 种内存淘汰策略:

  1. volatile-lru(least recently used):从已设置过期时间的数据集中挑选最近最少使用的数据淘汰。
  2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰。
  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。
  4. allkeys-lru(least recently used):从数据集(server.db[i].dict)中移除最近最少使用的数据淘汰。
  5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰。
  6. no-eviction(默认内存淘汰策略):禁止驱逐数据,当内存不足以容纳新写入数据时,新写入操作会报错。
  7. volatile-lfu(least frequently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰。
  8. allkeys-lfu(least frequently used):从数据集(server.db[i].dict)中移除最不经常使用的数据淘汰。

        allkeys表示从所有的键值中淘汰数据,而 volatile-xxx 表示从设置了过期时间的键值中淘汰数据。

redis事务

        Redis 事务提供了一种将多个命令请求打包的功能。然后,再按顺序执行打包的所有命令,并且不会被中途打断。 不满足原子性,满足持久性(鸡肋)。

redis优化 

一个 Redis 命令的执行可以简化为以下 4 步:

  1. 发送命令
  2. 命令排队
  3. 命令执行
  4. 返回结果

        其中,第 1 步和第 4 步耗费时间之和称为 Round Trip Time (RTT,往返时间) ,也就是数据在网络上传输的时间。使用批量操作可以减少网络传输次数,进而有效减小网络开销,大幅减少 RTT。

原生批量操作命令

简化步骤 

  1. 找到 key 对应的所有 hash slot;
  2. 分别向对应的 Redis 节点发起 MGET 请求获取数据;
  3. 等待所有请求执行结束,重新组装结果数据,保持跟入参 key 的顺序一致,然后返回结果。

pipeline

        利用 pipeline(流水线) 将一批 Redis 命令封装成一组,这些 Redis 命令会被一次性提交到 Redis 服务器,只需要一次网络传输。不过,需要注意控制一次批量操作的 元素个数(例如 500 以内,实际也和元素字节数有关),避免网络传输的数据量过大。

原生批量操作命令和 pipeline 的是有区别的,使用的时候需要注意:

  • 原生批量操作命令是原子操作,pipeline 是非原子操作。
  • pipeline 可以打包不同的命令,原生批量操作命令不可以。
  • 原生批量操作命令是 Redis 服务端支持实现的,而 pipeline 需要服务端和客户端的共同实现。

顺带补充一下 pipeline 和 Redis 事务的对比:

  • 事务是原子操作,pipeline 是非原子操作。两个不同的事务不会同时运行,而 pipeline 可以同时以交错方式执行。
  • Redis 事务中每个命令都需要发送到服务端,而 Pipeline 只需要发送一次,请求次数更少。

Lua脚本

        Lua 脚本同样支持批量操作多条命令。一段 Lua 脚本可以视作一条命令执行,可以看作是 原子操作 。也就是说,一段 Lua 脚本执行过程中不会有其他脚本或 Redis 命令同时执行,保证了操作不会被其他指令插入或打扰,这是 pipeline 所不具备的。

        并且,Lua 脚本中支持一些简单的逻辑处理比如使用命令读取值并在 Lua 脚本中进行处理,这同样是 pipeline 所不具备的。

Lua 脚本缺陷:

  • 如果 Lua 脚本运行时出错并中途结束,之后的操作不会进行,但是之前已经发生的写操作不会撤销,所以即使使用了 Lua 脚本,也不能实现类似数据库回滚的原子性。
  • Redis Cluster 下 Lua 脚本的原子操作也无法保证了,原因同样是无法保证所有的 key 都在同一个 hash slot(哈希槽)上。

大量key集中过期

对于过期 key,Redis 采用的是 定期删除+惰性/懒汉式删除 策略。

        定期删除执行过程中,如果突然遇到大量过期 key 的话,客户端请求必须等待定期清理过期 key 任务线程执行完成,因为这个这个定期任务线程是在 Redis 主线程中执行的。这就导致客户端请求没办法被及时处理,响应速度会比较慢。

如何解决呢? 下面是两种常见的方法:

  1. 给 key 设置随机过期时间。
  2. 开启 lazy-free(惰性删除/延迟释放) 。lazy-free 特性是 Redis 4.0 开始引入的,指的是让 Redis 采用异步方式延迟释放 key 使用的内存,将该操作交给单独的子线程处理,避免阻塞主线程。

redis bigkey

        如果一个 key 对应的 value 所占用的内存比较大,那这个 key 就可以看作是 bigkey。具体多大才算大呢?有一个不是特别精确的参考标准:

  • String 类型的 value 超过 1MB
  • 复合类型(List、Hash、Set、Sorted Set 等)的 value 包含的元素超过 5000 个(不过,对于复合类型的 value 来说,不一定包含的元素越多,占用的内存就越多)。

bigkey 通常是由于下面这些原因产生的:

  • 程序设计不当,比如直接使用 String 类型存储较大的文件对应的二进制数据。
  • 对于业务的数据规模考虑不周到,比如使用集合类型的时候没有考虑到数据量的快速增长。
  • 未及时清理垃圾数据,比如哈希中冗余了大量的无用键值对。

        bigkey 除了会消耗更多的内存空间和带宽,还会对性能造成比较大的影响。

        在 Redis 常见阻塞原因总结这篇文章中我们提到:大 key 还会造成阻塞问题。具体来说,主要体现在下面三个方面:

  1. 客户端超时阻塞:由于 Redis 执行命令是单线程处理,然后在操作大 key 时会比较耗时,那么就会阻塞 Redis,从客户端这一视角看,就是很久很久都没有响应。
  2. 网络阻塞:每次获取大 key 产生的网络流量较大,如果一个 key 的大小是 1 MB,每秒访问量为 1000,那么每秒会产生 1000MB 的流量,这对于普通千兆网卡的服务器来说是灾难性的。
  3. 工作线程阻塞:如果使用 del 删除大 key 时,会阻塞工作线程,这样就没办法处理后续的命令。

        大 key 造成的阻塞问题还会进一步影响到主从同步和集群扩容。

        综上,大 key 带来的潜在问题是非常多的,我们应该尽量避免 Redis 中存在 bigkey。

如何发现bigkey

使用 Redis 自带的 --bigkeys 参数来查找。 

使用 Redis 自带的 SCAN 命令

借助开源工具分析 RDB 文件。

借助公有云的 Redis 分析服务。

如何处理bigkey

bigkey 的常见处理以及优化办法如下(这些方法可以配合起来使用):

  • 分割 bigkey:将一个 bigkey 分割为多个小 key。例如,将一个含有上万字段数量的 Hash 按照一定策略(比如二次哈希)拆分为多个 Hash。
  • 手动清理:Redis 4.0+ 可以使用 UNLINK 命令来异步删除一个或多个指定的 key。Redis 4.0 以下可以考虑使用 SCAN 命令结合 DEL 命令来分批次删除。
  • 采用合适的数据结构:例如,文件二进制数据不使用 String 保存、使用 HyperLogLog 统计页面 UV、Bitmap 保存状态信息(0/1)。
  • 开启 lazy-free(惰性删除/延迟释放) :lazy-free 特性是 Redis 4.0 开始引入的,指的是让 Redis 采用异步方式延迟释放 key 使用的内存,将该操作交给单独的子线程处理,避免阻塞主线程。

redis hotkey 

        如果一个 key 的访问次数比较多且明显多于其他 key 的话,那这个 key 就可以看作是 hotkey(热 Key)。hotkey 出现的原因主要是某个热点数据访问量暴增,如重大的热搜事件、参与秒杀的商品。

hotkey危害

        处理 hotkey 会占用大量的 CPU 和带宽,可能会影响 Redis 实例对其他请求的正常处理。此外,如果突然访问 hotkey 的请求超出了 Redis 的处理能力,Redis 就会直接宕机。这种情况下,大量请求将落到后面的数据库上,可能会导致数据库崩溃。因此,hotkey 很可能成为系统性能的瓶颈点,需要单独对其进行优化,以确保系统的高可用性和稳定性。

如何发现hotkey 

使用 Redis 自带的 --hotkeys 参数来查找。

使用MONITOR 命令。 

借助开源项目。 

根据业务情况提前预估。

业务代码中记录分析。

借助公有云的 Redis 分析服务。

如何处理hotkey
  • 读写分离:主节点处理写请求,从节点处理读请求。
  • 使用 Redis Cluster:将热点数据分散存储在多个 Redis 节点上。
  • 二级缓存:hotkey 采用二级缓存的方式进行处理,将 hotkey 存放一份到 JVM 本地内存中(可以用 Caffeine)

慢查询命令

        Redis 慢查询统计的是命令执行这一步骤的耗时,慢查询命令也就是那些命令执行时间较长的命令。

如何找到慢查询命令

        在 redis.conf 文件中,我们可以使用 slowlog-log-slower-than 参数设置耗时命令的阈值,并使用 slowlog-max-len 参数设置耗时命令的最大记录条数。

        当 Redis 服务器检测到执行时间超过 slowlog-log-slower-than阈值的命令时,就会将该命令记录在慢查询日志(slow log) 中,这点和 MySQL 记录慢查询语句类似。当慢查询日志超过设定的最大记录条数之后,Redis 会把最早的执行命令依次舍弃。

        ⚠️注意:由于慢查询日志会占用一定内存空间,如果设置最大记录条数过大,可能会导致内存占用过高的问题。

        获取慢查询日志的内容很简单,直接使用SLOWLOG GET 命令即可。

慢查询日志中的每个条目都由以下六个值组成:

  1. 唯一渐进的日志标识符。
  2. 处理记录命令的 Unix 时间戳。
  3. 执行所需的时间量,以微秒为单位。
  4. 组成命令参数的数组。
  5. 客户端 IP 地址和端口。
  6. 客户端名称。

redis生产问题 

缓存穿透 

        缓存穿透说简单点就是大量请求的 key 是不合理的,根本不存在于缓存中,也不存在于数据库中 。这就导致这些请求直接到了数据库上,根本没有经过缓存这一层,对数据库造成了巨大的压力,可能直接就被这么多请求弄宕机了。

解决方法

        最基本的就是首先做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库 id 不能小于 0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等。

1)缓存无效 key

        如果缓存和数据库都查不到某个 key 的数据就写一个到 Redis 中去并设置过期时间,具体命令如下:SET key value EX 10086 。这种方式可以解决请求的 key 变化不频繁的情况,如果黑客恶意攻击,每次构建不同的请求 key,会导致 Redis 中缓存大量无效的 key 。很明显,这种方案并不能从根本上解决此问题。如果非要用这种方式来解决穿透问题的话,尽量将无效的 key 的过期时间设置短一点比如 1 分钟。

另外,这里多说一嘴,一般情况下我们是这样设计 key 的:表名:列名:主键名:主键值

2)布隆过滤器

        布隆过滤器是一个非常神奇的数据结构,通过它我们可以非常方便地判断一个给定数据是否存在于海量数据中。我们可以把它看作由二进制向量(或者说位数组)和一系列随机映射函数(哈希函数)两部分组成的数据结构。相比于我们平时常用的 List、Map、Set 等数据结构,它占用空间更少并且效率更高,但是缺点是其返回的结果是概率性的,而不是非常准确的。理论情况下添加到集合中的元素越多,误报的可能性就越大。并且,存放在布隆过滤器的数据不容易删除。

        把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会走下面的流程。

3)接口限流

        根据用户或者 IP 对接口进行限流,对于异常频繁的访问行为,还可以采取黑名单机制,例如将异常 IP 列入黑名单。

        后面提到的缓存击穿和雪崩都可以配合接口限流来解决,毕竟这些问题的关键都是有很多请求落到了数据库上造成数据库压力过大。

缓存击穿

        缓存击穿中,请求的 key 对应的是 热点数据 ,该数据 存在于数据库中,但不存在于缓存中(通常是因为缓存中的那份数据已经过期) 。这就可能会导致瞬时大量的请求直接打到了数据库上,对数据库造成了巨大的压力,可能直接就被这么多请求弄宕机了。

解决方法
  1. 永不过期(不推荐):设置热点数据永不过期或者过期时间比较长。
  2. 提前预热(推荐):针对热点数据提前预热,将其存入缓存中并设置合理的过期时间比如秒杀场景下的数据在秒杀结束之前不过期。
  3. 加锁(看情况):在缓存失效后,通过设置互斥锁确保只有一个请求去查询数据库并更新缓存。

缓存雪崩 

        缓存在同一时间大面积的失效,导致大量的请求都直接落到了数据库上,对数据库造成了巨大的压力。  

解决方法

针对 Redis 服务不可用的情况:

  1. Redis 集群:采用 Redis 集群,避免单机出现问题整个缓存服务都没办法使用。Redis Cluster 和 Redis Sentinel 是两种最常用的 Redis 集群实现方案。
  2. 多级缓存:设置多级缓存,例如本地缓存+Redis 缓存的二级缓存组合,当 Redis 缓存出现问题时,还可以从本地缓存中获取到部分数据。

针对大量缓存同时失效的情况:

  1. 设置随机失效时间(可选):为缓存设置随机的失效时间,例如在固定过期时间的基础上加上一个随机值,这样可以避免大量缓存同时到期,从而减少缓存雪崩的风险。
  2. 提前预热(推荐):针对热点数据提前预热,将其存入缓存中并设置合理的过期时间比如秒杀场景下的数据在秒杀结束之前不过期。
  3. 持久缓存策略(看情况):虽然一般不推荐设置缓存永不过期,但对于某些关键性和变化不频繁的数据,可以考虑这种策略。

常见的缓存预热方式有两种:

  1. 使用定时任务,比如 xxl-job,来定时触发缓存预热的逻辑,将数据库中的热点数据查询出来并存入缓存中。
  2. 使用消息队列,比如 Kafka,来异步地进行缓存预热,将数据库中的热点数据的主键或者 ID 发送到消息队列中,然后由缓存服务消费消息队列中的数据,根据主键或者 ID 查询数据库并更新缓存。

        缓存雪崩和缓存击穿比较像,但缓存雪崩导致的原因是缓存中的大量或者所有数据失效,缓存击穿导致的原因主要是某个热点数据不存在与缓存中(通常是因为缓存中的那份数据已经过期)。 

如何保证缓存与数据库数据的一致性

  • 缓存失效时间变短(不推荐,治标不治本):我们让缓存数据的过期时间变短,这样的话缓存就会从数据库中加载数据。另外,这种解决办法对于先操作缓存后操作数据库的场景不适用。
  • 增加缓存更新重试机制(常用):如果缓存服务当前不可用导致缓存删除失败的话,我们就隔一段时间进行重试,重试次数可以自己定。不过,这里更适合引入消息队列实现异步重试,将删除缓存重试的消息投递到消息队列,然后由专门的消费者来重试,直到成功。虽然说多引入了一个消息队列,但其整体带来的收益还是要更高一些。

Redis常见阻塞原因总结 

Redis常见阻塞原因总结 | JavaGuide 

redis集群

redis使用规范 

  • 使用连接池:避免频繁创建关闭客户端连接。
  • 尽量不使用 O(n)指令,使用 O(n) 命令时要关注 n 的数量:像 KEYS *HGETALLLRANGESMEMBERSSINTER/SUNION/SDIFF等 O(n) 命令并非不能使用,但是需要明确 n 的值。另外,有遍历的需求可以使用 HSCANSSCANZSCAN 代替。
  • 使用批量操作减少网络传输:原生批量操作命令(比如 MGETMSET等等)、pipeline、Lua 脚本。
  • 尽量不适用 Redis 事务:Redis 事务实现的功能比较鸡肋,可以使用 Lua 脚本代替。
  • 禁止长时间开启 monitor:对性能影响比较大。
  • 控制 key 的生命周期:避免 Redis 中存放了太多不经常被访问的数据

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值