准确全面理解“幂等”

        如今”幂等“在处理MQ重复消费问题上得到广泛运用,很少有开发人员对幂等不了解的。但是,了解意味熟悉吗?熟悉又意味着准确且全面吗?

        前一段时间我在给我们小组讲解分享“幂等组件”的设计时,触不及防的被一个问题“什么是‘幂等’”。我竟然一下子回答不上来,因为在我看来,这个是个人人都知道的常识,跟本不需要去解释。当时我没有时间仔细地组织语言便回答“幂等就是多个相同的请求,返回相同结果,比如......”。

        后来,在推广“幂等组件”发现小组成员对“幂等”本身理解及运用存在误区,引起一些问题:

  1. 把“幂等”中的“结是一致的”误解成“返回相同响应结果”
  2. 不分场合使用”幂等组件“
  3. 把“幂等”和“缓存”混淆

        首先,幂等是指“系统对用户的同一操作的一次请求或多次请求产生的结果是一致的,没有副作用”。以下两场景分别说明什么是“同一操作”,“一次或多次请求”,“结果一致”,“没有副作用”。

        为了方便说明问题,我这里列出两个有代表性的场景,一个线上,一个线下:

A:网上购物

用户在“购物车“页面上,点击”结算“按钮,系统基于这一购物车,生成一个”订单“,然后跳转的到订单页面,页面有”支付“,”取消“两个按钮。点了”支付“,并且心急再点了几下支付,最后点击”取消“(不考虑现实如担保支付等复杂情况)

B:用户刷地铁卡进站乘车

乘客持地铁卡在某个站点,某个闸门刷卡进站。但由于闸门反应慢心急的乘客刷了一次(或多次)

1、什么是“同一操作”,如何标识同一操作

        对于A场景,用户在同一个”订单“上点击多次”支付“是同一操作,但”支付“和”取消支付“不是同一个操作,因此在这一场景如,可以有<订单号,点击按钮>这个二元组来标识同一操作。

对于B场景,从业务层面上理解“用户某一次进站”是“同一操作”。在不考虑其它干扰的情况下,同一操作的标识是<地铁卡ID,站点ID,闸门ID>(当然就种标识有很多漏洞,此处简化当前的讨论,在附录中我们再详细的讨论并优化)

2、什么是“一次或多次请求”

对于A场景,用户每点击一次“支付”按钮就是一次请求。

对于B场景,用户每在闸机上刷一次卡的动作就是一次请求。

从上面的例子可以看出,【请求】和【操作】之间一定是“多对一”的关系

3、什么是“结果一致”

对于A场景,“结果一致”是用户只需要付一次钱,不会因为“多次请求”付多次,而电商系统也只收到一次货款,只扣减一次库存,商家只需发一次货。

对于B场景,“结果一致”是指乘客的地铁卡只付一次钱,地铁的收款系统也只收了一次钱。

4、什么是“没有副作用”

指在“结果一致”的前提一下,没有对系统的状态统计(数据库,缓存等)产生多余影响。

对于A场景,用户购物次数不变。

对于B场景,乘客的进站次数不变。

如果说“结果一致”是“幂等”的最低要求,那么“没有副作用”则是“幂等”最高要求。

“幂等”的常见理解误区

1、把“幂等”中的“结是一致的”误解成向调用方“返回相同响应结果”

有一些开发人员误解“幂等”的定义(“系统对用户的同一操作的一次请求或多次请求产生的结果是一致的,没有副作用”)里的“结果”为“给调用方的响应结果”,这是错误的。

这里的“结果”指的是系统的一系列“状态集合”,而“状态集合”需要分场景解释。

对于A场景:“结果”对应的“状态集合”包含:用户支付账户余额、商品的库存数量、商家的收款账户余额。

对于B场景:“结果”对应的“状态集合”包含:地铁卡余额、地铁账户余额。

“响应结果”不是必需要相同。例如,同样是对于幂等的重复请求,被调方在保证系统幂等性处理前提下,可以根据具体情况做出不同响应方式:1、返回正常响应结果(调用方不必感知已重复),2、返回特定误错误,这个误错码让调用方感知已经重复。具体使用何种响应方式取决于接口调用方需求或接口开发方开规范

2、不分场合要求“幂等处理”

实现“幂等”是有代价的,不分场合要求“幂等处理”,只会增加不必要系统开销。

以下情况是不必要做幂等处理的

2.1、读请求:读请求是天然幂等的,在“读请求”的幂等处理是“画蛇添足”

2.2、无状态的固定值的写请求:

“无状态的写请求”是指写操作与被操作对象的之前状态无关,且在写的值是固定值:

2.2.1、被操作对象的之前状态无关

比如,set XX = 1 where id=YYY;不需要考虑XX 之前值是多少,操作过程及结果都不受到XX之前的值的影响。

而含有自增、自减等不满足这个条件。set XX = XX+1 where id=YYY,a++,b--,其操作结果都依赖于XX,a,b原来的值

2.2.2、写的值是固定值

比如,set XX = 1 where id=YYY中 =1是固定值,满足。

而 ,set XX = CURRENT_TIMESTAMP where id=YYY, a= system.currentTimeMillis(),b=Math.rand(), 要么随时间变化,要么是随机值。都不是固定值。不满足固定值条件

2.3、不宜做幂等的场景:

有些场景用幂等不仅起不了防重的作用,还会引起安全隐患。

比如已登录的用户在没有登出的又重复登录,在安全要不说较高的情况下,不能做幂等。系统既不能继续返回当前token,还必需重新生成token,并使当前token强制失效(尽管原来的token没有过期),还有相应的会话信息已要重新生成。如果不让原来token失效,那么原来token可能会继续起作用,导致权限管理漏洞。如果不停止旧的会话,那么新登录的会话就获取旧会话信息,信息泄露。

3、把“幂等”和“缓存”混淆

“幂等”大多数实现方案用redis做为记录的存储,且幂等逻辑模式与“旁路缓存”相似,导致一些对概念模糊的初级开发人员常把“幂等”和“缓存”混淆。

两者区别:

3.1、使用场景不同,也不存在没有交集

“幂等”不必要用于“读请求”(详见2.1),只能用于满足条件的写请求

“缓存”只能用于“读请求”,写请求不可“缓存”

3.2、目的不同

“幂等”目的在于“防重”,“缓存”目的在于“减少数据库压力,提高性能”

3.3、优化效率不同

有的开发人员不顾前两条,执意要把“幂等组件”当做“缓存组件”用,然后说认为“XXX在redis存在情况下,不需要访问数据库等”。

我只能说,他们“高射炮打蚊子”,不划算。因为”缓存“本身有这高效的实现。而且以后两种组件也不必然这不同的优化路径。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值