电商-扣减库存的思想碰撞 [redis的eval函数与胶水语言lua的结合]

背景


我们大家都知道,在一个电商系统里面,库存是一个很敏感的系统组成部分。这是因为在这一点上,我们的程序执行模型必须在一个点上做出改变,那便是从并行执行模型到串行执行模型的切换 因我们的某一个商品的库存资源可以认为是有且只有一个的,不管前面的执行过程有多少个线程,多少个用户,在实际扣减或回滚的时候,我们期望它是原子的,内存可见的,有序的执行顺序。

实现案例结构图


在这里插入图片描述

可能的复杂逻辑


由上图我们不难看出,在扣减库存操作的时候,我们在很大一部分操作需要将并行执行转化为串行执行模型,当有一个sku的时候我们还好做一些(类比,mysql的version乐观锁,redis的setnx,incre,decre),可以使得单个sku的扣减库存得到原子性操作。

但是,如果我们要执行的是购物车加购过来的一批商品的时候,我们就不得不使得我们的原子粒度扩大,这就带来更大的挑战,当多个sku执行扣减操作的时候,我们必须要保证这些扣减操作具有事务性,我们希望成功率高一些,这就使得我们锁住的资源不仅仅是某一条,而是很多条。这是准确性和执行速度双重指标对我们的考验




执行速度和准确性


  • 简述

用户在客户端下单,或取消订单,我们首先要保障的是执行结果是正确的,其次要保证执行速度要快,然而,满足这两点并不容易,执行速度的快和为了准确性的串行转化本身就是矛盾的。

串行执行加上原子操作,还有具有事务性,乍一看,上图的执行模型中,貌似这些技术中间件都是满足的,于是乎我们可以把考虑的重点放到执行速度上。


  • 执行速度的考虑【网络IO次数的优化】

在速度上的考虑,博主认为,网络IO + 执行IO = 最终耗时。 网络io在一般情况下我们的系统都会选择内网部署,所以速度区别应该不大,但是,我们要考虑交互次数要尽可能的少。针对多个sku,多次扣减操作,在这点上无非就是一个大事务,大原子操作。不管我们的sku存储结构是散列的或是Tree结构保存,当我们操作的时候我们必不可免的就是要挨个操作。从服务端锁住code,进行循环扣减,挨个交互。这太痛苦了。 我们期望尽可能少的交互次数,有没有一种方案,可以与存储中间件进行一次交互,批量将这些sku的库存信息准确的完成扣减?答案是有的,那就是redis的scriptLoad函数,我们下文再讲

  • 执行IO的考虑【存储介质的考虑】

在执行模型上,读过操作系统的相关同学应该清楚,一般当我们的程序执行期间,进行了多少次IO操作,有没有引起中断?内核态和用户态之间有多少数据交互? 上下文之间有多少次切换?这都将影响我们的执行效率,当然很多我们都不可控,这是由执行中间件决定的,比如nginx利用linux的epoll事件模型,用链表过滤的机制,较少了用户态和内核态之间的数据交互。回到正文, 博主认为,我们能够决定最多的即为存储介质的选择。

在这里插入图片描述

OK,越靠近CPU执行的存储介质速度越快,在这里,我们可以将有可能触发硬盘IO的操作全部摒弃,我们期望选择的是高速缓存。但是这并不是由我们决定的,我们没有权限决定CPU内部那点仅有的寄存器资源来单独为我们保存什么,所以我们退一步讲,选择RAM是最优的策略。答案是redis!

这需要你维护一套尽可能简单的存储结构,便于清理key,便于操作,或hash,或list,或key-value,选择性很高。



eval函数和scriptload函数


由上文,我们推出,可能redis的scirptload搭配lua脚本来实现这个功能是最合理的方案。(当然,可能有其他更加合理的方案,欢迎思想碰撞和教导)。下文简单介绍一下该方案的工作原理

在这里插入图片描述

  • 内置的lua解释器

lua语言作为一个胶水语言,小巧而灵活,何为胶水语言呢?形象一点来讲,就是把这个语言粘贴到其他的系统上,类似于lua module for nginx、redis的内置lua解释器。那么这么做有什么好处呢?好处就在于,内置的lua解释器和上传机制将保证你的操作可定制,并且快速的执行在所谓的服务端。 这是一次网络IO就可以完成的自定义操作。不得不惊叹设计者的思想
  • 一次上传,多次执行

在redis中,我们经过scriptload上传到redis服务器的脚本将会缓存到服务器,我们可以通过evalsha传递一个简短的字符串,即可在服务器端进行索引查找。不用每次都上传该文件。
  • 原子性和事务性

redis的eval函数最重要的特性既是它保证该脚本执行的原子性和事务性,它是实时内存可见的。在执行该脚本的同时,不会让其他的redis命令开始执行。

由于该脚本执行在redis的服务端,那么,我们可以理解为它是纯内存操作。我们经常说的一句话,内存操作耗时可以忽略不计。这就是小巧的lua脚本带来的最高的回报,这下对于我们的扣减库存操作,它也就只是内存级别的数据结构操作罢了。


  • 高效的内置对象

  • base
  • table
  • string
  • math
  • debug
  • cjson
  • cmsgpack

更多的详细信息可以查阅: http://redisdoc.com/script/eval.html


方案总结


redis的eval函数,本质是将我们的扣减操作转变成了一次内网的io操作,一次redis服务器的内存操作。并保证了原子性和事务性。这使得我们的操作可以准确快速的执行。 没有最屌,没有最高大上的方案或中间件,一切只不过是思想的碰撞罢了,在适当的场景运用适当的技术手段解决相应的技术需求。如有不当,欢迎大家评论批评。
  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值