服务幂等以及常用实现方式
现在稍具规模的网站和大型应用都不再是单机模式,而是分布式应用,基于多机的集群构建的应用,这样服务能力就可以基本实现横向扩容(scale out),不会像单机模式下的纵向扩容(scale up)会受到单机服务能力上限的限制。另外,随着“微服务”概念的火爆,很多应用在构建之初就已经走在了分布式的路线上了,所以就目前行业的发展来看,基于分布式的应用会越来越普遍,甚至变成常态。加上docker这些容器技术的出现,应用分布式化的工具也越来越成熟。
分布式的复杂性
众所周知,构建分布式应用所面临的复杂度远远超出集中式的单一应用,导致复杂性的因素有很多,在此只提其中一点:网络的不可靠性。在单一进程内部,对一个函数的调用,结果只有两种——成功和失败,失败的情况下,调用者可以决定做一些事情弥补。但是在跨进程的调用中,对一个远程(也可以在同一个节点上)进程上运行的函数调用除了会得到成功和失败,还会有第三种的情况——超时,这个现象被称为分布式的三态。这也是困扰分布式应用构建的最核心因素之一,很多分布式应用的复杂度之所以上升这么多也是因为三态之中的超时引起的。
简单看看超时给我们带来的困扰,进程A调用进程B上的函数f,对于成功和失败的结果,相信和单机下一样,进程A都可以进行很好地的处理,因为结果是很明确的。如果进程A调用f之后,在允许的等待最大时间内没有返回结果,就是调用超时了,此时进程A能做什么?其实进程A什么都做不了,因为超时是一个不明确的结果——成功和失败都有可能。详细解释下可能的情况:
成功的情况:进程A把数据通过网络传输到进程B上,f执行成功,通 网络返回执行结果给进程A,可是网络不太好,传输失败了,进程A并 未在指定时间内收到结果,认为超时了。 失败的情况:情况和成功的情况差不多,只是f执行失败了,但是结 果依然传输失败,进程A也认为执行超时了。 未执行的情况:进程A的数据发送到进程B所在的节点过程中网络失败 了,或者发送到了进程B所在的机器上,但是进程B没有消费掉在TCP 网络层的数据等等 由此可见,进程A对于超时确实无能为力,有太多的可能存在的情况了。但是分布式协作过程中又必须解决这个问题,不然分布式应用是没意义的,这种情况下,一般会采用让进程A尝试重试——即重复发起之前的调用。但是这样也可能会带来问题,因为超时的那次调用可能已经成功了,再次以同样的参数调用f会不会带来额外的问题?这就引出本文的主角——幂等性。
幂等性
幂等性本来是一个数学概念,在计算机方面用来表示对同一个过程应用相同的参数多次和应用一次产生的效果是一样,这样的过程即被称为满足幂等性。
有了这个概念之后,假如之前的f是满足幂等性的,那么是不是意味着进程A在调用f超时之后,可以继续重复调用f多次?这样最起码进程A可以在超时情况下做一些促进事情正向发展的努力。所以这种方式是分布式节点间常用的方式,那么如何保证幂等呢?
如何实现幂等性
在考虑实现幂等之前,先看看有哪些操作是天然幂等的,以SQL为例。update tab1 set col1 = 1 where id = 2这样的更新语句,无论执行多少次结果都是不受影响的,所以是幂等的。update tab1 set col1 = col1 + 1 where id = 2这样的更新语句会随着每次更新不断变化,所以不是幂等的。所以在考虑之前,先识别出幂等和非幂等操作。
业务系统实现幂等的通用方式:一般是排重表校验,在业务操作所在的库建一张小表,名称暂时搞成dup_forbidden,核心字段就一个biz_id,并且在这个字段上建立一个unique index,其他字段可以根据业务需求来扩充。那么原来的业务f实现幂等的伪代码如下:
begin transaction;
count = insert ignore dup_forbidden (...biz_id...) value(...biz_id...)
if (count > 0) {f(biz_id)
}
commit;
可以认为这是一套业务系统实现幂等的模板做法,通过insert ignore返回值来判断是否已经执行过了,但是针对不同的情况可能还有变化。使用事务的目的是为了保证f和dup_forbidden的操作同时成功和失败。本质上来看,dup_forbidden表就是通过unique index来屏蔽对f的多次调用,事实上很多业务已经存在dup_forbidden表的功能。
考虑如下场景:在一个面向交易的分布式应用中,支付子系统完成了支付功能,支付子系统通知订单子系统,通知的方式无非是调用订单子系统的一个函数f而已,只是调用的方式分为同步和异步。无论是同步还是异步,f都可能存在超时,所以为了支持重试,f必须是幂等的。f会首先根据传入的订单号来查找订单,检查订单状态。如果是已经支付,就会直接返回成功。如果是待支付状态,那么会尝试锁定(悲观锁和乐观锁)订单,修改状态,指定其他操作,其中锁定只是为了防止并发操作。伪代码实现如下:
begin transaction;
count = update deal_tab set status = paid where id = xx_id and status = unpaid
if (count > 0) {f(xx_id)
}
commit;
从这个例子可以看出deal_tab订单表本身已经可以作为dup_forbidden表的作用了,所以insert防重操作变成update来实现,当然这个是乐观锁的版本。悲观锁的版本如下:
begin transaction;
deal = select * from deal_tab where id = xx_id for update
if (deal.status == paid) {return true;
} else if (deal.status = unpaid) {f(xx_id)
}
commit;
当然基于悲观锁的做法对于高并发的系统是不建议的,毕竟长时间锁定记录会降低系统的TPS。
当然,所有这些方案都是基于业务存在唯一的业务编号来设计实现的,可能会存在完全没有业务编号的吗?答案是it depends。即使没有完全唯一的编号,我们也可以人为生成编号,比如调用方负责生成调用编号,同一个调用编号发起的多次调用都被视为一次调用,既可以作为唯一键来排重。事实上,这种情况确实比较少!
总结
业务系统实现幂等性的方式基本确定。系统关键接口的幂等性为以后系统的长期发展,特别是往分布式方向发展打下了很好的根基,可以大大简化分布式应用的构建复杂度。
from: http://outofmemory.cn/wiki/service-idempotency
服务幂等以及常用实现方式相关推荐
- J2EE常用资源管理方式总结
J2EE底层是多线程的,无论何种资源管理的策略都是与线程相关的,因此通过合理的资源管理来应对多线程的环境是非常关键的.现在总结一下几种J2EE中常见的资源管理方式: 实例池 容器管理的单例 ...
- 移动小额支付业务系统几种常用实现方式的分析和比较
关键词: 移动小额支付 小额支付 移动支付 电子支付 摘 要: 随着移动小额支付业务逐渐走入人们的日常生活中,它已成为运营商争夺的数据业务焦点之一.文章对当前移动小额支付业务系统的 ...
- linux设置开机自启服务,linux设置服务开机自启动的三种方式
linux设置服务开机自启动的三种方式 这里介绍一下linux开机自动启动的几种方法,共计3种,大家可以借鉴一下!经验里面以centos 5.3系统为例! 方法1:.利用ntsysv命令进行设置,利用 ...
- 谈谈站长的几种常用赚钱方式
从互联网产生到供广大网民所需的各类网站的诞生,导航站,搜索引擎,社区类,门户类,论坛类,新闻类,企业类,平台类,购物类,网站的诞生,站长功不可没.站长在经营网站的同时付出的财力人力,网站只有盈利才能生 ...
- 数据采集工具之Flume的常用采集方式详细使用示例
数据采集工具之Flume的常用采集方式详细使用示例 Flume Flume概述 Flume架构 核心的组件 常用Channel.Sink.Source类型 Flume架构模式 安装Flume Flum ...
- 苹果应用内支付(iOS IAP)的流程与常用攻击方式
苹果应用内支付(iOS IAP)的流程与常用攻击方式 Jan 19, 2017 常见支付流程 iap(in app purchase)指苹果应用内支付, 目前主要有两种方式. 1. 客户端直接veri ...
- jQuery中ajax的4种常用请求方式
jQuery中ajax的4种常用请求方式:1.$.ajax()返回其创建的 XMLHttpRequest 对象. $.ajax() 只有一个参数:参数 key/value 对象,包含各配置及回调函数信 ...
- python-django-ORM,常用查询方式
介绍django model 的一些常用查询方式 首先是一些文档性的帮助 __exact 精确等于 like 'aaa' __iexact 精确等于 忽略大小写 ilike 'aaa' __conta ...
- spring中的依赖注入——构造函数注入、set方法注入( 更常用的方式)、复杂类型的注入/集合类型的注入
spring中的依赖注入 依赖注入: Dependency Injection IOC的作用:降低程序间的耦合(依赖关系) 依赖关系的管理:以后都交给spring来维护.在当前类需要用到其他类的对象, ...
最新文章
- suse linux登录黑屏,SUSE Linux登录时黑屏解决办法
- 沈阳建立通用航空产业基地,开辟国内首家无人机专用空域
- raid5通常需要几块盘_raid5需要几块硬盘
- 记录 之 tf.placeholder() 函数的意义及用法
- 永信至诚携 “企业安全人才培养解决方案”惊艳WOT技术峰会
- 十大有用但又偏执的Java编程技术
- 使用Spring 3 MVC处理表单
- Flutter 中的国际化之多语言环境
- PAT 乙级 1008. 数组元素循环右移问题 (20) Java版
- hdfs读写流程_必须掌握的分布式文件存储系统—HDFS
- 用激光把谷歌的标志投射到月球是否可行?
- CURL不能访问 但浏览器可以访问
- 广数系统加工中心编程_数控铣和加工中心编程
- 修改远程git仓库密码 - 报错 fatal: Authentication failed for
- 答题拿奖两不误:华为云知乎金牌答题官,就是你!
- 亲自动手写一个深度学习框架
- android 怎么选择audio hal
- Android相机预览,指定区域显示预览框,在区域内出现人脸进行人脸识别,并抓拍人脸照片存在本地,CameraX,虹软人脸识别
- matlab保存所有图,Matlab中图片保存的5种方法
- 【Python数据科学手册】Pandas——十二、处理时间序列
热门文章
- 解决两台centos虚拟机Telnet服务无法联机的问题
- ios 开发者账号申请流程 最新
- 机器学习算法一览(附python和R代码)
- 学习笔记Spark(四)—— Spark编程基础(创建RDD、RDD算子、文件读取与存储)
- python学习笔记(一)——操作符和运算变量
- 剑指offer 11. 旋转数组的最小数字(很详细!)
- Halcon初学者知识 【11】自定义算子和应用实例
- excel两个表格数据对比_Excel小技巧:实例教你快速对比多个表格的差异
- linux mount 挂载U盘
- 办公室计算机网络使用情况,企事业单位办公网络的现状及解决方案.doc