SpringBoot學習整合使用RabbitMQ

SpringBoot集成使用RabbitMQ

學習RabbitMQ

介紹

MQ全稱爲Message Queue,即消息隊列, RabbitMQ是由erlang語言開發,基於AMQP(Advanced Message
Queue 高級消息隊列協議)協議實現的消息隊列,它是一種應用程序之間的通信方法,消息隊列在分佈式系統開
發中應用非常廣泛。RabbitMQ官方地址:http://www.rabbitmq.com/
開發中消息隊列通常有如下應用場景:
1、任務異步處理。
將不需要同步處理的並且耗時長的操作由消息隊列通知消息接收方進行異步處理。提高了應用程序的響應時間。
2、應用程序解耦合
MQ相當於一箇中介,生產方通過MQ與消費方交互,它將應用程序進行解耦合。

RabbitMQ 的工作原理

在這裏插入圖片描述

組成部分說明如下:

  • Broker :消息隊列服務進程,此進程包括兩個部分:Exchange和Queue。
  • Exchange :消息隊列交換機,按一定的規則將消息路由轉發到某個隊列,對消息進行過慮
  • Queue :消息隊列,存儲消息的隊列,消息到達隊列並轉發給指定的消費方。
  • Producer :消息生產者,即生產方客戶端,生產方客戶端將消息發送到MQ。
  • Consumer :消息消費者,即消費方客戶端,接收MQ轉發的消息。

下載安裝

RabbitMQ的下載地址:下載地址

說明:RabbitMq需要先安裝Erlang

Erlang下載地址
別的下載安裝都可以參考官網

工作模式

RabbitMQ有以下幾種工作模式 :
1、Work queues
2、Publish/Subscribe
3、Routing
4、Topics
5、Header
6、RPC

Work queues 可以參考官網給的demo案列

work queues與入門程序相比,多了一個消費端,兩個消費端共同消費同一個隊列中的消息。
應用場景:對於 任務過重或任務較多情況使用工作隊列可以提高任務處理的速度

測試:
1、使用入門程序,啓動多個消費者。
2、生產者發送多個消息。
結果:
1、一條消息只會被一個消費者接收;
2、rabbit採用輪詢的方式將消息是平均發送給消費者的;
3、消費者在處理完某條消息後,纔會收到下一條消息。

Publish/subscribe

工作模式

發佈訂閱模式:
1、每個消費者監聽自己的隊列。
2、生產者將消息發給broker,由交換機將消息轉發到綁定此交換機的每個隊列,每個綁定交換機的隊列都將接收
到消息

代碼
  • 生產者代碼
 package com.xuecheng.test.rabbitmq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer02_publish {
	//隊列名稱
	private static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
    private static final String QUEUE_INFORM_SMS = "queue_inform_sms";
    private static final String EXCHANGE_FANOUT_INFORM="exchange_fanout_inform";
    public static void main(String[] args) {
        Connection connection = null;
        Channel channel = null;
        try {
            //創建一個與MQ的連接
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("127.0.0.1");
            factory.setPort(5672);
            factory.setUsername("guest");
            factory.setPassword("guest");
            factory.setVirtualHost("/");//rabbitmq默認虛擬機名稱爲“/”,虛擬機相當於一個獨立的mq服務//創建一個連接
            connection = factory.newConnection();
            //創建與交換機的通道,每個通道代表一個會話
            channel = connection.createChannel();
            //聲明交換機 String exchange, BuiltinExchangeType type
            /**
             * 參數明細
             * 1、交換機名稱
             * 2、交換機類型,fanout、topic、direct、headers
             */
            channel.exchangeDeclare(EXCHANGE_FANOUT_INFORM, BuiltinExchangeType.FANOUT);
            //聲明隊列
//           (String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String,
Object> arguments)
            /**
             * 參數明細:
             * 1、隊列名稱
             * 2、是否持久化
             * 3、是否獨佔此隊列
             * 4、隊列不用是否自動刪除
             * 5、參數
             */
            channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
            channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null);
            //交換機和隊列綁定String queue, String exchange, String routingKey
            /**
             * 參數明細
             * 1、隊列名稱
             * 2、交換機名稱
             * 3、路由key
             */
            channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_FANOUT_INFORM,"");
            channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_FANOUT_INFORM,"");
            //發送消息
            for (int i=0;i<10;i++){
                String message = "inform to user"+i;
                //向交換機發送消息 String exchange, String routingKey, BasicProperties props,
                byte[] body
                /**
                 * 參數明細
                 * 1、交換機名稱,不指令使用默認交換機名稱 Default Exchange
                 * 2、routingKey(路由key),根據key名稱將消息轉發到具體的隊列,這裏		       填寫隊列名稱表示消
息將發到此隊列
                 * 3、消息屬性
                 * 4、消息內容
                 */
                channel.basicPublish(EXCHANGE_FANOUT_INFORM, "", null, message.getBytes());
                System.out.println("Send Message is:'" + message + "'");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }finally{
            if(channel!=null){
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }
            }
            if(connection!=null){
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
  • 郵件發送消費者
public class Consumer02_subscribe_email {
    //隊列名稱
    private static final String QUEUE_INFORM_EMAIL = "inform_queue_email";
    private static final String EXCHANGE_FANOUT_INFORM="inform_exchange_fanout";
    public static void main(String[] args) throws IOException, TimeoutException {
        //創建一個與MQ的連接
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setPort(5672);
        factory.setUsername("guest");
        factory.setPassword("guest");
        factory.setVirtualHost("/");//rabbitmq默認虛擬機名稱爲“/”,虛擬機相當於一個獨立的mq服務器
        //創建一個連接
        Connection connection = factory.newConnection();
        //創建與交換機的通道,每個通道代表一個會話
        Channel channel = connection.createChannel();
        //聲明交換機 String exchange, BuiltinExchangeType type
        /**
         * 參數明細
         * 1、交換機名稱
         * 2、交換機類型,fanout、topic、direct、headers
         */
        channel.exchangeDeclare(EXCHANGE_FANOUT_INFORM, BuiltinExchangeType.FANOUT);
        //聲明隊列
//            channel.queueDeclare(String queue, boolean durable, boolean exclusive, boolean
autoDelete, Map<String, Object> arguments)
        /**
         * 參數明細:
         * 1、隊列名稱
         * 2、是否持久化
         * 3、是否獨佔此隊列
         * 4、隊列不用是否自動刪除
         * 5、參數
         */
        channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
        //交換機和隊列綁定String queue, String exchange, String routingKey
        /**
         * 參數明細
         * 1、隊列名稱
         * 2、交換機名稱
         * 3、路由key
         */
        channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_FANOUT_INFORM,"");
        //定義消費方法
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
                long deliveryTag = envelope.getDeliveryTag();
                String exchange = envelope.getExchange();
                //消息內容
                String message = new String(body, "utf‐8");
                System.out.println(message);
            }
        };
        /**
         * 監聽隊列String queue, boolean autoAck,Consumer callback
         * 參數明細
         * 1、隊列名稱
         * 2、是否自動回覆,設置爲true爲表示消息接收到自動向mq回覆接收到了,mq接收到回覆會刪除消息,設置
爲false則需要手動回覆
         * 3、消費消息的方法,消費者接收到消息後調用此方法
         */
        channel.basicConsume(QUEUE_INFORM_EMAIL, true, defaultConsumer);
    }
}

Routing(路由模式)

工作模式

路由模式:
1、每個消費者監聽自己的隊列,並且設置routingkey。
2、生產者將消息發給交換機,由交換機根據routingkey來轉發消息到指定的隊列。

代碼
  • 生產者代碼
package com.xuecheng.test.rabbitmq;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer03_routing {
    //隊列名稱
    private static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
    private static final String QUEUE_INFORM_SMS = "queue_inform_sms";
    private static final String EXCHANGE_ROUTING_INFORM="exchange_routing_inform";
    public static void main(String[] args) {
        Connection connection = null;
        Channel channel = null;
        try {
            //創建一個與MQ的連接
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("127.0.0.1");
            factory.setPort(5672);
            factory.setUsername("guest");
            factory.setPassword("guest");
            factory.setVirtualHost("/");//rabbitmq默認虛擬機名稱爲“/”,虛擬機相當於一個獨立的mq服務//創建一個連接
            connection = factory.newConnection();
            //創建與交換機的通道,每個通道代表一個會話
            channel = connection.createChannel();
            //聲明交換機 String exchange, BuiltinExchangeType type
            /**
             * 參數明細
             * 1、交換機名稱
             * 2、交換機類型,fanout、topic、direct、headers
             */
            channel.exchangeDeclare(EXCHANGE_ROUTING_INFORM, BuiltinExchangeType.DIRECT);
            //聲明隊列
//            channel.queueDeclare(String queue, boolean durable, boolean exclusive, boolean
autoDelete, Map<String, Object> arguments)
            /**
             * 參數明細:
             * 1、隊列名稱
             * 2、是否持久化
             * 3、是否獨佔此隊列
             * 4、隊列不用是否自動刪除
             * 5、參數
             */
            channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
            channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null);
            //交換機和隊列綁定String queue, String exchange, String routingKey
            /**
             * 參數明細
             * 1、隊列名稱
             * 2、交換機名稱
             * 3、路由key
             */
            channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_ROUTING_INFORM,QUEUE_INFORM_EMAIL);
            channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_ROUTING_INFORM,QUEUE_INFORM_SMS);
            //發送郵件消息
            for (int i=0;i<10;i++){
                String message = "email inform to user"+i;
                //向交換機發送消息 String exchange, String routingKey, BasicProperties props,
byte[] body
                /**
                 * 參數明細
                 * 1、交換機名稱,不指令使用默認交換機名稱 Default Exchange
                 * 2、routingKey(路由key),根據key名稱將消息轉發到具體的隊列,這裏填寫隊列名稱表示消
息將發到此隊列
                 * 3、消息屬性
                 * 4、消息內容
                 */
                channel.basicPublish(EXCHANGE_ROUTING_INFORM, QUEUE_INFORM_EMAIL, null,
                message.getBytes());
                System.out.println("Send Message is:'" + message + "'");
            }
            //發送短信消息
            for (int i=0;i<10;i++){
                String message = "sms inform to user"+i;
                //向交換機發送消息 String exchange, String routingKey, BasicProperties props,
byte[] body
                channel.basicPublish(EXCHANGE_ROUTING_INFORM, QUEUE_INFORM_SMS, null,
message.getBytes());
                System.out.println("Send Message is:'" + message + "'");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }finally{
            if(channel!=null){
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }
            }
            if(connection!=null){
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
  • 消費者代碼
public class Consumer03_routing_email {
    //隊列名稱
    private static final String QUEUE_INFORM_EMAIL = "inform_queue_email";
    private static final String EXCHANGE_ROUTING_INFORM="inform_exchange_routing";
    public static void main(String[] args) throws IOException, TimeoutException {
        //創建一個與MQ的連接
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setPort(5672);
        factory.setUsername("guest");
        factory.setPassword("guest");
        factory.setVirtualHost("/");//rabbitmq默認虛擬機名稱爲“/”,虛擬機相當於一個獨立的mq服務器
        //創建一個連接
        Connection connection = factory.newConnection();
        //創建與交換機的通道,每個通道代表一個會話
        Channel channel = connection.createChannel();
        //聲明交換機 String exchange, BuiltinExchangeType type
        /**
         * 參數明細
         * 1、交換機名稱
         * 2、交換機類型,fanout、topic、direct、headers
         */
        channel.exchangeDeclare(EXCHANGE_ROUTING_INFORM, BuiltinExchangeType.DIRECT);
        //聲明隊列
//            channel.queueDeclare(String queue, boolean durable, boolean exclusive, boolean
autoDelete, Map<String, Object> arguments)
        /**
         * 參數明細:
         * 1、隊列名稱
         * 2、是否持久化
         * 3、是否獨佔此隊列
         * 4、隊列不用是否自動刪除
         * 5、參數
         */
        channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
        //交換機和隊列綁定String queue, String exchange, String routingKey
        /**
         * 參數明細
         * 1、隊列名稱
         * 2、交換機名稱
         * 3、路由key
         */
        channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_ROUTING_INFORM,QUEUE_INFORM_EMAIL);
        //定義消費方法
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
            AMQP.BasicProperties properties, byte[] body) throws IOException {
                long deliveryTag = envelope.getDeliveryTag();
                String exchange = envelope.getExchange();
                //消息內容
                String message = new String(body, "utf‐8");
                System.out.println(message);
            }
        };
        /**
         * 監聽隊列String queue, boolean autoAck,Consumer callback
         * 參數明細
         * 1、隊列名稱
         * 2、是否自動回覆,設置爲true爲表示消息接收到自動向mq回覆接收到了,mq接收到回覆會刪除消息,設置
爲false則需要手動回覆
         * 3、消費消息的方法,消費者接收到消息後調用此方法
         */
        channel.basicConsume(QUEUE_INFORM_EMAIL, true, defaultConsumer);
    }
}

**

別的模式就不一一介紹了,接下來我們看看中作當中使用Springboot怎麼使用RabbitMq

**

SpringBoot整合RabbitMq

搭建SpringBoot環境

  • 添加的pom.xml文件依賴如下
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring‐boot‐starter‐amqp</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring‐boot‐starter‐test</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring‐boot‐starter‐logging</artifactId>
</dependency>

### application.yml配置

server:
  port: 44000
spring:
  application:
    name: test‐rabbitmq‐producer
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtualHost: /

配置

定義RabbitConfig類,配置Exchange、Queue、及綁定交換機。本例配置Topic交換機。


import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author Administrator
 * @version 1.0
 * @create 2018-06-17 20:45
 **/
@Configuration
public class RabbitmqConfig {
    public static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
    public static final String QUEUE_INFORM_SMS = "queue_inform_sms";
    public static final String EXCHANGE_TOPICS_INFORM="exchange_topics_inform";
    public static final String ROUTINGKEY_EMAIL="inform.#.email.#";
    public static final String ROUTINGKEY_SMS="inform.#.sms.#";

    //聲明交換機
    @Bean(EXCHANGE_TOPICS_INFORM)
    public Exchange EXCHANGE_TOPICS_INFORM(){
        //durable(true) 持久化,mq重啓之後交換機還在
        return ExchangeBuilder.topicExchange(EXCHANGE_TOPICS_INFORM).durable(true).build();
    }

    //聲明QUEUE_INFORM_EMAIL隊列
    @Bean(QUEUE_INFORM_EMAIL)
    public Queue QUEUE_INFORM_EMAIL(){
        return new Queue(QUEUE_INFORM_EMAIL);
    }
    //聲明QUEUE_INFORM_SMS隊列
    @Bean(QUEUE_INFORM_SMS)
    public Queue QUEUE_INFORM_SMS(){
        return new Queue(QUEUE_INFORM_SMS);
    }

    //ROUTINGKEY_EMAIL隊列綁定交換機,指定routingKey
    @Bean
    public Binding BINDING_QUEUE_INFORM_EMAIL(@Qualifier(QUEUE_INFORM_EMAIL) Queue queue,
                                              @Qualifier(EXCHANGE_TOPICS_INFORM) Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(ROUTINGKEY_EMAIL).noargs();
    }
    //ROUTINGKEY_SMS隊列綁定交換機,指定routingKey
    @Bean
    public Binding BINDING_ROUTINGKEY_SMS(@Qualifier(QUEUE_INFORM_SMS) Queue queue,
                                              @Qualifier(EXCHANGE_TOPICS_INFORM) Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(ROUTINGKEY_SMS).noargs();
    }


}

生產端代碼

import com.alibaba.fastjson.JSON;
import com.xuecheng.test.rabbitmq.config.RabbitmqConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.HashMap;
import java.util.Map;

/**
 * @program: xcEduService01
 * @description: 整合測試生產端
 * @author: Chai.duolai
 * @create: 2020-03-16 17:47
 **/
@SpringBootTest
@RunWith(SpringRunner.class)
public class Producer05_topics_springboot {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Test
    public void testSendEmail(){
        
        String message = "send email message to user";
        /**
         * 參數:
         * 1、交換機名稱
         * 2、routingKey
         * 3、消息內容
         */
//        rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE_TOPICS_INFORM,"inform.email",message);
        rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE_TOPICS_INFORM,"inform.email",message);
    }
    
    //使用rabbitTemplate發送消息
    @Test
    public void testSendPostPage(){
        
        Map message = new HashMap<>();
        message.put("pageId","5a795ac7dd573c04508f3a56");
        //將消息對象轉成json串
        String messageString = JSON.toJSONString(message);
        //路由key,就是站點ID
        String routingKey = "5a751fab6abb5044e0d19ea1";
        /**
         * 參數:
         * 1、交換機名稱
         * 2、routingKey
         * 3、消息內容
         */
        rabbitTemplate.convertAndSend("ex_routing_cms_postpage",routingKey,messageString);
        
    }
    
}

消費端

創建消費端工程,添加依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring‐boot‐starter‐amqp</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring‐boot‐starter‐test</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring‐boot‐starter‐logging</artifactId>
</dependency>

使用@RabbitListener註解監聽隊列。

import com.rabbitmq.client.Channel;
import com.xuecheng.test.rabbitmq.config.RabbitmqConfig;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class ReceiveHandler {
    //監聽email隊列
    @RabbitListener(queues = {RabbitmqConfig.QUEUE_INFORM_EMAIL})
    public void receive_email(String msg,Message message,Channel channel){
        System.out.println(msg);
    }
    //監聽sms隊列
    @RabbitListener(queues = {RabbitmqConfig.QUEUE_INFORM_SMS})
    public void receive_sms(String msg,Message message,Channel channel){
        System.out.println(msg);
    }
}

這是我在學習和工作當中使用RabbitMq的經驗,如有不足,歡迎交流

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章