来源https://javaguide.net

RabbitMQ 2-可靠性投递与生产实践

可靠性投递

​ 首先需要明确,效率与可靠性是无法兼得的,如果要保证每一个环节都成功,势必会对消息的收发效率造成影响。 如果是一些业务实时一致性要求不是特别高的场合,可以牺牲一些可靠性来换取效率。

① 代表消息从生产者发送到Exchange;

② 代表消息从Exchange路由到Queue;

③ 代表消息在Queue中存储;

④ 代表消费者订阅Queue并消费消息。

1、确保消息发送到RabbitMQ服务器

可能因为网络或者Broker的问题导致①失败,而生产者是无法知道消息是否正确发送到Broker的。 有两种解决方案,第一种是Transaction(事务)模式,第二种Confirm(确认)模式。

在通过channel.txSelect方法开启事务之后,我们便可以发布消息给RabbitMQ了。如果事务提交成功,则消息一定到达了 RabbitMQ 中,如果在事务提交执行之前由于RabbitMQ异常崩溃或者其他原因抛出异常,这个时候我们便可以将其捕获,进而通过执行 channel.txRollback方法来实现事务回滚。使用事务机制的话会“吸干”RabbitMQ的性能,一般不建议使用。

生产者通过调用channel.confirmSelect方法(即Confirm.Select命令)将信道设置为confirm模式。一旦消息被投递到所有匹配的队列之后,RabbitMQ就会发送一个确认(Basic.Ack)给生产者(包含消息的唯一ID),这就使得 生产者知晓消息已经正确到达了目的地了。

参考:

com.gupaoedu.transaction

com.gupaoedu.confirm

2、确保消息路由到正确的队列

可能因为路由关键字错误、队列不存在、队列名称错误导致②失败。

使用mandatory参数和ReturnListener,可以实现消息无法路由的时候返回给生产者。

另一种方式就是使用备份交换机(alternate-exchange),无法路由的消息会发送到这个交换机上。

Map<String,Object> arguments = new HashMap<String,Object>();
arguments.put("alternate-exchange","ALTERNATE_EXCHANGE"); // 指定交换机的备份交换机 channel.exchangeDeclare("TEST_EXCHANGE","topic", false, false, false, arguments);

参考:

com.gupaoedu.returnlistener

3、确保消息在队列正确地存储

可能因为系统宕机、重启、关闭等等情况导致存储在队列的消息丢失,即③出现问题。 解决方案:

  1. 队列持久化

    // String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
    channel.queueDeclare(QUEUE_NAME, true, false, false, null);
  2. 交换机持久化

    // String exchange, boolean durable
    channel.exchangeDeclare("MY_EXCHANGE","true");
  3. 消息持久化

    AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
    .deliveryMode(2) // 2代表持久化,其他代表瞬态
    .build(); channel.basicPublish("", QUEUE_NAME, properties, msg.getBytes());
  4. 集群,镜像队列,参考下一节

4、确保消息从队列正确地投递到消费者

如果消费者收到消息后未来得及处理即发生异常,或者处理过程中发生异常,会导致④失败。

为了保证消息从队列可靠地到达消费者,RabbitMQ提供了消息确认机制(message acknowledgement)。消费者在订阅队列时,可以指定autoAck参数,当autoAck等于false时,RabbitMQ会等待消费者显式地回复确认信号后才从队列中移去消息。

如果消息消费失败,也可以调用Basic.Reject或者Basic.Nack来拒绝当前消息而不是确认。如果requeue参数设置为true,可以把这条消息重新存入队列,以便发给下一个消费者(当然,只有一个消费者的时候,这种方式可能会出 现无限循环重复消费的情况,可以投递到新的队列中,或者只打印异常日志)。

5、消费者回调

消费者处理消息以后,可以再发送一条消息给生产者,或者调用生产者的API,告知消息处理完毕。

参考:二代支付中异步通信的回执,多次交互。某提单APP,发送碎屏保消息后,消费者必须回调API。

6、补偿机制

对于一定时间没有得到响应的消息,可以设置一个定时重发的机制,但要控制次数,比如最多重发3次,否则会造 成消息堆积。

参考:ATM存款未得到应答时发送5次确认;ATM取款未得到应答时,发送5次冲正。根据业务表状态做一个重发。

7、消息幂等性

服务端是没有这种控制的,只能在消费端控制。如何避免消息的重复消费?

消息重复可能会有两个原因:

1、生产者的问题,环节①重复发送消息,比如在开启了Confirm模式但未收到确认。

2、环节④出了问题,由于消费者未发送ACK或者其他原因,消息重复投递。

对于重复发送的消息,可以对每一条消息生成一个唯一的业务ID,通过日志或者建表来做重复控制。 参考:银行的重账控制环节。

8、消息的顺序性

消息的顺序性指的是消费者消费的顺序跟生产者产生消息的顺序是一致的。

在RabbitMQ中,一个队列有多个消费者时,由于不同的消费者消费消息的速度是不一样的,顺序无法保证。

参考:消息:1、新增门店 2、绑定产品 3、激活门店,这种情况下消息消费顺序不能颠倒。

高可用架构

RabbitMQ集群

集群主要用于实现高可用与负载均衡。

RabbitMQ通过/var/lib/rabbitmq/.erlang.cookie来验证身份,需要在所有节点上保持一致。

集群有两种节点类型,一种是磁盘节点,一种是内存节点。集群中至少需要一个磁盘节点以实现元数据的持久化, 未指定类型的情况下,默认为磁盘节点。

集群通过25672端口两两通信,需要开放防火墙的端口。

需要注意的是,RabbitMQ集群无法搭建在广域网上,除非使用federation或者shovel等插件。 集群的配置步骤:

1、配置hosts

2、同步erlang.cookie

3、加入集群

RabbitMQ镜像队列

集群方式下,队列和消息是无法在节点之间同步的,因此需要使用RabbitMQ的镜像队列机制进行同步。

操作方式 命令或步骤
rabbitmqctl (Windows) rabbitmqctl set_policy ha-all "^ha." "{""ha-mode"":""all""}"
HTTP API PUT /api/policies/%2f/ha-all {"pattern":"^ha.", "definition":{"ha-mode":"all"}}
Web UI Navigate to Admin > Policies > Add / update a policy Name输入:mirror_image Pattern输入:^(代表匹配所有) Definition点击 HA mode,右边输入:all

如图:

参考资料:

RabbitMQ之镜像队列

HAproxy负载+Keepalived高可用

在两个内存节点上安装HAProxy

yum install haproxy

编辑配置文件

vim /etc/haproxy/haproxy.cfg

内容修改为:

global
log 127.0.0.1 local2
chroot /var/lib/haproxy
pidfile /var/run/haproxy.pid
maxconn 4000
user haproxy
group haproxy
daemon
stats socket /var/lib/haproxy/stats defaults
log global
option dontlognull
option redispatch
retries 3
timeout connect 10s
timeout client 1m
timeout server 1m
maxconn 3000 listen http_front
mode http
bind 0.0.0.0:1080 #监听端口
stats refresh 30s #统计页面自动刷新时间
stats uri /haproxy?stats #统计页面url
stats realm Haproxy Manager #统计页面密码框上提示文本
stats auth admin:123456 #统计页面用户名和密码设置 listen rabbitmq_admin
bind 0.0.0.0:15673
server node1 192.168.8.40:15672
server node2 192.168.8.45:15672 listen rabbitmq_cluster 0.0.0.0:5673
mode tcp
balance roundrobin
timeout client 3h
timeout server 3h
timeout connect 3h
server node1 192.168.8.40:5672 check inter 5s rise 2 fall 3
server node2 192.168.8.45:5672 check inter 5s rise 2 fall 3

启动HAProxy

haproxy -f /etc/haproxy/haproxy.cfg

安装Keepalived

yum -y install keepalived

修改配置文件

vim /etc/keepalived/keepalived.conf

内容改成(物理网卡和当前主机IP要修改):

global_defs {
notification_email {
acassen@firewall.loc
failover@firewall.loc
sysadmin@firewall.loc
}
notification_email_from Alexandre.Cassen@firewall.loc
smtp_server 192.168.200.1
smtp_connect_timeout 30
router_id LVS_DEVEL
vrrp_skip_check_adv_addr
# vrrp_strict # 注释掉,不然访问不到VIP
vrrp_garp_interval 0
vrrp_gna_interval 0
} global_defs {
notification_email {
acassen@firewall.loc
failover@firewall.loc
sysadmin@firewall.loc
}
notification_email_from Alexandre.Cassen@firewall.loc
smtp_server 192.168.200.1
smtp_connect_timeout 30
router_id LVS_DEVEL
vrrp_skip_check_adv_addr
# vrrp_strict # 注释掉,不然访问不到VIP
vrrp_garp_interval 0
vrrp_gna_interval 0
}
# 检测任务
vrrp_script check_haproxy {
# 检测HAProxy脚本
script "/etc/keepalived/script/check_haproxy.sh"
# 每隔两秒检测
interval 2
# 权重
weight 2
}
# 虚拟组
vrrp_instance haproxy {
state MASTER # 此处为`主`,备机是 `BACKUP`
interface ens33 # 物理网卡,根据情况而定
mcast_src_ip 192.168.8.40 # 当前主机ip
virtual_router_id 51 # 虚拟路由id,同一个组内需要相同
priority 100 # 主机的优先权要比备机高
advert_int 1 # 心跳检查频率,单位:秒
authentication { # 认证,组内的要相同
auth_type PASS
auth_pass 1111
}
# 调用脚本
track_script {
check_haproxy
}
# 虚拟ip,多个换行
virtual_ipaddress {
192.168.8.201
}
}

启动keepalived

keepalived -D

网络分区

为什么会出现分区?因为RabbitMQ对网络延迟非常敏感,为了保证数据一致性和性能,在出现网络故障时,集群 节点会出现分区。

参 考 脑 图 .xmind

RabbitMQ Network Partitions

RabbitMQ Network Partitions 处理策略

模拟RabbitMQ网络分区

广域网的同步方案

federation插件

shovel插件

实践经验总结

1、配置文件与命名规范

集中放在properties文件中

体现元数据类型(_VHOST _EXCHANGE _QUEUE);

体现数据来源和去向(XXX_TO_XXX);

2、调用封装

可以对 Template 做进一步封装,简化消息的发送。

3、信息落库+定时任务

将需要发送的消息保存在数据库中,可以实现消息的可追溯和重复控制,需要配合定时任务来实现。

4、运维监控

参考:

zabbix系列zabbix3.4监控rabbitmq

5、插件

tracing https://www.rabbitmq.com/plugins.html

6、如何减少连接数

合并消息的发送,建议单条消息不要超过4M(4096KB)

思考

消费者的集群或者微服务的多个实例,会不会重复接收消息? 生产者先发送消息还是先登记业务表?(打款错误的例子) 谁来创建对象(交换机、队列、绑定关系)?

重复创建会有什么问题?

持久化的队列和非持久化的交换机可以绑定吗?可以

如何设计一个MQ服务? http://www.xuxueli.com/xxl-mq/#/

面试题

1、消息队列的作用与使用场景?

2、创建队列和交换机的方法?

3、多个消费者监听一个生产者时,消息如何分发?

4、无法被路由的消息,去了哪里?

5、消息在什么时候会变成Dead Letter(死信)?

6、RabbitMQ如何实现延迟队列?

7、如何保证消息的可靠性投递?

8、如何在服务端和消费端做限流?

9、如何保证消息的顺序性?

10、RabbitMQ的节点类型?

课后作业

  1. 安装Erlang、RabbitMQ
  2. 高可用集群搭建(可选)
  3. 编写 Java API
  4. SpringBoot 集成 RabbitMQ

参考资料

  1. GitLib代码
  2. 安装文件
  3. PDF书籍
  4. 常用命令

来源于: https://javaguide.net

微信公众号:不止极客

百万架构师第四十一课:RabbitMq:可靠性投递和实践经验|JavaGuide的更多相关文章

  1. RabbitMQ可靠性投递及高可用集群

    可靠性投递: 首先需要明确,效率与可靠性是无法兼得的,如果要保证每一个环节都成功,势必会对消息的收发效率造成影响.如果是一些业务实时一致性要求不是特别高的场合,可以牺牲一些可靠性来换取效率. 要保证消 ...

  2. rabbitMq可靠性投递之手动ACK

    #手动应答#spring.rabbitmq.listener.simple.acknowledge-mode=manual#spring.rabbitmq.listener.simple.acknow ...

  3. rabbitMq可靠性投递之配置(消息至交换机,至队列不通的回调)

    @Bean public RabbitTemplate rabbitTemplate(CachingConnectionFactory factory) { //若使用confirm-callback ...

  4. 零基础学习云计算及大数据DBA集群架构师【企业级运维技术及实践项目2015年1月29日周五】

    LNMP/LEMP项目搭建 { 项目框架 # Linux_____WEB_____PHP_____DB # rhel7_____apache__-(libphp5.so)-__php__-(php-m ...

  5. 最新咕咆+鲁班+图灵+享学+蚂蚁+硅谷+源码 Java架构师资料《Java架构师VIP课程》

    最新的Java架构师完整资料,完整视频+源码+文档. 每一套都是一百多个G的资料,无密. JAVA架构师全套课程 咕泡学院互联网架构师第一期 咕泡学院互联网架构师第二期 咕泡学院互联网架构师第三期 博 ...

  6. 专访 | 新浪架构师:0-5年Java工程师的职业规划如何做?

    经历了2018年末的阵痛,大家都积攒着一股暗劲蠢蠢欲动. 3月初即将迎来2019年互联网行业换工作的大潮,技术工程师的升级换位对于一家互联网公司来说无疑是命脉般的存在——技术强则公司强! 如何做一个抢 ...

  7. 百万年薪架构师一文整理RabbitMQ、ActiveMQ、RocketMQ、Kafka

    一般来说,大型应用通常会被拆分成多个子系统,这些子系统可能会部署在多台机器上,也可能只是一台机器的多个进程中,这样的应用就是分布式应用.在讨论分布式应用时,很多初学者会把它和集群这个概念搞混,因为从部 ...

  8. 撩课-Web架构师养成系列第一篇

    前言 Web架构师养成系列共15篇,每周更新一篇,主要分享.探讨目前大前端领域(前端.后端.移动端)企业中正在用的各种成熟的.新的技术.部分文章也会分析一些框架的底层实现,让我们做到知其然知其所以然. ...

  9. 撩课-Web架构师养成系列(第二篇)-async

    前言 Web架构师养成系列共15篇,每周更新一篇,主要分享.探讨目前大前端领域(前端.后端.移动端)企业中正在用的各种成熟的.新的技术.部分文章也会分析一些框架的底层实现,让我们做到知其然知其所以然. ...

  10. 最新linux运维高级架构课13期 架构师课程

    有会员购买的,分享给大家.完整一套,可以学习一下.     ├─L001-2017linux运维高级架构师13期-运维与自动化运维发展-10节 │      1-1运维职业发展.avi │      ...

随机推荐

  1. springboot 实现通用责任链模式

    1.概述 在我们平时的工作中,填写分布填写数据,比如填入商品的基本信息,所有人信息,明细信息,这种情况就可以使用责任链模式来处理. 2.代码实现 2.1商品对象 public class Produc ...

  2. 纯JS+CSS实现羊了个羊

    前言 省流 gitee上扒的,感觉还不错,拿下来玩玩. https://gitee.com/kenxq/ylgy.git 技术说明 纯JS+CSS实现羊了个羊,包含部分特效,响应式手机.电脑.ipad ...

  3. C# 获取系统盘符

    1.使用.net管理对象(引入System.Management) public static List<string> getDisk() { WqlObjectQuery wmique ...

  4. uni-app小程序(抖音)text组件使用踩坑

    前情 uni-app是我比较喜欢的跨平台框架,它能开发小程序/H5/APP(安卓/iOS),重要的是对前端开发友好,自带的IDE让开发体验也挺棒的,公司项目就是主推uni-app. 坑位 最近在开发一 ...

  5. uni-app小程序项目使用iconfont字体图标

    前情 uni-app是我比较喜欢的跨平台框架,它能开发小程序/H5/APP(安卓/iOS),重要的是对前端开发友好,自带的IDE让开发体验非常棒,公司项目就是主推uni-app. 为什么要这么做? 借 ...

  6. VTK 视角的旋转、平移、缩放

    在CAD/CAM软件中,都需要旋转.平移和缩放视角,来观察操作图形.由于VTK定义的交互的类型不是很适用,所有通过定义一套自己的交互方式. 在下面代码中,鼠标左键平移,滚轮缩放,右键旋转. 先定义一个 ...

  7. 【双堆懒删除】codeforces 1294 D. MEX maximizing

    前言 双堆懒删除 当需要维护若干元素中的最大值(或最小值)时,可以用一个堆维护,但是堆只擅长处理堆顶元素,对堆中任意元素的处理就束手无策了.此时,可以引入另外一个堆,我们定义原来的堆为保存堆 \(ex ...

  8. 他又又来了,c#开源sql解析引擎类库【SqlParser.Net 1.0】正式发布,它可以帮助你简单快速高效的解析和处理sql

    背景 hi 大家好,我是三合,在过往的岁月中,我曾经想过要写以下这些工具 写一个通过拦截业务系统所有sql,然后根据这些sql自动分析表与表,字段与字段之间是如何关联的工具,即sql血缘分析工具 想着 ...

  9. Debian 11 (bullseye) 国内软件源

      本文整理了Debian 11在国内的几个软件源. 1.使用说明 一般情况下,将/etc/apt/sources.list文件中Debian默认的软件仓库地址和安全更新仓库地址修改为国内的镜像地址即 ...

  10. maven:Could not transfer artifact from/to maven-default-http-blocker (http://0.0.0.0/): Blocked m...

    今天在拉完项目后拉取包的过程中,maven报错: Could not transfer artifact from/to 对应的包 maven-default-http-blocker (http:/ ...