微服务中的幂等设计

首发公众号:二进制社区,转载联系:binary0101@126.com幂等本来是数学运算里的概念,维基百科对其的定义为:

  1. 在某二元运算下,幂等元素是指被本身重复运算(或对于函数是为复合)的结果等于它本身的元素。
  2. 某一元运算为幂等的时,其做用在任一元素两次后会和其做用一次的结果相同。例如,高斯符号即是幂等的。

衍生到微服务领域,幂等指的是使用相同的参数屡次调用相同的API,对后端产生的影响是一致的,许多人理解为屡次调用返回的结果是同样,这种观点是错误的,连最基本的查询接口也不多是屡次查询返回一样的结果,幂等的侧重点是对后端的影响,并不关心返回的数据。幂等是全部客户端-服务端系统都须要考虑的问题,不管你是C-S仍是B-S模式,但在B-S模式下,这个问题更为突出,尤为是近几年流行的微服务架构下,这是由于在微服务模式下,单个应用简单,但服务总体更为复杂,完成一个功能须要走较长的调用链,总体链路可能由不一样的语言不一样框架实现,每一个微服务为了保证服务的质量,经常会对一些不肯定的因素(如:网络异常、资源耗尽等)致使的问题进行容错处理,典型措施就是重试。微服务中的幂等设计上图是互联网应用中很是精简的调用流程,应该说许多公司的链路远比这个复杂,流程中的每一个环节均可能出现重试:前端

  1. 用户可能会频繁点击按钮
  2. 浏览器在网络不稳定状况下重发请求
  3. 反向代码因为网关响应超时或者网络异常重发到另一个实例
  4. 网关一样可能由于超时/出错自动调用另一个service

若是请求只涉及到资源查询,显然调用屡次并不会对后端数据发生更改,因此查询类的API自然就是幂等的,但若是这是一个创单接口,咱们就不得不考虑幂等的问题,创单过程当中会涉及到库存扣减、余额扣减、订单数据入库、消息通知等等修改后端资源的操做,为了防止这类请求重复提交到后端,前端页面一般会在用户点击提交后禁用按钮,后端成功后清空表单跳转到提示页面,对于后端服务来说,一般会采用如下方式实现幂等:java

  1. 数据库惟一约束:经过业务设计,肯定几个能够惟一识别订单字段设置为惟一索引,这种方式好处就是简单,代码基本不作改动,缺点也很明显,因此的重复请求要穿透整个链路,一直到数据库才能判重,对链路上资源消耗很大,会给数据库带来巨大的压力,即便服务扩容,TPS也很难上去
  2. MVCC:多版本并发控制方式,操做时带上版本号:update t1 set x=y ,version=version+1 where version=xxx,优势是提高了并发响应能力,实现也简单,缺点是只适用更新接口,仍是会将重复请求达到数据库,数据库压力较大
  3. 状态机机制,本质上是MVCC方式的变种:订单有多个业务状态,每次操做数据会带上一个状态,只有在上一个状态匹配的状况下会更新数据,优缺点和MVCC大同小异,但这种机制解决了插入的问题,不只仅适用在更新接口
  4. Token机制,这是一种很是高效的幂等机制,在性能和功能上都达到很好的效果,接下来咱们就详细讨论下这种机制的实现

Token机制的核心就是要求客户端的每次请求里必须携带一个UUID,产生UUID的算法不少,如:雪花算法,ObjectID,经常使用的开发语言也有对应的实现,即便client生成UUID有困难,也能够调用ID生成器预先生成一批缓存到本地,这里就不一一展开。有了这个UUID,能够在多个环节实现拦截,并且这种判断是很是高效的,几乎都是O(1)的时间复杂度,远比数据库惟一约束判断快,譬如在nginx里,咱们可使用lua获取到请求里的UUID,将该UUID放入leveldb、redis、memcache,下次请求时判断该值是否存在,存在直接返回错误不然放行,若是不是使用nginx或者实施上述方案有困难,能够在网关层实现,对于java语言(其余语言也相似),能够作到透明化处理:在网关里注册Idempotentfilter,该filter提取UUID,一样经过leveldb、redis、memcache等高速缓存判断是否能够放行,显然token方案能够尽早发现和拦截订单,大大下降资源消耗,减小数据库压力,对业务也是透明无浸入,但只依赖token机制显然是有缺陷的:当缓存服务出错ldb文件丢失,redis数据意外清空,memcache意外重启等,会致使咱们的UUID标志丢失,这时就须要数据库来兜底,数据库的惟一约束是不可或缺的,当出现这些极端状况时,即便请求达到DB,也不会形成数据重复。有幂等需求的接口,建议采用token机制实现高效的排除重复请求,固然最终落地式须要结合具体的业务场景.nginx

更多深度文章,关注:二进制社区

相关文章
相关标签/搜索