RabbitMQ(8)-集群架构知识的补充以及java实现

由于RabbitMQ集群对延迟非常敏感,所以只适合在本地局域网内使用

一.知识补充

1.设计目标:

允许生产者和消费者在RabbitMQ节点崩溃的情况下继续运行;

通过添加更多的节点来线性扩展消息通信吞吐量。

2.RabbitMQ会始终记录四种类型的内部元数据:

队列元数据-----队列名称和属性(持久化、自动删除);

交换器元数据------交换器名称、类型和属性;

绑定元数据-----一张简单的表格展示了如何将消息路由到队列

vhost元数据-----vhost内的队列、交换器和绑定提供命名空间和安全属性

引入集群时,rabbitmq需要追踪新的元数据类型

3.RabbitMQ默认不会将队列和状态复制到所有节点的原因:

存储空间----添加新的节点不会带来更多的存储空间;

性能----消息的发布需要将消息复制到集群每个节点,对于持久化消息,每一条消息都会引发磁盘活动,每次增加节点,网络和磁盘负载都会增加

4.分布交换器:

交换器只是一个名称,一个队列的绑定列表(一张查询队列的表),消息的路由是信道将消息上的路由键同交换器的绑定列表进行比较然后 路由消息。

5.消息丢失的解决办法

场景:AMQPbasic.publish命令不会返回消息状态,当信道崩溃时有可能生产者还在不断地创建消息

a.AMQP事务:消息路由到队列之前一直阻塞

b.发送方确认模式

6.内存节点与磁盘节点

内存节点会将所有的队列、交换器、绑定、用户、权限和vhost的元数据定义都存储于内存中,磁盘节点会将其保存在磁盘上,内存节点就可以提供出色的性能,磁盘节点能保障集群配置信息丢失于重启的操作上,所以磁盘节点至少为一个以上,碰巧磁盘节点宕机,还可以路由消息,只是不能创建队列、交换器、绑定、用户、权限和vhost

7.内存节点在重启之后如何获得元数据和状态信息

新节点加入集群时,你必须列出集群的所有磁盘节点并作为运行集群的命令参数

8.镜像队列以及工作原理

镜像队列的主拷贝仅存在于一个节点上(主节点master),在集群中的其他节点拥有从队列的拷贝,主节点不可用时最老的从节点会被选举为新的主队列

工作原理:

信道负责将消息路由到合适的队列上,并将消息队列投递到镜像队列的从队列上

RabbitMQ不能区分故障转移中丢失的确认消息和那些尚未得到确认的消息,已经消费但尚未被确认的消息会重新入队到原来的位置,如果说客户端不支持消息取消通知,应该避免使用镜像队列,那么队列会源源不断的接收投递过来的消息,当然在AMQP2.4开始已经添加这个取消通知的扩展

9.升级集群节点:

9.1通过RabbitMQ Management插件来备份当前配置

9.2关闭所有生产者等待消费者消费完队列中所有消息

9.3关闭节点,并解压新版到现在的安装目录

9.4选择其中一个磁盘节点作为升级节点,启动时会将持久化的集群数据升级到新版本,然后启动其他磁盘节点,它们会获取升级后的集群数据

9.5启动集群内存节点,所有的元数据和配置信息都会被保留



2.代码实现

Consumer:

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class ClusterTestConsumer {

    private static final String EXCHANGE_NAME="cluster_test";
    private static final String QUEUE_NAME="cluster_test";
    private static final String ROUTING_KEY="cluster_test";
    public static void main(String[] args) {
        ConnectionFactory factory = new ConnectionFactory();
        Connection connection = null;
        //haproxy监听其他三台rabbitmq服务,连接haproxy即可进行负载均衡和故障转移等操作
        factory.setPort(5670);
        factory.setHost("192.168.111.131");
        factory.setUsername("admin");
        factory.setPassword("admin");
        while(true) {
            try {
                connection = factory.newConnection();
                final Channel channel = connection.createChannel();
                channel.exchangeDeclare(EXCHANGE_NAME,"direct",false,false,null);
                channel.queueDeclare(QUEUE_NAME,false,false,false,null);
                channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,ROUTING_KEY);
                System.out.println("Ready for testing!");
                Consumer cluster_test = new DefaultConsumer(channel){
                    @Override
                    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                        String msg = new String(body,"UTF-8");
                        System.out.println("Receive message:........"+msg);
                        //通过投递消息的标记DeliveryTag向服务器确认该消息被成功消费
                        channel.basicAck(envelope.getDeliveryTag(),false);
                    }
                };
                //消息消费
                channel.basicConsume(QUEUE_NAME,false,"cluster_test",cluster_test);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                e.printStackTrace();
            }
        }
    }
}

Producer:

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;

import java.io.IOException;
import java.util.Date;
import java.util.concurrent.TimeoutException;

public class ClusterTestProducer {
    private static final String EXCHANGE_NAME = "cluster_test";
    private static final String ROUTING_KEY = "cluster_test";

    public static void main(String[] args) {
        ConnectionFactory factory = new ConnectionFactory();
        Connection connection = null;
        //haproxy监听其他三台rabbitmq服务,连接haproxy即可进行负载均衡和故障转移等操作
        factory.setPort(5670);
        factory.setHost("192.168.111.131");
        factory.setUsername("admin");
        factory.setPassword("admin");
        try {
            connection = factory.newConnection();
            final Channel channel = connection.createChannel();
            String msg = "{\"context\":\"cluster_test\",\"time\":" + new Date().getTime() + "}";
            channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes("UTF-8"));
            System.out.println("Sent Cluster test message!!!");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
    }
}

测试时可断开其中一台rabbitmq服务,之后消费者便会重新连接到集群中正常工作

你可能感兴趣的:(镜像队列,集群升级,设计目标,rabbitmq集群架构)