当前位置: 首页 > article >正文

基于Redis实现幂等判断

核心思路:

当用户发出提交请求时,在 Redis 中创建一个带有过期时间的唯一标识,表示这个请求已经提交过了。如果 Redis 中已经存在这个标识,则拒绝本次提交,避免重复操作。

基本准备:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.7.10</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>2.7.10</version>
</dependency>

大体思路:在业务侧进行加锁的幂等判断,在规定时间内操作只能算一次成功的请求

import com.sa.config.RedissonManager;
import org.redisson.api.RLock;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Service
public class RepeatedSubmitService {

    @Resource
    private RedisTemplate redisTemplate;

    private static final long EXPIRE_TIME = 5;  // 过期时间,单位秒


    /**
     * 防止重复提交操作
     * @param userId userId 用户ID
     * @param actionId actionId 操作标识(可以是业务类型或者表单ID等)
     * @return true 表示操作允许,false 表示重复提交
     */
    public boolean check(String userId,String actionId){
        // 生成redisKey,作为唯一标识
        String redisKey = "submitLock:"+userId+":"+actionId;

        // 尝试使用 SETNX 来防止重复提交,返回 true 表示设置成功(没有重复提交)
        Boolean success = redisTemplate.opsForValue()
                .setIfAbsent(redisKey,"LOCKED",EXPIRE_TIME, TimeUnit.SECONDS);

        if (Boolean.TRUE.equals(success)){
            return true;
        }else{
            // Redis 中已经存在锁键,说明是重复提交
            return false;
        }
    }

    public void submit(String userId,String actionId){
        if (check(userId,actionId)){
            System.out.println("业务操作成功");
        }else {
            System.out.println("重复提交");
        }
    }

}
package com.sa.controller;


import com.sa.pojo.Order;
import com.sa.service.RepeatedSubmitService;
import lombok.extern.log4j.Log4j2;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@Log4j2
@RestController
public class OrderController {

    @Resource
    private RepeatedSubmitService repeatedSubmitService;

    @GetMapping("/submit")
    public void submit(@RequestBody Order order){
        String userId = order.getUserId();
        String actionId = order.getActionId();
        log.info("userId:{},actionId:{}",userId,actionId);
        repeatedSubmitService.submit(userId,actionId);
    }
}

在5秒内的重复提交记录,只能是一条生效,剩余的请求在业务侧进行失效处理

改进

此处还可以基于本地缓存实现,这里采用Map模拟,也可以使用Caffine本地缓存

import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

/**
 * 幂等性校验
 */
@Service
public class IdempotencyService {


    private Map<String, Boolean> requestCache = new HashMap<>();

    /**
     * 检查是否是重复的请求
     *
     * @param requestId
     * @return
     */
    private synchronized boolean check(String requestId) {
        if (requestCache.containsKey(requestId)) {
            return false;
        }
        requestCache.put(requestId, true);
        return true;
    }

    /**
     * 模拟业务操作
     */
    public void processRequest(String requestId) {
        if (check(requestId)) {
            // 处理业务逻辑
            System.out.println("处理请求: " + requestId);
        } else {
            System.out.println("请求重复: " + requestId);
        }
    }

}

或是使用Redisson来进行实现:

  • RLock 替代 setIfAbsent:Redisson 的 RLock 封装了 Redis 分布式锁的功能,简化了操作。通过 lock.tryLock 来尝试获取锁,获取成功则继续执行操作,获取失败则表示重复提交。

  • 自动续期和过期时间:Redisson 内置了看门狗机制,会自动续期锁,防止长时间业务执行时锁提前释放。通过 tryLock(100, EXPIRE_TIME, TimeUnit.SECONDS) 可以设置锁的最大等待时间和最大存活时间,超时后锁自动释放。

  • 锁的释放:Redisson 自动确保锁的释放在 finally 块中进行,避免因异常导致锁未被释放。

    public boolean checkForRedisson(String userId,String actionId){
        // 生成redisKey,作为唯一标识
        String redisKey = "submitLock:"+userId+":"+actionId;

        // 获取分布式锁对象
        RLock lock = RedissonManager.getClient().getLock(redisKey);

        try{
            Boolean success = lock.tryLock(100,EXPIRE_TIME, TimeUnit.SECONDS);
            if (success){
                return true;
            }else{
                return false;
            }
        }catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }finally {
            if(lock.isHeldByCurrentThread()){
                lock.unlock();
            }
        }
    }

 


http://www.kler.cn/a/315297.html

相关文章:

  • VTK知识学习(8)-坐标系统
  • Vulnhub靶场案例渗透[8]- HackableII
  • 计算机网络 (1)互联网的组成
  • 力扣-Mysql-3308- 寻找表现最佳的司机(中等)
  • 【AI日记】24.11.14 复习和准备 RAG 项目 | JavaScript RAG Web Apps with LlamaIndex
  • 相机光学(四十二)——sony的HDR技术
  • 异步请求的方法以及原理
  • MyBatis动态SQL中的`if`标签使用【后端 19】
  • C++ 条件变量:wait、wait_for、wait_until
  • 【开源大模型生态9】百度的文心大模型
  • 主播和礼品检测系统源码分享
  • 高速下载大模型文件
  • 【读点论文】Text Recognition in the Wild: A Survey 非常纯粹的OCR研究,专业细致,脉络清晰
  • Datawhale X 南瓜书 task01学习笔记
  • Vue3(一) Vite创建Vue3工程,选项式API与组合式API;setup的使用;Vue中的响应式ref,reactive
  • c语言练习题1(数组和循环)
  • python发送邮件 - email smtplib
  • vue2项目实现国际化(若依框架示例)
  • c语言习题
  • JS领域的AI工程利器分享
  • Spring Cloud Gateway组件
  • 如何在 Spring Boot中更改默认端口
  • sql语法学习
  • 【HTTPS】对称加密和非对称加密
  • 【C++前缀和 状态压缩】2588. 统计美丽子数组数目|1696
  • Springboot使用ThreadPoolTaskScheduler轻量级多线程定时任务框架