数据一致性(一) - 接口调用一致性

栏目: 后端 · 发布时间: 6年前

内容简介:客户端调用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
}
复制代码

一致性分析:

  1. feign调用B服务接口成功,状态码为2XX。则B服务事务已经提交,A进行事务提交。 若A事务提交成功,则一致; 若A事务提交失败但此时B中事务已经提交,则不一致。
  2. B没有接收到网络请求。B未被执行,feign调用抛出异常,A事务不进行提交,进入回滚,数据一致。
  3. B执行成功,请求返回时异常。B事务已经提交,feign调用B服务接口异常,A事务回滚,数据不一致。
  4. 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则手动提交位移
复制代码

一致性分析:

  1. A服务发送消息到消息队列成功,却提交事务失败,出现数据不一致。
  2. B消费端一:手动确认后,消息从消息队列删除,B事务提交失败,出现数据不一致。
  3. 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回查,根据结果确认或取消。

一致性分析:

  1. A中preCreate执行异常。应抛出异常,不再执行业务代码和事件表插入uuid,去执行cancel。若B执行则状态也为未确认,不影响一致性; 若cancel也执行失败,比如此时B挂掉,B重启后应去调用服务A的回查接口,确定状态。状态一致。
  2. 业务代码A执行失败,事务回滚,feign调用B取消。若取消成功,则状态一致;若取消失败,应抛出异常,confirm不再执行。状态一致。
  3. 业务代码A执行成功,事务提交失败,执行事务回滚。若回滚失败,抛出异常,不再执行cancel和confirm,B会执行超时回查确定状态;若回滚成功,则执行取消。状态一致。
  4. A事务提交成功,确认失败(比如A执行确认时,服务A或者B刚好挂掉)。则服务B超时回查,发现uuid存在,修改状态为确认。状态一致。
  5. 预处理完成后,去执行业务代码,若业务代码执行缓慢,B服务认为超时,则服务B超时回查,若A的事务还未提交,A的回查接口返回取消, 则B被取消,A却提交了事务,此时出现事务状态的不一致。此时可以通过设置B稍微大的超时时间来调整,可以让服务A在预处理的feign调用时传入期待的超时时间。

总结

以上是接口间调用的几种方式,这里只提供一种大概的思路,应用时可以自己优化,同步发送也可修改为异步+重试等方式。 若一致性要求可采用方式三,uuid+插入表也可采用其他的方式实现。为了提高一致性,接口调用也要尽量是幂等的,可通过业务逻辑的幂等性或 uuid实现。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Transcending CSS

Transcending CSS

Andy Clarke、Molly E. Holzschlag / New Riders / November 15, 2006 / $49.99

As the Web evolves to incorporate new standards and the latest browsers offer new possibilities for creative design, the art of creating Web sites is also changing. Few Web designers are experienced p......一起来看看 《Transcending CSS》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具