Redis相关知识点总结(常用数据类型,事务,乐观锁,持久化,订阅,哨兵模式,缓存穿透,缓存击穿,缓存雪崩等)

Redis相关知识点总结(常用数据类型,事务,乐观锁,持久化,订阅,哨兵模式,缓存穿透,缓存击穿,缓存雪崩等)


redis简介:

Redis 是完全开源的,遵守 BSD 协议(点击了解BSD协议),是一个高性能的 key-value 数据库。

Redis 与其他 key - value 缓存产品有以下三个特点:

  • Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。

  • Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。

  • Redis支持数据的备份,即master-slave模式的数据备份。

redis中文官方网站链接


redis-bebchmark性能测试

Redis-benchmark是官方自带的Redis性能测试工具,可以有效的测试Redis服务的性能。
Redis作为当前最常用的开源内存数据库,读写性能都十分高,据官方数据表示Redis读的速度是110000次/秒,写的速度是81000次/秒。

测试命令示例:

1、redis-benchmark -h 192.168.1.201 -p 6379 -c 100 -n 100000
100个并发连接,100000个请求,检测host为localhost 端口为6379的redis服务器性能

2、redis-benchmark -h 192.168.1.201 -p 6379 -q -d 100

测试存取大小为100字节的数据包的性能

3、redis-benchmark -t set,lpush -n 100000 -q

只测试某些操作的性能

4、redis-benchmark -n 100000 -q script load "redis.call('set','foo','bar')"

只测试某些数值存取的性能

推荐:

redis-bebchmark性能测试相关文档


redis事务操作

Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:

批量操作在发送 EXEC 命令前被放入队列缓存。
收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。

一个事务从开始到执行会经历以下三个阶段:

  • 开始事务。

  • 命令入队。

  • 执行事务。

以下是一个事务的例子, 它先以 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的所有命令:

redis 127.0.0.1:6379> MULTI
OK

redis 127.0.0.1:6379> SET book-name "Mastering C++ in 21 days"
QUEUED

redis 127.0.0.1:6379> GET book-name
QUEUED

redis 127.0.0.1:6379> SADD tag "C++" "Programming" "Mastering Series"
QUEUED

redis 127.0.0.1:6379> SMEMBERS tag
QUEUED

redis 127.0.0.1:6379> EXEC
1) OK
2) "Mastering C++ in 21 days"
3) (integer) 3
4) 1) "Mastering Series"
   2) "C++"
   3) "Programming"

单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。

事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。
如:

redis 127.0.0.1:7000> multi
OK
redis 127.0.0.1:7000> set a aaa
QUEUED
redis 127.0.0.1:7000> set b bbb
QUEUED
redis 127.0.0.1:7000> set c ccc
QUEUED
redis 127.0.0.1:7000> exec
1) OK
2) OK
3) OK

如果在 set b bbb 处失败,set a 已成功不会回滚,set c 还会继续执行。

redis事务命令:

  • DISCARD
    取消事务,放弃执行事务块内的所有命令。

  • EXEC
    执行所有事务块内的命令。

  • MULTI
    标记一个事务块的开始。


redis实现乐观锁

  1. UNWATCH
    取消 WATCH 命令对所有 key 的监视。
  2. WATCH key [key …]
    监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

使用说明:

添加watch关键字,监测redis相应的值,如key1,开启事务(MULTI),然后对key1执行相应的操作指令,操作命令首先存入事务的队列,此时在另一个线程同时操作key1,然后我们这边执行(EXEC)这个对key1操作的事务就会失败,保证了数据的完整性,类似于数据库事务的回滚操作;如果没有watch乐观锁,即使有另一个线程同时操作了key1,正常的事务执行是直接可以执行成功的,就会出现脏数据等情况;

基于redis的乐观锁:

  1. 乐观锁的实现,必须基于WATCH,然后利用redis的事务。

  2. WATCH生命周期,只是和事务关联的,一个事务执行完毕,相应的watch的生命周期即结束。

推荐:

乐观锁实现过程演示相关文档


五种常用类型:

String 字符串类型

字符串类型是Redis中最为基础的数据存储类型,是一个由字节组成的序列,他在Redis中是二进制安全的,这便意味着该类型可以接受任何格式的数据,如JPEG图像数据货Json对象描述信息等,是标准的key-value,一般来存字符串,整数和浮点数。Value最多可以容纳的数据长度为512MB
应用场景:很常见的场景用于统计网站访问数量,当前在线人数等。incr命令(++操作)

在这里插入图片描述

List 列表类型

Redis的列表允许用户从序列的两端推入或者弹出元素,列表由多个字符串值组成的有序可重复的序列,是链表结构,所以向列表两端添加元素的时间复杂度为0(1),获取越接近两端的元素速度就越快。这意味着即使是一个有几千万个元素的列表,获取头部或尾部的10条记录也是极快的。List中可以包含的最大元素数量是4294967295。
应用场景:1.最新消息排行榜。2.消息队列,以完成多程序之间的消息交换。可以用push操作将任务存在list中(生产者),然后线程在用pop操作将任务取出进行执行。(消费者)

在这里插入图片描述

Set 集合类型

Redis的集合是无序不可重复的,和列表一样,在执行插入和删除和判断是否存在某元素时,效率是很高的。集合最大的优势在于可以进行交集并集差集操作。Set可包含的最大元素数量是4294967295。
应用场景:1.利用交集求共同好友。2.利用唯一性,可以统计访问网站的所有独立IP。3.好友推荐的时候根据tag求交集,大于某个threshold(临界值的)就可以推荐。

在这里插入图片描述

Hash 哈希类型

Redis中的散列可以看成具有String key和String value的map容器,可以将多个key-value存储到一个key中。每一个Hash可以存储4294967295个键值对。
应用场景:例如存储、读取、修改用户属性(name,age,pwd等)

在这里插入图片描述

Zset 有序集合
和set很像,都是字符串的集合,都不允许重复的成员出现在一个set中。他们之间差别在于有序集合中每一个成员都会有一个分数(score)与之关联,Redis正是通过分数来为集合中的成员进行从小到大的排序。尽管有序集合中的成员必须是卫衣的,但是分数(score)却可以重复。
应用场景:可以用于一个大型在线游戏的积分排行榜,每当玩家的分数发生变化时,可以执行zadd更新玩家分数(score),此后在通过zrange获取几分top ten的用户信息。

在这里插入图片描述
以上五种类型的总结参考自:https://www.cnblogs.com/dijia478/p/8058775.html

三种特殊类型:

Geospatial 地理位置

redis的geospatial在redis 3.2版本推出的,这个功能可以推算地理位置的信息,两地之间的距离,方圆几里的人。

GEO提供了一些命令来让我们使用,简单介绍如下:



    GEOADD 添加一个或多个地理位置元素到一个key中 
    格式:GEOADD key longitude latitude member [longitude latitude member ...]


    GEODIST 返回一个key中指定两个位置之间的距离
    格式:GEODIST key member1 member2 [unit]  unit可以指定长度单位:m,km,ft等 默认为m


    GEOHASH  返回一个或多个位置元素的 Geohash 表示,Geohash是一种经纬度散列算法,具体请百度。
    格式: GEOHASH key member [member ...]


    GEOPOS 返回一个或多个位置的经纬度信息,由于采用了geohash算法,返回的经纬度和添加时的数据可能会有细小误差
    格式: GEOPOS key member [member ...]


    GEORADIUS  以给定位置为中心,半径不超过给定半径的附近所有位置
    格式 GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]


    GEORADIUSBYMEMBER 和GEORADIUS相似,只是中心点不是指定经纬度,而是指定已添加的某个位置作为中心
    格式: GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]

推荐:

geo实现演示相关文档


Hyerloglog 基数统计

HyperLogLog可以接受多个元素作为输入,并给出输入元素的基数估算值。

• 基数:集合中不同元素的数量。比如 {‘apple’, ‘banana’, ‘cherry’, ‘banana’, ‘apple’} 基数就是 3 ;
• 估算值:算法给出的基数并不是精确的,可能会比 实际稍微多一些或者稍微少一些,但会控制在合理的范围之内。

值得一说的是 基数估计的结果是一个带有 0.81% 标准错误(standard error)的近似值。

HyperLogLog 的优点是,即使输入元素的数量或者体积非常非常大,计算基数所需的空间总是固定的、并且是很小的。在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成 鲜明对比。但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以HyperLogLog 不能像集合那样,返回输入的各个元素。

常用命令:

pfadd 添加

影响基数估值则返回1否则返回0.若key不存在则创建
时间复杂度O(1)

127.0.0.1:6379> pfadd m1 1 2 3 4 1 2 3 2 2 2 2
(integer) 1

pfcount 获得基数值

得到基数值,白话就叫做去重值(1,1,2,2,3)的插入pfcount得到的是3
可一次统计多个key
时间复杂度为O(N),N为key的个数
返回值是一个带有 0.81% 标准错误(standard error)的近似值.

127.0.0.1:6379> pfadd m1 1 2 3 4 1 2 3 2 2 2 2
(integer) 1
127.0.0.1:6379> pfcount m1
(integer) 4

pfmerge 合并多个key

取多个key的并集
命令只会返回 OK.
时间复杂度为O(N)

127.0.0.1:6379> pfadd m1 1 2 3 4 1 2 3 2 2 2 2
(integer) 1
127.0.0.1:6379> pfcount m1
(integer) 4
127.0.0.1:6379> pfadd m2 3 3 3 4 4 4 5 5 5 6 6 6 1
(integer) 1
127.0.0.1:6379> pfcount m2
(integer) 5
127.0.0.1:6379> pfmerge mergeDes m1 m2
OK
127.0.0.1:6379> pfcount mergeDes
(integer) 6

应用场景分析:

一般我们评估一个网站,主要有两个指标

  • pv(page, view):页面访问量

  • uv(user,view):访问的用户

一般来说,pv和uv的 统计可以自己来做,也可以借助第三方工具。

自己如何实现:pv;可以通过计数器,key的话加上当前的日期,每当有用户请求的时候,hincrby自增一次,就实现了。但是uv的话,要处理另一个问题,就是去重;

如何去重:需要前端每个用户生成唯一的id,无论是登录还是未登录,都需要一个唯一id,伴随着请求一起到后端,在后端,通过sadd,set集合去处理,因为set集合存储的话,是不能存重复的。然后通过scard统计集合的大小。进而得出uv数据。

但是如果有千万个uv,需要的存储空间是非常惊人的,像这种数量特别多的用户访问量的话,一般不需要特别精确,就可以使用Hyerloglog 。

一般使用场景:

  • 统计注册 IP 数

  • 统计每日访问 IP 数

  • 统计页面实时 UV 数

  • 统计在线用户数

  • 统计用户每天搜索不同词条的个数


Bitmap 类型

就是通过一个bit (0/1)位来表示某个元素对应的值或者状态,其中的key就是对应元素本身。我们知道8个bit可以组成一个Byte,所以bitmap本身会极大的节省储存空间。Redis从2.2.0版本开始新增了setbit,getbit,bitcount等几个bitmap相关命令。并没有新增新的数据类型,因为setbit等命令只不过是在set上的扩展。

应用场景:
打卡/签到,统计用户信息,登录,未登录,活跃,不活跃。。。

如:打卡统计:
在这里插入图片描述


redis持久化的两种机制(rdb,aof):

RDB:快照形式是直接把内存中的数据保存到一个 dump.rdp 文件中,定时保存策略。
AOF:把所有的对Redis的服务器进行修改的命令都存到一个文件(appendonly.aof)里,命令的集合。

rdb文件

dump.rdb是由Redis服务器自动生成的,默认情况下 每隔一段时间redis服务器程序会自动对数据库做一次遍历,把内存快照写在一个叫做“dump.rdb”的文件里,这个持久化机制叫做SNAPSHOT(快照)。有了SNAPSHOT后,如果服务器宕机,重新启动redis服务器程序时redis会自动加载dump.rdb,将数据库状态恢复到上一次做SNAPSHOT时的状态。至于多久做一次SNAPSHOT,SNAPSHOT文件的路径和文件名,你可以在redis的conf文件里指定。

在这里插入图片描述
save 900 1:表示900 秒内如果至少有 1 个 key 的值变化,则保存
save 300 10:表示300 秒内如果至少有 10 个 key 的值变化,则保存
save 60 10000:表示60 秒内如果至少有 10000 个 key 的值变化,则保存

优点

  • RDB是一个非常紧凑(compact)的文件,它保存了redis 在某个时间点上的数据集。这种文件非常适合用于进行备份和灾难恢复。
  • 生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。
  • RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

缺点

  • RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运行都要执行fork操作创建子进程,属于重量级操作,如果不采用压缩算法(内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑),频繁执行成本过高(影响性能)
  • RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式的RDB版本,存在老版本Redis服务无法兼容新版RDB格式的问题(版本不兼容)
  • 在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改(数据有丢失)

aof文件:

appendOnly.aof中会记录所有对redis的写操作

aof创建原理:
在这里插入图片描述每当有一个写命令过来时,就直接保存在我们的AOF文件中。

Redis.conf配置:

appendfsync yes   
appendfsync always     #每次有数据修改发生时都会写入AOF文件。
appendfsync everysec   #每秒钟同步一次,该策略为AOF的缺省策略。

AOF 就可以做到全程持久化,只需要在配置文件中开启(默认是no),appendonly yes开启 AOF 之后,Redis 每执行一个修改数据的命令,都会把它添加到 AOF 文件中,当 Redis 重启时,将会读取 AOF 文件进行“重放”以恢复到 Redis 关闭前的最后时刻。

rdb和aof的区别:

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。

RDB 和 AOF ,应该使用用哪一个?

  • 如果你非常关心你的数据,但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久。
  • AOF 将 Redis 执行的每一条命令追加到磁盘中,处理巨大的写入会降低 Redis 的性能。
  • 数据库备份和灾难恢复:定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快。
  • Redis 支持同时开启 RDB 和 AOF,系统重启后,Redis 会优先使用 AOF 来恢复数据,这样丢失的数据会最少。

推荐:

rdb和aof相关文档1
rdb和aof相关文档2


redis订阅发布

Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:

在这里插入图片描述常用命令:

1	PSUBSCRIBE pattern [pattern ...]
订阅一个或多个符合给定模式的频道。
2	PUBSUB subcommand [argument [argument ...]]
查看订阅与发布系统状态。
3	PUBLISH channel message
将信息发送到指定的频道。
4	PUNSUBSCRIBE [pattern [pattern ...]]
退订所有给定模式的频道。
5	SUBSCRIBE channel [channel ...]
订阅给定的一个或多个频道的信息。
6	UNSUBSCRIBE [channel [channel ...]]
退订给定的频道。

redis集群 主从配置 哨兵模式

主从复制

概念:

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower),数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。
默认情况下,每台Redis服务器都是主节点,一个主节点可以有0个或者多个从节点,但每个从节点只能由一个主节点。

作用:

  • 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。

  • 故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式

  • 负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下,通过多个从节点分担负载,提高并发量。

  • 高可用基石:主从复制还是哨兵和集群能够实施的基础。

为什么使用集群:

  • 单台服务器难以负载大量的请求
  • 单台服务器故障率高,系统崩坏概率大
  • 单台服务器内存容量有限。

一主二从的主从配置:

从机只能读,不能写,主机(6379)可读可写但是多用于写。

    127.0.0.1:6381> set name lidabao # 从机6381写入失败
    (error) READONLY You can't write against a read only replica.

    127.0.0.1:6380> set name lidabao # 从机6380写入失败
    (error) READONLY You can't write against a read only replica.

    127.0.0.1:6379> set name lidabao 
    OK
    127.0.0.1:6379> get name
    "lidabao"
  • 当主机断电宕机后,默认情况下从机的角色不会发生变化 ,集群中只是失去了写操作,当主机恢复以后,又会连接上从机恢复原状。
  • 当从机断电宕机后,若不是使用配置文件配置的从机,再次启动后作为主机是无法获取之前主机的数据的,若此时重新配置称为从机,又可以获取到主机的所有数据。这里就要提到一个同步原理。
  • 默认情况下,主机故障后,不会出现新的主机,有两种方式可以产生新的主机:
    1.从机手动执行命令slaveof no one,这样执行以后从机会独立出来成为一个主机
    2.使用哨兵模式(自动选举)

哨兵模式

参考文档:https://www.jianshu.com/p/06ab9daf921d

当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑 哨兵模式

概念:

哨兵模式 是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。

单机单个哨兵:
在这里插入图片描述
哨兵的作用:

  • 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。

  • 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。

然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。

用文字描述一下故障切换failover)的过程。假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。

在这里插入图片描述


redis缓存穿透,缓存击穿和缓存雪崩

缓存基本流程:
前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果。
在这里插入图片描述

  • 缓存穿透

概念:

一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就去数据库查询。一些恶意的请求会故意大量查询不存在的key,一直请求数据库,就会对数据库造成很大的压力。这就叫做缓存穿透直白点说就是故意避开缓存去数据库查询数据

比如 传入的参数为-1,而这个-1就是一定不存在的对象。就会每次都去查询数据库,而每次查询都是空,每次又都不会进行缓存。假如有恶意攻击,就可以利用这个漏洞,对数据库造成压力,甚至压垮数据库。

解决方案:

1.接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截,或者使用布隆过滤器

2.从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,(这样做有一个缺陷:存储空对象也需要空间,大量的空对象会耗费一定的空间,存储效率并不高。解决这个缺陷的方式就是设置较短过期时间),缓存有效时间可以设置短点,如30秒。这样可以防止类似攻击用户反复用同一个id暴力攻击的情况。


  • 缓存击穿

概念:

缓存击穿是指一个key访问频率特别高,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就略过缓存,直接请求数据库,导致数据库瞬间压力骤增,相当于击穿了缓存的这层屏障。相较于缓存穿透,缓存击穿的目的性更强只是针对其中某个key的缓存不可用而导致击穿,但是其他的key依然可以使用缓存响应。直白点说就是缓存过期导致数据库中的数据被集中高频访问。

比如 热搜排行上,一个热点新闻(例如某某明星出轨)被同时大量访问,吃瓜群众纷纷围观,就可能导致缓存击穿。

解决方案:

1.设置热点数据永远不过期。
这样就不会出现热点数据过期的情况,但是当Redis内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。

2.加互斥锁,保证同时刻只有一个线程访问:
在这里插入图片描述


  • 缓存雪崩

概念:

缓存雪崩是指缓存中数据大批量到过期时间 或者 缓存整体不能提供服务,而查询数据量巨大,引起数据库压力过大甚至宕机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案:

1.保持缓存层的高可用性

使用Redis 哨兵模式或者Redis 集群部署方式,即便个别Redis 节点下线,整个缓存层依然可以使用。除此之外,还可以在多个机房部署 Redis,这样即便是机房死机,依然可以实现缓存层的高可用。

2.限流降级组件

无论是缓存层还是存储层都会有出错的概率,可以将它们视为资源。作为并发量较大的分布式系统,假如有一个资源不可用,可能会造成所有线程在获取这个资源时异常,造成整个系统不可用。降级在高并发系统中是非常正常的,比如推荐服务中,如果个性化推荐服务不可用,可以降级补充热点数据,不至于造成整个推荐服务不可用。常见的限流降级组件如 Hystrix、Sentinel 等。

3.缓存不过期

Redis 中保存的 key 永不失效,这样就不会出现大量缓存同时失效的问题,但是随之而来的就是Redis 需要更多的存储空间。

4.优化缓存过期时间

设计缓存时,为每一个 key 选择合适的过期时间,避免大量的 key 在同一时刻同时失效,造成缓存雪崩。

5.使用互斥锁重建缓存

在高并发场景下,为了避免大量的请求同时到达存储层查询数据、重建缓存,可以使用互斥锁控制,如根据 key 去缓存层查询数据,当缓存层为命中时,对 key 加锁,然后从存储层查询数据,将数据写入缓存层,最后释放锁。若其他线程发现获取锁失败,则让线程休眠一段时间后重试。对于锁的类型,如果是在单机环境下可以使用 Java 并发包下的 Lock,如果是在分布式环境下,可以使用分布式锁(Redis 中的 SETNX 方法)。

分布式环境下使用Redis 分布式锁实现缓存重建,优点是设计思路简单,对数据一致性有保障;缺点是代码复杂度增加,有可能会造成用户等待。假设在高并发下,缓存重建期间 key 是锁着的,如果当前并发 1000 个请求,其中 999 个都在阻塞,会导致 999 个用户请求阻塞而等待。

6.异步重建缓存

在这种方案下构建缓存采取异步策略,会从线程池中获取线程来异步构建缓存,从而不会让所有的请求直接到达存储层,该方案中每个Redis key 维护逻辑超时时间,当逻辑超时时间小于当前时间时,则说明当前缓存已经失效,应当进行缓存更新,否则说明当前缓存未失效,直接返回缓存中的 value 值。如在Redis 中将 key 的过期时间设置为 60 min,在对应的 value 中设置逻辑过期时间为 30 min。这样当 key 到了 30 min 的逻辑过期时间,就可以异步更新这个 key 的缓存,但是在更新缓存的这段时间内,旧的缓存依然可用。这种异步重建缓存的方式可以有效避免大量的 key 同时失效。

猜你喜欢

转载自blog.csdn.net/weixin_44958006/article/details/109310046