热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:Redis管道事务Lua脚本对比

本文由编程笔记#小编为大家整理,主要介绍了Redis管道事务Lua脚本对比相关的知识,希望对你有一定的参考价值。
本文由编程笔记#小编为大家整理,主要介绍了Redis 管道事务Lua 脚本对比相关的知识,希望对你有一定的参考价值。




概述

Redis 提供三种将客户端多条命令打包发送给服务端执行的方式: Pipelining(管道) 、 Transactions(事务) 和 Lua Scripts(Lua 脚本)。本文不会过细的讨论三种方式的基础知识,将从这三种方式的 优势 、 局限性 和 原子性 方面展开讨论


Pipelining(管道)

Redis 管道是三者之中最简单的,当客户端需要执行多条 redis 命令时,可以通过管道一次性将要执行的多条命令发送给服务端,其作用是为了降低 RTT(Round Trip Time) 对性能的影响,比如我们使用 nc 命令将两条指令发送给 redis 服务端


$ printf "INCR x\r\nINCR x\r\n" | nc localhost 6379
:1
:2

可以看到,管道只是简单的将多个命令拼接在一起,命令之间用换行符(/r/n)分割,并没有在第一条命令前或最后一条命令后面添加开始/结束标志位


redis 服务端接收到管道发送过来的多条命令后,会一直执命令,并将命令的执行结果进行缓存,直到最后一条命令执行完成,再所有命令的执行结果一次性返回给客户端


Pipelining 的优势


在性能方面, Pipelining 有下面两个优势:





  • 将多条命令打包一次性发送给服务端,减少了客户端与服务端之间的网络调用次数,节省了 
    RTT



  • 避免了上下文切换,当客户端/服务端需要从网络中读写数据时,都会产生一次系统调用,系统调用是非常耗时的操作,其中设计到程序由用户态切换到内核态,再从内核态切换回用户态的过程。当我们执行 10 条 
    redis 命令的时候,就会发生 10 次用户态到内核态的上下文切换,但如果我们使用 
    Pipeining 将多条命令打包成一条一次性发送给服务端,就只会产生一次上下文切换


Pipelining 原子性


我们都知道, redis 执行命令的时候是单线程执行的,所以 redis 中的所有命令都具备原子性,这意味着 redis 并不会在执行某条命令的中途停止去执行另一条命令


但是 Pipelining 并不具备原子性,想象一下有两个客户端 client1 和 client2 同时向 redis 服务端发送 Pipelining 命令,每条 Pipelining 包含 5 条 redis 命令。 redis 可以保证 client1 管道中的命令始终是顺序执行的, client2 管道中的命令也是一样,始终按照管道中传入的顺序执行命令


但是 redis 并不能保证等 client1 管道中的所有命令执行完成,再执行 client2 管道中的命令,因此,在服务端中的命令执行顺序有可能是下面这种情况这种行为显示 Pipelining 在执行的时候并不会阻塞服务端。即使 client1 向客户端发送了包含多条指令的 Pipelining ,其他客户端也不会被阻塞,因为他们发送的指令可以插入到 Pipelining 中间执行


Pipelining 局限性


只有在 Pipelining 内所有命令执行完后,服务端才会把执行结果通过数组的方式返回给客户端。在执行 Pipelining 内的命令的时候,如果某些指令执行失败, Pipelining 仍会继续执行


比如下面的例子


$ printf "SET name huangxy\r\nINCR name\r\nGET name\r\n" | nc localhost 6379
+OK
-ERR value is not an integer or out of range
$6
huangxy

Pipelining 中第二条指令执行失败, Pipelining 并不会停止,而是会继续执行,等所有命令都执行完的时候,再将结果返回给客户端,其中第二条指令返回的是错误信息


Pipelining 的这个特性会导致一个问题,就是当 Pipelining 中的指令需要读取之前指令设置 key 的时候,需要额外小心,因为 key 的值有可能会被其他客户端修改。此时 Pipelining 的执行结果往往就不是我们所预期的


Pipelining 使用场景





  • 对性能有要求



  • 需要发送多个指令到服务端



  • 不需要上个命令的返回结果作为下个命令的输入


Transactions(事务)

redis 中的事务,跟我们之前在学关系型数据库的时候所了解到的事务概念有点区别。 redis 中的事务机制主要是用来对多个命令进行排队,并在最后决定是否需要执行事务中的所有命令与否


与管道不同,事务使用特殊的命令来标记事务的开始和结束( MULTI 、 EXEC 、 DISCARD )。服务器还可以对事务中的命令进行排队(这样客户端可以一次发送一条命令)。除此之外,一些第三方库还喜欢在客户端中对事务的命令进行缓存,然后通过在管道中发送整个事务的方式对其进行优化


事务的优点


事务提供了 WATCH 命令,使我们可以实现 CAS 功能,比如通过事务,我们可以实现跟 INCR 命令一样的功能


WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC

事务的原子性


redis 事务具备原子性,当一个事务正在执行时,服务端会阻塞其接收到的其他命令,只有在事务执行完成时,才会执行接下来的命令,因此事务具备原子性


事务的局限性


跟 Pipelining 一样,只有在事务执行完成时,才会把事务中多个命令的结果一并返回给客户端,因此客户端在事务还没有执行完的时候,无法获取其命令的执行结果


如果事务中的其中一个命令发生错误,会有以下两种可能性:





  • 当发生语法错误,在执行 
    EXEC 命令的时候,事务将会被丢弃,不会执行



  • 当发生运行时错误(操作了错误的数据类型)时, 
    redis 会将报错信息缓存起来,继续执行后面的命令,并在最后将所有命令的执行结果返回给客户端(报错信息也会返回)。这意味着 
    redis 事务中没有回滚机制


事务使用场景





  • 需要原子地执行多个命令



  • 不需要事务中间命令的执行结果来编排后面的命令


Lua 脚本

redis 从 2.6 版本开始引入对 Lua 脚本的支持,通过在服务器中嵌入 Lua 环境, redis 客户端可以直接使用 Lua 脚本,在服务端原子地执行多个 redis 命令


Lua 脚本的优势


与 Pipelining 和 事务不同的是,在脚本内部,我们可以在脚本中获取中间命令的返回结果,然后根据结果值做相应的处理(如 if 判断)


local key = KEYS[1]
local new = ARGV[1]
local current = redis.call('GET', key)
if (current == falseor (tonumber(new) < tonumber(current)) then
  redis.call('SET', key, new)
  return 1
else
  return 0
end

同时, redis 服务端还支持对 Lua 脚本进行缓存(使用 SCRIPT LOAD 或 EVAL 执行过的脚本服务端都会对其进行缓存),下次可以使用 EVALSHA 命令调用缓存的脚本,节省带宽


Lua 脚本的原子性


Lua 脚本跟事务一样具备原子性,当脚本执行中时,服务端接收到的命令会被阻塞


Lua 脚本的局限性


Lua 脚本在功能上没有过多的限制,但要注意的一点是,Lua 脚本在执行的时候,会阻塞其他命令的执行,所以不宜在脚本中写太耗时的处理逻辑


Lua 脚本的使用场景





  • 需要原子性地执行多个命令



  • 需要中间值来组合后面的命令



  • 需要中间值来编排后面的命令



  • 常用于扩展 
    redis 功能,实现符合自己业务场景的命令


参考文档



  • https://redis.io/topics/pipelining



  • https://redis.io/topics/transactions



  • https://redis.io/commands/eval



  • https://rafaeleyng.github.io/redis-pipelining-transactions-and-lua-scripts



  • 《Redis 设计与实现》 黄健宏著



推荐阅读
  • 深入解析Java枚举及其高级特性
    本文详细介绍了Java枚举的概念、语法、使用规则和应用场景,并探讨了其在实际编程中的高级应用。所有相关内容已收录于GitHub仓库[JavaLearningmanual](https://github.com/Ziphtracks/JavaLearningmanual),欢迎Star并持续关注。 ... [详细]
  • 本题探讨了在大数据结构背景下,如何通过整体二分和CDQ分治等高级算法优化处理复杂的时间序列问题。题目设定包括节点数量、查询次数和权重限制,并详细分析了解决方案中的关键步骤。 ... [详细]
  • 2018-2019学年第六周《Java数据结构与算法》学习总结
    本文总结了2018-2019学年第六周在《Java数据结构与算法》课程中的学习内容,重点介绍了非线性数据结构——树的相关知识及其应用。 ... [详细]
  • Nginx 反向代理与负载均衡实验
    本实验旨在通过配置 Nginx 实现反向代理和负载均衡,确保从北京本地代理服务器访问上海的 Web 服务器时,能够依次显示红、黄、绿三种颜色页面以验证负载均衡效果。 ... [详细]
  • 本文介绍了在Java环境中使用PDFBox和XPDF工具从PDF文件中提取文本内容的方法。重点讨论了处理中文字符集及解决相关错误的技术细节,特别是针对某些特定格式的PDF文件(如网上填写的报名表和下载的论文)遇到的问题及解决方案。 ... [详细]
  • 本文介绍了如何使用JavaScript的Fetch API与Express服务器进行交互,涵盖了GET、POST、PUT和DELETE请求的实现,并展示了如何处理JSON响应。 ... [详细]
  • 本文详细介绍了如何在PHP中进行数组删除、清空等操作,并提供了在Visual Studio Code中创建PHP文件的步骤。 ... [详细]
  • 在高并发需求的C++项目中,我们最初选择了JsonCpp进行JSON解析和序列化。然而,在处理大数据量时,JsonCpp频繁抛出异常,尤其是在多线程环境下问题更为突出。通过分析发现,旧版本的JsonCpp存在多线程安全性和性能瓶颈。经过评估,我们最终选择了RapidJSON作为替代方案,并实现了显著的性能提升。 ... [详细]
  • This post discusses an issue encountered while using the @name annotation in documentation generation, specifically regarding nested class processing and unexpected output. ... [详细]
  • 本文介绍了如何在多线程环境中实现异步任务的事务控制,确保任务执行的一致性和可靠性。通过使用计数器和异常标记字段,系统能够准确判断所有异步线程的执行结果,并根据结果决定是否回滚或提交事务。 ... [详细]
  • JSOI2010 蔬菜庆典:树结构中的无限大权值问题
    本文探讨了 JSOI2010 的蔬菜庆典问题,主要关注如何处理非根非叶子节点的无限大权值情况。通过分析根节点及其子树的特性,提出了有效的解决方案,并详细解释了算法的实现过程。 ... [详细]
  • ZooKeeper集群脑裂问题及其解决方案
    本文深入探讨了ZooKeeper集群中可能出现的脑裂问题,分析其成因,并提供了多种有效的解决方案,确保集群在高可用性环境下的稳定运行。 ... [详细]
  • 本文介绍了如何利用Python进行批量图片尺寸调整,包括放大和等比例缩放。文中提供了详细的代码示例,并解释了每个步骤的具体实现方法。 ... [详细]
  • JavaScript 中创建对象的多种方法
    本文详细介绍了 JavaScript 中创建对象的几种常见方式,包括对象字面量、构造函数和 Object.create 方法,并提供了示例代码和属性描述符的解释。 ... [详细]
  • 深入理解TCP/IP协议中的MTU与MSS及以太网数据帧
    本文详细探讨了TCP/IP协议中MTU(最大传输单元)和MSS(最大分段大小)的概念及其在以太网数据帧中的应用。通过分析这些关键参数的工作机制,帮助读者更好地理解网络通信中的数据包处理过程。 ... [详细]
author-avatar
台湾菜文
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有