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

简介: 本篇文章讲述了在高并发场景下 redis缓存一致性问题 & 秒杀场景下的实战分析, 数据库缓存不一致解决方案, 缓存与数据库双写一致以及秒杀场景下缓存一致性问题的实战解决方案

👳我亲爱的各位大佬们好

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

@[toc]
在这里插入图片描述

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

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

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

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

①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. 先删除缓存;

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

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

删除缓存重试机制

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

在这里插入图片描述

除缓存重试机制

1. 写请求更新数据库

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

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

减库存的方式

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

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

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

866cf5a1baf5430a85321906f7b576fb.gif

性能的优化

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

高并发读

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

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

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

高并发写

缓存

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

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

优化DB性能

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

排队

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

006HJgYYgy1ft42gwg5hog306y06yjwa.gif

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

目录
相关文章
|
7月前
|
缓存 NoSQL 关系型数据库
MySQL 与 Redis 如何保证双写一致性?
我是小假 期待与你的下一次相遇 ~
813 7
|
7月前
|
缓存 负载均衡 监控
135_负载均衡:Redis缓存 - 提高缓存命中率的配置与最佳实践
在现代大型语言模型(LLM)部署架构中,缓存系统扮演着至关重要的角色。随着LLM应用规模的不断扩大和用户需求的持续增长,如何构建高效、可靠的缓存架构成为系统性能优化的核心挑战。Redis作为业界领先的内存数据库,因其高性能、丰富的数据结构和灵活的配置选项,已成为LLM部署中首选的缓存解决方案。
797 25
|
7月前
|
缓存 并行计算 监控
vLLM 性能优化实战:批处理、量化与缓存配置方案
本文深入解析vLLM高性能部署实践,揭秘如何通过continuous batching、PagedAttention与前缀缓存提升吞吐;详解批处理、量化、并发参数调优,助力实现高TPS与低延迟平衡,真正发挥vLLM生产级潜力。
1986 0
vLLM 性能优化实战:批处理、量化与缓存配置方案
|
8月前
|
存储 缓存 NoSQL
Redis专题-实战篇二-商户查询缓存
本文介绍了缓存的基本概念、应用场景及实现方式,涵盖Redis缓存设计、缓存更新策略、缓存穿透问题及其解决方案。重点讲解了缓存空对象与布隆过滤器的使用,并通过代码示例演示了商铺查询的缓存优化实践。
355 1
Redis专题-实战篇二-商户查询缓存
|
7月前
|
NoSQL 算法 Redis
【Docker】(3)学习Docker中 镜像与容器数据卷、映射关系!手把手带你安装 MySql主从同步 和 Redis三主三从集群!并且进行主从切换与扩容操作,还有分析 哈希分区 等知识点!
Union文件系统(UnionFS)是一种**分层、轻量级并且高性能的文件系统**,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem) Union 文件系统是 Docker 镜像的基础。 镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
839 6
|
7月前
|
缓存 运维 监控
Redis 7.0 高性能缓存架构设计与优化
🌟蒋星熠Jaxonic,技术宇宙中的星际旅人。深耕Redis 7.0高性能缓存架构,探索函数化编程、多层缓存、集群优化与分片消息系统,用代码在二进制星河中谱写极客诗篇。
1436 3
|
8月前
|
缓存 NoSQL 关系型数据库
Redis缓存和分布式锁
Redis 是一种高性能的键值存储系统,广泛用于缓存、消息队列和内存数据库。其典型应用包括缓解关系型数据库压力,通过缓存热点数据提高查询效率,支持高并发访问。此外,Redis 还可用于实现分布式锁,解决分布式系统中的资源竞争问题。文章还探讨了缓存的更新策略、缓存穿透与雪崩的解决方案,以及 Redlock 算法等关键技术。
|
10月前
|
存储 缓存 安全
Go语言实战案例-LRU缓存机制模拟
本文介绍了使用Go语言实现LRU缓存机制的方法。LRU(最近最少使用)是一种常见缓存淘汰策略,当缓存满时,优先删除最近最少使用的数据。实现中使用哈希表和双向链表结合的方式,确保Get和Put操作均在O(1)时间内完成。适用于Web缓存、数据库查询优化等场景。
|
12月前
|
缓存 NoSQL 关系型数据库
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
|
缓存 NoSQL Java
Redis+Caffeine构建高性能二级缓存
大家好,我是摘星。今天为大家带来的是Redis+Caffeine构建高性能二级缓存,废话不多说直接开始~
1585 0