redis缓存一致性问题 & 秒杀场景下的实战分析

👳我亲爱的各位大佬们好
♨️本篇文章记录的为 redis缓存一致性问题 & 秒杀场景下的实战分析相关内容,适合在学Java的小白,帮助新手快速上手,也适合复习中,面试中的大佬🙉🙉🙉。
♨️如果文章有什么需要改进的地方还请大佬不吝赐教❤️🧡💛
👨‍🔧 个人主页 : 阿千弟


在这里插入图片描述

为什么会有缓存一致性问题

由于我们的缓存的数据源来自于数据库 , 而数据库的数据是会发生变化的 , 因此,如果当数据库中数据发生变化,而缓存却没有同步 , 此时就会有一致性问题存在

数据库缓存不一致解决方案

用户使用缓存中的过时数据,就会产生类似多线程数据安全问题,从而影响业务,产品口碑等;怎么解决呢?有如下几种方案

①Cache Aside Pattern (旁路缓存模式)

人工编码方式:缓存调用者在更新完数据库后再去更新缓存,也称之为双写方案

1. Cache-Aside读流程

在这里插入图片描述

2. Cache-Aside 写流程

更新的时候,先更新数据库,然后再删除缓存

好处
读的时候,先读缓存,缓存命中的话,直接返回数据
缓存没有命中的话,就去读数据库,从数据库取出数据,放入缓存后,同时返回响应。

②Read/Write Through Pattern (读写穿透模式)

由系统本身完成,数据库与缓存的问题交由系统本身去处理

1. Read-Through的简要流程

  • 从缓存读取数据,读到直接返回

  • 如果读取不到的话,从数据库加载,写入缓存后,再返回响应。

2. Write through

Write-Through模式下,当发生写请求时,也是由缓存抽象层完成数据源和缓存数据的更新
在这里插入图片描述

③Write Behind Caching Pattern (异步缓存写入)

Write Behind则是只更新缓存,不直接更新数据库,通过批量异步的方式来更新数据库。

在这里插入图片描述

缓存与数据库双写一致

核心思路如下:

根据 id 查询时,如果缓存未命中,则查询数据库,将数据库结果写入缓存,并设置超时时间

根据 id 修改时,先修改数据库,再删除缓存

保持最终一致性

①. 采用延时双删策略
在这里插入图片描述
伪代码如下

public void write(String key,Object data){
	 redis.delKey(key);
	 db.updateData(data);
	 Thread.sleep(500);
	 redis.delKey(key);
 }

具体的步骤就是:

1. 先删除缓存;
2. 再写数据库;
3. 休眠500毫秒;
4. 再次删除缓存。

这个休眠时间 = 读业务逻辑数据的耗时 + 几百毫秒(redis和数据库主从同步的耗时)。 为了确保读请求结束,写请求可以删除读请求可能带来的缓存脏数据。

删除缓存重试机制

  • 不管是延时双删还是Cache-Aside的先操作数据库再删除缓存,如果第二步的删除缓存失败呢,删除失败会导致脏数据。
  • 删除失败就多删除几次呀,保证删除缓存成功。

在这里插入图片描述

除缓存重试机制

1. 写请求更新数据库
2. 缓存因为某些原因,删除失败
3. 把删除失败的key放到消息队列
4. 消费消息队列的消息,获取要删除的key
5. 重试删除缓存操作
在这里插入图片描述

秒杀场景下缓存一致性问题

减库存的方式

电商场景下的购买过程一般分为两步:下单和付款。“提交订单”即为下单,“支付订单”即为付款。

减库存方式基本有以下几种方式

1. 下单减库存。买家下单后,扣减商品库存。

2. 付款减库存。买家下单后,并不立即扣减库存,而是等到付款后才真正扣减库存。但因为付款时才减库存,如果并发比较高,可能出现买家下单后付不了款的情况,因为商品已经被其他人买走了。

3. 预扣库存。买家下单后,库存为其保留一定的时间(如 15 分钟),超过这段时间,库存自动释放,释放后其他买家可以购买。

下单减库存

优势:用户体验最好。下单减库存是最简单的减库存方式,也是控制最精确的一种。下单时可以直接通过数据库事务机制控制商品库存,所以一定不会出现已下单却付不了款的情况。

劣势:可能卖不出去。正常情况下,买家下单后付款概率很高,所以不会有太大问题。但有一种场景例外,就是当卖家参加某个促销活动时,竞争对手通过恶意下单的方式将该商品全部下单,导致库存清零,那么这就不能正常售卖了。恶意下单的人是不会真正付款的,这正是 “下单减库存” 的不足之处。

付款减库存

优势:一定实际售卖。“下单减库存” 可能导致恶意下单,从而影响卖家的商品销售, “付款减库存” 由于需要付出真金白银,可以有效避免。

劣势:用户体验较差。用户下单后,不一定会实际付款,假设有 100 件商品,就可能出现 200 人下单成功的情况,因为下单时不会减库存,所以也就可能出现下单成功数远远超过真正库存数的情况,这尤其会发生在大促的热门商品上。如此一来就会导致很多买家下单成功后却付不了款,购物体验自然是比较差的。

预扣库存

优势:缓解了以上两种方式的问题。预扣库存实际就是“下单减库存”和 “付款减库存”两种方式的结合,将两次操作进行了前后关联,下单时预扣库存,付款时释放库存。

劣势:并没有彻底解决以上问题。比如针对恶意下单的场景,虽然可以把有效付款时间设置为 10 分钟,但恶意买家完全可以在 10 分钟之后再次下单。

实际如何减库存

业界最为常见的是预扣库存。无论是外卖点餐还是电商购物,下单后一般都有个 “有效付款时间”,超过该时间订单自动释放,这就是典型的预扣库存方案。

预扣库存还需要解决恶意下单和避免超卖的问题。

恶意下单

结合安全和反作弊措施来制止。

  • 识别频繁下单不付款的买家并进行打标,这样可以在打标买家下单时不减库存
  • 为大促商品设置单人最大购买件数,一人最多只能买 N 件商品
  • 对重复下单不付款的行为进行次数限制阻断。

避免超卖

对于普通商品,秒杀只是一种大促手段,即使库存超卖,商家也可以通过补货来解决。而对于一些商品,秒杀作为一种营销手段,完全不允许库存为负,也就是在数据一致性上,需要保证大并发请求时数据库中的库存字段值不能为负。

  • 通过事务来判断,即保证减后库存不能为负,否则就回滚。
  • 直接设置数据库字段类型为无符号整数,这样一旦库存为负就会在执行 SQL 时报错。
  • 使用 CASE WHEN 判断语句:UPDATE item SET inventory CASE WHEN inventory xxx THEN inventory xxx ELSE inventory

在这里插入图片描述

性能的优化

库存是个关键数据,更是个热点数据。对系统来说,热点的实际影响就是 “高读” 和 “高写”,也是秒杀场景下最为核心的一个技术难题。

高并发读

秒杀场景解决高并发读问题,关键词是“分层校验”。在读链路时,只进行不影响性能的检查操作,如用户是否具有秒杀资格、商品状态是否正常、用户答题是否正确、秒杀是否已经结束、是否非法请求等,而不做一致性校验等容易引发瓶颈的检查操作。直到写链路时,才对库存做一致性检查,在数据层保证最终准确性。

在分层校验设定下,系统可以采用分布式缓存甚至 LocalCache 来抵抗高并发读。即允许读场景下一定的脏数据,这样只会导致少量原本无库存的下单请求被误认为是有库存的,等到真正写数据时再保证最终一致性,由此做到高可用和一致性之间的平衡。

分层校验的核心思想是:不同层次尽可能过滤掉无效请求,只在“漏斗” 最末端进行有效处理,从而缩短系统瓶颈的影响路径。

高并发写

缓存

秒杀商品和普通商品的减库存是有差异的,核心区别在数据量级小、交易时间短,如果减库存逻辑非常单一的话,可以直接在一个带有持久化功能的缓存中进行减库存操作。

如果有比较复杂的减库存逻辑,或者需要使用到事务,那就必须在数据库中完成减库存操作。

优化DB性能

库存数据落地到数据库实现其实是一行存储(MySQL),因此会有大量线程来竞争 InnoDB 行锁。但并发越高,等待线程就会越多,TPS 下降,RT 上升,吞吐量会受到严重影响。

排队

通过缓存加入集群分布式锁,从而控制集群对数据库同一行记录进行操作的并发度,同时也能控制单个商品占用数据库连接的数量,防止热点商品占用过多的数据库连接。

在这里插入图片描述

如果这篇【文章】有帮助到你💖,希望可以给我点个赞👍,创作不易,如果有对Java后端或者对spring感兴趣的朋友,请多多关注💖💖💖
👨‍🔧 个人主页 : 阿千弟

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿千弟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值