经验整理-3-RabbitMQ/RocketMQ等-100-@

----------MQ对比决择----------

?为什么使用RabbitMQ,对比其他队列?

参考:https://my.oschina.net/blogByRzc/blog/3012251
  1.rabbitMq(性能中等,但抗高并发性能最好,适合稳定性要求高的企业级应用,数据一致性强(消息不丢失原因--应答模式ack才删)性能和吞吐量的要求不高
        rabbitMq 几万级数据量,基于erlang语言开发,因此响应速度快些,并且社区活跃度比较活跃,可视化界面。缺点就是数据吞吐量相对与小一些,并且是基于erlang语言开发,比较重的问题难以维护
优点:
可靠性:包括持久性机制、投递确认、发布者证实和高可用性机制;
灵活的路由:消息在到达队列前是通过交换机进行路由的。RabbitMQ为典型的路由逻辑提供了多种内置交换机类型

由于Erlang语言的特性,消息队列性能较好,支持高并发;
健壮、稳定、易用、跨平台、支持多种语言、文档齐全;
有消息确认机制和持久化机制,可靠性高;
高度可定制的路由;
管理界面较丰富,在互联网公司也有较大规模的应用,社区活跃度高。

缺点:
本身对负载均衡的支持不好,但可以弥补成高可用:在集群中的机器上进行镜像,以确保在硬件问题下还保证消息安全;(镜像模式描述:镜像模式是多个rabbitmq实现主从备份,多个rabbitmq之间是数据同步的,使用的时候一直在访问一个主的raabitmq,从的实现备份。如果主的宕机可以自动切换到从的mq,不影响整合系统的使用。)--响应吐量更慢了。

 

    2.rocketMq(定位于非日志的可靠消息传输(日志场景也OK),目前RocketMQ在阿里集团被广泛应用在订单,交易,充值,流计算,消息推送,日志流式处理,binglog分发等场景)
        rocketMq几十万级别数据量,基于Java开发,应对了淘宝双十一考验,并且文档十分的完善,拥有一些其他消息队列不具备的高级特性,定时推送其他消息队列是延迟推送,如rabbitMq通过设置expire字段设置延迟推送时间。又比如rocketmq实现分布式事务,比较可靠的。
优点
单机支持1万以上持久化队列;
RocketMQ的所有消息都是持久化的,先写入系统内存PAGECACHE,然后刷盘,可以保证内存与磁盘都有一份数据,而访问时,直接从内存读取
模型简单,接口易用(JMS的接口很多场合并不太实用);
性能非常好,可以允许大量堆积消息在Broker中;
支持多种消费模式,包括集群消费、广播消费等;(广播是一个消费多个topic)
各个环节分布式扩展设计,支持主从和高可用
开发度较活跃,版本更新很快。


缺点
支持的 客户端语言不多,目前是Java及C++,其中C++还不成熟;
RocketMQ社区关注度及成熟度也不及前两者;
没有Web管理界面,提供了一个 CLI (命令行界面) 管理工具带来查询、管理和诊断各种问题;
没有在MQ核心里实现JMS等接口;

   3.kafka(依赖zk,可动态拓展节点无限扩容,高吞吐量,10万级,适合大数据日志处理,少量数据丢失)
基于Pull的模式来处理消息消费,追求高吞吐量,一开始的目的就是用于日志收集和传输。适合产生大量数据的互联网服务的数据收集业务。)
      kafka真正的大规模分布式消息队列,提供的核心功能比较少。基于zookeeper实现的分布式消息订阅。
总结就是一句话:发送者确认模式开启,消息持久化默认开启,消费者消费开启手动ack
优点
客户端语言丰富:支持Java、.Net、PHP、Ruby、Python、Go等多种语言;
高性能:单机写入TPS约在100万条/秒,消息大小10个字节;
提供完全分布式架构,并有replica机制,拥有较高的可用性和可靠性,理论上支持消息无限堆积
支持批量操作
消费者采用Pull方式获取消息。消息有序,通过控制能够保证所有消息被消费且仅被消费一次;
有优秀的第三方KafkaWeb管理界面Kafka-Manager;
日志领域比较成熟,被多家公司和多个开源项目使用。
缺点
Kafka单机超过64个队列/分区时,Load时会发生明显的飙高现象。队列越多,负载越高,发送消息响应时间变长;
使用短轮询方式,实时性取决于轮询间隔时间;
消费失败不支持重试;
支持消息顺序,但是一台代理宕机后,就会产生消息乱序;
社区更新较慢。

 4.activeMQ(安装方便,JMS规范,适合中小企业级
        不支持集群,需要利用zk等,很麻烦。
支持模式:队列模式(点对点模式)、发布-订阅模式
优点
跨平台(JAVA编写与平台无关,ActiveMQ几乎可以运行在任何的JVM上);
可以用JDBC:可以将数据持久化到数据库。虽然使用JDBC会降低ActiveMQ的性能,但是数据库一直都是开发人员最熟悉的存储介质;
支持JMS规范:支持JMS规范提供的统一接口;
支持自动重连和错误重试机制;
有安全机制:支持基于shiro,jaas等多种安全配置机制,可以对Queue/Topic进行认证和授权;
监控完善:拥有完善的监控,包括WebConsole,JMX,Shell命令行,Jolokia的RESTful API;
界面友善:提供的WebConsole可以满足大部分情况,还有很多第三方的组件可以使用,比如hawtio;
缺点
社区活跃度不及RabbitMQ高;
根据其他用户反馈,会出莫名其妙的问题,会丢失消息
目前重心放到activemq6.0产品Apollo,对5.x的维护较少;
不适合用于上千个队列的应用场景;

 5.zeromq安装方便,JMS规范,适合中小企业级
参考:https://blog.csdn.net/Jacoob1024/article/details/81075554
zeromq只是一个异步消息库,在套接字的基础上提供了类似于消息代理的机制。使用 ZeroMQ 的话,需要对自己的业务代码进行改造,不利于服务解耦。ZeroMQ,本地进程之间的coordination。(很多公司 用zmq 只是用来方便地做socket。 比 Twisted 还方便)
消息发送端的内存或者磁盘中。不支持持久化。
zeromq:c
协议:TCP、UDP
负载均衡:去中心化,不支持负载均衡,不支持集群。本身只是一个多线程网络库
订阅形式和消息分发:点对点(p2p)


----------RocketMQ----------

?RocketMQ工作原理?

1,启动Namesrver,Namesrver起来后监听端口,等待Broker、Produer、Consumer连上来,相当于一个路由控制中心(注册中心)。
2,Broker启动,跟所有的Namesrv保持***长连接***,定时发送心跳包心跳包中包含当前Broker信息(IP+端口等)以及存储所有topic信息注册成功后,namesrv集群中就有Topic跟Broker的映射关系。
3,收发消息前,先创建topic,创建topic时需要指定该topic要存储在哪些Broker上。也可以在发送消息时自动创建Topic。
4,Producer发送消息,启动时先跟Namesrv集群中的其中一台建立长连接,并从Namesrv中获取当前发送的Topic存在哪些Broker上,然后跟对应的Broker建立长连接,直接向Broker发消息。(dubbo的rpc调用也是长连接)
5,Consumer跟Producer类似。Consumer跟其中一台Namesrv建立长连接,获取当前订阅Topic存在哪些Broker上,然后直接跟Broker建立连接通道,开始消费消息

各个模块的作用
Namesrv: 存储当前集群所有Brokers信息、Topic跟Broker的对应关系
Broker: 集群最核心模块,主要负责***Topic消息存储***、消费者的消费位点管理(消费进度)
Producer: 消息生产者,每个生产者都有一个ID(编号),多个生产者实例可以共用同一个ID。同一个ID下所有实例组成一个生产者集群。
Consumer: 消息消费者,每个订阅者也有一个ID(编号),多个消费者实例可以共用同一个ID。同一个ID下所有实例组成一个消费者集群。

?RocketMQ的作用或优点?

削峰填谷(主要解决瞬时写压力大于应用服务能力导致消息丢失、系统奔溃等问题)
系统解耦(解决不同重要程度、不同能力级别系统之间依赖导致一死全死)
提升性能(当存在一对多调用时,可以发一条消息给消息系统,让消息系统通知相关系统
蓄流压测(线上有些链路不好压测,可以通过堆积一定量消息再放开来压测)

?我搭建过,如何搭建?如何使用?

https://blog.csdn.net/qq_26896085/article/details/104151165
通过docker 搭建2master+2slave的集群:
1、nameserver创建容器2个
2、创建broker容器4个=2master+2slave,修改其配置文件broker.con,每个里的broker名称和端口不一致;nameserver地址都配上面的那两个。

3、启动nameserver
4、启动broker
5、启动rocketMq-console工具,即可查看客户端ui控制台。搭建完成。


?RocketMQ搭建和使用过程中遇到哪些问题?

?RocketMQ与*的区别?

RocketMQ如何解决消息幂等

注意:每次重试后,消息ID都不一致,所以不能使用消息ID判断幂等
解决办法:使用自定义全局ID判断幂等,例如流水ID、订单号
使用msg.setKeys 进行区分


?Producer顺序发送,如何确保消息顺序?

一般消息是通过轮询所有队列发送的最好根据业务比如说订单号orderId相同的消息发送到同一个队列, 或者同一用户userId发送到同一队列等等,用orderId或userId取模队列数。
messageQueueList [orderId%messageQueueList.size()]
messageQueueList [userId%messageQueueList.size()]

?如何保证消息不丢失?

分别从Producer发送机制、Broker的持久化机制,以及消费者的offSet机制来最大程度保证消息不易丢失
一、producer重试发送消息
1、默认情况下,可以通过同步的方式阻塞式的发送,check SendStatus校验发送状态,状态是OK,表示消息一定成功的投递到了Broker,状态超时或者失败,则会触发默认的2次重试此方法的发送结果,可能Broker存储成功了,也可能没成功
2、采取事务消息的投递方式,并不能保证消息100%投递成功到了Broker,但是如果消息发送Ack失败的话,此消息会存储在CommitLog当中,但是对ConsumerQueue是不可见的。可以在日志中查看到这条异常的消息,严格意义上来讲,也并没有完全丢失
3、RocketMQ支持 日志的索引,如果一条消息发送之后超时,也可以通过查询日志的API,来check是否在Broker存储成功
二、broker的持久化机制

1、消息支持持久化到Commitlog里面,即使宕机后重启,未消费的消息也是可以加载出来的
2、Broker自身支持同步刷盘、异步刷盘的策略,可以保证接收到的消息一定存储在本地的内存中
3、Broker集群支持 1主N从的策略支持同步复制和异步复制的方式同步复制可以保证即使Master 磁盘崩溃,消息仍然不会丢失

三、消费端的重试机制
消费者可以根据自身的策略批量Pull消息
1、Consumer自身维护一个持久化的offset对应MessageQueue里面的min offset),标记已经成功消费或者已经成功发回到broker的消息下标
2、如果Consumer消费失败,那么它会把这个消息发回给Broker,发回成功后,再更新自己的offset
3、如果Consumer消费失败,发回给broker时,broker挂掉了,那么Consumer会定时重试这个操作
如果Consumer和broker一起挂了,消息也不会丢失,因为consumer 里面的offset是定时持久化的,重启之后,继续拉取offset之前的消息到本地

?如何避免重复消费问题?

业务上消费消息要幂等。redis分布式锁。
 

----------kafka----------
?工作原理?

Zookeeper 作为 Kafka 集群管理,主要作用包括:
1、Leader 选举:所有的Kafka Broker节点一起去Zookeeper上注册一个临时节点,因为只有一个会注册成功,其他的都会失败,所以这个成功注册临时节点的会成为主节点Kafka Broker Controller,其他的从节点。(这个过程叫Controller在ZooKeeper注册Watch),主节点会监听其他的从节点的所有信息。
主节点挂了,会重新选一次,重复上面的操作;
从节点挂了,主节点会读取该宕机broker上所有的partition在zookeeper上的状态,并选取ISR列表中的一个replica作为partition leader
2、在 Consumer Group 发生变化时进行 Rebalance;
3、持久化 Kafka 维护的 Consumer Group 对应的 offset 值;

?kafka的作用或优点或应用场景?

1)日志收集:一个公司可以用Kafka可以收集各种服务的log,通过kafka以统一接口服务的方式开放给各种consumer,例如hadoop、HBase、Solr等。
2)消息系统:解耦生产者和消费者、缓存消息等,提升消费者效率,不用每个接收端服务都去发一次
3)用户活动跟踪Kafka经常被用来记录web用户或者app用户的各种活动,如浏览网页、搜索、点击等活动,这些活动信息被各个服务器发布到kafka的topic中,然后订阅者通过订阅这些topic来做实时的监控分析,或者装载到hadoop、数据仓库中做离线分析和挖掘。
4)运营指标:Kafka也经常用来记录运营监控数据。包括收集各种分布式应用的数据,生产各种操作的集中反馈,比如报警和报告。
5)流式处理:比如spark streaming和storm
6)事件源

?kafka我搭建过,如何搭建?如何使用?

通过docker 搭建3个节点的集群:
https://blog.csdn.net/u011420410/article/details/94735492
1. 下载kafka安装包
2. 在docker上制作kafka镜像包
3. 生成集群的挂载文件,配置3个节点名称、zk等信息
4. 创建并启动容器
5. pull拉取kafka-manager镜像,启动kafka-manager,即可查看客户端ui控制台。搭建完成。

?搭建和使用过程中遇到哪些问题?

1.kafka压缩包解压好之后,改配置server.properties时,listeners=PLAINTEXT://192.168.131.130:9092//这个PLAINTEXT没有加,报什么协议不对
2.消息重复消费的问题:

  1. 自动提交 offset 时,由于 offset 不会立即提交,所以可能会造成单次异常却重复消费多条连续的消息;
  2. 手动提交 offset 时,如果选中每消费一条消息,都手动提交一次 offset,那么针对每个分区来讲,单次异常只会至多重复消费一条消息;

?Kafka对比主流MQ,*与*的区别?

1、吞吐量对比:Kafka 高吞吐量、低延迟
Kafka > RabbitMq  > ActiveMQ。
Kafka内部采用消息的批量处理,zerocopy机制,数据的存储和获取是本地磁盘顺序批量操作,具有O(1)的复杂度,消息处理的效率很高。
RabbitMQ在吞吐量方面逊于Kafka,他们的出发点不一样,RabbitMQ支持对消息的可靠的传递,支持事务,RabbitMQ不支持批量的操作;基于存储的可靠性的要求存储可以采用内存或者硬盘。
2、在架构模型方面:
RabbitMQ遵循amqp协议,RabbitMQ的Broker由Exchange、Binding和Queue组成,其中Exchange和Binding组成了消息的路由键;客户端Producer通过连接Channel和Server进行通信,Consumer从Queue获取消息进行消费(长连接,Queue有消息会推送到Consumer端,Consumer循环从输入流读取数据)。RabbitMQ以Broker为中心;有消息的确认机制。
Kafka遵从一般的MQ结构,Producer、Broker和Consumer,以Consumer为中心,消息的消费进度(offset)由Consumer维护(在0.9.0以后的版本中,提交到一个特殊的Topic中),Consumer根据消费的offset,从Broker上批量pull数据;无消息确认机制。
3、在可用性方面:
RabbitMQ支持mirror的Queue,主Queue失效,mirror Queue接管。
Kafka的Broker支持主备模式。
ActiveMQ也支持主备模式。
4、在集群负载均衡方面
Kafka采用Zookeeper对集群中的Broker、Consumer进行管理,可以注册Topic到Zookeeper上;通过Zookeeper的协调机制,Producer保存对应Topic的Broker信息,可以随机或者轮询发送到Broker上;并且Producer可以基于语义指定分片,消息发送到Broker的某分片上。
RabbitMQ的负载均衡需要单独的Loadbalancer进行支持。
否则,RabbitMQ:对负载均衡的支持不好。rabbitmq客户端发送消息要和broker建立连接,需要事先知道broker上有哪些交换器,有哪些队列。通常要声明要发送的目标队列,如果没有目标队列,会在broker上创建一个队列,如果有,就什么都不处理,接着往这个队列发送消息。假设大部分繁重任务的队列都创建在同一个broker上,那么这个broker的负载就会过大.
使用镜像队列机制建立rabbitmq集群可以解决这个问题,形成master-slave的架构,master节点会均匀分布在不同的服务器上,让每一台服务器分摊负载。slave节点只是负责转发,在master失效时会选择加入时间最长的slave成为master。当rabbitmq新节点加入镜像队列的时候,队列中的消息不会同步到新的slave中,除非调用同步命令,但是调用命令后,队列会阻塞,不能在生产环境中调用同步命令。
下图展示Kafka与主流MQ的同步发送(注:Kafka还支持异步发送模式,性能比同步发送高的多)性能对比:
5、可扩展性:kafka集群支持热扩展,无限扩容
 6、持久性、可靠性:消息被持久化到本地磁盘,并且支持数据备份防止数据丢失
7、高并发:支持数千个客户端同时读写
8、
消息回溯:由于Kafak时刻记录了offset信息,并在消息实体中添加了时间属性,kafka有提供了seek()方法实现从特定点进行消费。因此可以比较容易地实现消息回溯。
而RabbitMQ不具备这个特性。

?消息丢失的场景有哪些?怎么解决?

消息丢失的场景:
1、当异步发送消息时, Producer 不会等待服务器的反馈,如果网络发生异常或其它情况,则可能会丢失消息;
2、当异步批量发送消息时,如果 Producer down 掉,则缓冲区消息可能丢失;
3、当acks=0时,Producer 不会等待服务器的反馈,如果网络发生异常或其它情况,可能会丢失消息;
4、当acks=1时,如果 leader 节点在接收到消息之后,并且在 follower 节点复制数据完成之前产生错误,则这条消息会丢失;
解决:
所以如果需要保证消息不丢失,至少需要满足以下条件:
同步阻塞方式发送消息
设置 acks=-1或者acks=all
 

----------RabbitMQ----------

?(高可用)RabbitMQ服务器集群结构原理图?

为了解决普通集群模式中的单点故障问题,镜像模式中把需要的队列做成镜像队列消息实体会在镜像之间同步通讯,而不是在消费者拉取消息时临时拉取(普通集群模式不通讯,在节点2消费时找不到,再去其他节点拉取一遍就是临时拉取,普通集群模式单点故障就丢失了)。

?RabbitMQ服务器集群如何搭建?(需要装三方工具erlang)

目的:为增加吞吐量和节点崩溃的情况下继续运行。
每台虚拟机上安装如下的组件包,分别如下:
a.Jdk 1.8
b.Erlang运行时环境
c.RabbitMq的Server组件
rabbitmq集群可以借助HAProxy、LVS技术,或者在客户端使用算法实现负载均衡(如,生产者按hash指定发送到不同的队列,多个消费者。)

主要应用HAProxy:
Docker下RabbitMQ集群+HAProxy+KeepAlived步骤
https://blog.csdn.net/u014116780/article/details/98370219
1)docker run运行几个Rabbitmq容器。比如节点名称1,2,3(非docker则在host里把它们所在主机Ip 映射到节点名称1,2,3,必须不同)
2)这里要保证它们三个的Erlang cookie相同。(由于Erlang节点间通过认证Erlang cookie的方式来允许互相通信
3) 执行加入集群命令,2,3加入1,此时就建好了集群,rabbitmqctl join_cluster --ram rabbit@my-rabbit1(至少配置一个节点为磁盘节点,其余部分配置为内存节点)
4)主节点再执行一下设置策略命令,设为镜像模式(高可用模式)。rabbitmqctl set_policy ha-all "^" '{"ha-mode":"all"}'
5)HAProxy为RabbitMQ集群做负载均衡器(LVS 性能最好,但是搭建相对复杂,Nginx的upstream模块支持群集功能,但是对群集节点的健康检查功能不强,性能没有HAProxy 好。
6)KeepAlived配置高可用

?RabbitMQ服务器集群镜像模式和普通模式区别 ?

镜像模式和普通模式的不同之处在于,消息实体会主动在镜像节点间同步,而不会在consumer取数据时临时拉取.该模式带来的副作用也很明显,除了降低系统性能意外,如果镜像队列过多,加之有大量的消息进入,集群内部的网络带宽将会被这种同步通讯大大消耗掉,所以在对可靠性要求较高的场合中适用.

?RabbitMQ消费者集群?

消费者集群默认是均摊消费。

?RabbitMQ 消息如何保证不丢失?

可能出现消息丢失的情况

  • ①表示 消息从生产者发送到 Broker(rabbitmq服务器)

  • ②表示 消息从 Exchange(交换机) 路由到 Queue

  • ③表示 消息存储在 Queue 中,是存储在内存中,重启消息丢失

  • ④表示 消费者订阅 Queue 并消费消息

解决
第①处修改
生产者代码里,采用服务端确认方式
rabbitmq 服务端确认收到消息;分为两种模式
第一:Transaction模式
阻塞
性能低
严重浪费服务器资源
第二:Confirm模式又分为三种
发送一条,确认一条(性能低)
发送N条,批量确认(N这个值不好把握),一条失败,这N条全部重新发送
异步确认(推荐,每次批量确认的条数不一样,重写ConfirmCallback)
小结
根据目前业务的特性,大量的消息会发送到 rabbitmq 服务端,且数量不固定,选择性能和可靠性适中的异步确认模式,重写ConfirmCallback
生产者端RabbitMQConfig配置类的代码修改
template.setConfirmCallback()
    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        //异步确认服务端是否收到消息 如果未收到抛出异常 会重新发送消息
        template.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                if (!ack) {
                    System.out.println("发送消息失败:" + cause);
                    throw new RuntimeException("发送异常:" + cause);
                }
            }
        });
        template.setMessageConverter(new Jackson2JsonMessageConverter());
        return template;
    }

 第②处修改----一般不会出现这种情况
路由保证

在第二步中,交换机的消息有可能不能正确路由到对应的队列上,比如 路由键 错误或者队列不存在等等--------一般不会出现这种情况
可以设置消息会回发到生产者服务端,再进行后续处理(推荐)
也可以设置备份交换机(本文并未采用,修改量相对比较复杂)
小结
采用消息回发,重写ReturnCallback
原因:比较简单
生产者端的代码修改
template.setMandatory(true);
template.setReturnCallback()
    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        //路由失败 回发消费者端
        template.setMandatory(true);
        template.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            public void returnedMessage(Message message,
                                        int replyCode,
                                        String replyText,
                                        String exchange,
                                        String routingKey) {
                service.insert("replyCode: " + replyCode + "replyText: " + replyText + "exchange: " + exchange + "routingKey: " + routingKey,
                        1, new String(message.getBody()));
            }
        });
        //异步确认服务端是否收到消息 如果未收到抛出异常 会重新发送消息
        template.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                if (!ack) {
                    System.out.println("发送消息失败:" + cause);
                    throw new RuntimeException("发送异常:" + cause);
                }
            }
        });
        template.setMessageConverter(new Jackson2JsonMessageConverter());
        return template;
    }

建议将回发的数据写入数据库,后续处理的代码,有数据之后再进行开发也可以
service.insert() 为插入数据库的代码

 第③处修改---持久化交换机和队列、以及消息
持久化

持久化设置好之后,即使 rabbitmq 或者 web 应用挂掉了,重启相关应用队列中的消息还在,而且可以重新消费
交换机的持久化 (消费者配置类)
第二个参数true表示持久化
    @Bean("mobileExchange")
    public DirectExchange exchange() {
        // 参数: 队列名称 是否持久化是否自动删除
        return new DirectExchange("mobile.direct", true, false);
    }
队列的持久化 (消费者配置类)
第二个参数true表示持久化
    @Bean("employeeQueue")
    public Queue employeeQueue() {
        // 参数: 队列名称 是否持久化 是否独占 是否自动删除
        return new Queue("employee.queue", true, false, false);
    }

消息的持久化 (生产者配置类)---生产者修改消息的属性
这个比较复杂,本文在实际生产环境做了一个封装类 MessageHelper
关键代码 messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);设置消息的持久化
public class MessageHelper {
    public static <T> T msgToObj(Message message, Class<T> clazz) {
        if (null == message || null == clazz) {
            return null;
        }
        String str = new String(message.getBody());
        T obj = JSONObject.parseObject(str, clazz);
        return obj;
    }
    public static <T> List<T> msgToList(Message message, Class<T> clazz) {
        if (null == message || null == clazz) {
            return null;
        }
        String str = new String(message.getBody());
        List<T> obj = JSONObject.parseArray(str, clazz);
        return obj;
    }
    public static Message objToMsg(Object obj) {
        if (null == obj) {
            return null;
        }
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
        messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON);
        String str = JSONObject.toJSONString(obj);
        return new Message(str.getBytes(), messageProperties);
    }
}

第④处修改
消费者确认
代码    意义
NONE    自动应答
MANUAL    手动应答
AUTO    自动应答(在程序运行完)
AUTO有点区别
1.没有发生异常,正常 ACK 答应
2.手动抛出AmqpRejectAndRequeueException nack , requeue 拒绝,重回队列
3.手动抛出AmqpRejectAndDontRequeueException nack,不重新入队
消费者端修改
本文采用的 MANUAL, 监听类配置,设置为手动提交factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setMessageConverter(new Jackson2JsonMessageConverter());
        factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        factory.setAutoStartup(true);
        return factory;
    }

监听类修改
@RabbitListener设置containerFactory = "rabbitListenerContainerFactory"
channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false); 表示手动确认
出现异常,将信息插入到数据库
    @RabbitListener(queues = "employee.queue", containerFactory = "rabbitListenerContainerFactory")
    public void saveQuotaComment(Message msg, Channel channel) throws IOException {
        try {
            List<Leader> leaders = MessageHelper.msgToList(msg, Leader.class);
            leaderMapper.batchAddLeader(leaders);
        } catch (Exception e) {
            exceptionExecute(e, msg);
        } finally {
            channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
        }
    }
    void exceptionExecute(Exception e, Message msg) {
        //将异常信息插入到数据库
        String str = new String(msg.getBody());
        StackTraceElement[] stackTrace = e.getStackTrace();
        String exceptionContent = "";
        for (int i = 0; i < stackTrace.length; i++) {
            exceptionContent += stackTrace[i].toString();
        }
        exceptionMapper.insert(e.getMessage() + " >> " + exceptionContent.substring(0, 1000), 2, str);
        e.printStackTrace();
    }
附一种必须的生产者方案:消息补偿措施,重新发送消息处理

?RabbitMQ关键名词?

1.Server(Broker):接收客户端连接,实现AMQP协议的消息队列和路由功能的进程; Virtual Host:虚拟主机的概念,类似权限控制组,一个Virtual Host虚拟主机里,类似权限控制组,一个虚拟主机可以有多个Exchange和Queue。(每个VirtualHost相当月一个相对独立的2.RabbitMQ服务器,每个VirtualHost之间是相互隔离的。exchange、queue、message不能互通。)
3.Exchange:交换机,接收生产者发送的消息,并根据交换机路由名称Routing Key将消息路由到服务器中的队列Queue。 交换机类型ExchangeType决定了路由消息行为
4.basicAck 应答
5.basicNack 丢弃该消息进入死信队列(得服务端先配好死信,客户端再消费)
6.ttl:过期时间/生存时间,
普通消费者队列会自动清除,在消费者端的map里配置x-message-ttl=多少毫秒,应用范围是所有消息(是在消费者队列里设置);
生产者每条消息设置TTL,可以每条消息时重置设定,在生产者端的properties 里配置expiration=多少毫秒,应用范围是可以是单条消息(是在生产者发出的每条消息上设置);
参考:https://blog.csdn.net/lql_h/article/details/88813499
如果都设置了,取最小时间为准。
7.注意事项:做为生产者,在启服时,并不会对连接rabbitMQ,更不会去创建Topic,Queue及绑定。
而作为消费者,在启服后,会连接rabbitMQ,并检查Queue是否有消息可消费。
所以应该将消费的rabbitMQ配置,加上@Primary,否则在rabbitMQ上没有对应的Queue时,报错,无法启动服务器

?怎么实现延迟执行队列?

1)死信队列设置ttl,到期后,再路由到其他正常队列监听消息。当消息在一个队列中变成死信(消息没有任何消费者去消费)之后,它能被重新publish到另一个Exchange,这个Exchange就是DLXDLX也是一个正常的Exchange,和一般的Exchange没什么区别,它能在任何的队列上被指定,实际就是设置某个队列的属性当这个队列中有死信时,RabbitMQ就会自动将这个消息重新发布到设置的Exchange上去,进而路由到另一个队列.

2)rabbitmq-delayed-message-exchange,安装延迟队列功能插件,
参考:https://blog.csdn.net/skiof007/article/details/80914318??
x-delayed-message是插件提供的类型,并不是rabbitmq本身的.
使用方法:
生产者,声明一个x-delayed-message类型的exchange来使用delayed-messaging特性 (绑定延迟交换机,比如,x-delayed-message="direct");
生产者发送时,在header添加”x-delay”参数来控制每条消息的延时时间。(在header设置延迟时间)

?实现定时阶梯性通知?

详情,最后面再讲。选第二种延时队列来拓展,

?交换机是什么,有几种类型?

生产者发送消息不会向传统方式直接将消息投递到队列中,而是先将消息投递到交换机中,在由交换机转发到具体的队列。
交换机有四种类型。  
1、 Fanout exchange(扇型交换机)将消息路由给绑定到它身上的所有队列   (发布订阅)
2、Direct exchange(直连交换机)是根据消息携带的路由键(routing key)将消息投递给对应队列的 (路由模式
3、Topic exchange(主题交换机)队列通过路由键绑定到交换机上,然后,交换机根据消息里的路由值,将消息路由给一个或多个绑定队列   (主题模式
4、Headers exchange(头交换机)类似主题交换机,但是头交换机使用多个消息属性来代替路由键建立路由规则。通过判断消息头的值能否与指定的绑定相匹配来确立路由规则


 

?操作队列有几种模式?
五种形式队列.
一.简单队列模式(点对点队列模式)

功能:一个生产者P发送消息到队列Q,一个消费者C接收

二.工作队列模式(公平性模式)
三.发布订阅(fanout)(绑定的交换机)(Publish/Subscribe):
1.创建新的连接
2.创建通道
3.绑定的交换机 参数1交互机名称 参数2 exchange类型
4.发送消息
5.关闭通道、连接.
四.路由模式(Routing)(绑定的交换机):
1.创建新的连接
五.通配符模式(Topics)(绑定的交换机):
1.创建新的连接

?消费者与队列长连接的好处?

提高效率,节省资源,因为----建立连接都会有三次握手(关闭连接是4次)。

?消费者接收确认功能分几种模式?

为了保证消息从队列可靠地到达消费者,RabbitMQ提供消息确认机制(message acknowledgment)。确认模式主要分为下面三种:

AcknowledgeMode.NONE:不确认
AcknowledgeMode.AUTO:自动确认
AcknowledgeMode.MANUAL:手动确认
注意:在springboot项目中通过在配置文件中指定消息确认的模式,如下指定手动确认模式:

spring.rabbitmq.listener.simple.acknowledge-mode = manual
手动确认与自动确认的区别:

自动确认:这种模式下,当发送者发送完消息之后,它会自动认为消费者已经成功接收到该条消息。这种方式效率较高,如果在发送过程中,如果网络中断或者连接断开,将会导致消息丢失
手动确认:消费者成功消费完消息之后,会显式发回一个应答(ack信号,这样可以在异常时重发,默认5次),RabbitMQ只有成功接收到这个应答消息,才将消息从内存或磁盘中移除。这种方式效率较低点,但是能保证绝大部分的消息不会丢失,当然肯定还有一些小概率会发生消息丢失(丢了再看日志)的情况。

?为什么要使用消息(异常)重试机制?什么场景要重试?怎么做重试?重试的原理是什么?

       一、一定要做重试的原因:如果消费者处理消息失败后不重试,然后主动发送成功应答给rabbitmq,rabbitmq就会将删除队列中的消息,从而造成消息的丢失。所以我们要在消费者处理消息失败的时候,重试一定的次数。比如重试3次,如果重试3次之后还是失败,则把这条消息发送到死信队列。
      二、什么场景要重试:如果是调用数据库超时三方接口超时等未明确错误的异常,需要重试
如果是调用数据库余额不足或者数据转换异常等明确错误的异常,不要重试(再试也肯定失败),业务就应该失败或解决数据转换BUG
     三、怎么做重试:在  retry重试属性里,增加或修改重试配置(默认遇到异常会一直重试):

        ####开启消费者重试          enabled: true

         ####最大重试次数          max-attempts: 5

        ####重试间隔次数          initial-interval: 3000

     四、重试的原理:Spring的@RabbitListener底层通过AOP实现了监控机制发现消费端异常就会默认加入缓存重试,直到成功。(如果正常执行完,就会默认应答,除非设置了手动应答)


重试太多可能带来哪些问题?怎么解决?
问题:重复执行,比如重复记账,加钱(如果里面是异步执行加钱,这个加钱超时异常了,(主动重试或自动)重试的间隔时间设置太短,再来一次异步加钱,重复记账了)
解决:1、重试间隔时间设置大点
2、
全局消息id,生产者在Message请求头设置消息id(messageId),UUID即可,消费者接收的时候,判断一下messageId是否重复(redis)

 ?RabbitMQ签收模式是怎么回事?怎么使用的??、
一、签收模式:如果正常执行完,就会默认应答,除非设置了手动应答,如果手动应答设置开启,就要主动告诉消费端这个队列我收到了,主动告诉会一直在队列中不会删除缓存
二、怎么使用:开启手动应答配置(开启手动ack )
 acknowledge-mode: manual 
// 手动签收
channel.basicAck(deliveryTagfalse);

总结,应答和异常重试是两个对立的场景。两个都需要手动开启设置才能更好的应用在记账安全业务里。应答了就说明没有异常
 
?死信队列是什么?死信队列出现有哪些原因?如何使用死信交换机呢?
一、死信队列:是队列里的一个封装的一个子属性,里面也是一个完整的交换器-队列模式
二、出现原因:
消息被拒绝(basic.reject或basic.nack)并且requeue=false.(这个应用场景是:主动重试2次还失败之后,主动调的拒绝命令进入死信队列
消息TTL过期
队列达到最大长度(队列满了,无法再添加数据到mq中
三、怎么使用:
1、生产者先注入一个死信队列(交换机名称、路由名称、队列名等信息),然后再注入业务队列时,把死信队列名和死信交换机名作为一个map入参带进去,实现了绑定。
2、
定义业务(普通)队列的时候指定参数
x-dead-letter-exchange: 用来设置死信后发送的交换机
x-dead-letter-routing-key:用来设置死信的routingKey


上图方法名上还有一个@Bean,漏截图的部分。


?RabbitMQ解决分布式事务问题?----详情见,面试整理-6-分布式事务-分布式锁-分布式JOB-网站跨域请求

MQ解决分布式事务三个重要概念

  1. 确保生产者消息一定要投递到MQ服务器中  Confirm机制
  2. 确保消费者能够正确的消费消息,采用手动ACK(注意幂等)
  3. 如何保证第一个事务一定要创建成功(在创建一个补单的队列,绑定同一个交换机,检查订单数据是否已经创建在数据库中 实现补偿机制)

生产者 一定确保消息投递到MQ服务器(使用)

RabbitMQ解决分布式事务原理: 采用最终一致性原理。需要保证以下三要素
1、确认生产者一定要将数据投递到MQ服务器中(采用MQ消息确认机制)
2、MQ消费者消息能够正确消费消息,采用手动ACK模式(注意重试幂等性问题)
3、如何保证第一个事务先执行,采用补偿机制,在创建一个补单消费者进行监听,如果订单没有创建成功,进行补单。

?延时队列怎么准时取消订单?
不需要用延时队列,大材小用了,它主要是用来阶梯性;
实现1:(不采用)延时队列本身是没有消费者的(直拉拒绝的意思),设置TTL放进去之后,需要绑定到其他普通队列2。队列2监听,队列1的时间到之后,队列2就会收到消息,然后取消订单
实现2:给当前队列在生产者发每个消息的时候,或者消费队列属性里设置一个TTL时间。就能实现延迟消费了(优先选最短)。

?RabbitMQ如何实现定时阶梯性通知?
case判断:
case(ttl时间)
3秒:重新发送一个60秒
60秒:重新发送一个10分
10分:不再发送

最先淘汰的方案:本来打算使用将失败的消息写到数据库等待发送,然后每秒查询数据库获取消息通知前端。但觉得这样的处理方式太粗暴。存在以下缺点:1 、每秒请求有点儿浪费资源; 2 、通知方式不稳定; 3 、无法承受大数据量 4、由数据量大可能查询慢,不准时(如果是在做30分钟立马取消订单业务,不准时,库存就无法及时得到释放,影响成单数

最初用的方案(不是阶梯性通知,只靠重试机制):新建一个正常通知主队列A,账务通知一次失败后,立马重试一次,如果还失败,就把它放进通知主队列里,消费端绑定监听通知主队列,并开启重试机制,10秒通次一次,最多3次。到3次了,消费队列会自动删除(缺点:超限后自动就删除了,多次没通知到也没法报警;还有策略全10秒也不好


优化后的方案也有两个
 一、选延时队列来拓展。(建意用)
延时队列本身是没有消费者的(直拉拒绝的意思),设置TTL放进去之后,需要将其DLX绑定到其他普通队列2队列2监听,队列1的时间到之后,队列2就会收到消息,然后通知一次,若失败重置TTL到期(生存)时间,假设当前3秒,那重新发布一条60秒进延时队列。如此3秒,60秒,10分,执行到10分这次停止,我就不再重放队列了。等业务人工补偿,邮件告警

(DLX会重新路由转发到指定的队列)


二、选死信队列来拓展,(使用rabbitmq的死信队列来实现。)(不太好用,放弃)
优化最初用的方案(改成阶梯性通知,不靠重试机制,用死信):先建一个正常通知主队列A(消费端队列的绑定),账务通知一次失败后,立马重试一次,如果还失败,就把它放进通知主队列里,主队列直接nack,到达死信队列(死信队列可以设置每次时间阶梯),把死信的交换机路由到一台正常的主队列。重试失败,就。。。(后续),消费队列会自动删除(缺点:超限后自动就删除了,多次没通知到也没法报警;还有策略全10秒也不好) 



----------RabbitMQ--实战--------

?实现原理?       
所以我们现在要实现消息的重试,实现效果为:
      首先,将消息携带routtingkey的消息发送到正常转发器exchange@normal,exchange@normal将消息发送到正常队列queue@normal,queue@normal得到消息后进行处理,如果处理成功,则给rabbitmq发送应答。如果消息处理失败,判断消息失败的次数:如果失败次数小于3次,则将消息发送到重试转发器exchange@retry,exchange@retry得到消息后,发送到重试队列queue@retry,queue@retry10s后,将该条消息再次发送到正常转发器exchange@normal进行正常的消费;如果失败次数超过3次,则将消息发送到死信交易机exchange@filed,exchange@filed死信交易机将失败了的消息发送给死信队列queue@filed,然后可以根据业务需求处理失败了的数据。比如保存到失败文件或者数据库等,也可以人工处理后,重新发送给exchange@normal。


??RabbitMQ消息中间件集群搭建和如何配集群?如何配多个IP地址不同服务的?
1、集群 

2、多个IP不同服务
https://blog.csdn.net/qq_33000453/article/details/100051865

队列集群消息会重复吗


RabbitMQ消息队列怎么监控的?满了怎么办?TPS高了怎么办?
怎么监控的:1、使用Zabbix监控RabbitMQ消息队列, 编写监控脚本和添加Zabbix配置文件,定时监控到超出脚本配置里的指标就会报警。
2、使用rabbitmqctl管理和监控,查看虚拟主机,查看队列,查看list_exchanges,查看用户,查看连接,查看消费者信息,查看未被确认的队列,查看单个队列的内存使用,
https://www.cnblogs.com/minseo/p/10309121.html

 

发布了39 篇原创文章 · 获赞 0 · 访问量 784

猜你喜欢

转载自blog.csdn.net/qq_15458763/article/details/103892213