Lua脚本和Redis事务哪种方式在保证原子性方面性能更高?
wptr33 2025-01-17 13:13 21 浏览
通过之前的介绍我们知道了在Redis中可以通过Lua脚本和Redis的事务操作都能够保证命令执行的原子性操作,但是二者在实现机制以及使用性能上却是有着明显的不同,下面我们就来详细介绍一下两种方式的区别与联系,方便开发者能够更好的选择合适的方式来实现涉及到Redis的原子性操作场景。
Lua脚本
??Lua脚本是Redis中通过EVAL或者是通过EVALSHA命令执行的一种操作,这种操作允许客户端在Redis服务端直接执行自定义的Lua脚本,通过这种方式可以提高操作的的灵活性,保证操作的原子性,减少了客户端与服务端之间的通信连接次数,提高了命令执行效率。在之前的介绍中,我们也分析了Lua脚本在Redis客户端中执行的事原子性的操作,也就是说整个脚本的会被当做一条命令来执行,要么执行全部成功,要么全部失败,不会出现部分成功部分失败的结果,这样开发者就不会担心因为并发问题而导致数据不一致的情况出现。
Lua脚本的基本概念
??根据之前的介绍,我们知道Lua脚本其实是在Redis服务端中执行的,并且在执行Lua脚本的过程中,不会被其他客户端的操作命令所打断,也就是说,一旦脚本命令开始执行,那么Redis会保证不会有其他的客户端命令在其执行过程中对脚本进行干扰,这样就可以有效的保证原子性操作,即使在脚本执行的过程中,有其他的客户端发送了执行命令的请求,那么Redis也会等待脚本执行完成之后才会调用其他请求执行命令。
??从上面的分析中我们也可以看到,既然是个脚本,那么必然就会包含很多的执行步骤。通过脚本执行Redis命令可以有效的减少Redis的网络访问次数,例如原本执行三次网络访问命令的的过程,现在我们就可以通过一个脚本就可以搞定,如下所示。
if redis.call('exists', KEYS[1]) == 1 then
return redis.call('set', KEYS[1], ARGV[1])
else
return redis.error_reply('Key does not exist')
end
??因此,对于需要同时执行多个操作命令的过程,我们可以通过编写Lua脚本的方式来实现,这种方式通常比依赖Redis事务更加高效,因为在Redis事务中是不支持这种的复杂判断逻辑的。
??这里或许会有人问,既然是分开的几步操作,那么会不会因为并发问题导致数据不一致,例如在操作到第三步操作的时候突然断开操作了,第三步操作还没来得及操作,这就会导致数据不一致性的情况发生,这里在之前的分享中,我们提到过Lua脚本在Redis的操作中是一个原子性的,也就是说这些所有的命令只会有两个结果,成功和失败,不会出现中间状态。
EVAL命令和EVALSHA命令介绍
??EVAL命令是执行Lua脚本的操作命令,其语法如下所示。
EVAL script numkeys key1 key2 ... keyN arg1 arg2 ... argM
??其中,各个参数含义如下所示。
- script:Lua 脚本的代码内容。
- numkeys:要传递给脚本的键的数量。
- key1 key2 ... keyN:在脚本中使用的Redis中的键。
- arg1 arg2 ... argM:传递给脚本的参数,这里需要注意,这个参数是不包含键的。
??如下所示,给出一个简单的示例代码
EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 mykey "hello"
??这个脚本执行完成之后,会将mykey的值设置为hello
??EVALSHA这个命令是用来执行已经预先加载到Redis中的Lua脚本的命令,其基本语法如下所示。
EVALSHA sha1 numkeys key1 key2 ... keyN arg1 arg2 ... argM
??其中各个参数含义如下所示。
- sha1:是脚本的SHA1校验和,这个值可以通过 SCRIPT LOAD 命令计算得到。
- numkeys、key1、key2 等与 EVAL 命令相同。
??示例代码如下所示
EVALSHA "d2d2bcd5b8a5ad12a59d87a679e6f5e5758b79e8" 1 mykey "world"
??这段命令就可以通过执行之前执行过的SCRIPT LOAD命令加载到Redis中的Lua脚本其中d2d2bcd5b8a5ad12a59d87a679e6f5e5758b79e8其实就是脚本的SHA1校验和。
??SCRIPT LOAD命令其实就是用来将Lua脚本加载到Redis中的操作,然后执行成功之后会返回SHA1的校验和,这样我们就可以通过这个校验和加上EVALSHA命令来调用该脚本了,就可以避免每次都发送大段的脚本代码,如下所示。
SCRIPT LOAD "return redis.call('set', KEYS[1], ARGV[1])"
??当然除了这些之外,Redis内部还支持了对Lua脚本的高级支持,有兴趣的读者可以深入了解。
性能问题
??通过对Lua执行原理的介绍,我们也可以知道,虽然Lua脚本的执行是原子性的,但是但是如果在脚本执行的时候包含了特别耗时的操作,这个时候,可能会对Redis服务性能有所影响,Redis也是考虑到了此方面的弱点,所以提供了一个配置来设置Lua脚本执行的时间,如果超过了这个设置的脚本执行时间,那么Lua脚本就不会管脚本是否正常执行,直接返回执行失败,这里需要注意,这个执行失败所代表的含义就是所有的操作都是执行失败的,不会说出现部分结果成功,部分结果失败。
??同样,由于是脚本执行,所以对于执行操作中出现的一些不可预见的错误也就没有办法及时的进行处理。
Redis事务
??所谓的事务就是将一组命令放到一起通过原子性的方式进行执行,确保这些命令要么全部执行成功,要么就是全部失败。但是对于Redis中的事务来讲,与传统的关系型数据库不同的是对于事务隔离性、事务执行方式、事务错误处理等方便的支持。Redis的事务操作主要是基于MULTI、EXEC、WATCH、DISCARD 等命令实现,也没有提供关系型数据库类似的回滚机制,在执行操作的过程中事务命令会依次被存入到一个命令执行队列中按照顺序进行执行,如果某些命令执行失败了,那么其他的命令依然会执行,这样Redis的事务提供了对于原子性的保证,但是没有隔离性和持久性。
Redis事务命令
??在Redis中,涉及到事务操作的命令主要有如下一些。
- MULTI 命令主要用于启动事务,从这个命令开始之后的所有命令都会被标记为事务操作的部分,一直到 EXEC 命令执行之前。
- EXEC 命令用于执行事务队列中的所有命令。这些命令都是原子执行的。执行成功之后,EXEC 命令返回执行结果的列表。
- DISCARD 命令用于放弃当前事务,清除事务队列。调用 DISCARD 后,事务中的所有命令将不再执行,Redis 会返回 "OK"。
- WATCH 命令用于监视一个或多个键,一旦事务开始之前,某个被监视的键发生变化,那么 EXEC就会执行失败,事务也就不会被执行。
- UNWATCH 命令用于取消对一个或多个键的监视。
Redis事务执行流程
??Redis 事务的执行流程如下所示
- 开始事务:通过 MULTI 命令来开启一个事务的执行操作,Redis会将其后的所有命令都排列到一个事务命令执行队列中。
- 执行事务命令:在 MULTI 和 EXEC 之间所有的命令都会被客户端发送到事务队列中,但是这些命令并不会立即被执行。
- 提交事务:通过 EXEC 命令提交事务,Redis会按照序依次执行事务队列中的所有的命令。然后EXEC 会返回一个列表,包含每个命令的执行结果。
- 取消事务:如果在事务开始后决定不执行事务中的命令,我们就可以通过调用 DISCARD 命令来取消事务执行操作,然后清除所有队列中的命令。
??从上面的分析中我们可以看出,Redis的事务操作也可以有效的保证事务执行的原子性,也就是说事务中的命令要么完全执行成功,要么完全失败,无论事务中包含了多少的命令,都会被Redis逐一的执行,这样就保证了事务操作的原子性,这里所谓的原子性是指,将这些命令合并到一起执行,这个执行的过程是不会被其他客户端的命令所打断的,这个与Lua脚本执行的原子性是一样的。
??但是需要注意,这里的原子性只保证了这一组命令的执行过程的原子性,并不会像是关系型数据库那样,提供了回滚机制。所以也不会保证严格的持久性以及像是关系型数据库那样的ACID的保障。
??在支持复杂逻辑处理方面,由于Redis脚本只是简单的命令组合并不支持太多复杂的逻辑处理,所以使用起来不是太方便,虽然从Redis5之后引入了事务的隔离性但是比起Lua脚本的精确控制还是稍逊一些。
总结
??通过上面的分析,我们也知道了在Redis中无论是Lua脚本还是通过事务操作都是可以保证原子性的,但是这里所提到的原子性只是保证了一组操作过程在Redis中执行的时候不会被其他的客户端的命令操作所打断。但是有一点Lua脚本要比Redis事务机制要好,就是在执行操作的性能上,Lua脚本的性能要比事务操作更好,另外就是对于业务操作的灵活性上,Redis事务只是简单的命令组合,而Lua脚本则是提供了更加复杂的逻辑处理。因此Lua脚本在保证原子性方便和复杂逻辑处理方面表现会更优。
相关推荐
- redis的八种使用场景
-
前言:redis是我们工作开发中,经常要打交道的,下面对redis的使用场景做总结介绍也是对redis举报的功能做梳理。缓存Redis最常见的用途是作为缓存,用于加速应用程序的响应速度。...
- 基于Redis的3种分布式ID生成策略
-
在分布式系统设计中,全局唯一ID是一个基础而关键的组件。随着业务规模扩大和系统架构向微服务演进,传统的单机自增ID已无法满足需求。高并发、高可用的分布式ID生成方案成为构建可靠分布式系统的必要条件。R...
- 基于OpenWrt系统路由器的模式切换与网页设计
-
摘要:目前商用WiFi路由器已应用到多个领域,商家通过给用户提供一个稳定免费WiFi热点达到吸引客户、提升服务的目标。传统路由器自带的Luci界面提供了工厂模式的Web界面,用户可通过该界面配置路...
- 这篇文章教你看明白 nginx-ingress 控制器
-
主机nginx一般nginx做主机反向代理(网关)有以下配置...
- 如何用redis实现注册中心
-
一句话总结使用Redis实现注册中心:服务注册...
- 爱可可老师24小时热门分享(2020.5.10)
-
No1.看自己以前写的代码是种什么体验?No2.DooM-chip!国外网友SylvainLefebvre自制的无CPU、无操作码、无指令计数器...No3.我认为CS学位可以更好,如...
- Apportable:拯救程序员,IOS一秒变安卓
-
摘要:还在为了跨平台使用cocos2d-x吗,拯救objc程序员的奇葩来了,ApportableSDK:FreeAndroidsupportforcocos2d-iPhone。App...
- JAVA实现超买超卖方案汇总,那个最适合你,一篇文章彻底讲透
-
以下是几种Java实现超买超卖问题的核心解决方案及代码示例,针对高并发场景下的库存扣减问题:方案一:Redis原子操作+Lua脚本(推荐)//使用Redis+Lua保证原子性publicbo...
- 3月26日更新 快速施法自动施法可独立设置
-
2016年3月26日DOTA2有一个79.6MB的更新主要是针对自动施法和快速施法的调整本来内容不多不少朋友都有自动施法和快速施法的困扰英文更新日志一些视觉BUG修复就不翻译了主要翻译自动施...
- Redis 是如何提供服务的
-
在刚刚接触Redis的时候,最想要知道的是一个’setnameJhon’命令到达Redis服务器的时候,它是如何返回’OK’的?里面命令处理的流程如何,具体细节怎么样?你一定有问过自己...
- lua _G、_VERSION使用
-
到这里我们已经把lua基础库中的函数介绍完了,除了函数外基础库中还有两个常量,一个是_G,另一个是_VERSION。_G是基础库本身,指向自己,这个变量很有意思,可以无限引用自己,最后得到的还是自己,...
- China's top diplomat to chair third China-Pacific Island countries foreign ministers' meeting
-
BEIJING,May21(Xinhua)--ChineseForeignMinisterWangYi,alsoamemberofthePoliticalBureau...
- 移动工作交流工具Lua推出Insights数据分析产品
-
Lua是一个适用于各种职业人士的移动交流平台,它在今天推出了一项叫做Insights的全新功能。Insights是一个数据平台,客户可以在上面实时看到员工之间的交流情况,并分析这些情况对公司发展的影响...
- Redis 7新武器:用Redis Stack实现向量搜索的极限压测
-
当传统关系型数据库还在为向量相似度搜索的性能挣扎时,Redis7的RedisStack...
- Nginx/OpenResty详解,Nginx Lua编程,重定向与内部子请求
-
重定向与内部子请求Nginx的rewrite指令不仅可以在Nginx内部的server、location之间进行跳转,还可以进行外部链接的重定向。通过ngx_lua模块的Lua函数除了能实现Nginx...
- 一周热门
-
-
C# 13 和 .NET 9 全知道 :13 使用 ASP.NET Core 构建网站 (1)
-
因果推断Matching方式实现代码 因果推断模型
-
git pull命令使用实例 git pull--rebase
-
git pull 和git fetch 命令分别有什么作用?二者有什么区别?
-
面试官:git pull是哪两个指令的组合?
-
git 执行pull错误如何撤销 git pull fail
-
git fetch 和git pull 的异同 git中fetch和pull的区别
-
git pull 之后本地代码被覆盖 解决方案
-
还可以这样玩?Git基本原理及各种骚操作,涨知识了
-
git命令之pull git.pull
-
- 最近发表
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)
- c语言 switch (34)
- git commit (34)