推荐一种非侵入式幂等性的Java实现

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 推荐一种非侵入式幂等性的Java实现


幂等性

今天我们来谈谈什么是幂等性

引用百度百科的解析如下:

幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。

在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。例如,“setTrue()”函数就是一个幂等函数,无论多次执行,其结果都是一样的.更复杂的操作幂等保证是利用唯一交易号(流水号)实现。

这解析,确实有点了,大家话看看就行了!!!(●'◡'●)

那对于我们程序员来说,我们关心的更多是下面这些问题:

什么地方,什么场景下需要用到幂等?

幂等,我们需要怎么做,如何实现幂等呢?

如何实现幂等呢

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

什么场景下需要用到幂等

  • 前端表单重复提交问题
  • 用户订单支付问题
  • 银行业务办理取号问题
  • 用户恶意进行调接口问题
  • 接口超时重复提交问题
  • MQ消息进行重复消费
  • ...

当然了,还有很多场景会用到幂等,这里咱们就不一一列举出来了。

那我们要如何设计一个幂等功能呢,而且还是代码非侵入式

代码非侵入式的意思,就是,我们的业务逻辑代码,不需要处理幂等校验的逻辑。

业务功能不处理?那交给谁处理呢?别着急,听哥们一一道来。^_^

业务功能不处理?

这里,要实现代码非侵入式的幂等校验,我们就要使用到切面编程了(@Aspect

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

幂等的实现原理

在系统中一些接口需要增加幂等处理,幂等的概念是一个业务请求只能执行一次。类似银行业务办理,首先需要取一个号,然后用户使用这个号去柜台办理业务。这个号只能使用一次,如果过期或者已办理这个号就无效了。

我们的幂等也是使用这种原理。

  • 1.首先客户端调用通过我们的系统获取一个号,我们称之为幂等号,这个号已经存在我们的系统中。
  • 2.客户端使用这个号,调用我们的接口。
  • 3.我们系统判断这个号在我们的系统中已经存在,如果存在则允许业务办理,如果不存在,则表示这是一个非法的号,我们直接抛出异常。
  • 4.当业务处理完成,我们会将这个号从我们的系统中删除掉。

好了,这实现步骤,也是十分清晰了呀!!!^_^

那么我们下面就来看代码如何实现了

幂等的代码实现

  • 定义一个幂等处理接口
public interface Idempotence {
    /**
     * 检查是否存在幂等号
     * @param idempotenceId 幂等号
     * @return 是否存在
     */
    boolean check(String idempotenceId);
    /**
     * 记录幂等号
     * @param idempotenceId 幂等号
     */
    void record(String idempotenceId);
    /**
     * 记录幂等号
     * @param idempotenceId 幂等号
     * @param time 过期时间
     */
    void record(String idempotenceId, Integer time);
    /**
     * 删除幂等号
     * @param idempotenceId 幂等号
     */
    void delete(String idempotenceId);
}
  • 定义一个幂等处理接口实现类
@Component
public class RedisIdempotence implements Idempotence {
    @Autowired
    private RedisRepository redisRepository;
    @Override
    public boolean check(String idempotenceId) {
        return redisRepository.exists(idempotenceId);
    }
    @Override
    public void record(String idempotenceId) {
        redisRepository.set(idempotenceId,"1");
    }
    @Override
    public void record(String idempotenceId,Integer time) {
        redisRepository.setExpire(idempotenceId,"1",time);
    }
    @Override
    public void delete(String idempotenceId) {
        redisRepository.del(idempotenceId);
    }
}

这个实现类,咱们就用redis存储这个幂等号 实现4个方法:

检查是否存在幂等号

记录幂等号

记录幂等号(带过期时间)

删除幂等号

  • 幂等工具类
@Component
public class IdempotenceUtil {
    @Autowired
    private RedisRepository redisRepository;
    /**
     * 生成幂等号
     * @return
     */
    public String generateId() {
        String uuid = UUID.randomUUID().toString();
        String uId=Base64Util.encode(uuid).toLowerCase();
        redisRepository.setExpire(uId,"1",1800);
        return uId;
    }
    /**
     * 从Header里面获取幂等号
     * @return
     */
    public String getHeaderIdempotenceId(){
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String idempotenceId=request.getHeader("idempotenceId");
        return idempotenceId;
    }
}

这个工具类,提供两个方法。

1.生成一个幂等号,咱们就用uuid

2.从Header里面获取幂等号

  • 定义一个注解
/**
 * 接口增加幂等性
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface IdempotenceRequired {
}
  • 切面
@Aspect
@Slf4j
@Component
public class IdempotenceSupportAdvice {
    @Autowired
    private Idempotence idempotence;
    @Autowired
    IdempotenceUtil idempotenceUtil;
    /**
     * 拦截有@IdempotenceRequired 注解 的方法。
     */
    @Pointcut("@annotation(xxx.xxx.IdempotenceRequired)")
    public void idempotenceMethod(){}
    @AfterThrowing(value = "idempotenceMethod()()",throwing = "e")
    public void afterThrowing(Throwable e){
        if(!(e instanceof IdempotencyException)) {
            // 从HTTP header中获取幂等号idempotenceId
            String idempotenceId = idempotenceUtil.getHeaderIdempotenceId();
            idempotence.record(idempotenceId, 1800);
        }
    }
    @Around(value = "idempotenceMethod()")
    public Object around(ProceedingJoinPoint  joinPoint) throws Throwable {
        // 从HTTP header中获取幂等号idempotenceId
        String idempotenceId = idempotenceUtil.getHeaderIdempotenceId();
        if(StringUtils.isEmpty(idempotenceId)){
            //不存在幂等号则不进行额外操作
            return joinPoint.proceed();
        }
        // 前置操作 幂等号是否存在
        boolean existed = idempotence.check(idempotenceId);
        if (!existed) {
            throw new IdempotencyException("{success:false,message:"操作重复,请重新输入幂等号重试!",data:-2}");
        }
        //删除幂等号
        idempotence.delete(idempotenceId);
        Object result = joinPoint.proceed();
        return result;
    }
}
  • 定义个controller
@RequestMapping("/idempotence")
public class IdempotenceController {
    /**
     * 生成幂等号
     * @return
     */
    @GetMapping("/generateId")
    public JsonResult generateId(){
        IdempotenceUtil idempotenceUtil=SpringUtil.getBean(IdempotenceUtil.class);
        String uId=idempotenceUtil.generateId();
        return JsonResult.success("成功生成!").setData(uId);
    }
}

好了,实现的代码,就是这些了,理解起来也是比较简单,没有过多复杂的逻辑。

接下来,就是如何使用的问题了,

使用的问题

这个使用,也是十分的简单啦!!!

幂等的使用

「服务端:」

不是所有的方法都需要切面拦截 ,只有 IdempotenceRequired 注解的方法才会被拦截。

例如下面接口:

@IdempotenceRequired
@PostMapping("/getUsers")
public JsonResult getUsers(){
    //执行正常业务逻辑
    ...
}

在开发幂等接口时,只需要在方法上简单增加一个 IdempotenceRequired 注解即可。

这基本上就是代码非侵入式了呀!!!

代码非侵入式

「客户端:」

服务端处理好后,在客户端访问接口的时候需要执行以下步骤:

  • 需要先获取幂等号
  • 然后将幂等号添加到请求头中

  • 1.获取幂等号http://服务地址/idempotence/generateIdhttp://xn--zfry9hnb732h/idempotence/generateId

获取幂等号

  • 2.请求调用

往header中添加幂等号

往header中添加幂等号

好了,到这里幂等的实现,就已经完成了!!!^_^

那我们就可以愉快的编写代码了!!!^_^



相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
6月前
|
Arthas 监控 IDE
去哪儿网开源的一个对应用透明,无侵入的Java应用诊断工具
今天 V 哥给大家带来一款开源工具Bistoury,Bistoury 是去哪儿网开源的一个对应用透明,无侵入的java应用诊断工具,用于提升开发人员的诊断效率和能力。
|
消息中间件 监控 JavaScript
2.4K star,一个高性能、无侵入的Java性能监控和统计工具,有点东西!
2.4K star,一个高性能、无侵入的Java性能监控和统计工具,有点东西!
|
运维 监控 Java
《Jpom一款低侵入式Java运维、监控软件》电子版地址
Jpom一款低侵入式Java运维、监控软件
117 0
《Jpom一款低侵入式Java运维、监控软件》电子版地址
|
Arthas 监控 前端开发
Java高级用法,写个代理侵入你 ?
本文主要介绍 Java高级用法之Insrument
198 0
|
运维 监控 JavaScript
【直播回顾】云栖社区特邀专家蒋泽银:Jpom一款低侵入式Java运维、监控软件
讲解Jpom的由来、Jpom技术架构,演示Jpom的安装使用,讲解Jpom未来的计划、提问和交流。
1962 0
【直播回顾】云栖社区特邀专家蒋泽银:Jpom一款低侵入式Java运维、监控软件
|
运维 监控 JavaScript
【直播预告】云栖社区特邀专家蒋泽银:Jpom一款低侵入式Java运维、监控软件
讲解Jpom的由来、Jpom技术架构,演示Jpom的安装使用,讲解Jpom未来的计划、提问和交流。
12806 0
|
监控 Java 开发工具
MyPerf4J 一个高性能、无侵入的Java性能监控和统计工具
一个极快的Java接口性能监控和统计工具。受perf4j和TProfiler启发而来。 致力于成为可在生产环境长时间使用的性能监控和统计工具!
3627 0
|
12天前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
91 38
|
9天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
4天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####