一个注解实现接口幂等性,真心优雅!

这篇具有很好参考价值的文章主要介绍了一个注解实现接口幂等性,真心优雅!。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、什么是幂等性?

简单来说,就是对一个接口执行重复的多次请求,与一次请求所产生的结果是相同的,听起来非常容易理解,但要真正的在系统中要始终保持这个目标,是需要很严谨的设计的,在实际的生产环境下,我们应该保证任何接口都是幂等的,而如何正确的实现幂等,就是本文要讨论的内容。

二、哪些请求天生就是幂等的?

首先,我们要知道查询类的请求一般都是天然幂等的,除此之外,删除请求在大多数情况下也是幂等的,但是ABA场景下除外。

举一个简单的例子

比如,先请求了一次删除A的操作,但由于响应超时,又自动请求了一次删除A的操作,如果在两次请求之间,又插入了一次A,而实际上新插入的这一次A,是不应该被删除的,这就是ABA问题,不过,在大多数业务场景中,ABA问题都是可以忽略的。

除了查询和删除之外,还有更新操作,同样的更新操作在大多数场景下也是天然幂等的,其例外是也会存在ABA的问题,更重要的是,比如执行update table set a = a + 1 where v = 1这样的更新就非幂等了。

最后,就还剩插入了,插入大多数情况下都是非幂等的,除非是利用数据库唯一索引来保证数据不会重复产生。

三、为什么需要幂等

1.超时重试

当发起一次RPC请求时,难免会因为网络不稳定而导致请求失败,一般遇到这样的问题我们希望能够重新请求一次,正常情况下没有问题,但有时请求实际上已经发出去了,只是在请求响应时网络异常或者超时,此时,请求方如果再重新发起一次请求,那被请求方就需要保证幂等了。

2.异步回调

异步回调是提升系统接口吞吐量的一种常用方式,很明显,此类接口一定是需要保证幂等性的。

3.消息队列

现在常用的消息队列框架,比如:Kafka、RocketMQ、RabbitMQ在消息传递时都会采取At least once原则(也就是至少一次原则,在消息传递时,不允许丢消息,但是允许有重复的消息),既然消息队列不保证不会出现重复的消息,那消费者自然要保证处理逻辑的幂等性了。

四、实现幂等的关键因素

关键因素1

幂等唯一标识,可以叫它幂等号或者幂等令牌或者全局ID,总之就是客户端与服务端一次请求时的唯一标识,一般情况下由客户端来生成,也可以让第三方来统一分配。

关键因素2

有了唯一标识以后,服务端只需要确保这个唯一标识只被使用一次即可,一种常见的方式就是利用数据库的唯一索引。

五、注解实现幂等性

下面演示一种利用Redis来实现的方式。

推荐一个开源免费的 Spring Boot 实战项目:

https://github.com/javastacks/spring-boot-best-practice

1.自定义注解

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

@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {

    /**
     * 参数名,表示将从哪个参数中获取属性值。
     * 获取到的属性值将作为KEY。
     *
     * @return
     */
    String name() default "";

    /**
     * 属性,表示将获取哪个属性的值。
     *
     * @return
     */
    String field() default "";

    /**
     * 参数类型
     *
     * @return
     */
    Class type();

}

2.统一的请求入参对象

@Data
public class RequestData<T> {

    private Header header;

    private T body;

}


@Data
public class Header {

    private String token;

}

@Data
public class Order {

    String orderNo;

}

3.AOP处理

import com.springboot.micrometer.annotation.Idempotent;
import com.springboot.micrometer.entity.RequestData;
import com.springboot.micrometer.idempotent.RedisIdempotentStorage;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Map;

@Aspect
@Component
public class IdempotentAspect {

    @Resource
    private RedisIdempotentStorage redisIdempotentStorage;

    @Pointcut("@annotation(com.springboot.micrometer.annotation.Idempotent)")
    public void idempotent() {
    }

    @Around("idempotent()")
    public Object methodAround(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Idempotent idempotent = method.getAnnotation(Idempotent.class);

        String field = idempotent.field();
        String name = idempotent.name();
        Class clazzType = idempotent.type();

        String token = "";

        Object object = clazzType.newInstance();
        Map<String, Object> paramValue = AopUtils.getParamValue(joinPoint);
        if (object instanceof RequestData) {
            RequestData idempotentEntity = (RequestData) paramValue.get(name);
            token = String.valueOf(AopUtils.getFieldValue(idempotentEntity.getHeader(), field));
        }

        if (redisIdempotentStorage.delete(token)) {
            return joinPoint.proceed();
        }
        return "重复请求";
    }
}
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.CodeSignature;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class AopUtils {

    public static Object getFieldValue(Object obj, String name) throws Exception {
        Field[] fields = obj.getClass().getDeclaredFields();
        Object object = null;
        for (Field field : fields) {
            field.setAccessible(true);
            if (field.getName().toUpperCase().equals(name.toUpperCase())) {
                object = field.get(obj);
                break;
            }
        }
        return object;
    }


    public static Map<String, Object> getParamValue(ProceedingJoinPoint joinPoint) {
        Object[] paramValues = joinPoint.getArgs();
        String[] paramNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
        Map<String, Object> param = new HashMap<>(paramNames.length);

        for (int i = 0; i < paramNames.length; i++) {
            param.put(paramNames[i], paramValues[i]);
        }
        return param;
    }
}

4.Token值生成

import com.springboot.micrometer.idempotent.RedisIdempotentStorage;
import com.springboot.micrometer.util.IdGeneratorUtil;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping("/idGenerator")
public class IdGeneratorController {

    @Resource
    private RedisIdempotentStorage redisIdempotentStorage;

    @RequestMapping("/getIdGeneratorToken")
    public String getIdGeneratorToken() {
        String generateId = IdGeneratorUtil.generateId();
        redisIdempotentStorage.save(generateId);
        return generateId;
    }

}
public interface IdempotentStorage {

    void save(String idempotentId);

    boolean delete(String idempotentId);
}
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

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

@Component
public class RedisIdempotentStorage implements IdempotentStorage {

    @Resource
    private RedisTemplate<String, Serializable> redisTemplate;

    @Override
    public void save(String idempotentId) {
        redisTemplate.opsForValue().set(idempotentId, idempotentId, 10, TimeUnit.MINUTES);
    }

    @Override
    public boolean delete(String idempotentId) {
        return redisTemplate.delete(idempotentId);
    }
}
import java.util.UUID;

public class IdGeneratorUtil {

    public static String generateId() {
        return UUID.randomUUID().toString();
    }

}

5. 请求示例

调用接口之前,先申请一个token,然后带着服务端返回的token值,再去请求。

import com.springboot.micrometer.annotation.Idempotent;
import com.springboot.micrometer.entity.Order;
import com.springboot.micrometer.entity.RequestData;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/order")
public class OrderController {

    @RequestMapping("/saveOrder")
    @Idempotent(name = "requestData", type = RequestData.class, field = "token")
    public String saveOrder(@RequestBody RequestData<Order> requestData) {
        return "success";
    }

}

请求获取token值。

一个注解实现接口幂等性,真心优雅!

带着token值,第一次请求成功。

一个注解实现接口幂等性,真心优雅!

第二次请求失败。

一个注解实现接口幂等性,真心优雅!

版权声明:本文为CSDN博主「码拉松」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/CSDN_WYL2016/article/details/122993086

更多文章推荐:

1.Spring Boot 3.x 教程,太全了!

2.2,000+ 道 Java面试题及答案整理(2024最新版)

3.《Java开发手册(嵩山版)》最新发布!

觉得不错,别忘了随手点赞+转发哦!文章来源地址https://www.toymoban.com/news/detail-774093.html

到了这里,关于一个注解实现接口幂等性,真心优雅!的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 腾讯二面:如何保证接口幂等性?高并发下的接口幂等性如何实现?

    什么是接口幂等性 接口幂等性这一概念源于数学,原意是指一个操作如果连续执行多次所产生的结果与仅执行一次的效果相同,那么我们就称这个操作是幂等的。在互联网领域,特别是在Web服务、API设计和分布式系统中,接口幂等性具有非常重要的意义。 具体到HTTP接口或者

    2024年03月19日
    浏览(43)
  • 分布式接口幂等性设计实现

    面对分布式架构和微服务复杂的系统架构和网络超时服务器异常等带来的系统稳定性问题,分布式接口的幂等性设计显得尤为重要。本文简要介绍了几种分布式接口幂等性设计实现,包括Token去重机制、乐观锁机制、数据库主键和状态机实现等,以加深理解。 1、分布式接口幂

    2024年02月05日
    浏览(40)
  • Spring Boot 实现接口幂等性的 4 种方案

    现在的系统或平台,为了追求体验性、内容丰富性,都是前后端分离,系统为了解藕会引入各种MQ等,都不可避免就引入了数据幂等性的问题。 幂等是一个数学与计算机学概念,在数学中某一运算为幂等时,其作用在任一元素两次会和其作用一次的结果相同。 在计算机编程中

    2024年02月06日
    浏览(50)
  • 「并发编程实战」接口幂等性设计的最佳实现(8种实现方案)

    文章参考: 实战!聊聊幂等设计 基于幂等表思想的幂等实践 追忆四年前:一段关于我被外企CTO用登录注册吊打的不堪往事 弹力设计篇之“幂等性设计” 幂等是一个数学与计算机科学概念。 在数学中,幂等用函数表达式就是: f(x) = f(f(x)) 。比如求绝对值的函数,就是幂等的

    2024年01月22日
    浏览(41)
  • 接口幂等性详解

    所谓接口幂等性就是:在特定场景下,同一条件的多次接口调用,保证操作只执行一次,如果接口没有保证幂等性,在以下场景就会产生问题 前端重复提交:用户进行注册、创建个人信息等操作,由于网络抖动导致页面没有及时响应,用户认为没有成功而多次点击提交按钮,

    2024年02月19日
    浏览(38)
  • 如何保证接口的幂等性?

    作者:小牛呼噜噜 | https://xiaoniuhululu.com 计算机内功、源码解析、科技故事、项目实战、面试八股等更多硬核文章,首发于公众号「小牛呼噜噜」 目录 什么是幂等性? 为什么需要保证幂等性? 接口幂等设计和防止重复提交可以等同吗? 常用保证幂等性的措施 先select再inse

    2024年02月08日
    浏览(38)
  • redis如何保证接口的幂等性

    背景 如何防止接口中同样的数据提交,以及如何保证消息不被重复消费,这些都是 shigen 在学习的过程中遇到的问题。今天,趁着在学习 redis 的间隙,我写了一篇文章进行简单的实现。 注意:仅使用于单机的场景,对于分布式、高并发场景,还是建议使用分布式锁。 首先我

    2024年02月09日
    浏览(40)
  • 114 接口中幂等性的保证

    同样是 面试问题  如何确保接口的 幂等性  幂等是一个 较为抽象的概念, 多次重复访问, 不会导致业务逻辑的异常  这里从增删改查, 几个方面列一下  一般来说, 我们核心需要关注的就是 新增 和 更新 对于 增加元素, 首先针对唯一约束进行校验, 然后再处理新增的相

    2024年04月23日
    浏览(30)
  • Java接口幂等性,如何重试?

    当我们写好一个项目时,有没有深深的思考过,如何处理接口幂等性问题呢?今天我们以屈原这句著名诗句“路漫漫其修远兮,吾将上下而求索”的精神来探索一下这个问题。 幂等性:简单来说就是一个操作多次执行的结果和一次执行产生的结果一致。 答:在计算机应用中

    2024年02月10日
    浏览(51)
  • 一文搞定接口幂等性架构设计方案

    现如今很多系统都会基于分布式或微服务思想完成对系统的架构设计。那么在这一个系统中,就会存在若干个微服务,而且服务间也会产生相互通信调用。那么既然产生了服务调用,就必然会存在服务调用延迟或失败的问题。当出现这种问题,服务端会进行重试等操作或客户

    2023年04月20日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包