由浅入深的介绍扣减业务中的一些高并发构建方案(上)

简介: 在后台开发领域,高并发的扣减一直是比较热门的话题,在各类技术博客、大会分享以及面试问题中出现频率都非常高。可见它的重要性和技术知识点的密集性。

前言

在后台开发领域,高并发的扣减一直是比较热门的话题,在各类技术博客、大会分享以及面试问题中出现频率都非常高。可见它的重要性和技术知识点的密集性。

此次主题的技术分享将分上中下三节来介绍,将由浅入深,由简至繁的介绍扣减业务中的一些高并发构建方案,这些方案中实现复杂度、支撑的性能和并发的量级有所区别

本文为第一节(上)的介绍。

对扣减业务的理解

相信大家对秒杀高并发扣减库存有所了解,这只是扣减类业务中的一个有代表性、具备一定记住复杂度的场景,它并不能代表扣减类业务的全部场景。

除了秒杀之外,常见的扣减类业务有:

  • 购买一个或多个商品时扣减的库存
  • 针对用户设置的某个或某几个商品做到购买的次数限制
  • 支付订单时扣减的金额等等。

上述业务场景有几个共性点。

  1. 购买的或设置需要扣减的数量
  2. 一次可以是一个或多个
  3. 数量是共享的,每个用户都可以扣减某一个数据的数量

扣减类业务定义:

扣减类业务是指在电商、金融、物流等领域中,需要对某种资源进行扣减的业务,如库存扣减、余额扣减、物流资源扣减等。 这类业务通常需要保证数据的一致性和准确性,避免出现超卖、欠费、漏发等问题。

扣减类业务的处理过程通常包括以下几个步骤:

  1. 根据业务需求,确定需要扣减的资源类型和数量。
  2. 查询资源的当前状态,判断是否满足扣减条件。
  3. 如果满足扣减条件,则进行扣减操作,并更新资源的状态。
  4. 如果不满足扣减条件,则返回错误信息,提示用户资源不足或操作失败。

扣减业务关注的技术点

发生资源扣减,必然就会存在返还对应的资源。

比如用户购买了商品之后,因为一些原因想要退货,这个时候就需要将商品的库存、商品设置的购买次数等,因此在实现的时候还需要考虑返还。

基于扣减的业务的定义,我们把关于扣减的实现需要关注的点如下:

  • 当前剩余的数量需要大于等于当次需要扣减的数量,既不允许超卖
  • 对同一个数据的数量存在用户并发扣减
  • 需要保证并发一致性,可用性和性能,性能至少是秒级
  • 次扣减有多个数量时,其中一个扣减不成功,既不成功需要回滚

返回实现需要关注的技术点:

  • 必须有扣减才能返还
  • 返还的熟练必须要加回来,不能少
  • 返还的总数量不能大于扣减时的总量
  • 一次扣减可以有多次返还
  • 返还需要保证幂等性

在了解了扣减的业务的场景定义,确定了在实现时需要包含的功能点以及各功能点的实现要求后,下面将开始介绍扣减资源的实现方案。

下面将以库存扣减为例介绍实现方案,其他扣减场景,比如限次购买、支付扣减等技术方案基本类似,可以举一反三。

使用数据库实现扣减

顾名思义,纯数据库的方案就是扣减业务的实现完全依赖数据库提供的各项功能,而不依赖其他额外的一些存储和中间进了。

好处:逻辑简单,开发及部署成本低

主要是依赖各类主流数据库提供的两个特性:

  • 基于数据库乐观所在方式,保证数据并发扣减的强一致性。
  • 基于数据库的事务实现批量扣减,部分失败时的数据回滚。

基于上述特性实现的架构方案如下图所示:

数据库方式扣减实现.png

扣减服务一般是后端内部实现的 rpc 服务,不涉及前端,通常是供后端其他服务调用,比如购买等。

数据库主要涉及两张表,扣减剩余数量表和流水表

  • 扣减剩余数量表主要字段结构
字段名 英文名 含义
商品 ID f_sku_id 商品标识
当前剩余可购买数量 f_leaved_amount 剩余可用数量,随扣减实时变化
  • 流水表主要字段结构
字段名 英文名 含义
扣减编号 uuid 唯一标识一次成功扣减记录
商品 ID f_sku_id 商品标识
本次扣减数量 num 本次需要扣减的商品数量

扣减接口实现流程

接口实现流程图如下:

扣减接口实现流程.png

流程分析:

首先进行的是数据校验,在其中可以做一些常规的参数格式校验。

其次,它还可以进行库存库存的前置校验。比如当数据库中库存只有 8 个时,而用户要购买 10 个,此时在数据校验中即可前置拦截,减少对于数据库的写操作。

如果在校验时剩余库存有八个,此时校验会通过,但在后续的实际扣减时,因为其他用户也在并发的扣减,可能会出现了幻读

因此,用户实际去扣减时,不足两个导致失败,这种场景就会导致多一次数据库查询,降低了整体的扣减性能。

在实践中,前置校验是必须的。

在事务之后,则是数据库更新操作,因为用户扣减的商品数量可以是一个或多个,只要其中一个扣减不成功,则判定用户不能购买。

注意,因为在事物之后对商品使用 for 循环进行处理,每一次循环都需要判断结果,如果一个扣减失败,则进行事物回滚。

单条商品的扣减 SQL如下:

单条商品扣减sql.png

此 SQL 采用类似乐观锁的方式实现了原子性。在外条件里,判断此时需要的数量小于等于剩余的数量。

在扣减服务的代码里,判断此 SQL 执行的返回值,如果值为 1,表示扣减成功,即用户此次购买的数量、当前的库存可以满足,否则返回 0 进行回滚即可。

扣减完成之后,需要记住流水数据,每一次扣减时都需要外部用户传入一个UUID作为流水编号,此编号是全局唯一的。

用户在扣减时传入唯一的编号有两个作用:

  • 第一个作用是当用户返还数量时,需要带回此编号,因为标识次返还属于历史上的具体哪次扣减
  • 第二个作用是进行幂等性控制,当用户调用扣减接口出现超时时,因为用户不知道是否成功,用户可以采用此编号进行重试或反查,在重试时使用此编号进行标识防重。

扣减架构实现升级

我们知道多一次查询,就会增加数据库库的压力,同时对整体服务性能也有一定的影响,因此可以通过读写分离进行升级优化。

升级后的接口实现流程如下图所示:

扣减实现升级架构.png

可以看到图中的升级策略采用数据库的读写分离来实现,数据库已有的功能,改动较小。

扣减服务分两个数据操作,查询时读从库,扣减(写操作)时写主库。

读写分离之后,根据二八原则,80%的均为读流量,主库的压力降低了 80%。

但采用了读写分离,也会导致读取的数据不准确(主从可能会有延迟)的问题。

但是库存也是实时变化的,短暂的差异业务是可以容忍的。

扣减架构实现再次升级

上面提到的因为在扣减前为了降低数据库的压力,增加了前置校验导致的性能下降问题并没有得到太多实质性的升级解决。

那么接下来我们该从什么方向上解决这个问题了?如下图所示:

扣减实现再升级架构.png

可以看到升级后,增加了缓存,用于提供读数据,提高性能;另外缓存的数据由从数据库的 binlog 异构同步过来

基于该实现架构,提高了整体性能,从而提高用户的体验性。

综上所述,纯数据库扣减方案有以下几个优点:

  • 实现简单,即使读使用了前置缓存,整体代码工程就两个,即扣减服务和数据映射同步服务
  • 使用数据库的 ACD 特性进行扣减,业务上库存数量既不会出现超卖,也不会出现少卖

不足之处:

  • 但不足之处是当扣减 SQ 数量增多时,性能会较差。因为对每个 SQ 都需要单独扣减,导致事物非常大,极端情况下可能出现几十秒的情况。

此扣减方案适用场景

在上述的优点和不足背景下,此方案在落地上有哪些适用场景?

  • 在一些企业内部 ERP 系统里的次数限制
  • 中小电商占领的库存管理
  • 政府系统等场景里

其实此方案是比较适合系统的用户并发数、对于请求的耗时要求、购买商品的数量相对较小的场景。

总结

综上所述,不同的扣减库存方案各有优缺点,需要根据具体的业务场景和需求进行选择。

一个优秀的方案一定是建立在对业务需求和对本质问题的理解之上。

如果你觉得今天的内容对你所启发,欢迎分享给身边的朋友,我们一起交流。


思考题:

  1. 是否可以使用数据库索引来提升性能?应如何设置索引来满足如防重等诉求?
  2. 能否将数据库进行分库或分表,利用分库或分表来提升写入的性能?该如何进行分库或分表?需要考虑的因素有哪些?

可以把你的想法、思路或者总结写在评论区。

相关文章
|
存储 缓存 安全
高并发内存池实战:用C++构建高性能服务器(下)
高并发内存池实战:用C++构建高性能服务器
高并发内存池实战:用C++构建高性能服务器(下)
|
6月前
|
负载均衡 前端开发 算法
聊聊高并发应用中电商秒杀场景的方案实现
聊聊高并发应用中电商秒杀场景的方案实现
290 0
|
15天前
|
缓存 关系型数据库 MySQL
高并发架构系列:数据库主从同步的 3 种方案
本文详解高并发场景下数据库主从同步的三种解决方案:数据主从同步、数据库半同步复制、数据库中间件同步和缓存记录写key同步,旨在帮助解决数据一致性问题。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
高并发架构系列:数据库主从同步的 3 种方案
|
5月前
|
消息中间件 数据挖掘 程序员
【建议收藏】高并发下的分布式事务:如何选择最优方案?
本文介绍了分布式事务的三种常见解决方案。在分布式系统中,事务处理变得复杂,需确保ACID特性。TCC(Try-Confirm-Cancel)方案适用于严格资金要求的场景,如银行转账,通过预留、确认和取消步骤确保一致性。可靠消息最终一致性方案适合一致性要求较低的场景,如电商积分处理,通过消息中间件实现最终一致性。最大努力通知方案则用于允许不一致的场景,如数据分析,通过重复通知尽可能达成一致性。选择合适的方案取决于具体应用场景。
156 5
|
3月前
|
消息中间件 负载均衡 应用服务中间件
高并发环境下的Nginx整合方案
【8月更文挑战第20天】在高并发环境下,整合Nginx代理服务器、静态文件服务器、Tomcat集群、Mycat数据库读写分离和消息队列,可以构建一个强大、灵活且可扩展的Web服务架构。
47 1
|
5月前
|
canal 缓存 关系型数据库
高并发场景下,6种方案,保证缓存和数据库的最终一致性!
在解决缓存一致性的过程中,有多种途径可以保证缓存的最终一致性,应该根据场景来设计合适的方案,读多写少的场景下,可以选择采用“Cache-Aside结合消费数据库日志做补偿”的方案,写多的场景下,可以选择采用“Write-Through结合分布式锁”的方案,写多的极端场景下,可以选择采用“Write-Behind”的方案。
1273 0
|
6月前
|
缓存 监控 测试技术
ERP系统对接方案与API接口封装系列(高并发)
企业资源规划(ERP)系统是现代企业管理的核心,它集成了企业内部的各个部门和业务流程。为了实现ERP系统与其他外部系统或应用程序之间的数据交换和协作,需要对接方案。API(应用程序编程接口)是实现系统对接的常用方法之一。
|
6月前
|
前端开发 Java API
构建异步高并发服务器:Netty与Spring Boot的完美结合
构建异步高并发服务器:Netty与Spring Boot的完美结合
|
存储 缓存 Linux
高并发内存池实战:用C++构建高性能服务器(上)
高并发内存池实战:用C++构建高性能服务器
|
缓存 负载均衡 监控
Flask 高并发部署方案详细教程!
Flask 高并发部署方案详细教程!

热门文章

最新文章

  • 1
    高并发场景下,到底先更新缓存还是先更新数据库?
    67
  • 2
    Java面试题:解释Java NIO与BIO的区别,以及NIO的优势和应用场景。如何在高并发应用中实现NIO?
    75
  • 3
    Java面试题:设计一个线程安全的单例模式,并解释其内存占用和垃圾回收机制;使用生产者消费者模式实现一个并发安全的队列;设计一个支持高并发的分布式锁
    68
  • 4
    Java面试题:如何实现一个线程安全的单例模式,并确保其在高并发环境下的内存管理效率?如何使用CyclicBarrier来实现一个多阶段的数据处理任务,确保所有阶段的数据一致性?
    62
  • 5
    Java面试题:结合建造者模式与内存优化,设计一个可扩展的高性能对象创建框架?利用多线程工具类与并发框架,实现一个高并发的分布式任务调度系统?设计一个高性能的实时事件通知系统
    55
  • 6
    Java面试题:假设你正在开发一个Java后端服务,该服务需要处理高并发的用户请求,并且对内存使用效率有严格的要求,在多线程环境下,如何确保共享资源的线程安全?
    69
  • 7
    在Java中实现高并发的数据访问控制
    42
  • 8
    使用Java构建一个高并发的网络服务
    29
  • 9
    微服务06----Eureka注册中心,微服务的两大服务,订单服务和用户服务,订单服务需要远程调用我们的用,户服务,消费者,如果环境改变,硬编码问题就会随之产生,为了应对高并发,我们可能会部署成一个集
    37
  • 10
    如何设计一个秒杀系统,(高并发高可用分布式集群)
    129