利用业务事务处理WEB服务交互
WEB服务接口(这里专指有状态更新的服务接口)如果允许补偿操作的话,很多情况下可以遵循如下的业务事务交互方式来处理:
客户端步骤:
客户调用服务接口=>
1.、如果返回成功=>更新状态成功,继续本地系统处理
2、 如果返回失败=>更新状态失败,继续本地系统处理
3、 如果调用出现异常=>更新状态重试,继续本地系统处理,并采用定时器进行补偿重试调用服务接口,直到接口正确返回成功或者返回失败
服务端步骤:
1、接收到客户调用=>检查是否是同一个调用(根据OID,OID一致则是认为是同一个调用)=>如果不是同一个调用,执行并返回结果
2、 否则如果是同一个调用,返回第一次调用的执行的结果(可能是成功、也可能是失败,这里可以返回错误码)
客户方使用服务接口时需要提供的必要信息有:
1、客户方身份标志ID =>目的是标志客户身份
2、与业务相关的独立外部订单号,也可以叫做操作流水号(OID) ,对于该客户来说这个OID必须唯一(但是可以和其他客户的 OID相同) =>用来标记客户的消息,如果该客户每次都在该接口上使用相同的OID,那么服务方可以认为客户进行的是一个处理。
3、真正的业务数据
接口大概可以是这样 Result method(clientId,OID,bussinessData)throws Exception;
上面1、2是保证同一个客户的同一次操作多次调用不会产生副作用而建立的模型,3才是真正的业务数据。
为什么要这么设计呢?原因很简单, WEB服务调用需要考虑的通信异常的情况,如果服务调用时一旦发生通讯异常,那么客户将无法判定当前的服务操作是否成功。我们可以看一个例子:
A用调用银行的扣款接口,银行接口返回true,则表示扣款成功。返回false,则表示扣款失败,客户A根据返回值在本地做相应的处理。如果服务调用过程中,通讯出现了异常,那么客户A无法判定银行扣款是否成功,那么客户A需要银行提供的查询接口进行查询,来判定此次调用是否成功。这个时候就需要一个查询ID,我们这里使用OID来表示。那么银行怎么知道我们的OID呢? 所以就需要通过银行扣款接口来传递OID流水号,银行在处理扣款过程中,需要将客户的OID记录下来。一旦客户通过OID查询时,就可以得到扣款操作的结果。?(有的人可能不明白为什么这个OID一定要客户自己定义,而不是由银行来自动生成,道理很简单,因为如果在扣款接口调用出现异常时,我们根本无法得到银行提供的OID。)
正因为有了OID,所以扣款接口的处理逻辑需要有一定变化, 过程如下:
首先,银行在银行系统中需要检查客户A的该次调用的OID流水号是否存在,不存在表示没有进行过调用,则进行扣款操作并返回扣款结果。如果OID流水号存在,那么查询调用结果,并返回成功还是失败。
因此,对于客户A来说,这里的扣款接口其实是包含了查询和扣款两个功能,如果没有扣款,那么我就扣。如果已经扣了,那么就返回结果。
由于WEB服务的存在,要进行分布式事务处理的复杂度就非常高。如果系统需求允许采用补偿操作来进行扣款,我们就可以在调用扣款服务出现异常时,采用定时器或者其他方式补偿调用扣款服务接口来执行扣款。而本地系统则可以按照已经扣款的方式进行处理。这个是在牺牲了事务的部分完整性和一致性上来完成的。
一般情况下,使用这种业务事务就已经能够非常好的满足需求了,不需要再依赖底层的分布式事务处理,这样也节省了资源,提高了效率。
注:
事务的典型特点是具备ACID特征。ACID指的是Atomic(原子的)、Consistent(一致的)、Isolated(隔离的)以及Durable(持续的),它们代表着事务处理应该具备的四个特征:
原子性:组成事务处理的语句形成了一个逻辑单元,不能只执行其中的一部分
一致性:在事务处理执行之前和之后,数据是一致的。
隔离性:一个事务处理对另一个事务处理没有影响。
持续性:当事务处理成功执行到结束的时候,其效果在数据库中被永久纪录下来。
传统事务和业务事务区别:
传统事务要求系统在每一个时刻都保持一致性,而业务事务则放宽了这一限制,只要求系统处理数据最后的状态保持一致就可以了。