🐳 一文学会 消息中间件 《RabbitMQ》

1,983 阅读8分钟

一、什么是 RabbitMQ

  • RabbitMQ是一个开源的消息代理软件,也被称为面向消息的中间件。它实现了高级消息队列协议(AMQP),并能接受、存储和转发消息。RabbitMQ服务器是用Erlang语言编写的,而Erlang是以高性能、健壮以及可伸缩性出名的。

  • RabbitMQ就像一个特别靠谱的邮递员,帮助你在不同的地方传递消息。比如你在一个地方写了封信(消息),RabbitMQ就能帮你确保这封信能够准确、可靠地送到收信人的手里。而且,它还能处理很多信,不会因为信太多就忙不过来。所以,RabbitMQ就是一个帮你传递消息的好帮手,让你不用担心消息会丢失或者送错地方。

二、安装RabbitMQ : (docker安装)

2.1 下载镜像

docker pull rabbitmq:3.13-management

2.2 docker启动RabbitMQ

docker run -d \
--name rabbitmq \
-p 5672:5672 \
-p 15672:15672 \
-v rabbitmq-plugin:/plugins \
-e RABBITMQ_DEFAULT_USER=guest \
-e RABBITMQ_DEFAULT_PASS=123456 \
rabbitmq:3.13-management

image.png

2.3 浏览器输入ip:15672

image.png

image.png

三、 RabbitMQ 的应用模式

  1. 异步处理:提升系统响应速度与吞吐量。

  2. 应用解耦:降低系统模块间的耦合度,提高可维护性。

  3. 流量削峰:缓解突发流量对系统的压力。

  4. 数据同步:实现分布式系统间的数据一致性与同步。

四、 RabbitMQ 的工作模式

4.1 简单模式(Simple)

  • 在简单模式下,消息生产者(P)将消息发送到消息队列(queue),消息消费者(C)从队列中获取并消费消息。
  • 此模式下,我们无需指定交换机,RabbitMQ会通过默认的Direct类型的交换机将消息投递到指定的队列,队列与它绑定时的binding key其实就是队列的名称。

代码实现

① 导入pom文件

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

② 新建工具类 RabbitMQConnectionUtil

public class RabbitMQConnectionUtil {
    //定义RabbitMQ静态字符串
    public static final String RabbitMQ_ADDRESS = "192.168.118.130";
    public static Connection getConnection() throws Exception {
        // 创建连接工厂  
        ConnectionFactory factory = new ConnectionFactory();
        // 设置IP地址值 
        factory.setHost(RabbitMQ_ADDRESS);
        // 设置端口  
        factory.setPort(5672);
        //设置用户名、密码
        factory.setVirtualHost("/");  
        factory.setUsername("guest");  
        factory.setPassword("123456");
        // 通过工程获取连接  
        Connection connection = factory.newConnection();
        return connection;  
    }
}

③新建Producer类(生产者)

public class Producer {
    public static void main(String[] args) throws Exception {
        Connection connection = RabbitMQConnectionUtil.getConnection();
        // 创建频道
        Channel channel = connection.createChannel();
        channel.queueDeclare("simple", true, false, false, null);
        String message = "我是简单模式";
        channel.basicPublish("", "simple", null, message.getBytes());
        System.out.println("生产者发出消息:" + message);
        // 关闭资源  
        channel.close();  
        connection.close();
    }
}

我们来看该工作模式的方法 queueDeclare、basicPublish 参数说明

image.png

image.png

④新建Consumer类(消费者)

public class Consumer {
    public static void main(String[] args) throws Exception {
        Connection connection = RabbitMQConnectionUtil.getConnection();
        // 4. 创建Channel  
        Channel channel = connection.createChannel();
        // 接收消息  
        DefaultConsumer consumer = new DefaultConsumer(channel){
            /**
             * 回调方法 : 当收到消息后,会自动执行该方法
             * @param consumerTag :标识
             * @param envelope :获取一些信息,交换机,路由key...
             * @param properties :配置信息
             * @param body :数据
             * @throws IOException
             */
            @Override  
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("标识:"+consumerTag);
                System.out.println("交换机:"+envelope.getExchange());
                System.out.println("路由:"+envelope.getRoutingKey());
                System.out.println("配置信息:"+properties);
                System.out.println("接收到的消息:"+new String(body));
  
            }  
  
        };
        // 参数1. queue:队列名称  
        // 参数2. autoAck:是否自动确认,类似咱们发短信,发送成功会收到一个确认消息  
        // 参数3. callback:回调对象
        channel.basicConsume("simple",true,consumer);
    }
}

⑤ 运行这两个类

image.png

image.png

4.2 工作队列模式(Work Queues)

  • 工作队列模式旨在实现任务的平均分配,避免单个消费者处理所有消息负载的情况。

  • 在这种模式下,多个消费者可以订阅同一个队列,RabbitMQ会按照平均或公平的方式将消息分发给各个消费者,从而提高系统的处理能力和可扩展性。

① 新建 Producer (生产者)

public class Producer {
    public static final String QUEUE_NAME = "work";
    public static void main(String[] args) throws Exception {
        Connection connection = RabbitMQConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        //循环创建20条消息
        for (int i = 1; i <= 20; i++) {
            String body = i+"工作队列模式:";
            channel.basicPublish("",QUEUE_NAME,null,body.getBytes());
        }
        channel.close();
        connection.close();
    }
}

② 新建ConsumerOne(消费者1)

public class ConsumerOne {
    static final String QUEUE_NAME = "work";
    public static void main(String[] args) throws Exception {
        Connection connection = RabbitMQConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        Consumer consumer = 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));
            }
        };
        channel.basicConsume(QUEUE_NAME,true,consumer);
    }
}

③新建ConsumerTwo(消费者2)

public class ConsumerTwo {
    static final String QUEUE_NAME = "work";
    public static void main(String[] args) throws Exception {
        Connection connection = RabbitMQConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        Consumer consumer = 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));
            }
        };
        channel.basicConsume(QUEUE_NAME,true,consumer);
    }
}

④ 先启动两个消费者,再启动生产者

image.png

4.3 发布/订阅模式(Publish/Subscribe):广播模式

  • 发布/订阅模式允许消息生产者将消息发送到交换机(exchange),而不是直接发送到队列。
  • 多个队列可以绑定到同一个交换机,并以广播的形式接收生产者发布的消息。这样,所有订阅了该交换机的队列都能接收到相同的消息,实现了一对多的消息传递。

① 新建Producer(生产者)

public class Producer {
    public static void main(String[] args) throws Exception {
      // 1、获取连接  
        Connection connection = RabbitMQConnectionUtil.getConnection();
      // 2、创建频道  
        Channel channel = connection.createChannel();
        // 参数1. exchange:交换机名称  
        // 参数2. type:交换机类型  
        //     DIRECT("direct"):定向  
        //     FANOUT("fanout"):扇形(广播),发送消息到每一个与之绑定队列。  
        //     TOPIC("topic"):通配符的方式  
        //     HEADERS("headers"):参数匹配  
        // 参数3. durable:是否持久化  
        // 参数4. autoDelete:自动删除  
        // 参数5. internal:内部使用。一般false  
        // 参数6. arguments:其它参数  
        String exchangeName = "fanout";
        // 3、创建交换机
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT,true,false,false,null);
        // 4、创建队列  
        String queueOneName = "fanout_queueOne";
        String queueTwoName = "fanout_queueTwo";
        channel.queueDeclare(queueOneName,true,false,false,null);
        channel.queueDeclare(queueTwoName,true,false,false,null);
        // 5、绑定队列和交换机  
        // 参数1. queue:队列名称  
       // 参数2. exchange:交换机名称  
       // 参数3. routingKey:路由键,绑定规则  
       //     如果交换机的类型为fanout,routingKey设置为""  
        channel.queueBind(queueOneName,exchangeName,"");
        channel.queueBind(queueTwoName,exchangeName,"");
        String body = "发布订阅模式发出消息";
        // 6、发送消息  
        channel.basicPublish(exchangeName,"",null,body.getBytes());
        // 7、释放资源  
        channel.close();  
        connection.close();
    }  
  
}

② 新建ConsumerOne(消费者1)

public class ConsumerOne {
    public static void main(String[] args) throws Exception {
        Connection connection = RabbitMQConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        String queueOneName = "fanout_queueOne";
        channel.queueDeclare(queueOneName,true,false,false,null);
        Consumer consumer = 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));
            }
        };
        channel.basicConsume(queueOneName,true,consumer);
    }  
  
}

③新建ConsumerTwo(消费者2)

public class ConsumerTwo {
    public static void main(String[] args) throws Exception {  
        Connection connection = RabbitMQConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        String queueTwoName = "fanout_queueTwo";
        channel.queueDeclare(queueTwoName,true,false,false,null);
        Consumer consumer = 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));
            }
        };
        channel.basicConsume(queueTwoName,true,consumer);
    }
}

④ 先启动两个消费者,再启动生产者

image.png

4.4 路由模式(Routing)

  • 路由模式在发布/订阅模式的基础上增加了消息的定向路由功能。

  • 在这种模式下,交换机根据消息的路由键(routing key)来决定将消息发送到哪个队列。只有与路由键匹配的队列才能接收到消息,这提供了更灵活的消息传递控制。

① 新建Producer(生产者)

public class Producer {
    public static void main(String[] args) throws Exception {
      Connection connection = RabbitMQConnectionUtil.getConnection();
      Channel channel = connection.createChannel();
      String exchangeName = "direct";
      // 创建交换机  
      channel.exchangeDeclare(exchangeName,BuiltinExchangeType.DIRECT,true,false,false,null);
      // 创建队列  
      String queueOneName = "direct_queueOne";
      String queueTwoName = "direct_queueTwo";
      // 声明(创建)队列  
      channel.queueDeclare(queueOneName,true,false,false,null);
      channel.queueDeclare(queueTwoName,true,false,false,null);
  
      // 队列绑定交换机  
      // 队列1绑定hello
      channel.queueBind(queueOneName,exchangeName,"hello");
      // 队列2绑定hello、world
      channel.queueBind(queueTwoName,exchangeName,"hello");
      channel.queueBind(queueTwoName,exchangeName,"world");
      String message = "生产者绑定的路由为world";
      // 发送消息
      channel.basicPublish(exchangeName,"world",null,message.getBytes());
      // 释放资源  
     channel.close();
      connection.close();
    }
}

② 新建ConsumerOne(消费者1)

public class ConsumerOne {
    public static void main(String[] args) throws Exception {
        Connection connection = RabbitMQConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        String queueOneName = "direct_queueOne";
        channel.queueDeclare(queueOneName,true,false,false,null);
        Consumer consumer = 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));
            }
        };
        channel.basicConsume(queueOneName,true,consumer);
    }  
  
}

③新建ConsumerTwo(消费者2)

public class ConsumerTwo {
    public static void main(String[] args) throws Exception {  
        Connection connection = RabbitMQConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        String queueTwoName = "direct_queueTwo";
        channel.queueDeclare(queueTwoName,true,false,false,null);
        Consumer consumer = 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));
            }
        };
        channel.basicConsume(queueTwoName,true,consumer);
    }  
  
}

④ 先启动两个消费者,再启动生产者

image.png

4.5 主题模式(Topics)

  • 主题模式是对路由模式的一种扩展,它允许使用通配符来匹配路由键。

  • 通过定义包含通配符的主题(topic),可以实现更复杂的消息路由逻辑。例如,“*.log”可以匹配所有以“.log”结尾的路由键,从而实现对特定类型消息的筛选和分发。

① 新建Producer(生产者)

public class Producer {
    public static void main(String[] args) throws Exception {
        Connection connection = RabbitMQConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        String exchangeName = "topic";
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.TOPIC,true,false,false,null);
        String queueOneName = "topic_queueOne";
        String queueTwoName = "topic_queueTwo";
        channel.queueDeclare(queueOneName,true,false,false,null);
        channel.queueDeclare(queueTwoName,true,false,false,null);
        // 绑定队列和交换机  
       // 参数1. queue:队列名称
       // 参数2. exchange:交换机名称
       // 参数3. routingKey:路由键,绑定规则
        channel.queueBind(queueOneName,exchangeName,"#.word");
        channel.queueBind(queueOneName,exchangeName,"hello.*");
        channel.queueBind(queueTwoName,exchangeName,"*.*");
        String body = "主题模式消息发出";
        //设置routingKey
        channel.basicPublish(exchangeName,"bad.word",null,body.getBytes());
        channel.close();  
        connection.close();
    }
}

② 新建ConsumerOne(消费者1)

public class ConsumerOne {
    public static void main(String[] args) throws Exception {
        Connection connection = RabbitMQConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        String QUEUE_NAME = "topic_queueOne";
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        Consumer consumer = 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));
            }
        };
        channel.basicConsume(QUEUE_NAME,true,consumer);
    }
}

③新建ConsumerTwo(消费者2)

public class ConsumerTwo {
    public static void main(String[] args) throws Exception {
        Connection connection = RabbitMQConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        String QUEUE_NAME = "topic_queueTwo";
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        Consumer consumer = 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));
            }
        };
        channel.basicConsume(QUEUE_NAME,true,consumer);
  
    }  
  
}

④ 先启动两个消费者,再启动生产者

image.png

五、总结

RabbitMQ作为一款开源的消息代理软件,通过实现高级消息队列协议,为分布式系统提供了高效可靠的消息传递解决方案。本文详细介绍了RabbitMQ的基本概念、安装方法以及多种应用模式和工作模式。无论是简单模式、工作队列模式,还是发布/订阅模式、路由模式和主题模式,RabbitMQ都能满足不同的消息传递需求,帮助实现异步处理、应用解耦、流量削峰和数据同步等关键业务目标。总之,RabbitMQ的灵活性和强大功能使其成为现代分布式系统中不可或缺的重要组件,助力企业构建高效稳定的消息通信体系。