消息队列--RabbitMQ使用案例

245 阅读4分钟

1. 使用前准备

1.1 添加依赖

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

1.2 配置

  • 为虚拟主机添加用户
  • 为用户设置权限

2. 使用

2.1 第一种模型(直连)

一个生产者一个消费者

P:生产者,发送消息的程序
C:消费者,消息接收者,会一直等到消息的到来
quene:消息队列

代码示例:

  • 生产者
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);//此处注意的是连接用的端口15672,通信端口5672
        connectionFactory.setUsername("ems");//用户名
        connectionFactory.setPassword("ems");//密码
        connectionFactory.setVirtualHost("/ems");//虚拟主机
        Connection connection = connectionFactory.newConnection();//连接
        //创建通道
        Channel channel = connection.createChannel();
        //参数1: 是否持久化  参数2:是否独占队列 参数3:是否自动删除  参数4:其他属性
        channel.queueDeclare("hello", true, false, false, null);
        channel.basicPublish("", "hello", null, "hello rabbitmq".getBytes());
        channel.close();
        connection.close();
  • 消费者 注意:使用测试类不支持多线程
 public static void main(String[] args) throws IOException, TimeoutException {
//        创建连接工程
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("ems");
        connectionFactory.setPassword("ems");
        connectionFactory.setVirtualHost("/ems");
//        创建链接
        Connection connection = connectionFactory.newConnection();
//        创建通道
        Channel channel = connection.createChannel();
//        通道绑定队列->和生产者参数要一样
        channel.queueDeclare("hello", true, false, false, null);
        /**
         *消费消息
         * 参数1.队列名
         * 参数2.开始消息的自动确认机制
         * 参数3.消息的回调接口
         */
        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("body = " + new Stringbody(body));
            }
        });
//        不建议关闭,处于监听状态,消息过来方便消费
//        channel.close();
//        connection.close();
    }
  • 以上代码可以封装成工具类
public class RabbitMQUtils {
    private static ConnectionFactory connectionFactory;
//类加载时候执行,只执行一次
    static {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/ems");
        connectionFactory.setUsername("ems");
        connectionFactory.setPassword("ems");
    }

    /**
     * 提供连接
     */
    public static Connection getConnection() {
        try {
            return connectionFactory.newConnection();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 关闭连接
     * 关闭通道
     */
    public static void closeConnection(Channel channel, Connection connection) {
        try {
            if (channel != null) {
                channel.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.1.1 持久化

2.1.1.1 队列持久化

  • 生产者
//第二个参数队列持久化 
channel.queueDeclare("hello", true, false, false, null);
  • 消费者 生产者的参数和消费者的参数保持一致
channel.queueDeclare("hello", true, false, false, null);

D:标识持久化

2.1.1.2 消息持久化

        /**
         * 参数1:交换机名称
         * 参数2:队列名称
         * 参数3:消息持久化MessageProperties.PERSISTENT_TEXT_PLAIN
         * 参数4:发送消息的内容
         */
 channel.basicPublish("", "hello", MessageProperties.PERSISTENT_TEXT_PLAIN, "hello rabbitmq".getBytes());

2.2 第二种模型(任务模型:work quene)

生产者的生产速度大于消费者的速度,则使用任务模型,一个生产者多个消费者。

2.2.1 默认消费者平均消费

消费者消费消息的数量和速度没有关系,消费者平均消费

  • 生产者
public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare("work", true, false, false, null);
        //发布消息
        for (int i = 0; i < 10; i++) {
            channel.basicPublish("", "work", null, (i + "hello").getBytes());
        }
        RabbitMQUtils.closeConnection(channel,connection);
    }
  • 消费者1
public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare("work", true, false, false, null);
        channel.basicConsume("work", true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者-1 " + new String(body));
            }
        });
    }
  • 消费者2
public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare("work", true, false, false, null);
        channel.basicConsume("work", true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者-2 " + new String(body));
            }
        });
    }

2.2.2 消息确认机制和能者多劳

自动确认机制:RatherMQ会将队列中的消息平均分配给消费者,然后消费者去消费,和消费时间没有关系,这样宕机会存在消息的丢失。

消费者一方的设置消息确认机制

//参数2:消息自动确认机制 true;一般使用不自动确认
channel.basicConsume("work", true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者-2 " + new String(body));
            }
        });

2.2.2.1 实现能者多劳

生产者不变,消费者变为:

public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare("work", true, false, false, null);
        //保证每次只过来一个消息
        channel.basicQos(1);
        //参数2:消息自动确认机制
        channel.basicConsume("work", false, new DefaultConsumer(channel) {
            @SneakyThrows
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                Thread.sleep(2000);
                System.out.println("消费者-2 " + new String(body));
                /**
                 * 手动确认
                 * 参数1:手动确认消息标志
                 * 参数2:fasle每次确认1个消息,true:多个
                 */
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        });
    }

2.3 第三种模式(fanout广播)

注意:生产者的每条消息消费者都能消费,而不是分配的

  • 生产者
public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        /**
         * 管道声明指定交换机
         * 参数1:交换机名
         * 参数2:交换机类型;fanout:广播
         */
        channel.exchangeDeclare("logs", "fanout");
        /**
         * 发送消息
         * 参数1:交换机名
         * 参数2:路由key
         * 参数3:消息持久化
         * 参数4:消息内容
         */
        channel.basicPublish("logs", "", null, "fanout messsage".getBytes());
        RabbitMQUtils.closeConnection(channel, connection);
    }
  • 消费者
public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        //通道绑定交换机
        channel.exchangeDeclare("logs", "fanout");
        //临时队列
        String queueName = channel.queueDeclare().getQueue();
        /**
         * 绑定交换机队列
         * 参数1:队列名
         * 参数2:交换机名
         * 参数3: 路由key
         */
        channel.queueBind(queueName, "logs", "");
        //消费消息
        channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者1-->" + new String(body));
            }
        });
    }

2.4 第四种模式(Routing路由)

2.4.1 订阅模式->Direct

解释

  1. 队列与交换机的绑定不是任意绑定的,需要指定一个路由key(RoutingKey)
  2. 消息的发送方向交换机发送消息时也需要绑定RoutingKey
  3. 交换机不再把消息交给每个队列,而是根据RoutingKey判断,只有RoutingKey一样才会受到消息 说明:
  4. 交换机类型为Direct
  5. error、info为路由key,消费者1只能接收到error的消息;消费者2能接收到error、info的消息
  • 生产者
public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare("logs_direct", "direct");
        //发送消息
        String routingKey = "info";
        channel.basicPublish("logs_direct", routingKey, null, "这是路由模式下的订阅模式".getBytes());
        RabbitMQUtils.closeConnection(channel,connection);
    }
  • 消费者1
public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare("logs_direct", "direct");
        //创建一个临时队列
        String queue = channel.queueDeclare().getQueue();
        //绑定路由key
        channel.queueBind(queue, "logs_direct", "error");
        //消费者消费消息
        channel.basicConsume(queue,true,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者1接受的消息-->>>" + new String(body));
            }
        });
    }
  • 消费者2
public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare("logs_direct", "direct");
        //创建一个临时队列
        String queue = channel.queueDeclare().getQueue();
        //绑定路由key
        channel.queueBind(queue, "logs_direct", "error");
        channel.queueBind(queue, "logs_direct", "info");
        //消费者消费消息
        channel.basicConsume(queue,true,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者2接受的消息-->>>" + new String(body));
            }
        });
    }

2.4.2 订阅模式->Topoc

动态路由

  • 使用通配符 *、#:
  1. *:代表一个
  2. #:代表多个。
  • 生产者
 public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        //管道绑定交换机
        channel.exchangeDeclare("topics", "topic");
        //发布消息
        String routingKey = "user.save";
        channel.basicPublish("topics", routingKey, null, ("动态路由消息+routingKey:" + routingKey).getBytes());
        RabbitMQUtils.closeConnection(channel, connection);
    }
  • 消费者1
public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare("topics", "topic");
        //创建临时队列
        String queue = channel.queueDeclare().getQueue();
        //绑定交换机和队列,动态通配符形式绑定routingKey
        channel.queueBind(queue, "topics", "user.*");

        channel.basicConsume(queue,true,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者1:"+new String(body));
            }
        });
    }
  • 消费者2
public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare("topics", "topic");
        //创建临时队列
        String queue = channel.queueDeclare().getQueue();
        //绑定交换机和队列,动态通配符形式绑定routingKey
        channel.queueBind(queue, "topics", "user.#");

        channel.basicConsume(queue,true,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者2->user.#:"+new String(body));
            }
        });
    }