上一篇: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文件
org.springframework.bootspring-boot-starter-webcom.bootsmodule-boots-redis1.0.0.RELEASEorg.springframework.bootspring-boot-starter-aop
新建重复提交注解
/** * 重复提交注解 * @author:溪云阁 * @date:2020年5月24日 */public @interface NoRepeatSubmit {}
新建重复提交拦截
/** * 重复提交拦截 * @author:溪云阁 * @date:2020年5月24日 */@Aspect@Component@Slf4jpublic 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# 最大活跃连接数,默认是8spring.redis.lettuce.pool.maxActive: 100# 最大空闲连接数 ,默认是8spring.redis.lettuce.pool.maxIdle: 100# 最小空闲连接数 ,默认是0spring.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 */ @ApiOperation(value = "获取字符串信息") @GetMapping(value = "/getString", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) @NoRepeatSubmit @SneakyThrows(CommonRuntimeException.class) public ResponseMsg 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--
作者:@溪云阁
如需要源码,转发,关注后私信我。
部分图片或代码来源网络,如侵权请联系删除,谢谢!