面试电商与订单相关的问题

一、避免重复下单支付

解决方案:

(1)通过订单id来校验其幂等性

(2)支付接口在Nginx层面不要设置失败或超时重试

二、产品快照

商品信息是可以修改的,当用户下单后,为了更好解决后面可能存在的买卖纠纷,创建订单时会同步保存一份商品详情信息,称之为订单产品快照。

同一件商品,会有很多用户会购买,如果热销商品,短时间就会有上万的订单。如果每个订单都创建一份快照,存储成本太高。另外商品信息虽然支持修改,但毕竟是一个低频动作。我们可以理解成,大部分订单的产品快照信息都是一样的,除非下单时用户修改过。

我们的解决方案时,用新下单的订单id直接关联最新一份的产品快照id即可。

由于订单产品快照属于非核心操作,即使失败也不应该影响用户正常购买流程,所以通常采用异步流程

执行。


三、购物车混合存储

购物车是电商系统的标配功能,暂存用户想要购买的商品。分为添加商品、列表查看、结算下单三个动作。

技术设计并不是特别复杂,存储的信息也相对有限(用户id、商品id、sku_id、数量、添加时间)。这里特别拿出来单讲主要是用户体验层面要注意几个问题:

添加购物车时,后端校验用户未登录,常规思路,引导用户跳转登录页,待登录成功后,再添加购物车。多了一步操作,给用户一种强迫的感觉,体验会比较差。

如果细心体验京东、淘宝等大平台,你会发现即使未登录态也可以添加购物车,这到底是怎么实现的?

细细琢磨其实原理并不复杂,服务端这边在用户登录态校验时,做了分支路由

,当用户未登录时,会创建一个临时Token,作为用户的唯一标识,购物车数据挂载在该Token下,为了避免购物车数据相互影响以及设计的复杂度,这里会有一个临时购物车表。

四、库存超卖

常见的库存扣减方式有:

  • 下单减库存:即当买家下单后,在商品的总库存中减去买家购买数量。下单减库存是最简单的减库存方式,也是控制最精确的一种,下单时直接通过数据库的事务机制控制商品库存,这样一定不会出现超卖的情况。但是你要知道,有些人下完单可能并不会付款。
  • 付款减库存
  • :即买家下单后,并不立即减库存,而是等到有用户付款后才真正减库存,否则库存一直保留给其他买家。但因为付款时才减库存,如果并发比较高,有可能出现买家下单后付不了款的情况,因为可能商品已经被其他人买走了。
  • 预扣库存:这种方式相对复杂一些,买家下单后,库存为其保留一定的时间(如 30 分钟),超过这个时间,库存将会自动释放,释放后其他买家就可以继续购买。在买家付款前,系统会校验该订单的库存是否还有保留:如果没有保留,则再次尝试预扣;如果库存不足(也就是预扣失败)则不允许继续付款;如果预扣成功,则完成付款并实际地减去库存。

至于采用哪一种减库存方式更多是业务层面的考虑,减库存最核心的是大并发请求时保证数据库中的库存字段值不能为负数。

方案一:

通常在扣减库存的场景下使用行级锁

,通过数据库引擎本身对记录加锁的控制,保证数据库的更新的安全性,并且通过where语句的条件,保证库存不会被减到 0 以下,也就是能够有效的控制超卖的场景。

方案二:

设置数据库的字段数据为无符号整数,这样减后库存字段值小于零时 SQL 语句会报错。


五、账户余额更新,保证事务

用户支付,我们要从买家账户减掉一定金额,再往卖家增加一定金额,为了保证数据的完整性、可追溯性,变更余额时,我们通常会同时插入一条记录流水。

账户流水核心字段:流水ID、金额、交易双方账户、交易时间戳、订单号、

注意:账户流水只能新增,不能修改和删除。流水号必须是自增的。

后续,系统对账时,我们只需要对交易流水明细数据做累计即可,如果出现和余额不一致情况,一般以交易流水为准来修复余额数据。

更新余额、记录流水 虽属于两个操作,但是要保证要么都成功,要么都失败。要做到事务。

数据库的事务隔离级别

有:读未提交(RU)、读已提交(RC)、可重复读(RR)、串行化(Serializable)

常用的隔离级别是 RC 和 RR ,因为这两种隔离级别都可以避免脏读。

当然,如果涉及多个微服务调用,一般情况下,我们借助重试机制,保证最终一致是常用的方案。


六、读写分离导致数据不一致

互联网业务大部分都是 读多写少,为了提升数据库集群

的吞吐性能,我们通常会采用 主从架构、读写分离




部署一个主库实例,客户端请求所有写操作全部写到主库,然后借助 MySQL 自带的 主从同步

功能,做一些简单配置,可以近乎实时的将主库的数据同步给 多个从库实例,主从延迟非常小,一般不超过 1 毫秒

客户端请求的所有读操作全部打到 从库,借助多实例集群提升读请求的整体处理能力。

但是,主从同步虽然近乎实时,但还是有个 时间差 ,主库数据刚更新完,但数据还没来得及同步到从库,后续读请求直接访问

了从库,看到的还是旧数据,会造成一致性的问题。

因此,对实时性要求很高的by id

查询,我们还是需要让它走主库,而不是一股脑地把所有读请求都往从库打。

七、历史订单归档

根据二八定律,系统绝大部分的性能开销花在20%的业务。数据也不例外,从数据的使用频率来看,经常被业务访问的数据称为热点数据;反之,称之为冷数据。

在了解的数据的冷、热特性后,便可以指导我们做一些有针对性的性能优化

。这里面有业务层面的优化,也有技术层面的优化。比如:电商网站,一般只能查询3个月内的订单,如果你想看看3个月前的订单,需要访问历史订单页面。

实现思路:

1、冷热数据区分的标准是什么?要结合业务思考,可能要找产品同学一块讨论才能做决策,切记不要拍脑袋。以电商订单为例:

  • 方案一:以“下单时间”为标准,将3 个月前的订单数据当作冷数据,3 个月内的当作热数据。
  • 方案二:根据“订单状态”字段来区分,已完结的订单当作冷数据,未完结的订单当作热数据。
  • 方案三:组合方式,把下单时间 > 3 个月且状态为“已完结”的订单标识为冷数据,其他的当作热数据。

2、如何触发冷热数据的分离

展开阅读全文

本文系作者在时代Java发表,未经许可,不得转载。

如有侵权,请联系nowjava@qq.com删除。

编辑于

关注时代Java

关注时代Java