作者 | 慕容千语

来源 | http://suo.im/5PaEZI

在实际的开发项目中,一个对外暴露的接口往往会面临,瞬间大量的重复的请求提交,如果想过滤掉重复请求造成对业务的伤害,那就需要实现幂等

我们来解释一下幂等的概念:

任意多次执行所产生的影响均与一次执行的影响相同。按照这个含义,最终的含义就是 对数据库的影响只能是一次性的,不能重复处理。

如何保证其幂等性,通常有以下手段:

1、数据库建立唯一性索引,可以保证最终插入数据库的只有一条数据
2、token机制,每次接口请求前先获取一个token,然后再下次请求的时候在请求的header体中加上这个token,后台进行验证,如果验证通过删除token,下次请求再次判断token
3、悲观锁或者乐观锁,悲观锁可以保证每次for update的时候其他sql无法update数据(在数据库引擎是innodb的时候,select的条件必须是唯一索引,防止锁全表)
4、先查询后判断,首先通过查询数据库是否存在数据,如果存在证明已经请求过了,直接拒绝该请求,如果没有存在,就证明是第一次进来,直接放行。

redis实现自动幂等的原理图:

一、搭建redis的服务Api

1、首先是搭建redis服务器。

2、引入springboot中到的redis的stater,或者Spring封装的jedis也可以,后面主要用到的api就是它的set方法和exists方法,这里我们使用springboot的封装好的redisTemplate

/*** redis工具类*/
@Component
publicclassRedisService{@AutowiredprivateRedisTemplate redisTemplate;/*** 写入缓存* @param key* @param value* @return*/publicbooleanset(finalString key, Object value) {boolean result = false;try{ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();operations.set(key, value);result = true;} catch(Exception e) {e.printStackTrace();}return result;}/*** 写入缓存设置时效时间* @param key* @param value* @return*/publicboolean setEx(finalString key, Object value, Long expireTime) {boolean result = false;try{ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();operations.set(key, value);redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);result = true;} catch(Exception e) {e.printStackTrace();}return result;}/*** 判断缓存中是否有对应的value* @param key* @return*/publicboolean exists(finalString key) {return redisTemplate.hasKey(key);}/*** 读取缓存* @param key* @return*/publicObjectget(finalString key) {Object result = null;ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();result = operations.get(key);return result;}/*** 删除对应的value* @param key*/publicboolean remove(finalString key) {if(exists(key)) {Booleandelete= redisTemplate.delete(key);returndelete;}returnfalse;}
}

二、自定义注解AutoIdempotent

自定义一个注解,定义此注解的主要目的是把它添加在需要实现幂等的方法上,凡是某个方法注解了它,都会实现自动幂等。后台利用反射如果扫描到这个注解,就会处理这个方法实现自动幂等,使用元注解ElementType.METHOD表示它只能放在方法上,etentionPolicy.RUNTIME表示它在运行时。

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public@interfaceAutoIdempotent{
}

三、token创建和检验

1、token服务接口

我们新建一个接口,创建token服务,里面主要是两个方法,一个用来创建token,一个用来验证token。创建token主要产生的是一个字符串,检验token的话主要是传达request对象,为什么要传request对象呢?主要作用就是获取header里面的token,然后检验,通过抛出的Exception来获取具体的报错信息返回给前端。

publicinterfaceTokenService{/*** 创建token* @return*/public  String createToken();/*** 检验token* @param request* @return*/publicboolean checkToken(HttpServletRequest request) throwsException;
}

2、token的服务实现类

token引用了redis服务,创建token采用随机算法工具类生成随机uuid字符串,然后放入到redis中(为了防止数据的冗余保留,这里设置过期时间为10000秒,具体可视业务而定),如果放入成功,最后返回这个token值。checkToken方法就是从header中获取token到值(如果header中拿不到,就从paramter中获取),如若不存在,直接抛出异常。这个异常信息可以被拦截器捕捉到,然后返回给前端。

@Service
publicclassTokenServiceImplimplementsTokenService{@AutowiredprivateRedisService redisService;/*** 创建token** @return*/@OverridepublicString createToken() {String str = RandomUtil.randomUUID();StrBuilder token = newStrBuilder();try{token.append(Constant.Redis.TOKEN_PREFIX).append(str);redisService.setEx(token.toString(), token.toString(),10000L);boolean notEmpty = StrUtil.isNotEmpty(token.toString());if(notEmpty) {return token.toString();}}catch(Exception ex){ex.printStackTrace();}returnnull;}/*** 检验token** @param request* @return*/@Overridepublicboolean checkToken(HttpServletRequest request) throwsException{String token = request.getHeader(Constant.TOKEN_NAME);if(StrUtil.isBlank(token)) {// header中不存在tokentoken = request.getParameter(Constant.TOKEN_NAME);if(StrUtil.isBlank(token)) {// parameter中也不存在tokenthrownewServiceException(Constant.ResponseCode.ILLEGAL_ARGUMENT, 100);}}if(!redisService.exists(token)) {thrownewServiceException(Constant.ResponseCode.REPETITIVE_OPERATION, 200);}boolean remove = redisService.remove(token);if(!remove) {thrownewServiceException(Constant.ResponseCode.REPETITIVE_OPERATION, 200);}returntrue;}
}

四、拦截器的配置

1、web配置类,实现WebMvcConfigurerAdapter,主要作用就是添加autoIdempotentInterceptor到配置类中,这样我们到拦截器才能生效,注意使用@Configuration注解,这样在容器启动是时候就可以添加进入context中。

@Configuration
publicclassWebConfigurationextendsWebMvcConfigurerAdapter{@ResourceprivateAutoIdempotentInterceptor autoIdempotentInterceptor;/*** 添加拦截器* @param registry*/@Overridepublicvoid addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(autoIdempotentInterceptor);super.addInterceptors(registry);}
}

2、拦截处理器:主要的功能是拦截扫描到AutoIdempotent到注解到方法,然后调用tokenService的checkToken()方法校验token是否正确,如果捕捉到异常就将异常信息渲染成json返回给前端

/*** 拦截器*/
@Component
publicclassAutoIdempotentInterceptorimplementsHandlerInterceptor{@AutowiredprivateTokenService tokenService;/*** 预处理** @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublicboolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throwsException{if(!(handler instanceofHandlerMethod)) {returntrue;}HandlerMethod handlerMethod = (HandlerMethod) handler;Method method = handlerMethod.getMethod();//被ApiIdempotment标记的扫描AutoIdempotent methodAnnotation = method.getAnnotation(AutoIdempotent.class);if(methodAnnotation != null) {try{return tokenService.checkToken(request);// 幂等性校验, 校验通过则放行, 校验失败则抛出异常, 并通过统一异常处理返回友好提示}catch(Exception ex){ResultVo failedResult = ResultVo.getFailedResult(101, ex.getMessage());writeReturnJson(response, JSONUtil.toJsonStr(failedResult));throw ex;}}//必须返回true,否则会被拦截一切请求returntrue;}@Overridepublicvoid postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throwsException{}@Overridepublicvoid afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throwsException{}/*** 返回的json值* @param response* @param json* @throws Exception*/privatevoid writeReturnJson(HttpServletResponse response, String json) throwsException{PrintWriter writer = null;response.setCharacterEncoding("UTF-8");response.setContentType("text/html; charset=utf-8");try{writer = response.getWriter();writer.print(json);} catch(IOException e) {} finally{if(writer != null)writer.close();}}
}

五、测试用例

1、模拟业务请求类

首先我们需要通过/get/token路径通过getToken()方法去获取具体的token,然后我们调用testIdempotence方法,这个方法上面注解了@AutoIdempotent,拦截器会拦截所有的请求,当判断到处理的方法上面有该注解的时候,就会调用TokenService中的checkToken()方法,如果捕获到异常会将异常抛出调用者,下面我们来模拟请求一下:

@RestController
publicclassBusinessController{@ResourceprivateTokenService tokenService;@ResourceprivateTestService testService;@PostMapping("/get/token")publicString  getToken(){String token = tokenService.createToken();if(StrUtil.isNotEmpty(token)) {ResultVo resultVo = newResultVo();resultVo.setCode(Constant.code_success);resultVo.setMessage(Constant.SUCCESS);resultVo.setData(token);returnJSONUtil.toJsonStr(resultVo);}returnStrUtil.EMPTY;}@AutoIdempotent@PostMapping("/test/Idempotence")publicString testIdempotence() {String businessResult = testService.testIdempotence();if(StrUtil.isNotEmpty(businessResult)) {ResultVo successResult = ResultVo.getSuccessResult(businessResult);returnJSONUtil.toJsonStr(successResult);}returnStrUtil.EMPTY;}
}

2、使用postman请求

首先访问get/token路径获取到具体到token:

利用获取到到token,然后放到具体请求到header中,可以看到第一次请求成功,接着我们请求第二次:

第二次请求,返回到是重复性操作,可见重复性验证通过,再多次请求到时候我们只让其第一次成功,第二次就是失败:

六、总结

本篇博客介绍了使用springboot和拦截器、redis来优雅的实现接口幂等,对于幂等在实际的开发过程中是十分重要的,因为一个接口可能会被无数的客户端调用,如何保证其不影响后台的业务处理,如何保证其只影响数据一次是非常重要的,它可以防止产生脏数据或者乱数据,也可以减少并发量,实乃十分有益的一件事。而传统的做法是每次判断数据,这种做法不够智能化和自动化,比较麻烦。而今天的这种自动化处理也可以提升程序的伸缩性。

【热门内容】
Redis 限流的 3 种方式,还有谁不会!多账号统一登陆,账号模块的系统设计SpringBoot 那些自带 Buff 的工具类,你用过几个?增加了一行代码,让我们提高了 3000% 的性能300多本程序员经典技术书籍高清PDF
加我微信,备注:资料扫码备注:资料,自动获取

瞬间几千次的重复提交,我用 SpringBoot+Redis 扛住了相关推荐

  1. springboot 订单重复提交_瞬间几千次的重复提交,我用Spring Boot+Redis扛住了

    在实际的开发项目中,一个对外暴露的接口往往会面临,瞬间大量的重复的请求提交,如果想过滤掉重复请求造成对业务的伤害,那就需要实现幂等! 我们来解释一下幂等的概念: 任意多次执行所产生的影响均与一次执行的 ...

  2. 瞬间几千次的重复提交,我用 SpringBoot+Redis 扛住了!

    来源:jianshu.com/p/c806003a8530 前言: 在实际的开发项目中,一个对外暴露的接口往往会面临很多次请求,我们来解释一下幂等的概念:任意多次执行所产生的影响均与一次执行的影响相同 ...

  3. 瞬间几千次的重复提交,我用 Spring Boot + Redis 扛住了!

    前言: 在实际的开发项目中,一个对外暴露的接口往往会面临很多次请求,我们来解释一下幂等的概念:任意多次执行所产生的影响均与一次执行的影响相同.按照这个含义,最终的含义就是 对数据库的影响只能是一次性的 ...

  4. spring 两次进入拦截器_Spring Boot+Redis 扛住,瞬间千次重复提交(实例)

    前言: 在实际的开发项目中,一个对外暴露的接口往往会面临,瞬间大量的重复的请求提交,如果想过滤掉重复请求造成对业务的伤害,那就需要实现幂等! 我们来解释一下幂等的概念: 任意多次执行所产生的影响均与一 ...

  5. springboot redis token_Spring Boot+Redis 扛住,瞬间千次重复提交(实例)

    前言: 在实际的开发项目中,一个对外暴露的接口往往会面临,瞬间大量的重复的请求提交,如果想过滤掉重复请求造成对业务的伤害,那就需要实现幂等! 我们来解释一下幂等的概念: 任意多次执行所产生的影响均与一 ...

  6. redis+aop防重复提交

    文章目录 1.防重复提交注解 2.redis分布式锁 3.防止重复提交Aop 之前有记录一篇用redis+拦截器防重复提交的内容: redis+拦截器防重复提交 1.防重复提交注解 @Target(E ...

  7. query 防止ajax重复提交

    项目用到js了,首选jquery,能用库用库,原则. 碰到重复提交的问题,禁止住才行.百度google,还是Google给力. 知乎上有个高人,总结了四种,利用Jquery .post方法返回jqXH ...

  8. java mysql防重复提交_防止数据重复提交的6种方法(超简单)!

    有位朋友,某天突然问磊哥:在 Java 中,防止重复提交最简单的方案是什么? 这句话中包含了两个关键信息,第一:防止重复提交:第二:最简单. 于是磊哥问他,是单机环境还是分布式环境? 得到的反馈是单机 ...

  9. 最简单的6种防止数据重复提交的方法!(干货)

    有位朋友,某天突然问磊哥:在 Java 中,防止重复提交最简单的方案是什么? 这句话中包含了两个关键信息,第一:防止重复提交:第二:最简单. 于是磊哥问他,是单机环境还是分布式环境? 得到的反馈是单机 ...

最新文章

  1. pc端汽车obd软件下载?_常用的ERP软件电脑端下载
  2. python怎么读取txt文件内容然后保存到excel-Python实现读取txt文件并转换为excel的方法示例...
  3. python地理数据处理 下载_python-doc/将Python用于地理空间数据处理.md at master · zhuxinyizhizun/python-doc · GitHub...
  4. k30pro杀进程严重怎么解决_命运2掉帧严重怎么解决?提高帧数方法介绍
  5. (转载)一套完整的UI设计规范手册(IOS版)
  6. Matplotlib作业2
  7. UML类图中会涉及到的一些概念、关系
  8. 一枚比特币30多万,家用电脑24小时挖矿,多久才能挖到一枚?
  9. Premiere 五套特效转场插件合集 FilmImpact Transition Packs V3.6.11
  10. 牛顿莱布尼茨计算机公式,牛顿莱布尼茨公式
  11. 寻找2020java 蓝桥杯
  12. 计算机专业大学分数线低的,成绩徘徊在985、211分数线边缘,这4所大学考上容易,退档率极低...
  13. SECS协议的SML表示
  14. 如何在CSDN上上传资源
  15. 【FND】EBS中Java并发程序开发
  16. html5随鼠标移动动画,使用JS实现气泡跟随鼠标移动的动画效果
  17. 猿辅导python编程课网课怎么样_猿辅导网课怎么样 靠谱吗
  18. 说得清的Epoll原理
  19. 微软crm 开发笔记 系统配置使用
  20. Hudson使用之Tomcat运行Hudson

热门文章

  1. CSDN/博客园等博客的文章搬家到wordpress网站
  2. Python中 val[0:-1] 切片选择
  3. 心态很容易受别人影响_很容易被别人说的话影响心情,应该怎么办?
  4. 基于计算机视觉的食物新鲜度分级检测
  5. 逆向工程核心原理——消息钩取
  6. Cesium(六)淹没分析(polygon版本)
  7. kali:ARP欺骗
  8. 分析师:一季度特斯拉电动汽车产量和交付量将低于华尔街预期
  9. 特斯拉第二季度交付9.52万辆电动汽车创纪录 股价盘后上涨7%
  10. 20221211英语学习