Redis高级

发布时间 2024-01-03 21:46:35作者: yanggdgg

一、数据类型

1. String

1.1 简介

String是Redis最基本的类型,一个key对应一个value。String是二进制安全的,意味着String可以包含任何数据,比如序列化对象或者一张图片。String最多可以放512M的数据。

1.2 使用场景

value 除了是字符串以外还可以是数字。

  • 计数器

  • 统计多单位的数量

  • 粉丝数

  • 对象缓存存储

  • 分布式锁

2. List

2.1 简介

List是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。

底层是一个双向链表, 对两端操作性能极高,通过索引操作中间的节点性能较差。

一个List最多可以包含 2^{32}-1个元素 ( 每个列表超过40亿个元素)。

2.2 使用场景

  • 消息队列

  • 排行榜

  • 最新列表

3. Set 

3.1 简介

与List类似是一个列表功能,但Set是自动排重的,当需要存储一个列表数据,又不希望出现重复数据时,Set是一个很好的选择。

Set是String类型的无序集合,它底层其实是一个value为null的hash表,所以添加、删除、查找的时间复杂度都是O(1)。

3.2 使用场景

  • 黑白名单

  • 随机展示

  • 好友

  • 关注人

  • 粉丝

  • 感兴趣的人集合

4. Hash

4.1 简介

Hash是一个键值对的集合。Hash 是一个 String 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。

Hash存储结构优化

  • 如果field数量较少,存储结构优化为类数组结构

  • 如果field数量较多,存储结构使用HashMap

4.2 使用场景

  • 购物车

  • 存储对象

5. Zset

5.1 简介

Zset与Set非常相似,是一个没有重复元素的String集合。

不同之处是Zset的每个元素都关联了一个分数(score),这个分数被用来按照从低分到高分的方式排序集合中的元素。集合的元素是唯一的, 但分数可以重复。

因为元素是有序的,所以可以根据分数(score)或者次序(position)来获取一个范围内的元素。

5.2 使用场景

  • 延时队列

  • 排行榜

  • 限流

6. Bitmaps

6.1 简介

在Redis中,Bitmaps是一种特殊的数据类型,它实际上是一系列位(0或1)的集合。这个数据结构允许你对位进行高效的操作,并且可以用于解决一些有趣的问题,如位图统计、用户在线状态等。

6.2 使用场景

  • 活跃天数

  • 打卡天数

  • 登录天数

  • 用户签到

  • 统计活跃用户

  • 统计用户是否在线

  • 实现布隆过滤器

7. Geospatial

7.1 简介

GEO,Geographic,地理信息的缩写。该类型就是元素的二维坐标,在地图上就是经纬度。Redis基于该类型,提供了经纬度设置、查询、范围查询、距离查询、经纬度Hash等常见操作。

7.2 使用场景

  • 附近的电影院

  • 附近的好友

  • 离最近的火锅店

8. HyperLogLog

8.1 简介

在我们做站点流量统计的时候一般会统计页面UV(独立访客:unique visitor)和PV(即页面浏览量:page view)。redis HyperLogLog是用来做基数统计的算法,HyperLogLog的优点是:在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且使很小的。

什么是基数?

比如数据集{1,3,5,7,5,7,8},那么这个数据集的基数集为{1,3,5,7,8}, 基数(不重复元素个数)为5.基数估计就是在误差可接受的范围内,快速计算基数。

8.2 使用场景

基数不大,数据量不大就用不上,会有点大材小用浪费空间,有局限性,就是只能统计基数数量,而没办法去知道具体的内容是什么,和bitmap相比,属于两种特定统计情况,简单来说,HyperLogLog 去重比 bitmaps 方便很多,一般可以bitmap和hyperloglog配合使用,bitmap标识哪些用户活跃。

  • 网站PV统计

  • 网站UV统计

  • 统计访问量(IP数)

  • 统计在线用户数

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

  • 统计文章真实阅读数

二、Redis发布订阅

Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。

 订阅命令:

subscribe 主题名字

发布命令:

publish 主题名 发布的内容

三、慢查询

1. 介绍

慢查询就是查询速度较慢的查询。

说明:

  1. 慢查询发生在第3阶段

  2. 客户端超时不一定慢查询,但慢查询是客户端超时的一个可能因素

  3. 慢查询日志是存放在Redis内存列表中

2. 慢查询日志

慢查询日志是Redis服务端在命令执行前后计算每条命令的执行时长,当超过某个阈值是记录下来的日志。

日志中记录了慢查询发生的时间,还有执行时长、具体什么命令等信息,它可以用来帮助开发和运维人员定位系统中存在的慢查询。 

可以使用 slowlog get 命令获取慢查询日志,在 slowlog get 后面还可以加一 个数字,用于指定获取慢查询日志的条数。

SLOWLOG get 3

slowlog len 命令获取慢查询日志的长度

> slowlog len
(integer) 121

3. 配置慢查询参数

配置慢查询参数如下:

  • 命令执行时长的指定阈值 slowlog-log-slower-than:

    slowlog-log-slower-than的作用是指定命令执行时长的阈值,执行命令的时长超过这个阈值时就会被记录下来。

  • 存放慢查询日志的条数 slowlog-max-len。

    slowlog-max-len的作用是指定慢查询日志最多存储的条数。实际上,Redis使用了一个列表存放慢查询日志,slowlog-max-len就是这个列表的最大长度。

配置方式:

【1】查看慢日志配置

查看redis慢日志配置,登陆redis服务器,使用redis-cli客户端连接redis server

127.0.0.1:6379> config get slow*
1) "slowlog-max-len"
2) "128"
3) "slowlog-log-slower-than"
4) "10000"

参数说明:10000阈值,单位微秒,此处为10毫秒,128慢日志记录保存数量的阈值,此处保存128条。

【2】修改Redis配置文件

比如,把slowlog-log-slower-than设置为1000,slowlog-max-len 设置为1200

slowlog-log-slower-than 1000
slowlog-max-len 1200

【3】使用 config set 命令动态修改。

比如,还是把slowlog-log-slower-than设置为1000,slowlog-max-len设置为1200

> config set slowlog-log-slower-than 1000
OK
> config set slowlog-max-len 1200
OK
> config rewrite
OK

4. 建议

slowlog-max-len配置建议

  • 线上建议调大慢查询列表,记录慢查询时Redis会对长命令做截断操作,并不会占用大量内存。

  • 增大慢查询列表可以减缓慢查询被剔除的可能,例如线上可设置为1000以上。

slowlog-log-slower-than配置建议

  • 默认值超过10毫秒判定为慢查询,需要根据Redis并发量调整该值。

  • 由于Redis采用单线程响应命令,对于高流量的场景,如果命令执行时间在1毫秒以上,那么Redis最多可支撑OPS(Operations Per Second每秒操作数)不到1000。因此对于高OPS场景的Redis建议设置为1毫秒。

四、Redis 流水线 pipeline

批量网络命令通信

 经历了 n次时间 = n次网络时间 + n次命令时间 

 

流水线:

 经历了 1次pipeline(n条命令) = 1次网络时间 + n次命令时间,这大大减少了网络时间的开销,这就是流水线。

pipeline-Jedis实现

引入依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

使用pipeline

@Test
void test3() {

    redisTemplate.executePipelined((RedisConnection connection)->{
        long start = System.currentTimeMillis();
        Object k1 = connection.get("k1".getBytes());
        Object k2 = connection.get("k2".getBytes());
        Object k3 = connection.get("k3".getBytes());
        long end = System.currentTimeMillis();
        System.out.println("花费时间:"+(end-start));
        return null;
    });

}

五、Redis持久化机制

Redis提供了两个不同形式的持久化方式

  • RDB(Redis DataBase)

  • AOF(Append Only File)

1. RDB

在指定的时间间隔内将内存的数据集快照写入磁盘,也就是行话讲的快照,它恢复时是将快照文件直接读到内存里。

注意: 这种格式是经过压缩的二进制文件。

2. AOF

日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来。

3. 如何选择

不要仅仅使用RDB

RDB数据快照文件,都是每隔5分钟,或者更长时间生成一次,这个时候就得接受一旦redis进程宕机,那么会丢失最近5分钟的数据。

也不要仅仅使用AOF

  1. 你通过AOF做冷备,没有RDB做冷备,来的恢复速度更快。

  2. RDB每次简单粗暴生成数据快照,更加健壮,可以避免AOF这种复杂的备份和恢复机制的bug。

综合使用AOF和RDB两种持久化机制

用AOF来保证数据不丢失,作为数据恢复的第一选择,用RDB来做不同程度的冷备,在AOF文件都丢失或损坏不可用的时候,还可以使用RDB来进行快速的数据恢复。

六、Redis事务

1. 简介

Redis事务是一组命令的集合,一个事务中的所有命令都将被序列化,按照一次性、顺序性、排他性的执行一系列的命令。

 

Redis事务三大特性

  • 单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断;

  • 没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行,也就不存在”事务内的查询要看到事务里的更新,在事务外查询不能看到”。

  • 不保证原子性:redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚;

Redis事务执行的三个阶段

 

  • 开启:以 MULTI 开始一个事务;

  • 入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面;

  • 执行:由 EXEC 命令触发事务;

2. 基本操作

Multi:开始事务

Exec:执行

discard:放弃事务

事务从输入Multi命令开始,输入的命令都会依次压入命令缓冲队列中,并不会执行,直到输入Exec后,Redis会将之前的命令缓冲队列中的命令依次执行。组队过程中,可以通过discard来放弃组队。

注意:

  • 命令集合中含有错误的指令(注意是语法错误),均连坐,全部失败。
  • 运行时错误,即非语法错误,正确命令都会执行,错误命令返回错误。

七、主从复制

1. 简介

 主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave),数据的复制是单向的,只能由主节点到从节点

 

主从复制的作用

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

  2. 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。

  3. 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务 (即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。

  4. 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。

2. 原理

主从复制可以分为3个阶段

  • 连接建立阶段(即准备阶段)

  • 数据同步阶段

  • 命令传播阶段

复制过程大致分为6个过程

 

  1. 保存主节点(master)信息。

  2. 从节点(slave)内部通过每秒运行的定时任务维护复制相关逻辑,当定时任务发现存在新的主节点后,会尝试与该节点建立网络连接

  3. 从节点与主节点建立网络连接

    从节点会建立一个 socket 套接字,从节点建立了一个端口为51234的套接字,专门用于接受主节点发送的复制命令

  4. 发送ping命令

    连接建立成功后从节点发送 ping 请求进行首次通信。

作用:

  • 检测主从之间网络套接字是否可用。

  • 检测主节点当前是否可以接受命令 。

  1. 权限验证。

    如果主节点设置了 requirepass 参数,则需要密码验证,从节点必须配置 masterauth 参数保证与主节点相同的密码才能通过验证;如果验证失败复制将终止,从节点重新发起复制流程。

  2. 同步数据集。

    主从复制连接正常通信后,对于首次建立复制的场景,主节点会把持有的数据全部发送给从节点,这部分操作是耗时最长的步骤。

  1. 主从同步策略

     主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。 redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。

八、Redis哨兵监控

1. 简介

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

哨兵作用

  1. 集群监控:负责监控redis master和slave进程是否正常工作

  2. 消息通知:如果某个redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员

  3. 故障转移:如果master node挂掉了,会自动转移到slave node上

  4. 配置中心:如果故障转移发生了,通知client客户端新的master地址

2. 原理

2.1 监控阶段

 

  1. sentinel(哨兵1)----->向master(主)和slave(从)发起info,拿到全信息。

  2. sentinel(哨兵2)----->向master(主)发起info,就知道已经存在的sentinel(哨兵1)的信息,并且连接slave(从)。

  3. sentinel(哨兵2)----->向sentinel(哨兵1)发起subscribe(订阅)。

2.2 通知阶段

sentinel不断的向master和slave发起通知,收集信息。

2.3 故障转移阶段

通知阶段sentinel发送的通知没得到master的回应,就会把master标记为SRI_S_DOWN,并且把master的状态发给各个sentinel,其他sentinel听到master挂了,说我不信,我也去看看,并把结果共享给各个sentinel,当有一半的sentinel都认为master挂了的时候,就会把master标记为SRI_0_DOWN。

2.4 投票阶段

方式: 自己最先接到哪个sentinel的竞选通知就会把票投给它。

剔除一些情况:

  1. 不在线的

  2. 响应慢的

  3. 与原来master断开时间久的

  4. 优先级原则

总结

  1. 监控: 哨兵周期性地检查与其关联的Redis节点的健康状态。它发送PING命令,检查节点是否响应,还可以通过执行自定义脚本来进一步检查节点的状态。

  2. 故障检测: 当一个Redis节点被认为不可用时,哨兵将其标记为主观下线(SDOWN)。主观下线是哨兵自己的观点,表示它认为节点已经故障。

  3. 共识: 多个哨兵之间进行通信,通过共识算法判断是否有足够的哨兵都认为某个节点是主观下线,形成共识。这时,哨兵将该节点标记为客观下线(ODOWN)。

  4. 故障切换: 当一个主节点被标记为客观下线时,哨兵会选择一个可用的从节点升级为新的主节点,以确保系统的持续可用性。这个过程叫做故障切换。

  5. 通知: 哨兵会通知其他哨兵和客户端关于发生的故障切换,以便它们可以调整配置并连接到新的主节点。

  6. 自愈和监控: 一旦故障切换完成,哨兵会继续监控集群中的节点,以确保新的主节点正常工作。如果其他节点恢复,哨兵还可以将它们重新加入集群。

  7. 配置管理: 哨兵还负责管理配置信息,包括监视的节点、故障切换策略、通知方式等。它可以根据需要动态调整配置。

九、Redis Cluster

Redis有三种集群模式

  • 主从模式

  • Sentinel模式

  • Cluster模式

哨兵模式缺点:

  1. 当master挂掉的时候,sentinel 会选举出来一个 master,选举的时候是没有办法去访问Redis的,会存在访问瞬断的情况;

  2. 哨兵模式,对外只有master节点可以写,slave节点只能用于读。尽管Redis单节点最多支持10W的QPS,但是在电商大促的时候,写数据的压力全部在master上。

  3. Redis的单节点内存不能设置过大,若数据过大在主从同步将会很慢;在节点启动的时候,时间特别长;

1. Redis Cluster简介

Redis集群是一个由多个主从节点群组成的分布式服务集群,它具有复制、高可用和分片特性。

 

Redis集群的优点

  • Redis集群有多个master,可以减小访问瞬断问题的影响

  • Redis集群有多个master,可以提供更高的并发量 

  • Redis集群可以分片存储,这样就可以存储更多的数据

2. 原理

 Redis Cluster将所有数据划分为16384个slots(槽位),每个节点负责其中一部分槽位。槽位的信息存储于每个节点中。只有master节点会被分配槽位,slave节点不会分配槽位。

3. SpringDataRedis 连接集群

引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置文件

spring:
  redis:
    cluster:
      nodes: 192.168.184.137:8001,192.168.184.137:8002,192.168.184.138:8001,192.168.184.138:8002,192.168.184.139:8001,192.168.184.139:800

十、Redis常见问题

1. 集群脑裂

Redis集群中的“脑裂”(Brain Split)是指集群中的不同节点之间因为网络问题而失去联系,导致集群被分割成两个或多个相互独立的子集。在这种情况下,集群的不同部分可能无法相互通信,每个部分都可能认为自己是有效的集群,并试图独立进行操作。

Redis的集群脑裂是指因为网络问题,导致Redis Master节点跟Redis slave节点和Sentinel集群处于不同的网络分区,此时因为sentinel集群无法感知到master的存在,所以将slave节点提升为master节点。

注意:

此时存在两个不同的master节点,就像一个大脑分裂成了两个。集群脑裂问题中,如果客户端还在基于原来的master节点 继续写入数据,那么新的Master节点将无法同步这些数据,当网络问题解决之后,sentinel集群将原先的Master节点降为slave节点,此时再从新的master中同步数据,将会造成大量的数据丢失。

解决方案

redis.conf配置参数:

min-replicas-to-write 1
min-replicas-max-lag 5

参数:

  • 第一个参数表示最少的slave节点为1个

  • 第二个参数表示数据复制和同步的延迟不能超过5秒

2. Redis 缓存预热

出现在大型项目中。

2.1 缓存冷启动

缓存中没有数据,由于缓存冷启动一点数据都没有,如果直接就对外提供服务了,那么并发量上来Mysql就裸奔挂掉了。

2.2 冷启动应用场景

新启动的系统没有任何缓存数据,在缓存重建数据的过程中,系统性能和数据库负载都不太好,所以最好是在系统上线之前就把要缓存的热点数据加载到缓存中,这种缓存预加载手段就是缓存预热。

2.3 解决思路

  • 提前给redis中灌入部分数据,再提供服务

  • 如果数据量非常大,就不可能将所有数据都写入redis,因为数据量太大了,第一是因为耗费的时间太长了,第二根本redis容纳不下所有的数据

  • 需要根据当天的具体访问情况,实时统计出访问频率较高的热数据

  • 然后将访问频率较高的热数据写入redis中,肯定是热数据也比较多,我们也得多个服务并行读取数据去写,并行的分布式的缓存预热

3. Redis缓存穿透

6.3.1 概念

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

解释:

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

6.3.2 解决方案

  • 对空值缓存:如果一个查询返回的数据为空(不管数据是否存在),我们仍然把这个空结果缓存,设置空结果的过期时间会很短,最长不超过5分钟。

  • 布隆过滤器:如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。

6.3.3 布隆过滤器

布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”。

注意:

布隆说不存在一定不存在,布隆说存在你要小心了,它有可能不存在

4. Redis 缓存击穿问题

4.1 概念

一个热点 key,在缓存过期的一瞬间,同时有大量的请求打进来,由于此时缓存过期了,所以请求最终都会走到数据库,造成瞬时数据库请求量大、压力骤增,甚至可能打垮数据库。

4.2 解决方案

  • 互斥锁:在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程拿不到锁就阻塞等着,等到第一个线程将数据写入缓存后,其他线程直接查询缓存。

  • 热点数据不过期:直接将缓存设置为不过期,然后由定时任务去异步加载数据,更新缓存(不咋靠谱)

5. Redis缓存雪崩问题

5.1 概念

缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

5.2 解决方案

  • 过期时间打散:既然是大量缓存集中失效,那最容易想到就是让他们不集中生效。可以给缓存的过期时间时加上一个随机值时间,使得每个 key 的过期时间分布开来,不会集中在同一时刻失效。

  • 热点数据不过期:该方式和缓存击穿一样,也是要着重考虑刷新的时间间隔和数据异常如何处理的情况。

  • 加互斥锁: 该方式和缓存击穿一样,按 key 维度加锁,对于同一个 key,只允许一个线程去计算,其他线程原地阻塞等待第一个线程的计算结果,然后直接走缓存即可

 

  用二级缓存解决:先查询本地nginx缓存查询有没有数据,有数据,直接返回; nginx缓存没有数据再去redis分布式缓存查询,如果有将redis缓存同步nginx缓存在返回;如果redis没有,取数据库查询,数据库存在将数据同步到redis并返回。只要nginx和redis的过期时间不一样,就解决了redis缓存雪崩问题,并合理使用了分布式缓存。

6. Redis 开发规范

6.1 key设计技巧

  1. 把表名转换为key前缀,如 tag:

  2. 把第二段放置用于区分key的字段,对应msyql中主键的列名,如 user_id

  3. 第三段放置主键值,如 2,3,4

  4. 第四段写存储的列名

示例:

#   表名 主键 主键值 存储列名字
set user:user_id:1:name 张三
set user:user_id:1:age 20

6.2 value设计

拒绝bigkey

防止网卡流量、慢查询,string类型控制在10KB以内,hash、 list、set、zset元素个数不要超过5000。

6.3 命令使用

  1. 禁用命令 :禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。

  2. 合理使用select :redis的多数据库较弱,使用数字进行区分,很多客户端支持较差, 同时多业务用多数据库实际还是单线程处理,会有干扰。

  3. 使用批量操作提高效率

    1. 原生命令:例如mget、mset。

    2. 非原生命令:可以使用pipeline提高效率

注意:但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。

  1. 不建议过多使用Redis事务功能

    Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上。

7. Redis 数据一致性问题

缓存说明:

从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。

三种更新策略

  1. 先更新数据库,再更新缓存

  2. 先删除缓存,再更新数据库

  3. 先更新数据库,再删除缓存

先更新数据库,再更新缓存

这套方案,大家是普遍反对的。为什么呢?

线程安全角度 ,同时有请求A和请求B进行更新操作,那么会出现

(1)线程A更新了数据库

(2)线程B更新了数据库

(3)线程B更新了缓存

(4)线程A更新了缓存

这就出现请求A更新缓存应该比请求B更新缓存早才对,但是因为网络等原因,B却比A更早更新了缓存。这就导致了脏数据,因此不考虑。

先删缓存,再更新数据库

该方案会导致不一致的原因是。同时有一个请求A进行更新操作,另一个请求B进行查询操作。那么会出现如下情形:

(1)请求A进行写操作,删除缓存

(2)请求B查询发现缓存不存在

(3)请求B去数据库查询得到旧值

(4)请求B将旧值写入缓存

(5)请求A将新值写入数据库

注意: 该数据永远都是脏数据。

先更新数据库,再延时删缓存√

这种情况存在并发问题吗?

(1)缓存刚好失效

(2)请求A查询数据库,得一个旧值

(3)请求B将新值写入数据库

(4)请求B删除缓存

(5)请求A将查到的旧值写入缓存

发生这种情况的概率又有多少?

发生上述情况有一个先天性条件,就是步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。可是,大家想想,数据库的读操作的速度远快于写操作的,因此步骤(3)耗时比步骤(2)更短,这一情形很难出现。