RabbitMQ详细解说

一.RabbitMQ的介绍

1.使用docker安装

docker run -d --hostname my-rabbit --name some-rabbit -p 15672:15672 -p 5672:5672 rabbitmq:3-management

15672:是rabbitmq给我们提供了管理页面
5672:是收发消息的端口

2.RabbitMQ的架构简单介绍

- Publisher - 生产者:发布消息到RabbitMQ中的Exchange
- Consumer - 消费者:监听RabbitMQ中的Queue中的消息
- Exchange - 交换机:和生产者建立连接并接收生产者的消息
- Queue - 队列:Exchange会将消息分发到指定的Queue,Queue和消费者进行交互
- Routes - 路由:交换机以什么样的策略将消息发布到Queue

注意点:写的所有的操作都是和虚拟主机绑定在一起的,而不是和rabbitmq绑定的  

二.Java代码实现RabbitMQ

1.简单的接收,发送到mq中

1.消费者

step01-依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

step02-配置文件

spring.rabbitmq.host=139.159.210.107
spring.rabbitmq.username=guest
spring.rabbitmq.port=5672
spring.rabbitmq.password=guest
#mq相当于excel文件一般,虚拟主机相当于excel文件的表单
spring.rabbitmq.virtual-host=/

step03-rabbitmq的配置,因为正常来说消息队列没有一个先后顺序,所以为了避免其中一个启动服务的时候,不会没有消息队列

@Configuration
public class RabbitConfig {

    public static final String HELLO_QUEUE_NAME= "hello";

    /**
     * 消息队列,向spring容器中注册一个Queue对象,那么系统就会自动创建一个队列
     * @return
     */
    @Bean
    Queue queue(){

        return new Queue(HELLO_XUSIQI_QUEUE_NAME);
    }
}

step04-监听

@Component
public class ConsumerHandler {
    /**
     * 监听哪个队列
     * @param msg:参数就是收到的具体的消息内容
     */
    @RabbitListener(queues = RabbitConfig.HELLO_QUEUE_NAME)
    public void handler1(String msg){
        System.out.println("handler1 = " + msg);
    }
}
2.生产者

step01-依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

step02-配置文件

spring.rabbitmq.host=139.159.210.107
spring.rabbitmq.username=guest
spring.rabbitmq.port=5672
spring.rabbitmq.password=guest
#虚拟主机
spring.rabbitmq.virtual-host=/

step03-rabbit的配置

@Configuration
public class RabbitConfig {

    public static final String HELLO_QUEUE_NAME= "hello";

    /**
     * 消息队列,向spring容器中注册一个Queue对象,那么系统就会自动创建一个队列
     * @return
     */
    @Bean
    Queue queue(){

        return new Queue(HELLO_QUEUE_NAME);
    }
}

step04-发送消息到消息队列

    //spring提供的模板,像jdbcTemplate一样的的都是spring封装好的
    @Autowired
    RabbitTemplate rabbitTemplate1;
	@Test
    void test01(){     
        rabbitTemplate1.convertAndSend(RabbitConfig.HELLO_QUEUE_NAME,"欧克");
    }

2.一个队列对应了多个消费者

一个队列对应了多个消费者,默认情况下,由队列对消息进行平均分配,消息会被分到不同的消费者手 中。消费者可以配置各自的并发能力,进而提高消息的消费能力,也可以配置手动 ack,来决定是否要 消费某一条消息。

/**
 * 消费者,收消息的
 */
@Component
public class ConsumerHandler {
    /**
     * 监听哪个队列
     * @param msg
     */
    @RabbitListener(queues = RabbitConfig.HELLO_QUEUE_NAME)
    public void handler1(String msg){
        System.out.println("handler1 = " + msg);
    }

    //如果没有写多少并发数,那么每一个消费者,平均分配,和上面的加起来就两个消费者,两个线程
    //@RabbitListener(queues = RabbitConfig.HELLO_QUEUE_NAME)
    //concurrency = "10"指的是该消费者一次可以消费十条消息
    @RabbitListener(queues = RabbitConfig.HELLO_QUEUE_NAME,concurrency = "10")
    public void handler2(String msg){
        System.out.println("handler2="+msg);
    }
}

三.交换机

生产者发送消息是发送给交换机的,消费者获取消息是获取消息队列的

1.Direct(直连交换机)

前言:DirectExchange 的路由策略是将消息队列绑定到一个 DirectExchange 上,当一条消息到达DirectExchange 时会被转发到与该条消息 routing key 相同的 Queue 上,意思是当发送的路由key和绑定的路由key是相同的,那么消息队列就会消费

step01-首先提供消息队列Queue,然后创建一个DirectExchange对象,三个参数分别是名字,重启后是否依然有效以及长期未用时是否删除。 创建Binding对象将Exchange和Queue绑定在一起。 DirectExchange和Binding两个Bean的配置可以省略掉,即如果使用DirectExchange,可以只配置一个Queue的实例即可

@Configuration
public class DirectExchangeConfig {
    public static final String DIRECTEXCHANGE_NAME="directexchange01";
    public static final String DIRECT_QUEUE_NAME01="directqueue01";
    public static final String DIRECT_QUEUE_NAME02="directqueue02";

    @Bean
    DirectExchange directExchange(){
        //第一个参数:交换机的名字;第二个参数,是否持久化,持久化的话,下次登录的时候不会消失;第三个参数:当没有连接到队列的时候,是否要自动的清除交换机
        return new DirectExchange(DIRECTEXCHANGE_NAME,true,false);
    }

    @Bean
    Queue queue01(){
        //第一个参数:队列的名字;第二个参数,是否持久化,持久化的话,下次登录的时候不会消失;第三个参数,是排他,和springmvc时一样,如果为true那么就会只能是自己消费,不能让别的服务消费;第四个参数:当没有连接到消费者的时候,是否要自动的清除队列
        return new Queue(DIRECT_QUEUE_NAME01,true,false,false);
    }

    @Bean
    Queue queue02(){
        //第一个参数:队列的名字;第二个参数,是否持久化,持久化的话,下次登录的时候不会消失;第三个参数,是排他,和springmvc时一样;第四个参数:当没有连接到消费者的时候,是否要自动的清除队列
        return new Queue(DIRECT_QUEUE_NAME02,true,false,false);
    }

    /**
     * 将直连交换机和队列连接起来
     queue01()方法定义的消息队列绑定到directExchange()方法定义的交换机上
     with()写的是你的routingkey
     * @return
     */
    @Bean
    Binding binding01(){

        return BindingBuilder.bind(queue01())
                .to(directExchange())
                //直连交换机需要一个路由key,来寻找对应的队列
                .with(DIRECT_QUEUE_NAME01);
    }

    @Bean
    Binding binding02(){
        return BindingBuilder.bind(queue02())
                .to(directExchange())
                //路由是队列的名字
                .withQueueName();
    }
}

step02-消费者

@Component
public class DirectConsumerHandle {

    @RabbitListener(queues=DirectExchangeConfig.DIRECT_QUEUE_NAME01)
    public void handle01(String msg){
        System.out.println("handle01 = " + msg);
    }

    @RabbitListener(queues = DirectExchangeConfig.DIRECT_QUEUE_NAME02)
    public void handle02(String msg){
        System.out.println("handle02 = " + msg);
    }
}

通过 @RabbitListener 注解指定一个方法是一个消息消费方法,方法参数就是所接收到的消息。

step03-生产者

    @Test
    void test01(){
        rabbitTemplate1.convertAndSend(DirectExchangeConfig.DIRECTEXCHANGE_NAME,DirectExchangeConfig.DIRECT_QUEUE_NAME01,"欧克");
    }

//第二个参数是routingkey

2.Fanout(扇形交换机)

前言:FanoutExchange 的数据交换策略是把所有到达 FanoutExchange 的消息转发给所有与它绑定的Queue 上,在这种策略中,routingkey 将不起任何作用,只要消息队列和交换机绑定在一起的,就能够接收到发送的消息

step01-配置类

@Configuration
public class RabbitFanoutConfig {
	public final static String FANOUTNAME = "sang-fanout";
	@Bean
	FanoutExchange fanoutExchange() {
		return new FanoutExchange(FANOUTNAME, true, false);
	}
	@Bean
	Queue queueOne() {
		return new Queue("queue-one");
	}
	@Bean
	Queue queueTwo() {
		return new Queue("queue-two");
	}
	@Bean
	Binding bindingOne() {
		return BindingBuilder.bind(queueOne()).to(fanoutExchange());
	}
	@Bean
	Binding bindingTwo() {
		return BindingBuilder.bind(queueTwo()).to(fanoutExchange());
	}
}

step02-生产者

@RunWith(SpringRunner.class)
@SpringBootTest
public class RabbitmqApplicationTests {
	@Autowired
	RabbitTemplate rabbitTemplate;
	@Test
	public void fanoutTest() {
        //第二个参数是routingkey不需要参数
		rabbitTemplate.convertAndSend(RabbitFanoutConfig.FANOUTNAME,null, "hello 			fanout!");
	}
}

step03-消费者

@Component
public class FanoutReceiver {
    @RabbitListener(queues = "queue-one")
    public void handler1(String message) {
    	System.out.println("FanoutReceiver:handler1:" + message);
    }
    @RabbitListener(queues = "queue-two")
    public void handler2(String message) {
    	System.out.println("FanoutReceiver:handler2:" + message);
    }
}

3.Topic(主题交换机) 

前言:主题交换机类似直连交换机,不同的是,主题交换机的routingkey支持统配

TopicExchange 是比较复杂但是也比较灵活的一种路由策略,在 TopicExchange 中,Queue 通过routingkey 绑定到 TopicExchange 上,当消息到达 TopicExchange 后,TopicExchange 根据消息的 routingkey 将消息路由到一个或者多个 Queue 上

step01-配置类

@Configuration
public class RabbitTopicConfig {
	public final static String TOPICNAME = "sang-topic";
	@Bean
    TopicExchange topicExchange() {
    	return new TopicExchange(TOPICNAME, true, false);
    }
    @Bean
    Queue xiaomi() {
    	return new Queue("xiaomi");
    }
    @Bean
    Queue huawei() {
    	return new Queue("huawei");
    }
    @Bean
    Queue phone() {
    	return new Queue("phone");
    }
    @Bean
    Binding xiaomiBinding() {
    	return BindingBuilder.bind(xiaomi()).to(topicExchange()).with("xiaomi.#");
    }
    @Bean
    Binding huaweiBinding() {
    	return BindingBuilder.bind(huawei()).to(topicExchange()).with("huawei.#");
    }
    @Bean
    Binding phoneBinding() {
    	return BindingBuilder.bind(phone()).to(topicExchange()).with("#.phone.#");
    }
}

step02-消费者

@Component
public class TopicReceiver {
    @RabbitListener(queues = "phone")
    public void handler1(String message) {
    	System.out.println("PhoneReceiver:" + message);
    }
    @RabbitListener(queues = "xiaomi")
    public void handler2(String message) {
    	System.out.println("XiaoMiReceiver:"+message);
    }
    @RabbitListener(queues = "huawei")
    public void handler3(String message) {
    	System.out.println("HuaWeiReceiver:"+message);
    }
}

step03-生产者

@RunWith(SpringRunner.class)
@SpringBootTest
public class RabbitmqApplicationTests {
    @Autowired
    RabbitTemplate rabbitTemplate;
    @Test
    public void topicTest() {
        rabbitTemplate.convertAndSend(RabbitTopicConfig.TOPICNAME,"xiaomi.news","小米新		闻..");
        rabbitTemplate.convertAndSend(RabbitTopicConfig.TOPICNAME,"huawei.news","华为新		闻..");
        rabbitTemplate.convertAndSend(RabbitTopicConfig.TOPICNAME,"xiaomi.phone","小米手		机..");
        rabbitTemplate.convertAndSend(RabbitTopicConfig.TOPICNAME,"huawei.phone","华为手		机..");
        rabbitTemplate.convertAndSend(RabbitTopicConfig.TOPICNAME,"phone.news","手机新			闻..");
    }
}

4.Header(头部交换机)

前言:header是根据消息头来进行接收消息队列的,之前消费者可以通过String msg来接收消息,现在得使用Message对象或者Byte[]数组来进行接收消息,因为是通过字节流

step01-绑定

 step02-消费者

step03-生产者

 

四.rpc(远程过程调用协议)

为了解决两个服务之间的调用,一个服务调用另一个服务的时候,收到另一个服务返回值给另一个服务一个响应

五.消息有效期

正常来说,我们使用rabbitmq的时候,消息是没有过期时间的,我们可以自己设定消息的过期时间,过期的消息也就进入了死信队列

延迟队列=死信队列+过期时间(TTL)

1.给消息设置过期时间

 2.给队列设置过期时间

六.延迟消息队列

 

当我们设定消息有效期的时候,我们可以设置一个延迟消息队列,也就是当普通的消息队列设定时间结束后,会进入到死信队列中去,然后通过死信队列的消费来做业务逻辑。例如:当我们下单的时候,如果三十分钟后不付款就会清理掉,并进入到死信队列中,我们在通过消费者监听死信队列来将该订单取消掉  

配置死信交换机

@Configuration
public class DirectExchangeConfig {
    public static final String MYEXCHANGE_NAME="myexchange01";
    public static final String MY_QUEUE_NAME01="myqueue01";
    public static final String DEADEXCHANGE_NAME="deadexchange02";
    public static final String DEAD_QUEUE_NAME02="deadqueue02";

    @Bean
    DirectExchange myExchange(){
        //第一个参数:交换机的名字;第二个参数,是否持久化,持久化的话,下次登录的时候不会消失;第三个参数:当没有连接到队列的时候,是否要自动的清除交换机
        return new DirectExchange(MYEXCHANGE_NAME,true,false);
    }

    @Bean
    Queue queue01(){
        Map<String, Object> args = new HashMap<>();
        //设置消息过期时间
        args.put("x-message-ttl", 0);
        //设置死信交换机
        args.put("x-dead-letter-exchange", DEADEXCHANGE_NAME);
        //设置死信 routing_key
        args.put("x-dead-letter-routing-key", DEAD_QUEUE_NAME02);
        //第一个参数:队列的名字;第二个参数,是否持久化,持久化的话,下次登录的时候不会消失;第三个参数,是排他,和springmvc时一样,如果为true那么就会只能是自己消费,不能让别的服务消费;第四个参数:当没有连接到消费者的时候,是否要自动的清除队列
        return new Queue(MY_QUEUE_NAME01,true,false,false,args);
    }

     @Bean
    DirectExchange deadExchange(){
        //第一个参数:交换机的名字;第二个参数,是否持久化,持久化的话,下次登录的时候不会消失;第三个参数:当没有连接到队列的时候,是否要自动的清除交换机
        return new DirectExchange(DEADEXCHANGE_NAME,true,false);
    }
    @Bean
    Queue deadqueue02(){
        //第一个参数:队列的名字;第二个参数,是否持久化,持久化的话,下次登录的时候不会消失;第三个参数,是排他,和springmvc时一样;第四个参数:当没有连接到消费者的时候,是否要自动的清除队列
        return new Queue(DEAD_QUEUE_NAME02,true,false,false);
    }

    /**
     * 将直连交换机和队列连接起来
     queue01()方法定义的消息队列绑定到directExchange()方法定义的交换机上
     with()写的是你的routingkey
     * @return
     */
    @Bean
    Binding binding01(){

        return BindingBuilder.bind(queue01())
                .to(myExchange())
                //直连交换机需要一个路由key,来寻找对应的队列
                .with(MY_QUEUE_NAME01);
    }

    @Bean
    Binding binding02(){
        return BindingBuilder.bind(deadqueue02())
                .to(deadExchange())
                //路由是队列的名字
                .withQueueName();
    }
}

七.消息发送的可靠性

1.事务机制

step01-首先需要先提供一个事务管理器,如下:

@Bean
RabbitTransactionManager transactionManager(ConnectionFactory connectionFactory){
	return new RabbitTransactionManager(connectionFactory);
}

 step02-在消息生产者上面做两件事:添加事务注解并设置通信信道为事务模式

@Service
public class MsgService {
    @Autowired
    RabbitTemplate rabbitTemplate;
    @Transactional
    public void send() {
        rabbitTemplate.setChannelTransacted(true);
        rabbitTemplate.convertAndSend(RabbitConfig.JAVABOY_EXCHANGE_NAME,RabbitConfig.J
        AVABOY_QUEUE_NAME,"hello rabbitmq!".getBytes());
        int i = 1 / 0;
    }
}

这里注意两点

\1. 发送消息的方法上添加 @Transactional 注解标记事务。 
\2. 调用 setChannelTransacted 方法设置为 true 开启事务模式。 
这就 OK 了。 

在上面的案例中,我们在结尾来了个 1/0 ,这在运行时必然抛出异常,我们可以尝试运行该方法,发现 
消息并未发送成功。 
当我们开启事务模式之后,RabbitMQ 生产者发送消息会多出四个步骤: 
\1. 客户端发出请求,将信道设置为事务模式。 
\2. 服务端给出回复,同意将信道设置为事务模式。 
\3. 客户端发送消息。 
\4. 客户端提交事务。 
\5. 服务端给出响应,确认事务提交。
上面的步骤,除了第三步是本来就有的,其他几个步骤都是平白无故多出来的。所以大家看到,事务模式其实效率有点低,这并非一个最佳解决方案。我们可以想想,什么项目会用到消息中间件?一般来说都是一些高并发的项目,这个时候并发性能尤为重要。
所以,RabbitMQ 还提供了发送方确认机制(publisher confirm)来确保消息发送成功,这种方式,性
能要远远高于事务模式,一起来看下。

2.发送方确认机制

前言: 1.消息到达交换器的确认回调;2.消息到达队列的回调

step01-首先我们移除刚刚关于事务的代码,然后在 application.properties 中配置开启消息发送方确认机制, 如下:

spring.rabbitmq.publisher-confirm-type=correlated
spring.rabbitmq.publisher-returns=true

第一行是配置消息到达交换器的确认回调,第二行则是配置消息到达队列的回调。 
第一行属性的配置有三个取值:
\1. none:表示禁用发布确认模式,默认即此。 
\2. correlated:表示成功发布消息到交换器后会触发的回调方法。 
\3. simple:类似 correlated,并且支持 waitForConfirms() 和 waitForConfirmsOrDie() 方法的调用。 

step02-接下来我们要开启两个监听,具体配置如下

@Configuration
public class RabbitConfig implements RabbitTemplate.ConfirmCallback,
RabbitTemplate.ReturnsCallback {
    public static final String JAVABOY_EXCHANGE_NAME = "javaboy_exchange_name";
    public static final String JAVABOY_QUEUE_NAME = "javaboy_queue_name";
    private static final Logger logger =LoggerFactory.getLogger(RabbitConfig.class);
    @Autowired
    RabbitTemplate rabbitTemplate;
    @Bean
    Queue queue() {
    	return new Queue(JAVABOY_QUEUE_NAME);
    }
    @Bean
    DirectExchange directExchange() {
    	return new DirectExchange(JAVABOY_EXCHANGE_NAME);
    }
    @Bean
    Binding binding() {
        return BindingBuilder.bind(queue()).to(directExchange())
        .with(JAVABOY_QUEUE_NAME);
    }
    //表示构造方法执行完毕之后,就会自动调用init方法
    @PostConstruct
    public void initRabbitTemplate() {
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnsCallback(this);
    }
    /**
    消息到达交换机的回调
    */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String
    cause) {
        if (ack) {
        	logger.info("{}:消息成功到达交换器",correlationData.getId());
        }else{
        	logger.error("{}:消息发送失败", correlationData.getId());
        }
    }
    @Override
    public void returnedMessage(ReturnedMessage returned) {
        logger.error("{}:消息未成功路由到队列",
        returned.getMessage().getMessageProperties().getMessageId());
    }
}

关于这个配置类,我说如下几点:

 

\1. 定义配置类,实现 RabbitTemplate.ConfirmCallback 和 RabbitTemplate.ReturnsCallback 两个接口,这两个接口,前者的回调用来确定消息到达交换 器,后者则会在消息路由到队列失败时被调用。 

\2. 定义 initRabbitTemplate 方法并添加 @PostConstruct 注解,在该方法中为 rabbitTemplate 分别配置这两个 Callback

3.重试机制

前面所说的事务机制和发送方确认机制,都是发送方确认消息发送成功的办法。如果发送方一开始就连 不上 MQ,那么 Spring Boot 中也有相应的重试机制,但是这个重试机制就和 MQ 本身没有关系了,这是利用 Spring 中的 retry 机制来完成的

spring.rabbitmq.template.retry.enabled=true
spring.rabbitmq.template.retry.initial-interval=1000ms
spring.rabbitmq.template.retry.max-attempts=10
spring.rabbitmq.template.retry.max-interval=10000ms
spring.rabbitmq.template.retry.multiplier=2

开启重试机制。
重试起始间隔时间。
最大重试次数。
最大重试间隔时间。
间隔时间乘数。(这里配置间隔时间乘数为 2,则第一次间隔时间 1 秒,第二次重试间隔时间 2
秒,第三次 4 秒,以此类推)

配置完成后,再次启动 Spring Boot 项目,然后关掉 MQ,此时尝试发送消息,就会发送失败,进而导 致自动重试。

八.消息消费的可靠性

在消费的过程中,抛出异常了,消息会自动重新回到消息队列中,等待下一次被消费。默认情况下,消费方法带有自动确认机制,如果消费成功,该方法会将成功的消费报告给RabbitMQ,如果消费失败,也告诉MQ。这会导致出现一个问题,就是如果前面的业务已经消费了,后面出现异常的没有消费,当重新回到消息队列中去时,会重新进行全部消费,我们这是应该使用消息的幂等性+手动确认机制

1.自动确认机制(默认情况下)

2.手动确认机制

2.1推模式手动确认
要开启手动确认,需要我们首先关闭自动确认,关闭方式如下
spring.rabbitmq.listener.simple.acknowledge-mode=manual

接下来我们来看下消费者中的代码

@RabbitListener(queues = RabbitConfig.JAVABOY_QUEUE_NAME)
public void handle3(Message message,Channel channel) {
    //获取消息的唯一标识符
    long deliveryTag = message.getMessageProperties().getDeliveryTag();
    try {
        byte[] body=message.getBody();
    //消息消费的代码写到这里
        String s = new String(body,0,body.length);
        System.out.println("s = " + s);
        //消费完成后,手动 ack,告诉rabbitmq消息消费成功
        //第一个参数是消息的唯一标识符
        //第二个参数是否批处理,如果设置韦true,则表示所有处理unacked的消息都设置为消费成功,false只管自己
        channel.basicAck(deliveryTag, false);
    } catch (Exception e) {
        //手动 nack
        try {
            //告诉rabbitmq消费失败
            //第三个参数是指是否重新进入队列中
        	channel.basicNack(deliveryTag, false, true);
        } catch (IOException ex) {
        	ex.printStackTrace();
        }
    }
}

将消费者要做的事情放到一个 try..catch 代码块中。 如果消息正常消费成功,则执行 basicAck 完成确认。 如果消息消费失败,则执行 basicNack 方法,告诉 RabbitMQ 消息消费失败。

这里涉及到两个方法:

basicAck:这个是手动确认消息已经成功消费,该方法有两个参数:第一个参数表示消息的唯一标记id; 第二个参数表示是否开启批处理, multiple 如果为 false,表示仅确认当前消息消费成功,如果为 true,则表示当前消息之前所有未被当前消费者确认的消息都消费成功。 

basicNack:这个是告诉 RabbitMQ 当前消息未被成功消费,该方法有三个参数:第一个参数表示消息的唯一标识id;第二个参数multiple 如果为 false,表示仅确认当前消息消费失败,如果为 true,则表示当前消息之前所有未被当前消费者确认的消息都消费失败;第三个参数表示消费失败的消息是否重新回到队列中,等待下一次消费,如果为false,则该消息会进入到死信队列

2.2拉模式手动确认

方法一:可以使用事务的方式

方法二:拉模式手动 ack 比较麻烦一些,在 Spring 中封装的 RabbitTemplate 中并未找到对应的方法,所以我们得用原生的办法

public void receive2() {
    //创建一个带事务的通道
    Channel channel =
    rabbitTemplate.getConnectionFactory().createConnection().createChannel(false);
    long deliveryTag = 0L;
    try {
        //第二个参数表示:是否自动ACK
        GetResponse getResponse =
        channel.basicGet(RabbitConfig.JAVABOY_QUEUE_NAME, false);
        //获取消息的唯一标识
        deliveryTag = getResponse.getEnvelope().getDeliveryTag();
        System.out.println("o = " + new String((getResponse.getBody()), "UTF-8"));
        channel.basicAck(deliveryTag, false);
    } catch (IOException e) {
        try {
        	channel.basicNack(deliveryTag, false, true);
        } catch (IOException ex) {
        	ex.printStackTrace();
        }
    }
}

九.MQ集群

1.普通集群

普通集群模式:就是将 RabbitMQ 部署到多台服务器上,每个服务器启动一个 RabbitMQ 实例,多个实例之间进行消息通信。此时我们创建的队列 Queue,它的元数据(主要就是 Queue 的一些配置信息)会在所有的 RabbitMQ 实例中进行同步,但是队列中的消息只会存在于一个 RabbitMQ 实例上,而不会同步到其他队列。 当我们消费消息的时候,如果连接到了另外一个实例,那么那个实例会通过元数据定位到 Queue 所在 的位置,然后访问 Queue 所在的实例,拉取数据过来发送给消费者。

缺点:这种集群可以提高 RabbitMQ 的消息吞吐能力,但是无法保证高可用,因为一旦一个 RabbitMQ 实例挂了,消息就没法访问了,如果消息队列做了持久化,那么等 RabbitMQ 实例恢复后,就可以继续访问了;如果消息队列没做持久化,那么消息就丢了。

 

搭建普通集群

step01-创建三个mq

docker run -d --hostname rabbit01 --name mq01 -p 5671:5672 -p 15671:15672 -e
RABBITMQ_ERLANG_COOKIE="pan_rabbitmq_cookie" rabbitmq:3-management

docker run -d --hostname rabbit02 --name mq02 --link mq01:mylink01 -p 5672:5672 -
p 15672:15672 -e RABBITMQ_ERLANG_COOKIE="pan_rabbitmq_cookie" rabbitmq:3-
management

docker run -d --hostname rabbit03 --name mq03 --link mq01:mylink02 --link
mq02:mylink03 -p 5673:5672 -p 15673:15672 -e
RABBITMQ_ERLANG_COOKIE="pan_rabbitmq_cookie" rabbitmq:3-management

step02-以mq01容器为主,然后进入到其他MQ容器中配置一下

docker exec -it mq02 /bin/bash

rabbitmqctl stop_app
rabbitmqctl join_cluster rabbit@rabbit01
rabbitmqctl start_app

rabbitmqctl cluster_status			查看集群的状态

step03-代码测试

1.接下来我们来简单测试一下这个集群。 我们创建一个名为 mq_cluster_demo 的父工程,然后在其中创建两个子工程。 第一个子工程名为 provider,是一个消息生产者

 

2.配置文件

spring.rabbitmq.addresses=localhost:5671,localhost:5672,localhost:5673    所有的mq的ip
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest

3.测试

@Configuration
public class RabbitConfig {
public static final String MY_QUEUE_NAME = "my_queue_name";
public static final String MY_EXCHANGE_NAME = "my_exchange_name";
public static final String MY_ROUTING_KEY = "my_queue_name";
@Bean
Queue queue() {
return new Queue(MY_QUEUE_NAME, true, false, false);
}
@Bean
DirectExchange directExchange() {
return new DirectExchange(MY_EXCHANGE_NAME, true, false);
}
@Bean
Binding binding() {
return BindingBuilder.bind(queue())
.to(directExchange())

2.镜像集群(和redis的集群类似)

它和普通集群最大的区别在于 Queue 数据和原数据不再是单独存储在一台机器上,而是同时存储在多 台机器上。也就是说每个 RabbitMQ 实例都有一份镜像数据(副本数据)。每次写入消息的时候都会自 动把数据同步到多台实例上去,这样一旦其中一台机器发生故障,其他机器还有一份副本数据可以继续 提供服务,也就实现了高可用。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值