消息队列带来的问题

复杂性问题

MQ的加入大大增加了系统的复杂度,以前系统是同步的远程调用,现在是通过MQ的异步调用。

一致性问题

A系统处理完业务,通过MQ给B,C,D三个系统发送消息数据,如果B系统,C系统处理成功,D系统处理失败。

可用性问题

如果MQ挂掉,系统中依赖MQ的服务都将不可用。比如:用户订单下单付款成功,会发送消息积分奖励消息到MQ。监听队列的消费者收到消息后给用户下发积分。如果MQ挂掉,用户收不到短信通知也收不到积分奖励。

复杂性问题

消息重复消费

解决:        幂等设计的方案

约束控制:数据库的唯一索引或者redis的setnx

前置条件:数据库的查询条件+分布式锁

全局id:生成消息的唯一id

消息丢失问题

解决:三个阶段保证。第一阶段:消息的生产阶段;第二阶段:消息的存储阶段;第三阶段:消息的消费阶段。

生产阶段:ack机制。消息发送到broker后,会返回给生产端消息已收到。若长时间未确认,生产端会重发。

存储阶段:一般消息不会丢失,除非服务器宕机。可以做持久化操作,消息发送到broker会做持久化到硬盘。成功后再返回给生产方确认机制。

消费阶段:与生产阶段类似。ack请求-确认机制。消费端成功处理后消息后,给broker发送确认消息。

有序性消费

比如商城下订单,订单付款成功,需要给用户发放积分,短信通知。发送积分和短信通知需要顺序消费。积分奖励发送成功之后才给用户发送短信。可以根据订单号哈希取模。保证同一个订单号的积分奖励和短信通知写入同一个队列。一个队列保证一个消费者,这样就可以保证消息顺序消费(局部顺序消费)。这样效率不高,我们同样可以开启多线程消费一个队列,但也要保证同一个消息在同一个线程里被消费。

一致性问题

产生消息的业务动作与发送的一致,就是说,如果操作成功了,那么这个操作产生的消息一定要发送出去,否则就丢失消息了。另一方面,如果业务行为没有发生或者失败则不能把消息发送出去。

事务消息:实现了消息生成者本地事务与消息发送的原子性,保证消息生成者本地事务处理成功与消息发送成功的最终一致性问题。

  1.  事务消息与普通消息的区别就在于消息生产环节,生产者首先预发送一条消息到MQ(这也被称为发送half消息)
  2. MQ接受到消息后,先进行持久化,则存储中会新增一条状态为待发送的消息
  3. 然后返回ACK给消息生产者,此时MQ不会触发消息推送事件
  4. 生产者预发送消息成功后,执行本地事务
  5. 执行本地事务,执行完成后,发送执行结果给MQ
  6. MQ会根据结果删除或者更新消息状态为可发送
  7. 如果消息状态更新为可发送,则MQ会push消息给消费者,后面消息的消费和普通消息是一样的

注意点:由于MQ通常都会保证消息能够投递成功,因此,如果业务没有及时返回ACK结果,那么就有可能造成MQ的重复消息投递问题。因此,对于消息最终一致性的方案,消息的消费者必须要对消息的消费支持幂等,不能造成同一条消息的重复消费的情况。

  如果consumer消费失败,是否需要producer做回滚?

  答:事务消息,producer不会因为consumer消费失败而做回滚,采用事务消息的应用,其所追求的是高可用最终一致性,消息消费失败的话,MQ自己会负责重推消息,直到消费成功。因此,事务消息是针对生产端而言的,而消费端,消费端的一致性是通过MQ的重试机制来完成的。

基于本地消息的最终一致性

基于本地消息的最终一致性方案的最核心做法就是在执行业务操作的时候,记录一条消息数据到DB,并且消息数据的记录与业务数据的记录必须在同一个事务内完成,这是该方案的前提核心保障。在记录完成后消息数据后,后面我们就可以通过一个定时任务到DB中去轮训状态为待发送的消息,然后将消息投递给MQ。这个过程中可能存在消息投递失败的可能,此时就依靠重试机制来保证,直到成功收到MQ的ACK确认之后,再将消息状态更新或者消息清除;而后面消息的消费失败的话,则依赖MQ本身的重试来完成,其最后做到两边系统数据的最终一致性。

        问题:每个业务系统在使用该方案时,都需要在对应的业务库创建一张消息表来存储消息,可以将该功能单独提取出来,做成一个消息服务来统一处理。

独立消息服务的最终一致性

过程其实就是模拟了事务消息的消息预发送过程,如果预发送消息失败,生产者业务就不会去执行,对于生产者的业务而言,它是强依赖于该消息服务的。不过好在独立消息服务支持水平扩容,只要部署多台,做成HA的集群模式,就能够保证其可靠性。在消息服务中,还有一个单独地定时任务,会定期轮训长时间处于待发送状态的消息,通过一个check补偿机制来确认该消息对应的业务是否成功,如果对应的业务处理成功,则将消息修改为可发送,然后将其投递给MQ;如果业务处理失败,则将对应的消息更新或者删除即可。因此在使用该方案时,消息生产者必须同时实现一个check服务,来供消息服务做消息的确认。对于消息的消费,该方案与上面的处理是一样,都是通过MQ自身的重发机制来保证消息被消费。 

可用性问题

搭建集群方式

搭建步骤

  • 修改 3 台机器的主机名称 vim /etc/hostname
  • 配置各个节点的 hosts 文件,让各个节点都能互相识别对方
         vim /etc/hosts
        10.211.55.74 node1  
        10.211.55.75 node2
        10.211.55.76 node3

  • 以确保各个节点的 cookie 文件使用的是同一个值
        在 node1 上执行远程操作命令
        scp /var/lib/rabbitmq/.erlang.cookie root@node2:/var/lib/rabbitmq/.erlang.cookie
        scp /var/lib/rabbitmq/.erlang.cookie root@node3:/var/lib/rabbitmq/.erlang.cookie
  • 启动 RabbitMQ 服务,顺带启动 Erlang 虚拟机和 RbbitMQ 应用服务(在三台节点上分别执行         以下命令)
        rabbitmq-server -detached
  • 在节点 2 执行
        rabbitmqctl stop_app
        (rabbitmqctl stop 会将 Erlang 虚拟机关闭, rabbitmqctl stop_app 只关闭 RabbitMQ 服务 )
        rabbitmqctl reset
        rabbitmqctl join_cluster rabbit@node1
        rabbitmqctl start_app(只启动应用服务 )
  • 在节点 3 执行rabbitmqctl stop_app
        rabbitmqctl reset
        rabbitmqctl join_cluster rabbit@node2
        rabbitmqctl start_app
  • 集群状态
        rabbitmqctl cluster_status
  • 需要重新设置用户
        创建账号
        rabbitmqctl add_user admin 123
        设置用户角色
        rabbitmqctl set_user_tags admin administrator
        设置用户权限
        rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"
  • 解除集群节点(node2 node3 机器分别执行)
        rabbitmqctl stop_app
        rabbitmqctl reset
        rabbitmqctl start_app
        rabbitmqctl cluster_status
        rabbitmqctl forget_cluster_node rabbit@node2(node1 机器上执行 )
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值