内容简介:客户端调用A服务的接口,A服务接口中又调用了B服务。 如果A服务和B服务都执行成功,则成功,并且二者事务都应提交; 如果A服务或B服务任意一个失败,则失败,且二者事务都不执行或回滚。 因为网络请求的不可靠性,如果A调用B失败,可能:1. B没有接收到网络请求;2. B收到后执行失败; 3. B执行成功,请求返回时异常。 4. B调用超时,B可能执行成功也可能失败。因此当A调用B时,可能出现不一致。A服务接口:B服务接口:
客户端调用A服务的接口,A服务接口中又调用了B服务。 如果A服务和B服务都执行成功,则成功,并且二者事务都应提交; 如果A服务或B服务任意一个失败,则失败,且二者事务都不执行或回滚。 因为网络请求的不可靠性,如果A调用B失败,可能:1. B没有接收到网络请求;2. B收到后执行失败; 3. B执行成功,请求返回时异常。 4. B调用超时,B可能执行成功也可能失败。因此当A调用B时,可能出现不一致。
接口调用几种方式
方式一:feign直接调用
A服务接口:
try { 业务代码A feign调用B服务接口 事务提交 } catch (Exception e) { 事务回滚 } 复制代码
B服务接口:
try { 业务代码B 事务提交 //此时接口状态码2XX } catch (Exception e) { 事务回滚 //此时接口状态码非2XX } 复制代码
一致性分析:
- feign调用B服务接口成功,状态码为2XX。则B服务事务已经提交,A进行事务提交。 若A事务提交成功,则一致; 若A事务提交失败但此时B中事务已经提交,则不一致。
- B没有接收到网络请求。B未被执行,feign调用抛出异常,A事务不进行提交,进入回滚,数据一致。
- B执行成功,请求返回时异常。B事务已经提交,feign调用B服务接口异常,A事务回滚,数据不一致。
- B调用超时。可能为B没有接收到网络请求,也可能B执行成功,请求返回时异常,也可能B收到请求响应缓慢。一致性状态不确定,都有可能。
方式二:基于可靠消息
服务A业务代码执行完后发送消息到消息队列,如rabbitmq,并用ack等方式确保发送成功; B服务接收消费成功后,手动确认消息,kafka则用手动提交位移的方式。
A服务生产者:
try { 业务代码A ack = rabbitmqProducer.sendAndGetAck if !ack { //发送失败可以设置自动重试,不重试就抛出异常 throws new RabbitmqSendException } 事务提交 } catch (Exception e) { //事务回滚 } 复制代码
B服务消息队列消费端一:
// try { 业务代码B channel.basicAck手动确认//kafka则手动提交位移 事务提交 //此时接口状态码2XX } catch (Exception e) { 事务回滚 //此时接口状态码非2XX } 复制代码
B服务消息队列消费端二:
// try { 业务代码B 事务提交 //此时接口状态码2XX } catch (Exception e) { 事务回滚 //此时接口状态码非2XX } channel.basicAck手动确认//kafka则手动提交位移 复制代码
一致性分析:
- A服务发送消息到消息队列成功,却提交事务失败,出现数据不一致。
- B消费端一:手动确认后,消息从消息队列删除,B事务提交失败,出现数据不一致。
- B消费端二:B事务提交成功,手动确认失败,可能会重复收到该条消息,出现不一致。 此时可在消息中添加uuid,服务B收到消息后根据uuid进行一次去重再处理等方式来实现幂等性。
方式三:预执行+确认+回查,类似TCC
A服务需要插入一个表transaction_record记录调用状态,提供给B服务回调。
A服务业务接口:
String uuid = generateUUID()//生成一个uuid try{ feign.preCreate(uuid,...)//feign调用B预执行,比如B服务为创建订单服务,预创建一个订单,但状态为待确认 业务代码A 将uuid插入transaction_record表中 事务提交 }catch (Exception e) { 事务回滚 feign.cancel(uuid)//feign调用B取消,比如B服务为创建订单服务,设置该订单状态为取消 } feign.confirm(uuid)//feign调用B确认,比如B服务为创建订单服务,设置该订单状态为确认,此时订单可用 复制代码
A服务服务回查接口,提供给B服务回查状态:
get /v1/transaction/{uuid} 从transaction_record表中查询,有则返回确认,没有则返回取消 复制代码
B服务需要提供预执行、确认、取消接口。若预执行后迟迟没有执行确认或取消,则B向A回查,根据结果确认或取消。
一致性分析:
- A中preCreate执行异常。应抛出异常,不再执行业务代码和事件表插入uuid,去执行cancel。若B执行则状态也为未确认,不影响一致性; 若cancel也执行失败,比如此时B挂掉,B重启后应去调用服务A的回查接口,确定状态。状态一致。
- 业务代码A执行失败,事务回滚,feign调用B取消。若取消成功,则状态一致;若取消失败,应抛出异常,confirm不再执行。状态一致。
- 业务代码A执行成功,事务提交失败,执行事务回滚。若回滚失败,抛出异常,不再执行cancel和confirm,B会执行超时回查确定状态;若回滚成功,则执行取消。状态一致。
- A事务提交成功,确认失败(比如A执行确认时,服务A或者B刚好挂掉)。则服务B超时回查,发现uuid存在,修改状态为确认。状态一致。
- 预处理完成后,去执行业务代码,若业务代码执行缓慢,B服务认为超时,则服务B超时回查,若A的事务还未提交,A的回查接口返回取消, 则B被取消,A却提交了事务,此时出现事务状态的不一致。此时可以通过设置B稍微大的超时时间来调整,可以让服务A在预处理的feign调用时传入期待的超时时间。
总结
以上是接口间调用的几种方式,这里只提供一种大概的思路,应用时可以自己优化,同步发送也可修改为异步+重试等方式。 若一致性要求可采用方式三,uuid+插入表也可采用其他的方式实现。为了提高一致性,接口调用也要尽量是幂等的,可通过业务逻辑的幂等性或 uuid实现。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Python源码剖析
陈儒 / 电子工业出版社 / 2008-6 / 69.80元
作为主流的动态语言,Python不仅简单易学、移植性好,而且拥有强大丰富的库的支持。此外,Python强大的可扩展性,让开发人员既可以非常容易地利用C/C++编写Python的扩展模块,还能将Python嵌入到C/C++程序中,为自己的系统添加动态扩展和动态编程的能力。. 为了更好地利用Python语言,无论是使用Python语言本身,还是将Python与C/C++交互使用,深刻理解Pyth......一起来看看 《Python源码剖析》 这本书的介绍吧!