一个简略的下单流程包括,产品校验,订单计价,扣库存,保存订单。其中扣库存的并发问题是整个流程中最费事,最杂乱的环节,能够说聚集了所有的才智和头发。

高并发下单加锁吗?

处理扣库存并发问题,很简略让人想到加锁,加锁的意图是为了约束同步代码块并发,进一步的确保原子性,可见性和重排序,完成数据一致性。

单机加 jvm 锁,分布式加分布式锁。这让我不由想起分布式体系一句黑话,分布式体系中,没有什么问题是不能通过添加中间环节处理的,但处理一个问题常常会带来别的的问题,是的,你没听错,以空间换时刻,以并发换数据一致性,在这儿,锁粒度和范围对并发影响是最直接的,规划的时分尽或许的缩小锁粒度和范围,一般粒度是 skuId,范围尽量减小。

锁时长,锁过期是别的两个不得不考虑的问题。最费事的锁过期,常用处理计划是依托 redission 的看门狗机制,相当于守时使命给锁续命,但粗犷续命会添加 rt,一同添加其他恳求的堵塞时长。

尽量防止献身并发的计划!
尽量防止献身并发的计划!
尽量防止献身并发的计划!

一次偶然的机遇,我的搭档向我引荐了 Google 的 chubby。为什么咱们不能用失望锁+达观锁的组合呢?在锁过期的时分,达观锁兜底,不影响恳求 rt,也能确保数据一致性。这是个不错的计划,合适简略的场景!

一次偶然的机遇,一条公式冲击我的大脑,redis = 高功能 + 原子性。机敏的你必定知道加锁便是为了确保原子性,根据 redis 完成分布式锁也是由于 redis 的原子性和高功能(想想什么状况用 mysql 和 zk),假如我用 redis 代替锁,是不是既能确保扣库存的原子性,一同由于没有锁,又不需求考虑加锁带来的问题。

说干就干,马上画个图。(图片被紧缩,有点糊,我上传到图床,点击能跳转到图床看清晰的,假如点击的图片不清晰,联络我,给我留言

高并发下单加锁吗?
我把订单流程分为5大块,有点杂乱,且听我细细道来。

Order process:

扣库存是约束订单并发的瓶颈,依托 redis 的原子性确保数据一致性,高功能进步并发

2pc

根据二阶段提交思维,第一步首要刺进 INIT 状况的订单

冷热路由

第二步有个路由,冷门产品走 mysql 下单,热门产品并发大,依托 redis 撑。

怎么知道产品冷热,答案是 bitMap,所以咱们还需求一个守时使命(job4)保护 bitMap。冷热数据的核算来源一般是购物车,埋点核算。大电商平台来源更丰富,大数据追踪,算法引荐等。

故障处理

lua 扣减库存时,需求考虑恳求超时和 redis 宕机。恳求超时比较好处理,能够 catch 超时反常,依据业务选择重试或回来。redis 宕机比较棘手,后边剖析。

降级

这儿说一下降级。redis 宕机之后,走冷门订单流程。但是这儿的规划会变的很杂乱,由于需求处理两个问题,怎么判定 redis 宕机,走冷门路由会不会把 mysql 压垮?这两个问题持续谈论下去会衍生出更多,比如走冷门路由的机遇,冷门路由会不会把 mysql 压垮等,所以这儿触发熔断还需求马上敞开限流,展开真的很杂乱,下次有机遇说。

扣库存后续动作忽然变得顺畅,刺进订单库存流水,修正订单状况 UNPAY,发送核销优惠券 mq,日志记载等。这几个步骤中,

  • 流水用于记载订单和库存的绑定,重建产品库存缓存会用到
  • 核销优惠券选择异步,发核销优惠券的 mq,需求考虑音讯丢失和重复消费,上游订单服务记载本地音讯表,一同有个守时使命(job1)扫描重发,下流做好幂等
  • 咱们还需求重视该流程或许会呈现 jvm 宕机,这是很严重的事端,按理说没有顺畅走完订单流程的订单归于反常订单,反常订单的库存需求返还 redis,所以还需求一个守时使命处理反常订单。

JOB2

redis 没有库存流水,被扣库存 x 无法得知

订单流程有几处宕机需求考虑,一处是履行 lua 脚本时 redis 宕机,另一处是扣完库存之后,jvm 宕机。无论是 redis 仍是 jvm 宕机,这些订单都会回来反常信息到前端,所以这些订单的是无效的,需求还库存到 redis。

mysql 和 redis 的流水描绘同一件事情,即记载该笔订单所扣库存。在反常状况下,或许只有 redis 有流水,依然能够作为判定库存现已扣减的依据,在极端反常的状况,lua 脚本刚扣完库存,redis 进程死了或者宕机,尽管 lua 是原子性的,但宕机可不是原子性,库存 x 现已扣了,没有流水记载,无法知道 x (redis 的单点问题能够通过 redis ha 确保)。

假如 redis 康复了,但数据没了,怎么办?
假如 redis 康复了,但数据丢失了(库存改变还没耐久化就宕机,redis 重启康复的是旧数据),怎么办?

Rebuild stock cache of sku

剩余库存 = (库存服务的总库存减去预占库存) – (mysql 和 redis 流水去重,核算的库存)

把目光锁定到右下角,重建 sku 库存缓存的逻辑。一般地,在 redis 扣完库存,会发个 mq 音讯到库存服务,耐久化该库存改变。库存服务采用 a/b 库存的规划,别离记载产品总库存和预占库存,为的是处理高并发场景业务操作库存和用户下单操作库存时的锁抵触问题。库存服务里的库存是推迟的,订单服务没发的音讯和库存服务没消费的音讯形成推迟。

咱们既然把库存缓存到 redis,不妨想一下怎么精确核算库存的数量。

  • 在刚开始启动服务的时分,redis 没有数据,这时分库存 t = a – b(a/b库存)
  • 服务运行一段时刻,redis 有库存 t, 此刻 t = a – b – (库存服务还没消费的扣库存音讯),所以拿 mysql 和 redis 的流水去重,核算出已扣未消费库存。redis 宕机后,会有一个未知已扣库存 x, x 几乎没有算出来的或许(鄙人极力了),也没必要算出来,你想,当你 redis 反常了,库存 x 对应的订单是反常订单,反常订单不会回来给用户,用户只会收到下单反常的回来,所以库存 x 是无效的,丢掉就好。

Payment process

用户付出之后,才发扣库存音讯到库存服务落地。落地库存服务的流程很简略,不再论述。重点说说新增库存和削减库存。新增库存不会形成超卖,简略粗犷的加就好。削减库存相当于下单,需求小心超卖问题,所以现在 redis 扣了库存,再履行本地业务,简简略单,凄凄惨惨戚戚,乍暖还寒时分,最难将息,三杯两盏淡酒,咋敌…

多说两句

纵观整幅图,比照简略下单流程,能够发现,为了处理高并发下单,引进一个中间环节,而引进中间环节的副作用需求咱们处理。尽管订单流程变杂乱了,但并发进步了。一般来说,redis qps 10万,实际上没有10万,假如你的业务 qps 超越单机 redis 约束,记住,分布式的核心思维便是拆,把库存均匀打散到多台 redis。

打散之后需求处理库存歪斜问题,或许实例 a 现已卖完了,实例 b 还有部分库存,但部分用户恳求打到实例 a,就会形成明明有货,但下单失败。这个问题也很棘手,感兴趣的同学能够自行研讨,学会教教我。

上述流程通过简化,真实状况会更杂乱,不一定合适实际场景。假如有错误的地方,烦请留言评论,多多沟通,互相学习,一同前进。

还有个问题需求提,流程中的业务问题。能够发现,订单流程是没有业务操控的。一方面咱们认为,数据很宝贵,不分正常反常。反常的订单数据可作为剖析体系架构缺陷的依据,另一方面接口尽量防止长业务,特别是高并发下,业务越短越好。

回答几个问题

为什么感觉拿掉分布式锁之后,流程变得很杂乱?

其实我大可给订单流程前后包裹一个分布式锁,新的规划就变成下图,能够看到,核心库存扣减逻辑并没有改变,所以分布式锁的存在并不是让流程变杂乱的原因。

高并发下单加锁吗?

为什么流程忽然变的很杂乱?

  • 为确保数据一致性,加了几个守时使命和一个重建缓存接口;为进步功能,加了冷热路由;为削减杂乱度,把库存扣减音讯推迟到付出,整体比简略下单流程多了几道工序
  • 由于引进异构数据库,数据源由一变多,就需求保护数据源数据一致性。能够说,这些流程纯纯是为了确保多个数据源的数据一致性。假如以前咱们在 mysql 做库存扣减,根据 mysql 业务就能确保数据一致性。但是 mysql 的 qps 并不高,他合适并发不高的状况,所以我才会让冷门产品走 mysql 下单流程,由于冷门产品几乎没有并发
  • 所以流程变得杂乱的原因是保护数据一致性