springboot2.2.X手册:分布式系统下,重复提交的解决方案
目录
上一篇:springboot2.2.X手册:是时候用Lettuce替换Jedis操作Redis缓存了
上一篇中我们讲解了redis,主要是因为接下来的更新,都会涉及到redis的操作,所以就放在上一篇了。
今天我们主要讲解重复提交的问题,这种问题,算是比较常见,但是又容易出问题,加上现在基本上都是微服务架构,今天来聊一下在分布式系统下,如果防止重复提交。
什么是幂等性
小编以前面试过一家公司,被问到什么是幂等性,当时不懂,就瞎扯了一番,惨遭面试官鄙视。
幂等性指的是多次运算结果一样,用公式来表示就是F(F(x))=F(x)。
在我们的对数据库的操作中,以下操作就是幂等性
select查询就是最基础的幂等性
delete删除也是一样,删除多少次都是一样的结果
update这里分两种,如果是更新某个值,那就是幂等性;如果是更新累加操作的,那就是非幂等性。
insert是非幂等性操作,毕竟每次都增加一条,从而导致数据变化了
重复提交如何产生
重复问题发生的情况比较多,小编总结了一下以下几点
1、提交按钮点击两次
2、浏览器提交后进行后退操作,然后再一次提交
3、使用浏览器的历史记录进行重复提交表单
4、重复的请求浏览器的http请求
5、nginx不断重新发送
6、分布式RPC中,进行了try重试等
基于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);
}
}
在进行接口调试中,我们点击多次提交,快速一些,可以看到提示,证明成功。
当你的服务进行拓展的时候,进行提交后,只会去redis进行验证,从而可以实现集群化部署而不用担心单个服务重复提交问题。
--END--
作者:@溪云阁
如需要源码,转发,关注后私信我。
部分图片或代码来源网络,如侵权请联系删除,谢谢!
上一篇: 状态模式
下一篇: 设计模式梳理——状态模式