微服务与幂等性

随着应用架构由单体架构到微服务架构进行演变,现如今市面上超过50%的应用都会基于分布式或微服务完成系统架构设计。在微服务架构体系内,就会存在若干个微服务,这些服务可能基于RPC或者HTTPS等协议进行通讯。那么既然服务之间存在相互调用,那么必然存在服务调用延迟或者失败的情况,当出现这种问题,服务端会进行重试等操作或客户端有可能会进行多次点击提交。如果这样请求多次的话,那最终处理的数据结果就一定要保证统一,如支付场景。此时就需要通过保证业务幂等性方案来完成。

幂等性简介

幂等本身是一个数学概念。即 f(n) = 1^n ,无论n为多少,f(n)的值永远为1。在编程开发中,对于幂等的定义为:无论对某一个资源操作了多少次,其影响都应是相同的。 换句话说就是:在接口重复调用的情况下,对系统产生的影响是一样的,但是返回值允许不同,如查询。
幂等性包括数据幂等、接口幂等、服务幂等、消息幂等。

以SQL为例:

  • select * from table where id=1。此SQL无论执行多少次,虽然结果有可能出现不同,都不会对数据产生 改变,具备幂等性。
  • insert into table(id,name) values(1,'小莫') 。此SQL如果id或name有唯一性约束,多次操作只允许插 入一条记录,则具备幂等性。如果不是,则不具备幂等性,多次操作会产生多条数据。
  • update table set score=100 where id = 1 。此SQL无论执行多少次,对数据产生的影响都是相同的。具备幂等性。
  • update table set score=50+score where id = 1 。此SQL涉及到了计算,每次操作对数据都会产生影响。 不具备幂等性。
  • delete from table where id = 1 。此SQL多次操作,产生的结果相同,具备幂等性。
    幂等性设计主要从两个维度进行考虑:空间、时间。
  • 空间:定义了幂等的范围,如生成订单的话,不允许出现重复下单。
  • 时间:定义幂等的有效期。有些业务需要永久性保证幂等,如下单、支付等。而部分业务只要保证一段时间幂等即可。
    同时对于幂等的使用一般都会伴随着出现锁的概念,用于解决并发安全问题。

业务与幂等性

在业务开发与分布式系统设计中,幂等性是一个非常重要的概念,有非常多的场景需要考虑幂等性的问题,尤其对于现在的分布式系统,经常性的考虑重试、重发等操作,一旦产生这些操作,则必须要考虑幂等性问题。以交易系统、支付系统等尤其明显,如:

  • 当用户购物进行下单操作,用户操作多次,但订单系统对于本次操作只能产生一个订单。
  • 当用户对订单进行付款,支付系统不管出现什么问题,应该只对用户扣一次款。
  • 当支付成功对库存扣减时,库存系统对订单中商品的库存数量也只能扣减一次。
  • 当对商品进行发货时,也需保证物流系统有且只能发一次货。
    在电商系统中还有非常多的场景需要保证幂等性。但是一旦考虑幂等后,服务逻辑务必会变的更加复杂。因此是否要考虑幂等,需要根据具体业务场景具体分析。而且在实现幂等时,还会把并行执行的功能改为串行化,降低了执行效率。
    此处以下单减库存为例,当用户生成订单成功后,会对订单中商品进行扣减库存。 订单服务会调用库存服务 进行库存扣减。库存服务会完成具体扣减实现。
    现在对于功能调用的设计,有可能出现调用超时,因为出现如网络抖动,虽然库存服务执行成功了,但结果并没有在超时时间内返回,则订单服务也会进行重试。那就会出现问题,stock对于之前的执行已经成功了, 只是结果没有按时返回。而订单服务又重新发起请求对商品进行库存扣减。 此时出现库存扣减两次的问题。 对于这种问题,就需要通过幂等性进行结果。

接口幂等

对于幂等的考虑,主要解决两点前后端交互与服务间交互。这两点有时都要考虑幂等性的实现。从前端的思路解决 的话,主要有三种:前端防重、PRG模式、Token机制。

前端防重

通过前端防重保证幂等是最简单的实现方式,前端相关属性和JS代码即可完成设置。可靠性并不好,有经验的人员 可以通过工具跳过页面仍能重复提交。主要适用于表单重复提交或按钮重复点击。

PRG模式

PRG模式即POST-REDIRECT-GET。当用户进行表单提交时,会重定向到另外一个提交成功页面,而不是停留在原先的表单页面。这样就避免了用户刷新导致重复提交。同时防止了通过浏览器按钮前进/后退导致表单重复提交。 是一种比较常见的前端防重策略。

Token机制

方案介绍

通过token机制来保证幂等是一种非常常见的解决方案,同时也适合绝大部分场景。该方案需要前后端进行一定程 度的交互来完成。

1)服务端提供获取token接口,供客户端进行使用。服务端生成token后,如果当前为分布式架构,将token存放 于redis中,如果是单体架构,可以保存在jvm缓存中。
2)当客户端获取到token后,会携带着token发起请求。
3)服务端接收到客户端请求后,首先会判断该token在redis中是否存在。如果存在,则完成进行业务处理,业务 处理完成后,再删除token。如果不存在,代表当前请求是重复请求,直接向客户端返回对应标识。
但是现在有一个问题,当前是先执行业务再删除token。在高并发下,很有可能出现第一次访问时token存在,完 成具体业务操作。但在还没有删除token时,客户端又携带token发起请求,此时,因为token还存在,第二次请求 也会验证通过,执行具体业务操作。
对于这个问题的解决方案的思想就是并行变串行。会造成一定性能损耗与吞吐量降低。
第一种方案:对于业务代码执行和删除token整体加线程锁。当后续线程再来访问时,则阻塞排队。
第二种方案:借助redis单线程和incr是原子性的特点。当第一次获取token时,以token作为key,对其进行自增。 然后将token进行返回,当客户端携带token访问执行业务代码时,对于判断token是否存在不用删除,而是对其继续incr。如果incr后的返回值为2。则是一个合法请求允许执行,如果是其他值,则代表是非法请求,直接返回。

那如果先删除token再执行业务呢?其实也会存在问题,假设具体业务代码执行超时或失败,没有向客户端返回明确结果,那客户端就很有可能会进行重试,但此时之前的token已经被删除了,则会被认为是重复请求,不再进行业务处理。

这种方案无需进行额外处理,一个token只能代表一次请求。一旦业务执行出现异常,则让客户端重新获取令牌, 重新发起一次访问即可。推荐使用先删除token方案
但是无论先删token还是后删token,都会有一个相同的问题。每次业务请求都回产生一个额外的请求去获取 token。但是,业务失败或超时,在生产环境下,一万个里最多也就十个左右会失败,那为了这十来个请求,让其他九千九百多个请求都产生额外请求,就有一些得不偿失了。虽然redis性能好,但是这也是一种资源的浪费。

实现

基于自定义业务流程实现


这种实现方式省略,与传统实现无异。

基于自定义注解实现

直接把token实现嵌入到方法中会造成大量重复代码的出现。因此可以通过自定义注解将上述代码进行改造。在需 要保证幂等的方法上,添加自定义注解即可。

  1. 在token_common中新建自定义注解Idemptent
public class IdemptentInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (!(handler instanceof HandlerMethod)) {return true;}HandlerMethod handlerMethod = (HandlerMethod) handler;Method method = handlerMethod.getMethod();Idemptent annotation = method.getAnnotation(Idemptent.class);if (annotation != null){//进行幂等性校验checkToken(request);}return true;}@Autowiredprivate RedisTemplate redisTemplate;//幂等性校验private void checkToken(HttpServletRequest request) {String token = request.getHeader("token");if (StringUtils.isEmpty(token)){throw new RuntimeException("非法参数");}boolean delResult = redisTemplate.delete(token);if (!delResult){//删除失败throw new RuntimeException("重复请求");}}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}
}
  1. 修改token_service_order启动类,让其继承WebMvcConfigurerAdapter
@Bean
public IdemptentInterceptor idemptentInterceptor() {return new IdemptentInterceptor();
}@Override
public void addInterceptors(InterceptorRegistry registry) {//幂等拦截器registry.addInterceptor(idemptentInterceptor());super.addInterceptors(registry);
}
  1. 更新token_service_order与token_service_order_api,新增添加订单方法,并且方法添加自定义幂等注解
@Idemptent
@PostMapping("/genOrder2")
public String genOrder2(@RequestBody Order order){order.setId(String.valueOf(idWorker.nextId()));order.setCreateTime(new Date());order.setUpdateTime(new Date());int result = orderService.addOrder(order);if (result == 1){System.out.println("success");return "success";}else {System.out.println("fail");return "fail";}
}

深入浅出业务幂等性---1、幂等性介绍与接口幂等相关推荐

  1. 架构师之路系列:接口幂等性是个什么东东?如何实现接口幂等设计?

    怎么理解接口幂等? 幂等实际是一个数学上的概念,在数学上如果函数满足 f(x) = f(f(x)),那么我们称函数f具备幂等性.举个栗子,假设f(x) =|x|,即函数f表示取x的绝对值,那么f(x) ...

  2. JAVA后端如何保证业务操作的幂等性

    JAVA后端如何保证业务操作的幂等性 说到幂等性,应该很多人都知道这个词,顾名思义,就是无论操作多少次,产生的结果都是相等的.尤其是交易中,在开发过程中,时时刻刻要考虑交易的幂等性,例如,客户端因为网 ...

  3. 什么是幂等性、幂等性解决方案

    一.什么是幂等性 幂等性是一个技术术语.类似鉴权.都有一堆的解决方案 二.什么情况需要幂等 业务开发中,经常会遇到重复提交的情况,无论是由于网络问题无法收到请求结果而重新发起请求,或是前端的操作抖动而 ...

  4. Python requests介绍之接口介绍

    Python requests介绍 引用官网介绍 Requests 唯一的一个非转基因的 Python HTTP 库,人类可以安全享用. Requests 允许你发送纯天然,植物饲养的 HTTP/1. ...

  5. (123)FPGA面试题-介绍低速接口(UART、IIC、SPI),SPI有几根线,每根线的作用?(三)

    1.1 FPGA面试题-介绍低速接口(UART.IIC.SPI),SPI有几根线,每根线的作用?(三) 1.1.1 本节目录 1)本节目录: 2)本节引言: 3)FPGA简介: 4)FPGA面试题-介 ...

  6. 图文介绍--光纤接口类型

    图文介绍--光纤接口类型ST.SC.FC.LC ST.SC.FC光纤接头是早期不同企业开发形成的标准,使用效果一样,各有优缺点. ST.SC连接器接头常用于一般网络.ST头插入后旋转半周有一卡口固定, ...

  7. Qemu-guest-agent(QGA)原理介绍及接口扩展

    Qemu-guest-agent(QGA)原理介绍及接口扩展 1. 原理介绍 qga是一个运行在虚拟机内部的普通应用程序(可执行文件名称默认为qemu-ga,服务名称默认为qemu-guest-age ...

  8. Java后端接口幂等的方案

    原文网址:Java后端接口幂等的方案_IT利刃出鞘的博客-CSDN博客 简介 本文介绍Java后端接口幂等的方案. 接口幂等也是Java后端面试中常见的问题. 幂等的含义 对同一个系统,使用同样的条件 ...

  9. 接口幂等设计探索实践

    幂等性原本是数学上的概念,即使公式:f(x)=f(f(x)) 能够成立的数学性质.用在编程领域,则意为对同一个系统,使用同样的条件,一次请求和重复的多次请求对系统资源的影响是一致的.或者说是符合预期的 ...

最新文章

  1. 深入理解CSS3 Animation 帧动画
  2. Linux命令行之逗趣无极限
  3. 表达式x=x(x-1)
  4. java判断是否换行_如何检测java中的换行符
  5. 最耐用的手机盘点 网友:我这个能用到品牌商“破产”!
  6. 我发现了25个影响力达20多年的 Windows 0day,微软刚修完11个
  7. jQuery 判断是否包含某个属性
  8. Android 侧边栏快速索引(点击索引、滑动索引),通讯录样式
  9. 华众 mysql_华众虚拟主机管理系统HZhost三大常见错误!
  10. 谈谈你对JMM的理解
  11. 计算机组成原理——Part Three 性能指标
  12. 如何理解奇偶校验只能发现数据代码中奇数位出错的情况
  13. 云借阅-图书管理系统
  14. 数据分析基础之matplotlib绘制散点图
  15. Hadoop 命令操作大全
  16. Pytorch踩坑记之交叉熵(nn.CrossEntropy,nn.NLLLoss,nn.BCELoss的区别和使用)
  17. 100以内的数分解问题
  18. Windows远程桌面(RDP)密码凭证获取 (゚益゚メ) 渗透测试
  19. 河南2021年高考成绩位次查询,河南高考成绩位次排名查询2020,河南高考一分一段表...
  20. 警察局抓了a,b,c,d 4名偷窃嫌疑犯,其中只有一人是小偷。审问中,a说:“我不是小偷。”b说:“c是小偷。”c说:“小偷肯定是d。”d说:“c在冤枉人。”

热门文章

  1. 待办事项下拉html代码,一款简洁易用的HTML5当日待办事项列表
  2. java list map嵌套_java Map集合嵌套,value为Map和value为List
  3. Vue3教程:结合 Ant-Design-of-Vue 实践 Composition API
  4. 多线程(二)线程控制(创建 退出 等待)
  5. list类模板与vetcor有何不同
  6. 读书笔记:微积分的历程-从牛顿到勒贝格
  7. 英语每日听写练习 Day 6
  8. Aptina荣获2010年EDN创新奖
  9. Struts2 下载取消报异常最终解决办法 1.2 版本
  10. mysql数据库总是自动关闭_宝塔数据库mysql总是自动停止解决总汇