当前位置: 首页 > news >正文

电商项目实战总结

1、订单系统的设计与实现
(1)订单重复下单问题:高并发场景下下单完成前用户重复点击下单按钮,导致重复生成订单。
解决办法,让下单具有幂等性,多次下单和一次操作效果相同。具体操作就是为订单系统增加一个生成订单号的服务,用户进入创建订单页面时,前端会先调用这个生成订单号的服务获得一个订单号,这样无法网络原因还是用户原因等各种情况,这些重复请求中的订单号都是相同的
(2)订单ABA问题
比如支付完成后填写订单号,一开始填写为666,后更新为888,正常是没有问题的。如果因为网络原因,订单修改666请求达到后,修改成功的消息没有返回到调用方。之后修改888的请求到达,物流单号修改为888。由于666修改成功没有达到,触发重试逻辑,导致物流单号又修改为了888。
解决办法,可以想并发编程中一样,通过增加版本号来解决该问题,订单服务在更新数据时需要比较当前请求中的版本号和数据库中版本号是否一致,如果一致再更新数据版本号+1,否则更新失败。需要注意的是比较版本号、更新数据和版本号加1这一系列操作需要放在同一个事务中执行,具有原子性。
(3)分库分表的设计与实现
分库分表首先要决定是分库还是分表,分表是为了解决因数据量大而导致的查询慢问题,分库是为了应对高并发问题,如果一个数据库实例撑不住,就把并发请求发散到多个实例中。
在分库分表时,为了让B+树的高度控制在一定范围,保证查询的性能,表中 数据往往不宜超过2000w条,分表数量应为2的幂次方,这样迁移的数量最少
分库分表时选择分片键很重要,比如进入主页时只有用户id,这时想要查询订单就需要从所有表查起,所以我们可以往往将订单id拼接用户id后两位,形成最终的订单id。
具体实现:
比较常见的是,通过组件的形式,像Sharding-JDBC 这些组件集成在应用程序内,用于代理应用程序的所有数据库请求,并把请求自动路由到对应的数据库实例上。
2、分布式唯一ID的生成
分布式唯一ID需要保证全局唯一性、趋势递增、单调递增、信息安全。
常见方法介绍:
(1)UUID
优点:性能高,本地生成没有消耗
缺点:UUID16字节太长,通常以36长度的字符串表示,很多场景不适用
信息不安全:基于 MAC 地址生成 UUID 的算法可能会造成 MAC 地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。
另外,UUID作为主键在特定环境下有一些问题,比如在DB场景下非常不适用,由于UUID具有无序性,在InnoDb引擎下,会引起数据位置变动频繁,严重影响性能。
(2)雪花算法及其衍生
雪花算法划分了多端,分别表示机器、时间等
优点是毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。生成ID的性能高。可以根据业务特性分配bit位,灵活性高。
缺点是非常依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务处于不可用
(3)美团的leaf方案
leaf-segment方案:
每次从数据不再只拿取一个,而是分批量拿走一个号段的ID,放进本地的双buffer缓存中,使用双缓存可以避免因号段用尽,导致取号段这个时间出现线程阻塞的问题

left-snowflake方案:
避免时钟回拨,多个节点会在zookeeper中记录自身的系统时间,并定期获取其他节点的系统时间,来判断自身有没有发生时钟回拨,否则会启动失败并报警。
同时为了降低对zookeeper的依赖,会在本地缓存workID文件,即使发生问题,也能使服务征程启动
想要“简单 + 高可用 + 可水平扩展”——选 Leaf‐Segment;
想要“性能极限 + 去中心化 + 严格趋势递增”——选 Leaf‐Snowflake;
3、历史归档数据冷热分离
迁移的过程,我们是逐表批次删除,对于每张订单表,先从MySQL从获得指定批量的数据,写⼊MongoDB,再从MySQL中删除已写⼊MongoDB的部分。
这⾥存在着⼀个多源的数据操作,为了保证数据的⼀致
性,看起来似乎需要分布式事务。但是其实这⾥并不需要分布式事务,解决的关键在于写⼊订单数据到MongoDB
时,我们要记住同时写⼊当前迁⼊数据的最⼤订单ID,让这两个操作执⾏在同⼀个事务之中。
在这个过程中,我们需要注意的问题是,尽量不要影响线上的业务。迁移如此⼤量的数据,或多或少都会影响数
据库的性能,因此应该尽量选择在闲时迁移⽽且每次数据库操作的记录数不宜太多。迁移太多容易出错,另外迁移前一定要做好备份,这样即使操作失误也可以用备份来恢复。
4、订单支付通过延迟任务实现支付超时判断
电商项目在用户下单时就需要锁定商品库存,
如果用户长期不支付,锁定的商品就无法正常销售。
所以,通常对于订单都会设定一个支付时间,比如五分钟内需要完成支付。如果没有支付,就需要取消订
单,释放库存。那应该如何设计订单的超时判断流程?

如果通过设置定时任务,比如五分钟对订单进行超时判断,但这消息并不及时,对于并发量较高的场景不适合,更别说对于秒杀场景了

RocketMQ事务消息机制的核心是对消息状态进行不断的确认。具体流程是支付宝预下单时会发送事务消息,用来通知订单取消。然后rocketmq会回查订单状态,如果超过最大次数,就会取消订单,如果支付成功就会RollBack,消息取消,后续就不会取消的订单了,如果未支付 ,记录回查次数后,返回UNKNOW状态,等待下次回查。
5、项目总结:
(1)运营人员在秒杀的运营后台,根据指定商品,创建秒杀活动,指定活动的开始时间、结束时间、活动库存等。
(2)秒杀活动开始前,会向商城系统的redis cluster集群写入首页秒杀活动信息和往秒杀系统的Redis主从集群写诸如秒杀商品库存等信息。
(3)用户进入秒杀商详页准备秒杀
(4)商详页可以看到立即抢购按钮,这里可以增加逻辑判断来限制是否可以点击
(5)结算页,用户可以更改购买数量,切换地址,支付方式等,更复杂场景还可以支持积分、购物券、红包、配送时效等。
(6)确认无误后,用户提交订单,在这里后端可以调用风控、限购等接口,来完善校验,都通过后,完成库存的扣减和订单的生成
(7)订单完成后根据用户选择的支付方式,跳转到对应的支付页面。
这样一来,秒杀业务从开始到用户抢购,到最后活动结束关闭,整个流程就形成了闭环。

用户点击下单按钮->向订单服务发送请求->从订单服务获取订单号->显示订单页->用户提交订单->调用订单服务接口创建订单->订单创建成功失败
之所以先获取订单号再创建订单,是为了保证幂等性,防止重复创建订单。而订单ABA问题,可以使用乐观锁的方式,即通过比价版本来解决(订单号要保持唯一、安全、趋势递增,这里采用了美团的leaf 雪花方案)

另外用户预下单 过程,里面的逻辑包括检验商品价格、订单落库(待支付)、调库存预扣(冻结库存)。(为了防止超卖现象,库存扣减采用了redis+lua脚本实现库存查找和库存扣减的原子化操作,这里可以与数据库方案、分布式锁方案相比较)

订单创建成功后->调用第三方支付接口->返回支付二维码->用户完成支付-》支付成功或失败

如果支付成功,则更新订单状态为已支付并真实扣减库存,否则关闭订单并释放库存。(支付成功为了防止超卖,扣减库存也应该使用redis+lua脚本)
另外这里存在一个订单延迟支付的问题,这里采用的是RocketMq的事务型消息通过反向通知,完成订单超时自动回滚的操作(相比于定时任务,定时任务对订单状态的判断会不及时,)

另外检查订单状态时,如果未支付则释放库存、取消本地订单、然后通知支付宝取消订单。如果已支付,需要判断订单是否正常结束。这是因为在用户完成扫码支付后,支付宝正常会向商城发送支付成功的通知。但这个通知没有事务保证,所以很有可能失败,这时就要对订单超时判断时对状态进行对齐。
一种方案是设置定时任务,5分钟后对订单进行超时判断。超时判断时,先去支付宝查询订单状态。但这种方案对订单超时判断不及时,应该至少半分钟或一分钟查一下,实现比较复杂。
所以使用了第二种RocketMq。
另外还要加一个兜底补偿机制,部署了一个定时任务,批量回退超时的订单。在这个任务中,会以十分钟为间隔,对超过超时时间未支付的订单进行统一的撤回操作。这其实就是一种事务消息的兜底补偿机制,以处理那些事务消息机制有可能漏处理的超时订单。在设计金融相关业务时,这种兜底策略会显得尤为重要。
另外考虑支付成功和超时处理是两个链路,高并发下有可能发生支付成功了修改时发现订单关闭了,这种情况通常是退款,确保用户利益不受损,当然如果库存充足,也可以为用户重新生成订单,扣减库存。
另外还可能发生用户关闭订单时发现订单已变为已支付状态这种情况下就取消回滚操作即可。

在这个流程中,我们可以使用Redis布隆器,防止大量请求不存在的商品或订单引发缓存穿透问题。
本项目中用户下单冻结库存使用分布式事务seata的AT模式,支付成功后异步扣减库存使用RocketMQ实现。
秒杀场景下为了提高并发性能,库存扣减现在redis上进行,之后再在数据库中扣减。

http://www.dtcms.com/a/393623.html

相关文章:

  • 22.元类、静态鸭子类型、抽象基类
  • 【论文速递】2025年第21周(May-18-24)(Robotics/Embodied AI/LLM)
  • Android 自定义电池组件(BatteryView)
  • 基于 Stripe/Metering 的用量计费:从 SLO 指标到账单流水
  • 如何解决 pip install 安装报错 ModuleNotFoundError: No module named ‘fastapi’ 问题
  • 论文阅读——隧道中毫米波MIMO信道特性的实验研究
  • The Library: 1靶场渗透
  • 23种设计模式之【装饰器模式】-核心原理与 Java实践
  • 动态规划中的背包问题:0/1 背包与完全背包的核心解析
  • PHP应用-组件框架前端模版渲染三方插件富文本编辑器CVE审计(2024小迪安全DAY30笔记)
  • uniapp 如何判断发的请求是网络异常uni.request
  • 学习:uniapp全栈微信小程序vue3后台 (25)
  • 23种设计模式之【原型模式】-核心原理与 Java实践
  • Netty 重放解码器ReplayingDecoder揭秘:重写轻量异常机制 和 ConstantPool
  • getgeo 生物信息 R语言 表型信息表”“样本信息表”或“临床信息表 phenodata phenotype data
  • OceanBase备租户创建(二):通过BACKUP DATABASE PLUS ARCHIVELOG
  • Linux文件打包压缩与软件安装管理完全指南
  • KingbaseES数据备份操作详解(图文教程)
  • 中断屏蔽实现方法-ARM内核
  • Kotlin 协程之 SharedFlow 与 StateFlow 深度解析
  • python爬虫(请求+解析+案例)
  • 111-Christopher-Dall_Arm-Timers-and-Fire:Arm架构计时器与半虚拟化时间
  • switch缺少break出现bug
  • 【自然语言处理】(3) --RNN循环神经网络
  • C# 中的 ReferenceEquals 方法
  • BERT:用于语言理解的深度双向Transformer预训练【简单分析】
  • 力扣hot100:两数相加(模拟竖式加法详解)(2)
  • Zotero + Word 插件管理参考文献的引用
  • 用Python一键整理文件:自动分类DOCX与PDF,告别文件夹杂乱
  • Ubuntu部署Elasticsearch教程