背景
我们大家都知道,在一个电商系统里面,库存是一个很敏感的系统组成部分。这是因为在这一点上,我们的程序执行模型必须在一个点上做出改变,那便是从并行执行模型到串行执行模型的切换。 因我们的某一个商品的库存资源可以认为是有且只有一个的,不管前面的执行过程有多少个线程,多少个用户,在实际扣减或回滚的时候,我们期望它是原子的,内存可见的,有序的执行顺序。
实现案例结构图
可能的复杂逻辑
由上图我们不难看出,在扣减库存操作的时候,我们在很大一部分操作需要将并行执行转化为串行执行模型,当有一个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事件模型,用链表过滤的机制,较少了用户态和内核态之间的数据交互。回到正文, 博主认为,我们能够决定最多的即为存储介质的选择。
这需要你维护一套尽可能简单的存储结构,便于清理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服务器的内存操作。并保证了原子性和事务性。这使得我们的操作可以准确快速的执行。 没有最屌,没有最高大上的方案或中间件,一切只不过是思想的碰撞罢了,在适当的场景运用适当的技术手段解决相应的技术需求。如有不当,欢迎大家评论批评。