微服务架构-最终一致性方案设计思路及代码

1、如何做到消息一致,消息可靠投递

2、消息中间件的使用,快速入门rabbitMQ

3、设计消息子系统(重点)

 一、可靠消息最终一致(异步确保型)

 

实现

业务处理服务在业务事务提交前,向实时消息服务请求发送消息,实时消息服务只记录消息数据,而不真正发送。业务处理服务在业务事务提交后,向实时消息服务确认发送。只有在得到确认发送指令后,实时消息服务才真正发送

消息

    业务处理服务在业务事务回滚后,向实时消息服务取消发送。消息状态确认系统定期

找到未确认发送或回滚发送的消息,向业务处理服务询问消息状态,业务处理服务根

据消息ID或消息内容确定该消息是否有效 

 约束

被动方的处理结果不影响主动方的处理结果,被动方的消息处理操作是幂等操作

成本 

     可靠消息系统建设成本

     一次消息发送需要两次请求,业务处理服务需实现消息状态回查接口

优点、适用范围

消息数据独立存储、独立伸缩,降低业务系统与消息系统间的耦合

 需要实现的服务 

  可查询操作、幂等操作

  方案特点

兼容所有实现AMQP标准的MQ中间件(

确保业务数据可靠的前提下,实现业务数据的最终一致(理想状态下基本是准实时一致)

适用

    1、对应支付系统会计异步记账业务 

    2、普通的积分账户增加积分的服务

分布式部署环境下,需要通过网络进行通讯,就引入了数据传输的不确定性也就是CAP理论中的P(分区容错性的问题),但是我们需要保证消息尽可能的一致

消息发送一致性的概念:是指产生消息的业务动作与消息发送的一致。

(也就是说,如果业务操作成功,那么由这个业务操作所产生的消息一定要成功投递出去,否则就丢消息)

 举例:

 /** 支付订单处理 **/ 

 

public  completeOrder() { 

 

//订单服务本地更新订单状态(业务操作) 

 

orderDao::update(); 

 

// 调用积分服务给积分帐户增加积分(发送消息) 

 

pointService::update(); 

 

}

 

1、如果业务操作成功,执行消息发送前应用故障,消息发不出去,导致消息丢失(订单系统与积分系统的数据不一致); 

 

2、如果业务操作成功,应用正常,但消息系统故障或网络故障,也会导致消息发不出去(订单系统与积分系统的数据不一致);

 

2那消息投递如何做到可靠呢?

 

看看下图的思路:

 

1. 主动方应用先把消息发给消息中间件,消息状态标记为“待确认”; 

2. 消息中间件收到消息后,把消息持久化到消息存储中,但并不向被动方应用投递消息; 

3. 消息中间件返回消息持久化结果(成功/失败),主动方应用根据返 

回结果进行判断如何进行业务操作处理:

a) 失败:放弃业务操作处理,结束(必要时向上层返回失败结果); 

b) 成功:执行业务操作处理; 

4. 业务操作完成后,把业务操作结果(成功/失败)发送给消息中间件; 

5. 消息中间件收到业务操作结果后,根据业务结果进行处理; 

a) 失败:删除消息存储中的消息,结束; 

b) 成功:更新消息存储中的消息状态为“待发送(可发送)”,紧接 着执行消息投递; 

6. 被动方应用监听并接收“待发送”状态的消息,执行业务处理; 

7. 业务处理完成后,向消息中间件发送ACK,确认消息已经收到(消息 )

中间件将从队列中删除该消息)

当然除了正向的流程以外还有异常的处理流程,也要保证可靠性

 

 

1、从主动方应用的角度

 

 

 

 

 

 

 

2.从中间件的角度

 

 

 

 

 

 

 

 

3.异常情况的总结处理

 

 

 

 

 

三、常规消息队列的流程跟特点

 

 

 

MQ队列消息模型的特点 

队列消息模型的特点:

1、消息生产者将消息发送到Queue中,然后消息消费者监听Queue并接收消息;

2、消息被确认消费以后,就会从Queue中删除,所以消息消费者不会消费到已经被消费的消息;

3、Queue支持存在多个消费者,但是对某一个消息而言,只会有一个消费者成功消费。

3.1MQ队列消息的生产与消费常规流程

 

 

 

 

常用的MQ中间件产品 ActiveMQRabbitMQkafka等基本都是这样的流程基于AMQP协议

① Producer生成消息并发送给MQ(同步、异步); 

② MQ接收消息并将消息数据持久化到消息存储(持久化操作为可选配置); 

③ MQProducer返回消息的接收结果(返回值、异常); 

④ Consumer监听并消费MQ中的消息; 

⑤ Consumer获取到消息后执行业务处理; 

⑥ Consumer对已成功消费的消息向MQ进行ACK确认(确认后的消息将从MQ中删除)。

 

3.2AMQP协议的认识

 

 

 

RabbitMQ Server: 也叫broker server,它是一种传输服务。 他的角色就是维护一条从ProducerConsumer的路线,保证数据能够按照指定的方式进行传输。但是这个保证也不是100%的保证,但是对于普通的应用来说这已经足够了。当然对于商业系统来说,可以再做一层数据一致性的guard,就可以彻底保证系统的一致性了。

Producer: 消息生产者,如图ABC,数据的发送方。消息生产者连接RabbitMQ服务器然后将消息投递到Exchange

Consumer:消息消费者,如图123,数据的接收方。消息消费者订阅队列,RabbitMQQueue中的消息发送到消息消费者。

Exchange:生产者将消息发送到Exchange(交换器),由Exchange将消息路由到一个或多个Queue中(或者丢弃)。Exchange并不存储消息。RabbitMQ中的Exchangefanoutdirecttopicheaders四种类型,每种类型对应不同的路由规则,后面详细介绍这四种类型。

Queue:(队列)是RabbitMQ的内部对象,用于存储消息。消息消费者就是通过订阅队列来获取消息的,RabbitMQ中的消息都只能存储在Queue中,生产者生产消息并最终投递到Queue中,消费者可以从Queue中获取消息并消费。多个消费者可以订阅同一个Queue,这时Queue中的消息会被平均分摊给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理。 

RoutingKey:生产者在将消息发送给Exchange的时候,一般会指定一个routing key,来指定这个消息的路由规则,而这个routing key需要与Exchange Typebinding key联合使用才能最终生效。在Exchange Typebinding key固定的情况下(在正常使用时一般这些内容都是固定配置好的),我们的生产者就可以在发送消息给Exchange时,通过指定routing key来决定消息流向哪里。RabbitMQrouting key设定的长度限制为255 bytes

Connection: (连接)。ProducerConsumer都是通过TCP连接到RabbitMQ Server的。以后我们可以看到,程序的起始处就是建立这个TCP连接。

Channels: (信道)。它建立在上述的TCP连接中。数据流动都是在Channel中进行的。也就是说,一般情况是程序起始建立TCP连接,第二步就是建立这个Channel

通过我们的使用我们发现跟前面的消息一致性流程对比实际上现有的消息中间件,没办法满足一致性处理的流程

那怎么解决? 

四、当前包含了消息管理子系统

 

 

消息系统组成

 

  1.消息服务子系统:是最重要的一个子系统,它接收并存储预发送的消息,并提供进一步的确认功能。一般需要实现以下接口服务。

存储预发送消息(主动方应用系统)

确认并发送消息(主动方应用系统)

查询状态确认超时的消息(消息状态确认子系统)

确认消息已被成功消费(被动方应用系统)

查询消费确认超时的消息(消息恢复子系统)

2、消息管理子系统:提供一个可视化的管理界面,对可靠消息服务系统中的数据,进行查询和管理。比如可查看已死亡的消息,可通过界面手工重发等。

3、消息状态确认子系统:提供对异常情况的处理。当消息服务子系统收到并保存预发送消息,但因异常情况,没有收到确认发送消息时,这种消息不可能一直留存在数据库中。这种情况的数据,就需要消息状态确认子系统定期捞取这些待确认超时的数据,去调用主动方应用系统中的业务查询接口进行核对确认。根据核对结果决定是发送消息还是删除数据。

4、消息恢复子系统:如果消息数据已经接收到业务确认,这种经过业务确认的消息,就一定要发送到MQ,并被消费方成功消费,绝不能丢。消息恢复子系统定期捞取那些状态是“发送中”,而没有被消费确认的超时消息,进行重新发送。

5、实时消息服务子系统(MQ):消费方监听程序,接收MQ消息,成功处理后调用消息服务子系统的接口,确认消息已被成功消费,可以删除。

整体流程:

1用户下单,主动方应用预发送消息给消息服务子系统。

2消息服务子系统存储预发送的消息。

3返回存储预发送消息的结果。

4如果第3步返回的结果是成功的,则执行业务操作,否则不执行。

5业务操作成功后,调用消息服务子系统进行确认发送消息。

6将消息服务库中存储的预发送消息发送,并更新该消息的状态为已发送(但不是已被消费)。

7消息中间件发送消息到消费端应用。

8消费端应用调用被动方应用服务。

9被动方应用返回结果给消费端应用。

10消费端应用向消息中间件ack此条消息,并向消息服务子系统进行确认成功消费消息,11让消息服务子系统删除该条消息或者将状态置为已成功消费。

12消息状态子系统定时去查一下消息数据,看看有没有是已发送状态的超时消息,就是一直没有变成已成功消费的那种消息主动方应用系统应该提供查询接口,针对某条消息查询该条消息对应的业务数据是否为处理成功

13如果业务数据是处理成功的状态,那么就再次调用确认并发送消息,即进入第6步。

14如果业务数据是处理失败的,那么就调用消息服务子系统进行删除该条消息数据。

 

五、消息子系统的构建

1. 构建消息服务子系统(包含接口)

存储预发送消息(主动方应用系统)

确认并发送消息(主动方应用系统)

确认消息已被成功消费(被动方应用系统)

查询状态确认超时的消息(消息状态确认子系统)

查询消费确认超时的消息(消息恢复子系统)

2.构建其他服务(骨架搭建)

   1.订单服务

   2.积分服务  

   3.消息子系统

   4.网关服务

3.简单的封装rabbitMQ的连接池

1.解决连接池,无法取出连接的问题

4、消息发布确认

5、消息存储的设计(任务数据存储)

version 版本号

create_time  创建时间

message_id    消息ID

message_body  消息内容

consumer_queue  消费队列
message_retries_number  消息重发次数
dead     是否死亡status    状态(预发送、发送中)

因为后期需要通过筛选时间来找到超时的任务,得到某个任务的状态,所以需要hash存储数据,集合用做筛选

6、消息id的生成 

  必须是唯一的id,区分任务,通过任务id得到任务状态

7、每个服务提供任务执行结果查询接口

      1、每个服务都应该有任务状态服务接口,在分布式系统当中,为避免任务失败,所以会重试,如果同一个任务执行了两次,结果肯定不是我们期望的所以我们要解决幂等性问题。

8、消息任务状态子系统

消息状态确认子系统:提供对异常情况的处理。当消息服务子系统收到并保存预发送消息,但因异常情况,没有收到确认发送消息时,这种消息不可能一直留存在数据库中。这种情况的数据,就需要消息状态确认子系统定期捞取这些待确认超时的数据,去调用主动方应用系统中的业务查询接口进行核对确认。根据核对结果决定是发送消息还是删除数据。

 9、消息任务恢复子系统

消息恢复子系统:如果消息数据已经接收到业务确认,这种经过业务确认的消息,就一定要发送到MQ,并被消费方成功消费,绝不能丢。消息恢复子系统定期捞取那些状态是“发送中”,而没有被消费确认的超时消息,进行重新发送。

 

代码演示:https://github.com/sea24/Final-Consistency

posted @ 2019-11-07 17:53  sea24  阅读(1009)  评论(0编辑  收藏  举报