RabbitMQ

RabbitMQ

简介

  • 实现了AMQP(高级队列协议)

  • Erlang是面向并发的编程语言,充分利用了CPU的性能,延迟特别低

  • 支持多种语言通讯:Java,Python...都有相应的API

  • 支持海量的插件

安装

  1. vi docker-compose.yml
  2. 把rabbitmq的配置写入文件
  3. vi docker-compose up -d
  4. curl localhost:5672

图形化页面启动

  1. sbin目录下执行 ./rabbitmq-plugins enable rabbitmq_management
  2. 访问15672端口:默认用户名密码 guest

架构:

  • Publisher
  • Consumer
  • virtual host ,默认为“/”
  • Exchange
  • Queue

Publisher与virtual host建立连接,Publisher创建Channel,

通过Channel发送到virtual host的某一个Exchange上.

Exchange通过路由规则发送到某一个或多个Queue中.

Consumer一样,与virtual host建立连接,创建Channel,从队列中消费消息

通讯方式

RabbitMQ提供的7种通讯方式

构建Connection工具类

  • 导入依赖:amqp-client

  • 构建工具类

    import com.rabbitmq.client.Connection;
    import com.rabbitmq.client.ConnectionFactory;
    
    public class RabbitMQConnectionUtil {
        public final static String HOST = "192.168.2.107";
        public final static int PORT= 5672;
        public final static String VITUAL_HOST = "test";
        public final static String USERNAME = "lubanrabbitmq";
        public final static String PASSWORD = "lubanrabbitmq123456";
    
        public static Connection getConnection() throws Exception {
            ConnectionFactory connectionFactory = new ConnectionFactory();
            connectionFactory.setHost(HOST);
            connectionFactory.setPort(PORT);
            connectionFactory.setVirtualHost(VITUAL_HOST);
            connectionFactory.setUsername(USERNAME);
            connectionFactory.setPassword(PASSWORD);
            return  connectionFactory.newConnection();
        }
    }
    

1.Hello World

public class Producer {

    public static void main(String[] args) throws Exception {
        Connection connection = RabbitMQConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare("zyl",false,false,false,null);

        channel.basicPublish("", "zyl", null, "hello".getBytes(StandardCharsets.UTF_8));
        System.out.println("消息发布成功");
        System.in.read();
    }
}

public class Consumer {
    public static void main(String[] args) throws Exception {
        Connection connection = RabbitMQConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare("zyl",false,false,false,null);

        DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("收到消息");
            }
        };
        channel.basicConsume("zyl",true,defaultConsumer);
        System.out.println("开始监听");

        System.in.read();
    }
}

2.Work Queues

一个队列中的消息,只会被一个消费者成功的消费

默认情况下,RabbitMQ的队列会将消息以轮询的方式交给不同的消费者

消费者拿到消息后,需要给生产者一个ack,RabbitMQ认为消费者已经拿到消息了

设置消息的流控,消费者可以尽可能的多消费消息

public class WorkQueueConsumer {


    @Test
    public void test1() throws Exception {
        String queueName = "work";


        Connection connection = RabbitMQConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare(queueName,false,false,false,null);

        channel.basicQos(1);

        DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("Consumer1 收到消息");
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        channel.basicConsume(queueName,true,defaultConsumer);
        System.out.println("Consumer1 开始监听");

        System.in.read();
    }

    @Test
    public void test2() throws Exception {
        String queueName = "work";


        Connection connection = RabbitMQConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare(queueName,false,false,false,null);

        channel.basicQos(1);

        DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("Consumer2 收到消息");
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        channel.basicConsume(queueName,false,defaultConsumer);
        System.out.println("Consumer2 开始监听");

        System.in.read();
    }
}

3.Publish/Subcribe 发布订阅模式

Exchange:FANOUT

如何构建一个自定义的交换机,并指定类型是FANOUT,让交换机和多个Queue绑定在一起

public class PubProducer {

    @Test
    public  void publish() throws Exception {
        String queueName = "pub1";
        String queueName2 = "pub2";
        String exchange = "pubExchange";
        Connection connection = RabbitMQConnectionUtil.getConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(exchange, BuiltinExchangeType.FANOUT.getType());

        channel.queueDeclare(queueName,false,false,false,null);
        channel.queueDeclare(queueName2,false,false,false,null);

        channel.queueBind(queueName, exchange, "");
        channel.queueBind(queueName2, exchange, "");


        for (int i = 0; i < 10; i++) {
            channel.basicPublish(exchange,"" , null, (" WorkQueue "+i).getBytes(StandardCharsets.UTF_8));
        }

        System.out.println("消息发布成功");
        System.in.read();
    }
}

4.Routing

public class RoutingProducer {

    @Test
    public  void publish() throws Exception {
        String queueName = "rou1";
        String queueName2 = "rou2";
        String exchange = "rouExchange";

        Connection connection = RabbitMQConnectionUtil.getConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(exchange, BuiltinExchangeType.DIRECT.getType());

        channel.queueDeclare(queueName,false,false,false,null);
        channel.queueDeclare(queueName2,false,false,false,null);

        channel.queueBind(queueName, exchange, "rou1");
        channel.queueBind(queueName2, exchange, "rou2");

        for (int i = 0; i < 10; i++) {
            channel.basicPublish(exchange,"rou1" , null, (" WorkQueue "+i).getBytes(StandardCharsets.UTF_8));
        }

        for (int i = 0; i < 10; i++) {
            channel.basicPublish(exchange,"rou2" , null, (" WorkQueue "+i).getBytes(StandardCharsets.UTF_8));
        }

        System.out.println("消息发布成功");
        System.in.read();
    }
}

5.Topic

public class TopicProducer {

    @Test
    public  void publish() throws Exception {
        String queueName = "topic1";
        String queueName2 = "topic2";
        String exchange = "topicExchange";

        Connection connection = RabbitMQConnectionUtil.getConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(exchange, BuiltinExchangeType.TOPIC.getType());

        channel.queueDeclare(queueName,false,false,false,null);
        channel.queueDeclare(queueName2,false,false,false,null);

        channel.queueBind(queueName, exchange, "*.A.*");
        channel.queueBind(queueName2, exchange, "*.B.*");
        channel.queueBind(queueName2, exchange, "C.#");

        for (int i = 0; i < 10; i++) {
            channel.basicPublish(exchange,"AAA.A.AAA" , null, (" WorkQueue "+i).getBytes(StandardCharsets.UTF_8));
        }

        for (int i = 0; i < 10; i++) {
            channel.basicPublish(exchange,"BBB.B.BBB" , null, (" WorkQueue "+i).getBytes(StandardCharsets.UTF_8));
        }

        for (int i = 0; i < 10; i++) {
            channel.basicPublish(exchange,"C.CC.CCCC.CC.CCC" , null, (" WorkQueue "+i).getBytes(StandardCharsets.UTF_8));
        }

        System.out.println("消息发布成功");
        System.in.read();
    }
}

6.RPC(了解)

SpringBoot操作RabbitMQ

声明消息

导入依赖

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

配置信息

spring.rabbitmq.host=192.168.2.107
spring.rabbitmq.port=5672
spring.rabbitmq.username=lubanrabbitmq
spring.rabbitmq.password=lubanrabbitmq123456
spring.rabbitmq.virtual-host=test
##设置消费者不自动ack
spring.rabbitmq.listener.direct.acknowledge-mode=manual
##每次消费的数量
spring.rabbitmq.listener.direct.prefetch=10

声明交换机,队列

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MQConfig {

    private final static String EXCHANGE = "bootExchange";
    private final static String QUEUE = "bootQueue";
    private final static String ROUTINGKEY = "bootRoutingkey";


    @Bean
    public Exchange exchange() {
        return ExchangeBuilder.topicExchange(EXCHANGE).build();
    }

    @Bean
    public Queue queue() {
        return QueueBuilder.durable(QUEUE).build();
    }

    @Bean
    public Binding bind(Exchange exchange,Queue queue) {
        return BindingBuilder.bind(queue).to(exchange).with(ROUTINGKEY).noargs();
    }

}

生产消息

import com.example.rabbitmq.config.MQConfig;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class ProducerTest {


    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void publish(){
        rabbitTemplate.send(MQConfig.EXCHANGE,"1.zyl",new Message("hello".getBytes()));
    }

    @Test
    public void publishWithProperties(){
        rabbitTemplate.convertAndSend(MQConfig.EXCHANGE, "1.zyl", new Message("hello".getBytes()), new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().setCorrelationId("234");
                return message;
            }
        });
    }
}

消费消息

@Component
public class ConsumerTest {

    @RabbitListener(queues = MQConfig.QUEUE )
    public void publishWithProperties(String msg, Message message, Channel channel) throws IOException {
        System.out.println(msg);
        String correlationId = message.getMessageProperties().getCorrelationId();
        System.out.println("correlationId  "+correlationId);
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
    }
}

消息的可靠性保证

1.保证消息一定送达到Exchange

Confirm机制

2.保证消息可以路由到Queue

Return机制

保证Exchange的消息一定到Queue

在发送消息时 将basicPublish方法参数中的mandatory设置为true,当消息没有路由到队列中时,就会执行return回调

3.保证Queue可以持久化消息

DeliveryMode设置消息持久化,为2代表持久化,为1代表不持久化

4.保证消费者可以正常消费消息

手动ack

import com.example.rabbitmq.util.RabbitMQConnectionUtil;
import com.rabbitmq.client.*;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.jms.JmsProperties;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class ConfirmProducer {

    @Test
    public  void publish() throws Exception {
        String queueName = "topic1";
        String queueName2 = "topic2";
        String exchange = "topicExchange";

        Connection connection = RabbitMQConnectionUtil.getConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(exchange, BuiltinExchangeType.TOPIC.getType());

        channel.queueDeclare(queueName,true,false,false,null);

        channel.queueDeclare(queueName2,false,false,false,null);

        channel.confirmSelect();
        channel.addConfirmListener(new ConfirmListener() {
            @Override
            public void handleAck(long l, boolean b) throws IOException {
                System.out.println("消息已经到交换机");
            }

            @Override
            public void handleNack(long l, boolean b) throws IOException {
                System.out.println("消息没有到交换机");
            }
        });
        channel.addReturnListener(new ReturnListener() {
            @Override
            public void handleReturn(int i, String s, String s1, String s2, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException {
                System.out.println("消息没有到Queue,可能Queue名不对或其他原因");
            }
        });


        channel.queueBind(queueName, exchange, "*.A.*");
        channel.queueBind(queueName2, exchange, "*.B.*");
        channel.queueBind(queueName2, exchange, "C.#");

        AMQP.BasicProperties basicProperties = new AMQP.BasicProperties();
        //设置持久化
        AMQP.BasicProperties prop = basicProperties.builder().deliveryMode(JmsProperties.DeliveryMode.PERSISTENT.getValue()).build();
        for (int i = 0; i < 10; i++) {
            channel.basicPublish(exchange,"AAA.A.AAA" ,true, prop, (" WorkQueue "+i).getBytes(StandardCharsets.UTF_8));
        }

        for (int i = 0; i < 10; i++) {
            channel.basicPublish(exchange,"LL" ,true, prop, (" WorkQueue "+i).getBytes(StandardCharsets.UTF_8));
        }

        System.out.println("消息发布成功");
        System.in.read();
    }
}

5.SpringBoot保证消息可靠性

Confirm

  • 配置文件开启confirm机制

    spring.rabbitmq.publisher-confirm-type=correlated
    
  • 在发送消息时配置rabbitTemplate

rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if(ack){
            System.out.println("消息已成功发到交换机");
        }else{
            System.out.println("没有发送到交换机 可以尝试重试!!!");
        }
    }
});

Return

  • 配置文件开启return机制
spring.rabbitmq.publisher-returns=true

在发送消息时配置rabbitTemplate

rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
    @Override
    public void returnedMessage(ReturnedMessage returned) {
        String s = new String(returned.getMessage().getBody());
        
        System.out.println("消息"+s+"路由到队列失败,请开启补救措施");
    }
});

消息持久化

rabbitTemplate.convertAndSend(MQConfig.EXCHANGE, "1.zyl", new Message("hello".getBytes()), new MessagePostProcessor() {
    @Override
    public Message postProcessMessage(Message message) throws AmqpException {
        message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);//开启持久化
        return message;
    }
});

RabbitMQ死信队列&延迟交换机

什么是死信队列?

出现的场景

  1. 消息被拒绝(nack或reject),并且requeue设置为false(不放回原来的队列了)
  2. 设置了最长存活时间,在这个最长存活时间到期未被消费
    1. message设置ttl
    2. queue设置ttl,整个队列的消息都有ttl
  3. 超过了队列的最大允许的长度

死信队列的应用

  1. 在队列已满的情况下,消息也不会丢失
  2. 实现延迟消费的效果

实现死信队列

准备Exchange&Queue

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DeadLetterConfig {

    public  static final String NORMAL_EXCHANGE = "normal-exchange";

    public  static final String NORMAL_QUEUE = "normal-queue";

    public  static final String NORMAL_ROUTING_KEY = "normal.#";

    public  static final String DEAD_EXCHANGE = "dead-exchange";

    public  static final String DEAD_QUEUE = "dead-queue";

    public  static final String DEAD_ROUTING_KEY = "dead.#";


    @Bean
    public Exchange normalExchange() {
        return ExchangeBuilder.topicExchange(NORMAL_EXCHANGE).build();
    }

    @Bean
    public Queue normalQueue() {
        return QueueBuilder.durable(NORMAL_QUEUE).deadLetterExchange(DEAD_EXCHANGE).deadLetterRoutingKey("dead.abc").build();
    }

    @Bean
    public Binding normalBind(Exchange normalExchange, Queue normalQueue) {
        return BindingBuilder.bind(normalQueue).to(normalExchange).with(NORMAL_ROUTING_KEY).noargs();
    }

    @Bean
    public Exchange deadExchange() {
        return ExchangeBuilder.topicExchange(DEAD_EXCHANGE).build();
    }

    @Bean
    public Queue deadQueue() {
        return QueueBuilder.durable(DEAD_QUEUE).build();
    }

    @Bean
    public Binding deadBind(Exchange deadExchange, Queue deadQueue) {
        return BindingBuilder.bind(deadQueue).to(deadExchange).with(DEAD_ROUTING_KEY).noargs();
    }

}

实现死信队列

消费者拒绝消息
@Test
public void publishLetter() throws IOException {
    System.out.println("消息发送成功");
    rabbitTemplate.convertAndSend(DeadLetterConfig.NORMAL_EXCHANGE, "normal.11", new Message("hello".getBytes()));
    System.in.read();
}

@RabbitListener(queuesToDeclare = @Queue(DeadLetterConfig.NORMAL_QUEUE)  )
    public void consume(String msg, Message message, Channel channel) throws IOException {
        System.out.println("DeadLetterConfig"+ msg);
        String correlationId = message.getMessageProperties().getCorrelationId();
        channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);
        channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
}
消息的生存时间
  • 给消息设置TTL
@Test
public void publishLetterTTl() throws IOException {
    rabbitTemplate.convertAndSend(DeadLetterConfig.NORMAL_EXCHANGE, "normal.11", new Message("hello".getBytes()), new MessagePostProcessor() {
        @Override
        public Message postProcessMessage(Message message) throws AmqpException {
            message.getMessageProperties().setExpiration("20000");
            return message;
        }
    });
    System.in.read();
}
  • 给队列设置TTL

    @Bean
    public Queue normalQueue() {
        return QueueBuilder.durable(NORMAL_QUEUE)
                .deadLetterExchange(DEAD_EXCHANGE)
                .deadLetterRoutingKey("dead.abc")
                .ttl(10000)
                .build();
    }
    
队列MaxLength
@Bean
public Queue normalQueue() {
    return QueueBuilder.durable(NORMAL_QUEUE)
            .deadLetterExchange(DEAD_EXCHANGE)
            .deadLetterRoutingKey("dead.abc")
            .ttl(10000)
            .maxLength(2) //多了就近死信队列
            .build();
}

延迟交换机

死信队列实现延迟消费时,如果延迟时间比较复杂,比较多.直接使用死信队列需要创建大量的队列设定不同的时间.可以使用延迟交换机来解决这个问题

  • 构建延迟交换机
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;

@Configuration
public class DelayedConfig {

    public  static final String DELAY_EXCHANGE = "delay-exchange";

    public  static final String DELAY_QUEUE = "delay-queue";

    public  static final String DELAY_ROUTING_KEY = "delay.#";


    @Bean
    public Exchange delayExchange() {
        HashMap<String, Object> map = new HashMap<>();
        map.put("x-delayed-type","topic");
        CustomExchange customExchange = new CustomExchange(DELAY_EXCHANGE, "x-delayed-message",true,false,map);
        return customExchange;
    }

    @Bean
    public Queue delayQueue() {
        return QueueBuilder.durable(DELAY_QUEUE).build();
    }

    @Bean
    public Binding normalBind(Exchange delayExchange, Queue delayQueue) {
        return BindingBuilder.bind(delayQueue).to(delayExchange).with(DELAY_ROUTING_KEY).noargs();
    }

}
  • 设置延迟时间

    @Test
    public void delayPublish() throws IOException {
        rabbitTemplate.convertAndSend(DelayedConfig.DELAY_EXCHANGE, "delay.11", new Message("hello".getBytes()), new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().setDelay(10000);//***
                return message;
            }
        });
        System.in.read();
    }
    

RabbitMQ的集群

  • 高可用(避免单点故障问题)
  • 提升MQ的效率

搭建RabbitMQ集群

  • 准备两台虚拟机(克隆)

  • 准备RabbitMQ的yml文件

  • 让RabbitMQ服务实现join操作

    1. rabbitmqctl stop_app
    2. rabbitmqctl reset
    3. rabbitmqctl join_cluster rabbit@rabbitmq1
    4. rabbitmqctl start_app
  • 设置镜像模式

    在指定的rabbitmq中设置好镜像策略即可

RabbitMQ其他内容

Headers类型Exchange

基于key-value方式,让Exchange和Queue绑定在一起,

想比topic更加灵活

x-match = all

x-match = any

@Configuration
public class HeaderConfig {

    public  static final String HEADERS_EXCHANGE = "headers-exchange";

    public  static final String HEADERS_QUEUE = "headers-queue";


    @Bean
    public Exchange headersExchange() {
        return  ExchangeBuilder.headersExchange(HEADERS_EXCHANGE).build();
    }

    @Bean
    public Queue headersQueue() {
        return QueueBuilder.durable(HEADERS_QUEUE).build();
    }

    @Bean
    public Binding normalBind(Exchange headersExchange, Queue headersQueue) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("x-match", "all");
        map.put("name", "jack");
        map.put("age", "12");
        return BindingBuilder.bind(headersQueue).to(headersExchange).with("").and(map);
    }

}

 @Test
 public void headersPublish() throws IOException {
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setHeader("name", "zyl");
        messageProperties.setHeader("age", "13");

        rabbitTemplate.convertAndSend(HeaderConfig.HEADERS_EXCHANGE, "delay.11", new Message("hello".getBytes(),messageProperties));
        System.in.read();
    }
posted @   Acaak  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示