前言
现在稍具规模的网站和大型应用都不再是单机模式,而是分布式应用,基于多机的集群构建的应用,这样服务能力就可以基本实现横向扩容(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){
returntrue;
}elseif(deal.status = unpaid){
f(xx_id)
}
commit;
当然基于悲观锁的做法对于高并发的系统是不建议的,毕竟长时间锁定记录会降低系统的TPS。
当然,所有这些方案都是基于业务存在唯一的业务编号来设计实现的,可能会存在完全没有业务编号的吗?答案是it depends。即使没有完全唯一的编号,我们也可以人为生成编号,比如调用方负责生成调用编号,同一个调用编号发起的多次调用都被视为一次调用,既可以作为唯一键来排重。事实上,这种情况确实比较少!
总结
业务系统实现幂等性的方式基本确定。系统关键接口的幂等性为以后系统的长期发展,特别是往分布式方向发展打下了很好的根基,可以大大简化分布式应用的构建复杂度。
相关推荐
消息幂等是分布式系统常用的组件,具有广泛的应用价值,如异步化、解耦、削峰等。消息中间件是一个可靠的组件,能够保证消息至少被消费一次。但是,这种可靠的特性也会导致消息可能被多次投递,例如程序重启或消费...
在本文中,我们将深入探讨几种重要的理论和实践方法,包括ACID特性、CAP定理、BASE原则,以及几种常见的分布式事务处理协议:二段提交、三段提交和TCC(Try-Confirm-Cancel)模式,同时也会提到幂等性这一关键概念。...
本文将基于给定文件提供的信息,详细介绍这两种方法的基本原理、实现步骤以及通过C语言编程的具体实例。 #### 二、幂法与反幂法基本原理 ##### 1. 幂法(Power Method) **目的**:求解矩阵的按模最大特征值及其...
在Java中,Redis是一个常用的分布式缓存和数据库系统,它提供了一种实现分布式锁的方法,如使用`setIfAbsent`命令来设置键值对,如果键不存在,则设置成功,表示获取到了锁;若键已存在,则设置失败,意味着其他线程...
1. **快速幂算法**:快速幂是模拟幂运算的一种常用方法,其核心思想是利用幂运算的乘法性质(a^(m+n) = a^m * a^n)。通过不断将指数拆分为二进制形式,可以显著减少计算次数。例如,计算2^10可以转化为2^(1*2^3 + 0...
在编程领域,C语言是一种非常基础且强大的编程语言,它被广泛用于系统开发、嵌入式系统以及各种软件工程。由于其高效的性能和简洁的语法,C语言也常被用来实现算法。本资源"常用算法C语言实现代码"提供了一种深入...
在计算机科学和线性代数领域,幂法和反幂法是两种常用的技术,主要用于求解实对称矩阵或复共轭对称矩阵的最大和最小特征值。这些方法在数值线性代数中占有重要地位,特别是在大型稀疏矩阵处理时,由于其计算效率和...
C语言是一种基础且强大的编程语言,它在计算机...总的来说,这些C语言的常用方法和技巧提高了代码的效率、可读性和可移植性,是每个C程序员应该掌握的基本技能。在实际编程中灵活运用这些技巧,可以显著提升代码质量。
本资源“常用的数值算法C++实现”提供了一系列经典的数值分析算法的C++实现,帮助开发者理解和应用这些方法。以下是对每个压缩包内文件所对应算法的详细解释: 1. **最小二乘法.cpp**: 最小二乘法是一种优化技术...
SpringBoot简化了Spring应用的初始搭建以及开发过程,它集成了大量的常用组件,如数据访问、安全、缓存等,使得开发者可以快速地构建可运行的项目。在这个秒杀系统中,SpringBoot将作为基础框架,提供依赖管理和...
在实际应用中,除了幂法和反幂法,还有许多其他的方法,如QR分解法、雅可比方法、高斯-赛德尔迭代等,它们各有优势,适用于不同类型的矩阵和问题。选择合适的方法取决于矩阵的特性、所需精度以及计算资源。 总结来...
矩阵特征值与特征向量的计算是数值线性代数的重点,书中会讲解如何使用幂迭代法、雅可比法或者QR分解等方法来求解,这些方法在数据分析、控制系统设计等场景中有着广泛的应用。 最后,线性与非线性方程组求解是科学...
《基于SpringBoot的秒杀系统设计与实现...通过合理的架构设计和技术选型,实现了秒杀过程中的数据一致性、防刷策略以及系统稳定性。对于学习和实践SpringBoot应用开发,以及理解秒杀系统的设计原理具有很高的参考价值。
1. **线性方程组求解**:线性代数是许多科学问题的基础,包括矩阵求逆、高斯消元法、LU分解、Cholesky分解和QR分解等,它们都是解决大型线性系统的关键方法。 2. **插值**:插值用于估算未知数据点的值,如拉格朗日...
以上就是Java常用类库的一些核心知识点,涵盖了字符串处理、系统交互、国际化、日期时间、数学计算、大数处理、对象复制、数组操作、比较和正则表达式以及定时任务等多个方面,这些都是Java编程中不可或缺的基础工具...
这里的“复杂网络中常用模型的MATLAB实现”是针对这一领域的实践教程,包含BA模型(Barabási-Albert模型)以及其他两种网络模型的实现。以下是这些模型的详细介绍: 1. **BA模型**: BA模型由Barabási和Albert于...
伙伴系统是一种常用的内存管理策略,其核心思想是将内存分割成大小相同的块,并按照2的幂次方来组织。该方法能够快速分配和回收内存,同时减少了内存碎片。然而,伙伴系统通常不适合处理大小不一的内存分配请求,...
这些代码可能包括了模幂运算的函数,不同进制之间转换的函数,以及可能的加密算法实现,比如使用S盒的对称加密过程。通过阅读和理解这些代码,我们可以学习如何在实际项目中应用这些理论概念。 总的来说,模幂运算...
可以使用QR分解、幂迭代法或Lanczos算法等方法求解。 这些算法是计算机科学和工程计算的基础,熟练掌握它们对于提升编程能力、理解和解决实际问题至关重要。通过许士良的这个程序集,学习者可以深入理解这些算法的...