RabbitMQ - 消息中间件

本文介绍了RabbitMQ的基本概念,包括消息队列、交换机、路由等核心组件,并提供了多种应用场景的示例代码,如简单的队列模式、工作模式、发布订阅模式、路由模式和主题模式。
摘要由CSDN通过智能技术生成

概述

  • MQ(Message Queue):用于服务之间进行异步通信的中间件
  • RabbitMQ是基于AMQP(advanced message queue protocol)高级队列协议的消息中间件,由 Erlang 语言开发,因此安装需要erlang环境,因为跟spring有共同的血缘关系,所以spring 全家桶对其的支持应该是相当完善的
  • 一般消息队列都是生产者将消息发送到队列 消费者监听队列进行消费 rabbitmq 一个虚拟主机(默认 /)持有一个或者多个交换机(Exchange) 用户只能在虚拟主机的粒度进行权限控制 交换机根据一定的策略(RoutingKey)绑定(Binding)到队列(Queue)上 这样生产者和队列就没有直接联系 而是将消息发送的交换机 交换机再把消息转发到对应绑定的队列上
  • AMQP高级消息队列协议
    • 定义:Advanced Message Queueing Protocol是面向消息的中间件的开放标准应用层协议,AMQP的特征是消息导向,排队,路由(包括点对点和发布和订阅),可靠性和安全性
    • AMQP要求消息传递提供商和客户端的行为在不同供应商实现可互操作的情况下,以与SMTP,HTTP,FTP等相同的方式创建了可互操作的系统
    • AMQP协议是具有现代特征的二进制协议。一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开发标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同开发语言等条件的限制
    • AMQP是一种二进制应用层协议,旨在有效地支持各种消息应用和通信模式
  • AMQP核心概念
    • Server:又称作Broker,用于接受客户端的连接,实现AMQP实体服务;
    • Connection:连接,应用程序与Broker的网络连接
    • Channel:网络信道,几乎所有的操作都在Channel中进行,Channel是进行消息读写的通道。客户端可建立多个Channel,每个Channel代表一个会话任务
    • Message:消息,服务器和应用程序之间传送的数据,有Properties和Body组成。Properties可以对消息进行修饰,比如消息的优先级、延迟等高级特性。Body则是消息体内容,即我们要传输的数据,仅仅创建了客户端到Broker之间的连接后,客户端还是不能发送消息的。需要为每一个Connection创建Channel,AMQP协议规定只有通过Channel才能执行AMQP的命令。一个Connection可以包含多个Channel。之所以需要Channel,是因为TCP连接的建立和释放都是十分昂贵的,如果一个客户端每一个线程都需要与Broker交互,如果每一个线程都建立一个TCP连接,暂且不考虑TCP连接是否浪费,就算操作系统也无法承受每秒建立如此多的TCP连接。RabbitMQ建议客户端线程之间不要共用Channel,至少要保证共用Channel的线程发送消息必须是串行的,但是建议尽量共用Connection。
    • Virtual Host:虚拟地址,是一个逻辑概念,用于进行逻辑隔离,是最上层的消息路由。一个Virtual Host里面可以有若干个Exchange和Queue,同一个Virtual Host里面不能有相同名称的Exchange或者Queue。Virtual Host是权限控制的最小粒度
    • Exchange:交换机,用于接收消息,可根据路由键将消息转发到绑定的队列
    • Binding:Exchange和Queue之间的虚拟连接,Exchange在与多个Message Queue发生Binding后会生成一张路由表,路由表中存储着Message Queue所需消息的限制条件即Binding Key。当Exchange收到Message时会解析其Header得到Routing Key,Exchange根据Routing Key与Exchange Type将Message路由到Message Queue。Binding Key由Consumer在Binding Exchange与Message Queue时指定,而Routing Key由Producer发送Message时指定,两者的匹配方式由Exchange Type决定
    • Routing Key:一个路由规则,虚拟机可用它来确定如何路由一个特定的消息
    • Queue:也称作Message Queue,即消息队列,用于保存消息并将他们转发给消费者

使用

入门案例

  • 示意图
    在这里插入图片描述

  • 消息生产者

public class Send {
	// 定义队列名称
    private final static String QUEUE_NAME = "hello";
    public static void main(String[] argv) throws Exception {
    	// 连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 服务器主机
        factory.setHost("localhost");
        try (
        	// 创建连接
        	Connection connection = factory.newConnection();
        	Channel channel = connection.createChannel()
        ) {
   			// 声明队列
   			// 参数一:队列名称
   			// 参数二durable:持久化
   			// 参数三exclusive:排他性,不允许其他程序消费
   			// 参数四autoDelete:对列没有信息自动删除队列
   			// 参数五argument:配置死信队列
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            // 消息
            String message = "Hello World!";
            // 发布消息
            // 参数一exchange:路由
            // 参数二QUEUE_NAME:队列名称
            // 参数三:路由
            // 参数四:消息
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8"));
            System.out.println(" [x] Sent '" + message + "'");
        }
    }
}
  • 消息消费者
public class Recv {
	//队列的名称
    private final static String QUEUE_NAME = "hello";
    public static void main(String[] argv) throws Exception {
    	// 连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 设置ip地址
        factory.setHost("localhost");
        // 获取连接对象
        Connection connection = factory.newConnection();
        // 获取通道
        Channel channel = connection.createChannel();
		//声明一个队列  必须与send方 参数一致
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(" [x] Received '" + message + "'");
        };
        // 通道连接消费者部分
        // 参数二autoAck:自动应答
        // 参数三:消费后的函数
        channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
    }
}

相关参数

  • durable
    • RabbitMQ默认将消息存储在内存中,若RabbitMQ宕机,那么整个队列会丢失,可以把这个属性设置成true,表示这个队列需要做持久化
    • durable属性只是声明队列是持久化的,RabbitMQ宕机或者重启之后,队列依然存在,但是里面的消息没有持久化,也会丢失所以需要针对消息也做持久化
    • channel.basicPublish(“”,QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,msg.getBytes());
  • exclusive
    • 如果想创建一个只有自己可见的队列,即不允许其它用户访问,RabbitMQ允许你将一个Queue声明成为排他性的(Exclusive Queue)
    • 特点
      • 只对首次声明它的连接(Connection)可见
      • 会在其连接断开的时候自动删除
  • autoDelete:当所有消费客户端连接断开后,是否自动删除队列 true:删除false:不删除
  • 消息持久化:channel.basicPublish(“”,QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(“UTF-8”));

消息签收机制

  • 当消息一旦被消费者接收,队列中的消息就会被删除,RabbitMQ通过消息确认机制(Acknowlege)来确认消息被接收,当消费者获取消息后,会向RabbitMQ发送回执ACK,告知消息已经被接收。不过这种回执ACK分两种情况:
    • 自动ACK:消息一旦被接收,消费者自动发送ACK
    • 手动ACK:消息接收后,不会发送ACK,需要手动调用
  • 自动签收
    • channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
  • 手动签收
// 消息消费者
public class Recv {
	//队列的名称
    private final static String QUEUE_NAME = "hello";
    public static void main(String[] argv) throws Exception {
    	// 连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 设置ip地址
        factory.setHost("localhost");
        // 获取连接对象
        Connection connection = factory.newConnection();
        // 获取通道
        Channel channel = connection.createChannel();
		//声明一个队列  必须与send方 参数一致
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        DeliverCallback deliverCallback =new DeliverCallback(){
           @Override
            public void handle(String consumerTag, Delivery delivery) throws IOException {
               try{
                   // 发生异常
                   System.out.println(1/0);
                   String message = new String(delivery.getBody(), "UTF-8");
                   System.out.println(" [x] Received '" + message + "'");
                   // 进行应答
                   // 参数一:消息的id
                   // 参数二:是否进行批量处理
                   channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
               }catch (Exception e){
                   e.printStackTrace();
                   int num=0;
                   if(num<=0){
                   		// 参数三:是否将消息放回消息队列
                   		channel.basicNack(delivery.getEnvelope().getDeliveryTag(),false,false);
                   }else{
                   		channel.basicNack(delivery.getEnvelope().getDeliveryTag(),false,true);
                   }
               }
            }
        };
        // 通道连接消费者部分
        // 参数二autoAck:false开启手动签收
        channel.basicConsume(QUEUE_NAME, false, deliverCallback, consumerTag -> { });
    }
}

Worker模式

  • 示意图
    在这里插入图片描述
  • 消息生产者
public class NewTask {
    private static final String TASK_QUEUE_NAME = "task_queue";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try (Connection connection = factory.newConnection();
            Channel channel = connection.createChannel()) {
            channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);

            String message = "tasequeue";
            //发送批量消息
            for(int i=0;i<20;i++){
                channel.basicPublish("", TASK_QUEUE_NAME,
                        MessageProperties.PERSISTENT_TEXT_PLAIN,
                        (message+i).getBytes("UTF-8"));
            }

            System.out.println(" [x] Sent '" + message + "'");
        }
    }
}
  • 消息消费者
public class Worker {
    private static final String TASK_QUEUE_NAME = "task_queue";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        final Connection connection = factory.newConnection();
        final Channel channel = connection.createChannel();

        channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
		// 默认生产者将消息平均分配给消费者
        // 消费者能力不一致则需开启channel.basicQos(1);则每个消费者每次从队列中获取一个
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
            }
            System.out.println(" [x] Received '" + message + "'");
        };
        //此模式必须手动ACK
        channel.basicConsume(TASK_QUEUE_NAME, false, deliverCallback, consumerTag -> { });
    }
}

Pub/Sub模式

  • 示意图
    在这里插入图片描述
    • 通过路由器x分发给队列,队列可以重复接受路由信息
  • 消息生产者
public class EmitLog {
	// 路由名
    private static final String EXCHANGE_NAME = "logs";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try (
        	Connection connection = factory.newConnection();
            Channel channel = connection.createChannel()
        ) {
            // 广播模式
            channel.exchangeDeclare(EXCHANGE_NAME, "fanout");

            String message = "info: Hello World!";
			// 参数1 路由器名
            channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8"));
            System.out.println(" [x] Sent '" + message + "'");
        }
    }
}
  • 消息消费者
public class ReceiveLogs {
    private static final String EXCHANGE_NAME = "logs";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
		
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
        // 生成随机路由,具有排他性
        String queueName = channel.queueDeclare().getQueue();
        channel.queueBind(queueName, EXCHANGE_NAME, "");

        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(" [x] Received '" + message + "'");
        };
        channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { });
    }
}

Routing模式

  • 示意图
    在这里插入图片描述
    • 路由与队列约定key,队列只接收约定key的信息
  • 消息生产者
public class EmitLogDirect {
    private static final String EXCHANGE_NAME = "direct_logs";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
			// key
            String severity = "info";
            String message = "directMsg";

            channel.basicPublish(EXCHANGE_NAME, severity, null, message.getBytes("UTF-8"));
            System.out.println(" [x] Sent '" + severity + "':'" + message + "'");
        }
    }
}
  • 消息消费者
public class ReceiveLogsDirect {
    private static final String EXCHANGE_NAME = "direct_logs";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        String queueName = channel.queueDeclare().getQueue();
		// 接收key
        channel.queueBind(queueName, EXCHANGE_NAME, "info");
        channel.queueBind(queueName, EXCHANGE_NAME, "error");
        channel.queueBind(queueName, EXCHANGE_NAME, "warning");
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(" [x] Received '" + delivery.getEnvelope().getRoutingKey() + "':'" + message + "'");
        };
        channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
        });
    }
}

Topic模式

  • 示意图
    在这里插入图片描述
    • 通配符标注约定的key
  • 消息生产者
public class EmitLogTopic {
    private static final String EXCHANGE_NAME = "topic_logs";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {

            channel.exchangeDeclare(EXCHANGE_NAME, "topic");

            String routingKey = "order1.save";
            String message = "topicMsg";

            channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes("UTF-8"));
            System.out.println(" [x] Sent '" + routingKey + "':'" + message + "'");
        }
    }
}
  • 消息消费者
public class ReceiveLogsTopic {
    private static final String EXCHANGE_NAME = "topic_logs";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(EXCHANGE_NAME, "topic");
        String queueName = channel.queueDeclare().getQueue();

        channel.queueBind(queueName, EXCHANGE_NAME, "order.*");

        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(" [x] Received '" + delivery.getEnvelope().getRoutingKey() + "':'" + message + "'");
        };
        channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { });
    }
}

SpringBoot整合RabbitMQ

简单Queue模式

  • 消息生产者
@Controller
public class QueueController {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @RequestMapping("/queue")
    @ResponseBody
    public String sendMsg(String msg){
        rabbitTemplate.convertAndSend("","boot_queue",msg);
        return "发送成功";
    }
}
  • 消息消费者
@Component
public class QueueListener {
    @RabbitListener(queuesToDeclare = @Queue("boot_queue"))
    public void receiveMsg(String msg, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, Channel channel){
        System.out.println("收到消息:"+msg);
    }
}

Worker模式

  • 签收模式配置
spring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.rabbitmq.listener.simple.prefetch=1
  • 消息生产者
@Controller
public class WorkerController {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @RequestMapping("/worker")
    @ResponseBody
    public String sendMsg(){
        for(int i= 0;i<20;i++){
            rabbitTemplate.convertAndSend("","boot_worker","msg:"+i);
        }
        return "发送成功";
    }
}
  • 消息消费者
@Component
public class Worker {
    @RabbitListener(queuesToDeclare = @Queue("boot_worker"))
    public void receiveMsg(String msg, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, Channel channel) throws IOException {
        System.out.println("工作者1:"+msg);
        channel.basicAck(deliveryTag,false);
    }
}

Pub/Sub模式

  • 消息生产者
@Controller
public class PubSubController {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @RequestMapping("/pubsub")
    @ResponseBody
    public String sendMsg(String msg){
        rabbitTemplate.convertAndSend("boot_pubsub","","广播消息");
        return "发送成功";
    }
}
  • 消息消费者
@Component
public class PubSubReceiver {
    @RabbitListener(bindings = @QueueBinding(value = @Queue,exchange = @Exchange(name = "boot_pubsub",type = "fanout")))
    public void receiveMsg(String msg, @Header(AmqpHeaders.DELIVERY_TAG)long deliveryTag, Channel channel) throws Exception {
        System.out.println("收到消息1:"+msg);
        channel.basicAck(deliveryTag,false);
    }
}

Routing模式

  • 消息生产者
@Controller
public class RountingController {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @RequestMapping("/rounting")
    @ResponseBody
    public String sendMsg(String key){
        rabbitTemplate.convertAndSend("boot_rounting_exchange",key,"rounting消息");
        return "发送成功";
    }
}
  • 消息消费者
@Component
public class RountingReceiver {
    @RabbitListener(bindings = @QueueBinding(value = @Queue(name = "boot_rounting_queue01"),
            exchange = @Exchange(name = "boot_rounting_exchange",type = "direct"),
            key = {"error","info"}
    ))
    public void receiveMsg(String msg, @Header(AmqpHeaders.DELIVERY_TAG)long deliveryTag, Channel channel) throws Exception {
        System.out.println("error&info 收到消息:"+msg);
        channel.basicAck(deliveryTag,false);
    }
}

Topic模式

  • 消息生产者
@Controller
public class TopicController {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @RequestMapping("/topic")
    @ResponseBody
    public String sendMsg(String key){
        rabbitTemplate.convertAndSend("boot_topic_exchange",key,"topic消息");
        return "发送成功";
    }
}
  • 消息消费者
@Component
public class TopicReceiver01 {
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue("boot_topic_queue01"),
            exchange = @Exchange(name = "boot_topic_exchange",type = "topic"),
            key = "order.*"
    ))
    public void receiveMsg(String msg, @Header(AmqpHeaders.DELIVERY_TAG)long deliveryTag, Channel channel) throws Exception {
        System.out.println("topic收取消息:"+msg);
        channel.basicAck(deliveryTag,false);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值