githubjava
客户端调用A服务的接口,A服务接口中又调用了B服务。 若是A服务和B服务都执行成功,则成功,而且两者事务都应提交; 若是A服务或B服务任意一个失败,则失败,且两者事务都不执行或回滚。 由于网络请求的不可靠性,若是A调用B失败,可能:1. B没有接收到网络请求;2. B收到后执行失败; 3. B执行成功,请求返回时异常。 4. B调用超时,B可能执行成功也可能失败。所以当A调用B时,可能出现不一致。git
A服务接口:github
try {
业务代码A
feign调用B服务接口
事务提交
} catch (Exception e) {
事务回滚
}
复制代码
B服务接口:bash
try {
业务代码B
事务提交
//此时接口状态码2XX
} catch (Exception e) {
事务回滚
//此时接口状态码非2XX
}
复制代码
一致性分析:网络
服务A业务代码执行完后发送消息到消息队列,如rabbitmq,并用ack等方式确保发送成功; B服务接收消费成功后,手动确认消息,kafka则用手动提交位移的方式。异步
A服务生产者:优化
try {
业务代码A
ack = rabbitmqProducer.sendAndGetAck
if !ack {
//发送失败能够设置自动重试,不重试就抛出异常
throws new RabbitmqSendException
}
事务提交
} catch (Exception e) {
//事务回滚
}
复制代码
B服务消息队列消费端一:ui
//
try {
业务代码B
channel.basicAck手动确认//kafka则手动提交位移
事务提交
//此时接口状态码2XX
} catch (Exception e) {
事务回滚
//此时接口状态码非2XX
}
复制代码
B服务消息队列消费端二:spa
//
try {
业务代码B
事务提交
//此时接口状态码2XX
} catch (Exception e) {
事务回滚
//此时接口状态码非2XX
}
channel.basicAck手动确认//kafka则手动提交位移
复制代码
一致性分析:code
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回查,根据结果确认或取消。
一致性分析:
以上是接口间调用的几种方式,这里只提供一种大概的思路,应用时能够本身优化,同步发送也可修改成异步+重试等方式。 若一致性要求可采用方式三,uuid+插入表也可采用其余的方式实现。为了提升一致性,接口调用也要尽可能是幂等的,可经过业务逻辑的幂等性或 uuid实现。