分布式、高性能和高可用解决方案 (分布式)

时间:2021-01-09 00:53:27


1 分布式

分布式系统会把一个应用系统拆分为可独立部署的多个服务,因此需要服务与服务之间远程协作才能完成事务操作。切记,分布式一定是存在网络分区的。

2 使用分布式需要解决哪些问题?

1)分布式 ID
2)rpc通信
3)分布式事务
4)分布式锁
5)分布式协调
下面我们逐一提供解决方法:

3 分布式ID

分布式、高性能和高可用解决方案 (分布式)


一个最基本的分布式 ID 需要满足下面这些要求:

  • 全局唯一 :ID 的全局唯一性肯定是首先要满足的!
  • 高性能 : 分布式 ID 的生成速度要快,对本地资源消耗要小。
  • 高可用 :生成分布式ID 的服务要保证可用性无限接近于 100%。
  • 方便易用 :拿来即用,使用方便,快速接入!

除了这些之外,一个比较好的分布式 ID 还应保证:

  • 安全 :ID 中不包含敏感信息。 有序递增 :如果要把 ID 存放在数据库的话,ID 的有序性可以提升数据库写入速度。并且,很多时候,我们还很有可能会直接通过 ID 来进行排序。
  • 有具体的业务含义 :生成的 ID 如果能有具体的业务含义,可以让定位问题以及开发更透明化(通过 ID 就能确定是哪个业务)。
  • 独立部署:也就是分布式系统单独有一个发号器服务,专门用来生成分布式 ID。这样就生成 ID的服务可以和业务相关的服务解耦。不过,这样同样带来了网络调用消耗增加的问题。 总的来说,如果需要用到分布式 ID的场景比较多的话,独立部署的发号器服务还是很有必要的。

3.1 UUID

分布式、高性能和高可用解决方案 (分布式)


UUID 可以保证唯一性,因为其生成规则包括 MAC 地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素,计算机基于这些规则生成的 UUID 是肯定不会重复的。

UUID 的优缺点 :

优点 :生成速度比较快、简单易用

缺点 : 存储消耗空间大(32 个字符串,128 位) 、 不安全(基于 MAC 地址生成 UUID 的算法会造成 MAC 地址泄露)、无序(非自增)、没有具体业务含义、需要解决重复 ID 问题(当机器时间不对的情况下,可能导致会产生重复 ID)。

3.2 Snowflake(雪花算法)

Snowflake 是 Twitter 开源的分布式 ID 生成算法。Snowflake 由 64 bit 的二进制数字组成,这 64bit 的二进制被分成了几部分,每一部分存储的数据都有特定的含义:

分布式、高性能和高可用解决方案 (分布式)


有很多基于 Snowflake 算法的开源实现比如美团 的 Leaf、百度的UidGenerator,并且这些开源实现对原有的 Snowflake 算法进行了优化。

Snowflake 算法的优缺点 :

优点 :生成速度比较快、生成的 ID 有序递增、比较灵活(可以对 Snowflake 算法进行简单的改造比如加入业务 ID)

缺点 : 需要解决重复 ID 问题(依赖时间,当机器时间不对的情况下,可能导致会产生重复 ID)。

3.3 数据库号段模式

将ID存在在内存里面,需要用到的时候,直接从内存里面拿。数据库的号段模式也是目前比较主流的一种分布式 ID 生成方式。像滴滴开源的Tinyid (opens new window) 就是基于这种方式来做的。 不过,TinyId 使用了双号段缓存、增加多 db 支持等方式来进一步优化。

分布式、高性能和高可用解决方案 (分布式)


current_max_id 字段和step字段主要用于获取批量 ID,获取的批量 id 为: current_max_id ~ current_max_id+step。version 字段主要用于解决并发问题(乐观锁),biz_type 主要用于表示业余类型。

数据库号段模式的优缺点:

优点 :ID 有序递增、存储消耗空间小

缺点 :存在数据库单点问题(可以使用数据库集群解决,不过增加了复杂度)、ID 没有具体业务含义、安全问题(比如根据订单 ID 的递增规律就能推算出每天的订单量,商业机密啊!)

3 RPC通信

3.1 什么是 RPC?

RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。比如两个不同的服务 A、B 部署在两台不同的机器上,那么服务 A 如果想要调用服务 B 中的某个方法该怎么办呢?使用 HTTP请求 当然可以,但是可能会比较慢而且一些优化做的并不好。 RPC 的出现就是为了解决这个问题。

3.2 RPC原理是什么

分布式、高性能和高可用解决方案 (分布式)

  • 服务消费端(client)以本地调用的方式调用远程服务;
  • 客户端 Stub(client stub)接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体(序列化):RpcRequest;
  • 客户端 Stub(client stub) 找到远程服务的地址,并将消息发送到服务提供端;
  • 服务端 Stub(桩)收到消息将消息反序列化为Java对象:RpcRequest;
  • 服务端 Stub(桩)根据RpcRequest中的类、方法、方法参数等信息调用本地的方法;
  • 服务端Stub(桩)得到方法执行结果并将组装成能够进行网络传输的消息体:RpcResponse(序列化)发送至消费方;
  • 客户端Stub(client stub)接收到消息并将消息反序列化为Java对象:RpcResponse ,这样也就得到了最终结果。

3.3 RPC 解决了什么问题?

从上面对 RPC 介绍的内容中,概括来讲RPC 主要解决了:让分布式或者微服务系统中不同服务之间的调用像本地调用一样简单。

3.4 常用微服务RPC框架

跟语言平台绑定的开源 RPC 框架主要有下面几种。

  • Dubbo:国内最早开源的 RPC 框架,由阿里巴巴公司开发并于 2011 年末对外开源,仅支持 Java 语言。
  • Motan:微博内部使用的 RPC 框架,于 2016 年对外开源,仅支持 Java 语言。
  • Tars:腾讯内部使用的 RPC 框架,于2017 年对外开源,仅支持 C++ 语言。
  • Spring Cloud:国外 Pivotal 公司 2014 年对外开源的 RPC框架,仅支持Java 语言

而跨语言平台的开源 RPC 框架主要有以下几种。

  • gRPC:Google 于 2015 年对外开源的跨语言 RPC 框架,支持多种语言。
  • Thrift:最初是由 Facebook开发的内部系统跨语言的 RPC 框架,2007 年贡献给了 Apache 基金,成为 Apache 开源项目之一,支持多种语言。
    毫无疑问,我们肯定是选用Spring Cloud
  • 分布式、高性能和高可用解决方案 (分布式)

  • 根据上图描述,Spring Cloud 微服务架构是由多个组件一起组成的,各个组件的交互流程如下。
    请求统一通过 API 网关 Zuul 来访问内部服务,先经过 Token 进行安全认证。 通过安全认证后,网关 Zuul 从注册中心Eureka 获取可用服务节点列表。 从可用服务节点中选取一个可用节点,然后把请求分发到这个节点。整个请求过程中,Hystrix组件负责处理服务超时熔断,Turbine 组件负责监控服务间的调用和熔断相关指标,Sleuth 组件负责调用链监控,ELK 负责日志分析。

3.5 什么是fegin

有了 Eureka ,RestTemplate ,Ribbon, 我们就可以愉快地进行服务间的调用了,但是使用 RestTemplate 还是不方便,我们每次都要进行这样的调用。
。我们可以将被调用的服务代码映射到消费者端,这样我们就可以 **“无缝开发” **啦。

// 使用 @FeignClient 注解来指定提供者的名字
@FeignClient(value = "eureka-client-provider")
public interface TestClient {
// 这里一定要注意需要使用的是提供者那端的请求相对路径,这里就相当于映射了
@RequestMapping(value = "/provider/xxx",
method = RequestMethod.POST)
CommonResponse<List<Plan>> getPlans(@RequestBody planGetRequest request);
}
4 分布式事务

4.1 CAP和BASE原则

4.1.1 理解CAP

CAP是 Consistency、Availability、Partition tolerance三个词语的缩写,分别表示一致性、可用性、分区容忍性。

下面通过数据库读写分离来解释CAP

分布式、高性能和高可用解决方案 (分布式)


C - Consistency:

一致性是指写操作后的读操作可以读取到最新的数据状态,当数据分布在多个节点上,从任意结点读取到的数据都是最新的状态。

主数据库中写入,从从数据库中读取的一定是主数据库中写入的数据。同步的时候,从数据库进行锁定,保证读取的数据是最新的,如果写入失败,则返回返回错误或响应超时。

A - Availability :

可用性是指任何事务操作都可以得到响应结果,且不会出现响应超时或响应错误。

不能对从数据库进行锁定,可以读取旧的数据,但是不能返回错误或者响应超时。

P - Partition tolerance :

通常分布式系统的各各结点部署在不同的子网,这就是网络分区,不可避免的会出现由于网络问题而导致结点之间通信失败,此时仍可对外提供服务,这叫分区容忍性。

分区容忍性分是布式系统具备的基本能力,是必备的条件。

4.1.2 CAP组合方式

1)AP:放弃一致性,追求分区容忍性和可用性。这是很多分布式系统设计时的选择。比如商品管理,完全可以实现AP,前提是只要用户可以接受所查询的到数据在一定时间内不是最新的即可。
2)CP:放弃可用性,追求一致性和分区容错性,我们的zookeeper其实就是追求的强一致,又比如跨行转账,一次转账请求要等待双方银行系统都完成整个事务才算完成。
3)CA:放弃分区容忍性,即不进行分区,不考虑由于网络不通或结点挂掉的问题,则可以实现一致性和可用性。那么系统将不是一个标准的分布式系统,我们最常用的关系型数据就满足了CA。

4.1.3 BASE理论

BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent (最终一致性)三个短语的缩写。BASE理论是对CAP中AP的一个扩展,通过牺牲强一致性来获得可用性,当出现故障允许部分不可用但要保证核心功能可用,允许数据在一段时间内是不一致的,但最终达到一致状态。 满足BASE理论的事务,我们称之为“柔性事务”。

4.2 2PC

2PC即两阶段提交协议,是将整个事务流程分为两个阶段,准备阶段(Prepare phase)、提交阶段(commitphase),2是指两个阶段,P是指准备阶段,C是指提交阶段。即,每个分支事务完成成功,全局事务执行成功,有一个失败,就回滚全局事务。

分布式、高性能和高可用解决方案 (分布式)

4.2.1 XA方案

分布式、高性能和高可用解决方案 (分布式)

AP(Application Program):即应用程序,可以理解为使用DTP分布式事务的程序。
RM(Resource Manager):即资源管理器,可以理解为事务的参与者,一般情况下是指一个数据库实例,通过资源管理器对该数据库进行控制,资源管理器控制着分支事务。
TM(Transaction Manager):事务管理器,负责协调和管理事务,事务管理器控制着全局事务,管理事务生命周期,并协调各个RM。全局事务是指分布式事务处理环境中,需要操作多个数据库共同完成一个工作,这个工作即是一个全局事务。
执行过程
1)在准备阶段RM执行实际的业务操作,但不提交事务,资源锁定;
2)在提交阶段TM会接受RM在准备阶段的执行回复,只要有任一个RM执行失败,TM会通知所有RM执行回滚操作,否则,TM将会通知所有RM提交该事务。提交阶段结束资源锁释放。
存在的问题:
1、需要本地数据库支持XA协议。
2、资源锁需要等到两个阶段结束才释放,性能较差。

4.2.2 Seata方案

Seata是由阿里中间件团队发起的开源项目 Fescar,后更名为Seata,它是一个是开源的分布式事务框架。

分布式、高性能和高可用解决方案 (分布式)

  • Transaction Coordinator (TC):事务协调器,它是独立的中间件,需要独立部署运行,它维护全局事务的运行状态,接收TM指令发起全局事务的提交与回滚,负责与RM通信协调各各分支事务的提交或回滚。
  • Transaction Manager(TM):事务管理器,TM需要嵌入应用程序中工作,它负责开启一个全局事务,并最终向TC发起全局提交或全局回滚的指令
  • ResourceManager (RM): 控制分支事务,负责分支注册、状态汇报,并接收事务协调器TC的指令,驱动分支(本地)事务的提交和回滚。

具体的执行流程如下:

  1. 用户服务的 TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID。
  2. 用户服务的 RM 向 TC 注册 分支事务,该分支事务在用户服务执行新增用户逻辑,并将其纳入 XID 对应全局
    事务的管辖。
  3. 用户服务执行分支事务,向用户表插入一条记录。
  4. 逻辑执行到远程调用积分服务时(XID 在微服务调用链路的上下文中传播)。积分服务的RM 向 TC 注册分支事
    务,该分支事务执行增加积分的逻辑,并将其纳入 XID 对应全局事务的管辖。
  5. 积分服务执行分支事务,向积分记录表插入一条记录,执行完毕后,返回用户服务。
  6. 用户服务分支事务执行完毕。
  7. TM 向 TC 发起针对 XID 的全局提交或回滚决议。
  8. TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。

分布式、高性能和高可用解决方案 (分布式)


具体项目使用

@Service
public class AccountInfoServiceImpl implements AccountInfoService {
private Logger logger = LoggerFactory.getLogger(AccountInfoServiceImpl.class);
@Autowired
AccountInfoDao accountInfoDao;
@Autowired
Bank2Client bank2Client;

//张三转账
@Override
@GlobalTransactional
@Transactional
public void updateAccountBalance(String accountNo, Double amount) {
logger.info("******** Bank1 Service Begin ... xid: {}", RootContext.getXID());
//张三扣减金额
accountInfoDao.updateAccountBalance(accountNo, amount *‐1);
//向李四转账
String remoteRst = bank2Client.transfer(amount);
//远程调用失败
if (remoteRst.equals("fallback")) {
throw new RuntimeException("bank1 下游服务异常");
}
//人为制造错误
if (amount == 3) {
throw new RuntimeException("bank1 make exception 3");
}
}
}

将@GlobalTransactional注解标注在全局事务发起的Service实现方法上,开启全局事务:GlobalTransactionalInterceptor会拦截@GlobalTransactional注解的方法,生成全局事务ID(XID),XID会在整个分布式事务中传递。
在远程调用时,spring-cloud-alibaba-seata会拦截Feign调用将XID传递到下游服务。
Seata实现2PC要点:
1、全局事务开始使用 @GlobalTransactional标识 。
2、每个本地事务方案仍然使用@Transactional标识。
3、每个数据都需要创建undo_log表,此表是seata保证本地事务一致性的关键。

4.3 TCC

TCC是Try、Confirm、Cancel三个词语的缩写,TCC要求每个分支事务实现三个操作:预处理Try、确认Confirm、撤销Cancel。Try操作做业务检查及资源预留,Confirm做业务确认操作,Cancel实现一个与Try相反的操作即回滚操作。TM首先发起所有的分支事务的try操作,任何一个分支事务的try操作执行失败,TM将会发起所有分支事务的Cancel操作,若try操作全部成功,TM将会发起所有分支事务的Confirm操作,其中Confirm/Cancel操作若执行失败,TM会进行重试。先尝试,如果都能满足则confirm,否则就cancel。
TCC存在的三个问题:

1)空回滚:还没有try,就cancel了
2)幂等:try,confirm,cancel每次操作结果不同
3) 悬挂:只try没有cancel
问题解决:
1)账户A的cancel方法需要判断try方法是否执行,正常执行try后方可执行cancel。
2)try,cancel、confirm方法实现幂等。
3)账号B在try方法中不允许更新账户金额,在confirm中更新账户金额。
4)账户B的cancel方法需要判断try方法是否执行,正常执行try后方可执行cancel。
账户A

try:
try幂等校验
try悬挂处理
检查余额是否够30元
扣减30元
confirm:

cancel:
cancel幂等校验
cancel空回滚处理
增加可用余额30元

账户B

try:

confirm:
confirm幂等校验
正式增加30元
cancel:

4.3.1 具体实现

不推荐使用,代码侵入性太强

4.4 分布式事务解决方案之可靠消息最终一致性

可靠消息最终一致性方案是指当事务发起方执行完成本地事务后并发出一条消息,事务参与方(消息消费者)一定能够接收消息并处理事务成功,此方案强调的是只要消息发给事务参与方最终事务要达到一致。
需要解决的三个问题:
1.本地事务与消息发送的原子性问题
本地事务与消息发送的原子性问题即:事务发起方在本地事务执行成功后消息必须发出去,否则就丢弃消息。即实现本地事务和消息发送的原子性,要么都成功,要么都失败。本地事务与消息发送的原子性问题是实现可靠消息最终一致性方案的关键问题。
2、事务参与方接收消息的可靠性
事务参与方必须能够从消息队列接收到消息,如果接收消息失败可以重复接收消息。
3、消息重复消费的问题
由于网络2的存在,若某一个消费节点超时但是消费成功,此时消息中间件会重复投递此消息,就导致了消息的重复消费。
要解决消息重复消费的问题就要实现事务参与方的方法幂等性。

4.4.1 本地消息表方案

本地消息表这个方案最初是eBay提出的,此方案的核心是通过本地事务保证数据业务操作和消息的一致性,然后通过定时任务将消息发送至消息中间件,待确认消息发送给消费方成功再将消息删除。

分布式、高性能和高可用解决方案 (分布式)

4.4.2 RocketMQ事务消息方案

分布式、高性能和高可用解决方案 (分布式)


执行流程如下: 为方便理解我们还以注册送积分的例子来描述 整个流程。 Producer 即MQ发送方,本例中是用户服务,负责新增用户。MQ订阅方即消息消费方,本例中是积分服务,负责新增积分。

1、Producer 发送事务消息

Producer (MQ发送方)发送事务消息至MQ Server,MQ Server将消息状态标记为Prepared(预备状态),注意此时这条消息消费者(MQ订阅方)是无法消费到的。 本例中,Producer 发送 ”增加积分消息“ 到MQ Server。

2、MQ Server回应消息发送成功

MQ Server接收到Producer 发送给的消息则回应发送成功表示MQ已接收到消息。 (解决问题1

3、Producer 执行本地事务

Producer 端执行业务代码逻辑,通过本地数据库事务控制。 本例中,Producer 执行添加用户操作。

4、消息投递

若Producer 本地事务执行成功则自动向MQServer发送commit消息,MQ Server接收到commit消息后将”增加积分消息“ 状态标记为可消费,此时MQ订阅方(积分服务)即正常消费消息,若消费不成功,MQ可以重复发送消息(解决问题2);

若Producer 本地事务执行失败则自动向MQServer发送rollback消息,MQ Server接收到rollback消息后将删除”增加积分消息“ 。

MQ订阅方(积分服务)消费消息,消费成功则向MQ回应ack,否则将重复接收消息。这里ack默认自动回应,即 程序执行正常则自动回应ack。(解决问题3

5、事务回查

如果执行Producer端本地事务过程中,执行端挂掉,或者超时,MQ Server将会不停的询问同组的其他 Producer来获取事务执行状态,这个过程叫事务回查。MQ Server会根据事务回查结果来决定是否投递消息。

以上主干流程已由RocketMQ实现,对用户侧来说,用户需要分别实现本地事务执行以及本地事务回查方法,因此只需关注本地事务的执行状态即可。

public interface RocketMQLocalTransactionListener {
/**
* ‐ 发送prepare消息成功此方法被回调,该方法用于执行本地事务
* ‐ @param msg 回传的消息,利用transactionId即可获取到该消息的唯一Id
* ‐ @param arg 调用send方法时传递的参数,当send时候若有额外的参数可以传递到send方法中,这里能获取到
* ‐ @return 返回事务状态,COMMIT:提交 ROLLBACK:回滚 UNKNOW:回调
*/
RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg);

/**
* ‐ @param msg 通过获取transactionId来判断这条消息的本地事务执行状态
* ‐ @return 返回事务状态,COMMIT:提交 ROLLBACK:回滚 UNKNOW:回调
*/
RocketMQLocalTransactionState checkLocalTransaction(Message msg);
}

4.4.3 项目中实现

分布式、高性能和高可用解决方案 (分布式)

@Service
@Slf4j
public class AccountInfoServiceImpl implements AccountInfoService {
@Resource
private RocketMQTemplate rocketMQTemplate;
@Autowired
private AccountInfoDao accountInfoDao;

/**
* 更新帐号余额‐发送消息
* producer向MQ Server发送消息
*
* @param accountChangeEvent
*/
@Override
public void sendUpdateAccountBalance(AccountChangeEvent accountChangeEvent) {
//构建消息体
JSONObject jsonObject = new JSONObject();
jsonObject.put("accountChange", accountChangeEvent);
Message<String> message =
MessageBuilder.withPayload(jsonObject.toJSONString()).build();
TransactionSendResult sendResult =
rocketMQTemplate.sendMessageInTransaction("producer_group_txmsg_bank1", "topic_txmsg", message,
null);
log.info("send transcation message body={},result= {} ",message.getPayload(),sendResult.getSendStatus());
}

/**
* 更新帐号余额‐本地事务
* producer发送消息完成后接收到MQ Server的回应即开始执行本地事务
*
* @param accountChangeEvent
*/
@Transactional
@Override
public void doUpdateAccountBalance(AccountChangeEvent accountChangeEvent) {
log.info("开始更新本地事务,事务号:{}", accountChangeEvent.getTxNo());
accountInfoDao.updateAccountBalance(accountChangeEvent.getAccountNo(), accountChangeEvent.getAmount() * ‐1);
//为幂等作准备
accountInfoDao.addTx(accountChangeEvent.getTxNo());
if (accountChangeEvent.getAmount() == 2) {
throw new RuntimeException("bank1更新本地事务时抛出异常");
}
log.info("结束更新本地事务,事务号:{}", accountChangeEvent.getTxNo());
}
}

编写RocketMQLocalTransactionListener接口实现类,实现执行本地事务和事务回查两个方法。

@Component
@Slf4j
@RocketMQTransactionListener(txProducerGroup = "producer_group_txmsg_bank1")
public class ProducerTxmsgListener implements RocketMQLocalTransactionListener {
@Autowired
AccountInfoService accountInfoService;
@Autowired
AccountInfoDao accountInfoDao;

//消息发送成功回调此方法,此方法执行本地事务
@Override
@Transactional
public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object arg) {
//解析消息内容
try {
String jsonString = new String((byte[]) message.getPayload());
JSONObject jsonObject = JSONObject.parseObject(jsonString);
AccountChangeEvent accountChangeEvent =
JSONObject.parseObject(jsonObject.getString("accountChange"), AccountChangeEvent.class);
//扣除金额
accountInfoService.doUpdateAccountBalance(accountChangeEvent);
return RocketMQLocalTransactionState.COMMIT;
} catch (Exception e) {
log.error("executeLocalTransaction 事务执行失败", e);
e.printStackTrace();
return RocketMQLocalTransactionState.ROLLBACK;
}
}

//此方法检查事务执行状态
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
RocketMQLocalTransactionState state;
final JSONObject jsonObject = JSON.parseObject(new String((byte[])
message.getPayload()));
AccountChangeEvent accountChangeEvent =
JSONObject.parseObject(jsonObject.getString("accountChange"), AccountChangeEvent.class);
//事务id
String txNo = accountChangeEvent.getTxNo();
int isexistTx = accountInfoDao.isExistTx(txNo);
log.info("回查事务,事务号: {} 结果: {}", accountChangeEvent.getTxNo(), isexistTx);
if (isexistTx > 0) {
state = RocketMQLocalTransactionState.COMMIT;
} else {
state = RocketMQLocalTransactionState.UNKNOWN;
}
return state;
}
}
@RestController
@Slf4j
public class AccountInfoController {
@Autowired
private AccountInfoService accountInfoService;

@GetMapping(value = "/transfer")
public String transfer(@RequestParam("accountNo") String accountNo, @RequestParam("amount")
Double amount) {
String tx_no = UUID.randomUUID().toString();
AccountChangeEvent accountChangeEvent = new AccountChangeEvent(accountNo, amount, tx_no);
accountInfoService.sendUpdateAccountBalance(accountChangeEvent);
return "转账成功";
}
}

4.5 分布式事务解决方案之最大努力通知

分布式、高性能和高可用解决方案 (分布式)


交互流程:

1、账户系统调用充值系统接口

2、充值系统完成支付处理向账户系统发起充值结果通知若通知失败,则充值系统按策略进行重复通知

3、账户系统接收到充值结果通知修改充值状态。

4、账户系统未接收到通知会主动调用充值系统的接口查询充值结果。

最大努力通知与可靠消息一致性有什么不同?

1、解决方案思想不同
可靠消息一致性,发起通知方需要保证将消息发出去,并且将消息发到接收通知方,消息的可靠性关键由发起通知方来保证。
最大努力通知,发起通知方尽最大的努力将业务处理结果通知为接收通知方,但是可能消息接收不到,此时需要接收通知方主动调用发起通知方的接口查询业务处理结果,通知的可靠性关键在接收通知方。
2、两者的业务应用场景不同
可靠消息一致性关注的是交易过程的事务一致,以异步的方式完成交易。
最大努力通知关注的是交易后的通知事务,即将交易结果可靠的通知出去。
3、技术解决方向不同
可靠消息一致性要解决消息从发出到接收的一致性,即消息发出并且被接收到。最大努力通知无法保证消息从发出到接收的一致性,只提供消息接收的可靠性机制。可靠机制是,最大努力的将消息通知给接收方,当消息无法被接收方接收时,由接收方主动查询消息(业务处理结果)。

5 分布式锁