导读            

现在这个时代大家可能最关心的就是钱了,那么有没有想过你银行转账给你没有一次是转多的,要么失败,要么成功,为什么不能失误一下多转一笔呢?醒醒吧年轻人,别做梦了,做银行的能那么傻x吗?

今天我们就来谈一谈为什么银行转账不能多给我转一笔?关乎到钱的问题,小伙伴们打起精神!!!

要想要理解上述的疑惑,不得不提的一个概念就是幂等性,至于什么是幂等性,如何通过代码实现幂等性,下面将会详细讲述。

           什么是幂等性          

所谓幂等性通俗的将就是一次请求和多次请求同一个资源产生相同的副作用。用数学语言表达就是f(x)=f(f(x))。

维基百科的幂等性定义如下:

幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。
在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。
幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。
例如,“setTrue()”函数就是一个幂等函数,无论多次执行,其结果都是一样的,更复杂的操作幂等保证是利用唯一交易号(流水号)实现.

        为什么需要幂等性       

在系统高并发的环境下,很有可能因为网络,阻塞等等问题导致客户端或者调用方并不能及时的收到服务端的反馈甚至是调用超时的问题。总之,就是请求方调用了你的服务,但是没有收到任何的信息,完全懵逼的状态。比如订单的问题,可能会遇到如下的几个问题:

1. 创建订单时,第一次调用服务超时,再次调用是否产生两笔订单?

2. 订单创建成功去减库存时,第一次减库存超时,是否会多扣一次?

3. 订单支付时,服务端扣钱成功,但是接口反馈超时,此时再次调用支付,是否会多扣一笔呢?

作为消费者,前两种能接受,第三种情况就MMP了,哈哈哈!!!这种情况一般有如下两种解决方式:

1. 服务方提供一个查询操作是否成功的api,第一次超时之后,调用方调用查询接口,如果查到了就走成功的流程,失败了就走失败的流程。

2. 另一种就是服务方需要使用幂等的方式保证一次和多次的请求结果一致。

        HTTP的幂等性       

GET:只是获取资源,对资源本身没有任何副作用,天然的幂等性。

HEAD:本质上和GET一样,获取头信息,主要是探活的作用,具有幂等性。

OPTIONS:获取当前URL所支持的方法,因此也是具有幂等性的。

DELETE:用于删除资源,有副作用,但是它应该满足幂等性,比如根据id删除某一个资源,调用方可以调用N次而不用担心引起的错误(根据业务需求而变)。

PUT:用于更新资源,有副作用,但是它应该满足幂等性,比如根据id更新数据,调用多次和N次的作用是相同的(根据业务需求而变)。

POST:用于添加资源,多次提交很可能产生副作用,比如订单提交,多次提交很可能产生多笔订单。

        幂等性的实现方式        

对于客户端交互的接口,可以在前端拦截一部分,例如防止表单重复提交,按钮置灰,隐藏,不可点击等方式。但是前端进行拦截器显然是针对普通用户,懂点技术的都可以模拟请求调用接口,所以后端幂等性很重要。

后端的幂等性如何实现?将会从以下几个方面介绍。

1. 数据库去重表

在往数据库中插入数据的时候,利用数据库唯一索引特性,保证数据唯一。比如订单的流水号,也可以是多个字段的组合。

实现比较简单,读者可以自己实现看看,这里不再提供demo了。


2. 状态机

很多业务中多有多个状态,比如订单的状态有提交、待支付、已支付、取消、退款等等状态。后端可以根据不同的状态去保证幂等性,比如在退款的时候,一定要保证这笔订单是已支付的状态。

业务中常常出现,读者可以自己实现看看,不再提供demo。

3. TOKEN机制

针对客户端连续点击或者调用方的超时重试等情况,例如提交订单,此种操作就可以用Token的机制实现防止重复提交。

TOKEN机制如何实现?简单的说就是调用方在调用接口的时候先向后端请求一个全局ID(TOKEN),请求的时候携带这个全局ID一起请求,后端需要对这个全局ID校验来保证幂等操作,流程如下图:

主要的流程步骤如下:

1. 客户端先发送获取token的请求,服务端会生成一个全局唯一的ID保存在redis中,同时把这个ID返回给客户端。

2. 客户端调用业务请求的时候必须携带这个token,一般放在请求头上。

3. 服务端会校验这个Token,如果校验成功,则执行业务。

4. 如果校验失败,则表示重复操作,直接返回指定的结果给客户端。

通过以上的流程分析,唯一的重点就是这个全局唯一ID如何生成,在分布式服务中往往都会有一个生成全局ID的服务来保证ID的唯一性,但是工程量和实现难度比较大,UUID的数据量相对有些大,此处陈某选择的是雪花算法生成全局唯一ID,不了解雪花算法的读者下一篇文章会着重介绍。

        TOKEN代码实现       

陈某选择的环境是SpringBoot+Redis单机环境+注解+拦截器的方式实现,只是演示一下思想,具体的代码可以参照实现。

redis如何实现,获取Token接口将全局唯一Id存入Redis(一定要设置失效时间,根据业务需求),业务请求的时候直接从redis中删除,根据delete的返回值判断,返回true表示第一次请求,返回false表示重复请求。代码如下:

@Service
public class TokenServiceImpl implements TokenService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overridepublic String getToken() {//获取全局唯一idlong nextId = SnowflakeUtil.nextId();//存入redis,设置10分钟失效stringRedisTemplate.opsForValue().set(String.valueOf(nextId), UUID.randomUUID().toString(),10, TimeUnit.MINUTES);return String.valueOf(nextId);}/*** 删除记录,true表示第一次提交,false重复提交*/@Overridepublic Boolean checkToken(String token) {return stringRedisTemplate.delete(token);}
}

注解的实现如下,标注在controller类上表示当前类上全部接口都做幂等,标注单个方法上,表示单个接口做幂等操作。

/*** @Description 幂等操作的注解* @Author CJB* @Date 2020/3/25 10:19*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatLimiter {
}

请求头的拦截器,用于提取请求头和校验请求头,如下:

/*** @Description 获取请求头的信息,具体校验逻辑读者自己实现* @Author CJB* @Date 2020/3/25 11:09*/
@Component
public class HeaderIntercept implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//获取tokenString token = request.getHeader(HeaderConstant.TOKEN);//校验逻辑if (!validToken(token))throw new TokenInvalidException("TOKEN失效");//获取其他的参数.....RequestHeader header = RequestHeader.builder().token(token).build();//放入request中request.setAttribute(HeaderConstant.HEADER_INFO,header);return true;}/*** 校验token,逻辑自己实现* @param token* @return*/private boolean validToken(String token){return Boolean.TRUE;}
}

保证幂等性的拦截器,直接从redis中删除token,成功则第一次提交,不成功则重复提交。

@Component
public class RepeatIntercept implements HandlerInterceptor {@Autowiredprivate TokenService tokenService;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (handler instanceof HandlerMethod){//获取方法上的参数RepeatLimiter repeatLimiter = AnnotationUtils.findAnnotation(((HandlerMethod) handler).getMethod(), RepeatLimiter.class);if (Objects.isNull(repeatLimiter)){//获取controller类上注解repeatLimiter=AnnotationUtils.findAnnotation(((HandlerMethod) handler).getBean().getClass(),RepeatLimiter.class);}//使用注解,需要拦截验证if (Objects.nonNull(repeatLimiter)){//获取全局token,表单提交的唯一idRequestHeader info = RequestContextUtils.getHeaderInfo();//没有携带token,抛异常,这里的异常需要全局捕获if (StringUtils.isEmpty(info.getToken()))throw new RepeatException();//校验tokenBoolean flag = tokenService.checkToken(info.getToken());//删除失败,表示if (Boolean.FALSE.equals(flag))//抛出重复提交的异常throw new RepeatException();}}return true;}
}

接口幂等实现,代码如下:

@RestController
@RequestMapping("/order")
public class OrderController {@Autowiredprivate OrderService orderService;/*** 下单* @param order* @return*/@PostMapping@RepeatLimiter  //幂等性保证public CommenResult add(@RequestBody Order order){orderService.save(order);return new CommenResult("200","下单成功");}
}

        效果演示        

1. 发送getToken的请求获取Token

2. 携带Token下单第一次:

3. 第二次下单:

        总结        

至此幂等性的定义和幂等性的实现方式已经介绍完了,本篇文章介绍了三种实现幂等性的方式,读者可以在实际开发中根据业务需求使用对应的方式。

TOKEN机制的代码实现已经上传,如有需要回复关键词幂等获取。

文章看到这里,如果觉得陈某写得好,能够有所收获的话,可以点个在看,分享一波,谢谢!!!

留言

幂等性如何实现?带你了解一波!!!相关推荐

  1. Android之自定义带圆角的水纹波效果

    1 需求 自定义带圆角的水温波效果 2 代码实现 bg_navigation_ripple.xml <?xml version="1.0" encoding="ut ...

  2. matlab 设计带阻型陷波滤波器_Q值可调、最大增益恒定的带通滤波器

    带通滤波器(BPF)被广泛用于通带非常窄.通带以外任何其它频率被衰减的应用. 公式(1)是带通滤波器的二阶带通传输函数: 其中,K代表恒定的滤波器增益,Q代表滤波器的品质因数. 在H.Martinez ...

  3. macOS新版本终于删掉自带Python2,这波操作连Python死忠粉都叫好

    点击上方"AI遇见机器学习",选择"星标"公众号 重磅干货,第一时间送达 鱼羊 发自 凹非寺 量子位 | 公众号 QbitAI 喜大普奔,苹果现已正式推出的ma ...

  4. 把执行结果转成json对象报错_于一次JSON格式错误 之 手把手带你走一波FastJSON将对象转成JSON字符串流程...

    一.前言 最近老大说要新增一个试用广告的功能,我巴拉巴拉的从之前推送广告那里将代码cv过来,然后跟老大说搞定了!过一会老大说返回的json格式不对!于是乎我瞧了瞧: { "adsArea1& ...

  5. 元宇宙被这个圈子带火了一波,是我没想到的

    金磊 萧箫 发自 凹非寺 量子位 | 公众号 QbitAI 最近有个热闹的事,值得说道说道. 事情是这样的: 先是中国第一个进入元宇宙时代的媒体集团Meta Media超媒体控股,打造了个叫Meta ...

  6. 冒着变砖的风险,带你体验一波 Android O !!!

    版权声明: 本公众号发布的所有文章,均属于原创,版权归本公众号所有. 允许有条件转载,转载请附带底部二维码. 一.前言 本周,Google 宣布下一代 Android 系统被命名为 "And ...

  7. 带大家写一波微信公众号的爬取

    开发工具 python版本 : 3.6.4 相关模块: pdfkit模块: requests模块: 以及一些Python自带的模块. 抓包工具: fiddler 环境搭建 python 环境 安装Py ...

  8. 带大家写一波微信公众号的爬取!谁说微信爬不了的!

    开发工具 python版本 : 3.6.4 相关模块: pdfkit模块: requests模块: 以及一些Python自带的模块. 抓包工具: fiddler 环境搭建 python 环境 安装Py ...

  9. 编程小白模拟简易比特币系统,手把手带你写一波!(附代码)

    作者 | VV一笑ヽ 责编 | Carol 出品 | 区块链大本营(blockchain_camp) 封面 | CSDN 付费下载于视觉中国 如果有一个P2P的Demo,我们要怎么才能应用到区块链当中 ...

最新文章

  1. mongodb小结(转)
  2. linux系统nginx启动不了,nginx启动不了,求大神帮助!
  3. 回望之六:太阳山与红寺堡
  4. mysql第四步安装失败_MySQL8.0版本的安装以及解决安装后MySQL服务无法启动的问题...
  5. 剑指Offer丑数问题
  6. linux下启动应用程序,RedHat Linux 9下启动应用程序
  7. wubi for ubuntu 9.04 无法运行
  8. Hello World程序的起源与历史
  9. 最小生成树:Kruskal算法
  10. kali foremost 分离文件_只需 1 分钟,这个网站用 AI 分离歌曲的人声、伴奏和乐器声...
  11. wowza流媒体服务器安全设置
  12. html实体注册商标,html 注册商标,html 注册商标代码
  13. 【Python】利用Python对招聘信息数据分析
  14. AngularJS controller调用factory
  15. Kali linux配置
  16. Python自动化:根据模板批量生成含指定数据的word文档
  17. Mysql进阶优化篇02——索引失效的10种情况及原理
  18. AVR中C语言while语言,AVR单片机汇编语言程序实例
  19. 猿圈 题库_猿圈AI考试题库 智慧在线考试宝典
  20. ios 使用AVFoundation从视频中提取音频

热门文章

  1. 物联网与区块链相关的国家标准
  2. 2022年广西桂林二级建造师考试精选试题及答案
  3. 裸辞4个月,面试了30家公司,终于找到理想工作了
  4. tableau了解一
  5. c语言winpcap编程,c语言Winpcap编程结构并接收解析arp包
  6. 汽车融资租赁订单流程-自营模式
  7. 浅谈面向对象程序设计方法
  8. oracle 所有字母大写,Oracle函数(把每个单词首个字母变为大写)
  9. pyqt5运行出现Process finished with exit code -1073740791 (0xC0000409)
  10. C++关键字override