Redis bigkey(大 Key)
什么是 bigkey?
简单来说,如果一个 key 对应的 value 所占用的内存比较大,那这个 key 就可以看作是 bigkey。
体多大才算大呢?有一个不是特别精确的参考标准:
String 类型的 value 超过 1MB
复合类型(List、Hash、Set、Sorted Set 等)的 value 包含的元素超过 5000 个(不过,对于复合类型的 value 来说,不一定包含的元素越多,占用的内存就越多)
bigkey 是怎么产生的?
bigkey 通常是由于下面这些原因产生的:
1、程序设计不当,比如直接使用 String 类型存储较大的文件对应的二进制数据。
2、对于业务的数据规模考虑不周到,比如使用集合类型的时候没有考虑到数据量的快速增长。
3、未及时清理垃圾数据,比如哈希中冗余了大量的无用键值对。
有什么危害?
bigkey 除了会消耗更多的内存空间和带宽,还会对性能造成比较大的影响,比如造成阻塞问题。
**客户端超时阻塞:**由于 Redis 执行命令是单线程处理,然后在操作大 key 时会比较耗时,那么就会阻塞 Redis,从客户端这一视角看,就是很久很久都没有响应。
**网络阻塞:**每次获取大 key 产生的网络流量较大,如果一个 key 的大小是 1 MB,每秒访问量为 1000,那么每秒会产生 1000MB 的流量,这对于普通千兆网卡的服务器来说是灾难性的。
**工作线程阻塞:**如果使用 del 删除大 key 时,会阻塞工作线程,这样就没办法处理后续的命令。
如何处理 bigkey?
bigkey 的常见处理以及优化办法如下(这些方法可以配合起来使用):
**1、分割 bigkey:**将一个 bigkey 分割为多个小 key。例如,将一个含有上万字段数量的 Hash 按照一定策略(比如二次哈希)拆分为多个 Hash。
**2、手动清理:**Redis 4.0+ 可以使用 UNLINK 命令来异步删除一个或多个指定的 key。Redis 4.0 以下可以考虑使用 SCAN 命令结合 DEL 命令来分批次删除。
**3、采用合适的数据结构:**例如,文件二进制数据不使用 String 保存、使用 HyperLogLog 统计页面 UV、Bitmap 保存状态信息(0/1)。
4、开启 lazy-free(惰性删除/延迟释放) :lazy-free 特性是 Redis 4.0 开始引入的,指的是让 Redis 采用异步方式延迟释放 key 使用的内存,将该操作交给单独的子线程处理,避免阻塞主线程。
Redis hotkey(热 Key)
什么是 hotkey?
如果一个 key 的访问次数比较多且明显多于其他 key 的话,那这个 key 就可以看作是 hotkey(热 Key)。
hotkey 出现的原因主要是某个热点数据访问量暴增,如重大的热搜事件、参与秒杀的商品。
hotkey 有什么危害?
**处理 hotkey 会占用大量的 CPU 和带宽,可能会影响 Redis 实例对其他请求的正常处理。此外,如果突然访问 hotkey 的请求超出了 Redis 的处理能力,Redis 就会直接宕机。**这种情况下,大量请求将落到后面的数据库上,可能会导致数据库崩溃。
如何解决 hotkey?
hotkey 的常见处理以及优化办法如下(这些方法可以配合起来使用):
1、读写分离:主节点处理写请求,从节点处理读请求。
2、使用 Redis Cluster:将热点数据分散存储在多个 Redis 节点上。
3、二级缓存:hotkey 采用二级缓存的方式进行处理,将 hotkey 存放一份到 JVM 本地内存中(可以用 Caffeine)。
Redis生产问题
缓存穿透
缓存穿透就是大量请求的 key是不合理的,数据根本不存在于缓存中,也不存在于数据库中 。只是key被人大量请求
这就导致这些请求直接到了数据库上,对数据库造成了巨大的压力,可能直接就被这么多请求弄宕机了。
解决方法:
1、缓存空对象: 我们可以缓存一个空对象,让不合理请求得到一个空对象,这种方式适用于请求的key变化不明显的情况。
优点:简单
缺点:消耗内存,并且当有这个数据的时候,会造成数据不一致的问题。
**2、布隆过滤器:**通过它我们可以非常方便地判断一个给定数据是否存在于海量数据中,把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话,在进入到缓存中进行判断。
布隆过滤器的实现:使用位图来把所有的key通过多个hash函数保存在位图中进行查询。
布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。
数组越小,产生的误判率越小。
3、做好参数校验,增加参数的复杂度
缓存击穿
缓存击穿问题也叫热点Key问题,就是一个会被高并发访问的数据失效了,并且缓存重建比较复杂,无数的请求访问会在瞬间给数据库带来巨大的冲击。
常见的解决方案有两种:
- 互斥锁
- 逻辑过期:嗯!逻辑过期并不是真正的过期,只是用一个字段来存储过期时间,当一个线程发现数据已经过期的时候,就会新开启一个线程来重建数据,这个时候其他线程获得的都是过期的数据,只到缓存重建完成。
**互斥锁方案:**由于保证了互斥性,所以数据一致,且实现简单,因为仅仅只需要加一把锁而已,也没其他的事情需要操心,所以没有额外的内存消耗,缺点在于有锁就有死锁问题的发生,且只能串行执行性能肯定受到影响。
逻辑过期方案: 线程读取过程中不需要等待,性能好,有一个额外的线程持有锁去进行重构数据,但是在重构数据完成前,其他的线程只能返回之前的数据,且实现起来麻烦
缓存穿透和缓存击穿有什么区别?
缓存穿透中,请求的 key 既不存在于缓存中,也不存在于数据库中。
缓存击穿中,请求的 key 对应的是 热点数据 ,该数据存在于数据库中,但不存在于缓存中(通常是因为缓存中的那份数据已经过期)。
缓存雪崩
大量的缓存在同一时间的失效或者redis服务器宕机,导致大量的请求都直接落到了数据库上,对数据库造成了巨大的压力。
例如:数据库中的大量数据在同一时间过期,这个时候突然有大量的请求需要访问这些过期的数据。这就导致大量的请求直接落到数据库上,对数据库造成了巨大的压力。
哪些解决办法?
针对 Redis 服务不可用的情况:
1、采用 Redis 集群提高服务的可用行:哨兵模式、集群模式
2、限流,避免同时处理大量的请求。ngxin或spring cloud gateway
针对大量缓存同一时间失效的情况:
1、设置不同的失效时间比如随机设置缓存的失效时间。
2、给业务添加多级缓存。 Caffeine
缓存雪崩和缓存击穿有什么区别?
缓存雪崩导致的原因是缓存中的大量数据失效,缓存击穿导致的原因主要是因为缓存中的某个热点数据失效。
缓存三兄弟都可以使用限流来作为保底策略。
Reids持久化机制
使用缓存的时候,我们经常需要对内存中的数据进行持久化也就是将内存中的数据写入到硬盘中。大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了做数据同步(比如 Redis 集群的主从节点通过 RDB 文件同步数据)。
Redis 不同于 Memcached 的很重要一点就是,Redis 支持持久化,而且支持 3 种持久化方式:
- 快照(snapshotting,RDB)
- 只追加文件(append-only file, AOF)
- RDB 和 AOF 的混合持久化(Redis 4.0 新增)
这两种持久化方式有什么区别呢?
候选人:RDB是一个快照文件,它是把redis内存存储的数据写到磁盘上,当redis实例宕机恢复数据的时候,方便从RDB的快照文件中恢复数据。
AOF的含义是追加文件,当redis操作写命令的时候,都会存储这个AOF文件中,它相当于命令日志文件,当redis实例宕机恢复数据的时候,会从这个文件中再次执行一遍命令来恢复数据。
即RDB是定时对整个Redis数据做快照,AOF是记录了每次更改Redis数据的命令。
什么是 RDB 持久化?
RDB是一个快照文件,它是把redis内存存储的数据写到磁盘上,当redis实例宕机恢复数据的时候,方便从RDB的快照文件中恢复数据。
RDB 创建快照时会阻塞主线程吗?
Redis 提供了两个命令来生成 RDB 快照文件:
save
: 主进程来执行RDB,会阻塞所有的命令;bgsave
: fork 出一个子进程,子进程执行,不会阻塞 Redis 主进程,默认选项
RDB的执行原理?
什么是 AOF 持久化?
AOF的含义是追加文件,当redis操作写命令的时候,都会存储这个AOF文件中,当redis实例宕机恢复数据的时候,会从这个文件中再次执行一遍命令来恢复数据。
AOF 持久化就是当我们每执行一条更改 Redis 中的数据的命令,Redis 就会将该命令写入到 AOF 缓冲区 server.aof_buf
中,然后再写入到 AOF 文件中(这个文件此时还在系统内核缓存区未同步到磁盘),最后再根据持久化方式来决定何时将系统内核缓存区的数据同步到硬盘中的。
AOF 工作基本流程是怎样的?
AOF 持久化功能的实现可以简单分为 5 步:
- 命令追加(append):所有的写命令会追加到 AOF 缓冲区中。
- 文件写入(write):将 AOF 缓冲区的数据写入到 AOF 文件中。这一步需要调用
write
函数(系统调用),write
将数据写入到了系统内核缓冲区之后直接返回了(延迟写)。注意!!!此时并没有同步到磁盘。 - 文件同步(fsync):AOF 缓冲区根据对应的持久化方式(
fsync
策略)向硬盘做同步操作。这一步需要调用fsync
函数(系统调用),fsync
针对单个文件操作,对其进行强制硬盘同步,fsync
将阻塞直到写入磁盘完成后返回,保证了数据持久化。 - 文件重写(rewrite):随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的。
- 重启加载(load):当 Redis 重启时,可以加载 AOF 文件进行数据恢复。
AOF 持久化方式有哪些(三种刷盘方式)?
在 Redis 的配置文件中存在三种不同的 AOF 持久化方式( fsync
策略),它们分别是:
appendfsync always
:主线程调用write
执行写操作后,后台线程(aof_fsync
线程)立即会调用fsync
函数同步 AOF 文件(刷盘),fsync
完成后线程返回,这样会严重降低 Redis 的性能(write
+fsync
)。appendfsync everysec
:主线程调用write
执行写操作后立即返回,由后台线程(aof_fsync
线程)每秒钟调用fsync
函数(系统调用)同步一次 AOF 文件(write
+fsync
,fsync
间隔为 1 秒)appendfsync no
:主线程调用write
执行写操作后立即返回,让操作系统决定何时进行同步,Linux 下一般为 30 秒一次(write
但不fsync
,fsync
的时机由操作系统决定)。
AOF 为什么是在执行完命令之后记录日志?
关系型数据库(如 MySQL)通常都是执行命令之前记录日志(方便故障恢复),而 Redis AOF 持久化机制是在执行完命令之后再记录日志。
为什么是在执行完命令之后记录日志呢?
- 避免额外的检查开销,AOF 记录日志不会对命令进行语法检查;
- 在命令执行完之后再记录,不会阻塞当前的命令执行。
这样也带来了风险(我在前面介绍 AOF 持久化的时候也提到过):
-
如果刚执行完命令 Redis 就宕机会导致对应的修改丢失;
-
可能会阻塞后续其他命令的执行(AOF 记录日志是在 Redis 主线程中进行的)
AOF 重写了解吗?
当 AOF 变得太大时,Redis 能够在后台自动重写 AOF 产生一个新的 AOF 文件,这个新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小。
AOF 校验机制了解吗?
AOF 校验机制是 Redis 在启动时对 AOF 文件进行检查,以判断文件是否完整,是否有损坏或者丢失的数据。这个机制的原理其实非常简单,就是通过使用一种叫做 校验和(checksum) 的数字来验证 AOF 文件。
Redis 4.0 对于持久化机制做了什么优化?
Redis 4.0 开始支持 RDB 和 AOF 的混合持久化,即当AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点,快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。
如何选择 RDB 和 AOF?(即它们的优缺点)
RDB 比 AOF 优秀的地方:
1、RDB 文件存储的内容是经过压缩的二进制数据,文件很小,适合做数据的备份,灾难恢复。AOF 文件存储的是每一次写命令,通常会比 RDB 文件大很多。
2、使用 RDB 文件恢复数据,直接解析还原数据即可,不需要一条一条地执行命令,速度非常快。而 AOF 则需要依次执行每个写命令,速度非常慢。
AOF 比 RDB 优秀的地方:
1、RDB 的数据安全性不如 AOF,实时性没有AOF好。
2、AOF 以一种易于理解和解析的格式包含所有操作的日志。你可以轻松地导出 AOF 文件进行分析,你也可以直接操作 AOF 文件来解决一些问题
Redis内存碎片
什么是内存碎片?
内存碎片简单地理解为一些不可用的空闲内存。
比如分配了32 字节的连续内存空间,而你存储数据实际只需要使用 24 字节内存空间,那这多余出来的 8 字节内存空间如果后续没办法再被分配存储其他数据的话,就可以被称为内存碎片。
为什么会有 Redis 内存碎片?
1、Redis 存储存储数据的时候向操作系统申请的内存空间可能会大于数据实际需要的存储空间。
2、频繁修改 Redis 中的数据也会产生内存碎片。当 Redis 中的某个数据删除时,Redis 通常不会轻易释放内存给操作系统。
三种常用的缓存读写策略
Cache Aside Pattern(旁路缓存模式)
写:
先更新 db
然后直接删除 cache 。
读 :
从 cache 中读取数据,读取到就直接返回
cache 中读取不到的话,就从 db 中读取数据返回
再把数据放到 cache 中。
Read/Write Through Pattern(读写穿透)
Read/Write Through Pattern 中服务端把 cache 视为主要数据存储,从中读取数据并将数据写入其中。cache 服务负责将此数据读取和写入 db,从而减轻了应用程序的职责。
写(Write Through):
先查 cache,cache 中不存在,直接更新 db。
cache 中存在,则先更新 cache,然后 cache 服务自己更新 db(同步更新 cache 和 db)。
读(Read Through):
从 cache 中读取数据,读取到就直接返回 。
读取不到的话,先从 db 加载,写入到 cache 后返回响应。
Write Behind Pattern(异步缓存写入)
Write Behind Pattern 和 Read/Write Through Pattern 很相似,两者都是由 cache 服务来负责 cache 和 db 的读写。
但是,两个又有很大的不同:Read/Write Through 是同步更新 cache 和 db,而 Write Behind 则是只更新缓存,不直接更新 db,而是改为异步批量的方式来更新 db。