幂等性解决方案

1、幂等性

在计算机中,表示对【同一个过程】应用【相同的参数】多次和应用一次产生的效果是一样,这样的过程即被称为满足幂等性。

幂等:

update user set age = 25 where user_id=2

这中情况无论执行多少次,结果都不受影响,所以是幂等的。

非幂等:

update user set age= age + 1 where user_id = 2

,这样的更新语句每执行一次,结果都会不一样,所以是非幂等的。

2、幂等的应用场景

2.1 网络波动引起的重试

不同微服务、中间件间基于http、rpc或mq进行网络通信,当出现网络波动时(规定时间内客户端未收到服务端响应),客户端会进行重试。

比如http客户端设置200ms超时时间,但因为网络问题,201ms服务端才收到请求。此时客户端误认为服务端丢失请求,重试发送第二次,服务端此时会受到两条一样的请求。

2.2 用户重复操作

  • 同一时刻多次点击按钮,发送多次请求
  • 页面后退后重新进入,发送两次请求

3、需要关注幂等性的接口

通常只需要对计算式写请求(新增 &更新)作幂等性保证。
计算式:需要进行变化的

3.1 查询 GET

SELECT * FROM users WHERE xxx;

不会对数据产生任何变化,天然具备幂等性。

3.2 新增 PUT

INSERT INTO users (user_id, name) VALUES (1, 'zhangsan');

case1:带有业务上的唯一索引(如:user_id),重复插入会导致后续执行失败,具有幂等性;
case2:不带有业务上的唯一索引,多次插入会导致数据重复,不具有幂等性。

3.3 修改 POST

case1:直接赋值,不管执行多少次 score 都一样,具备幂等性。

UPDATE users SET score = 30 WHERE user_id = 1;

case2:计算赋值,每次操作 score 数据都不一样,不具备幂等性。

UPDATE users SET score = score + 30 WHERE user_id = 1;

3.4 删除 DELETE

case1:绝对值删除,重复多次结果一样,具备幂等性。

DELETE FROM users WHERE id = 1;

case2:相对值删除,重复多次结果不一致,不具备幂等性。

DELETE top(3) FROM users;

4、如何解决幂等性问题

4.1 从源头上控制重复请求

控制动作触发源头,即前端做幂等性控制实现

主要解决方案:

  • 控制操作次数:提交按钮仅可操作一次(提交动作后按钮置灰)
  • 及时重定向:下单/支付成功后跳转到成功提示页面,消除浏览器前进或后退造成的重复提交问题。

缺点:
只能算是解决问题的辅助方法,用户可以直接绕过前端发送请求

4.2 过滤重复动作

控制过滤重复动作,是指在动作流转过程中控制有效请求数量。

  • 分布式锁
    利用 Redis 记录当前处理的业务标识,当检测到没有此任务在处理中,就进入处理,否则判为重复请求,可做过滤处理。

订单发起支付请求,支付系统会去 Redis 缓存中查询是否存在该订单号的 Key,如果不存在,则向 Redis 增加 Key 为订单号。查询订单支付已经支付,如果没有则进行支付,支付完成后删除该订单号的 Key。通过 Redis 做到了分布式锁,只有这次订单订单支付请求完成,下次请求才能进来。

分布式锁相比去重表,将放并发做到了缓存中,较为高效。思路相同,同一时间只能完成一次支付请求。

  • 先读后写
    业务上过滤掉已经成功执行的操作

给用户发红包时先查询当前用户是否发过红包,发过则过滤

缺点:
可能会出现并发问题。比如两个线程都执行抢优惠券操作

select count from coupon;
update coupon set count=count-1;

两个用户查询后都是1,两次更新后库存为-1,出现超卖问题
一般用于并发量小的后台接口或者对并发安全问题不在意的场景下,比如后台需要封禁用户。封禁前先查询是否已被封禁,已被封禁则过滤。即使出现查询时未被封禁,执行时发现用户封禁表已经存在,只需捕获唯一索引异常即可。

相较分布式锁方案来讲,实现简单,不需要关注redis

4.3 解决重复写

常见的方式有:悲观锁(for update)、乐观锁、唯一约束。
按照应用上的最优收益,推荐排序为:乐观锁 > 唯一约束 > 悲观锁。

  • 悲观锁(Pessimistic Lock)

假设每一次拿数据,都有认为会被修改,所以给数据库的行或表上锁。
当数据库执行 select for update 时会获取被 select 中的数据行的行锁(排他锁),因此其他并发执行的 select for update 如果试图选中同一行则会发生排斥(需要等待行锁被释放),因此达到锁的效果。

select for update 获取的行锁会在当前事务结束时自动释放,因此必须在事务中使用。(注意 for update 要用在索引上,不然会锁表)

START TRANSACTION; # 开启事务
SELETE * FROM users WHERE id=1 FOR UPDATE;
UPDATE users SET name= 'xiaoming' WHERE id = 1;
COMMIT; # 提交事务
  • 乐观锁(Optimistic Lock)
    每次去拿数据的时候都认为别人不会修改。更新时如果 version 变化了,更新不会成功。不过,乐观锁存在失效的情况,就是常说的 ABA 问题,不过如果 version 版本一直是自增的就不会出现 ABA 的情况。
UPDATE users 
SET name='xiaoxiao', version=(version+1) 
WHERE id=1 AND version=version;

缺点:就是在操作业务前,需要先查询出当前的 version 版本

另外,还存在一种:状态机控制
例如:支付状态流转流程:待支付->支付中->已支付
具有一定要的前置要求的,严格来讲,也属于乐观锁的一种。

update users
set status=2
where status=1
  • 唯一约束
    常见的就是利用数据库唯一索引或者全局业务唯一标识(如:source+序列号等)。
    这个机制是利用了数据库的主键唯一约束的特性,解决了在 insert 场景时幂等问题。但主键的要求不是自增的主键,这样就需要业务生成全局唯一的主键。

全局 ID 生成方案:
• UUID:结合机器的网卡、当地时间、一个随记数来生成 UUID;

• 数据库自增 ID:使用数据库的 id 自增策略,如 MySQL 的 auto_increment。Redis 实现:通过提供像 INCR 和 INCRBY 这样的自增原子命令,保证生成的 ID 肯定是唯一有序的。

• 雪花算法-Snowflake:由 Twitter 开源的分布式 ID 生成算法,以划分命名空间的方式将 64-bit 位分割成多个部分,每个部分代表不同的含义。

5、总结

  • 前端控制控制重复请求
  • 分布式锁:适合核心业务或涉及到钱的操作
  • 先查后写:使用并发量低的场景
  • 悲观锁(for update):适合核心业务(不推荐使用)
  • 乐观锁-版本号:常用
  • 乐观锁-状态机控制:适合修改状态的场景
  • 唯一索引:业务上防重

高并发或核心业务:前端控制(筛选80%)+分布式锁(19%)+先查后写(过滤复杂逻辑)+唯一索引或版本号(1%)=100%

非高并发及非核心业务:前端控制(筛选80%)+先查后写(过滤复杂逻辑)+唯一索引或版本号(20%)=100%

状态流转:前端控制(筛选80%)+先查后写(过滤复杂逻辑)+乐观锁-状态机控制(20%)=100%

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

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

相关文章

Java | Leetcode Java题解之第103题二叉树的锯齿形层序遍历

题目&#xff1a; 题解&#xff1a; class Solution {public List<List<Integer>> zigzagLevelOrder(TreeNode root) {List<List<Integer>> ans new LinkedList<List<Integer>>();if (root null) {return ans;}Queue<TreeNode> n…

Go 使用bcrypt实现密码加密和和校验

在Go语言中&#xff0c;使用bcrypt算法进行密码的加密和校验是一种常见的做法&#xff0c;因为bcrypt算法可以提供强大的密码安全性。 bcrypt可以用于数据库中的用户密码保存&#xff0c;相比md5而言更加的安全可靠 文档 https://pkg.go.dev/golang.org/x/crypto/bcrypt 文档…

SYD881X HID工程重连后连接参数没有更新功耗下不来

SYD881X HID工程重连后连接参数没有更新功耗下不来 现在测试到一个问题,第一次连接上的时候过一段时间功耗会下来到100UA以内,这个是正常的,但是关掉手机蓝牙再打开手机蓝牙就发现功耗是500UA左右下不来了! 抓包发现第一次连接和重连的时候手机给的连接参数是一样的: 问题是当…

电力电子技术03 (1)---电路稳态分析方法

学习来源&#xff08;只用于个人学习笔记&#xff0c;建议对着老师视频学习理解更深入&#xff09;&#xff1a;2.2稳态分析的基本方法_哔哩哔哩_bilibili 一、Buck降压电路 Buck电路&#xff0c;也称为降压转换器&#xff0c;是一种DC-DC电压转换器&#xff0c;用于将输入电…

PHP精度处理

一、问题缘由 PHP 服务接收前端传过来的单价(字符串形式)和数量&#xff0c;把单价转成分(单价*100)&#xff0c;然后传给下游的 Golang 服务&#xff0c;不过最后从两个服务日志中发现金额相差 1。 以下为前端传的 {"amount": 4,"price": "9.2&qu…

代码随想录算法训练营第四十六天||139.单词拆分

一、139.单词拆分 给定一个非空字符串 s 和一个包含非空单词的列表 wordDict&#xff0c;判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。 说明&#xff1a; 拆分时可以重复使用字典中的单词。 你可以假设字典中没有重复的单词。 示例 1&#xff1a; 输入: s …

python下将sqlite数据提取出来,转化为json数据

问题描述&#xff1a;我需要将sqlite数据提取出来做一部分处理&#xff0c;使用pandas&#xff0c;sqlite3库实现 大致的思路&#xff1a;使用sqlite3读取指定路径的数据&#xff0c;然后使用pandas.read_sql_query接收数据&#xff0c;然后to_dict&#xff0c;再写入到json文件…

【强训笔记】day25

NO.1 思路&#xff1a;哈希质数判断。 代码实现&#xff1a; #include <iostream> #include<string> #include<cmath> using namespace std;bool isprime(int n) {if(n<2) return false;for(int i2;i<sqrt(n);i){if(n%i0) return false;}return true…

Vscode行尾序列LF和CRLF

提交代码时要注意&#xff1a; LF: line feed 代表换行 \n CRLF: carriage return line feed 代表回车并换行 \r\n (window系统下的换行) LF (Line Feed): 缩写&#xff1a;LF 字符&#xff1a;\n 描述&#xff1a;在Unix和Unix-like系统&#xff08;如Linux和macOS&#xf…

关于DOS

磁盘操作系统&#xff0c;Disk Operating System&#xff0c;属于单用户单任务操作系统&#xff0c;一次只能执行一个任务。一次只能执行一个任务。DOS操作系统是通过输入命令来执行一些操作。DOS已退出市场&#xff0c;WinNT/2K/XP中有个叫“命令提示符”&#xff08;CMD&…

2024-5-28 石群电路-16

2024-5-28&#xff0c;星期二&#xff0c;20:14&#xff0c;天气&#xff1a;晴&#xff0c;心情&#xff1a;晴。今天没有什么特别的事情发生&#xff0c;不过返校假期已经开始啦&#xff0c;和女朋友逛了街&#xff0c;吃了好吃的&#xff0c;学习也当然不能落下啦&#xff0…

Qt 自定义代理类

一.使用步骤 继承QStyledItemDelegate类&#xff1a;首先创建一个新的类并继承自QStyledItemDelegate类&#xff0c;作为您的自定义代理类。 实现代理类的构造函数&#xff1a;在代理类中实现构造函数&#xff0c;并在构造函数中调用基类的构造函数&#xff0c;可以选择传入一…

BIO/NIO学习

在传送文件的时候常常出现这么一个问题&#xff0c;就是当客户端的文件全部传送完了之后&#xff0c;服务器没有接收到客户端那边传过的停止信号&#xff0c;所以服务器也就跟着客户端停止运行了&#xff0c;我们可以使用 try {socket.shutdownOutput();} catch (IOException e…

web前端之vue动态访问静态资源、静态资源的动态访问、打包、public、import、URL、Vite

MENU 静态资源与打包规则动态访问静态资源直接导入将静态资存放在public目录中动态导入URL构造函数结束语实践与坑附文 静态资源与打包规则 介绍 Vite脚手架在打包代码的时候&#xff0c;会把源代码里对于静态资源的访问路径转换为打包后静态资源文件的路径。主要的区别是文件指…

ROS 话题通信(C++)

ROS 话题通信&#xff08;C&#xff09; 话题并不只属于发布者或订阅者,而是由ROS系统创建管理的,只要节点向NodeHandle大管家提出的话题发布需求或者话题订阅需求,这个话题就会自动被创建 这段话的核心是解释ROS&#xff08;Robot Operating System&#xff09;中话题&#xf…

go-gin中session实现redis前缀和db库选择+单点登录

分别实现了redigo中自动加前缀和session中自动加前缀 等有空了整理一个demo放到github上&#xff0c;到时候求个小星星 在gin-contrib/sessions/redis库中redis的前缀是被封装起来了&#xff0c;所以自定义前缀没有内部方法在这里我们自己实现一下NewStoreWithDBPrefix方法配…

记录一次开源 MaxKey 安装部署

官方文档&#xff1a;https://www.maxkey.top/doc/docs/intro/ 开源代码&#xff1a;https://toscode.mulanos.cn/dromara/MaxKey 发行版&#xff1a;https://toscode.mulanos.cn/dromara/MaxKey/releases 一、准备工作 yum install -y yum-utils yum-config-manager --add-r…

SwiftUI初探

SwiftUI 虽然出现了好几年(1.0好像2019年出的&#xff0c;还有SPM也是同一年)&#xff0c;现在已经到从1.0到5.0&#xff0c;但受限于对系统的要求(最低iOS13.0,有的要求17.0及以上)&#xff0c;每个版本里面差异也很大&#xff0c;语法和Flutter 的Dart 比较像。空闲之余可以先…

谈谈什么是生成器,它与列表推导式有何区别?以及如何使用Python进行异常处理?

生成器&#xff08;Generator&#xff09;是一种特殊的函数&#xff0c;它一次生成一个值&#xff0c;而不是一次性生成所有值。可以将其视为可恢复函数&#xff0c;在函数执行过程中&#xff0c;yield语句会返回需要的值给调用生成器的地方&#xff0c;然后退出函数。下一次调…

linux定时删除历史日志

在Linux系统中&#xff0c;日志文件是记录系统、应用程序或服务的运行信息、错误消息和警告的重要工具。然而&#xff0c;随着时间的推移&#xff0c;这些日志文件会不断积累&#xff0c;占用大量的磁盘空间。如果不及时清理&#xff0c;可能会导致磁盘空间不足&#xff0c;从而…