首页 > 其他分享 >重复请求的幂等问题

重复请求的幂等问题

时间:2024-12-16 17:20:36浏览次数:4  
标签:验收 请求 发起 重复 流程 问题 线程 代码

问题背景

某验收系统,客户发起验收流程时,由于前端没有做防重点击的限制,导致申请按钮连续点击了多次,重复发起了多条流程

历史逻辑

后端为了保证接口幂等,在发起验收流程的代码中加了几层逻辑如下:

  1. 判断验收记录状态是否为待发起, 如果不是,则立刻返回失败
  2. 发起流程的入口加了一层用户维度的锁,可以保证同一用户无法同时进入流程处理逻辑,伪代码如下:
@Component
public class ProcessManager {

    private final CopyOnWriteArraySet<String> userLock = new CopyOnWriteArraySet<>();

    /**
     * 流程操作
     */
    public void doAction(入参) {
        // 拦截器会提前解析token并将用户信息存入上下文
        AuthUser currentUser = ContextUtil.getCurrentUser();
        try {
            // 这里将上下文中的用户id存入CopyOnWriteArraySet中,如果保存失败说明当前用户正在操作流程,返回失败
            if (!userLock.add(currentUser.getId())) {
                throw new BusinessException("正在处理中,请勿重复操作");
            }

            // 此处是流程处理代码入口...
        } finally {
            // 流程代码结束后释放锁,放在finally代码块中,防止死锁
            userLock.remove(currentUser.getId());
            // 此处清理流程上下文信息...
        }
    }


}
  1. 流程处理完后,更新数据库中的验收记录状态为已发起

以上逻辑梳理成总体的伪代码如下:

@Autowired
privarte ProcessManager processManager;

public void startApply(入参) {
    // 校验验收记录的状态
    Record record = getRecord(入参);
    if (!record.getStatus().equals(待发起)) {
        返回失败
    }

    前置业务流程...

    // 发起流程
    processManager.doAction(入参);

    // 更新状态
    record.setStatus(待审批);
    baseMapper.updateById(record);
}

 

 可以转化为如下的流程图

 

问题分析

当出现以下情况时,会出现重复发起的问题

首先,为了保证事务的原子性,整个方法是一个大事务

多个线程同时查询验收记录,会查询到同样的未被改变状态的数据,会同时通过状态校验,进入到以下的情况:

 

由于代码执行有快慢,线程1率先发起了流程,并且完成了

 

此时线程2还未尝试获取锁,线程1就已经释放了锁,这时线程2也能顺利进入发起流程的代码,再次发起流程

 

当线程1和线程2都执行完成后,数据库中就生成了两条流程,出现了幂等性问题

解决思路

数据库锁

 

最开始查询验收记录时,可以在SQL后增加“for update”,将该条记录锁住,并且for update是当前读,能够读取到最新的已提交的数据

优点:可以保证只有一个线程进入后续代码

缺点:排它锁太重了,其他线程需要等待事务结束才能获取到锁查询数据,会严重影响性能

分布式锁

 

优点:可以保证只有一个线程进入后续代码,性能好,不影响其他查询

缺点:引入redis分布式锁又会面临redis集群的其他问题,可能会死锁或锁失效

数据库唯一索引

在流程实例表增加唯一索引,唯一字段根据业务属性决定,创建流程实例时把insert语句的方法用try-catch包裹起来,检测DuplicateKeyException异常,如果发生了重复键冲突,则直接报错

 

优点:根本上确保了流程实例的唯一性

缺点:每个线程都会执行到前置的业务处理,这部分是多余的计算

结论

通过对比优缺点,最终选择了数据库唯一索引的办法解决问题

首先根据业务特征,在流程实例表b_approve_instance增加了对应唯一索引

之后在insert的方法外包裹一层try-catch,代码如下

try {
    baseMapper.insert(instance);
} catch (DuplicateKeyException e) {
    throw new BusinessException("已提交,请勿重复提交");
}

 

标签:验收,请求,发起,重复,流程,问题,线程,代码
From: https://www.cnblogs.com/little-nobody/p/18610672

相关文章

  • 解决|配置denoising diffusion bridge model环境|flash-att、openmpi、mpi4py安装问题
    目录安装flash-att安装openmpi和mpi4py我在配置DDBM(https://github.com/alexzhou907/DDBM/tree/main)环境时遇到的问题:1、flash-att安装失败2、openmpi和mpi4py安装失败以下是基于我的情况的解决办法安装flash-attflash-att(1)对CUDA版本有要求(2)pytorch版本需与flash-att版本有......
  • VUE学习所遇问题【el-date-picker】限制了时间范围,就无法使用'此刻'按钮的效果
    最开始遇到的问题是:el-date-picker组件需要只能选择昨天与今天的时间信息初版代码:<el-date-pickerv-model="formData.withdrawTime"type="datetime"placeholder="请选择时间":picker-options="datetimePickerOptions"format="yyyy......
  • 积木大赛 类问题学习
    introP1969[NOIP2013提高组]积木大赛题目描述春春幼儿园举办了一年一度的“积木大赛”。今年比赛的内容是搭建一座宽度为\(n\)的大厦,大厦可以看成由\(n\)块宽度为\(1\)的积木组成,第\(i\)块积木的最终高度需要是\(h_i\)。在搭建开始之前,没有任何积木(可以看成\(n\)......
  • jstack排查问题
    1、先拿到tomcat进程IDps–ef|greptomcat记录下tomcat应用进程的ID:30027(我拿到的是这个值)2、拿到CPU占用最高、时间最长的线程ID#显示进程号为30027的进程信息,CPU、内存占用率等,top-H-p 30027当然这一步你也可以使用以下这个命令,显示特定PID下全部线程列表,以定位......
  • 软件缺少d3dx10_38.dll文件及错误提示问题
    其实很多用户玩单机游戏或者安装软件的时候就出现过这种问题,如果是新手第一时间会认为是软件或游戏出错了,其实并不是这样,其主要原因就是你电脑系统的该dll文件丢失了或没有安装一些系统软件平台所需要的动态链接库,这时你可以下载这个d3dx10_38.dll文件(挑选合适的版本文件)把它......
  • 新手安装SQLite常见问题
    SQLite简介SQLite是一个进程内的库,实现了自给自足的、无服务器的、零配置的、事务性的SQL数据库引擎。它是一个零配置的数据库,这意味着与其他数据库不一样,您不需要在系统中配置。就像其他数据库,SQLite引擎不是一个独立的进程,可以按应用程序需求进行静态或动态连接。SQLite......
  • 子查询关联条件字段没有指定表的别名导致的查询结果不正确的问题
    首先介绍一下表结构和背景; 有两个数据库表,供应商XX任务主表和供应商等级变更记录表;等级表里面有多个任务,两张表是通过同名称的字段,supplier_id关联;  ①SQL是XX任务表关联供应商等级表,结果是A+,实际是D等级,②SQL是查询的结果是A+等级;③SQL是比②SQL多了一个条件带了suppl......
  • STM32F407ZGT6-UCOSIII笔记2:UCOSIII任务创建实验-Printf 函数卡住 UCOSIII 系统问题解
    今日简单编写熟悉一下UCOSIII系统的任务创建代码,理解一下OS系统:并发现以及解决了Printf函数卡住UCOSIII系统问题解决文章提供测试代码讲解、完整工程下载、测试效果图目录文件结构解释:任务函数文件:目前各个文件任务:#include"main.h"#include"ComTask.h"#includ......
  • vue3开发中常见的代码错误或者其他相关问题小文章5.0
    41. 事件修饰符在组合式API中的使用错误示例:在组合式API中不正确地使用事件修饰符(如.prevent或.stop),导致事件处理逻辑失效。解决方案:确保在setup函数中正确使用事件修饰符。可以通过v-on的选项对象来添加修饰符。//在<scriptsetup>中import{ref}from'......
  • 两种方式实现css三球围绕中心旋转问题
     <!--*@Author:Simoon.jia*@Date:2024-12-1317:38:51*@LastEditors:Simoon.jia*@LastEditTime:2024-12-1319:00:34*@Description:描述--><!DOCTYPEhtml><htmllang="en"><head><metacharset="UT......