返回顶部

RabbitMQ学习笔记

Mac download RabbitMQ:

brew update
brew install rabbitmq

将会下载rabbitmq的关键依赖:例如erlang和otp。

rabbitmq的脚本和CLI工具安装在/usr/local/Cellar/rabbitmq下的sbin文件夹下,可以通过/usr/local/Cellar/rabbitmq/sbin来访问,以防这个文件夹不在path中,建议添加:

export PATH=$PATH:/usr/local/opt/rabbitmq/sbin

然后可以使用 rabbitmq-server 启动服务器。在Homebrew中,节点和CLI工具将默认使用登录用户帐户。不需要使用sudo。

 

web console地址:http://localhost:15672

username:guest

password:guest

ProtocolBound toPort
amqp 127.0.0.1 5672
clustering :: 25672
http :: 15672
mqtt :: 1883
stomp :: 61613

可以在admin标签中创建用户和virtualhost。

 

一、简单消费队列

 

依赖:

     <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.7.3</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.10</version>
        </dependency>    

 

使用队列前先声明:

            Channel channel = connection.createChannel();

            channel.queueDeclare("test_queue", false, false, false, null);        

生产者发送消息:

        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");//可以再设置端口,用户名密码,virtualhost
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            String message = "Hello World!";
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
            System.out.println(" [x] Sent '" + message + "'");
        }

 

消费者三种方式:

1. while循环

        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        QueueingConsumer consumer = new QueueingConsumer(channel);
        channel.basicConsume("test_queue", true, consumer);

        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String msg = new String(delivery.getBody());
            System.out.println("recv:" + msg);
        }    

2. 重写DefaultConsumer的handleDelivery方法,监听consumer对象:

        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare("test_queue", false, false, false, null);

        DefaultConsumer consumer = 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("new api" + msg);
            }
        };
        channel.basicConsume("test_queue", true, consumer);    

3. 使用回调函数(官方推荐,低版本amqp-client依赖中没有DeliverCallback接口):

        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        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 + "'");
        };
        channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });

 

二、Work queues

 

 

轮询模式:消费者消费的速度快与慢,都会获得相同的消息。(多几个简单消息队列的消费者)

公平分发(fair dispatch):每个消费者发送确认消息之前,不发送下一个消息,一次只处理一个消息。限制发送给同一个消费者不超过一条消息。这样处理消息快的消费者就可以多消费消息。

 

生产者:

channel.basicQos(1);

消费者:

使用basicQos 方法,将参数prefetchCount=1设置进去。这个告诉rabbitmq一次只分发一个消息给一个消费者,并且在消费者处理完成并确认前不要分发新的消息,相反,将消息发送给不是很忙的消费者。

int prefetchCount = 1;
channel.basicQos(prefetchCount);

关于队列大小:

 如果所有消费者都很忙,那么你的队列就会被排满,你可以选择添加新的消费者或者更换其他策略。

 

Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare("test_queue", false, false, false, null);
        channel.basicQos(1);
        DefaultConsumer consumer = 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("new api2:" + msg);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
        //设置自动消息应答ack false
    boolean autoAck = false;
    channel.basicConsume("test_queue", autoAck, consumer);

或者(官方):

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

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");

        System.out.println(" [x] Received '" + message + "'");
        try {
            doWork(message);
        } finally {
            System.out.println(" [x] Done");
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        }
    };
    channel.basicConsume(TASK_QUEUE_NAME, false, deliverCallback, consumerTag -> { });
  }

  private static void doWork(String task) {
    for (char ch : task.toCharArray()) {
        if (ch == '.') {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException _ignored) {
                Thread.currentThread().interrupt();
            }
        }
    }
  }
}

boolean autoAck = true;(自动确认模式)一旦rabbitmq将消息分发给消费者,就会将消息在内存中删除。这种情况杀死正在处理消息的消费者,就会丢失消息。

boolean autoAck = false;(手动确认模式)如果有一个消费者挂掉,就会发送给其他消费者。消费者消费完消息就会告诉rabbitmq消费完成,rabbitmq会将消息在内存中删除。

 

 rabbitmq消息持久化:

首先确保mq不丢失queue,将其声明为durable

            //第二个参数设置为true,为队列持久化
            boolean durable = true;
            channel.queueDeclare("test_queue", durable, false, false, null);        

当队列已经声明的时候,此时修改消息持久化true/false都会失败,rabbitmq不允许重新定义(不同参数)一个已存在的队列。你可以选择删除这个队列或者重新声明一个新的队列。而且生产者和消费者代码要同时更改。

现在确保了rabbitmq重启后queue不会丢失,下面来实现消息的持久化:

通过设置MessageProperties(实现BasicProperties)的值为PERSISTENT_TEXT_PLAIN

import com.rabbitmq.client.MessageProperties;

channel.basicPublish("", "task_queue",
            MessageProperties.PERSISTENT_TEXT_PLAIN,
            message.getBytes());

关于消息持久性的说明
将消息标记为持久性并不能完全保证消息不会丢失。虽然它告诉RabbitMQ将消息保存到磁盘,但是当RabbitMQ接受了一条消息并且还没有保存它时,仍然会有一个短时间窗口。此外,RabbitMQ不会对每条消息都执行fsync(2)——它可能只是保存到缓存中,而不是真正写到磁盘上。持久性保证并不强,但对于我们的简单任务队列来说已经足够了。如果您需要更强的保证,那么您可以使用publisher confirms

 

三、订阅模式: 

 

RabbitMQ消息传递模型的核心思想是,生产者从不直接向队列发送任何消息。实际上,通常生产者甚至不知道消息是否会被传递到任何队列。

相反,生产者只能将消息发送到交换器中,交换器是个十分简单的东西,一方面接收来自生产者的消息另一方面将消息推送给队列,交换器必须十分清楚的了解到接收消息后要做什么,添加到一个特定的队列?添加到很多队列?或者是丢弃消息。这个规则定义在交换器类型中。

有很多交换器类型可以使用:direct,topic,headersfanout,下面用fanout来讲解。

声明交换器:

channel.exchangeDeclare("exchange_name", "fanout");

分发交换非常简单,顾名思义,将所有消息广播到它所知道的队列中。

交换器列表

要列出服务器上的交换器,可以使用rabbitmqctl:

sudo rabbitmqctl list_exchanges

会列出一些 amq.*的交换器和默认的(未命名)的交换器,这些是默认创建的。

未命名的交换器:

在之前的部分中,我们对交换一无所知,但仍然能够将消息发送到队列。这是可能的,因为我们使用的是缺省交换,我们通过空字符串("")来标识它。

channel.basicPublish("", "hello", null, message.getBytes());

第一个参数是交换器的名字,空字符串表示默认或者未命名的交换器,消息将使用routingKey指定的名称(如果存在的话)路由到队列。

 

现在用交换器名字来取代:

channel.basicPublish( "exchange_name", "", null, message.getBytes());

首先,每当我们连接到Rabbit时,我们需要一个新的空队列。为此,我们可以创建一个具有随机名称的队列,或者,更好的方法是让服务器为我们选择一个随机队列名称。

其次,一旦我们断开消费者,队列应该被自动删除。

在Java客户端中,当我们不向queueDeclare()提供参数时,我们创建一个非持久的、排他的自动删除队列,并生成一个名称:

String queueName = channel.queueDeclare().getQueue();

此时queueName包含一个随机到队列名,例如,它可能像:amq.gen-JzTY20BRgKO-HjmUJj0wLg

绑定:

 

我们现在有交换器和队列,现在我们需要告诉交换器将消息发送到队列,这个过程叫做绑定。

channel.queueBind(queueName, "exchang_name", "");

现在交换器将会发送消息给队列。

绑定列表:

你可以列出现有的绑定列表(可以通过--vhost 来指定具体那个vitrualhost):

rabbitmqctl list_bindings

 

  

  1. 一个生产者,多个消费者
  2. 每一个消费者都有自己的队列
  3. 生产者不是将消息发送到队列,而是交换器
  4. 每个队列都要绑定到交换器上
  5. 生产者发送到消息,经过交换器,到达队列,可以实现一个消息被多个消费者消费

eg:注册->email ->otp

 

官方

生产者:

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 = argv.length < 1 ? "info: Hello World!" :
                            String.join(" ", argv);

        channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8"));
        System.out.println(" [x] Sent '" + message + "'");
    }
  }
}

消费者:

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

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 -> { });
  }
}

 

自定义:

生产者:

package com.dz.rabbitmq.ps;

import com.dz.rabbitmq.ConnectionUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

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

public class Sender {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare("test_exchange", "fanout");

        String msg = "publish/subscribe";
        channel.basicPublish("test_exchange","",null,msg.getBytes());
        System.out.println("finish");

        channel.close();
        connection.close();
    }
}

订阅者:

package com.dz.rabbitmq.ps;

import com.dz.rabbitmq.ConnectionUtil;
import com.rabbitmq.client.*;

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

public class Recv1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare("exchange2", false, false, false, null);
        channel.queueBind("exchange2", "test_exchange", "");
        channel.basicQos(1);

        DefaultConsumer consumer = 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("channel 2" + msg);
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };
        channel.basicConsume("exchange2", false, consumer);



    }
}


package com.dz.rabbitmq.ps;

import com.dz.rabbitmq.ConnectionUtil;
import com.rabbitmq.client.*;

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

public class Recv {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        channel.basicQos(1);
        channel.queueDeclare("exchange1", false, false, false, null);
        channel.queueBind("exchange1", "test_exchange", "");

        DefaultConsumer consumer = 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("channel 1" + msg);
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };

        channel.basicConsume("exchange1", false, consumer);


    }
}

 

可以在控制台手动解绑queue或者绑定queue

 

四、路由模式

 

 

之前使用fanout广播给所有消费者,现在我们想将error输出到特定的消费者,而另一个消费者接受全部类型的消息。   

 

Sender:

import com.dz.rabbitmq.ConnectionUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

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

public class Sender {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        //设置交换器路由模式为direct
        channel.exchangeDeclare("test_exchange_direct", "direct");

        String msg = "sender msg";
        //设置当前消息为error
        String routingKey = "error";
        channel.basicPublish("test_exchange_direct", routingKey, null, msg.getBytes());
        System.out.println("send msg");

        channel.close();
        connection.close();
    }
}
sender

 

Recv1:

import com.dz.rabbitmq.ConnectionUtil;
import com.rabbitmq.client.*;

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

public class Recv1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare("error_queue", false, false, false, null);

        //设置路由key为error,将队列和交换器绑定,并设置routingKey
        String routingKey = "error";
        channel.queueBind("error_queue", "test_exchange_direct", routingKey);
        channel.basicQos(1);

        DefaultConsumer consumer = 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("recv1 " + msg);
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };

        boolean autoAck = false;
        channel.basicConsume("error_queue", autoAck, consumer);
    }
}
recv1

 

Recv2:

import com.dz.rabbitmq.ConnectionUtil;
import com.rabbitmq.client.*;

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

public class Recv2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare("normal_queue", false, false, false, null);

        String routingKey1 = "info", routingKey2 = "warning",routingKey3="error";

        channel.queueBind("normal_queue", "test_exchange_direct", routingKey1);
        channel.queueBind("normal_queue", "test_exchange_direct", routingKey2);
        channel.queueBind("normal_queue", "test_exchange_direct", routingKey3);
        channel.basicQos(1);

        DefaultConsumer consumer = 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("recv1 " + msg);
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };

        boolean autoAck = false;
        channel.basicConsume("normal_queue", autoAck, consumer);
    }
}
recv2

 

posted @ 2020-02-23 22:58  _懒惰的猫  阅读(427)  评论(0编辑  收藏  举报