redis中的事务、lua脚本和管道的使用场景

2023-11-06

事务

redis中的事务并不像mysql中那么完美,只是简单的保证了原子性。redis中提供了四个命令来实现事务,MULTI:类似于mysql中的BEGIN;EXEC:类似于COMMIT;DISCARD类似于ROLLBACK;WATCH则是用于来实现mysql中类似锁的功能。具体的使用方法非常简单,例如:

127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr count
QUEUED
127.0.0.1:6379> incr count
QUEUED
127.0.0.1:6379> exec
1) (integer) 1
2) (integer) 2

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

redis事务的实现原理是把事务中的命令先放入队列中,当client提交了exec命令后,redis会把队列中的每一条命令按序执行一遍。如果在执行exec之前事务中断了,那么所有的命令都不会执行;如果执行了exec命令之后,那么所有的命令都会按序执行。但如果在事务执行期间redis被强制关闭,那么则需要使用redis-check-aof 工具对redis进行修复,删除那些部分执行的命令。下面分几种情况讨论下redis事务中需要注意的地方:

1)入队命令语法错误,此时还没有执行exec命令

虽然redis在碰到exec命令之前不会执行事务中的命令,但是,它会对每个命令进行适当的检查,当发现有某些明显的语法错误时,如参数个数不正确,则会在入队时,返回错误信息,并当看到exec命令调用discard命令进行回滚。例如:

127.0.0.1:6379> get name
"Jeff"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name Kate
QUEUED
127.0.0.1:6379> set name
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get name
"Jeff"

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

2) 当exec执行完毕后,执行其它命令时发生错误

当redis在执行命令时,如果出现了错误,那么redis不会终止其它命令的执行。即只要是正确的命令,无论在错误命令之前还是之后,都会顺利执行。例如:

127.0.0.1:6379> lpush visited "name1"
(integer) 1
127.0.0.1:6379> get name
"Kate"
127.0.0.1:6379> get count
"5"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name Jeff
QUEUED
127.0.0.1:6379> get visited
QUEUED
127.0.0.1:6379> set count 10
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
3) OK
127.0.0.1:6379> get name
"Jeff"
127.0.0.1:6379> get count
"10"

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

redis没有实现真正的回滚是因为redis只是一个key-value缓存数据库,如果加上日志回滚,将会影响其效率。

3)事务间的相互影响

事务中最长出现的影响就是同时修改一条记录,而redis中的事务默认没有对此进行处理,如果两个事务同时修改一条记录,首先执行exec的事务的结果将会被覆盖。这里我们可以使用watch命令,该命令用于监控某些具体的key,如果这些key被其它事务修改了,那么本事务再修改时就不会成功,然后返回失败的提示。

T1:
    watch name
    multi
    set name Jeff
    exec
T2:
    watch name
    multi
    set name Kate
    exec

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

如果T2先提交exec,那么T1提交时则更新失败,此时name依旧是Kate,然后在应用层决定是否需要重新执行该事务。

由于redis事务中的命令在遇到exec命令之前并没有真正的执行,所以我们无法在事务中的命令中使用前面命令的查询结果。我们唯一可以做的就是通过watch保证在我们进行修改时,如果其它事务刚好进行了修改,则我们的修改停止,然后应用层做相应的处理。比如:如果get key 返回的值是true,那么我们set otherkey value,否则什么也不做。这种情况下,虽然可以用事务+watch实现原子操作,但是不免有点太僵硬,很明显这是一个if……else语句。正是因为这个局限,使得lua脚本派上了大的用场。

参考文献:http://redis.io/topics/transactions

lua脚本(2.6.0及以后版本)

原先没有注意lua脚本的用法,上次还是请教了同事才知道redis中lua脚本的强大之处,然后果断在项目中用了一下,感觉非常完美。其使用方法非常简单,例如:

eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second

 
 
  • 1
  • 2

其中eval是lua脚本的解释器;eval的第一个参数是脚本的内容,第二个参数是脚本里面KEYS数组的长度(不包括ARGV参数的个数),这里是两个;紧接着就会有两个参数,用于传递个KEYS数组;后面剩下的参数全部传递给ARGV数组,相当于命令行参数。

如果我们想在lua脚本中调用redis的命令该如何操作?可以在脚本中使用redis.call()或redis.pcall()直接调用,两者用法类似,只是在遇到错误时,返回错误的提示方式不同。例如:

eval "return redis.call('set',KEYS[1],'bar')" 1 foo

 
 
  • 1
  • 2

redis确保正一条script脚本执行期间,其它任何脚本或者命令都无法执行。正是由于这种原子性,script才可以替代MULTI/EXEC作为事务使用。当然,官方文档也说了,正是由于script执行的原子性,所以我们不要在script中执行过长开销的程序,否则会验证影响其它请求的执行。

另外,redis为了减少每次客户端发送来的数据带宽(如果script太长,则发送来的内容可能非常多),会把每次新出现的脚本的sha1摘要保存下来,这样后续如果script不变的话,只需要调用evalsha命令+script摘要即可,而不需要重复传递过长的脚本内容。例如:

127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> eval "return redis.call('get','foo')" 0
"bar"
127.0.0.1:6379> evalsha 6b1bf486c81ceb7edf3c093f4c48582e38c0e791 0
"bar"

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

从这里可以看出把key和arg以参数的形式传递而不是直接写在script中的好处,因为这样可以把变量提取出来,使得script的sha1摘要保持不变,提高命中率。在应用程序中,可以先使用evalsha进行调用,如果失败,再使用eval进行操作,这样可以在一定程度上提高效率。

有了上面的知识,我们就可以使用lua脚本来灵活的使用redis的事务,这里举几个简单的例子。

场景1:我们要判断一个IP是不是第一次访问,如果是第一次访问,那么返回状态1,否则插入该ip,并返回状态0.

127.0.0.1:6379> eval "if redis.call('get',KEYS[1]) then return 1 else redis.call('set', KEYS[1], 'test') return 0 end" 1 test_127.0.0.1
(integer) 0
127.0.0.1:6379> eval "if redis.call('get',KEYS[1]) then return 1 else redis.call('set', KEYS[1], 'test') return 0 end" 1 test_127.0.0.1
(integer) 1

 
 
  • 1
  • 2
  • 3
  • 4
  • 5

场景2:使用redis限制30分钟内一个IP只允许访问5次

思路:每次想把当前的时间插入到redis的list中,然后判断list长度是否达到5次,如果大于5次,那么取出队首的元素,和当前时间进行判断,如果在30分钟之内,则返回-1,其它情况返回1.

eval "redis.call('rpush', KEYS[1],ARGV[1]);if (redis.call('llen',KEYS[1]) >tonumber(ARGV[2])) then if tonumber(ARGV[1])-redis.call('lpop', KEYS[1])<tonumber(ARGV[3]) then return -1 else return 1 end else return 1 end" 1 'test_127.0.0.1' 1451460590 5 1800

 
 
  • 1
  • 2

通过上面两个场景可以看到,我们仅仅使用了lua的if语句,就可以实现这么方便的操作,如果使用其它的lua语法,肯定更加方便。

官网文档上有这样一段话:

A Redis script is transactional by definition, so everything you can do with a Redis transaction, you can also do with a script, and usually the script will be both simpler and faster.

由此可以看出,官方还是支持大家尽量使用lua script来代替transaction的。

参考文献:http://redis.io/commands/eval

管道

大家都知道redis是基于TCP连接进行通信的,每一个request/response都需要经历一个RTT往返时间,如果需要执行很多短小的命令,这些往返时间的开销是很大的,在此情形下,redis提出了管道来提高执行效率。管道的思想是:如果client执行一些相互之间无关的命令或者不需要获取命令的返回值,那么redis允许你连续发送多条命令,而不需要等待前面命令执行完毕。比如我们执行3条INCR命令,如果使用管道,理论上只需要一个RTT+3条命令的执行时间即可,如果不适用管道,那么可能需要额外的两个RTT时间。因此,管道相当于批处理脚本,相当于是命令集,例如:

with r.pipeline(transaction=False) as pipe:
    pipe.set('key1', 'value1')
    pipe.set('key2', 'value2')
    pipe.set('key3', 'value3')
    pipe.execute()

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Pipeline在某些场景下非常有用,比如有多个command需要被“及时的”提交,而且他们对相应结果没有互相依赖,而且对结果响应也无需立即获得,那么pipeline就可以充当这种“批处理”的工具;而且在一定程度上,可以较大的提升性能,性能提升的原因主要是TCP链接中较少了“交互往返”的时间。例如:因为业务需要,我们需要把用户的操作过程记录在日志中以方便以后的统计,每隔3个小时生成一个新的日志文件,那么后台处理线程,将会扫描日志文件并将每条日志输出为“operation”:1,即表示操作次数为1;如果每个operation都发送一个command,事实上性能是很差的,而且是没有必要的;那么我们就可以使用pipeline批量提交即可。

管道和事务是不同的,pipeline只是表达“交互”中操作的传递的方向性,pipeline也可以在事务中运行,也可以不在。无论如何,pipeline中发送的每个command都会被server立即执行,如果执行失败,将会在此后的相应中得到信息;也就是pipeline并不是表达“所有command都一起成功”的语义,管道中前面命令失败,后面命令不会有影响,继续执行。简单来说就是管道中的命令是没有关系的,它们只是像管道一样流水发给server,而不是串行执行,仅此而已;但是如果pipeline的操作被封装在事务中,那么将有事务来确保操作的成功与失败。

使用管道可能在效率上比使用script要好,但是有的情况下只能使用script。因为在执行后面的命令时,无法得到前面命令的结果,就像事务一样,所以如果需要在后面命令中使用前面命令的value等结果,则只能使用script或者事务+watch。

参考文献:http://redis.io/topics/pipelining




http://blog.csdn.net/fangjian1204/article/details/50585080





本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

redis中的事务、lua脚本和管道的使用场景 的相关文章

  • Lua中按字符分割字符串

    我有像这样的字符串 ABC DEF 我需要将它们分开 字符并将两个部分分别分配给一个变量 在 Ruby 中 我会这样做 a b ABC DEF split 显然Lua没有这么简单的方法 经过一番挖掘后 我找不到一种简短的方法来实现我所追求的
  • 2 个具有共享 Redis 依赖的 Helm Chart

    目前 我有 2 个 Helm Charts Chart A 和 Chart B Chart A 和 Chart B 对 Redis 实例具有相同的依赖关系 如Chart yaml file dependencies name redis v
  • redis - 使用哈希

    我正在使用 redis 为我的 Web 应用程序实现社交流和通知系统 我是 redis 的新手 我对哈希值及其效率有一些疑问 我读过这篇很棒的文章Instagram 帖子 http instagram engineering tumblr
  • 有没有办法让特定的key在集群模式下定位到特定的redis实例上?

    我想让我的多锁位于不同的redis实例上 我发现redission可以指定一个实例来执行命令 但是如果该命令与key相关 则指定的实例会将命令传输到另一个实例 你能给我一些建议吗 你可以 但这并不是微不足道的 首先 Redis 在键中使用大
  • Redis是如何实现高吞吐量和高性能的?

    我知道这是一个非常普遍的问题 但是 我想了解允许 Redis 或 MemCached Cassandra 等缓存 以惊人的性能极限工作的主要架构决策是什么 如何维持连接 连接是 TCP 还是 HTTP 我知道它完全是用C写的 内存是如何管理
  • StackExchange.Redis的正确使用方法

    这个想法是使用更少的连接和更好的性能 连接会随时过期吗 对于另一个问题 redis GetDatabase 打开新连接 private static ConnectionMultiplexer redis private static ID
  • 想要在后台不间断地运行redis-server

    我已经下载了 redis 2 6 16 tar gz 文件并安装成功 安装后我运行 src redis server 它工作正常 但我不想每次都手动运行 src redis server 而是希望 redis server 作为后台进程持续
  • 在 Spring 4 中干掉通用的 RedisTemplate

    我读到你可以拥有 Autowired从 Spring 4 开始泛型 这太棒了 我有一个摘要RedisService我想参加的课程 Autowired一个通用的 RestTemplate 如下所示 public abstract class
  • Spring Redis 排序键

    我在 Redis Spring Data Redis 中有以下键 localhost gt Keys 1 id 1 Name C5796 Site DRG1 2 id 2 Name CX1XE Site DG1 3 id 3 Name C5
  • 没有适用于机器人的 Laravel 会话

    我在大型 Laravel 项目和 Redis 存储方面遇到问题 我们将会话存储在 Redis 中 我们已经有 28GB 的 RAM 然而 它的运行速度仍然相对较快 达到了极限 因为我们有来自搜索引擎机器人的大量点击 每天超过 250 000
  • 如何在Redis中正确存储图片?

    决定将图像存储在Redis中 如何正确执行 现在我这样做 redis gt set image path here is the base64 image code 我不确定这是否正常 将图片存储在Redis中是完全可以的 Redis 键和
  • ServiceStack PooledRedisClientManager 故障转移如何工作?

    根据 git commit 消息 ServiceStack 最近添加了故障转移支持 我最初认为这意味着我可以关闭我的一个 Redis 实例 并且我的池客户端管理器将优雅地处理故障转移并尝试与我的备用 Redis 实例之一连接 不幸的是 我的
  • nginx/uwsgi 服务器的持久内存中 Python 对象

    我怀疑这是否可能 但这是问题和提出的解决方案 提出的解决方案的可行性是这个问题的对象 我有一些需要可用于所有请求的 全局数据 我将这些数据保存到 Riak 并使用 Redis 作为缓存层以提高访问速度 目前 数据被分为约 30 个逻辑块 每
  • redis能完全取代mysql吗?

    简单的问题 我是否可以使用 redis 而不是 mysql 来处理各种 Web 应用程序 社交网络 地理位置服务等 IT 领域没有什么是不可能的 但有些事情可能会变得极其复杂 将键值存储用于全文搜索之类的事情可能会非常痛苦 另外 据我所知
  • redis.exceptions.ConnectionError:连接到本地主机时出现错误-2:6379。名称或服务未知

    当我在服务器中运行代码时出现此错误 我的环境是 debian 并且Python2 7 3 Traceback most recent call last File fetcher py line 4 in
  • 具有匹配模式的 ioredis 密钥

    我想用键匹配模式 LOGIN 搜索 Redis 数据库 我在我的应用程序中使用 ioredis 昨天我搜索了整个网络 我得到了一些执行这项工作的选项 如下所示 KEYS 扫描流 Issue import Redis from ioredis
  • Redis - 错误:值不是有效的浮点数

    我在 Redis 中有一个排序集 我试图通过在Python代码中使用zincrby来更新特定元素的计数器值 例如 conn zincrby usersSet float 1 user1 但它显示错误为 错误 值不是有效的浮点数 我在 cli
  • spring中如何使用jackson代替JdkSerializationRedisSerializer

    我在我的一个 Java 应用程序中使用 Redis 并且正在序列化要存储在 Redis 中的对象列表 但是 我注意到使用 RedisTemplate 会使用 JdkSerializationRedisSerializer 相反 我想使用 J
  • 执行 SET {Key} 超时,inst: 0,mgr: Inactive,queue: 2, qu=1, qs=1, qc=0, wr=1/1, in=0/0

    我正在尝试使用 StackExchange Redis 客户端将 90 KB pdf 文件保存到 Azure Redis 缓存中 我已将该文件转换为字节数组并尝试使用 stringSet 方法保存它并收到错误 Code byte bytes
  • Spring-boot中将redis-cache反序列化为对象的问题

    我在 Client 类中使用 JsonNode 来处理 MySQL 8 数据库中 JSON 类型的字段 即使对于 API 请求 它也能很好地工作 但是当我使用 Redis 启用缓存 我确实需要它 时 我注意到 Redis 无法序列化 Jso

随机推荐

  • 2-2-7 React 几个用Hooks的小技巧

    1 Hooks 控制流 if useEffect gt Hooks是对行为的声明 if else 是分支控制 不是声明的一部分 从理论上不应该有声明在控制流之下 在React内部通过Hooks的词法顺序来区分不同的Hook 2 Stacko
  • 在PyCharm上安装TENSORFLOW

    上周联系了一下南科大的老师 老师比较和蔼可亲 安排我先学习一下MNIST数据集进行深度学习方向图像识别的入门 本来上周应该要完成的但还是因为搭建平台 学校实训等各方面原因 没能及时完成 特别是在想用pycharm用来安装tensorflow
  • 台式计算机怎么连接蓝牙 win10,win10台式电脑蓝牙怎么开启(开启电脑蓝牙的步骤图)...

    原标题 win10台式电脑蓝牙怎么开启 开启电脑蓝牙的步骤图 虽然WiFi无线连接现在是主流 但蓝牙无线连接仍然以独特的优势得以在许多设备中保留 例如Win10动态锁自动锁定功能就是利用电脑与手机的蓝牙连接实现的 下面MS酋长就来分享一下W
  • 找回微信聊天记录-unbaksdpak解包软件图文教程

    前几天用小米助手备份恢复后微信聊天记录丢失 上网找资料搞鼓了一天终于找回了聊天记录 在上个分享帖子里有详细介绍了找回过程 但有机友某些步骤看不懂 要我再出个图文教程 我想主要应该是GitHub上的解包软件不会用吧 这次主要讲一下unbaks
  • 3分钟入门:Blob 对象的了解与创建

    Blob 对象 Blob 英文全称 binary large object 是指二进制类型大对象 Blob 对象表示不可变的 类似文件对象的原始数据 即它是类似文件对象的二进制数据 可以像操作 File 对象一样操作 Blob 对象 但话又
  • Python入门——第一章 python编程基础

    Python入门 文章目录 Python入门 第一章 python编程基础 1 1 基本输入输出 1 1 1使用print 函数进行简单输出 chr 函数 print 输出到指定文件 print 输出年份 月份 日期 1 1 2使用prin
  • 给定一个整数,判断它能否被3,5,7整除,并输出以下信息:

    1 能同时被3 5 7整除 直接输出3 5 7 每个数中间一个空格 2 只能被其中两个数整除 输出两个数 小的在前 大的在后 例如 3 5或者3 7或者5 7 中间用空格分隔 ififa 3 只能被其中一个数整除 输出这个除数 4 不能被任
  • ESP32学习笔记(七) 复位和时钟

    ESP32学习笔记 七 复位和时钟 目录 ESP32学习笔记 一 芯片型号介绍 ESP32学习笔记 二 开发环境搭建 VSCode platformio ESP32学习笔记 三 硬件资源介绍 ESP32学习笔记 四 串口通信 ESP32学习
  • FFMPEG常用的一些命令介绍:音频录制、视频录制

    1 视频和音频单独抓取 如果指定输入格式和设备 则ffmpeg可以直接捕获视频和音频 Linux下捕获摄像头的数据保存成视频文件 ffmpeg f video4linux2 s 1280x720 i dev video0 test mp4
  • [从零开始学习FPGA编程-31]:进阶篇 - 基本时序电路-RS触发器(Verilog语言)

    作者主页 文火冰糖的硅基工坊 文火冰糖 王文兵 的博客 文火冰糖的硅基工坊 CSDN博客 本文网址 目录 前言 第1章 基本RS触发器 1 1 概述 1 2 R
  • Linux安装go环境

    Linux安装go环境 安装go需要以下步骤 安装go需要以下步骤 下载安装包 到官网 https golang org dl 下载安装包 选择对应的操作系统和架构 比如Linux的64位系统可以选择 gox x x linux amd64
  • C语言_字符串拼接函数strcat使用及实现

    字符串拼接函数strcat 01 字符串拼接函数strcat函数原型 char strcat char dest const char src 作用 把src所指向的字符串 包括 0 复制到dest所指向的字符串后面 删除 dest原来末尾
  • LeetCode·每日一题·1080. 根到叶路径上的不足节点·递归

    作者 小迅 链接 https leetcode cn problems insufficient nodes in root to leaf paths solutions 2279048 di gui zhu shi chao ji xi
  • Eclipse中引用依赖库library失败的原因

    http blog csdn net dennisruan article details 50730641
  • C语言常见问题——数组初始化的四种方法

    有一回 小易同学对我说 你学过编程吗 我略略点一点头 他说 学过编程 我便考一考你 在使用数组之前 应不应该初始化数组 我暗想 这么简单的问题 也配考我么 不只是数组 在使用任何变量之前 都应该对变量进行初始化 这是一个编程的好习惯 可以有
  • element plus安装引入使用大坑

    最近做毕设 准备用vue写前端 自然用到了elementplus 处处踩雷必须记录 首先是版本对应 vue2对应element ui vue3对应element plus 强行安装只会出错 另外安装的时候需要把已经npm run的项目停下来
  • Linux内核分析与应用

    Linux 内核分析与应用 1 蜻蜓点水 可作抛砖引玉 1 概述 用到的几个命令 insmod dmesg 2 lsmod 3 章节测试 部分可参考 4 lt 1 gt Linux得以流行 是因为遵循了GPL协议 并不是因为遵循POSIX标
  • ElasticSearch6.x 之索引

    1 创建索引 创建索引语法规则 http elasticsearch 服务器访问地址 索引名称 Put请求 无参数 实列 http 192 168 1 74 9200 security 在elasticsearch 服务器上创建名为secu
  • flutter实现验证码自动填充

    UI效果图 实现思路 1 使用Stack层叠布局 2 创建 4 个 Container 作为显示 4个验证码的 视图 3 创建TextField覆盖在 Container 上面 设置字体位透明颜色 4 监听TextField 输入 填充 4
  • redis中的事务、lua脚本和管道的使用场景

    事务 redis中的事务并不像mysql中那么完美 只是简单的保证了原子性 redis中提供了四个命令来实现事务 MULTI 类似于mysql中的BEGIN EXEC 类似于COMMIT DISCARD类似于ROLLBACK WATCH则是