RabbitMQ消息中间件(二)

·  阅读 74

消息生产与消费案例

image.png

  1. 获取连接工厂:ConnectionFactory
  2. 获取一个连接:Connection
  3. 通过连接创建数据通信信道,用于发送和接收消息:Channel
  4. 在Broker上需要具体的消息存储队列:Queue
  5. 需要生产者和消费者:Producer & Consumer

消费端代码:

public class Consumer {

    private static final String QUEUE_NAME = "test01";
    public static void main(String[] args) throws IOException, TimeoutException {
        // 1. 创建连接工厂并配置
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.58.129");
        connectionFactory.setPort(5672);
        // 设置虚拟机
        connectionFactory.setVirtualHost("/test");
        // 2. 通过连接工厂建立连接
        Connection connection = connectionFactory.newConnection();

        // 3. 通过connection创建Channel
        Channel channel = connection.createChannel();

        // 4. 声明队列 (queue, durable, exclusive, autoDelete, args)
        channel.queueDeclare(QUEUE_NAME, true, false, false, null);

        // 5. 创建消费者
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            /**
             * 获取消息 (监听到有消息时调用)
             * @param consumerTag 消费者标签, 
             在监听队列时可以设置autoAck为false,即手动确认(避免消息的丢失),
              消息唯一性处理
             * @param envelope 信封
             * @param properties 消息的属性
             * @param body 消息的内容
             * @throws IOException
             */
            @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("Received message : " + msg);
            }
        };

        // 6. 设置Channel, 监听队列(String queue, boolean autoAck,Consumer callback)
        channel.basicConsume(QUEUE_NAME, true, defaultConsumer);
    }
}
复制代码

几个参数解释

  • queue:队列名称
  • durable:持久化,true 即使服务重启也不会被删除
  • exclusive:独占,true 队列只能使用一个连接,连接断开队列删除
  • autoDelete:自动删除,true 脱离了Exchange(连接断开),即队列没有Exchange关联时,自动删除
  • arguments:扩展参数
  • autoAck:是否自动签收(回执)

生产端代码:

public class Producer {

    private static final String QUEUE_NAME = "test01";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 1. 创建连接工厂并配置
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.58.129");
        connectionFactory.setPort(5672);
        // 设置虚拟机
        connectionFactory.setVirtualHost("/test");
        // 2. 通过连接工厂建立连接
        Connection connection = connectionFactory.newConnection();

        // 3. 通过connection创建Channel
        Channel channel = connection.createChannel();

        // 4. 通过Channel发送数据 (exchange, routingKey, props, body)
        // 不指定Exchange时, 交换机默认是AMQP default, 此时就看RoutingKey,
        // RoutingKey要等于队列名才能被路由, 否则消息会被删除
        for (int i = 0; i < 5; i++) {
            String msg = "Learn For RabbitMQ-" + i;
            channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
            System.out.println("Send message : " + msg);
        }
        // 5.关闭连接
        channel.close();
        connection.close();
    }
}
复制代码

不指定Exchange时,交换机默认是AMQP default,此时就看RoutingKeyRoutingKey要等于队列名才能被路由,否则消息会被删除。 image.png

Exchange交换机

交换机概念

用于接收消息,并根据路由键转发消息所绑定的队列。 image.png

蓝色框:客户端发送消息至交换机,通过路由键路由至指定的队列。
黄色框:交换机和队列通过路由键有一个绑定的关系。
绿色框:消费端通过监听队列来接收消息。

交换机属性

  • Type:交换机类型—— directtopicfanoutheaderssharding
  • Durability:是否需要持久化,true为持久化
  • Auto Delete:当最后一个绑定到Exchange上的队列删除后,即Exchange上没有队列绑定,自动删除该Exhcange
  • Internal:当前Exchange是否用于RabbitMQ内部使用,大多数使用默认False
  • Arguments:扩展参数,用于扩展AMQP协议定制化使用

交换机类型

需要声明一个交换机(指定交换机类型),声明一个队列,建立它们的绑定关系(设置RoutingKey)。

Direct Exchange

直连的方式。所有发送到Direct Exchange的消息被转发到RoutingKey中指定的Queueimage.png

队列绑定时的RoutingKey要与生产者中发送时指定的RoutingKey一致。

// Consumer
 // 声明交换机: 
 // (String exchange, String type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object) arguments)
 channel.exchangeDeclare("exchangeName", BuiltinExchangeType.DIRECT, true, false, false, null);

 // 声明队列 (String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object) args)
 channel.queueDeclare("queueName", true, false, false, null);
 
 // 建立绑定关系:
 channel.queueBind("queueName", "exchangeName", "routingKey");
 // ===================================================================
 // Producer
 // 发送消息 (String exchange, String routingKey, BasicProperties props, Bytes[] body)
 channel.basicPublish("exchangeName", "routingKey", null, "msg".getBytes());
复制代码

Topic Exchange

有一定路由规则的。所有发送到Topic Exchange的消息被转发到所有关心RoutingKey中的指定TopicQueue上。

ExchangeRoutingKey和某个Topic进行模糊匹配(可使用通配符:"#" 可多词,"*" 一个词),此时队列需要绑定一个Topicimage.png

发送消息时指定topicroutingKey 队列中的key可以进行模糊匹配

 // Consumer
 // 声明交换机: 
 // (String exchange, String type, boolean durable, boolean autoDelete,
 // boolean internal, Map<String, Object) arguments)
 channel.exchangeDeclare("exchangeName", BuiltinExchangeType.TOPIC, true, false, false, null);

 // 声明队列 (String queue, boolean durable, boolean exclusive, 
 //boolean autoDelete, Map<String, Object) args)
 channel.queueDeclare("queueName", true, false, false, null);
 
 // 建立绑定关系:
 channel.queueBind("queueName", "exchangeName", "routingKey.#");
 // ===================================================================
 // Producer
 // 发送消息 (String exchange, String routingKey, BasicProperties props, Bytes[] body)
 channel.basicPublish("exchangeName", "routingKey.hi", null, "msg".getBytes());
 channel.basicPublish("exchangeName", "routingKey.save", null, "msg".getBytes());
 channel.basicPublish("exchangeName", "routingKey.save.hi", null, "msg".getBytes());
复制代码

因为使用了模糊匹配的"#",可以匹配到发送的三条消息。因此可以收到三条消息。

Fanout Exchange

不处理路由键。只需简单将队列绑定到交换上。 发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。 Fanout Exchange转发消息速度最快。 image.png

不需要RoutingKey,只要交换机和队列有一个绑定关系,消息就可以转发。

 // Consumer
 // 声明交换机: 
 // (String exchange, String type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object) arguments)
 channel.exchangeDeclare("exchangeName", BuiltinExchangeType.FANOUT, true, false, false, null);

 // 声明队列 (String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object) args)
 channel.queueDeclare("queueName", true, false, false, null);
 
 // 建立绑定关系: 
 //(不设置routingKey, 这里为空)
 channel.queueBind("queueName", "exchangeName", "");
 // ===================================================================
 // Producer
 // 发送消息 (String exchange, String routingKey, BasicProperties props, Bytes[] body)
 // 同样routingKey为空 (也可以是任意字符串, 因为fanout并不依据routingKey)
 channel.basicPublish("exchangeName", "", null, "msg".getBytes());
复制代码

Headers Exchange(不常用)

Headers模式取消Routingkey,使用headers中的 key/value(键值对)匹配队列。

工作模式

工作队列模式(Work queues

image.png

应用场景

对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。

测试

  1. 使用入门程序,启动多个消费者。
  2. 生产者发送多个消息。

结果

  1. 一条消息只会被一个消费者接收;
  2. rabbitmq采用轮询的方式将消息是平均发送给消费者的;
  3. 消费者在处理完某条消息后,才会收到下一条消息。

发布订阅模式(Publish/Subscribe

image.png

  1. 每个消费者监听自己的队列。
  2. 生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息

对应交换机中的fanout类型

路由模式(Routing

image.png

  1. 每个消费者监听自己的队列,并且设置routingkey
  2. 生产者将消息发给交换机,由交换机根据routingkey来转发消息到指定的队列。

对应交换机中的direct类型

通配符模式(Topics

image.png 对应交换机中的topics类型

转发器模式(Header

对应交换机中的header类型

远程过程调用模式(RPC

image.png RPC即客户端远程调用服务端的方法,使用MQ可以实现RPC的异步调用,基于Direct交换机实现,流程如下:

  1. 客户端即是生产者就是消费者,向RPC请求队列发送RPC调用消息,同时监听RPC响应队列。
  2. 服务端监听RPC请求队列的消息,收到消息后执行服务端的方法,得到方法返回的结果。
  3. 服务端将RPC方法的结果发送到RPC响应队列。
  4. 客户端(RPC调用方)监听RPC响应队列,接收到RPC调用结果。

其它概念

Binding绑定:

ExchangeQueue之间的连接关系。 Binding中可包含RoutingKey或者参数。

Queue消息队列:

消息队列,实际存储消息数据。 Durability: 是否持久化。Durable:是,Transient:否。 Auto deletetrue 最后一个监听被移除后,该Queue会自动被删除。

Message消息:

服务器和应用程序之间传送的数据。 本质上是一段数据,由PropertiesPayload(Body)组成。

常用属性:delivery modeheaders(自定义属性)。

其他属性:

content_type、content_encoding、priority(消息的优先级 0-9)。 correlation_id、reply_to、expiration、message_id timestamp、type、user_id、app_id、cluster_id

// Producer
// 自定义属性
Map<String, Object> headerMap = new HashMap<>();
headerMap.put("prop1", "11");
headerMap.put("prop2", "22");

// 2: 持久化投递
AMQP.BasicProperties basicProperties = new AMQP.BasicProperties.Builder()
            .deliveryMode(2)
            .contentEncoding("UTF-8")
            .expiration("10000")
            .headers(headerMap)
            .build();

//=========================

 // Cosumer
 // 创建消费者
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
    /**
     * 获取消息 (监听到有消息时调用)
     * @param consumerTag 消费者标签, 
     在监听队列时可以设置autoAck为false,即手动确认(避免消息的丢失),
      消息唯一性处理
     * @param envelope 信封
     * @param properties 消息的属性
     * @param body 消息的内容
     * @throws IOException
     */
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope,
     AMQP.BasicProperties properties, byte[] body) throws IOException {
        String msg = new String(body, "utf-8");
        // properties中可以根据消息的属性获取相关信息。
        String contentType = properties.getContentType();
        Map<String, Object> headers = properties.getHeaders();
        Object prop1 = headers.get("prop1");

        System.out.println("Received message : " + msg);
    }
};
复制代码

Virtual Host虚拟主机:

虚拟地址,用于逻辑隔离,最上层的消息路由。 相当于Redis中的16个DB。

一个Virtual Host可以有若干个ExchangeQueue
同一个Virtual Host中不可以有相同名称的ExchangeQueue

分类:
后端
标签: