Dawn's Blogs

分享技术 记录成长

0%

Redis学习 (8) 应用问题和Redis 6新功能

应用问题

缓存穿透

key 对应的数据在数据源(DB)并不存在,每次针对此 key 的请求从缓存获取不到,那么就会从数据源请求数据,从而可能压垮数据源。

image-20221229145805794

解决方案

  • 对空值缓存:如果一个查询返回的数据为空(不管是数据是否不存在),仍然把这个空结果进行缓存,设置空结果的过期时间会很短,最长不超过五分钟。
  • 设置可访问的名单(白名单):可以访问的 id 记录在 Redis 缓存中,可以使用 string 类型或者 bitmaps 类型(id 作为 bitmaps 的偏移量)。每次访问时都在缓存中查询 id 是否在其中,若不存在就直接返回空值,不查询数据库。
  • 布隆过滤器(Bloom Filter):是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。

布隆过滤器原理:

布隆过滤器由一个位图和 N 个哈希函数组成。布隆过滤器会在位图上做标记,只有标记全部为 1,才说明可能存在。布隆过滤器标记的流程:

  • 第一步,使用 N 个哈希函数分别对数据做哈希计算,得到 N 个哈希值
  • 第二步,将第一步得到的 N 个哈希值对位图数组的长度取模,在位图数组的对应位置的值设置为 1

查询布隆过滤器说数据存在,并不一定证明数据库中存在这个数据,但是查询到数据不存在,数据库中一定就不存在这个数据

图片

缓存击穿

某一个 key 对应的数据存在,但在 redis 中过期,此时若有大量并发请求过来(热点数据),这些请求发现缓存过期一般都会从数据库中加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把数据库压垮。

image-20221229151135306

解决方案

  • 预先设置热点数据:把一些热门数据提前存入到 redis 里面,增加这些热门数据 key 的时长。

  • 实时调整热点数据:监控哪些数据热门,实时调整 key 的过期时长。

  • singleflight:可以将对同一个 Key 的并发请求进行合并,只让其中一个请求到数据库进行查询,其他请求共享同一个结果

    img

缓存雪崩

大量缓存在同一时刻全部失效(或者 Redis 宕机),造成瞬时 DB 请求量大、压力骤增,引起雪崩。缓存雪崩通常因为缓存服务器宕机、缓存的 key 设置了相同的过期时间等引起。

解决方案

大量数据同时过期

  • 构建多层缓存架构:nginx缓存 + redis缓存 +其他缓存。
  • 随机缓存失效时间:在原有的失效时间基础上增加一个随机值,这样每一个缓存的过期时间的重复率就会降低。
  • 互斥锁:如果发现访问的数据不在缓存里,就加个互斥锁,保证同一时间内只有一个请求来构建缓存(从数据库读取数据,再将数据更新到 Redis 里),互斥锁最好设置超时时间。
  • 后台更新缓存:业务线程不再负责更新缓存,缓存也不设置有效期,而是让缓存“永久有效”,并将更新缓存的工作交由后台线程定时更新

缓存数据不设置有效期,并不是意味着数据一直能在内存里,因为当系统内存紧张的时候,有些缓存数据会被淘汰,业务的视角就以为是数据未命中。有两种方法解决这个问题:

  • 后台线程不仅负责定时更新缓存,而且也负责频繁地检测缓存是否有效,检测到缓存失效了,原因可能是系统紧张而被淘汰的,于是就要马上从数据库读取数据,并更新到缓存。
  • 在业务线程发现缓存数据失效后,通过消息队列发送一条消息通知后台线程更新缓存,后台线程收到消息后,在更新缓存前可以判断缓存是否存在,存在就不执行更新缓存操作;不存在就读取数据库数据,并将数据加载到缓存。这种方式相比第一种方式缓存的更新会更及时,用户体验也比较好。

后台更新缓存机制同样适合在应用启动时,执行缓存预热

Redis 故障宕机

针对 Redis 故障宕机而引发的缓存雪崩问题,常见的应对方法有下面这几种:

  • 服务熔断或请求限流机制。
  • 构建 Redis 缓存高可靠集群,通过主从节点的方式构建 Redis 缓存高可靠集群

Redis 6 新特性

ACL

在 Redis 5 版本之前,Redis 安全规则只有密码控制,还有通过 rename 来调整高危命令比如 flushdb、 KEYS *、shutdown等。

Redis 6 则提供 ACL(访问控制表)的功能对用户进行更细粒度的权限控制 :

  • 接入权限:用户名和密码
  • 可以执行的命令
  • 可以操作的 KEY

IO 多线程

IO 多线程其实指客户端交互部分网络 IO 交互处理模块多线程,而非执行命令多线程,Redis6 执行命令依然是单线程。

image-20221229161621280

另外,多线程IO默认也是不开启的,需要在配置文件中配置:

io-threads-do-reads yes

io-threads 4