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感兴趣的朋友,请多多关注💖💖💖
👨‍🔧 个人主页 : 阿千弟

目录
相关文章
|
5月前
|
缓存 NoSQL 关系型数据库
MySQL 与 Redis 如何保证双写一致性?
我是小假 期待与你的下一次相遇 ~
581 7
|
6月前
|
存储 NoSQL 前端开发
Redis专题-实战篇一-基于Session和Redis实现登录业务
本项目基于SpringBoot实现黑马点评系统,涵盖Session与Redis两种登录方案。通过验证码登录、用户信息存储、拦截器校验等流程,解决集群环境下Session不共享问题,采用Redis替代Session实现数据共享与自动续期,提升系统可扩展性与安全性。
384 3
Redis专题-实战篇一-基于Session和Redis实现登录业务
|
6月前
|
存储 缓存 NoSQL
Redis专题-实战篇二-商户查询缓存
本文介绍了缓存的基本概念、应用场景及实现方式,涵盖Redis缓存设计、缓存更新策略、缓存穿透问题及其解决方案。重点讲解了缓存空对象与布隆过滤器的使用,并通过代码示例演示了商铺查询的缓存优化实践。
286 1
Redis专题-实战篇二-商户查询缓存
|
5月前
|
NoSQL 算法 Redis
【Docker】(3)学习Docker中 镜像与容器数据卷、映射关系!手把手带你安装 MySql主从同步 和 Redis三主三从集群!并且进行主从切换与扩容操作,还有分析 哈希分区 等知识点!
Union文件系统(UnionFS)是一种**分层、轻量级并且高性能的文件系统**,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem) Union 文件系统是 Docker 镜像的基础。 镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
671 5
|
6月前
|
缓存 Java 应用服务中间件
Spring Boot配置优化:Tomcat+数据库+缓存+日志,全场景教程
本文详解Spring Boot十大核心配置优化技巧,涵盖Tomcat连接池、数据库连接池、Jackson时区、日志管理、缓存策略、异步线程池等关键配置,结合代码示例与通俗解释,助你轻松掌握高并发场景下的性能调优方法,适用于实际项目落地。
1050 5
|
9月前
|
缓存 监控 NoSQL
Redis 实操要点:Java 最新技术栈的实战解析
本文介绍了基于Spring Boot 3、Redis 7和Lettuce客户端的Redis高级应用实践。内容包括:1)现代Java项目集成Redis的配置方法;2)使用Redisson实现分布式可重入锁与公平锁;3)缓存模式解决方案,包括布隆过滤器防穿透和随机过期时间防雪崩;4)Redis数据结构的高级应用,如HyperLogLog统计UV和GeoHash处理地理位置。文章提供了详细的代码示例,涵盖Redis在分布式系统中的核心应用场景,特别适合需要处理高并发、分布式锁等问题的开发场景。
544 42
|
9月前
|
机器学习/深度学习 存储 NoSQL
基于 Flink + Redis 的实时特征工程实战:电商场景动态分桶计数实现
本文介绍了基于 Flink 与 Redis 构建的电商场景下实时特征工程解决方案,重点实现动态分桶计数等复杂特征计算。通过流处理引擎 Flink 实时加工用户行为数据,结合 Redis 高性能存储,满足推荐系统毫秒级特征更新需求。技术架构涵盖状态管理、窗口计算、Redis 数据模型设计及特征服务集成,有效提升模型预测效果与系统吞吐能力。
960 10
|
9月前
|
缓存 NoSQL 算法
高并发秒杀系统实战(Redis+Lua分布式锁防超卖与库存扣减优化)
秒杀系统面临瞬时高并发、资源竞争和数据一致性挑战。传统方案如数据库锁或应用层锁存在性能瓶颈或分布式问题,而基于Redis的分布式锁与Lua脚本原子操作成为高效解决方案。通过Redis的`SETNX`实现分布式锁,结合Lua脚本完成库存扣减,确保操作原子性并大幅提升性能(QPS从120提升至8,200)。此外,分段库存策略、多级限流及服务降级机制进一步优化系统稳定性。最佳实践包括分层防控、黄金扣减法则与容灾设计,强调根据业务特性灵活组合技术手段以应对高并发场景。
2414 7
|
存储 缓存 NoSQL
Spring Boot2.5 实战 MongoDB 与高并发 Redis 缓存|学习笔记
快速学习 Spring Boot2.5 实战 MongoDB 与高并发 Redis 缓存
Spring Boot2.5 实战 MongoDB 与高并发 Redis 缓存|学习笔记
|
缓存 NoSQL 安全
6.0Spring Boot 2.0实战 Redis 分布式缓存6.0|学习笔记
快速学习6.0Spring Boot 2.0实战 Redis 分布式缓存6.0。
622 0
6.0Spring Boot 2.0实战 Redis 分布式缓存6.0|学习笔记