欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

springboot2.2.X手册:分布式系统下,重复提交的解决方案

程序员文章站 2022-06-17 17:09:18
...

目录

什么是幂等性

重复提交如何产生

基于redis的防止重复提交

引入POM文件

新建重复提交注解

新建重复提交拦截

新增redis配置

新建测试类


上一篇:springboot2.2.X手册:是时候用Lettuce替换Jedis操作Redis缓存了

上一篇中我们讲解了redis,主要是因为接下来的更新,都会涉及到redis的操作,所以就放在上一篇了。

今天我们主要讲解重复提交的问题,这种问题,算是比较常见,但是又容易出问题,加上现在基本上都是微服务架构,今天来聊一下在分布式系统下,如果防止重复提交。

springboot2.2.X手册:分布式系统下,重复提交的解决方案

 

什么是幂等性

小编以前面试过一家公司,被问到什么是幂等性,当时不懂,就瞎扯了一番,惨遭面试官鄙视。

幂等性指的是多次运算结果一样,用公式来表示就是F(F(x))=F(x)。

在我们的对数据库的操作中,以下操作就是幂等性

select查询就是最基础的幂等性

delete删除也是一样,删除多少次都是一样的结果

update这里分两种,如果是更新某个值,那就是幂等性;如果是更新累加操作的,那就是非幂等性。

insert是非幂等性操作,毕竟每次都增加一条,从而导致数据变化了

springboot2.2.X手册:分布式系统下,重复提交的解决方案

 

重复提交如何产生

重复问题发生的情况比较多,小编总结了一下以下几点

1、提交按钮点击两次

2、浏览器提交后进行后退操作,然后再一次提交

3、使用浏览器的历史记录进行重复提交表单

4、重复的请求浏览器的http请求

5、nginx不断重新发送

6、分布式RPC中,进行了try重试等

springboot2.2.X手册:分布式系统下,重复提交的解决方案

 

基于redis的防止重复提交

今天我们来讲解一种方法,基于redis的重复提交的防止方案,只供参考。

引入POM文件

<!-- springboot核心web -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<!-- 公共组件:redis缓存包 -->
		<dependency>
			<groupId>com.boots</groupId>
			<artifactId>module-boots-redis</artifactId>
			<version>1.0.0.RELEASE</version>
		</dependency>

		<!-- springboot切面aop工具 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>

新建重复提交注解

/**
 * 重复提交注解
 * @author:溪云阁
 * @date:2020年5月24日
 */

public @interface NoRepeatSubmit {

}

新建重复提交拦截

/**
 * 重复提交拦截
 * @author:溪云阁
 * @date:2020年5月24日
 */

@Aspect
@Component
@Slf4j
public class RepeatSubmit {

    @Autowired
    private RedisUtils redisUtils;

    /**
     * 重复提交拦截
     * @author 溪云阁
     * @param pjp
     * @return Object
     */
    @Around(value = "@annotation(com.module.boots.submit.NoRepeatSubmit)")
    public Object arround(ProceedingJoinPoint pjp) {
        Object obj = null;
        try {
            final ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            final String sessionId = RequestContextHolder.getRequestAttributes().getSessionId();
            final HttpServletRequest request = attributes.getRequest();
            final HttpServletResponse response = attributes.getResponse();
            final String key = sessionId + "-" + request.getServletPath();
            // 如果缓存中有这个url视为重复提交
            if (redisUtils.get(key) == null) {
                obj = pjp.proceed();
                redisUtils.set(key, 0, 2);
            } else {
                log.error("重复提交");
                response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
                response.setContentType("application/json;charset=UTF-8");
                response.getOutputStream().write(buildFailureMsg("重复提交,请稍后再提交").toString().getBytes("utf-8"));
            }
        }
        catch (final Throwable e) {
            e.printStackTrace();
            log.error("验证重复提交时出现未知异常!");
            return buildFailureMsg("重复提交出现问题").toJSONString();
        }
        return obj;
    }

    /**
     * 自定义错误信息
     * @author 溪云阁
     * @param errMsg
     * @return JSONObject
     */
    private JSONObject buildFailureMsg(String errMsg) {
        final JSONObject json = new JSONObject();
        json.put("respStatus", "01");
        json.put("respDesc", errMsg);
        json.put("data", null);
        return json;
    }
}

新增redis配置

# redis地址
spring.redis.host: 127.0.0.1
# redis端口号
spring.redis.port: 6379
# redis密码,如果没有不用填写,建议还是得有
spring.redis.password: 123456
# 最大活跃连接数,默认是8
spring.redis.lettuce.pool.maxActive: 100
# 最大空闲连接数 ,默认是8
spring.redis.lettuce.pool.maxIdle: 100
# 最小空闲连接数 ,默认是0
spring.redis.lettuce.pool.minIdle: 0

新建测试类

/**
 * @author:溪云阁
 * @date:2020年5月24日
 */
@SuppressWarnings("deprecation")
@Api(tags = { "WEB服务:数据接口" })
@RestController
@RequestMapping("web/submit")
public class SubmitController {

    /**
     * 获取字符串信息
     * @author 溪云阁
     * @param id
     * @param name
     * @return ResponseMsg<JSONObject>
     */
    @ApiOperation(value = "获取字符串信息")
    @GetMapping(value = "/getString", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @NoRepeatSubmit
    @SneakyThrows(CommonRuntimeException.class)
    public ResponseMsg<JSONObject> getString(@RequestParam("id") String id, @RequestParam("name") String name) {
        final JSONObject json = new JSONObject();
        json.put("id", id);
        json.put("name", name);
        return MsgUtils.buildSuccessMsg(json);
    }

}

springboot2.2.X手册:分布式系统下,重复提交的解决方案

 

在进行接口调试中,我们点击多次提交,快速一些,可以看到提示,证明成功。

当你的服务进行拓展的时候,进行提交后,只会去redis进行验证,从而可以实现集群化部署而不用担心单个服务重复提交问题。

--END--

作者:@溪云阁

如需要源码,转发,关注后私信我。

部分图片或代码来源网络,如侵权请联系删除,谢谢!