深入理解Redis事务、事务异常、乐观锁、管道

2024-06-02 00:04

本文主要是介绍深入理解Redis事务、事务异常、乐观锁、管道,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Redis事务与MySQL事务

  • 不一样。
  • 原子性:MySQL有Undo Log机制,支持强原子性,和回滚。Redis只能保证事务内指令可以不被干扰的在同一批次执行,且没有机制保证全部成功则提交,部分失败则回滚。
  • 隔离性:MySQL的隔离性指多个事务可以并发执行,MySQL有MVCC机制。而Redis没有,Redis是事务提交前的指令不会被执行,单线程的环境下,也就不存在事务未提交时,事务内外数据不一致的隔离性问题了。
  • 持久性:MySQL事务先写Undo Log,并有Redo Log的两阶段提交机制,可以保证持久性。但是Redis持久化机制只有RDB和AOF持久化策略,若事务成功执行且数据刚好被保存,则可以满足持久性。
  • 一致性:MySQL是指数据库从一个合法(指符合业务预期)状态转换成另一个合法状态,这种只要Redis执行不出错,可以保证。

Redis事务

  • 官方文档:https://redis.io/docs/latest/develop/interact/transactions/
  • 极简概括:将一批要执行的Redis指令,放入Redis的执行队列中,事务执行时(不包含事务未提交时) 使其不被并发过来的任务干扰执行。(无法做到严格意义上的ACID 4特性)。
  • 适用场景:
    • 性能优化:10条命令传输10次执行10次,与1次批量执行10条命令,性能有差异。
    • 乐观锁实现:结合Watch可以实现乐观锁。
  • 优点:如上的应用场景就是优点。
  • 缺点:无法像MySQL那样保证原子性、持久性。
  • 关键字:mutli(开启事务),discard(停止事务)、exec(执行事务)、watch(监视指定key)、unwatch(取消监视所有key)。

事务操作实操

测试multi与exec,常规执行

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a a
QUEUED
127.0.0.1:6379> set b b
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK

测试discard,事务未提交,强行终止,则修改不会生效

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a a1
QUEUED
127.0.0.1:6379> set b b1
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> get a
"a"
127.0.0.1:6379> get b
"b"

Redis事务异常(语法错误导致整个事务执行失败,非回滚操作)

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a a2
QUEUED
127.0.0.1:6379> sset b b2
(error) ERR unknown command `sset`, with args beginning with: `b`, `b2`, 
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get a
"a"
127.0.0.1:6379> get b
"b"

Redis事务异常(非语法错误引起的部分失败,无法保证ACID中的A,无回滚机制)

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a aa
QUEUED
127.0.0.1:6379> incr a
QUEUED
127.0.0.1:6379> set b bb
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
127.0.0.1:6379> get a
"aa"
127.0.0.1:6379> get b
"bb"

有Redis事务,为什么又出来了Lua?

  • Redis事务和Lua机制并不冲突,并且要比Redis事务更加强大。
  • 应对并发安全问题:虽然有了Lua的加持,仍不支持事务回滚或者,强原子性(要么都成功,要么都回滚),但是Lua可以保证当前的操作不被打断(无间隙执行),应对并发(例如超卖)问题,Lua能妥善解决。
  • Redis事务不支持流程控制,只支持函数调用:配合Lua用于实现无间隙执行的复杂逻辑,这样的用法非常多。因为高并发下,若单纯利用编程语言多次调Redis,实现判断或循环逻辑,这中间有间隙,会有并发问题发生。
    Lua是一门高性能脚本语言,Lua由标准C编写而成,几乎在所有操作系统和平台上都可以编译、运行。Lua脚本可以很容易的被C/C++代码调用,也可以反过来调用C/C++的函数,这使得Lua在应用程序中可以被广泛应用。

关于Redis+Lua是否是原子性执行的争议问题

https://redis.io/docs/latest/develop/interact/programmability/eval-intro/
对Redis官网进行搜索,出现了原子性的字眼。
原话是:
Blocking semantics that ensure the script’s atomic execution.
Lua lets you run part of your application logic inside Redis. Such scripts can perform conditional updates across multiple keys, possibly combining several different data types atomically.

但是我想了想有矛盾的地方:
MySQL使用了undo log来保证原子性,要么成功全部执行,要么失败全部回滚。
众所周知,Redis不支持回滚的,那么ACID的A就没办法全部保证,最多是没有执行期间没有间隙,不被其它过来的请求影响,引起并发问题。

然后我又看了看阿里某架构师对此的剖析,跟我设想的一样:
Redis会把Lua脚本当做一个整体去执行,中间不会被其它的命令插入,但是如果执行过程中出现了错误,事务是不会回滚的。
也就意味着执行Lua脚本的过程不可被拆分,不可被中断,但是遇到错误不会回滚。

Redis乐观锁

  • 悲观锁:很悲观,认为数据大概率会有并发一致性问题,首次请求过来时加具有互斥性的锁阻塞其它并发请求,但是Redis是高性能组件,阻塞会带来性能问题,所以不用悲观锁。
  • 乐观锁:乐观,认为数据小概率有并发一致性问题,所以读数据时不上锁,但是写数据时,会判断一下这个数据是否被改动,从而在旧值的基础上做修改,如果数据被改动,则失败掉此次执行。
  • 注意:redis在事务exec或者discard,都会取消对key的watch操作。
  • 解决问题:高并发读多写少场景下Redis数据一致性问题。
  • 演变:

假设用户a账户有100元,此时要添加10元

127.0.0.1:6379> set a_money 100
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incrby a_money 10
QUEUED
127.0.0.1:6379> exec
1) (integer) 110
127.0.0.1:6379> get a_money
"110"

假设用户a账户有110元,此时要添加20元,但是事务未提交期间,已经被其它请求改为了115,然后事务内加了20。
由于是加法,所以值正确,但是事务内的数据一般是不让改的,很多情况下的自增或者自减,是需要以原数据为基础基础为准的(这也是MySQL隔离级别的用意,所以有了当前读和快照读的区分)。

终端1
127.0.0.1:6379> get a_money
"110"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incrby a_money 20
QUEUED终端2
127.0.0.1:6379> get a_money
"110"
127.0.0.1:6379> incrby a_money 5
(integer) 115终端1
127.0.0.1:6379> get a_money
"110"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incrby a_money 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 135
127.0.0.1:6379> get a_money
"135"

Redis没有事务的隔离机制怎么办?使用watch加锁。

终端一
127.0.0.1:6379> watch a_money
OK
127.0.0.1:6379> get a_money
"135"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incrby a_money 20
QUEUED终端二模拟其它并发用户
127.0.0.1:6379> incrby a_money 5
(integer) 140终端1
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get a_money
"140"
事务没有成功被执行,因为watch监控了a_money的值,一旦事务执行期间,被事务外的请求锁修改,则失败掉此次事务。
乐观锁,在此处的体现就是,利用watch监控一下事务执行期间,a_money的值是否被改动。

unwatch 使用

终端1
127.0.0.1:6379> set a a
OK
127.0.0.1:6379> watch a
OK
127.0.0.1:6379> unwatch
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a a1终端2,模拟并发过来的用户请求
127.0.0.1:6379> set a a2
OK终端1,执行unwatch后,取消了对所有key的监控,执行exec时,就不是nil了。
127.0.0.1:6379> exec
1) OK
127.0.0.1:6379> get a
"a1

watch部分key,其余key的反应

终端1
127.0.0.1:6379> set a a
OK
127.0.0.1:6379> set b b
OK
127.0.0.1:6379> watch a
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a a1
QUEUED
127.0.0.1:6379> set b b1
QUEUED终端2
127.0.0.1:6379> set a a2
OK
127.0.0.1:6379> set b b2
OK终端1,watch a,没有watch b,事务提交时,被watch的key,可以影响没有被watch的key。
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get a
"a2"
127.0.0.1:6379> get b
"b2"

管道

  • 官方文档:https://redis.io/docs/latest/develop/use/pipelining/
  • 极简概括:将多个指令的操作,一次性发送给Redis,进行批量处理。
  • 解决问题:减少网络开销,减少频繁接收命令的开销(10轮request->exec->response,精简为1次request->10次exec->1次response),避免多条Redis指令通信往返时间。避免Redis服务器频繁的从用户态到内核态的调用,减少上下文通信时间。
  • 与事务对比:批量处理指令的行为,类似事务。
  • 注意:redis-cli会话内部并未提供管道命令,(但是使用Linux Shell端支持STDIN标准输入到redis-cli实现管道,例如echo -e "set a aa \n set b bb" | redis-cli --pipe),但redis-server提供了这个机制,管道机制最好用编程语言的客户端演示。
若在redis-cli会话内部实现管道,会有如下提示:
127.0.0.1:6379> pipe
(error) ERR unknown command `pipe`, with args beginning with: 
127.0.0.1:6379> pipeline
(error) ERR unknown command `pipeline`, with args beginning with:
  • PHP实现:
<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);$pipe = $redis->pipeline();$pipe->set('key1', 'value1');
$pipe->set('key2', 'value2');
$pipe->get('key1');
$pipe->get('key2');$responses = $pipe->exec();var_dump($responses);$redis->close();返回执行的结果
array(4) {[0]=>bool(true)[1]=>bool(true)[2]=>string(6) "value1"[3]=>string(6) "value2"
}

管道异常情况(Redis语法错误)

以PHP为例,经实际测试(set函数缺少参数2),Redis调用语法错误(非PHP语法错误),会升级为PHP出现致命错误,管道流程走不下去。

Fatal error: Uncaught ArgumentCountError: Redis::set() expects at least 2 arguments, 1 given in E:\Host\test\t1.php:7
Stack trace:
#0 E:\Host\test\t1.php(7): Redis->set('a')
#1 {main}thrown in E:\Host\test\t1.php on line 7

管道异常情况(Redis执行异常)

经过实测,对字符串进行递增操作,除了incr返回false外,其余上下文代码执行不受影响。

<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);$pipe = $redis->pipeline();$pipe->set('a', 'a');
$pipe->incr('a');
$pipe->set('b', 'b');
$pipe->get('a');
$pipe->get('b');$responses = $pipe->exec();var_dump($responses);$redis->close();array(5) {     [0]=>        bool(true)[1]=>bool(false)[2]=>bool(true)[3]=>string(1) "a"[4]=>string(1) "b"
}

这篇关于深入理解Redis事务、事务异常、乐观锁、管道的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1022476

相关文章

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

深入手撕链表

链表 分类概念单链表增尾插头插插入 删尾删头删删除 查完整实现带头不带头 双向链表初始化增尾插头插插入 删查完整代码 数组 分类 #mermaid-svg-qKD178fTiiaYeKjl {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-

深入理解RxJava:响应式编程的现代方式

在当今的软件开发世界中,异步编程和事件驱动的架构变得越来越重要。RxJava,作为响应式编程(Reactive Programming)的一个流行库,为Java和Android开发者提供了一种强大的方式来处理异步任务和事件流。本文将深入探讨RxJava的核心概念、优势以及如何在实际项目中应用它。 文章目录 💯 什么是RxJava?💯 响应式编程的优势💯 RxJava的核心概念

如何通俗理解注意力机制?

1、注意力机制(Attention Mechanism)是机器学习和深度学习中一种模拟人类注意力的方法,用于提高模型在处理大量信息时的效率和效果。通俗地理解,它就像是在一堆信息中找到最重要的部分,把注意力集中在这些关键点上,从而更好地完成任务。以下是几个简单的比喻来帮助理解注意力机制: 2、寻找重点:想象一下,你在阅读一篇文章的时候,有些段落特别重要,你会特别注意这些段落,反复阅读,而对其他部分