热门标签 | 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 设计与实现》 黄健宏著



推荐阅读
  • 1:Linux进程间通信类型2:管道(pipe)和有名管道(FIFO)。3:信号(signal),见signal函数、sigaction函数及信号集操作函数和信号的发送和捕捉函数( ... [详细]
  • 小记hbase数据库java API 常用方法及案例
    HBaseAdmin类:管理hbase数据库的表信息,‘创建表、删除表、列出表选项、使表有效/无效、添加或删除列簇’;  ... [详细]
  • python的面向对象和类标签(空格分隔):python类目录一、基本知识点1、面向过程编程视角聚焦于解决问题的步骤,特点是每一步的行为都基本固定&#x ... [详细]
  • 不会用国产单片机?看这里:GD32E230串口通信
    ​之前一直使用ST的STM32F031单片机,但是由于疫情还是啥啥原因,ST的芯片价格涨得没法看,因为我们是做产品,而且量比较大,ST的芯片就无法再用了,这个成本真的扛不起。于是在 ... [详细]
  • 极限定律 My Algorithm Space AC自动机算法详解
    转载自:http:www.cppblog.commythitarchive2009042180633.html首先简要介绍一下AC自动机:Aho-Corasickautomation ... [详细]
  • OpenCV入门(八)--形态学图像处理
    膨胀膨胀是指将一些图像(或图像中的一些区域,A)与核(B)进行卷积。核可以是任何的形状或大小,它拥有一个单独定义出来的参考点。膨胀举例:腐蚀腐蚀是 ... [详细]
  • 原理:    在父组件中使用axios获取异步数据传给子组件,但是发现子组件在渲染的时候并没有数据,在created里面打印也是空的,结果发现一开始子组件绑定的数据是空的,在请求数据没有返回数据时,子组件就已经加载了,并且他绑定的值也是空的使用vuex全局状态管理,其实简单,利用vuex的辅助函数(mapState,mapMutations)mapState是将st ... [详细]
  • 本文目录一览:1、python、什么是内建函数? ... [详细]
  • WCF怎么绑定netTcpBinding寄宿到控制台应用程序
    小编给大家分享一下WCF怎么绑定netTcpBinding寄宿到控制台应用程序,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读 ... [详细]
  • 一进入这个组件页面电脑内存就从40多一直上升到99浏览器也会卡死控制台也看不到返回值不知道这是后台的问题还是前台的问题我别的接口都没事就只有这个接口是这样弄了两天了也找不到原因如 ... [详细]
  • CentOS 7上安装配置salt-api
    环境:CentOS7,已配置网络yum源安装salt-apisudoyuminstallsalt-api安装CherryPypipinstallChe ... [详细]
  • Python 硬盘容量单位及网络速率单位换算
    目标:    了解常见数据单位和数据速率的换算关系。数据单位的换算: ... [详细]
  • 在Scrapy中,请求、响应和项等对象的生命周期是有限的:它们被创建、使用一段时间,最后被销毁。从所有这些对象中,请求可能是生命周期最长的请求,因为它一直在调度程序队列中等待,直到 ... [详细]
  • 怎么看python模块中的方法[python常见问题]
    查看python模块中的方法,可以在命令行中使用pydocmodules命令查看;还可以在交互解释器中使用help()查看;或者调用系 ... [详细]
  • 关于ScrumXPDevOps的学习
    最近听了ECUG大会上孙敬云老师的分享感觉受益匪浅,毕竟大学课本上只讲到瀑布模型就没有下文了,工作以后一直贯彻的都是Scrum路线,一直也没有时间好好的去学习整理这部分的知识,直到 ... [详细]
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社区 版权所有