RabbitMQ入门

256 阅读4分钟

概览

  • message queue 消息队列 先进先出的数据结构
  • 解耦
  • 提高可用性
  • 流量削峰
  • 数据分发 服务从MQ中获取数据

优点

  • 解耦
  • 流量削峰
  • 数据分发

缺点

  • 系统复杂性提高了
  • 系统可用性降低了
  • 一致性问题

控制台概览

HelloWord

导入依赖

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

发送者

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

import java.nio.charset.StandardCharsets;

/**
 * @author wangz
 */
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()) {
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            String message = "HelloWorld";
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.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;

import java.nio.charset.StandardCharsets;

public class Recv {

    private final static String QUEUE_NAME = "hello";

    public static void main(String[] argv) throws Exception {
        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(), StandardCharsets.UTF_8);
            System.out.println(" [x] Received '" + message + "'");
        };
        channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {
        });
    }
}

五种中间件模式

生产者消费者的channel属性必须严格一致

直连模式

  • 消费者持续监听队列
  • 生产者将消息放入队列中
  • ==生产者--队列--消费者==直连

生产者

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

import java.nio.charset.StandardCharsets;

/**
 * @author wangz
 */
public class Send {


    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();

        factory.setHost("localhost");
        factory.setPort(5672);
        factory.setVirtualHost("/snail");
        factory.setUsername("name");
        factory.setPassword("pwd");

        Connection connection = factory.newConnection();

        Channel channel = connection.createChannel();

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

        channel.basicPublish("", "hello", null, "hello mq".getBytes(StandardCharsets.UTF_8));
        System.out.println("success");

        channel.close();

        connection.close();

    }
}

消费者

import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * @author wangz
 */
public class Recv {


    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();

        factory.setHost("localhost");
        factory.setPort(5672);
        factory.setVirtualHost("/snail");
        factory.setUsername("user");
        factory.setPassword("pwd");

        Connection connection = factory.newConnection();

        Channel channel = connection.createChannel();

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

        channel.basicConsume("hello", true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println(new String(body));
            }
        });


    }
}

细节

// queue给通道声明队列,当队列不存在时,可以创建相对的队列
// durable 持久化,在磁盘中保存队列,设置为 false 时, 当服务重启时队列会丢失,注意是队列
// exclusive 是否独占队列
// autoDelete 消费完成之后是否删除消息
channel.queueDeclare("hello", false, false, false, null);

work queue

  • 在直连模式中,仅有一个消费者消费. 可能会造成消息的大量堆积. 使用 工作队列 解决这一问题.
  • 默认会使用轮询来解决多个消费者的问题
  • 可以调整使用 ack 机制达到最大的吞吐量
    • 自动ack 设置为false
    • channel.basicQos(5); 设置窗口大小
    • 设置ack机制,累计确认是否开启

生产者

package workqueue;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.MessageProperties;
import utiles.Connection;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * @author wangz
 */
public class Send {
    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = Connection.getChannel();

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

        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            channel.basicPublish("", "hello", MessageProperties.PERSISTENT_TEXT_PLAIN, String.valueOf(i).getBytes(StandardCharsets.UTF_8));

            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(i);

            }
        }
        Connection.close();

    }
}

消费者

package workqueue;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import utiles.Connection;

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

/**
 * @author wangz
 */
public class ConsumerTwo {
    public static void main(String[] args) throws IOException, TimeoutException {
        consumer();
    }

    static void consumer() throws IOException, TimeoutException {
        Channel channel = Connection.getChannel();
        channel.queueDeclare("hello", false, false, false, null);

        channel.basicConsume("hello", true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) {
                System.out.println(this.getClass().getName()+" : "+ new String(body));

            }
        });

    }
}
package workqueue;

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

/**
 * @author wangz
 */
public class ConsumerOne {
    public static void main(String[] args) throws IOException, TimeoutException {
        ConsumerTwo.consumer();

    }
}

Publish/Subscribe

  • 扇出/广播

  • 可以有多个消费者
  • 每个消费者都有自己的工作队列
  • 每个队列都要绑定到exchange 交换机 X
  • 生产者发送消息只能发送到交换机,由交换机决定发送到那个队列
  • 交换机把消息发送给绑定过的所有队列
  • 实现一条消息被多个消费者消费,不同种的消费者

生产者

package fanout;

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

import java.nio.charset.StandardCharsets;


/**
 * @author wangz
 * 将消息发送给交换机
 */
public class Prov {
    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 = "hello exchange";

            // 将消息发送给交换机
            channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes(StandardCharsets.UTF_8));

            System.out.println(" [x] Sent '" + message + "'");
        }
    }
}

消费者

package fanout;

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

import java.nio.charset.StandardCharsets;

/**
 * @author wangz
 */
public class Consumer {
    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");
        //自动创建一个queue兵器额返回他的名称
        String queueName = channel.queueDeclare().getQueue();

        System.out.println(queueName);
        //绑定交换机和队列
        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(), StandardCharsets.UTF_8);
            System.out.println(" [x] Received '" + message + "'");
        };

        channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
        });
    }
}

Routing

  • fanout 模式仅能进行简单广播,不能实现精准的通知

  • 在发送消息时加上一个路由,然后将消息发送给交换机

  • 交换机根据queue持有的 routerKey 分发消息

生产者

// 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, "direct");

String router = "router";
String message = "router test";
// 发送消息时携带路由信息
channel.basicPublish(EXCHANGE_NAME, router, null, message.getBytes(StandardCharsets.UTF_8));
System.out.println(" [x] Sent '" + router + "':'" + message + "'");

消费者

//声明交换机以及类型
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
// 创建临时的 queue 并且获取名称
String queueName = channel.queueDeclare().getQueue();
// 通道绑定交换机、queue ,同时携带 routerKey
channel.queueBind(queueName, EXCHANGE_NAME, "router");

Topic exchange

  • 动态路由

  • 当路由的规则太多时,会有大量的路由信息

  • 使用通配符简化

生产者

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()) {

            // 创建的交换机的类型为  topic
            channel.exchangeDeclare(EXCHANGE_NAME, "topic");

            // 这里的 routerKey与简单的路由中不同
            String routingKey = "aa.bb.cc";
            String message = "dynamic router";

            channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes(StandardCharsets.UTF_8));
            System.out.println(" [x] Sent '" + routingKey + "':'" + message + "'");
        }
    }
    //..
}

消费者

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, "aa.*.*");


    show(channel, queueName);
}

springboot整合

a914af59de501c7794b9dfc89a3ca953