- RabbitMQ可以通过3种方式实现分布式部署:集群、Federation和Shovel。这3种方式不是互斥的,可以根据需要选择其中的一种或者以几种的组合来达到分布式部署的目的。
- Federation和Shovel可以为RabbitMQ的分布式部署提供更高的灵活性,但同时也提高了部署的复杂性。
1、Federation
- Federation插件的作用是让RabbitMQ在不同的Broker节点之间进行消息传递(不需要是集群),该功能在很多场景下都非常有用:
- Federation插件能够在不同管理域(可能设置了不同的用户和vhost,也可能运行在不同版本的RabbitMQ和Erlang上)中的Broker或者集群之间传递消息。
- Federation插件基于AMQP 0-9-1协议在不同的Broker之间进行通信,并能够容忍不稳定的网络连接。
- 一个Broker节点中可以同时存在联邦交换器(或队列)或者本地交换器(或队列),只需要对特定的交换器(或队列)创建Federation连接(Federation link)。
- Federation不需要在N个Broker节点之间创建O(N*N)个连接(尽管这是最简单的使用方式),这也就意味着Federation在使用时更容易扩展。
- Federation插件可以让多个交换器或者多个队列进行连接。
- 一个联邦交换器(federated exchange)或者一个联邦队列(federated queue)可以接收上游(upstream)的消息,这里的上游是指位于其他Broker上的交换器或者队列。
- 联邦交换器能够将原本发送给上游交换器(upstream exchange)的消息路由到本地的某个队列中。
- 联邦队列则允许一个本地消费者接收到来自上游队列(upstream queue)的消息。
- 注意,理论上可以将一个federated queue与一个federated exchange绑定起来,不过这样会导放一些不可预测的结果,如果对结果评估不足,建议慎用这种搭配方式。
1.1、联邦交换器
1.1.1、联邦交换器的基本原理
- 假设图8-1中broker1部署在北京,Broker2部署在上海,彼此之间相距甚远,网络延迟是一个不得不面对的问题。
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220708225331204-1526009560.png)
-
- 如果有一个在北京的业务ClientA需要连接Broker1,并向其中的交换器exchangeA发送消息,网络延迟很小,就算在开启了publisher confirm机制或者事务机制的情况下,ClientA也可以迅速收到Broker1的确认信息。
- 如果有一个在上海的业务ClientB需要连接Broker1,并向其中的交换器exchangeA发送消息,那么ClientB与Broker1之间就会有很大的网络延迟,尤其是在开启了publisher confrrrn机制或者事务机制的情况下,ClientB会等待很长的延迟时间之后才能收到Broker1的确认信息,进而必然降低这个发送线程的性能,甚至造成一定程度上的阻塞。使用Federation插件就可以很好地解决网络延迟问题。
- 如图8-2所示,在Broker1中为交换器exchangeA(Broker1中的队列queueA通过"rkA"与exchangeA 进行了绑定)与Broker2之间建立一条单向的Federation link。
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220710104705039-1501505330.png)
- Federation link的实现原理:
- (1)Federation插件会在Broker2上会建立一个同名的交换器exchangeA(这个名称可以配置,默认同名),同时建立一个内部的交换器"federation:exchangeA->Broker1 B",并通过路由键"rkA"将这两个交换器绑定起来。交换器"federation:exchangeA->Broker1 B"名字中的"Broker1"是集群名,可以使用rabbitmqctl set_cluster_name {new_name}命令进行修改。
- (2)Federation插件还会在Broker2上建立一个队列"federation:exchangeA->broker1",并与交换器"federation:exchangeA->Broker1 B"进行绑定。
- (3)Federation插件会在Broker1中的交换器exchangeA与队列"federation:exchangeA->broker1"之间建立一条AMQP连接来实时地消费队列"federation:exchangeA->broker1"中的数据。
- 对客户端来说就是在Broker2的exchangeA和Broker1的exchangeA之间建立一条Federation link。
- 回到前面的问题,在上海的业务ClientB可以连接Broker2并向exchangeA发送消息,这样ClientB可以迅速发送完消息并收到确认信息,之后消息会通过Federation link转发到Broker1的交换器exchangeA中。最终消息会存入与exchangeA绑定的队列queueA中,消费者最终可以消费队列queueA中的消息。
- 经过Federation link转发的消息会带有特殊的headers属性标记。例如向Broker2中的交换器exchangeA发送一条内容为"federation test payload."的持久化消息,之后可以在Broker1中的队列queueA中消费到这条消息。
- Federation不仅可以便利生产者,也可以便利消费者。
- 假设某生产者将消息存入Broker2中的某个队列queueB,在北京的业务ClientA想要消费queueB的消息,消息的流转及确认必然要忍受较大的网络延迟,内部编码逻辑也会因这一因素变得更加复杂,这样不利于业务ClientA的发展。不如将这个消息转发的过程以及内部复杂的编程逻辑交给Federation去完成,而业务方在编码时不必再考虑网络延迟的问题。Federation使得生产者和消费者可以异地部署而又让这两方感受不到过多的差异。
- 图8-2,Broker2中的队列"federation:exchangeA->broker1"是一个相对普通的队列,可以直接通过客户端进行消费。
- 假设一个客户端ClientC通过Basic.Consume来消费队列"federation:exchangeA->broker1 B"的消息,那么发往Broker2中exchangeA的消息会有一部分(一半)被ClientC消费掉,而另一半会发往Broker1的exchangeA。
- 如果业务应用有要求所有发往Broker2中exchangeA的消息都要转发至Broker1的exchangeA中,此时就要注意队列"federation:exchangeA->broker1 B"不能有其他的消费者。而对于"异地均摊消费"这种特殊需求,队列"federation:exchangeA->broker1 B"这种天生特性提供了支持。
- 对于Broker2的交换器exchangeA而言,它是一个普通的交换器,可以创建一个新的队列绑定它,对它的用法没有什么特殊之处。
1.1.2、联邦交换器的种类
- 需要特别注意的是,对于默认的交换器(每个vhost下都会默认创建一个名为""的交换器)和内部交换器而言,不能对其使用Federation的功能。
- 普通的联邦交换器
- federated exchange:联邦交换器
- upstream exchange:上游交换器
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220708233945073-40267714.png)
- 一个federated exchange同样可以成为另一个交换器的upstream exchange。
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220708234340238-656844166.png)
- 两个交换器可以互为federated exchange和upstream exchange。其中参数"max_hops=1" 表示一条消息最多被转发的次数为1。
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220708234539377-1632752440.png)
- 对于联邦交换器而言,还有更复杂的拓扑逻辑部署方式。
1.2、联邦队列
- 联邦队列(federated queue)可以在多个Broker节点(或者集群)之间为单个队列提供均衡负载的功能。
- 一个联邦队列可以连接一个或者多个上游队列(upstream queue),并从上游队列中获取消息以满足本地消费者的消费。
1.2.1、简单的联邦队列
- 如图8-9所示,两个Broker中的几个联邦队列(灰色)和非联邦队列(白色)。队列queue1和queue2原本在broker2中,由于某种需求将其配置为federated queue,并将broker1作为upstream。
- Federation插件会在broker1上创建同名的队列queue1和queue2,与broker2中的队列queue1和queue2分别建立两个单向独立的Federation link。
- 当消费者ClinetA连接broker2并通过Basic.Consume消费队列queue1(或queue2)中的消息时:
- 如果队列queue1(或queue2)中本身有若干消息堆积,那么ClientA直接消费这些消息,此时broker2 中的queue1(或queue2)并不会拉取broker1中的queue1(或queue2)的消息。
- 如果队列queue1(或queue2)中没有消息堆积或者消息被消费完了,那么它会通过Federation link拉取在broker1中的上游队列queue1(或queue2)中的消息(如果有消息),然后存储到本地,之后再被消费者ClientA进行消费。
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220709201223739-1286482610.png)
- 消费者既可以消费broker2中的队列,又可以消费broker1中的队列。如果在broker1端的消费者来不及消费队列queue1中的消息,那么broker2端的消费者可以为其分担,这样就达到某种意义上的负载均衡了。
1.2.2、互为联邦队列
- 和federated exchange不同,一条消息可以在联邦队列间转发无限次。如图8-10中两个队列queue互为联邦队列。
- 队列中的消息除了被消费,还会转向有多余消费能力的一方,如果这种"多余的消费能力"在broker1和broker2中来回切换,那么消息也会在broker1和broker2中的队列queue中来回转发。
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220709202149343-1031285381.png)
1.2.3、联邦队列间不会传递
- 如图8-11所示,如果broker2中的队列queue2没有消息堆积或者消息被消费完了,消费者并不能通过Basic.Get来获取broker1中队列queue1的消息。如果消费者要继续消费broker1中的队列queue1的消息,就必须等待Federation link拉取消息存入broker2中的队列queue2,所以对于federated queue而言只能使用Basic.Consume进行消费(不能使用Basic.Get)。
- federated queue并不具备传递性。
- 队列queue2作为federated queue与队列queue1进行联邦,而队列queue2又作为队列queue3的upstram queue,但是队列queue1与queue3之间并没有产生任何联邦的关系。
- 如果队列queue1中有消息堆积,消费者连接broker3消费queue3中的消息,无论queue3处于何种状态,这些消费者都消费不到queue1中的消息,除非queue2中有queue1的消息。
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220709202752340-294427720.png)
1.3、Federation的使用
- 要使用Federation功能,需要配置以下2个内容:
- (1)配置一个或多个upstream,并且每个upstream均定义了到其他节点的Federation link。
- (2)定义匹配交换器或者队列的一种或多种策略(Policy)。
- 开启rabbitmq_federation和rabbitmq_federation_management插件。
- rabbitmq_federation_management依赖rabbitmq_management插件,因此开启前者时会默认开启后者。
//开启Federation插件
]# rabbitmq-plugins enable rabbitmq_federation
//开启Federation management插件(Federation的管理插件)
]# rabbitmq-plugins enable rabbitmq_federation_management
]# rabbitmq-plugins list
......
[E*] rabbitmq_federation 3.9.19
[E*] rabbitmq_federation_management 3.9.19
[e*] rabbitmq_management 3.9.19
[e*] rabbitmq_management_agent 3.9.19
[e*] rabbitmq_web_dispatch 3.9.19
- 开启rabbitmq_federation_management插件之后,在RabbitMQ的管理界面中"Admin"的右侧会多出"Federation Status" 和"Federation Upstreams"两个Tab页,如图8-12所示。
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220709214831091-391635571.png)
- 注意,当需要在集群中使用Federation功能时,集群中的所有节点都应该开启Federation插件。
- Federation upstream的信息全部都会保存在RabbitMQ的Mnesia数据库中,包括用户信息、权限信息、队列信息等。
- 配置Federation有3种级别:
- (1)Upstreams:每个upstream用于定义与其他Broker建立连接的信息。
- (2)Upstream sets:每个upstream set用于对一系列使用Federation功能的upstream进行分组。
- (3)Policies:每一个Policy会选定出一组交换器,或者队列,亦或者两者皆有,进而作用于一个单独的upsteam或者upstream set之上。
- 实际上,在简单场景下,基本上不使用Upstream sets,因为存在一种名为"all"的隐式upstream set,所有的upstream都会添加到这个set 之中。
- Upstreams和Upstream sets都属于运行时参数,就像交换器和队列一样,每个vhost都有不同的参数和策略的集合。
- 配置Federation的Upstreams和policy都有3种方法:
- (1)通过rabbitmqctl工具。
- (2)通过RabbitMQ Management插件提供的HTTP API接口。
- (3)通过rabbitmq_federation_management插件提供的Web管理界面的方式(最方便且通用)。不过基于Web管理界面的方式不能提供全部功能,比如无法针对upstream set进行管理。
//(1)使用rabbitmqctl工具创建一个upstream。
]# rabbitmqctl set_parameter --vhost test_vhost federation-upstream test_f1 '{"uri":"amqp://root:root@10.1.1.14:5672","ack-mode":"on-confirm"}'
//(2)通过调用HTTP API接口的方式创建一个upstream。
]# curl -i -u root:root -XPUT -d '{"value":{"uri":"amqp://root:root@10.1.1.14:5672","ack-mode":"on-confirm"}}' http://10.1.1.13:15672/api/parameters/federation-upstream/test_vhost/test_f2
//(3)通过在Web管理界面中添加的方式,在"Admin"->"Pederation Upstreams"->"Add a new upstream"中创建。
//(1)使用rabbitmqctl工具创建一个policy。
]# rabbitmqctl set_policy --vhost test_vhost --apply-to exchanges test_p1 "^exchange" '{"federation-upstream":"test_f1"}'
//(2)通过调用HTTP API接口的方式创建一个policy。
]# curl -i -u root:root -XPUT -d '{"pattern":"^exchange","definition":{"federation-upstream":"test_f2"},"apply-to":"exchanges"}' http://10.1.1.14:15672/api/policies/test_vhost/test_p2
//(3)通过在Web管理界面中添加的方式,在"Admin"->"Policies"->"Add / update a policy"中创建。
1.4、Federation示例
- 如图8-2所示,使用Federation插件在broker1(IP地址:10.1.1.13)和broker2(IP地址:10.1.1.14)间建立federated Link。
1.4.1、开启Federation插件
- 在broker1和broker2中开启Federation插件,最好同时开启Federation management插件。
//开启Federation插件
]# rabbitmq-plugins enable rabbitmq_federation
//开启Federation management插件(Federation的管理插件)
]# rabbitmq-plugins enable rabbitmq_federation_management
//创建一个用户,用户账密root/root
]# rabbitmqctl add_user root root
//为用户root配置角色administrator
]# rabbitmqctl set_user_tags root administrator
1.4.2、配置federation broker(broker1)
- broker1使用的是rabbit@hh13节点(是单节点,不是集群)。
1、创建一个vhost
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220710085244072-561949497.png)
2、创建一个交换器(exchange)
- 交换器的类型使用fanout,这样测试时就可以不用设置路由键了。
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220710090052074-1052477132.png)
3、创建一个队列(queue)
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220710090524007-1331674938.png)
4、创建一个Binding
- 将队列test1_queue绑定到交换器test1_exchange。
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220710091123062-1657029226.png)
5、创建一个Federation Upstream
- 创建一个upstream,就是(在本机上)创建一个连接到upstream broker机器的Federation Link。
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220710091547553-961934162.png)
- 通用参数如下:
- Virtual host:要在哪个vhost中创建upstream
- Name:定义这个upstream的名称。必填项。
- URI(uri):定义upstream的AMQP连接。必填项。本示例填写的是amqp://root:root@10.1.1.14:5672
- Prefetch count(prefetch count):定义Federation内部缓存的消息条数,即在收到上游消息之后且在发送到下游之前缓存的消息条数。
- Reconnect delay(reconnect-delay):Federation link由于某种原因断开之后,需要等待多少秒开始重新建立连接。
- Acknowledgement Mode(ack-mode):定义Federationlink的消息确认方式,有3种:on-confirm(默认)、on-publish、no-acko
- on-confirm:表示在接收到下游的确认消息(等待下游的Basic.Ack)之后再向上游发送消息确认,这个选项可以确保网络失败或者Broker宕机时不会丢失消息,但也是处理速度最慢的选项。
- on-publish:表示消息发送到下游后(并需要等待下游的Basic.Ack)再向上游发送消息确认,这个选项可以确保在网络失败的情况下不会丢失消息,但不能确保Broker岩机时不会丢失消息。
- no-ack:表示无须进行消息确认,这个选项处理速度最快,但也最容易丢失消息。
- Trust User-ID(trust-user-id):设定Federation是否使用"Validated User-ID"这个功能。如果设置为false或者没有设置,那么Federation会忽略消息的user_id属性;如果设置为true,则Federation只会转发user_id为上游任意有效的用户的消息。
- 只适用federated exchange的参数如下:
- Exchange(exchange):指定upstream exchange的名称,默认情况下和federated exchange同名。
- Max hops(max-hops):指定消息被丢弃前在Federation link中最大的跳转次数。默认为1。注意即使设置max-hops参数为大于1的值,同一条消息也不会在同一个Broker中出现2次,但是有可能会在多个节点中被复制。
- Expires(expires):指定Federation link断开之后,federated queue所对应的upstream
- queue的超时时间,默认为"none",表示为不删除,单位为ms。这个参数相当于设置普通队列的x-expires参数。
- Message TTL(message-ttl):为federated queue所对应的upstream queue设置,相当于普通队列的
- x-message-ttl参数。默认为"none",表示消息没有超时时间。
- HA policy(ha-policy):为federated queue所对应的upsteam queue设置,相当于普通队列的x-ha-policy参数。默认为"none",表示队列没有任何HA。
- 只适用federated queue的参数如下:
- Queue(queue):执行upstream queue的名称,默认情况下和federated queue同名。
6、创建一个policy
- 创建policy,就是创建一个过滤器。将(本机上的)哪个或哪些交换器(或者队列)通过Federaton Link映射到其他upstream broker。
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220710092110178-2112734816.png)
7、查看Federation的状态
- 可以看到Federation的状态是ERROR,这是因为upstream broker还没有相应虚拟主机test1_vhost。
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220710092450956-1863786062.png)
1.4.3、配置upstream broker(broker2)
- broker2使用的是rabbit@hh14节点(是单节点,不是集群)。
1、创建一个vhost
- 注意,vhost的名称要和broker1中的相同。
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220710093140712-355119899.png)
2、查看exchange和queue
- 可以看到,Federation在broker2上创建了一个同名的交换器test1_exchange,以及一个内建交换器"federation: test1_exchange -> rabbit@hh13 B"。
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220710095846425-793650909.png)
- 可以看到,Federation在broker2上创建了一个队列"federation: test1_exchange -> rabbit@hh13"
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220710103636714-1774423404.png)
1.4.4、测试Federation link
1、在upstream broker(broker2)上发送消息。
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220710101140857-418219952.png)
2、在federation broker(broker1)消费消息
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220710101621652-549948280.png)
2、Shovel
- 与Federation的数据转发功能类似,Shovel可以持续、可靠地从一个Broker中的队列(作为源端,即source)拉取数据并转发到另一个Broker中的交换器(作为目的端,即destination)。
- 源队列和目的交换器可以同时位于同一个Broker上,也可以位于不同的Broker上。
- Shovel的行为就像优秀的客户端应用程序能够负责连接源和目的地、负责消息的读写及负责连接失败问题的处理。
- Shovel的主要优势:
- 松耦合:Shovel可以移动位于不同管理域中的Broker(或者集群)上的消息,这些Broker(或者集群)可以包含不同的用户和vhost,也可以使用不同的RabbitMQ和Erlang版本。
- 支持广域网:Shovel插件同样基于AMQP协议在Broker之间进行通信,可以容忍时断时续的网络连接,并且能够保证消息的可靠性。
- 高度定制:当Shovel成功连接后,可以对其进行配置以执行相关的AMQP命令。
2.1、Shovel原理
- Shovel的结构示意图。通常情况下,都是将队列作为源端,将交换器作为目的端,如图8-15所示。
- 一共有两个Broker,broker1(IP地址:10.1.1.13)和broker2(IP地址:10.1.1.14)。
- broker1中有交换器exchange1和队列queue1,且这两者通过路由键"rk1"进行绑定。
- broker2中有交换器exchange2和队列queue2,且这两者通过路由键"rk2"进行绑定。
- 在队列queue1和交换器exchange2之间配置一个Shovel link。当一条内容为"Shovel test payload"的消息从客户端发送至交换器exchange1的时候,这条消息会经过图8-15中的数据流转最后存储在队列queue2中。如果在配置Shovel link时设置了add-forward-headers 参数为true,则在消费到队列queue2中这条消息的时候会有特殊的headers属性标记。
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220710112215135-1648404679.png)
- 也可以将队列做为目的端,如图8-17所示。虽然看起来队列queue1是通过Shovel link直接将消息转发至queue2的,其实中间也是经由broker2的交换器转发,只不过这个交换器是默认的交换器而己。
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220710120041359-320188380.png)
- 也可以将交换器为源端,如图8-18所示。虽然看起来交换器exchange1是通过Shovel link直接将消息转发至exchange2上的,实际上在broker1中会新建一个队列(名称由RabbitMQ自定义,比如图8-18中的"amq.gen-ZwolUsoUchY6a7xaPyrZZH")并绑定exchange1,消息从交换器exchange1过来先存储在这个队列中,然后Shovel再从这个队列中拉取消息并转发到交换器exchange2。
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220710120847058-1243382122.png)
- 前面所说的exchange1、queue1、exchange2及queue2都可以在Shovel成功连接源端或者目的端Broker之后再创建(执行一系列相应的AMQP配置声明),它们并不是非要在Shovel link建立之前创建。
- Shovel可以为源端或者目的端配置多个Broker的地址,这样可以使得源端或者目的端的Broker失效后能够尝试重连到其他Broker之上(随机挑选)。可以设置reconnect_delay参数避免由于重连行为导致的网络泛洪,或者可以在重连失败后直接停止连接。针对源端和目的端的所有配置声明会在重连成功之后被重新发迭。
2.2、Shove的使用
- 开启rabbitmq_shovel和rabbitmq_shovel_management插件。
- rabbitmq_shovel_management依赖rabbitmq_management插件,因此开启前者时会默认开启后者。
//开启shovel插件
]# rabbitmq-plugins enable rabbitmq_shovel
//开启Federation shovel插件(shovel的管理插件)
]# rabbitmq-plugins enable rabbitmq_shovel_management
]# rabbitmq-plugins list
......
[e*] rabbitmq_management 3.9.19
[e*] rabbitmq_management_agent 3.9.19
[E*] rabbitmq_shovel_management 3.9.19
[e*] rabbitmq_web_dispatch 3.9.19
- 开启rabbitmq_shovel_management插件之后,在RabbitMQ的管理界面中"Admin"的右侧会多出"Shovel Status" 和"Shovel Management"两个Tab页,如图8-19所示。
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220710153008502-81948088.png)
- Shovel既可以部署在源端,也可以部署在目的端。
- 有两种配置Shovel的方式:静态方式(static)和动态方式(dynamic)。
- 静态方式是指在rabbitmq.conf配置文件中设置。
- 动态方式是指通过Runtime Parameter设置。
2.2.1、静态方式
- 在rabbitmq.conf配置文件中针对Shovel插件的配置信息是一种Er1ang项式,由单条Shovel条目构成(shove1s部分的下一层)。
{rabbitmq_shovel, [{shovels, [{shovel_name, [ . .. ]}, ... ]}]}
- Shovel的每条目定义都源端与目的端的转发关系,其名称(shovel_name)必须是唯一的。Shovel的定义如下:
{shovel name, [{sources, [ ... ]}
,{destinations, [ ... ]}
,{queue, queue_name}
,{prefetch_count, count}
,{ack_mode, a_mode}
,{publish_properties, [ ... ]}
,{publish_fields, [ ... ]}
,{reconnect_delay, reconn_delay}
]}
[{rabbitmq_shovel, [
{shovels, [
{my_shovel, [
{sources, [
{broker, "amqp://root:root@10.1.1.13/my_vhost"},
{declarations, [
{'exchange.declare', [{exchange, <<"my_exchange1">>}, {type, <<"fanout">>}, durable]},
{'queue.declare', [{queue, <<"my-queue1">>}, durable]},
{'queue.bind', [{exchange, <<"my_exchang1">>}, {queue, <<"my-queue1">>}, {routing_key, <<"rk1" >>}]}
]}
]},
{destinations, [
{broker, "amqp://root3:root3@host3.domain/my_vhost"},
{declarations, [
{'exchange.declare', [{exchange, <<"my_exchange2">>}, {type, <<"direct">>}, durable]},
{'queue.declare', [{queue, <<"my-queue2">>}, durable]},
{'queue.bind', [{exchange, <<"my_exchang2">>}, {queue, <<"my-queue2">>}, {routing_key, <<"rk2" >>}]}
]}
]},
{queue, <<"queue1">>} ,
{ack_mode, on_confirm},
{prefetch_count, 64},
{publish_properties, [{delivery_mode, 2} ]},
{add_forward_headers, true},
{publish_fields, [{exchange, <<"my_exchang2">>}, {routing_key, <<"rk2">>}]},
{reconnect_delay, 5}
]}
]}
]}
- broker:是用于连接RabbitMQ的URI。定义了用于连接Shove1两端的服务器地址、用户名、密码、vhost和端口号等。
- 如果sources或者destinations是RabbitMQ集群,那么就使用brokers,并在其后用多个URI字符串(用"[]"包裹起来),比如{brokers, ["amqp://root:root@10.1.1.13/my_vhost", "amqp://root:root@10.1.1.13/my_vhost"]},这样的定义能够使得Shovel在主节点故障时转移到集群的另一个节点上。
- declarations:可选的。declarationlist:指定了可以使用的AMQP命令的列表,声明了队列、交换器和绑定关系。
- 注意其中所有的字符串并不是简单地用引号标注,而是同时用双尖括号包裹,比如<<"my-queue1">>。这里的双尖括号是要让ErLang程序不要将其视为简单的字符串,而是binary类型的字符串。如果没有双尖括号包裹,那么Shovel在启动的时候就会出错。
- 与sources和destinations同级的queue表示源端服务器上的队列名称。可以将queue设置为"<<>>",表示匿名队列(队列名称由RabbitMQ自动生)。
- prefetch_count:表示Shovel内部缓存的消息条数,可以参考Federation的相关参数。ShoveL的内部缓存是源端服务器和目的端服务器之间的中间缓存部分。
- ack_mode:表示在完成转发消息时的确认模式,和Federation的ack_mode一样也有三种。
- no_ack表示无须任何消息确认行为.
- on_publish表示Shovel会把每一条消息发送到目的端之后再向源端发送消息确认.
- on_confirm表示Shovel会使用publisher confirm机制,在收到目的端的消息确认之后再向源端发送消息确认。Shovel的ack_mode默认也是on_confirm,并且官方强烈建议使用该值。如果选择使用其他值,整体性能虽然会有略微提升,但是发生各种失效问题的情况时,消息的可靠性得不到保障。
- publishproperties:指消息发往目的端时需要特别设置的属性列表。默认情况下,被转发的消息的各个属性是被保留的,但是如果在publish_properties中对属性进行了设置则可以覆盖原先的属性值。publishproperties的属性列表包括contenttype、content_encoding、headers、delivery_mode、priority、correlationid、reply_to、expiration、message_id、timestamp、type、userid、app_id和clusterid。
- addforwardheaders:如果设置为true,则会在转发的消息内添加x-shovelled的header属性。
- publish_fields:定义了消息需要发往目的端服务器上的交换器以及标记在消息上的路由键。如果交换器和路由键没有定义,则Shovel会从原始消息上复制这些被忽略的设置。
- reconnect_delay:指定在Shovel link失效的情况下,重新建立连接前需要等待的时间,单位为秒。如果设置为0,则不会进行重连动作,即Shovel会在首次连接失效时停止工作。reconnect_delay默认为5秒。
2.2.2、动态方式
- 与Federation upstream类似,Shovel动态部署方式的配置信息会被保存到RabbitMQ的Mnesia数据库中,包括权限信息、用户信息和队列信息等内容。
- 每一个Shovel link都由一个相应的Parameter定义。配置Parameter有三种方法:
- (1)通过rabbitmqctl工具。
- (2)通过RabbitMQ Management插件提供的HTTP API接口。
- (3)通过rabbitmq_federation_management插件提供的Web管理界面的方式(最方便且通用)。
//(1)使用rabbitmqctl工具创建一个Shovel Link(当队列不存在时,会被自被创建,但交换器不会)
//源10.1.1.13,目的10.1.1.14,源队列test_queue1,目的队列test_queue2
rabbitmqctl set_parameter --vhost test_vhost1 shovel test_shovel1 '{"src-uri":"amqp://root:root@10.1.1.13:5672/test_vhost1","src-queue":"test_queue1","dest-uri":"amqp://root:root@10.1.1.14:5672/test_vhost2","dest-queue":"test_queue2","src-exchange-key":"rk2","prefetch-count":64,"reconnect-delay":5,"publish-properties":[],"add-forward-headers":true,"ack-mode":"on-confirm"}'
//源10.1.1.13,目的10.1.1.14,源队列test_queue1,目的交换器test_exchange2
rabbitmqctl set_parameter --vhost test_vhost1 shovel test_shovel1 '{"src-uri":"amqp://root:root@10.1.1.13:5672/test_vhost1","src-queue":"test_queue1","dest-uri":"amqp://root:root@10.1.1.14:5672/test_vhost2","dest-exchange":"test_exchange2","src-exchange-key":"rk2","prefetch-count":64,"reconnect-delay":5,"publish-properties":[],"add-forward-headers":true,"ack-mode":"on-confirm"}'
//(2)通过调用HTTP API接口的方式创建一个Shovel Link
//源10.1.1.13,目的10.1.1.14,源队列test_queue1,目的队列test_queue2
curl -i -u root:root -XPUT -d'{"value":{"src-uri":"amqp://root:root@10.1.1.13:5672/test_vhost1","src-queue":"test_queue1","dest-uri":"amqp://root:root@10.1.1.14:5672/test_vhost2","dest-queue":"test_queue2","src-exchange-key":"rk2","prefetch-count":64,"reconnect-delay":5,"publish-properties":[],"add-forward-headers":true,"ack-mode":"on-confirm"}}' http://10.1.1.13:15672/api/parameters/shovel/test_vhost1/test_shovel2
//(3)Web管理界面的方式
2.3、Shovel示例
- 如图8-17所示,使用Federation插件在broker1(IP地址:10.1.1.13)和broker2(IP地址:10.1.1.14)间建立federated exchange。
2.3.1、开启shovel插件
- 在broker1和broker2中开启Federation插件,最好同时开启rabbitmq_shovel插件。
//开启shovel插件
]# rabbitmq-plugins enable rabbitmq_shovel
//开启Federation shovel插件(shovel的管理插件)
]# rabbitmq-plugins enable rabbitmq_shovel_management
//创建一个用户,用户账密root/root
]# rabbitmqctl add_user root root
//为用户root配置角色administrator
]# rabbitmqctl set_user_tags root administrator
2.3.2、在两个broker上分别创建vhost、exchange和queue
- 下面的截图都是broker1上的。在broker2上的操作和broker1的一样,除了名称不一样外。
1、创建vhost
- 在broker1上创建test_vhost1,在broker2上创建test_vhost2。
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220710172953155-15972833.png)
2、创建exchange
- 交换器的类型使用fanout,这样测试时就可以不用设置路由键了。
- 在broker1上创建test_exchange1,在broker2上创建test_exchange2。
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220710173334580-912701580.png)
3、创建queue
- 在broker1上创建test_queue1,在broker2上创建test_queue2。
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220710173838386-1525808547.png)
4、创建一个binding
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220710174155542-462061193.png)
2.3.3、创建Shovel Link
- Shovel既可以部署在源端,也可以部署在目的端。因此创建操作可以在任意节点上进行。
- 队列到队列的实验成功,下面的操作就是队列到队列的。
1、创建Shovel Link
- Source URI是amqp://root:root@10.1.1.13:5672/test_vhost1
- Destination URI是amqp://root:root@10.1.1.14:5672/test_vhost3
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220710193919344-675559445.png)
2、查看Shovel Link的状态
]# rabbitmqctl eval 'rabbit_shovel_status:status().'
- 当Shovel处于启动、连接和创建资源时状态为starting;当Shove1正常运行时是running;当Shove1终止时是terminated。
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220710194354413-1451183349.png)
2.3.4、测试Shovel Link
1、在broker1上发送一个消息
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220710194730040-1161208580.png)
2、在broker2上消费这个消息
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220710195051209-1163783920.png)
3、解决消息堆积问题
- 消息堆积是在使用消息中间件过程中遇到的最正常不过的事情。
- 消息堆积是一把双刃剑,适量的堆积可以有削峰、缓存之用,但是如果堆积过于严重,那么就可能影响到其他队列的使用,导致整体服务质量的下降。对于一台普通的服务器来说,在一个队列中堆积1万至10万条消息,丝毫不会影响什么。但是如果这个队列中堆积超过一千万乃至一亿条消息时,可能会引起一些严重的问题,比如引起内存或者磁盘告警而造成所有Connection阻塞。
- 处理消息堆积严重的三种方法:
- (1)可以选择清空队列,或者采用空消费程序丢弃掉部分消息。不过对于重要的数据而言,丢弃消息的方案并不可取。
- (2)增加下游消费者的消费能力,这个思路可以通过后期优化代码逻辑或者增加消费者的实例数来实现。但是后期的代码优化在面临紧急情况时总归是"远水解不了近渴",并且有些业务场景也并非可以简单地通过增加消费实例而得以增强消费能力。
- (3)使用Shovel将队列中的消息转移到另一个集群。
- Shovel处理消息堆积,如图8-21所示。
- (1)当检测到当前运行的集群cluster1中的队列queue1中有严重消息堆积,比如通过/api/queues/vhost/name接口获取到队列的消息个数(messages)超过2千万或者消息占用大小(messages_bytes)超过10GB时,就启用shovel1将队列queue1中的消息转发至备份集群cluster2中的队列queue2。
- (2)当检测到队列queue1中的消息个数低于l百万或者消息占用大小低于1GB时就停止shovel1,然后让原本队列queuel中的消费者慢慢处理剩余的堆积。
- (3)当检测到队列queuel中的消息个数低于10万或者消息占用大小低于100MB时,就开启shovel2将队列queue2中暂存的消息返还给队列queue1。
- (4)当检测到队列queue1中的消息个数超过l百万或者消息占用大小高于1GB时就将shovel2停掉。
![](https://img2022.cnblogs.com/blog/2270526/202207/2270526-20220710204035198-1629890737.png)
- 如此,队列queue1就拥有了队列queue2这个"保镖"为它保驾护航。这里是"一备一"的情形,如果需要要"一备多",可以采用镜像队列或者引入Federation。