Redis系列(九)、Redis的“事务”及Lua脚本操作

本文深入探讨Redis中的事务机制,包括其乐观锁特性、与数据库事务的区别,以及如何使用Lua脚本来优化高并发场景下的事务处理。通过具体实例,如记录IP登录次数和限制访问频率,展示Lua脚本在Redis中的应用。

目录

"事务"

介绍 

使用事务,成功提交

使用事务,成功回滚

使用事务,语法错误,成功触发回滚

使用事务,执行错误,不会触发回滚  

LUA脚本

介绍

使用lua脚本的好处

应用

例1:记录IP登录次数

例2:当10秒内请求3次后拒绝访问

lua脚本缓存


大家都知道在RDBMS中有事务操作,同样在Redis中也是支持"事务"的,只是redis支持的是弱事务性,跟我们平时理解上有些不太一样,下面来看看有哪些不一样。同时我们也可以通过lua脚本实现redis的事务操作。

Redis系列文章:

Redis系列(一)、CentOS7下安装Redis6.0.3稳定版

Redis系列(二)、数据类型之字符串String 

Redis系列(三)、数据类型之哈希Hash

Redis系列(四)、数据类型之列表List

Redis系列(五)、数据类型之无序集合Set

Redis系列(六)、数据类型之有序集合ZSet(sorted_set)

Redis系列(七)、常用key命令

Redis系列(八)、常用服务器命令 


"事务"

介绍 

redis提供简单的事务命令,由multi和exec组成,实际上相当于将多个命令添加到一个执行的集合内,multi为begin,exec为commit,discard相当于rollback,watch相当于锁,对象若在事务执行前被修改则事务被打断。

因此redis的事务机制为乐观锁,如果在高并发场景下,如果多个客户端同时对一个key进行了watch,只要有一个客户端提交成功,其他客户端的操作都是无效的,因此redis事务不适合在高并发场景下使用,使用lua脚本可以更好的解决事务解决不了的场景。

Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证

  • 批量操作在发送 EXEC 命令前被放入队列缓存。
  • 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
  • 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。

一个事务从开始到执行会经历以下三个阶段

  • 开始事务。
  • 命令入队。
  • 执行事务。

如果事务中出现错误:

  • 还未exec就报错:如果事务中出现语法错误,则事务会成功回滚,整个事务中的命令都不会提交;
  • 成功执行exec后才报错:如果事务中出现的不是语法错误,而是执行错误,不会触发回滚,该事务中仅有该错误命令不会提交,其他命令依旧会继续提交,因此这里的"事务"打了个引号,和我们通常理解的数据库事务完全不一样。
#begin 开启一个redis事务
multi

#commit 提交事务里的命令队列
exec

#rollback 回滚
discard

#锁,监视一个或多个key,被监视的key若在提交事务前被修改,则事务被打断
watch key [key ...

#取消监视
unwatch

使用事务,成功提交

使用事务,成功回滚

使用事务,语法错误,成功触发回滚

 

使用事务,执行错误,不会触发回滚  

LUA脚本

介绍

Redis2.6之后新增的功能,我们可以在redis中通过lua脚本操作redis。与事务不同的是事务是将多个命令添加到一个执行的集合,执行的时候仍然是多个命令,会受到其他客户端的影响,而脚本会将多个命令和操作当成一个命令在redis中执行,也就是说该脚本在执行的过程中,不会被任何其他脚本或命令打断干扰。正是因此这种原子性,lua脚本才可以代替multi和exec的事务功能。同时也是因此,在lua脚本中不宜进行过大的开销操作,避免影响后续的其他请求的正常执行。

使用lua脚本的好处

  1. lua脚本是作为一个整体执行的.所以中间不会被其他命令插入
  2. 可以把多条命令一次性打包,所以可以有效减少网络开销;
  3. lua脚本可以常驻在redis内存中,所以在使用的时候,可以直接拿来复用.也减少了代码量

应用

redis脚本使用eval命令执行lua脚本,其中numkeys表示lua script里有多少个key参数,redis脚本根据该数字从后面的key和arg中取前n个作为key参数,之后的都作为arg参数:

eval script numkeys key [key ...] arg [arg ...]

例1:记录IP登录次数

#利用hash记录所有登录的IP次数
#key参数的数量必须和numkey一致,使用key或者argv可以实现一样的效果。如下面第一个命令里用了三个key,代表后面的三个参数分别对应脚本里的key1 key2 key3.第二个命令里用了一个key,代表了后面第一个参数对应脚本里的key1,后面第二和第三个参数对应脚本里的argv1和argv2
eval "return redis.call('hincrby', KEYS[1], KEYS[2], KEYS[3])" 3 h_host host_192.168.145.1 1

eval "return redis.call('hincrby', KEYS[1], ARGV[1], ARGV[2])" 1 h_host host_192.168.145.1 1

例2:当10秒内请求3次后拒绝访问

#1.给访问ip的key递增,2.判断该访问次数若为首次登录则设置过期时间10秒,3.若不是首次登录则判断是否大于3次,若大于则返回0,否则返回1。
eval "local request_times = redis.call('incr',KEYS[1]);if request_times == 1 then redis.call('expire',KEYS[1], ARGV[1]) end;if request_times > tonumber(ARGV[2]) then return 0 end return 1;" 1 test_127.0.0.1 10 3

通过上面的例子也可以看出,我们可以在redis里使用eval命令调用lua脚本,且该脚本在redis里作为单条命令去执行不会受到其余命令的影响,非常适用于高并发场景下的事务处理。同样我们可以在lua脚本里实现任何想要实现的功能,迭代,循环,判断,赋值 都是可以的。

lua脚本缓存

redis脚本也支持将脚本进行持久化,这样的话,下次再使用就不用输入那么长的lua脚本了。事实上使用eval执行的时候也会缓存,eval与load不同的是eval会将lua脚本执行并缓存,而load只会将脚本缓存。相同点是它们都使用sha算法进行缓存,因此只要lua脚本内容相同,eval与load缓存的sha码就是一样的。而缓存后的脚本,我们可以使用evalsha命令直接调用,极大的简化了我们的代码量,不用重复的将lua脚本写出来。

#eval 执行脚本并缓存
eval script numkeys key [key ...] arg [arg ...]

#load 缓存lua脚本
SCRIPT LOAD script

#使用缓存的脚本sha码调用脚本
EVALSHA sha1 numkeys key [key ...] arg [arg ...] 

#使用sha码判断脚本是否已缓存
SCRIPT EXISTS sha1 [sha1 ...]

#清空所有缓存的脚本
SCRIPT FLUSH

#杀死当前正在执行的所有lua脚本
SCRIPT KILL

 

希望本文对你有帮助,请点个赞鼓励一下作者吧~ 谢谢!

### Redis事务Lua脚本的关系及用法 #### 一、Redis事务的基础概念 Redis事务允许将一组命令打包成一个序列并按顺序执行,确保这些命令在执行过程中不被其他客户端的请求打断。事务通过 `MULTI` 开始,`EXEC` 提交,或者通过 `DISCARD` 取消[^4]。 - **MULTI**: 将后续的一系列命令标记为事务的一部分。 - **EXEC**: 执行所有已入队列的命令,并返回它们的结果列表。 - **WATCH**: 监视键的变化情况,用于乐观锁机制。 - **UNWATCH/DISCARD**: 如果监视到某些键发生变化,则取消当前事务。 尽管 Redis 事务提供了基本的操作隔离能力,但它并不支持真正的 ACID 特性中的回滚功能。一旦某个命令失败,其余命令仍会继续执行。 #### 二、Lua脚本的作用及其特性 Lua 脚本是一种更高级别的工具,可以在单次调用中完成复杂逻辑运算,同时利用 Redis 单线程模型保证整个过程具有原子性。以下是 Lua 脚本的关键属性: 1. **原子性保障** 当一条 Lua 脚本运行时,Redis 不会中断该脚本来处理其他命令或脚本,这使得 Lua 脚本天然具备原子性[^2]。 2. **减少网络延迟** 复杂操作可以通过一次网络传输传递给服务器端执行完毕后再返回结果,相比多次单独发送指令显著降低了通信成本[^5]。 3. **灵活性强** 用户可以直接编写自定义逻辑嵌套于脚本内部,而无需依赖外部程序控制流管理[^3]。 #### 三、实际应用案例——结合两者实现高效并发安全方案 下面展示了一个典型的例子:基于 RedisLua 实现分布式计数器加减操作的同时保持一致性约束条件下的更新行为。 ##### 场景描述 假设我们需要设计这样一个服务接口 `/increment_if_positive/{key}/{value}` ,它的语义是只有当指定 key 对应数值大于零时才允许增加 value 数量;否则拒绝修改原数据状态。 ##### 方法对比分析 如果我们单纯依靠普通的 INCRBY 或 DECRBY 来达成目标可能会遇到竞争状况导致最终效果偏离预期值。此时引入 Lua 则能有效规避此类风险。 ```lua -- redis-lua-script.lua local current_value = tonumber(redis.call('GET', KEYS[1])) if not current_value then -- If the key does not exist, treat it as zero. current_value = 0 end if ARGV[1] == 'add' and (current_value >= tonumber(ARGV[2])) then local new_val = current_value + tonumber(ARGV[3]) redis.call('SET', KEYS[1], tostring(new_val)) return {true, new_val} else return {false, current_value} end ``` 上述代码片段展示了如何构建满足特定业务需求的安全增量函数。其中: - 参数说明: - `KEYS[1]`: 表示待操作的目标存储位置名称; - `ARGV[1]`: 动作类型标志符(此处固定设为字符串 `'add'`); - `ARGV[2]`: 下限阈值参数; - `ARGV[3]`: 正整型步长变量。 - 返回结构体由布尔判断成功与否以及最新计算所得的实际数值组成。 最后一步是在应用程序层面加载此文件并通过 EVAL 命令触发远程解释环境解析执行流程即可[^1]。 --- ###
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王义凯_Rick

遇见即是缘,路过就给个评论吧~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值