解决方案:
(1)通过订单id来校验其幂等性
(2)支付接口在Nginx层面不要设置失败或超时重试
商品信息是可以修改的,当用户下单后,为了更好解决后面可能存在的买卖纠纷,创建订单时会同步保存一份商品详情信息,称之为订单产品快照。
同一件商品,会有很多用户会购买,如果热销商品,短时间就会有上万的订单。如果每个订单都创建一份快照,存储成本太高。另外商品信息虽然支持修改,但毕竟是一个低频动作。我们可以理解成,大部分订单的产品快照信息都是一样的,除非下单时用户修改过。
我们的解决方案时,用新下单的订单id直接关联最新一份的产品快照id即可。
由于订单产品快照属于非核心操作,即使失败也不应该影响用户正常购买流程,所以通常采用异步流程
执行。
购物车是电商系统的标配功能,暂存用户想要购买的商品。分为添加商品、列表查看、结算下单三个动作。
技术设计并不是特别复杂,存储的信息也相对有限(用户id、商品id、sku_id、数量、添加时间)。这里特别拿出来单讲主要是用户体验层面要注意几个问题:
添加购物车时,后端校验用户未登录,常规思路,引导用户跳转登录页,待登录成功后,再添加购物车。多了一步操作,给用户一种强迫的感觉,体验会比较差。
如果细心体验京东、淘宝等大平台,你会发现即使未登录态也可以添加购物车,这到底是怎么实现的?
细细琢磨其实原理并不复杂,服务端这边在用户登录态校验时,做了分支路由
,当用户未登录时,会创建一个临时Token,作为用户的唯一标识,购物车数据挂载在该Token下,为了避免购物车数据相互影响以及设计的复杂度,这里会有一个临时购物车表。
常见的库存扣减方式有:
至于采用哪一种减库存方式更多是业务层面的考虑,减库存最核心的是大并发请求时保证数据库中的库存字段值不能为负数。
方案一:
通常在扣减库存的场景下使用行级锁
,通过数据库引擎本身对记录加锁的控制,保证数据库的更新的安全性,并且通过where语句的条件,保证库存不会被减到 0 以下,也就是能够有效的控制超卖的场景。
方案二:
设置数据库的字段数据为无符号整数,这样减后库存字段值小于零时 SQL 语句会报错。
用户支付,我们要从买家账户减掉一定金额,再往卖家增加一定金额,为了保证数据的完整性、可追溯性,变更余额时,我们通常会同时插入一条记录流水。
账户流水核心字段:流水ID、金额、交易双方账户、交易时间戳、订单号、
注意:账户流水只能新增,不能修改和删除。流水号必须是自增的。
后续,系统对账时,我们只需要对交易流水明细数据做累计即可,如果出现和余额不一致情况,一般以交易流水为准来修复余额数据。
更新余额、记录流水 虽属于两个操作,但是要保证要么都成功,要么都失败。要做到事务。
数据库的事务隔离级别
有:读未提交(RU)、读已提交(RC)、可重复读(RR)、串行化(Serializable)
常用的隔离级别是 RC 和 RR ,因为这两种隔离级别都可以避免脏读。
当然,如果涉及多个微服务调用,一般情况下,我们借助重试机制,保证最终一致是常用的方案。
互联网业务大部分都是 读多写少,为了提升数据库集群
的吞吐性能,我们通常会采用 主从架构、读写分离
部署一个主库实例,客户端请求所有写操作全部写到主库,然后借助 MySQL 自带的 主从同步
功能,做一些简单配置,可以近乎实时的将主库的数据同步给 多个从库实例,主从延迟非常小,一般不超过 1 毫秒。
客户端请求的所有读操作全部打到 从库,借助多实例集群提升读请求的整体处理能力。
但是,主从同步虽然近乎实时,但还是有个 时间差 ,主库数据刚更新完,但数据还没来得及同步到从库,后续读请求直接访问
了从库,看到的还是旧数据,会造成一致性的问题。
因此,对实时性要求很高的by id
查询,我们还是需要让它走主库,而不是一股脑地把所有读请求都往从库打。
根据二八定律,系统绝大部分的性能开销花在20%的业务。数据也不例外,从数据的使用频率来看,经常被业务访问的数据称为热点数据;反之,称之为冷数据。
在了解的数据的冷、热特性后,便可以指导我们做一些有针对性的性能优化
。这里面有业务层面的优化,也有技术层面的优化。比如:电商网站,一般只能查询3个月内的订单,如果你想看看3个月前的订单,需要访问历史订单页面。
实现思路:
1、冷热数据区分的标准是什么?要结合业务思考,可能要找产品同学一块讨论才能做决策,切记不要拍脑袋。以电商订单为例:
2、如何触发冷热数据的分离
本文系作者在时代Java发表,未经许可,不得转载。
如有侵权,请联系nowjava@qq.com删除。