Redis 缓存穿透、雪崩、缓存数据库不一致、持久化方式、分布式锁、过期策略

1. Redis 缓存穿透

1.1 Redis 缓存穿透概念

访问了不存在的 key,缓存未命中,请求会穿透到 DB,量大时可能会对 DB 造成压力导致服务异常。

由于不恰当的业务功能实现,或者外部恶意攻击不断地请求某些不存在的数据内存,由于缓存中没有保存该数据,导致所有的请求都会落到数据库上,对数据库可能带来一定的压力,甚至崩溃。

1.2 Redis 缓存穿透解决方案

  • 针对这些不存在的 key,可以在 Redis 中保存对应的 key 和空数据,并设置较短的过期时间;
  • 或者使用 Redis Bitmap 来判断数据是否存在以过滤无效请求;

2. Redis 缓存雪崩

2.1 Redis 缓存雪崩概念

缓存同时失效或缓存服务异常,瞬时大量请求直接到达 DB,对 DB 造成压力导致服务异常,

2.2 Redis 缓存雪崩解决方案:

  • 保证缓存服务的高可用,采用集群,多主多从模式部署,并开启 RDB 和 AOF 备份,在 Redis 服务出问题时能快速根据备份文件恢复缓存数据;
  • 缓存过期时间的设置随机化;
  • 调用缓存服务时增加熔断模块,类似 Hystrix;

总的来说,缓存穿透和雪崩带来的影响都是缓存失效或未命中导致 DB 压力大,从而可能拖垮服务。这些情况都是可能导致 DB 异常从而影响服务的可用性。

其实关键就是过滤无效的数据,增加缓存命中率,并添加对应的隔离措施以增加整个服务的可用性。

3. Redis 缓存和数据库不一致问题

使用了缓存的话,就有双写问题。通常,涉及到双写问题,就会有数据不一致的情况。需要保存数据一致需要牺牲性能,不过实际场景中一般也不要求这么高的一致性。要求严格一致的话,可以将读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况。

缓存模式:Cache Aside Pattern(先淘汰缓存,再写数据库):

  • 读的时候,先读缓存,缓存没有的话,那么就读数据库,然后取出数据后放入缓存,同时返回响应读的时候;
  • 写的时候,先删除缓存,然后再更新数据库。

3.1 并发情况下出现了数据不一致问题怎么办?

eg. 有个写请求,此时删除了缓存中的数据,但是还没来得及写数据库;这个时候来了一个读请求,又把数据库中的旧数据 load 到缓存中去了,然后上一个请求的写数据库操作完成了。这个时候,数据库中的是最新的数据,缓存中的却是旧数据了.

解决思路:部分请求串行化或采取补偿操作。

  • 串行化:将数据库与缓存更新与读取操作进行异步串行化;将相同 id 的读写请求 hash 到同一台服务处理,服务中使用队列一个一个地执行。如果发现队列中有对应资源的写请求,那么就等待其执行结束后再去取值返回(这里需要考虑等待时间过长的问题,还有该方案影响较大,较复杂)。
  • 补偿操作:既然是延迟导致的数据不一致,那么根据数据库实际的延迟时间,我们使用定时任务或者消息触发,如果有写请求结束后,我们在指定的时间之后再删除一次该缓存值,这样即使有不一致的脏数据,那也只会出现在延迟的这一段时间中。

4. 基于 Redis 的分布式锁是怎么实现的?

分布式锁实现要保证几个基本点。

互斥性:任意时刻,只有一个资源能够获取到锁。
容灾性:能够在未成功释放锁的的情况下,一定时限内能够恢复锁的正常功能。
统一性:加锁和解锁保证同一资源来进行操作。

示例:我们通过 SETNX 命令来实现的分布式锁,设置了过期时间,不至于会出现线程异常导致锁无法释放的情况。

5. 为啥 Redis 单线程模型也能效率这么高?

Redis 基于 Reactor 模式开发了网络事件处理器,这个处理器叫做文件事件处理器(File Event Handler)。这个文件事件处理器是单线程的,Redis 才叫做单线程的模型,采用 IO 多路复用机制同时监听多个 Socket,根据 Socket 上的事件来选择对应的事件处理器来处理这个事件,为啥快呢:

  • 纯内存操作
  • 核心是基于非阻塞的 IO 多路复用机制
  • 单线程反而避免了多线程的频繁上下文切换问题)
  • 内部数据结构设计,整个的结构都类似于一个 map,查找效率贼高

Redis是单线程处理网络指令请求,所以不需要考虑并发安全问题。所有的网络请求都是一个线程处理。但不代表所有模块都是单线程。

6. Redis 持久化方式及其区别

Redis 有持久化机制的,它支持 AOF 和 RDB 两种持久化方式。

RDB:通过 fork 一个子进程保存当前内存的一个快照实现备份. 适合大规模的数据恢复,但是数据的完整性和一致性不高,因为 RDB 可能在最后一次备份时宕机了。另外备份时会占用内存,因为 Redis 在备份时会独立创建一个子进程,将数据写入到一个临时文件(此时内存中的数据是原来的两倍哦),最后再将临时文件替换之前的备份文件。配置方法如下:

    # save <指定时间间隔> <执行指定次数更新操作>,满足条件就将内存中的数据同步到硬盘中
    save <seconds> <changes>  
    # 指定本地数据库文件名,一般采用默认的 dump.rdb
    dbfilename dump.rdb 
    # 默认开启数据压缩
    rdbcompression yes

AOF: 通过对每条写入命令以 append-only 的模式写入一个日志文件中,在 Redis 重启的时候,可以通过回放 AOF 日志中的写入指令来重新构建整个数据集。 AOF 对数据数据的完整性和一致性支持更好,但是其备份日志文件一般会比 RDB 方式的备份文件更大,恢复也更慢,同时由于 fsync 的频率方式,会影响 Redis 的性能。

     # 打开aof
     appendonly yes     
     # 日志文件
     appendfilename "appendonly.aof"  
     # 更新条件
     # appendfsync always
     appendfsync everysec
     # appendfsync no
     # 触发重写的配置
     # 时间长了日志会特别大,此时需要触发重写
     # 重写的原理:Redis 会fork出一条新进程,读取内存中的数据,并重新写到一个临时文件中。
     #           最后替换旧的aof文件。
     auto-aof-rewrite-percentage 100
     auto-aof-rewrite-min-size 64mb

后续:Redis 4.0 之后,持久化增加了混合 RDB-AOF 持久化格式。具体可以参考

http://blog.huangz.me/diary/2016/redis-4-outline.html

7. Redis 如何实现分布式和高可用?

Redis 主从模式原理:

Redis 2.8 之前主从模式,主从之间的数据复制只有全复制机制,通过执行命令 SLAVEOF ip port,使得其成为从服务器。全数据复制流程如下:

从服务器向主服务器发送 SYNC 命令。
主服务器收到 SYNC 请求后,执行 BGSAVE 命令在后台生成 RDB 文件,并使用一个缓冲区记录从现在开始执行的写命令。
主服务器将生成的 RDB 文件发送给从服务器,从服务器根据RDB文件更新状态。
主服务器将缓冲区中的写命令发送给从服务器,从服务器执行这些命令,最终和主服务器达到一致的状态。

命令传播

数据复制完成后,为了保持一致的状态,主服务的写命令都需要传播给其从服务器。

不足:上面的方式存在不足,即如果从服务器是和主服务器断开后,又立马重新连接上后。那么此时如果还执行全同步的话,就十分浪费。因为全同步非常占用 CPU、IO 和带宽等。

Redis 2.8 之后,支持了 PSYNC,即部分重同步机制。部分重同步机制主要依靠:

主从服务器的复制偏移量: 用于记录主从服务器的状态是否一致,以及数据复制时的偏移量
主服务器的复制积压缓冲区:固定大小的(默认 1M)FIFO 队列,用于记录主服务器的写命令
主服务器的 ID:用于判断,从服务器断开连接后重新连接到的主服务器还是不是原来那个

部分重同步流程:

主从服务器都有一个复制偏移量,当执行了写命令时,复制偏移量对应会增加。

从服务器请求同步,带上当前的 offset 和之前的主服务器 ID;
如果请求中的主服务 ID 和当前的主服务器ID不一致,或者其 offset 的值已经不再复制积压缓冲区内,那么需要执行全同步,流程同上;
否则,执行部分同步,主服务器将复制积压缓冲区中 offset 之后的命令发送给从服务器;
从服务器执行对应写命令,并修改 offset,达到和主服务器状态一致。

8. Redis 的过期策略都有哪些?

8.1 定时删除策略

在设置 key 的过期时间的同时,为该 key 创建一个定时器,让定时器在 key 的过期时间来临时,对 key 进行删除。

  • 优点:保证内存尽快释放。
  • 缺点:若 key 过多,删除这些 key 会占用很多 CPU 时间, 而且每个 key 创建一个定时器,性能影响严重。

8.2 惰性删除策略

key 过期的时候不删除,每次从数据库获取 key 的时候去检查是否过期,若过期,则删除,返回 null。

  • 优点:CPU 时间占用比较少。
  • 缺点:若 key 很长时间没有被获取, 将不会被删除,可能造成内存泄露

8.3 定期删除策略

每隔一段时间执行一次删除(在 redis.conf 配置文件设置 hz,1s 刷新的频率)过期 key 操作。

  • 优点:可以控制删除操作的时长和频率,来减少 CPU 时间占用,可以避免惰性删除时候内存泄漏的问题。

  • 缺点:
    对内存友好方面,不如定时策略
    对 CPU 友好方面,不如惰性策略

那如果执行了上述的删除操作后,Redis 的内存空间还是不足怎么办呢?

设置了过期时间的,到期后会被上述操作删除掉;如果此时内存还是不够的话,Redis 会根据配置的策略来执行对应的操作,主要有 noeviction、allkeys-lru、allkeys-random、volatile-lru、volatile-random、volatile-ttl 这几种,balabalabala…

  • noeviction:当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。
  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。
  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。
  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。
  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。
  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。

部分内容参考以下链接

https://gitbook.cn/books/5d07228c2df51311ff3a6498/index.html

https://gitbook.cn/books/5c97654679ca930d56250d14/index.html

你可能感兴趣的:(Redis)