SpringBoot之rabbitmq使用

消息隊列中間件是分佈式系統中重要的組件,主要解決應用耦合,異步消息,流量削鋒等問題實現高性能,高可用,可伸縮和最終一致性架構
使用較多的消息隊列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ。
今天我們將會瞭解到在SpringBoot中使用rabbitmq

實現

1.1 rabbitmq簡介

RabbitMQ是由Erlang語言編寫的實現了高級消息隊列協議(AMQP)的開源消息代理軟件(也可稱爲 面向消息的中間件)。支持Windows、Linux/Unix、MAC OS X操作系統和包括JAVA在內的多種編程語言。

AMQP,即Advanced Message Queuing Protocol,一個提供統一消息服務的應用層標準高級消息隊列協議,是應用層協議的一個開放標準,爲面向消息的中間件設計。基於此協議的客戶端與消息中間件可傳遞消息,並不受 客戶端/中間件 不同產品,不同的開發語言等條件的限制
使用rabbitmq主要三種分發模式

1.1.1 工作隊列模式(Work Queue)

避免立即做一個資源密集型任務,必須等待它完成,而是把這個任務安排到稍後再做。我們將任務封裝爲消息並將其發送給隊列。後臺運行的工作進程將彈出任務並最終執行作業。當有多個worker同時運行時,任務將在它們之間共享。

 

image.png

1.1.2 分發模式(Fanout Exchange)

一個生產者,多個消費者,每一個消費者都有自己的一個隊列,生產者沒有將消息直接發送到隊列,而是發送到了交換機,每個隊列綁定交換機,生產者發送的消息經過交換機,到達隊列,實現一個消息被多個消費者獲取的目的。需要注意的是,如果將消息發送到一個沒有隊列綁定的exchange上面,那麼該消息將會丟失,這是因爲在rabbitMQ中exchange不具備存儲消息的能力,只有隊列具備存儲消息的能力。

 

image.png

 

image.png

1.1.3 通配符模式(Topic Exchange)

這種模式添加了一個路由鍵,生產者發佈消息的時候添加路由鍵,消費者綁定隊列到交換機時添加鍵值,這樣就可以接收到需要接收的消息。
符號“#”匹配一個或多個詞,符號“*”匹配不多不少一個詞

 

image.png

image.png

1.2、安裝rabbitmq

1.2.1 window

因爲rabbitmqerlang實現,所以我們需要先下載安裝erlang,然後再下載rabbitmq

1.2.2 mac

在mac系統中可以直接使用brew安裝,它會幫我們自動安裝管理依賴。

brew update
brew install rabbitmq

這樣,我們就可以使用rabbit-server啓動Rabbit服務了。

1.2.3 centos

在centos中可以使用yum安裝

sudo yum install rabbitmq

1.3 springboot整合

首先新建一個項目名爲rabbit-producer 消息生產者工程
並且添加依賴。

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

    </dependencies>

在yml配置rabbitmq地址

# rabbitmq配置
spring:
    rabbitmq:
      addresses: 127.0.0.1
      username: guest
      password: guest:

同理創建rabbit-consumer 消息消費者工程

1、普通工作隊列模式

首先在rabbit-producer工程中新建RabbitConfig文件,用於配置我們rabbitmq相關的資源
代碼如下

package com.yukong.rabbitproducer;

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

/**
 * @author yukong
 * @date 2018/8/22
 * @description rabbitmq配置類
 */
@Configuration
public class RabbitConfig {

    /**
     * 定義隊列名
     */
    private final static String STRING = "string";


    /**
     * 定義string隊列
     * @return
     */
    @Bean
    public Queue string() {
        return new Queue(STRING);
    
}

定義了名爲string的隊列。然後我們創建生產者RabbitProducer

package com.yukong.rabbitproducer;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.xml.ws.Action;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author yukong
 * @date 2018/8/22
 * @description rabbit消息生產者
 */
@Component
public class RabbitProducer {

    @Autowired
    private AmqpTemplate rabbitTemplate;

    public void stringSend() {
        Date date = new Date();
        String dateString = new SimpleDateFormat("YYYY-mm-DD hh:MM:ss").format(date);
        System.out.println("[string] send msg:" + dateString);  
      // 第一個參數爲剛剛定義的隊列名稱
        this.rabbitTemplate.convertAndSend("string", dateString);
    }
}

這裏注入一個AmqpTemplate來發布消息
接下來我們需要在rabbit-consumer工程配置一下消費者。
創建StringConsumer

package com.yukong.rabbitmqconsumer;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @author yukong
 * @date 2018/8/22
 * @description rabbitmq消費者 @RabbitListener(queues = "simpleMsg") 監聽名simpleMsg的隊列
 */
@Component
@RabbitListener(queues = "string")
public class StringConsumer {

    @Autowired private AmqpTemplate rabbitmqTemplate;

    /**
     * 消息消費
     * @RabbitHandler 代表此方法爲接受到消息後的處理方法
     */
    @RabbitHandler
    public void recieved(String msg) {
        System.out.println("[string] recieved message:" + msg);
    }

}

每一個註解的作用代碼裏面的註釋說的很詳細了我就不重複說了。
然後我們來測試,
首先在生產者工程新建一個測試類,用於生產消息。
代碼如下

package com.yukong.rabbitproducer;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class RabbitProducerApplicationTests {

    @Autowired
    private RabbitProducer producer;

    @Test
    public void testStringSend() {
        for (int i = 0; i < 10; i++) {
            producer.stringSend();
        }
    }

}

首先啓動生產者工程的測試類。然後再啓動消費者工程。

image.png


消息生產成功,一共十條。
啓動消費者工程。

image.png


消費者成功消費消息。
2、 fanout模式
fanout屬於廣播模式,只要跟它綁定的隊列都會通知並且接受到消息。
我們同理在RabbitConfig中配置一下fanout模式的隊列跟交換機。

 

//=================== fanout 模式  ====================

    @Bean
    public Queue fanoutA() {
        return new Queue("fanout.a");
    }

    @Bean
    public Queue fanoutB() {
        return new Queue("fanout.b");
    }

    @Bean
    public Queue fanoutC() {
        return new Queue("fanout.c");
    }

    /**
     * 定義個fanout交換器
     * @return
     */
    @Bean
    FanoutExchange fanoutExchange() {
        // 定義一個名爲fanoutExchange的fanout交換器
        return new FanoutExchange("fanoutExchange");
    }

    /**
     * 將定義的fanoutA隊列與fanoutExchange交換機綁定
     * @return
     */
    @Bean
    public Binding bindingExchangeWithA() {
        return BindingBuilder.bind(fanoutA()).to(fanoutExchange());
    }

    /**
     * 將定義的fanoutB隊列與fanoutExchange交換機綁定
     * @return
     */
    @Bean
    public Binding bindingExchangeWithB() {
        return BindingBuilder.bind(fanoutB()).to(fanoutExchange());
    }

    /**
     * 將定義的fanoutC隊列與fanoutExchange交換機綁定
     * @return
     */
    @Bean
    public Binding bindingExchangeWithC() {
        return BindingBuilder.bind(fanoutC()).to(fanoutExchange());
    }

在代碼中我們配置了三個隊列名、一個fanout交換機,並且將這三個隊列綁定到了fanout交換器上。只要我們往這個交換機生產新的消息,那麼這三個隊列都會收到。
接下來,我們在RabbitProducer 中添加fanout的生產方法。

public void fanoutSend() {
        Date date = new Date();
        String dateString = new SimpleDateFormat("YYYY-mm-DD hh:MM:ss").format(date);
        System.out.println("[fanout] send msg:" + dateString);
        // 注意 第一個參數是我們交換機的名稱 ,第二個參數是routerKey 我們不用管空着就可以,第三個是你要發送的消息
        this.rabbitTemplate.convertAndSend("fanoutExchange", "", dateString);
    }

同理我們需要在消費者工程新建三個消費者的類
代碼分別如下

@Component
@RabbitListener(queues = "fanout.a")
public class FanoutAConsumer {

    @Autowired
    private AmqpTemplate rabbitmqTemplate;

    /**
     * 消息消費
     * @RabbitHandler 代表此方法爲接受到消息後的處理方法
     */
    @RabbitHandler
    public void recieved(String msg) {
        System.out.println("[fanout.a] recieved message:" + msg);
    }
}


@Component
@RabbitListener(queues = "fanout.b")
public class FanoutBConsumer {

    @Autowired
    private AmqpTemplate rabbitmqTemplate;

    /**
     * 消息消費
     * @RabbitHandler 代表此方法爲接受到消息後的處理方法
     */
    @RabbitHandler
    public void recieved(String msg) {
        System.out.println("[fanout.b] recieved message:" + msg);
    }
}
@Component
@RabbitListener(queues = "fanout.c")
public class FanoutCConsumer {

    @Autowired
    private AmqpTemplate rabbitmqTemplate;

    /**
     * 消息消費
     * @RabbitHandler 代表此方法爲接受到消息後的處理方法
     */
    @RabbitHandler
    public void recieved(String msg) {
        System.out.println("[fanout.c] recieved message:" + msg);
    }
}

然後編寫一個名爲testFanout()的方法啓動我們的fanout生產方法,

   @Test
    public void testFanoutSend() {
        producer.fanoutSend();
    }

image.png

然後重啓消費者工程

 

image.png

 

三個隊列的消費都成功接收到消息。
3、topic模式,
同樣,配置topic隊列跟交換器,注意的是這裏需要多配置一個bindingKey

 //#################topic模式########################

    @Bean
    public Queue topiocA() {
        return new Queue("topic.a");
    }

    @Bean
    public Queue topicB() {
        return new Queue("topic.b");
    }

    @Bean
    public Queue topicC() {
        return new Queue("topic.c");
    }

    /**
     * 定義個topic交換器
     * @return
     */
    @Bean
    TopicExchange topicExchange() {
        // 定義一個名爲fanoutExchange的fanout交換器
        return new TopicExchange("topicExchange");
    }

    /**
     * 將定義的topicA隊列與topicExchange交換機綁定
     * @return
     */
    @Bean
    public Binding bindingTopicExchangeWithA() {
        return BindingBuilder.bind(topiocA()).to(topicExchange()).with("topic.msg");
    }

    /**
     * 將定義的topicB隊列與topicExchange交換機綁定
     * @return
     */
    @Bean
    public Binding bindingTopicExchangeWithB() {
        return BindingBuilder.bind(topicB()).to(topicExchange()).with("topic.#");
    }

    /**
     * 將定義的topicC隊列與topicExchange交換機綁定
     * @return
     */
    @Bean
    public Binding bindingTopicExchangeWithC() {
        return BindingBuilder.bind(topicC()).to(topicExchange()).with("topic.*.z");
    }
  • topicA的key爲topic.msg 那麼他只會接收包含topic.msg的消息
  • topicB的key爲topic.#那麼他只會接收topic開頭的消息
  • topicC的key爲topic.*.Z那麼他只會接收topic.B.z這樣格式的消息
    同理在RabbitProducer完成topic生產方法
public void topicTopic1Send() {
        Date date = new Date();
        String dateString = new SimpleDateFormat("YYYY-mm-DD hh:MM:ss").format(date);
        dateString = "[topic.msg] send msg:" + dateString;
        System.out.println(dateString);
        // 注意 第一個參數是我們交換機的名稱 ,第二個參數是routerKey topic.msg,第三個是你要發送的消息
        // 這條信息將會被 topic.a  topic.b接收
        this.rabbitTemplate.convertAndSend("topicExchange", "topic.msg", dateString);
    }

    public void topicTopic2Send() {
        Date date = new Date();
        String dateString = new SimpleDateFormat("YYYY-mm-DD hh:MM:ss").format(date);
        dateString = "[topic.good.msg] send msg:" + dateString;
        System.out.println(dateString);
        // 注意 第一個參數是我們交換機的名稱 ,第二個參數是routerKey ,第三個是你要發送的消息
        // 這條信息將會被topic.b接收
        this.rabbitTemplate.convertAndSend("topicExchange", "topic.good.msg", dateString);
    }

    public void topicTopic3Send() {
        Date date = new Date();
        String dateString = new SimpleDateFormat("YYYY-mm-DD hh:MM:ss").format(date);
        dateString = "[topic.m.z] send msg:" + dateString;
        System.out.println(dateString);
        // 注意 第一個參數是我們交換機的名稱 ,第二個參數是routerKey ,第三個是你要發送的消息
        // 這條信息將會被topic.b、topic.b接收
        this.rabbitTemplate.convertAndSend("topicExchange", "topic.m.z", dateString);
    }

然後在消費者工程新建隊列隊列的消費類

@Component
@RabbitListener(queues = "topic.a")
public class TopicAConsumer {

    @Autowired
    private AmqpTemplate rabbitmqTemplate;

    /**
     * 消息消費
     * @RabbitHandler 代表此方法爲接受到消息後的處理方法
     */
    @RabbitHandler
    public void recieved(String msg) {
        System.out.println("[topic.a] recieved message:" + msg);
    }
}
@Component
@RabbitListener(queues = "topic.b")
public class TopicBConsumer {

    @Autowired
    private AmqpTemplate rabbitmqTemplate;

    /**
     * 消息消費
     * @RabbitHandler 代表此方法爲接受到消息後的處理方法
     */
    @RabbitHandler
    public void recieved(String msg) {
        System.out.println("[topic.b] recieved message:" + msg);
    }
}
@Component
@RabbitListener(queues = "topic.c")
public class TopicCConsumer {

    @Autowired
    private AmqpTemplate rabbitmqTemplate;

    /**
     * 消息消費
     * @RabbitHandler 代表此方法爲接受到消息後的處理方法
     */
    @RabbitHandler
    public void recieved(String msg) {
        System.out.println("[topic.c] recieved message:" + msg);
    }
}

同理爲topic新建測試方法

 @Test
    public void testTopic() {
        producer.topicTopic1Send();
        producer.topicTopic2Send();
        producer.topicTopic3Send();
    }

image.png

 

消息成功發出。
啓動消費者工程,看看消息是不是按照規則被髮送消息

 

image.png

 

其中 隊列topic.a只配置topic.msg一條消息,正確
其中 隊列topic.b匹配三條消息,因爲三條消息都是topic開頭的 正確
其中 隊列topic.c匹配一條消息,只有一條消息滿足(也就是topic.m.z這條消息)

 

 

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