幂等性设计,及案例分析

一、redis锁处理幂等性失效

在这里插入图片描述

上面代码中,锁起不了作用;
——count方法,和insert方法在同一事务中,事务中包含锁,锁没有作用,锁的范围内,事务没提交,但释放锁后,事务提交前,被另一线程抢了执行权后,因为事务还没提交,另一线程拿到的count还是0。

以上代码问题:

  1. 对事物的理解使用有问题,幂等设计bug;
  2. redis锁使用有问题(单独案例讲述);

mysql默认事务级别——可重复读;
锁加错位置了,锁应该加在这个事务方法的外面;
正例:
在这里插入图片描述

stop the world:
学会用stop the world注释代码。

1.1 扩展:

事务在生产实践中经常犯的错误:

  • 事务范围:应该加入事务的代码未加入到事务中

1.1.1 图是另一个真实生产当中的事故-仅供参考:

在这里插入图片描述

  IdGenerator 是一个生成唯一标识符的工具类。它通常用于生成数据库表中的主键值,例如AUTO_INCREMENT 字段。

  • 事务大小:事务过大,是否有必要拆解小事务(如何优化),拆解后一致性问题。

传播范围(异常标注):

  • 多线程中不可传播;
  • 多个方法内如果异常被捕获将要被标记为异常事务,不可以再次提交(虽然不影响数据,但是有报错信息);

二、Transaction rolled back bacause it has been marked as rollback-only问题原因复盘

2.1 复盘

在这里插入图片描述

在这里插入图片描述

错误原因:

提交了一个被标记为异常的事务,会报这个错。

解决方法:

  • a处try-catch代码去掉;
  • 或者,b处@Transactional注解去掉;

无论是哪种解决方法,具体看业务。

三、mysql死锁场景

  • 问题1:jvm如果死锁了,java进程还在吗?——一直锁着。
  • 问题2:mysql如果死锁了,其他连接还能正常运行吗?——死锁一段时间后会自动释放,可配置;

3.1 mysql死锁复盘

在这里插入图片描述
在 MySQL 中,FOR UPDATE 子句用于在读取数据时锁定该记录,以防止其他事务同时更新或删除该记录。当多个事务试图同时锁定同一记录时,可能会导致死锁。
下面是一个可能导致死锁的场景:

假设有两个事务 T1 和 T2,它们都试图更新同一行数据。

  • 事务 T1 执行以下操作:
    • 读取一行数据并加上 FOR UPDATE 锁。
    • 等待一段时间(例如,进行一些计算或等待其他资源)。
  • 事务 T2 执行以下操作:
    • 读取同一行数据并加上 FOR UPDATE 锁。
    • 试图更新该记录,但由于 T1 已经锁定了该记录,因此事务 T2 被阻塞。 现在,事务 T1 等待一>段时间后准备更新记录,但由于事务 T2 已经锁定了该记录,因此事务 T1 也被阻塞。

这就形成了一个死锁,因为两个事务都在等待对方释放锁,而它们都无法继续执行下去。

为了避免死锁,可以采取以下措施:

  • 尽量减少锁定的时间,以避免其他事务长时间等待。
  • 按照相同的顺序访问数据,以避免冲突。

mysql死锁时间长好还是短好?
——短的话,不好控制长事务;长的话,发生死锁时,时间等待太久;

四、幂等性设计方法

4.1 幂等性设计:

  1. 有时我们在填写某些 form表单 时,保存按钮不小心快速点了两次,表中竟然产生了两条重复的数据,只是id不一样;
  2. 我们在项目中为了解决 接口超时 问题,通常会引入了 重试机制 。第一次请求接口超时了,请求方没能及时获取返回结果(此时有可能已经成功了),为了避免返回错误的结果(这种情况不可能直接返回失败吧?),于是会对该请求重试几次,这样也会产生重复的数据;
  3. mq消费者在读取消息时,有时候会读取到 重复消息 ,如果处理不好,也会产生重复的数据;

这些都是幂等性问题。

接口幂等性: 是指用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。

这类问题多发于接口的:

  • insert 操作,这种情况下多次请求,可能会产生重复数据;
  • update 操作,如果只是单纯的更新数据,比如: update user set status=1 where id=1 ,是没有问题的。如果还有计算,比如: update user set status=status+1where id=1 ,这种情况下多次请求,可能会导致数据错误;

那么我们要如何保证接口幂等性?请往下看。

4.1.1 insert前先select

 通常情况下,在保存数据的接口中,我们为了防止产生重复数据,一般会在 insert 前,先根据 name 或 code 字段 select 一下数据。如果该数据已存在,则执行 update 操作,如果不存在,才执行 insert 操作。
在这里插入图片描述
 该方案可能是我们平时在防止产生重复数据时,使用最多的方案。但是该方案不适用于并发场景,在并发场景中,要配合其他方案一起使用,否则同样会产生重复数据。

4.1.2 加悲观锁

4.1.2.1 支付场景

 支付场景在加减库存场景中,用户A的账号余额有150元,想转出100元,正常情况下用户A的余额只剩50元。一般情况下,sql是这样的:

update user amount = amount-100 where id=123;

 如果出现多次相同的请求,可能会导致用户A的余额变成负数。这种情况,用户A来可能要哭了。于此同时,系统开发人员可能也要哭了,因为这是很严重的系统bug。
 为了解决这个问题,可以加悲观锁,将用户A的那行数据锁住,在同一时刻只允许一个请求获得
锁,更新数据,其他的请求则等待。

通常情况下通过如下sql锁住单行数据:

select * from user id=123 for update;

条件:数据库引擎为innoDB

操作位于事务中
具体流程如下:
在这里插入图片描述
具体步骤:

  1. 多个请求同时根据id查询用户信息。
  2. 判断余额是否不足100,如果余额不足,则直接返回余额不足。
  3. 如果余额充足,则通过for update再次查询用户信息,并且尝试获取锁。
  4. 只有第一个请求能获取到行锁,其余没有获取锁的请求,则等待下一次获取锁的机会。
  5. 第一个请求获取到锁之后,判断余额是否不足100,如果余额足够,则进行update操作。
  6. 如果余额不足,说明是重复请求,则直接返回成功。
4.1.2.1 操作库场景
select* from stock_info where goods_id=12312 and storage_id=1 for update;

具体流程:

a:单件货品操作流程:

在这里插入图片描述

b:(同一个goodsId)多个单件货品,批量操作出库流程:

在这里插入图片描述
具体步骤:

  1. 多个请求同时根据goodsId和storageId操作货品的上下架,或者其他渠道订单批量下架操作;
  2. 判断当前货品是否有仓库货品;
  3. 如果货品库存充足,则通过for update再次查询货品库存信息,并且尝试获取锁;
  4. 只有第一个请求能获取到行锁,其余没有获取锁的请求,则等待下一次获取锁的机会;
  5. 第一个请求获取到锁之后,进行货品单件明细状态变更,成功后操作,则进行update操作加减库存;
  6. 如果库存不足或者单件不满足操作,则直接返回成功或者幂等状态。

 需要特别注意的是:如果使用的是mysql数据库,存储引擎必须用innodb,因为它才支持事 务。此外,这里id字段一定要是主键或者唯一索引,不然会锁住整张表。

 悲观锁需要在同一个事务操作过程中锁住一行数据,如果事务耗时比较长,会造成大量的请求等待,影响接口性能。此外,每次请求接口很难保证都有相同的返回值,所以不适合幂等性设计场景,但是在防重场景中是可以的使用的。在这里顺便说一下, 防重设计幂等设计 ,其实是有区别的。防重设计主要为了避免产生重复数据,对接口返回没有太多要求。而幂等设计除了避免产生重复数据之外,还要求每次请求都返回一样的结果。

4.1.3 加乐观锁

 既然悲观锁有性能问题,为了提升接口性能,我们可以使用乐观锁。需要在表中增加一个timestamp 或者 version 字段,这里以 version 字段为例。
在更新数据之前先查询一下数据:

select id,amount,version from user id=123;

中间就省略了,相信大家也知道。直接贴出sql中的乐观锁代码了:

update user set amount=amount+100,version=version+1where id=123 and version=1;

需要注意的是,如果影响行数为0:

 该 update 操作不会真正更新数据,最终sql的执行结果影响行数是 0 ,因为 version 已经变成 2了, where
中的 version=1 肯定无法满足条件。但为了保证接口幂等性,接口可以直接返回成功,因为 version
值已经修改了,那么前面必定已经成功过一次,后面都是重复的请求。

在这里插入图片描述
具体步骤:

  1. 先根据id查询用户信息,包含version字段;
  2. 根据id和version字段值作为where条件的参数,更新用户信息,同时version+1;
  3. 判断操作影响行数,如果影响1行,则说明是一次请求,可以做其他数据操作;
  4. 如果影响0行,说明是重复请求,则直接返回成功;

4.1.4 加唯一索引

 常规的创建唯一索引,和唯一联合索引的思路就不写了。

4.1.4.1 软删除可能引发的问题:

 在很多业务场景中,都使用“软删除”即使用flag或is_deleted等字段表示记录是否被删除,这种方式能很好地保存“历史记录”,但由于”历史记录”的存在,导致无法在表上建立唯一索引,需要通过程序来控制”数据唯一性”,其中一种程序实现逻辑就是“先尝试更新,更新失败则插入”,该方式在高并发下死锁频发。(select for update ;为什么?你能复现么?如何避免?)

 尽管可以通过程序来控制”数据唯一性”,但仍建议使用数据库级别的唯一约束来确保数据在表级别的”唯一”,对于”硬删除”方式,直接在唯一索引列上建立为唯一索引即可,对于”软删除”方式,可以通过 复合索引 方式来处理。

 假设当前有订单相关的表tb_order_worker,表中有order_id字段需要唯一约束,使用is_delete字段来标识记录是否被”软删除”,is_delete=1时表示记录被删除,is_delete=0时表示记录未被删除,需要控制满足is_delete=0时的记录中order_id唯一,如果对(order_id,is_delete)的建唯一索引,那么当同一订单被多次”软删除”时就会出现唯一索引冲突的问题。

解决方式一:

 提升is_delete列的取值范围,当is_delete=0时表示记录有效,当is_delete>0时表示记录被删除,在删除记录时将is_delete值设置为不同数值,只要确保相同order_id的记录使用不同数值即可(很多表都使用自增主键,可以取自增主键的值来作为is_delete值)。

解决方式二:

 新增列order_rid来保持方式一中is_delete的原有取值范围,当is_delete时设置order_rid=0,当is_delete=1时设置order_rid为任意非0值,只要确保相同order_id的记录使用不同值即可(同样建议参照自增主键值来设置),然后对(order_id,yn,order_rid)建唯一索引。

4.1.4.2 唯一索引和普通索引的区别?
4.1.4.2.1 查询
select * from t_user where id_card =1000;
  • 对于普通索引来说,查找到满足条件的第一个记录(1,1000)后,需要查找下一个记录,直到碰到第一个不满足id_card=1000条件的 记录;
  • 对于唯一索引来说,由于索引定义了唯一性,查找到第一个满足条件的记录后,就会停止继续检索

 性能差距微乎其微,因为mysql 数据是按照数据页为单位的,也就是说,当读取一条数据的时候,会将当前数据所在页都读入到内存,普通索引无非多了一次判断是否等于 的操作,相当于指针的寻找和一次计算,当然,如果该页码上,id_card=1000是最后一个数据,那么就需要取下一个页了,但是这种概率并不大。

 总结说,查询上,普通索引和唯一索引性能是没什么差异的

4.1.4.2.2 更新

 当需要更新一个数据页时,如果数据页在内存中就直接更新,而如果这个数据页还没有在内存中的话,在不影响数据一致 性的前提下,InooDB会将这些更新操作缓存在change buffer中,这样就不需要从磁盘中读入这个数据页了。在下次查询 需要访问这个数据页的时候,将数据页读入内存,然后执行change buffer中与这个页有关的操作。通过这种方式就能保证 这个数据逻辑的正确性。

这个change buffer通常被称为InnoDB的写缓冲?

 在MySQL5.5之前,叫插入缓冲(insert buffer),只针对insert做了优化;现在对delete和update也有效,叫做写缓冲(change buffer)。 它是一种应用在非唯一普通索引页(non-unique secondary index page)不在缓冲池中,对页进 行了写操作,并不会立刻将磁盘页加载到缓冲池,而仅仅记录缓冲变更(buffer changes),等未来数据被读取时,再将数据合并(merge)恢复到缓冲池中的技术。

写缓冲的目的是降低写操作的磁盘IO,提升数据库性能:

  对于唯一索引来说,所有的更新操作都要先判断这个操作是否违反唯一性约束。比如,要插入 (1,1000)这个记录,就要先判 断现在表中是否已经存在id_card=1000的记录,而这必须要将数据 页读入内存才能判断。如果都已经读入到内存了,那直接更新内存会更快,就没必要使用change buffer了。 因此,唯一索引的更新就不能使用change buffer,实际上也只有普通索引可以使用。

接着分析InnoDB更新流程:
处理流程如下:

  • 对于唯一索引来说,找到999和1001之间的位置,判断到没有冲突,插入这个值,语句执行结 束;
  • 对于普通索引来说,找到999和1001之间的位置,插入这个值,语句执行结束。

这样看来,普通索引和唯一索引对更新语句性能影响的差别,只是一个判断,只会耗费微小的 CPU时间。

真正影响性能的是第二种情况是,这个记录要更新的目标页不在内存中。处理流程如下:

  • 对于唯一索引来说,需要将数据页读入内存,判断到没有冲突,插入这个值,语句执行结束;
  • 对于普通索引来说,则是将更新记录在change buffer,语句执行就结束了。
4.1.4.2.3 总结

 将数据从磁盘读入内存涉及随机IO的访问,是数据库里面成本最高的操作之一。change buffer因为减少了随机磁盘访问, 所以对更新性能的提升是会很明显的。

 因此,对于写多读少的业务来说,页面在写完以后马上被访问到的概率比较小,此时change buffer的使用效果最好。

 这种 业务模型常见的就是账单类、日志类的系统。

 反过来,假设一个业务的更新模式是写入之后马上会做查询,那么即使满足了条件,将更新先记录在change buffer,但之 后由于马上要访问这个数据页,会立即触发merge过程。这样随机访问IO的次数不会减少,反而增加了change buffer的维 护代价。所以,对于这种业务模式来说,change buffer反而起到了副作用。

 redo log主要节省的是随机写磁盘的IO消耗(转成 顺序写),而change buffer主要节省的则是随机读磁盘的IO消耗。

4.1.4.2.4 Change buffer为什么只对非唯一普通索引页有效
  • 主键索引,唯一索引
    实际上对于【唯一索引】的更新,插入操作都会先判断当前操作是否违反唯一性约束,而这个操作就必须要将索引页读取到内存中,此时既然已经读取到内存了,那直接更新即可,没有需要在用Change buffer了。

  • 非唯一普通索引
    不需要判断当前操作是否违反唯一性约束,也就不需要将数据页读取到内存,因此可以直接使用 change buffer 更新。

基于此,Change buffer只有对普通索引可以使用,对唯一索引的更新无法生效

change buffer参考文章:MySQL十七:Change Buffer

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/177409.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

unity 点击3D物体

1. 在场景中添加事件系统 2. 为主相机添加射线检测 3. 为物体挂载以下脚本,物体必须带碰撞体 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems;// 挂在物体上,需要添加碰撞体 public …

Qt中实现页面切换的两种方式

文章目录 方式一 :使用QStackedWidget讲解代码结构main.cpp完整代码运行结果: 方式二 :代码结构完整代码mainwindow.hnewmainwindow.hmain.cppmainwindow.cppnewmainwindow.cppmainwindow.uinewmainwindow.ui 效果 方式一 :使用QS…

mathematica解非齐次常微分方程通用写法。解RC微分方程,输入硬写为Cos,通用写法:将微分方程的解函数表达式转为mathematica的纯函数

输入电压为余弦信号, mathematica解微分方程举例(mathematica解非齐次常微分方程通用写法)

天津重点大数据培训 大数据培训的三个重要内容

随着互联网的发展和技术的进步,大数据的应用范围越来越广泛,对于企业和个人来说,学习和掌握大数据技术已经成为了必不可少的一项能力。大数据技术是当前和未来的发展方向,对于想进入互联网行业或从事相关技术工作的人来说&#xf…

android下的app性能测试应主要针对那些方面,如何开展?

如何开展安卓手机下的App性能测试,对于优秀的测试人员而言,除了要懂得性能测试的步骤流程外,还应该懂的性能测试的一些其他知识,比如性能测试指标、各指标的意义,常用的性能测试工具、如何查看结果分析等等知识。所以本…

ubuntu配置 Conda 更改默认环境路径

我的需求是以后凡是新建一个虚拟环境都需要安装在一个挂载了大容量的分区/data里面 /home里面的是即将爆满但是还能塞点东西的硬盘. 如果您想要永久更改 Conda 的默认环境路径,可以编辑 Conda 的配置文件。首先,找到 Conda 的配置文件通常是 .condarc 文…

idea插件(一)-- SequenceDiagram(UML自动生成工具)

目录 1. 安装 2. 默认快捷键 3. 操作说明 4. 导出为图片与UML类图 4.1 导出为图片: 4.2 导出 UML 类图 SequenceDiagram是从java、kotlin、scala(Beta)和groovy(limited)代码生成简单序列图(UML&…

06 MIT线性代数-线性无关,基和维数Independence, basis, and dimension

1. 线性无关 Independence Suppose A is m by n with m<n (more unknowns than equations) Then there are nonzero solutions to Ax0 Reason: there will be free variables! A中具有至少一个自由变量&#xff0c;那么Ax0一定具有非零解。A的列向量可以线性组合得到零向…

【MySQL数据库】 二

本文主要介绍了数据库操作的操作流程 , 以及数据库和数据表的基本操作 . 一.数据库操作工作流程 1.用户在客户端输入SQL 2.客户端把SQL通过网络发送给服务器 3.服务器会执行这个SQL&#xff0c;把结果返回给客户端 4.客户端收到结果显示到界面上 二.数据库的操作 1.创建数…

【c++|opencv】二、灰度变换和空间滤波---3.均值滤波

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 均值滤波 1. 均值滤波 #include <iostream> #include <opencv2/opencv.hpp> #include"Salt.h"using namespace cv; using names…

计算机毕业设计选题推荐-校园失物招领微信小程序/安卓APP-项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

Flutter 01 目录结构入门

一、Flutter目录结构&#xff1a; 二、Flutter入口文件、入口方法&#xff1a; 三、Flutter Demo&#xff1a; demo1&#xff1a; import package:flutter/material.dart;//MaterialApp 和 Scaffold两个组件装饰App void main() {runApp(MaterialApp(home: Scaffold(appBar: A…

VSCode 设置平滑光标

1.点击左下角的设置按钮&#xff0c;再点击设置 2.点击文本编辑器&#xff0c;点击光标&#xff0c;勾选控制是否启用平滑插入动画。 3.随便打开一个文件&#xff0c;上下左右移动光标时&#xff0c;会发现非常的流畅。 原创作者&#xff1a;吴小糖 创作时间&#xff1a;2023…

SOLIDWORKS® 2024 新功能 - PDM

SOLIDWORKS 2024 新功能 - PDM 1、改进的视觉内容 • 通过装配体可视化功能&#xff0c;在 SOLIDWORKS 中以图形方式查看零部件数据&#xff0c;如工作流程状态。 • 使用特定图标迅速识别焊件切割清单零部件。 优点 重要数据和系统信息一目了然。 2、增强的数据保护和跟踪功…

Elasticsearch下载安装,IK分词器、Kibana下载安装使用,elasticsearch使用演示

首先给出自己使用版本的网盘链接&#xff1a;自己的版本7.17.14 链接&#xff1a;https://pan.baidu.com/s/1FSlI9jNf1KRP-OmZlCkEZw 提取码&#xff1a;1234 一般情况下 Elastic Search&#xff08;ES&#xff09; 并不单独使用&#xff0c;例如主流的技术组合 ELK&#xff08…

计算机视觉注意力机制小盘一波 (学习笔记)

将注意力的阶段大改分成了4个阶段 1.将深度神经网络与注意力机制相结合&#xff0c;代表性方法为RAM ⒉.明确预测判别性输入特征&#xff0c;代表性方法为STN 3.隐性且自适应地预测潜在的关键特征&#xff0c;代表方法为SENet 4.自注意力机制 通道注意力 在深度神经网络中…

第 04 章_逻辑架构

第 04 章_逻辑架构 1. 逻辑架构剖析 1. 1 服务器处理客户端请求 那服务器进程对客户端进程发送的请求做了什么处理&#xff0c;才能产生最后的处理结果呢&#xff1f;这里以查询请求为 例展示&#xff1a; 下面具体展开看一下&#xff1a; 1.2 Connectors 1.3 第 1 层&…

Oracle JDK 和OpenJDK两者有什么异同点

Oracle JDK 和 OpenJDK 是两种不同版本的 Java Development Kit&#xff08;Java 开发工具包&#xff09;&#xff0c;它们都提供了用于开发 Java 程序的一系列工具和库。以下是它们之间的一些主要异同点&#xff1a; 相同点&#xff1a; 功能&#xff1a;在大多数情况下&…

AD9371 官方例程裸机SW概述(一)

AD9371 系列快速入口 AD9371ZCU102 移植到 ZCU106 &#xff1a; AD9371 官方例程构建及单音信号收发 ad9371_tx_jesd -->util_ad9371_xcvr接口映射&#xff1a; AD9371 官方例程之 tx_jesd 与 xcvr接口映射 AD9371 官方例程 时钟间的关系与生成 &#xff1a; AD9371 官方…

Qt 中model/View 架构 详解,以及案例实现相薄功能

model/View 架构 导读 ​ 我们的系统需要显示大量数据,比如从数据库中读取数据,以自己的方式显示在自己的应用程序的界面中。早期的 Qt 要实现这个功能,需要定义一个组件,在这个组件中保存一个数据对象,比如一个列表。我们对这个列表进行查找、插入等的操作,或者把修改…