百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

Lua脚本和Redis事务哪种方式在保证原子性方面性能更高?

wptr33 2025-01-17 13:13 18 浏览

通过之前的介绍我们知道了在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 命令计算得到。
  • numkeyskey1key2 等与 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的事务操作主要是基于MULTIEXECWATCHDISCARD 等命令实现,也没有提供关系型数据库类似的回滚机制,在执行操作的过程中事务命令会依次被存入到一个命令执行队列中按照顺序进行执行,如果某些命令执行失败了,那么其他的命令依然会执行,这样Redis的事务提供了对于原子性的保证,但是没有隔离性和持久性。

Redis事务命令

??在Redis中,涉及到事务操作的命令主要有如下一些。

  • MULTI 命令主要用于启动事务,从这个命令开始之后的所有命令都会被标记为事务操作的部分,一直到 EXEC 命令执行之前。
  • EXEC 命令用于执行事务队列中的所有命令。这些命令都是原子执行的。执行成功之后,EXEC 命令返回执行结果的列表。
  • DISCARD 命令用于放弃当前事务,清除事务队列。调用 DISCARD 后,事务中的所有命令将不再执行,Redis 会返回 "OK"。
  • WATCH 命令用于监视一个或多个键,一旦事务开始之前,某个被监视的键发生变化,那么 EXEC就会执行失败,事务也就不会被执行。
  • UNWATCH 命令用于取消对一个或多个键的监视。

Redis事务执行流程

??Redis 事务的执行流程如下所示

  • 开始事务:通过 MULTI 命令来开启一个事务的执行操作,Redis会将其后的所有命令都排列到一个事务命令执行队列中。
  • 执行事务命令:在 MULTIEXEC 之间所有的命令都会被客户端发送到事务队列中,但是这些命令并不会立即被执行。
  • 提交事务:通过 EXEC 命令提交事务,Redis会按照序依次执行事务队列中的所有的命令。然后EXEC 会返回一个列表,包含每个命令的执行结果。
  • 取消事务:如果在事务开始后决定不执行事务中的命令,我们就可以通过调用 DISCARD 命令来取消事务执行操作,然后清除所有队列中的命令。

??从上面的分析中我们可以看出,Redis的事务操作也可以有效的保证事务执行的原子性,也就是说事务中的命令要么完全执行成功,要么完全失败,无论事务中包含了多少的命令,都会被Redis逐一的执行,这样就保证了事务操作的原子性,这里所谓的原子性是指,将这些命令合并到一起执行,这个执行的过程是不会被其他客户端的命令所打断的,这个与Lua脚本执行的原子性是一样的。

??但是需要注意,这里的原子性只保证了这一组命令的执行过程的原子性,并不会像是关系型数据库那样,提供了回滚机制。所以也不会保证严格的持久性以及像是关系型数据库那样的ACID的保障。

??在支持复杂逻辑处理方面,由于Redis脚本只是简单的命令组合并不支持太多复杂的逻辑处理,所以使用起来不是太方便,虽然从Redis5之后引入了事务的隔离性但是比起Lua脚本的精确控制还是稍逊一些。

总结

??通过上面的分析,我们也知道了在Redis中无论是Lua脚本还是通过事务操作都是可以保证原子性的,但是这里所提到的原子性只是保证了一组操作过程在Redis中执行的时候不会被其他的客户端的命令操作所打断。但是有一点Lua脚本要比Redis事务机制要好,就是在执行操作的性能上,Lua脚本的性能要比事务操作更好,另外就是对于业务操作的灵活性上,Redis事务只是简单的命令组合,而Lua脚本则是提供了更加复杂的逻辑处理。因此Lua脚本在保证原子性方便和复杂逻辑处理方面表现会更优。

相关推荐

威信Chronosonic XVX全新旗舰全球首发 设计特点彻底公开

第一眼看到WilsonAudio新推出的ChronosonicXVX音箱,相信大家都会直觉认为它是两年前超级旗舰WAMMMasterChronosonic的缩小版,不过这个推测并不完全正确。C...

C#高精度Timer和Delay以及时间测量

在PCHMI7.0后在工具箱里会多一个MsTimer,以及Delay和Microsecond两个类。...

python教程从基础到精通,第9课—日期与时间

Hello,小伙伴们,祝大家五.一玩得快乐!刚学习完了七大数据类型,今天咱们来学习日期与时间的表示方法。Python标准库中提供了时间和日期的支持:calendar:日历相关;time、datetim...

软件测试|教你轻松玩转Python日期时间

Python基础之日期时间处理...

Go语言中互斥锁与读写锁,你知多少?

简述Golang中的锁机制主要包含互斥锁和读写锁互斥锁互斥锁是传统并发程序对共享资源进行控制访问的主要手段。在Go中主要使用sync.Mutex的结构体表示。一个简单的示例:funcmutex()...

变形金刚动画大电影——经典台词赏析

YOURDAYSARENUMBEREDNOW,DECEPTI-CREEPS你们活不了多久了,霸天虎小子。-{铁皮说的话,体现了铁皮的嫉恶如仇,可是后来铁皮在飞船上遇袭身亡,可谓是出师未捷身先...

Python时间日期模块使用教程(python3日期)

1.时间日期处理概述在日常编程中,时间日期处理是非常常见的需求,比如:记录日志时间...

亚马逊介绍AWS“无服务器”云服务改进:数据库可线上扩充容量等

IT之家11月29日消息,在今天于美国拉斯维加斯展开的亚马逊“AWSre:Invent2023”活动中,亚马逊计算部门资深副总裁PeterDeSantis,介绍了旗下三款云端服务,IT...

2.日期格式 datetime(日期时间显示格式)

fromdatetimeimportdatetime1.获取当前日期和时间now=datetime.now()#2025-05-3110:56:01.4687822.格式化日期...

【科普】时间单位大盘点(时间单位都有哪些?)

时间单位,是7种基本单位之一,长度、时间、质量、物质的量、光照度、电流和(热力学)温度是七种基本单位。本词条中时间单位以时间从大到小列。今天我们来盘点下时间的单位换算...

基于PHP的Laravel框架,盘点Github高星Web管理后台,效率为王!

在Web开发工作中,选择一个高效、稳定的后台管理系统是提高开发效率的关键。虽然PHP在近些年中的热度有所减退,但其上手简单、开源、灵活且被广泛应用的特点,仍然使其在编程语言排行榜中保持前十的位置。这表...

如何使用PHP编写一个简单的留言板?

留言板是一个常见的Web应用程序,允许用户在网站上发布和查看留言。在本文中,我们将使用PHP编写一个简单的留言板,介绍构建过程中的关键步骤和技巧。一、准备工作在开始编写留言板之前,我们需要准备好以下工...

产品经理提需求时要考虑的 15 个隐性需求

虽然世界充满未知的变化,但是有一些大的方向还是可以把握的,本文跟大家谈谈产品经理提需求时要考虑的15个隐性需求,enjoy~俗话说,计划赶不上变化快,无论需求文档做得如何细致,考虑得如何周全,总会...

关于 PHP 启动 MongoDb 找不到指定模块问题

前言:最近有一个小demo,需要通过PHP将用户行为记录储存到MongoDB,再用Spark做协同过滤。由于以前处理跨语言交互是通过消息中间件,这次本地使用MongoDB却弄出了几个问...

PHP程序员老鸟面试经历(php程序员怎么样)

在任何时代找任何工作都有面试这么一说的。特别是高端技术类的工种对技术理论和技术实操能力要求很严格。大部分公司招收技术员工的要求也越来愈高。至于PHP程序员也是如此,我估计大多数PHP老鸟已经不在意所...