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

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

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

通过之前的介绍我们知道了在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脚本在保证原子性方便和复杂逻辑处理方面表现会更优。

相关推荐

十年之重修Redis原理(redis重试机制)

弱小和无知并不是生存的障碍,傲慢才是。--------面试者...

Redis 中ZSET数据类型命令使用及对应场景总结

1.zadd添加元素zaddkeyscoremember...

redis总结(redis常用)

RedisTemplate封装的工具类packagehk.com.easyview.common.helper;importcom.alibaba.fastjson.JSONObject;...

配置热更新系统(如何实现热更新)

整体设计概览┌────────────┐┌────────────────┐┌────────────┐│配置后台服务│--写入-->│Red...

java高级用法之:调用本地方法的利器JNA

简介JAVA是可以调用本地方法的,官方提供的调用方式叫做JNI,全称叫做javanativeinterface。要想使用JNI,我们需要在JAVA代码中定义native方法,然后通过javah命令...

SpringBoot:如何优雅地进行响应数据封装、异常处理

背景越来越多的项目开始基于前后端分离的模式进行开发,这对后端接口的报文格式便有了一定的要求。通常,我们会采用JSON格式作为前后端交换数据格式,从而减少沟通成本等。...

Java中有了基本类型为什么还要有包装类型(封装类型)

Java中基本数据类型与包装类型有:...

java面向对象三大特性:封装、继承、多态——举例说明(转载)

概念封装:封装就是将客观的事物抽象成类,类中存在属于这个类的属性和方法。...

java 面向对象编程:封装、继承、多态

Java中的封装(Encapsulation)、继承(Inheritance)和多态(Polymorphism)是面向对象编程的三大基本概念。它们有助于提高代码的可重用性、可扩展性和可维护性。...

怎样解析java中的封装(怎样解析java中的封装文件)

1.解析java中的封装1.1以生活中的例子为例,打开电视机的时候你只需要按下开关键,电视机就会打开,我们通过这个操作我们可以去间接的对电视机里面的元器件进行亮屏和显示界面操作,具体怎么实现我们并不...

python 示例代码(python代码详解)

以下是35个python代码示例,涵盖了从基础到高级的各种应用场景。这些示例旨在帮助你学习和理解python编程的各个方面。1.Hello,World!#python...

python 进阶突破——内置模块(Standard Library)

Python提供了丰富的内置模块(StandardLibrary),无需安装即可直接使用。以下是一些常用的内置模块及其主要功能:1.文件与系统操作...

Python程序员如何调试和分析Python脚本程序?附代码实现

调试和分析Python脚本程序调试技术和分析技术在Python开发中发挥着重要作用。调试器可以设置条件断点,帮助程序员分析所有代码。而分析器可以运行程序,并提供运行时的详细信息,同时也能找出程序中的性...

python中,函数和方法异同点(python方法和函数的区别)

在Python中,函数(Function)...

Python入门基础命令详解(python基础入门教程)

以下是Python基本命令的详解指南,专为初学者设计,涵盖基础语法、常用操作和实用示例:Python基本命令详解:入门必备指南1.Python简介特点:简洁易读、跨平台、丰富的库支持...