首页 > 数据库 >Redis使用lua脚本实现库存扣减

Redis使用lua脚本实现库存扣减

时间:2023-05-16 11:44:06浏览次数:53  
标签:脚本 扣减 Lua KEYS redis Redis lua EVAL

为什么使用Lua脚本为什么能合并多个原子操作?

Redis官方文档:https://redis.io/docs/manual/programmability/eval-intro/

 

Redis 保证脚本的原子执行。在执行脚本时,所有服务器活动在其整个运行期间都被阻止。这些语义意味着脚本的所有效果要么尚未发生,要么已经发生。

脚本提供了几个在许多情况下都很有价值的属性。这些包括:

  • 通过在数据所在的地方执行逻辑来提供局部性。数据局部性减少了整体延迟并节省了网络资源。
  • 确保脚本原子执行的阻塞语义。
  • 启用 Redis 中缺少的或过于小众的简单功能的组合。

Lua 允许您在 Redis 中运行部分应用程序逻辑。这样的脚本可以跨多个键执行条件更新,可能以原子方式组合几种不同的数据类型。

 

命令行应用Lua

Redis Eval 命令使用 Lua 解释器执行脚本。

这里能帮我们实现 Redis 执行 Lua 脚本的命令有两个,一个是 EVAL,另一个是 EVALSHA。

redis Eval 命令基本语法如下:

EVAL script numkeys key [key ...] arg [arg ...] 

其中 EVAL 是命令,script 是我们 Lua 脚本的字符串形式,numkeys 是我们要传入的参数数量,key 是我们的入参,可以传入多个,arg 是额外的入参。

以下尝试演示脚本KEYS和ARGV运行时全局变量之间输入参数的分布:

redis> EVAL "return { KEYS[1], KEYS[2], ARGV[1], ARGV[2], ARGV[3] }" 2 key1 key2 arg1 arg2 arg3
1) "key1"
2) "key2"
3) "arg1"
4) "arg2"
5) "arg3"

 

但这种方式需要每次都传入 Lua 脚本字符串,不仅浪费网络开销,同时 Redis 需要每次重新编译 Lua 脚本,对于我们追求性能极限的系统来说,不是很完美。所以这里就要说到另一个命令 EVALSHA 了,原生语法如下:

 

EVALSHA sha1 numkeys key [key ...] arg [arg ...]

 

不同的是这里传入的不是脚本字符串,而是一个加密串 sha1。这个 sha1 是从哪来的呢?它是通过另一个命令 SCRIPT LOAD 返回的,该命令是预加载脚本用的。

  ​

从脚本与 Redis 交互

可以通过redis.call()或从 Lua 脚本调用 Redis 命令redis.pcall()

两者几乎一模一样。两者都执行 Redis 命令及其提供的参数(如果这些参数表示格式正确的命令)。但是,这两个函数之间的区别在于处理运行时错误(例如语法错误)的方式。调用函数引发的错误redis.call()直接返回给执行它的客户端。相反,调用redis.pcall()函数时遇到的错误将返回到脚本的执行上下文,而不是进行可能的处理。

Java客户端应用Lua实例

注意Lua 脚本并不会自动帮你完成回滚操作,如果你的脚本逻辑中包含两步写操作,需要自己去做回滚。好在我们库存扣减的逻辑针对 Redis 的命令就两种,一个读一个写,并且写命令在最后,这样就不存在需要回滚的问题了。

以库存扣减核心操作为例,完成核心 Lua 脚本的编写。其主要实现的功能就是查询库存并判断库存是否充足,如果充足,则做相应的扣减操作,脚本内容如下:

-- 调用Redis的get指令,查询活动库存,其中KEYS[1]为传入的参数1,即库存key
local c_s = redis.call('get', KEYS[1])
-- 判断活动库存是否充足,其中KEYS[2]为传入的参数2,即当前抢购数量
if not c_s or tonumber(c_s) < tonumber(KEYS[2]) then
   return 0
end
-- 如果活动库存充足,则进行扣减操作。其中KEYS[2]为传入的参数2,即当前抢购数量
redis.call('decrby',KEYS[1], KEYS[2])

 

然后我们将 Lua 脚本转成字符串,并添加脚本预加载机制。

预加载可以有多种实现方式,一个是外部预加载好,生成了 sha1 然后配置到配置中心,这样 Java 代码从配置中心拉取最新 sha1 即可。

另一种方式是在服务启动时,来完成脚本的预加载,并生成单机全局变量 sha1。我们这里先采取第二种方式,代码结构如下图所示:

 

 

以上是将 Lua 脚本转成字符串形式,并通过 @PostConstruct 完成脚本的预加载。然后新增 EVALSHA 方法,如下图所示:

 

方法入参为活动商品库存 key 以及单次抢购数量,并在内部调用 Lua 脚本执行库存扣减操作。看起来是不是很简单?在写完底层核心方法之后,我们只需要在下单之前,调用该方法即可,具体如下图所示:

  

脚本缓存

到目前为止,我们已经使用EVAL命令来运行我们的脚本。

每当我们调用时EVAL,我们还会在请求中包含脚本的源代码。重复调用EVAL执行同一套参数化脚本,既浪费网络带宽,也对 Redis 有一定的开销。当然,节省网络和计算资源是关键,因此,Redis 为脚本提供了一种缓存机制。

您执行的每个脚本都EVAL存储在服务器保留的专用缓存中。缓存的内容由脚本的 SHA1 摘要总和组织,因此脚本的 SHA1 摘要总和在缓存中唯一标识它。您可以通过运行EVAL并随后调用来验证此行为INFO。

 

SCRIPT FLUSH

从脚本缓存中移除所有脚本。

SCRIPT LOAD script

将脚本 script 添加到脚本缓存中,但并不立即执行这个脚本。

标签:脚本,扣减,Lua,KEYS,redis,Redis,lua,EVAL
From: https://www.cnblogs.com/binyue/p/17401247.html

相关文章

  • 【Azure Redis 缓存】使用开源工具redis-copy时遇见6379端口无法连接到Redis服务器的
    问题描述当使用AzureRedis服务时,需要把一个Redis服务的数据导入到另一个Redis上,因为Redis服务没有使用高级版,所以不支持直接导入/导出RDB文件。以编程方式来读取数据并写入到新的Redis服务端,使用开源工具Redis-Copy却遇见了6379端口无法连接的问题。而用redis-cli.exe却......
  • Lua 数组
    Lua数组数组,就是相同数据类型的元素按一定顺序排列的集合,可以是一维数组和多维数组。Lua数组的索引键值可以使用整数表示,数组的大小不是固定的。和table的区别边界比较模糊一维数组一维数组是最简单的数组,其逻辑结构是线性表。一维数组可以用for循环出数组中的元素,如下实......
  • Lua 字符串
    Lua字符串字符串或串(String)是由数字、字母、下划线组成的一串字符。Lua语言中字符串可以使用以下三种方式来表示:单引号间的一串字符。双引号间的一串字符。[[与]]间的一串字符。以上三种方式的字符串实例如下:实例str1="Lua"print("双引号字符串:",str1)str2......
  • Redis数据结构二之SDS和双向链表
    本文首发于公众号:Hunter后端原文链接:Redis数据结构二之SDS和双向链表这一篇笔记介绍一下SDS(simpledynamicstring)和双向链表。以下是本篇笔记目录:SDS常数复杂度获取字符串长度杜绝缓冲区溢出减少修改字符串带来的内存重分配次数二进制安全兼容C字符串函数双向链......
  • Redis 持久化方式
    参考:小林coding https://xiaolincoding.com/redis/storage/aof.html#aof-%E9%87%8D%E5%86%99%E6%9C%BA%E5%88%B6https://www.cnblogs.com/lovezhr/p/15886823.html AOF(AppendOnlyFile)如果Redis 每执行一条写操作(不会记录读操作命令)命令,就把该命令 以追加的方式写入到......
  • Redis数据结构一之对象的介绍及各版本对应实现
    本文首发于公众号:Hunter后端原文链接:Redis数据结构一之对象的介绍及各版本对应实现本篇笔记开始介绍Redis数据结构的底层实现。当我们被问到Redis中有什么数据结构,或者说数据类型,我们可能会说有字符串、列表、哈希、集合、有序集合。其实这几种数据类型在Redis中都由......
  • Lua 函数
    Lua函数和pythongo函数类似,和java不同,他有多个返回值。函数的本质是复用和功能聚合在Lua中,函数是对语句和表达式进行抽象的主要方法。既可以用来处理一些特殊的工作,也可以用来计算一些值。Lua提供了许多的内建函数,你可以很方便的在程序中调用它们,如print()函数可以将传入......
  • Lua 数据类型
    数据类型是各种语言的基础,本质上无论是Java,python,go,shell,powershell,redis,c#等各种L4的语言,本质上还是C系列。类型和语法基本都是沿用了C的风格进行了重新组装和定义。java人员一定要注意string和nil的特殊性,确实有点别扭。Lua数据类型Lua是动态类型语言,变量不要类型定义,只......
  • Lua 环境安装
    Lua环境安装重点掌握IDEAlua插件安装Window系统上安装Luawindow下你可以使用一个叫"SciTE"的IDE环境来执行lua程序,下载地址为:Github下载地址:https://github.com/rjpcomputing/luaforwindows/releasesGoogleCode下载地址:https://code.google.com/p/lua......
  • redis 事务
    1.事务概述:1.1什么是事务:指可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序的串行化执行而不会被其他命令插入,不许加塞。即:一个队列中,一次性、顺序性、排他性的执行一系列命令。1.2与传统关系型数据库的事务相比redis事务的不同之处:我......