什么是接口幂等

幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中,即f(f(x)) = f(x).简单的来说就是一个操作多次执行产生的结果与一次执行产生的结果一致。有些系统操作天生就具有幂等性例如数据库的select语句,但更多时候是需要程序员来做保证的,尤其是在分布式系统环境中,接口能不能做到保证幂等性对系统的影响可能是非常大的,例如很常见的支付下单等场景,由于分布式环境中网络的复杂性,用户误操作,网络抖动,消息重复,服务超时导致业务自动重试等等各种情况都可能会使线上数据产生了不一致,造成生产事故。

实现方案

1、查询操作:查询一次和查询多次,在数据不变的情况下,查询结果是一样的。select是天然的幂等操作;2、删除操作:删除操作也是幂等的,删除一次和多次删除都是把数据删除。(注意可能返回结果不一样,删除的数据不存在,返回0,删除的数据多条,返回结果多个) ;3、唯一索引:利用数据库新增脏数据。比如:支付宝的资金账户,支付宝也有用户账户,每个用户只能有一个资金账户,怎么防止给用户创建资金账户多个,那么给资金账户表中的用户ID加唯一索引,所以一个用户新增成功一个资金账户记录。要点:唯一索引或唯一组合索引来防止新增数据存在脏数据(当表存在唯一索引,并发时新增报错时,再查询一次就可以了,数据应该已经存在了,返回结果即可);4、token机制:防止页面重复提交。采用token加redis或token加jvm内存。处理流程:1. 数据提交前要向服务的申请token,token放到redis或jvm内存,token有效时间;2. 提交后后台校验token,同时删除token,生成新的token返回。token特点:要申请,一次有效性,可以限流。注意:redis要用删除操作来判断token,删除成功代表token校验通过,如果用select+delete来校验token,存在并发问题,不建议使用;5、分布式锁:如果是分布式系统的话,构建全局唯一索引会比较困难,比如唯一性的字段就没有办法确定。这时候可以引入分布式锁,通过第三方的系统(Redis或Zookeeper),在业务系统插入数据或者更新数据前,需要先获取分布式锁,然后才能做操作,操作完成之后就释放锁。这样其实是把单机系统里面多线程并发锁的思路引入了多个系统的场景,也就是分布式系统中的解决思路。要点:某个长流程处理过程要求不能并发执行,可以在流程执行之前根据某个标志(用户ID+后缀等)获取分布式锁,其他流程执行时获取锁就会失败,也就是同一时间该流程只能有一个能执行成功,执行完成后,释放分布式锁(分布式锁要第三方系统提供)。6、select + insert:在设计单据相关的业务,或者是任务相关的业务,肯定会涉及到状态机(状态变更图)。简单理解,就是业务单据上面有个状态的字段,状态在不同的情况下会发生变更,一般情况下存在有限状态机。这时候,如果状态机已经处于下一个状态,这时候来了一个上一个状态的变更,理论上是不能够变更的,这样的话,保证了有限状态机的幂等。注意:订单等单据类业务,存在很长的状态流转,一定要深刻理解状态机,对业务系统设计能力提高有很大帮助。

基于token+Redis的实现方案

环境:springboot2.2.11.RELEASE + Redis

  • pom.xml 依赖
org.springframework.bootspring-boot-starter-data-redisorg.springframework.bootspring-boot-starter-weborg.apache.commonscommons-pool2
  • 自定义注解类,有该注解的需要验证token是否有效
@Documented@Inherited@Retention(RUNTIME)@Target({ METHOD, TYPE})public @interface ApiIdempotent {}
  • 拦截器定义,拦截请求方法进行token有效性验证
public class MethodIdempotentCheck implements HandlerInterceptor {private Logger logger = LoggerFactory.getLogger(MethodIdempotentCheck.class) ;@Resourceprivate StringRedisTemplate stringRedisTemplate ;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {if (handler instanceof HandlerMethod) {HandlerMethod handlerMethod = (HandlerMethod) handler ;Method method = handlerMethod.getMethod() ;Class> clazz = method.getClass() ;if (clazz.isAnnotationPresent(ApiIdempotent.class)) {if (!checkToken(request)) {failure(response) ;return false ;}} else {if (method.isAnnotationPresent(ApiIdempotent.class)) {if (!checkToken(request)) {failure(response) ;return false ;}}}}return true ;}private void failure(HttpServletResponse response) throws Exception {response.setContentType("application/json;charset=utf-8") ;response.getWriter().write("{"code": -1, "message": "重复提交"}") ;}private boolean checkToken(HttpServletRequest request) {logger.info("验证token") ;String token = request.getParameter("access-token") ;if (token == null || token.length() == 0) {token = request.getHeader("access-token") ;}logger.info("获取token:{}", token) ;if (token == null || token.length() == 0) {return false ;}boolean exists = stringRedisTemplate.hasKey(token) ;if (!exists) {return false ;}return stringRedisTemplate.delete(token) ;}}token会从header中获取请求参数中获取。
  • WebConfig 配置拦截器
@Configurationpublic class WebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(tokenInterceptor()) ;}@Beanpublic HandlerInterceptor tokenInterceptor() {return new MethodIdempotentCheck() ;}}
  • Controller 测试
@RestController@RequestMapping("/demo")public class DemoController {@Resourceprivate StringRedisTemplate stringRedisTemplate ;/** * 生成Token;这里token的有效期设置了10分钟。 * @return */@GetMapping("/create")public Object create() {String access_token = UUID.randomUUID().toString() ;stringRedisTemplate.opsForValue().set(access_token, access_token, 10, TimeUnit.MINUTES) ;return access_token ;}@PostMapping("/save")@ApiIdempotentpublic Object business(@RequestBody Users user) {Map result = new HashMap<>() ;// todo save Usersresult.put("code", 0) ;result.put("message", "创建成功") ;return result ;}}

business 方法加入了@ApiIdempotent注解,表示该方法需要进行token验证是否有效的请求

整个请求流程要先获取token,然后将得到的token放入header中或者请求参数中。

测试:

获取token:

请求业务方法将获取的token 添加到header中

再次请求:

每次请求token的验证是通过删除token进行的,所以当第二次再请求时,redis中已经没有了token所以这里就提示:重复提交了。

完毕!!!

给个关注,转发,谢谢

SpringBoot RabbitMQ消息可靠发送与接收

SpringBoot中使用Cache及JSR107的使用

SpringBoot开发自己的Starter

SpringBoot开发自己的@Enable功能

Java线上CPU100% 问题排查

Restful API设计规范

SpringCloud Nacos 服务动态配置

SpringCloud Nacos 服务消费者

SpringCloud Nacos 服务提供者

SpringCloud Alibaba 之 Nacos 服务

Spring Cloud Nacos 开启权限验证

SpringCloud Nacos 整合feign

SpringCloud Hystrix实现资源隔离应用

Alibaba Sentinel动态规则(Nacos数据源)

SpringCloud zuul 动态网关配置

springboot token_Springboot接口幂等性基于token实现方案相关推荐

  1. Spring Boot 实现接口幂等性的 4 种方案!还有谁不会?

    点击关注公众号,Java干货及时送达 作者:超级小豆丁 链接:mydlq.club/article/94 一.什么是幂等性 幂等是一个数学与计算机学概念,在数学中某一元运算为幂等时,其作用在任一元素两 ...

  2. Spring Boot 实现接口幂等性的 4 种方案作者:小小怪下士

    一.什么是幂等性 幂等是一个数学与计算机学概念,在数学中某一元运算为幂等时,其作用在任一元素两次后会和其作用一次的结果相同.在计算机中编程中,一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行 ...

  3. Spring Boot 实现接口幂等性的 4 种方案

    Java派 2023-02-17 09:00 发表于湖南 Java派 专注Java相关技术栈:Spring全家筒.Docker.k8s.Mysql.集群.微服务.中间件等知识. 18篇原创内容 公众号 ...

  4. Redis 处理接口幂等性的两种方案

    前言:接口幂等性问题,对于开发人员来说,是一个跟语言无关的公共问题.对于一些用户请求,在某些情况下是可能重复发送的,如果是查询类操作并无大碍,但其中有些是涉及写入操作的,一旦重复了,可能会导致很严重的 ...

  5. 处理接口幂等性的两种常见方案

    在上周发布的 TienChin 项目视频中,我和大家一共梳理了六种幂等性解决方案,接口幂等性处理算是一个非常常见的需求了,我们在很多项目中其实都会遇到.今天我们来看看两种比较简单的实现思路. 1. 接 ...

  6. 【SpringBoot应用篇】SpringBoot+Redis实现接口幂等性校验

    [SpringBoot应用篇]SpringBoot+Redis实现接口幂等性校验 幂等性 解决方法 Pom token令牌 yml @ApiIdempotentAnn ApiIdempotentInt ...

  7. SpringBoot接口幂等性实现的4种方案!

    点击上方 好好学java ,选择 星标 公众号重磅资讯,干货,第一时间送达今日推荐:分享一套基于SpringBoot和Vue的企业级中后台开源项目,这个项目有点哇塞!个人原创100W +访问量博客:点 ...

  8. SpringBoot 接口幂等性的实现方案

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 作者:超级小豆丁 http://www.mydlq.clu ...

  9. 4种SpringBoot 接口幂等性的实现方案!最后一个80%以上的开发会踩坑

    点击上方 蓝字 关注我们! 来源:mydlq.club/article/94/ 一.什么是幂等性 二.什么是接口幂等性 三.为什么需要实现幂等性 四.引入幂等性后对系统的影响 五.Restful AP ...

最新文章

  1. GraphSAGE: GCN落地必读论文
  2. Python多线程学习(下)
  3. 'SVN更新' has encountered a problem :An internal error occurred during: svn错误
  4. 从MVP到微软产品经理的几点心得
  5. 怎么使用小爱同学音响_智能音响购买指南!!!
  6. python 装饰器常见场景与用法
  7. kettle官网下载地址
  8. java制作主页,JSP教程基础篇之简单首页制作
  9. 网件r7000梅林系统虚拟内存创建失败,提示USB磁盘读写速度不满足要求解决办法,有需要创建虚拟内存吗??
  10. Kuma初步学习笔记-universal 模式
  11. HNUCM 道具的魅力值(贪心算法)
  12. 强化学习《蘑菇书 EasyRL第一章 概览》
  13. nginx error.log中的 favicon.ico 错误
  14. p2.第一章 Python基础入门 -- 冯诺依曼体系和计算机基础 (二)
  15. OSChina 周一乱弹 —— 济南源创会特刊
  16. 野百合的春天 ——布鲁斯鲍文
  17. rust高墙大门怎么造_rust自己建的高墙怎么拆 | 手游网游页游攻略大全
  18. [2]无线通信--CDMA多址技术(1)
  19. 大飞品树莓——自定制树莓派4B系统安装
  20. AviConverter v4.0是一款非常棒的Avi视频转换工具

热门文章

  1. sparkstreaming监听hdfs目录_flume kafka和sparkstreaming整合
  2. 分布式从mysql查数据_技术分享 | 从库数据的查找和参数 slave_rows_search_algorithms...
  3. Python用两个骰子玩掷骰子的游戏。本金为10元,当掷出“7”即获得奖金4元,否则扣除1元。编程测算玩到多少手时钱全部输完,及哪一手时钱数最多。
  4. Python基础教程:type()函数-动态创建类
  5. python基础教程:filter(),map()函数用法
  6. Python基础教程:生成器
  7. linux c语言中如何通过进程名获取进程PID(awk命令行指令)popen、pclose
  8. 为什么ubuntu64位下C语言for循环不能超过2147483647次?(size_t )
  9. Test_data detection-Illumination
  10. printf与sprintf