【微服务】接口的幂等性怎么设计?

news2024/11/26 21:41:44

一、什么是幂等?

幂等性:短时间内,对于相同输入的请求,无论进行多少次重复操作,都应该和单次调用的结果一致。

二、幂等问题产生的原因是什么?(或者说为什么需要实现幂等性?)

1、前端重复提交

在用户注册,用户创建商品的时候,用户填写完成注册表单或者创建好了商品点击提交,很多时候会因为网络波动没有及时对用户做出提交成功响应,致使用户认为自己没有成功提交,然后一直点击提交按钮,这时就会发生重复提交表单请求,在数据库中重复创建多条记录。

2、接口超时重试

很多时候HTTP客户端工具都默认开启超时重试的机制,比如Feign。为了防止网络波动超时等造成请求失败,都会添加重试机制,导致一个请求可能提交多次。

3、消息重复消费

当使用MQ消息中间件的时候,如果消费者处理完生产者消息,但是还没有提交offset,然后自己挂掉了。等到自己重启以后就会重复消费生产者消息。

三、幂等问题的解决方案

1、防重token令牌

防重token令牌

具体流程步骤:

  1. 客户端会先发送一个请求去获取 token,服务端会生成一个全局唯一的 ID 作为 token 保存在 redis 中,同时把这个 ID 返回给客户端。
  2. 客户端第一次调用业务请求的时候必须携带这个 token。
  3. 服务端会校验这个 token,如果校验成功,则执行业务,并删除 redis 中的 token。
  4. 客户端第二次调用业务请求的时候必须携带这个 token。
  5. 服务端会校验这个 token,如果校验失败,说明 redis 中已经没有对应的 token,则表示重复操作,直接返回指定的结果给客户端。

注意:

  1. 对 redis 中是否存在 token 以及删除 token 的代码逻辑建议用 Lua 脚本实现,保证原子性。Redis 结合 Lua 脚本可以解决多线程并发安全问题。
  2. 全局唯一 ID 可以用UUID (分布式 ID )。

2、基于 mysql 唯一索引实现

基于 mysql 唯一索引实现

具体流程步骤:

  1. 客户端会先发送一个请求去获取到分布式 ID。
  2. 客户端第一次调用业务请求的时候会携带分布式 ID,服务端使用这个分布式ID作为唯一索引来进行插入,一旦出现重复提交的情况,插入自然不会成功。

3、基于 redis 分布式锁实现

基于 redis 分布式锁实现

具体流程步骤:

  1. 客户端先请求服务端,会拿到一个能代表这次请求业务的唯一字段。
  2. 将该字段以 SETNX 的方式存入 redis 中,并根据业务设置相应的超时时间。
  3. 如果设置成功,表示这是第一次请求,则执行后续的业务逻辑。
  4. 如果设置失败,表示已经执行过当前请求,直接返回。

四、SpingBoot集成Redis实现防重token令牌机制

1.Token生成和验证的工具类TokenUtils

@Slf4j
@Component
public class TokenUtils {

    @Autowired
    private StringRedisTemplate redisTemplate;

    private static final String IDEMPOTENT_TOKEN_PREFIX = "idempotent_token:";

    /**
     * 创建 Token 存入 Redis,并返回该 Token
     */
    public static String generateToken(String value) {
        // 创建Token
        String token = UUID.randomUUID().toString();
        String key = IDEMPOTENT_TOKEN_PREFIX + token;
        // 存储 Token 到 Redis,且设置过期时间为 5分钟
        redisTemplate.opsForValue().set(key, value, 5, TimeUnit.MINUTES);
        // 返回 Token
        return token;
    }

    /**
     * 验证 Token
     */
    public static boolean validToken(String token, String value) {
        // 设置 Lua 脚本,其中 KEYS[1] 是 key,KEYS[2] 是 value
        String script = "if redis.call('get', KEYS[1]) == KEYS[2] then return redis.call('del', KEYS[1]) else return 0 end";
        RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        // 根据 Key 前缀拼接 Key
        String key = IDEMPOTENT_TOKEN_PREFIX + token;
        // 执行 Lua 脚本
        Long result = redisTemplate.execute(redisScript, Arrays.asList(key, value));
        // 根据返回结果判断是否成功成功匹配并删除 Redis 键值对,如果结果不为空和0,则验证通过。
        if (result != null && result != 0L) {
            log.info("验证 token={},key={},value={} 成功", token, key, value);
            return true;
        }
        log.info("验证 token={},key={},value={} 失败", token, key, value);
        return false;
    }
}

2.防重接口的自定义注解ApiIdempotent

package com.changlu.annontions;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiIdempotent {
}

3.防重注解拦截器ApiIdempotentInterceptor,会拦截所有标注自定义防重注解的controller方法

@Component
public class ApiIdempotentInterceptor implements HandlerInterceptor {

    @Autowired
    private TokenUtils tokenUtils;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //校验是否有执行方法
        if (!(handler instanceof HandlerMethod)) {
            return true;//若没有对应的方法执行器,就直接放行
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        ApiIdempotent annotation = method.getAnnotation(ApiIdempotent.class);
        //若是没有防重注解直接放行
        if (annotation != null) {
            //解析对应的请求头
            String token = request.getHeader("token");
            if (ObjectUtils.isEmpty(token)) {
                ServletUtils.renderString(response, "请携带token令牌");
                return false;
            }
            //若是校验失败直接进行响应
            if (!tokenUtils.validToken(token, "changlu")) {
                ServletUtils.renderString(response, "重复提交");
                return false;
            }
        }
        return true;
    }
}

4.注册拦截器

@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {

    @Autowired
    private ApiIdempotentInterceptor apiIdempotentInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(apiIdempotentInterceptor);
    }
}

5.控制器接口

@RestController
public class OrderController {

    @GetMapping("/order/token")
    public String getToken() {
        String userInfo = "changlu";
        // 生成 Token 字符串,并返回
        return TokenUtils.generateToken(userInfo);
    }

    //防重注解
    @ApiIdempotent
    @PostMapping("/order/create")
    public Object createOrder() {
        return "创建订单成功!";
    }
}

五、最后总结

对于下单等存在唯一主键的业务,可以使用基于mysql唯一索引的方式实现。订单号是唯一索引。
对于更新订单状态等相关的更新场景操作,可以使用基于mysql乐观锁的方式实现。version是订单状态
对于一人一单的业务,可以使用基于redis分布式锁的方式实现。set的key是商品+用户,别忘记超时时间。
对于其他场景,可以通过防重token令牌方案的方式实现。Redis+Lua脚本解决多线程并发安全问题。

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

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

相关文章

高频Postman接口测试面试题

一、Postman在工作中使用流程是什么样的&#xff1f; 新建集合管理根据接口所属的模块&#xff0c;在集合中不同模块下编写接口测试用例处理接口之间的数据关联操作添加环境变量在tests tab下中增加断言调试接口&#xff0c;确保接口能被正常调用批量运行用例或者导出通过Newm…

STM32H750VBT6烧录源码无反应的问题

当烧录后出现这种情况下&#xff0c;点击魔术棒里面 Linker,勾选第一个方框后再次烧录即可。

【机器学习】聚类算法的基本概念和实例代码以及局部度量学习的概念和实例代码

引言 聚类算法在许多领域都有广泛的应用&#xff0c;例如数据挖掘、生物信息学、图像处理等。 文章目录 引言一、聚类算法1.1 K-Means算法1.2 DBSCAN算法1.3 层次聚类&#xff08;Hierarchical Clustering&#xff09;算法1.4 高斯混合模型&#xff08;Gaussian Mixture Model&…

Python系统教程02

Python 中基本运算符的使用变量基本运算符和变量编写简单的 Python 程序 一、Python 中的加法、减法、乘法、除法、 1.1 Python 中的""运算符 "" 可以用来计算两个数的和 "" 可以用来拼接 运算符可以用来计算两个数的和运算符可以连接多个字符…

Leetcode面试经典150题-36-有效数独升级版-37.解数独

解法都在代码里&#xff0c;不懂就留言或者私信&#xff0c;比第一题稍微难点 public static void solveSudoku(char[][] board) {/**定义三个二维数组分别代表行、列、桶&#xff08;每9个格子&#xff09;*/boolean[][] rowExists new boolean[9][10];boolean[][] colExist…

不用U盘重装win10/11

创建适用于 Windows 的安装介质 Windows 10 Windows 8.1 Windows 7 Microsoft 365 免费试用版正在等待你使用 立即解锁 你可以使用安装介质&#xff08;U 盘或 DVD&#xff09;来安装 Windows 的新副本、执行全新安装或重新安装 Windows。 要创建安装介质&#xff0c;请转到…

街机 CAPCOM CPS2 中英文名字与驱动对照表

Part.I 简介 本文列举了街机 CPS2 中游戏的中英文名字与其驱动的对照&#xff0c;以帮助诸位更快地找到自己想玩的游戏。 注意&#xff1a;汉化版的街机模拟器 Kawaks 中游戏的中文名字是根据英文直译的&#xff0c;并不是习惯性的中文叫法。比如『三国志』英文名为『Warriors…

安全入门day.03

一、知识点 1、抓包技术应用意义 在渗透安全方面&#xff0c;通过抓包分析&#xff0c;安全人员可以模拟黑客的攻击行为&#xff0c;对系统进行渗透测试。这种测试有助于发现系统中存在的安全漏洞和弱点。一旦发现漏洞&#xff0c;可以立即采取措施进行修复&#xff0c;从而增…

Selenium实战技巧-多页面和Windows控件处理

01 多页面处理 做UI自动化的时候常常会遇到浏览器弹出新的Tab页&#xff0c;或者需要在多个网页服务之间来回取数据的情况。 比如在首页点击文章“Jmeter使用&#xff1f;”的链接&#xff0c;浏览器会弹出一个新的页面显示“Jmeter使用&#xff1f;”这篇文章的详情。此时如…

SpringBoot教程(二十七) | SpringBoot集成AOP实现异常处理

SpringBoot教程&#xff08;二十七&#xff09; | SpringBoot集成AOP实现异常处理 前言第一步&#xff1a;统一接口返回结果1. 统一封装结果包含如下参数2. 创建 枚举HttpStatusEnum&#xff08;返回结果代码&#xff09;3. 创建 ResponseResult &#xff08;返回实体类&#x…

如何使用vcftools提取特定的染色体

起源是由于bam文件没有过滤完全&#xff0c;导致calling出来的vcf文件还有线粒体中的染色体存在&#xff0c;因为在金标准文件中只有1-22号和X染色体&#xff0c;不包含线粒体和Y染色体&#xff0c;因为我使用的金标准文件是来自NA12878&#xff0c;是一位白种人女性。因此&…

VBA代码解决方案第十七讲:如何选择一个工作表,选择多个工作表

《VBA代码解决方案》(版权10028096)这套教程是我最早推出的教程&#xff0c;目前已经是第三版修订了。这套教程定位于入门后的提高&#xff0c;在学习这套教程过程中&#xff0c;侧重点是要理解及掌握我的“积木编程”思想。要灵活运用教程中的实例像搭积木一样把自己喜欢的代码…

基于小程序的学习交流论坛的设计与实现(代码+教程)

我们将制作一个具备帖子分类、发帖、搜索、点赞回复、学习小组组建以及用户登录等功能的小程序。下面将详细阐述每个功能的实现方法&#xff0c;并提供一些关键代码片段作为参考。 需求 帖子分类&#xff1a;对用户发布的帖子分类到对应的专区&#xff08;寻人寻物&#xff0…

算法-最长连续序列

leetcode的题目链接 这道题的思路主要是要求在O&#xff08;n)的时间复杂度下&#xff0c;所以你暴力解决肯定不行&#xff0c;暴力至少两层for循环&#xff0c;所以要在O&#xff08;n)的时间复杂度下&#xff0c;你可以使用HashSet来存储数组&#xff0c;对于每个数字&#…

分页查询--条件查询

使用pagehelper插件 我们在pom.xml文件中加入下面的语句&#xff0c;可以使用插件&#xff0c;进行分页查询 <dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1…

最管用的能屏蔽WIndows10/11系统功能按键的工具--powerToys键盘管理器

最近在开发中碰到需要屏蔽系统按键功能的需求&#xff0c;原本以为是程序里屏蔽按键&#xff0c;结果&#xff0c;原来是需要屏蔽操作系统默认按键功能。 这样的话&#xff0c;就只能往注册表&#xff0c;脚本&#xff0c;全局钩子函数&#xff0c;以及一些第三方的什么工具之…

工业智能物联网关,智慧医疗生态圈的创新驱动

项目背景 智慧化数字医疗正在推动医疗健康领域的转型&#xff0c;预计到2024年&#xff0c;全球数字医疗市场规模将达到3656.7亿美元&#xff0c;中国市场规模将增至4130亿元人民币&#xff0c;随着技术的持续创新和市场需求的不断增长&#xff0c;这一领域的需求和潜力将持续扩…

24最新Stable Diffusion入门指南(看完必会)超全面

前言 今天写这个帖子是带大家了解一款强大的 AI 绘画工具——Stable Diffusion&#xff0c;可以帮你解决很多应用层面的[AI控图]问题。 关于 Stable Diffusion 的内容很多&#xff0c;在本篇教程里&#xff0c;我会先为你介绍 Stable Diffusion 模型的运行原理、发展历程和相…

探索离线AI知识库的技术突破:AntSKPro AI 离线知识库一体机

在当今数字化时代&#xff0c;离线AI解决方案变得越来越重要&#xff0c;特别是在网络连接不稳定或不可用的情况下。最近&#xff0c;我有幸接触到一款名为AntSKPro AI 离线知识库一体机的设备&#xff0c;它展示了在离线环境下如何实现强大的AI支持。下面我将分享一些关于这款…

Ajax_00000

contents Ajax介绍 AJAX(Asynchronous JavaScript And XML)。 XML简介 XML&#xff1a;可扩展标记语言。 XML被设计用来传输和存储数据。 XML和HTML类似&#xff0c;不同的是HTML中都是预定义标签&#xff0c;而XML中没有预定义标签&#xff0c;全都是自定义标签&#xff0…