如何在Redis中实现事务

2023-11-19

事务介绍

事务(Transaction) ,是指作为单个逻辑工作单元执行的一系列操作。事务必须满足ACID原则(原子性、一致性、隔离性和持久性)。
简单来说,事务可能包括1~N条命令,当这些命令被作为事务处理时,将会顺序执行这些命令直到完成,并返回结果,如果中途有命令失败,则会回滚所有操作。
举个例子:

  1. 我们到银行ATM机取一笔钱,我们的操作可能是如下:

  2. 插卡(输入密码)

  3. 输入要取的金额

  4. ATM吐钞
    后台在你的户头上扣掉相应金额

整个操作是一个顺序,不可分割的整体。上一步完成后才会执行下一步,如果ATM没吐钞却扣了用户的钱,银行可是要关门了。

Redis中的事务

先来看一下事务相关的命令

命令原型 命令描述
MULTI 用于标记事务的开始,其后执行的命令都将被存入命令队列,直到执行EXEC时,这些命令才会被原子的执行。
EXEC 执行在一个事务内命令队列中的所有命令,同时将当前连接的状态恢复为正常状态,即非事务状态。如果在事务中执行了WATCH命令,那么只有当WATCH所监控的Keys没有被修改的前提下,EXEC命令才能执行事务队列中的所有命令,否则EXEC将放弃当前事务中的所有命令。
DISCARD 回滚事务队列中的所有命令,同时再将当前连接的状态恢复为正常状态,即非事务状态。如果WATCH命令被使用,该命令将UNWATCH所有的Keys。
WATCH key [key ...] 在MULTI命令执行之前,可以指定待监控的Keys,然而在执行EXEC之前,如果被监控的Keys发生修改,EXEC将放弃执行该事务队列中的所有命令。
UNWATCH 取消当前事务中指定监控的Keys,如果执行了EXEC或DISCARD命令,则无需再手工执行该命令了,因为在此之后,事务中所有被监控的Keys都将自动取消。

和关系型数据库中的事务相比,在Redis事务中如果有某一条命令执行失败,其后的命令仍然会被继续执行。
我们可以通过MULTI命令开启一个事务,有关系型数据库开发经验的人可以将其理解为BEGIN TRANSACTION语句。在该语句之后执行的命令都将被视为事务之内的操作,最后我们可以通过执行EXEC/DISCARD命令来提交/回滚该事务内的所有操作。这两个Redis命令可被视为等同于关系型数据库中的COMMIT/ROLLBACK语句。
在事务开启之前,如果客户端与服务器之间出现通讯故障并导致网络断开,其后所有待执行的语句都将不会被服务器执行。然而如果网络中断事件是发生在客户端执行EXEC命令之后,那么该事务中的所有命令都会被服务器执行。
当使用Append-Only模式时,Redis会通过调用系统函数write将该事务内的所有写操作在本次调用中全部写入磁盘。然而如果在写入的过程中出现系统崩溃,如电源故障导致的宕机,那么此时也许只有部分数据被写入到磁盘,而另外一部分数据却已经丢失。Redis服务器会在重新启动时执行一系列必要的一致性检测,一旦发现类似问题,就会立即退出并给出相应的错误提示。此时,我们就要充分利用Redis工具包中提供的redis-check-aof工具,该工具可以帮助我们定位到数据不一致的错误,并将已经写入的部分数据进行回滚。修复之后我们就可以再次重新启动Redis服务器了。

样例

@Test
public void test2Trans() { 
  Jedis jedis = new Jedis("localhost"); 
  long start = System.currentTimeMillis(); 
  Transaction tx = jedis.multi(); 
  for (int i = 0; i < 100000; i++) { 
    tx.set("t" + i, "t" + i); 
  } 
  List<Object> results = tx.exec(); 
  long end = System.currentTimeMillis(); 
  System.out.println("Transaction SET: " + ((end - start)/1000.0) + " seconds"); 
  jedis.disconnect(); 
}

得到事务结果result之后,可以检查当中是否有非OK的返回值,如果存在则说明中间执行错误,可以使用DISCARD来回滚执行结果。

WATCH命令

WATCHMULTI执行之前的某个Key提供监控(乐观锁)的功能,如果Key的值变化了,就会放弃事务的执行。
当事务EXEC执行完成之后,就会自动UNWATCH

Session 1 Session 2
(1)第1步
redis 127.0.0.1:6379> get age
"10"
redis 127.0.0.1:6379> watch age
OK
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379>
 
 
(2)第2步
redis 127.0.0.1:6379> set age 30
OK
redis 127.0.0.1:6379> get age
"30"
redis 127.0.0.1:6379>
(3)第3步
redis 127.0.0.1:6379> set age 20
QUEUED
redis 127.0.0.1:6379> exec
(nil)
redis 127.0.0.1:6379> get age
"30"
redis 127.0.0.1:6379>
 

样例

<?php  
header("content-type:text/html;charset=utf-8");  
$redis = new redis();  
$result = $redis->connect('localhost', 6379);  
$mywatchkey = $redis->get("mywatchkey");  
$rob_total = 100;   //抢购数量  
if($mywatchkey<$rob_total){  
    $redis->watch("mywatchkey");  
    $redis->multi();  
       
    //设置延迟,方便测试效果。  
    sleep(5);  
    //插入抢购数据  
    $redis->hSet("mywatchlist","user_id_".mt_rand(1, 9999),time());  
    $redis->set("mywatchkey",$mywatchkey+1);  
    $rob_result = $redis->exec();  
    if($rob_result){  
        $mywatchlist = $redis->hGetAll("mywatchlist");  
        echo "抢购成功!<br/>";  
        echo "剩余数量:".($rob_total-$mywatchkey-1)."<br/>";  
        echo "用户列表:<pre>";  
        var_dump($mywatchlist);  
    }else{  
        echo "手气不好,再抢购!";exit;  
    }  
}  
?>

在上例是一个秒杀的场景,该部分抢购的功能会被并行执行。
通过已销售数量(mywatchkey)的监控,达到了控制库存,避免超卖的作用。
WATCH是一个乐观锁,有利于减少并发中的冲突, 提高吞吐量。

乐观锁与悲观锁

乐观锁(Optimistic Lock)又叫共享锁(S锁),每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。

悲观锁(Pessimistic Lock)又叫排他锁(X锁),每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,都是在做操作之前先上锁。

Lua脚本与事务

Lua 以可嵌入,轻量,高效著称,Redis于2.6版本之后,增加了Lua语言解析模块,可以用于一些简单的事务与逻辑运算。

命令原型 命令描述
EVAL script numkeys key[key ...] arg [arg...] 传入并执行一段Lua脚本,script为脚本内容,numkeys表示传入参数数量,key表示脚本要访问的key,arg为传入参数
EVALSHA sha1 通过SHA1序列调用lua_scripts字典预存的脚本
SCRIPT FLUSH 用于清除服务器中lua有关的脚本,释放lua_scripts字典,关闭现有的lua环境,并重新创建
SCRIPT EXISTS sha1 输入SHA1校验和,判断是否存在
SCRIPT LOAD script 与EVAL相同,创建对应的lua函数,存放到字典中
SCRIPT KILL 杀掉正在执行的脚本。正在执行的脚本会中断并返回错误,脚本中的写操作已被执行则不能杀死,因为违反原子性原则。此时只有手动回滚或shutdown nosave来还原数据

应用原理

客户端将Lua脚本作为命令传给服务端,服务端读取并解析后,执行并返回结果

127.0.0.1:6379> eval 'return redis.call("zrange", "name2", 0 , -1);' 0
1) "1"

Redis启动时会创建一个内建的lua_script哈希表,客户端可以将脚本上传到该表,并得到一个SHA1序列。之后可以通过该序列来调用脚本。(类似存储过程)

redis> SCRIPT LOAD "return 'dlrow olleh'"
"d569c48906b1f4fca0469ba4eee89149b5148092"
 
redis> EVALSHA d569c48906b1f4fca0469ba4eee89149b5148092 0
"dlrow olleh"

约束

Redis会把Lua脚本作为一个整体执行,由于Redis是单线程,因此在脚本执行期间,其他脚本或命令是无法插入执行,这个特性符合事务的原子性。
TIP

  1. 表是Lua中的表达式,与很多流行语言不同。KEYS中的第一个元素是KEYS[1],第二个是KEYS[2](译注:不是0开始)

  2. nil是表的结束符,[1,2,nil,3]将自动变为[1,2],因此在表中不要使用nil。

  3. redis.call会触发Lua中的异常,redis.pcall将自动捕获所有能检测到的错误并以表的形式返回错误内容。

  4. Lua数字都将被转换为整数,发给Redis的小数点会丢失,返回前把它们转换成字符串类型。

  5. 确保在Lua中使用的所有KEY都在KEY表中,否则在将来的Redis版中你的脚本都有不能被很好支持的危险。

  6. 脚本要保持精简,以免阻塞其他客户端操作

一致性

为了保证脚本执行结果的一致性,重复执行同一段脚本,应该得到相同的结果。Redis做了如下约束:

  • Lua没有访问系统时间或者其他内部状态的命令。

  • Lua脚本在解析阶段,如果发现RANDOMKEYSRANDMEMBERTIME这类返回随机性结果的命令,且脚本中有写指令(SET)类,则会返回错误,不允许执行。

  • Lua脚本中调用返回无序元素的命令时,如SMEMBERS,Redis会在后台将命令的结果排序后传回脚本

  • Lua中的伪随机数生成函数math.randommath.randomseed会被替换为Redis内置的函数来执行,以保证脚本执行时的seed值不变。

样例

private static String getSCRIPT() {
        return "local key = KEYS[1]\n" +
                "local localIp = ARGV[1]\n" +
                "\n" +
                "local gateIp = redis.call(\"HGET\", key, \"gateIp\")\n" +
                "if gateIp == localIp then\n" +
                "    redis.call(\"HSET\", key, \"userStatus\", \"false\")\n" +
                "    return 1\n" +
                "else\n" +
                "    return 0\n" +
                "end";
    }
 
@Test
public void testTrans() { 
  ......
  Jedis jedis = new Jedis("localhost"); 
  result = jedis.evalsha(getSCRIPT, keys, args);
  ......
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在Redis中实现事务 的相关文章

  • 如何从父类中获取子类名

    我试图在不需要子类上的函数的情况下完成此任务 这可能吗 我有一种感觉 但我真的很想确定
  • 使用 php-ews(Exchange Web 服务)在特定日期后获取电子邮件

    在我的 PHP 脚本中 我需要弄清楚如何检索指定消息 ID 之后或特定日期之后的所有电子邮件 两者都可以 我只需要检索自上次抓取收件箱以来的新电子邮件 这个收件箱每天收到数千封电子邮件 而且我在 30 天内无法删除任何电子邮件 对于初始导入
  • 为什么我的会话仍然存在?

    我一定很愚蠢 因为似乎一件相当明显的事情现在让我完全困惑 我有一个会议 ie SESSION handbag id 在某个时刻 我需要彻底终止这个会话 ie at the start of the page session start el
  • CodeIgniter 自定义库未加载

    我是 CodeIgniter 的新手 并尝试用它开发一个相当简单的应用程序 只是一个用于处理想要娱乐中心通行证的员工的注册的表单 我正在尝试将事物分开以使它们更清晰 这是代码 应用程序 控制器 reccenter php class Rec
  • PHP 通过 SSL 连接到 MS SQL

    我想要实现的目标非常简单 我想通过安全连接从 PHP 脚本连接到外部 MS SQL 数据库 然而 这已被证明是有问题的 到目前为止 经过三个小时的研究 我不知所措 客户端的平台是Ubuntu 这意味着我无法使用SQLSRV 安全连接已经在不
  • Yii2 - 错误请求 (#400) |前端和后端cookie

    仅当我打开时才会出现此问题frontend and backend在相同的browser 设想 与后端交互 gt 切换选项卡 gt 与前端交互 gt 切换选项卡返回 gt 与后端交互 gt 错误请求 400 Cookie 后端 identi
  • AWS-PHP-SDK / SNS 直接寻址返回错误

    您好 我正在使用 Laravel 4 设置来利用 AWS SNS 向我的 iOS 设备发送推送消息 从 AWS 控制台向我的设备发布命令效果很好 然后我尝试从 PHP sns AWS get sns sns gt publish array
  • 适用于 Linux 的轻量级 IDE [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 您的要求无法解析为 laravel 的一组可安装软件包

    我使用 5 7v Laravel 和 7 2 1v PHP 和 Composer 最新版本 但是当我想创建新项目时出现这些错误 Your requirements could not be resolved to an installabl
  • Xdebug V3 不会停止 VSCode 中的断点

    我正在尝试使用 VSCode 在 XAMPP 上进行调试 但没有成功 我知道有很多关于这个的问题 我已经尽了一切努力 但仍然行不通 我的 xdebug 扩展确实有一件奇怪的事情 我目前使用 PHP v7 4 12 和 Xdebug 版本 3
  • 如何从网站网址中隐藏 .html 扩展名

    我知道这个问题以前曾被问过 但有人知道隐藏 html 扩展名的好方法吗 我已经尝试了许多代码和许多答案https stackoverflow com https stackoverflow com 但我没有看到结果 那是我再问你一次 我有一
  • 如何在 PHP 的 HTML 页面中显示错误消息?

    我有以下登录表单 login php 其中要求输入用户名和密码
  • 分页显示所有其他页面上第 1 页的相同帖子

    我最近在创建即将发生的事件列表时得到了很多帮助 请参阅此处显示即将举行的活动 包括今天的活动 https stackoverflow com questions 17343615 showing upcoming events includ
  • PHP 中标头的使用

    非常简单的问题 这两个 PHP 版本 5 标头调用中哪一个是 最好的 header Not Modified true 304 header HTTP 1 1 304 Not Modified 我很确定第一个是最多价的 但只是好奇如果在 H
  • docker 中的 php Curl 冲突 CURLOPT_FILE 和 CURLOPT_RETURNTRANSFER

    当我使用curl时CURLOPT FILE and CURLOPT RETURNTRANSFER选项 文件为空 没有任何curl错误 fp fopen saveTo w ch curl init fileUrl curl setopt ch
  • Nginx 502 网关错误。通过增加buffer来解决。为什么?

    我正在设置 LEMP 堆栈来运行 Drupal 我安装了 Nginx 和 PHP FastCGI Nginx 工作正常 但任何运行 PHP 的尝试都会出现错误 502 Bad Gateway 谷歌很快发现 nginx 502 错误网关 ht
  • 使用会话 php 创建 cookie?

    我使用会话来登录我网站中的用户 问题是 我想让用户remember密码 因此关闭 打开浏览器后他们不需要再次登录 我需要使用 cookie 和 session 来实现它吗 my code user POST user pass POST p
  • 在本地 SDK 服务器上工作时,实时 Google App Engine 上出现 404

    我已经在GAE标准环境上部署了几个PHP应用程序 一切正常 现在我正在部署一个新应用程序 该应用程序位于由gcloudSDK按预期工作 终端命令 dev appserver py log level warning app yaml 问题是
  • PayPal 网关已拒绝请求。安全标头无效(#10002:安全错误 Magento

    在 magento 中增加 PayPal 预付款 我已填写 magento admin 中的所有凭据 但是当我进入前端并单击 pay pal 按钮时 它给出了 PayPal 网关已拒绝请求 安全标头无效 10002 安全错误 我用谷歌搜索了
  • PHPUnit - 模拟 S3Client 无法正常工作

    库 aws aws sdk php 2 PHP 版本 PHP 5 4 24 cli 作曲家 json require php gt 5 3 1 aws aws sdk php 2 require dev phpunit phpunit 4

随机推荐

  • 【授权mysql某一个用户只读权限以及回滚】

    GRANT SELECT ON FPSSATURN TO dbmonopr02 GRANT SELECT ON 数据库名 TO 用户名 注意 中间有 单引号 你提供的SQL查询是在数据库FPSSATURN中的所有表上授予用户 dbmonop
  • struts property escape 输出 html 标签

    有时 在数据取出一大段文字要输出到页面上 如果有回车符号 在页面会显示不出来 要显示要用到escape参数 escape false
  • Qt边框border概述

    border概述 每个边框有3个方面 样式 或外观 颜色 以及其宽度 粗细 下面我们分别重点解释这三项 边框样式 border style 设置元素所有边框的样式 或者单独地为各边设置边框样式 它有10个属性值 分别是 none 无样式 h
  • 程序员必备:一款知识管理利器+效率工具

    回复 1024 送你一个特别推送 今天给大家推荐一款知识管理利器 其实也是一个不错的效率工具 我自己感觉确实很方便 也不错 所以才推荐给大家的 我比较喜欢这款工具的亮点是 它可以把我们自己记得笔记自动生成思维导图 这款工具是什么呢 它就叫
  • 无监督和有监督算法的区别

    无监督和有监督的理解方法有很多 主要可以从以下几方面来理解 1 无监督与监督学习的区别在于一个无教学值 一个有教学值 但是 个人认为他们的区别在于无监督学习一般是采用聚簇等算法来分类不同样本 而监督学习一般是利用教学值与实际输出值产生的误差
  • 机器学习——贝叶斯网络

    贝叶斯网络 贝叶斯网络 Bayesian Networks 也被称为信念网络 Belif Networks 或者因果网络 Causal Networks 是描述数据变量之间依赖关系的一种图形模式 是一种用来进行推理的模型 贝叶斯网络为人们提
  • JavaScript对象——数学对象

    说到JavaScript对象首先需要说一下内置对象 1 内置对象 内置对象 就是js语言自带的一些对象 这些对象供开发者使用 并提供了一些常用的或是最基本而必要的功能 属性或者方法 内置对象的优点 就是帮助开发者更快的进行开发 2 数学对象
  • 数字信号处理第五次试验:FIR数字滤波器设计与软件实现

    数字信号处理第五次试验 FIR数字滤波器设计与软件实现 前言 一 实验目的 二 实验原理与方法 三 实验环境 四 实验内容及步骤 五 实验结果截图 含分析 六 思考题 前言 为了帮助同学们完成痛苦的实验课程设计 本作者将其作出的实验结果及代
  • win10安装mujoco200,mujoco_py2.0.2.9,gym

    win10安装mujoco200 mujoco py2 0 2 9 gym 最近在学习强化学习 要用到这几个组件和引擎 尝试了很多方法才成功 于是写了两篇win10系统下安装mujoco和gym的总结 本文介绍的是在Win10系统下安装gy
  • 合并两个有序链表

    编程题 合并两个有序链表 保持链表顺序 例如 输入 链表1 1 gt 3 gt 5 gt 7 链表2 2 gt 4 gt 6 gt 8 输出 链表交集 1 gt 2 gt 3 gt 4 gt 5 gt 6 gt 7 gt 8 public
  • 图解RGB565、RGB555、RGB16、RGB24、RGB32、ARGB32等格式的区别

    音视频实践学习 android全平台编译ffmpeg以及x264与fdk aac实践 ubuntu下使用nginx和nginx rtmp module配置直播推流服务器 android全平台编译ffmpeg合并为单个库实践 android
  • 移植5- uboot之tftp启动kernel

    1 在主机上安装tftp server 2 在uboot中使用setenv设置serverip和ipaddr 并保存saveenv tftp mem addr kernel name 2016 7 16 ok210kernel地址是多少 o
  • 二)PyTorch入门基础串讲(二)

    10 PyTorch与线性代数 范数 在泛函分析中 它定义在赋范线性空间中 并满足一定的条件 即 非负性 齐次性 三角不等式 常被用来度量某个向量空间 或矩阵 中的每个向量的长度或大小 零范数 1范数 2范数 欧氏距离 p范数 核范数 to
  • Hive练习题

    文章目录 Hive练习题 题目一 题目二 题目三 Hive练习题 题目一 学生表 STUDENT 的字段含义 SNO 代表学号 SNAME 代表学生姓名 SAGE 代表学生年龄 SSEX 代表学生性别 课程表 COURSE 的字段含义 CN
  • 4Sum

    Given an array S of n integers are there elements a b c and d in S such that a b c d target Find all unique quadruplets
  • 【Python爬虫开发实战①】使用urllib以及XPath爬取可爱小猫图片

    个人主页 为梦而生 关注我一起学习吧 专栏 python网络爬虫从基础到实战 欢迎订阅 后面的内容会越来越有意思 往期推荐 Python爬虫开发基础 urllib库的基本使用 Python爬虫开发基础 XPath库及其基本用法 我们在之前已
  • 策略模式-

    定义 定义一系列的算法 把它们一个个封装起来 目的就是将算法的使用与算法的实现分离开来 从而算法的变化不会影响到使用算法的用户 适用场景 1 假如系统中有很多类 而他们的区别仅仅在于他们的行为不同 2 一个系统需要动态地在几种算法中选择一种
  • python pipline_python中sklearn的pipeline模块实例详解

    最近在看 深度学习 基于Keras的Python实践 魏贞原 这本书 书中8 3创建了一个Scikit Learn的Pipeline 首先标准化数据集 然后创建和评估基线神经网络模型 代码如下 数据正态化 改进算法 steps steps
  • web前端技术笔记(十六)bootstrap、表单正则和前端优化

    bootstrap bootstrap bootstrap 容器 bootstrap 栅格系统 栅格响应式布局案例 列偏移 bootstrap 隐藏类 bootstrap 按钮 bootstrap 表单 bootstrap 导航条 导航条案
  • 如何在Redis中实现事务

    事务介绍 事务 Transaction 是指作为单个逻辑工作单元执行的一系列操作 事务必须满足ACID原则 原子性 一致性 隔离性和持久性 简单来说 事务可能包括1 N条命令 当这些命令被作为事务处理时 将会顺序执行这些命令直到完成 并返回