一 使用缓存的合理性问题



热点数据,缓存才有价值
・频繁修改的数据,看情况考虑使用缓存
・数据不一致性
・缓存更新机制
•缓存可用性
・缓存服务降级
•缓存预热
・缓存穿透


二 常见缓存

1.缓存雪崩



缓存雪崩我们可以简单的理解为:由于原有缓存失效,新缓存还未到这个期间(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造 成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃
缓存正常从Redis中获取,示意图如下:

面试题-缓存+redis_持久化



缓存失效瞬间示意图如下:

面试题-缓存+redis_缓存_02

1.缓存雪崩的解决方案



(1)碰到这种情况,一般并发量不是特别多的时候,使用最多的解决方案 是加锁排队,伪代码如下:

加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。假设在高并发 下,缓存重建期间key是锁着的,这是过来1000个请求999个都在阻塞的。同样会导致用户等待超时,这是个治标不治本的方法!
注意:
加锁排队的解决方式是处理分布式环境的并发问题,有可能还要解决分布式锁的问题;线程还会被阻塞,用户体验很差!因此,在真正的高并发场景下很少使用!

面试题-缓存+redis_redis_03



(2)给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果 缓存标记失效,则更新数据缓存,

解释说明:
1、缓存标记:记录缓存数据是否过期,如果过期会触发通知另外的线程在后台 去更新实际key的缓存;
2、缓存数据:它的过期时间比缓存标记的时间延长1倍,例:标记缓存时间30分钟,数据缓存设置为60分钟。这样,当缓存标记key过期后,实际缓存还能把旧 数据返回给调用端,直到另外的线程在后台更新完成后,才会返回新缓存。

关于缓存崩溃的解决方法,这里提出了三种方案:使用锁或队列、设置过期标志 更新缓存、为key设置不同的缓存失效时间,还有一各被称为“二级缓存”的解决方 法,有兴趣的读者可以自行研究。

面试题-缓存+redis_缓存_04

2.缓存穿透



缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回
空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。


1.缓存穿透解决方案



(1)采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中, 一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
(2)如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这样第二次到缓存中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴!

面试题-缓存+redis_缓存_05



把空结果也给缓存起来,这样下次同样的请求就可以直接返回空了,即可以避免当查询的值为空时引起的缓存穿透。同时也可以单独设置个缓存区域存储空值,对要查询的key进行预先校验,然后再放行给后面的正常缓存处理逻辑。


3.缓存预热



缓存预热就是系统上线后,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!


1.缓存预热解决方案



(1)直接写个缓存刷新页面,上线时手工操作下;
(2)数据量不大,可以在项目启动的时候自动进行加载;
(3)定时刷新缓存;


4.缓存更新



除了缓存服务器自带的缓存失效策略之外(Redis默认的有6种策略可供选择) 我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:

(1)定时去清理过期的缓存;
(2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话 就去底层系统得到新数据并更新缓存。

两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺 点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方 案,大家可以根据自己的应用场景来权衡。


5.缓存降级



当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根 据一些关键数据进行自动降级,也可以配置开关实现人工降级。
降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级 的(如加入购物车、结算)。
在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出 哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:
(1)一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
(2)警告:有些服务在一段时间内成功率有波动(如在95〜100%之间),可以自动降级或人工降级,并发送告警;
(3)错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。


6.缓存崩溃/击穿



碰到这种情况,一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。
・加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。假设在高并发下,缓存重建期间key是锁着的,这时过来1000个请求999个都在阻塞的。同样会导致用户等待超时,这是个治标不治本的方法。


三 缓存降级

1.页面降级



在大促或者某些特殊情况下,某些页面占用了一些稀缺服务资源,在紧急情况下可以对其整个降级,以达到丢卒保帅;


2.页面片段降级



比如商品详情页中的商家部分因为数据错误了,此时需要对其进行降级;


3.页面异步请求降级



比如商品详情页上有推荐信息/配送至等异步加载的请求,如果这些信息响应慢或者后端服务有问题,可以进行降级;


4.服务功能降级



比如渲染商品详情页时需要调用一些不太重要的服务:相关分类、热销榜等,而这些服务在异常情况下直接不获取,即降级即可;


5.读降级



比如多级缓存模式,如果后端服务有问题,可以降级为只读缓存,这种方式适用于对读一致性要求不高的场景;


6.写降级



比如秒杀抢购,我们可以只进行Cache的更新,然后异步同步扣减库存到DB,保 证最终一致性即可,此时可以将DB降级为Cache。


7.爬虫降级



在大促活动时,可以将爬虫流量导向静态页或者返回空数据,从而保护后端稀缺资源。


四 Redis使用场景



1 缓存
2 会话缓存
3 时效性
4 访问频率
5 计数器
6 社交列表
7 记录用户判定信息
8 交集、并集和差集
10 热门列表与排行榜
11 最新动态
12 消息队列


五 Redis有哪些类型



1 String:字符串
2 Hash:字典
3 List :列表
4 Set :集合
5 Sorted Set :有序集合


六 Redis持久化机制

面试题-缓存+redis_持久化_06



Redis支持两种持久化存储方式: RDB(快照)和AOF. RDB是每隔一段时间存储一次文件,属于全量备份,RDB是内存数据的二进制序列化形式,在存储上非常紧凑;而AOF默认是每秒存储一次操作的写命令,属于连续的增量备份,AOF日志记录的是内存数据修改的写指令记.另外 AOF 日志在长期的运行过程中会变得无比庞大,数据库重启时需要加载 AOF 日志进行指令重放,这个时间就会无比漫长,所以需要定期进行 AOF 重写,给 AOF 日志进行瘦身.

Redis是支持持久化的内存数据库,也就是说redis需要经常将内存中的数据同步到硬盘来保证持久化.没有持久化的redis和memcache一样,相当于一个纯内存数据库.


1.RDB方案简介



RDB持久化方式会在一个特定的间隔保存那个时间点的一个数据快照
RDB持久化方案是将当前进程中的数据生成快照(一个临时文件),然后把这个快照保存到硬盘(因此也称作快照持久化),保存的文件后缀是rdb.
而当持久化结束后,在Redis重启的时候,会用这个快照替换上次持久化的文件,从而达到数据恢复的目的.


2.RDB持久化方案的实现



Redis默认会将数据在内存中以快照的方式写入到一个二进制文件中,该文件默认名称为dump.rdb.我们可以通过配置文件来设置自动做快照的持久化方式,比如可以配置redis在n秒内如果m个key修改,就自动做快照.

vim /usr/local/redis3.2.2/conf/redis.conf 修改配置文件
RDB默认就已开启,redis.conf中的具体配置参数如下:

save 900 1 #900秒内,超过1个key被修改,则发起快照保存;
save 300 10 #300秒内,超过10个key被修改,则发起快照保存;
save 60 10000 #60秒内,超过10000个key被修改,则发起快照保存.

dbfilename dump.rdb 持久化数据存储在本地的文件;

dir ./ 持久化数据存储在本地的路径,如果是在/redis/redis-3.0.6/src下启动的redis-cli,则数据会存储在当前src目录下.

面试题-缓存+redis_缓存_07

当满足save的条件时,比如更改了1个key,900s后会将数据写入临时文件,持久化完成后将临时文件替换旧的dump.rdb(存储数据的节点是到触发时间时的的节点).


3.使用RDB恢复数据



自动持久化的数据存储到dump.rdb后,实际只要重启redis服务即可完成数据恢复(启动redis的server时会从dump.rdb中先同步数据).

使用命令进行持久化save存储:
./redis-cli -h ip -p port save
./redis-cli -h ip -p port
bgsave
一个是在前台进行存储,一个是在后台进行存储.

面试题-缓存+redis_缓存_08

4.RDB的持久化过程



RDB持久化的过程,相当于在执行bgsave命令.该命令执行过程如下图所示:
如图所示,主线程需要调用系统函数fork(),构建出一个子进程进行持久化.很不幸的是,在构建子进程的过程中,父进程就会阻塞,无法响应客户端的请求.

而且,在测试中发现,fork函数在虚拟机上较慢,真机上较快.考虑到现在都是部署在docker容器中,很少部署在真机上,为了性能,所以在master上不建议打开RDB持久化.

面试题-缓存+redis_缓存_09

5.RDB方案的优点



1️⃣. RDB文件紧凑,所以适合备份,全量复制得场景.例如每 6 小时执行一次 bgsave,保存到文件系统之类的;

2️⃣. Redis 加载 RDB 恢复数据远远快于 AOF;

3️⃣. RDB使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了redis的高性能.


6.RDB方案的缺点



1️⃣.无法实现秒级持久化;

2️⃣.老版本的Redis无法兼容新版本的RDB;

3️⃣.RDB是每间隔一段时间进行一次持久化,如果持久化之间redis发生故障,数据可能会丢失,所以这种方式更适合对数据完整性要求不严格的时候.


7.RDB方案的特点



1️⃣. RDB方案是一种快照模式,即保存的是key-value格式的数据内容;

2️⃣. save 导致 redis 同步阻塞,基本已经废弃.bgsave 则不会导致阻塞,但也有缺点: 在 fork 时,需要增加内存服务器开销,因为当内存不够时,将使用虚拟内存,导致阻塞 Redis 运行,所以需要保证空闲内存足够;

3️⃣. 默认执行 shutdown 时,如果没有开启 AOF,则自动执行 bgsave;

4️⃣. 每次的 RDB 文件都是替换的.

5️⃣. RDB 有 2 种持久方式,同步 save 模式和异步 bgsave 模式.由于 save 是同步的,所以可以保证数据一致性,而 bgsave 则不能;


8.RDB方案的优化



Redis 会采用 LZF算法压缩 RDB 文件,让最终的 RDB 文件远小于内存大小,默认开启,但会消耗 CPU.


9.RDB方案的实现原理



我们知道 Redis 是单线程程序,这个线程要同时负责多个客户端套接字的并发读写操作和内存数据结构的逻辑读写.

在服务线上请求的同时,Redis 还需要进行内存快照,内存快照要求 Redis 必须进行文件 IO 操作,可文件 IO 操作是不能使用多路复用 API.

这意味着单线程在服务线上请求的同时,还要进行文件 IO 操作,而文件 IO 操作会严重拖累服务器请求的性能.

还有个重要的问题,为了不阻塞线上的业务,Redis 就需要一边持久化,一边响应客户端的请求.持久化的同时,内存数据结构还在改变,比如一个大型的 hash 字典正在持久化,结果一个请求过来把它给删掉了,可是还没持久化完呢,这该怎么办呢?

Redis 使用操作系统的多进程 COW(Copy On Write)机制来实现快照持久化,这个机制很有意思,也很少人知道.多进程 COW 也是鉴定程序员知识广度的一个重要指标.

Redis 在持久化时会调用 glibc 的函数 fork 产生一个子进程,快照持久化完全交给子进程来处理,父进程继续处理客户端请求.子进程刚刚产生时,它和父进程共享内存里面的代码段和数据段.这时你可以把父子进程想象成一个连体婴儿,它们在共享身体.这是 Linux 操作系统的机制,为了节约内存资源,所以尽可能让它们共享起来.在进程分离的一瞬间,内存的增长几乎没有明显变化.

子进程做数据持久化,不会修改现有的内存数据结构,它只是对数据结构进行遍历读取,然后序列化写到磁盘中.但是父进程不一样,它必须持续服务客户端请求,然后对内存数据结构进行不间断的修改.这个时候就会使用操作系统的 COW 机制来进行数据段页面的分离.

数据段是由很多操作系统的页面组合而成,当父进程对其中一个页面的数据进行修改时,会将被共享的页面复制一份分离出来,然后对这个复制的页面进行修改.这时子进程相应的页面是没有变化的,还是进程产生时那一瞬间的数据.

随着父进程修改操作的持续进行,越来越多的共享页面被分离出来,内存就会持续增长,但是也不会超过原有数据内存的 2 倍大小.另外一个 Redis 实例里冷数据占的比例往往是比较高的,所以很少会出现所有的页面都会被分离,被分离的往往只有其中一部分页面.每个页面的大小只有 4KB,一个 Redis 实例里面一般都会有成千上万个页面.

子进程因为数据没有变化,它能看到的内存里的数据在进程产生的一瞬间就凝固了,再也不会改变,这也是为什么 Redis 的持久化叫“快照”的原因.接下来子进程就可以非常安心地遍历数据,进行序列化写磁盘了.


10.AOF方案



AOF持久化方式则会记录每一个服务器收到的写操作。在服务启动时,这些记录 的操作会逐条执行从而重建出原来的数据。写操作命令记录的格式跟Redis协议 —致,以追加的方式进行保存
Redis的持久化是可以禁用的,就是说你可以让数据的生命周期只存在于服务器 的运行时间里。两种方式的持久化是可以同时存在的,但是当Redis重启时, AOF文件会被优先用于重建数据。


11.AOF持久化方案简介



AOF(Append Only File)持久化是将Redis执行过的写命令记录到一个单独的日志文件中,数据恢复时按照从前到后的顺序再将这些写命令再执行一遍,从而实现数据的恢复.


12.AOF持久化的同步(刷盘)策略



同步策略,或者说刷盘策略,也就是fsync. Redis官方的推荐配置是everysec,也就是Redis默认每隔一秒进行一次fsync调用,将缓冲区中的数据写到磁盘.

缓冲区同步(刷盘)策略,由参数 appendfsync 控制,一共3种:
1️⃣.always: 会调用系统 fsync 函数,直到同步到硬盘返回响应结果,严重影响redis性能;

2️⃣.everysec: 先调用操作系统的 write 函数,写到缓冲区,然后 redis再每秒执行一次操作系统的 fsync 函数,推荐使用这种方式;

3️⃣.no: 只执行操作系统的 write函数,具体同步到硬盘策略由操作系统决定,不推荐该方案,因为数据很不安全,容易丢失数据.

然而,如果磁盘性能不稳定,fsync的调用时间超过1秒钟,此时主线程进行AOF的时候会对比上次fsync成功的时间:如果距上次不到2s,主线程直接返回;如果超过2s,则主线程阻塞直到fsync同步完成.
因此AOF也是会影响redis的性能的.

注意:

在linux中,wrtie函数将数据写入文件的时候,是将数据写入操作系统的缓冲区,并未真正将数据刷入磁盘.而fsync函数可以强制让操作系统把缓冲区里的数据刷入磁盘.

所以我们为了保证读写性能最大化,可以将master的持久化关闭.


13.AOF持久化的实现



#启动aof持久化
appendonly yes

# aof的3种刷盘(同步)策略:

#appendfsync always(最常用) //收到写命令就立即写入到磁盘,效率最慢,但是可以保证数据最大的完整性;

#appendfsync everysec(推荐值) //每秒写一次硬盘,在性能和持久化方面做了很好的折中;

#appendfsync no //完全依赖os,性能最好,持久化没保证

面试题-缓存+redis_持久化_10

注意:

开启aof后之前的rdb模式就失效了,且之前的数据会被清空.


14.AOF方案的特点



1️⃣.AOF的默认文件名是 appendonly.aof,和 RDB 一样,保存在配置文件中的 dir 目录下;

2️⃣.AOF 相比于 RDB,每次都会保存写命令,数据实时性更高;

3️⃣.AOF 由于每次都会记录写命令,文件会很大,因此需要进行优化,称之为“重写机制”;

4️⃣.AOF 每次保存的写命令都会放在一个缓冲区,会根据不同的策略同步到磁盘.


15.AOF方案的优点



可以保持更高的数据完整性


16.AOF方案的缺点



随着时间的增长,这个AOF文件会越来越大,所以Redis内部有一套rewrite机制,来缩小AOF文件的体积.然而,在rewrite的过程中也是需要父进程来fork出一个子进程来进行rewrite操作,而fork操作会造成父进程阻塞,无法响应客户端的请求.

所以最终AOF文件比RDB文件大,且恢复速度慢.

类似于mysql日志,由于快照方式是在一定时间间隔做一次,所以如果发生redis意外宕机的情况,就会丢失最后一次快照后的所有被修改的数据.aof比快照方式有更好的持久化性,是由于redis在使用aof时,redis会将每一个收到的写命令都通过write函数追加到命令中,在redis重新启动时会重新执行文件中保存的写命令,然后在内存中重建这个数据库的内容,这个文件在redis/bin目录下,appendonly.aof.aof不是立即写到硬盘上,可以通过配置文件修改强制写到硬盘中.


17.AOF的“重写机制”详述

面试题-缓存+redis_持久化_11

1️⃣.首先父进程fork出一个子进程(类似于bgsave);

2️⃣.然后主进程会将命令写到2个缓冲区,一个是原有的 “AOF 缓存区”,一个是专门为子进程准备的 “AOF 重写缓冲区”;

3️⃣.子进程将命令写到新的 AOF 文件中,redis会批量创建新的aof文件,这些文件默认为32m大小,写完后通知主进程;

4️⃣.主进程会把“AOF 重写缓冲区”里的数据写到新 AOF 文件中;

5️⃣.最后将新的 AOF 文件替换老文件.


18.AOF方案的实现原理



AOF 日志存储的是 Redis 服务器的顺序指令序列,AOF 日志只记录对内存进行修改的指令记录.

假设 AOF 日志记录了自 Redis 实例创建以来所有的修改性指令序列,那么就可以通过对一个空的 Redis 实例顺序执行所有的指令——也就是“重放”,来恢复 Redis 当前实例的内存数据结构的状态.

Redis 会在收到客户端修改指令后,进行参数校验、逻辑处理,如果没问题,就立即将该指令文本存储到 AOF 日志中,也就是说,先执行指令才将日志存盘.这点不同于 leveldb、hbase 等存储引擎,它们都是先存储日志再做逻辑处理.

Redis 在长期运行的过程中,AOF 的日志会越变越长.如果实例宕机重启,重放整个 AOF 日志会非常耗时,导致长时间 Redis 无法对外提供服务,所以需要对 AOF 日志瘦身.

Redis 提供了 bgrewriteaof 指令用于对 AOF 日志进行瘦身.其原理就是开辟一个子进程对内存进行遍历,转换成一系列 Redis 的操作指令,序列化到一个新的 AOF 日志文件中.序列化完毕后再将操作期间发生的增量 AOF 日志追加到这个新的 AOF 日志文件中,追加完毕后就立即替代旧的 AOF 日志文件了,瘦身工作就完成了.

AOF 日志是以文件的形式存在的,当程序对 AOF 日志文件进行写操作时,实际上是将内容写到了内核为文件描述符分配的一个内存缓存中,然后内核会异步将脏数据刷回到磁盘的.

这就意味着如果机器突然宕机,AOF 日志内容可能还没有来得及完全刷到磁盘中,这个时候就会出现日志丢失.那该怎么办?

Linux 的 glibc 提供了 fsync(int fd) 函数可以将指定文件的内容强制从内核缓存刷到磁盘.只要 Redis 进程实时调用 fsync 函数就可以保证 AOF 日志不丢失,但是 fsync 是一个磁盘 IO 操作,它很慢!如果 Redis 执行一条指令就要 fsync 一次,那么 Redis 高性能的地位就不保了.

所以在生产环境的服务器中,Redis 通常是每隔 1s 左右执行一次 fsync 操作,这个 1s 的周期是可以配置的.这是在数据安全性和性能之间做的一个折中,在保持高性能的同时,尽可能使得数据少丢失.

Redis 同样也提供了另外两种策略,一个是永不 fsync---让操作系统来决定何时同步磁盘,这样做很不安全;另一个是来一个指令就 fsync 一次——结果导致非常慢.这两种策略在生产环境中基本很少使用,了解一下即可.


七 Redis内部结构



Redis内部使用一^?redisObject对象来表示所有的key和value。
•type :代表一个value对象具体是何种数据类型。
•encoding :是不同数据类型在redis内部的存储方式,比如:type=string 代表value存储的是一个普通字符串,那么对应的encoding可以是raw或者是int,如果是int则代表实际redis内部是按数值型类存储和表示这个字符串的,当然前提是这个字符串本身可以用数值表示,比如:“123" "45611"这样的字符串。
•vm字段:只有打开了 Redis的虚拟内存功能,此字段才会真正的分配内存,该功能默认是关闭状态的。Redis使用redisObject来表示所有的 key/value数据是比较浪费内存的,当然这些内存管理成本的付出主要也是为了给Redis不同数据类型提供一个统一的管理接口,实际作者也提供了多种方法帮助我们尽量节省内存使用。


八 Redis为什么是单线程的



因为CPU不是Redis的瓶颈。Redis的瓶颈最有可能是机器内存或者网络带宽。(以上主要来自官方FAQ)既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。


九 Redis内存淘汰机制



内存淘汰只是Redis提供的一个功能,为了更好地实现这个功能,必须为不同的 应用场景提供不同的策略,内存淘汰策略讲的是为实现内存淘汰我们具体怎么做,要解决的问题包括淘汰键空间如何选择?在键空间中淘汰键如何选择?
LRU(Least Recently Used)
LFU(Least Frequency Used)

Redis提供了下面几种淘汰策略供用户选择,其中默认的策略为noeviction策略:
•noeviction:当内存使用达到阈值的时候,所有引起申请内存的命令会报错
•allkeys-lru:在主键空间中,优先移除最近未使用的key
•allkeys-lfu:在内存不足时,Redis会再全部的key中干掉一个最近最少频次使用的key。
•allkeys-random:在主键空间中,随机移除某个key
•volatile-lru:在设置了过期时间的键空间中,优先移除最近未使用的key
•volatile-lfu:在内存不足时,Redis会在设置过了生存时间的key中干掉一个最近最少频次使用的key。
•volatile-random:在设置了过期时间的键空间中,随机移除某个key
•volatile-ttl :在设置了过期时间的键空间中,具有更早过期时间的key优先移除

注意:
这里补充一下主键空间和设置了过期时间的键空间,举个例子,假设我们有一批 键存储在Redis中,则有那么一个哈希表用于存储这批键及其值,如果这批键中 有一部分设置了过期时间,那么这批键还会被存储到另外一个哈希表中,这个哈希表中的值对应的是键被设置的过期时间。设置了过期时间的键空间为主键空间的子集。


十 如何选择淘汰策略



我们了解了Redis大概提供了这么几种淘汰策略,那么如何选择呢?淘汰策略的 选择可以通过下面的配置指定:
•maxmemory-policy noeviction
但是这个值填什么呢?为解决这个问题,我们需要了解我们的应用请求对于 Redis中存储的数据集的访问方式以及我们的诉求是什么。同时Redis也支持 Runtime修改淘汰策略,这使得我们不需要重启Redis实例而实时的调整内存淘 汰策略。
下面看看几种策略的适用场景:
•allkeys-lru:如果我们的应用对缓存的访问符合寫律分布(也就是存在相对 热点数据),或者我们不太清楚我们应用的缓存访问分布状况,我们可以选择 allkeys-lru 策略

•allkeys-random:如果我们的应用对于缓存key的访问概率相等,则可以使用这个策略

•volatile-ttl:这种策略使得我们可以向Redis提示哪些key更适合被eviction

另外,volatile-lru策略和volatile-random策略适合我们将一个Redis实例 既应用于缓存和又应用于持久化存储的时候,然而我们也可以通过使用两个 Redis实例来达到相同的效果,值得一提的是将key设置过期时间实际上会消耗更 多的内存,因此我们建议使用allkeys-lru策略从而更有效率的使用内存。


十一 Redis集群方案与实现



•客户端分片
•基于代理的分片
•路由查询
•客户端分片
•由客户端决定key写入或者读取的节点
•包括Jedis在内的一些客户端,实现了客户端分片机制


十二 Redis的持久化恢复



AOF 和 RDB 文件都可以用于服务器重启时的数据恢复,具体流程如下图:
从图中可以看出Redis优先加载 AOF文件,当没有 AOF 时才加载 RDB.当 AOF 或者 RDB 存在错误时,则加载失败.

面试题-缓存+redis_持久化_12

十三 两种持久化策略对比

1.RDB的优势



1️⃣.一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的.比如,你可能打算每个小时归档一次最近24小时的数据,同时还要每天归档一次最近30天的数据.通过这样的备份策略,一旦系统出现灾难性故障,我们可以非常容易的进行恢复.

2️⃣.对于灾难恢复而言,RDB是非常不错的选择.因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上.

3️⃣.性能最大化.对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了.

4️⃣.相比于AOF机制,如果数据集很大,RDB的启动效率会更高.


2.RDB的劣势



1️⃣.如果你想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择.因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失.

2️⃣.由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟.


3.AOF的优势



1️⃣.该机制可以带来更高的数据安全性,即数据持久性.Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步.事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失.而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中.可以预见,这种方式在效率上是最低的.

2️⃣.由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容.然而如果我们本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过redis-check-aof工具来帮助我们解决数据一致性的问题.

3️⃣.如果日志过大,Redis可以自动启用rewrite机制.即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行.因此在进行rewrite切换时可以更好的保证数据安全性.

4️⃣.AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作.事实上,我们也可以通过该文件完成数据的重建.


4. AOF的劣势



1️⃣.对于相同数量的数据集而言,AOF文件通常要大于RDB文件,RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快.

2️⃣.根据同步策略的不同,AOF在运行效率上往往会慢于RDB.总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效.

二者选择的标准,就是看系统是愿意牺牲一些性能,换取更高的缓存一致性(aof),还是愿意写操作频繁的时候,不启用备份来换取更高的性能,待手动运行save的时候,再做备份(rdb).


十四 生产环境中的持久化方案选择



1️⃣.master关闭持久化;

2️⃣.slave开启RDB,必要的时候AOF和RDB都开启.

该策略能够适应绝大部分场景和绝大部分集群架构.


1.为什么是绝大部分场景



因为这套策略存在部分数据丢失的可能性.redis的主从复制是异步的,master执行完客户端请求的命令后会立即返回结果给客户端,然后通过异步的方式把命令同步给slave,因此master可能还未来得及将命令传输给slave,就宕机了,此时slave变为master,数据就丢失了.

幸运的是,绝大部分的业务场景,都能容忍数据的部分丢失.即使真的遇到缓存雪崩的情况,代码中也有熔断器来进行资源保护,不至于所有的请求都转发到数据库而导致我们的服务崩溃


2.为什么是绝大部分集群架构



因为在集群中存在redis读写分离的情况下,就不适合这套方案了.

由于采用redis读写分离架构,就必须要考虑主从同步的延迟性问题.目前业内采用redis读写分离架构的项目,真的太少了.


3.为什么master关闭持久化



原因很简单,因为无论哪种持久化方式都会影响redis的性能,哪一种持久化都会造成CPU的卡顿,影响对客户端请求的处理.为了保证最佳的读写性能,所以将master的持久化关闭!


4.为什么slave开启RDB,而在必要的时候AOF和RDB都开启



Redis官方是不推荐单独开启AOF的,因为基于AOF持久化的数据恢复太慢.


十五 Redis4.0新特性–混合持久化



重启 Redis 时,我们很少使用 rdb 来恢复内存状态,因为会丢失大量数据.通常使用 AOF 日志重放,但是重放 AOF 日志的性能相对 rdb 来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间.

Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——混合持久化.将 rdb 文件的内容和增量的 AOF 日志文件存在一起,这里的 AOF 日志不再是全量的日志,而是自持久化开始到持久化结束的这段时间发生的增量 AOF 日志,通常这部分 AOF 日志很小.

于是在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志,就可以完全替代之前的 AOF 全量文件重放,重启效率因此得到大幅提升.

面试题-缓存+redis_缓存_13

十六 Redis持久化的问题排查和性能优化



Redis 持久化是影响 Redis 性能的高发地,也是面试中常问的一大块.


1.fork 操作



当 Redis 做 RDB 或者 AOF 重写时,必然要进行 fork 操作.对于操作系统来说,fork 是一个重量级操作.而且,fork 还会拷贝一些数据,虽然不会拷贝主进程里所有的物理空间,但会复制主进程的空间内存页表.假如有一个 10GB 的 Redis 进程,需要复制大约 20MB 的内存页表,因此 fork 操作的耗费时长跟进程总内存量息息相关.再加上,如果使用虚拟化技术,例如 Xen 虚拟机,fork 会更加耗时.

一个正常的 fork 耗时大概在 20毫秒左右.为什么呢?假设一个 Redis 实例的 OPS 在 5 万以上,如果 fork 操作耗时在秒级,那么将拖慢几万条命令的执行,对生产环境影响明显,

我们可以在 Info stats 统计中查询 latestforkusec 指标来获取最近一次 fork 操作的耗时,单位微秒.


2.如何对fork优化



1️⃣.优先使用物理机或者高效支持 fork 的虚拟化技术,避免使用 Xen;

2️⃣.控制 redis 实例的最大内存,尽量控制在 10GB 以内;

3️⃣.合理配置 Linux 内存分配策略,避免内存不足导致 fork 失败;

4️⃣.降低 fork 的频率,如适度放宽 AOF 自动触发时机,避免不必要的全量复制.


3.子进程开销的优化



fork 完毕之后,会创建子进程,子进程负责 RDB 或者 AOF 重写,这部分过程主要涉及到 CPU/内存/硬盘三个地方的优化.

1️⃣.CPU开销分析: CPU 写入文件的过程是 CPU 使用密集的过程,通常子进程对单核 CPU 利用率接近 90%. 那么如何优化呢? 既然是 CPU 密集型操作,就不要绑定单核 CPU,因为这样会和父 CPU 进行竞争.同时,不要和其他 CPU 密集型服务部署在同一个机器上.如果部署了多个 Redis 实例,尽力保证同一时刻只有一个子进程执行重写工作;

2️⃣.内存开销分析: 内存子进程通过 fork 操作产生,占用内存大小等同于父进程,理论上需要两倍的内存完成持久化操作,但 Linux 有 copy on write 机制,父子进程会共享相同的物理内存页,当父进程处理写操作时,会把要修改的页创建对应的副本,而子进程在 fork 操作过程中,共享整个父进程的内存快照.即如果重写过程中存在内存修改操作,父进程负责创建所修改内存页的副本,这里就是内存消耗的地方.如何优化呢?尽量保证同一时刻只有一个子进程在工作;避免大量写入时做重写操作.

3️⃣.硬盘开销分析: 子进程主要职责是将 RDB 或者 AOF 文件写入硬盘进行持久化,势必对硬盘造成压力,可通过工具例如 iostat, iotop 等,分析硬盘负载情况.
如何优化:
1️⃣.不要和其他高硬盘负载的服务放在一台机器上,例如 MQ,存储;

2️⃣.AOF 重写时会消耗大量硬盘的 IO,可以开启配置 no-appendfsync-on-rewrite,默认关闭.表示在 AOF 重写期间不做 fsync 操作;

3️⃣.当开启 AOF 的 Redis 在高并发场景下,如果使用普通机械硬盘,每秒的写速率是 100MB左右,这时Redis 的性能瓶颈在硬盘上,建议使用 SSD;

4️⃣.对于单机配置多个 Redis 实例的情况,可以配置不同实例分盘存储 AOF 文件,分摊硬盘压力.


4.AOF 追加阻塞



当开启 AOF 持久化时,常用的同步硬盘的策略是“每秒同步” everysec,用于平衡性能和数据安全性.对于这种方式,redis 使用另一条线程每秒执行 fsync 同步硬盘,当系统资源繁忙时,将造成 Redis 主线程阻塞。

通过上图可以发现: everysec 配置最多可能丢失 2 秒数据,不是 1 秒;如果系统 fsync 缓慢,将会导致 Redis 主线程阻塞影响效率.

面试题-缓存+redis_redis_14

1.问题定位



1️⃣.发生 AOF 阻塞时,会输入日志,用于记录 AOF fsync 阻塞导致拖慢 Redis 服务的行为;

2️⃣.每当 AOF 追加阻塞事件发生时,在 info Persistence 统计中,aofdelayedfsync 指标会累加,查看这个指标方便定位 AOF 阻塞问题;

3️⃣.AOF 同步最多运行 2 秒的延迟,当延迟发生时说明硬盘存在性能问题,可通过监控工具 iotop 查看,定位消耗 IO 的进程.


追加阻塞



当开启 AOF 持久化时,常用的同步硬盘的策略是“每秒同步” everysec,用于平衡性能和数据安全性.对于这种方式,redis 使用另一条线程每秒执行 fsync 同步硬盘,当系统资源繁忙时,将造成 Redis 主线程阻塞。

通过上图可以发现: everysec 配置最多可能丢失 2 秒数据,不是 1 秒;如果系统 fsync 缓慢,将会导致 Redis 主线程阻塞影响效率.

面试题-缓存+redis_redis_14

1.问题定位



1️⃣.发生 AOF 阻塞时,会输入日志,用于记录 AOF fsync 阻塞导致拖慢 Redis 服务的行为;

2️⃣.每当 AOF 追加阻塞事件发生时,在 info Persistence 统计中,aofdelayedfsync 指标会累加,查看这个指标方便定位 AOF 阻塞问题;

3️⃣.AOF 同步最多运行 2 秒的延迟,当延迟发生时说明硬盘存在性能问题,可通过监控工具 iotop 查看,定位消耗 IO 的进程.