Java接口幂等性设计场景解决方案v1.0

1-面试&实际开发场景1-1面试场景题目

分布式服务接口的幂等性如何设计(比如不能重复扣款)?

1-2 题目分析

一个分布式系统中的某个接口,要保证幂等性,如何保证?这个事,其实是你做分布式系统的时候必须要考虑的一个生产环境的技术问题,为什么呢?

实际案例1:

假如你有个服务提供一个付款业务的接口,而这个服务分别部署在5台服务器上,然后用户在前端操作时,不知道为啥,一个订单不小心发起了两次支付请求,然后这俩请求分散在了这个服务部署的不同的服务器上,这下好了,一个订单扣款扣了两次。

实际案例2:

订单系统调用支付系统进行支付,结果不消息网络,然后订单系统走了前面我们看到的重试retry机制,那就给你重试一次吧,那么支付系统收到了一个支付请求两次,而且因为负载均衡算法落在了不同的机器上。

小结:

所以你必须得知道这事,否则你做出来的分布式系统恐怕很容易埋坑!

2-幂等性介绍2-1-概念:

用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。

举个简单的例子:那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常了,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额发现多扣钱了,流水记录也变成了两条。在以前的单应用系统中,我们只需要对数据操作加入事务即可,发生错误的时候立即回滚,但是再响应客户端的时候也有可能网络中断或者异常等等情况。

2-2- 产生幂等性问题的原因:网络问题/用户误操作/恶意操作,用户点击了多次网络问题,微服务重试retry网络问题很常见,100次请求,都ok;1万次请求可能1次超时会重试;10万次可能10次超时会重试,100万次可能100次超时会重试;如果100个请求重复了,你没处理,导致订单扣款2次,100个订单都扣错了,每天被100个用户投诉,一个月被3000个用户投诉。2-3- 使用幂等性的场景前端重复提交:前端瞬时点击多次造成表单重复提交接口超时重试:接口可能会因为某些原因而调用失败,处于容错性考虑会加上失败重试的机制。如果接口调用一半,再次调用就会因为脏数据的存在而产生异常消息重复消费:在使用消息中间件来处理消息队列,且手动ack确认消息被正常消费时。如果消费者突然断开链接,那么已经执行了一半的消息会重新放回队列。被其他消费者重新消费时就会导致结果异常,如数据库重复数据, 数据库数据冲突,资源重复等请求重发:网络抖动引发的nginx重发请求,造成重复调用。3-幂等性的解决方案3-1- Insert接口幂等性1.使用分布式锁保证幂等性

秒杀场景下,一个用户只能购买同一商品一次的解决方法:采用用户ID+商品ID,存储到redis中,使用redis中的setNX操作,等待自然过期。

2.使用token机制保证幂等性

用户注册时,用户点击注册按钮多次,是不是会注册多个用户?我们可以在用户进入注册页面后由后台生成一个token,传给前端页面,用户在点击提交时,将token带给后台,后台使用该token作为分布式锁,setNX操作,执行成功后不释放锁,等待自然过期。

3.使用mysql unique key 保证幂等性

用户注册时,用户点击注册按钮多次,是不是会注册多个用户? 我们可以使用手机号作为mysql用户表唯一key。也就是一个手机号只能注册一次。

3-2- Update接口幂等性

update操作可能存在幂等性的问题:

1.用户更改个人信息,疯狂点击按钮,不会发生幂等性问题,因为数据始终为修改后的数据。

2.用户购买商品,用户在点击后,网络出现问题,可能再次点击,这样就会出现幂等性问题,导致购买了多次,可以使用乐观锁

update order set count=count-1,version=version+1 where id=1 and version=13-3- Delete接口幂等性

根据唯一id删除不会出现幂等性问题,因为第二次删除的时候mysql中已经不存在该数据

3-4- Select接口幂等性

查询操作不会改变数据,所以是天然的幂等性操作。

3-5- 混合操作(一个接口包含多种操作)

使用Token机制,或使用Token + 分布式锁的方案来解决幂等性问题。

4-幂等性解决方案实现思路4-1- Token机制实现

通过Token 机制实现接口的幂等性,这是一种比较通用性的实现方法。

具体流程步骤:

客户端会先发送一个请求去获取Token,服务端会生成一个全局唯一的ID作为Token保存在Redis中,同时把这个ID返回给客户端;客户端第二次调用业务请求的时候必须携带这个Token服务端会校验这个 Token,如果校验成功,则执行业务,并删除Redis中的 Token如果校验失败,说明Redis中已经没有对应的 Token,则表示重复操作,直接返回指定的结果给客户端。4-2- 基于MySQL实现

通过MySQL唯一索引的特性实现接口的幂等性。

具体流程步骤:

建立一张去重表,其中某个字段需要建立唯一索引;客户端去请求服务端,服务端会将这次请求的一些信息插入这张去重表中;因为表中某个字段带有唯一索引,如果插入成功,证明表中没有这次请求的信息,则执行后续的业务逻辑;如果插入失败,则代表已经执行过当前请求,直接返回。4-3- 基于Redis实现

通过RedisSETNX命令实现接口的幂等性。

SETNX key value:当且仅当key不存在时将key的值设为value;若给定的key已经存在,则SETNX不做任何动作。设置成功时返回1,否则返回0

具体流程步骤:

客户端先请求服务端,会拿到一个能代表这次请求业务的唯一字段;将该字段以SETNX的方式存入Redis中,并根据业务设置相应的超时时间;如果设置成功,证明这是第一次请求,则执行后续的业务逻辑;如果设置失败,则代表已经执行过当前请求,直接返回。5-幂等性解决方案案例实现5-1-基于Token机制的实现5-1-1-实现思路

为需要保证幂等性的每一次请求创建一个唯一的标识token,先获取token,并将此token存入到redis,请求接口时,将此token放在header或者作为请求参数请求接口,后端接口判断redis中是否存在此token;

如果存在,则正常处理业务逻辑,并从redis中删除此token,那么,如果是重复请求,由于token已经被删除,则不能能够通过校验,返回重复提交如果不存在,说明参数不合法或者是重复请求,返回提示即可5-1-2-请求流程当页面加载的时候通过接口获取token当访问接口时,会经过拦截器,如果发现该接口中有自定义的幂等性注解,说明该接口需要验证幂等性(查看请求头里是否有key=token的值,如果有,并且删除成功,那么接口就访问成功,否则为重复提交;如果发现该接口没有自定义的幂等性注解,则放行。5-1-3-代码演示1、使用的技术springBootredis自定义幂等性注解+拦截器请求拦截Jmeter压测工具2、创建项目
Java接口幂等性设计场景解决方案v1.0
3、导入pom.xml<?xml version=”1.0″ encoding=”UTF-8″?> <project xmlns=“http://maven.apache.org/POM/4.0.0” xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd”> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>springBoot-idempotent</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.2.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies> </project>4、自定义注解

该注解的目的是为了实现幂等性的校验,即添加了该注解的接口要实现幂等性验证

package com.ldp.idempotent.annotation; import java.lang.annotation.*; / * 自定义注解 * 说明:添加了该注解的接口要实现幂等性验证 */ @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ApiIdempotentAnn { boolean value() default true; } 5、幂等性拦截器package com.ldp.idempotent.intceptor; import com.ldp.idempotent.annotation.ApiIdempotentAnn; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.PrintWriter; import java.lang.reflect.Method; / * 幂等性拦截器 */ @Component public class ApiIdempotentInceptor extends HandlerInterceptorAdapter { @Autowired private StringRedisTemplate redisTemplate; / * 前置拦截器 *在方法被调用前执行。在该方法中可以做类似校验的功能。如果返回true,则继续调用下一个拦截器。如果返回false,则中断执行, * 也就是说我们想调用的方法 不会被执行,但是你可以修改response为你想要的响应。 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //如果hanler不是和HandlerMethod类型,则返回true if (!(handler instanceof HandlerMethod)) { return true; } //转化类型 final HandlerMethod handlerMethod = (HandlerMethod) handler; //获取方法类 final Method method = handlerMethod.getMethod(); // 判断当前method中是否有这个注解 boolean methodAnn = method.isAnnotationPresent(ApiIdempotentAnn.class); //如果有幂等性注解 if (methodAnn && method.getAnnotation(ApiIdempotentAnn.class).value()) { // 需要实现接口幂等性 //检查token //1.获取请求的接口方法 //查看当前接口的方法之上是否有自定义的注解@ApiIdempotentAnn //如果说包含了,则认为该接口是要进行幂等性校验的接口 //检验token //如果说有,则访问成功,执行逻辑业务,要删除redis中的token //如果说没有,则表示重复调用 //如果说没有包含了,则直接放行 checkToken(request); //如果token有值,说明是第一次调用 if (result) { //则放行 return super.preHandle(request, response, handler); } else {//如果token没有值,则表示不是第一次调用,是重复调用 response.setContentType(“application/json; charset=utf-8”); PrintWriter writer = response.getWriter(); writer.print(“重复调用”); writer.close(); response.flushBuffer(); return false; } } //否则没有该自定义幂等性注解,则放行 return super.preHandle(request, response, handler); } //检查token private boolean checkToken(HttpServletRequest request) { //从请求头对象中获取token String token = request.getHeader(“token”); //如果不存在,则返回false,说明是重复调用 if(token==null || ” “.equals(token)){ return false; } //否则就是存在,存在则把redis里删除token return redisTemplate.delete(token); } //后置,暂时没用 @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { super.postHandle(request, response, handler, modelAndView); } } 6、MVC配置文件package com.ldp.idempotent.config; import com.ldp.idempotent.intceptor.ApiIdempotentInceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; / * mvc配置 */ @Configuration public class MvcConfig implements WebMvcConfigurer { @Autowired private ApiIdempotentInceptor apiIdempotentInceptor; /* 添加自定义拦截器到Springmvc配置中,拦截所有请求 addInterceptor 需要一个实现HandlerInterceptor接口的拦截器实例 addPathPatterns 用于设置拦截器的过滤路径规则;addPathPatterns(“/”)对所有请求都拦截 excludePathPatterns:用于设置不需要拦截的过滤规则 */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(apiIdempotentInceptor).addPathPatterns(“/”); } } 7、接口实现package com.ldp.idempotent.controller; import com.ldp.idempotent.annotation.ApiIdempotentAnn; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; @RestController public class ApiController { @Autowired private StringRedisTemplate stringRedisTemplate; / * 前端获取token,然后把该token放入请求的header中 * * @return */ @GetMapping(“/getToken”) public String getToken() { String token = UUID.randomUUID().toString().substring(1, 9); stringRedisTemplate.opsForValue().set(token, “1”); return token; } //定义int类型的原子类的类 AtomicInteger num=new AtomicInteger(100); / * 主业务逻辑,num–,并且加了自定义接口 * * @return */ @GetMapping(“/submit”) @ApiIdempotentAnn public String submit() { // num– num.decrementAndGet(); return “success”; } / * 查看num的值 * * @return */ @GetMapping(“/getNum”) public String getNum() { return String.valueOf(num.get()); } } 8、PostMan测试获取token

浏览器访问:

http://localhost:9090/getToken,获取token的值
Java接口幂等性设计场景解决方案v1.0
执行幂等性业务接口第一次,在postman中调用当前接口,并在请求头中设置token
Java接口幂等性设计场景解决方案v1.0
第二次,再次postman中访问该业务接口,显示重复调用的提示
Java接口幂等性设计场景解决方案v1.0
查看num的值得接口

浏览器访问:

http://localhost:9090/getNum
Java接口幂等性设计场景解决方案v1.0
9-Jmeter压力测试工具测试

使用方法参考Jmeter压力测试工具使用说明v1.0

10-小结

通过以上代码演示了解到,本案例对submit接口方法使用了基于token的幂等性解决方案,也就是当前submit接口方法只能调用一次,如果由于网络抖动或者网络异常出现多点或者点击多次的情况,就会出现报错提示,不允许调用当前接口,那么也就解决了当前业务接口幂等性的问题。

免责声明:文章内容来自互联网,本站仅作为分享,不对其真实性负责,如有侵权等情况,请与本站联系删除。
转载请注明出处:Java接口幂等性设计场景解决方案v1.0 https://www.dachanpin.com/a/cyfx/11695.html

(0)
如何使用JAVA爬取网站数据?
上一篇 2023-05-12 03:36:00
Java教程:防缓存穿透利器-布隆滤器(BloomFilter)
下一篇 2023-05-12 03:37:03

相关推荐

  • 弹指一挥二十年——新华保险20年发展回顾之创业篇

      首年保费洋溢欢歌笑语的庆典谢幕,接踵而至的是硝烟弥漫的市场竞争。皇城根下,亿兴大厦简陋的职场里,一群人挑灯夜战,正在谋划京城寿险市场的新格局。   探寻新华保险20年成长之路,对于研究中国保险业乃至中国企业的崛起,都有着典型的样板意义。 一、创业篇   按照央行的要求,公司要在六个月内完成新股东的确认、资金注入和人民银行的再度确认审批。为了五亿元的资本金…

    创业分享 2023-05-29
  • 成长衣派童装教你如何稳妥的创业 提醒小心骗子骗局

    成长衣派作为童装加盟行业的领军品牌,成立以来始终本着诚信经营的原则,以加盟商利益最大化为目的,为数千位加盟商提供了优质创富平台。全国上千家门店的成功营业并盈利,正是成长衣派童装加盟稳妥创业的最佳证明。 又到毕业季,一大批毕业生涌入找工作的洪流,不仅毕业生之间竞争压力超大,对于那些跳槽的人来说,就业压力也受到了很大的冲击。怎样才能在发家致富的道路中脱颖而出呢?…

    创业分享 2023-05-30
  • 股票配资平台:外围罕见空头形态冲击A股 创业板反弹遭遇拦路虎

    2017互金高峰论坛召开 易通贷、点融等行业代表畅谈普惠金融 3、操盘牛人,我出钱,您操盘,免费操盘挑战巅峰 1、按天配资,1-5倍融资,随借随还,自在投资时间自由,超低利息 本网部分内容转载自其他媒体,目的在于传递更多信息,并不代表本网赞同其观点,其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不…

    创业分享 2023-05-19
  • 大厂研发成本大曝光,预算竟然这么足?

    近日,腾讯发布《2022 年腾讯研发大数据报告》,披露了 2022 年腾讯在研发投入、研发效能、开源协同等方面的重要数据。 《报告》显示,2022 年腾讯内部研发人员占比达到 74%,这意味着,平均每四个腾讯员工中,就有三个从事研发工作。 上下滑动查看更多报告详情 ▽▽▽ △ 来源腾讯,如侵删 看完报告,不禁感叹腾讯的研发人员占比着实是高,黑马君没忍住也去搜…

    2023-05-12
  • 桂花镇举行返乡务工创业人员恳谈会

    2月14日,由桂花镇党委政府主办的返乡务工创业人员恳谈会暨新春乡情坝坝宴在桂花镇金井村和合广场举行。现场500余名返乡务工人士及村民欢聚一堂,开座谈会、吃坝坝宴、赏歌舞、话家常、体验新春乡情。活动中,乡镇党委政府对在外创业、对家乡发展有贡献的成功人士授予荣誉奖牌,激发他们情系家乡、创业奋进、勤劳致富、繁荣家乡的热情。(全媒体记者 杨雨) (责任编辑…

    创业分享 2023-05-13

发表回复

登录后才能评论

联系我们

在线咨询: QQ交谈

邮件:362039258@qq.com

工作时间:周一至周五,9:30-16:30,节假日休息