SpringBoot18-RabbitMQ

1)、RabbitMQ學習

一、簡歷:

RabbitMQ是實現了高級消息隊列協議(AMQP)的開源消息代理軟件(亦稱面向消息的中間件)。RabbitMQ服務器是用Erlang語言編寫的,而羣集和故障轉移是構建在開放電信平臺框架上的。所有主要的編程語言均有與代理接口通訊的客戶端

二、何謂隊列?

​ 隊列是一個存儲、組織數據的數據結構,其最大的特性就是FIFO(先進先出),rabbitmqqueueRabbitmq的內部對象,用於存儲消息。

三、何爲消息隊列?(MQ)

  • 服務之間最常見的通信方式是直接調用彼此來通信,消息從一端發出後立即就可以到達另一端,稱爲即時消息通訊(同步通信);
  • 消息從某一端發出後,首先進入一個容器進行臨時存儲,當達到某種條件後,再由這個容器發送給另一端,稱爲延遲消息通訊(異步通信);

當然,容器的一個具體實現就是MQ;

四、消息隊列的四大好處:

**1、解耦;**每個成員不必受其他成員的影響,可以更獨立自主的做自己的事情,只通過一個簡單的容器來聯繫;

**2、提速;**在對於不使用消息隊列的時候,我們來想象一個場景:比如下訂單,下訂單後的操作有:減庫存、減金額、發送短信等等微服務操作,如果這些都靠server來發送,減庫存100ms、減金額100ms、發送短信100ms,那麼總體花費就是100+100+100=300ms,如果你使用多線程的方式也是接近200ms。如果使用消息隊列,那麼我們就不用管它多久發送達到,只需要插入到隊列中,讓他自己去執行就可以了,主程序只需要50ms即可。

**3、廣播;**有利於一次性給多個微服務發送消息message。

**4、削峯;**比如在秒殺的時候,同時1w個請求來請求服務器,如果全部打在服務器和數據庫上,服務器基本上是馬上GG,這時候我們就可以引入消息隊列,設置消息隊列的大小,比如秒殺的件只有100件,那麼設置消息隊列的大小爲100,其他沒有入隊列的全部返回未秒殺成功,這樣就成功的削峯完畢。

五、何爲AMQP?

1、介紹:

一個提供統一消息服務應用層標準高級消息隊列協議,是一個通用的應用層協議。消息發送與接收的雙發遵守這個協議就可以實現異步通訊。

2、原理:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-4MOVYDb5-1585154597521)(C:\Users\ouguangji\AppData\Roaming\Typora\typora-user-images\image-20200321233058378.png)]

  • Broker:接收和分發消息的應用,RabbitMQ Server就是Message Broker。
  • Virtual host:出於多租戶和安全設計的,把AMQP的基本組件劃分到一個虛擬的分組中,類似於網絡中的namespace概念。當多個不同的用戶使用同一個RabbitMQ Server提供服務的時候,就可以劃分出多個vhost,來對每一個用戶創建自己的exchange/Queue。
  • Connection:publisher/consumer和broker之間的TCP連接。斷開連接的操作只會在client端進行,Broker不會斷開連接,除非出現網絡故障或者broker服務出現問題。並且在一個TCP連接中也可以開通多個信道,進行流量均衡輸送。
  • Channel: 如果每一次訪問RabbitMQ都建立一個connection,那麼在消息量較大的時候建立TCP connection的開銷將會是巨大的,並且效率也是極低的。Channel是在Connection內部建立的邏輯連接,如果應用程序支持多線程,通常每個thread創建單獨的channel進行通訊,AMQP method 包含了channel id幫助客戶端和message broker識別channel,所以channel之間是完全隔離的。Channel作爲輕量級的Connection極大減少了操作系統建立TCP connection的開銷。
  • Queue:消息最終被送到這裏等待consumer取走,一個message可以被同時拷貝到多個queue中。
  • Binding:exchange和queue之間的虛擬連接,binding中可以包含routing key。Binding信息被保存到exchange中的查詢表中,用於message的分發依據。類似於計算機網絡中的路由器機制。

3、典型的“生成/消費”消息模型:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-B8vu8RlW-1585154597529)(C:\Users\ouguangji\AppData\Roaming\Typora\typora-user-images\image-20200321234847065.png)]

  • 生產者發送消息到broker server(RabbitMQ)。在Broker內部,用戶創建Exchange和Queue,然後通過binding機制將兩者聯繫起來。Exchange分發消息,根據類型 / binding的不同分發策略選擇正確的queue隊列。

  • 消費者Consumer從queue中拿取消息。

4、Exchange類型

Exchange類型有:Direct、Fanout、Topic三種類型。

  • Direct類型:點對點

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Eog2JXWY-1585154597534)(C:\Users\ouguangji\AppData\Roaming\Typora\typora-user-images\image-20200321235701619.png)]

  • Fanout類型:廣播

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-E2KcZ08d-1585154597536)(C:\Users\ouguangji\AppData\Roaming\Typora\typora-user-images\image-20200321235723708.png)]

  • Topic類型:通配符匹配規則

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-64asvDN9-1585154597539)(C:\Users\ouguangji\AppData\Roaming\Typora\typora-user-images\image-20200321235751540.png)]

    #:代表0個或多個單詞、字符

    *:代表1個或多個單詞、字符

轉載RabbitMQ的三種方式

2)、SpringBoot集成RabbitMQ:

1、打開上一次的mybatis-springboot環境:

2、添加pow.xml依賴:

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

3、配置application.properties:

# 配置rabbitmq
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.addresses=47.97.192.241

4、在Test中進行測試RabbitMQ:

rabbitmq在springboot中也存在自動配置類,自動配置類可以讀取配置文件中的配置進行自動配置。

而且我們可以看到 這個配置類 其實是一個懶加載機制:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
@EnableConfigurationProperties(RabbitProperties.class)
@Import(RabbitAnnotationDrivenConfiguration.class)

該配置判斷:在容器中是否存在RabbitTemplate模版,如果存在才進行自動配置,如果沒有使用RabbitTemplate就不進行自動配置。

並且我們找到rabbitTemplate這個函數,可以看到配置過程,配置文件是作爲參數傳入的:properties

@Bean
@ConditionalOnSingleCandidate(ConnectionFactory.class)
@ConditionalOnMissingBean(RabbitOperations.class)
public RabbitTemplate rabbitTemplate(RabbitProperties properties,
        ObjectProvider<MessageConverter> messageConverter,
        ObjectProvider<RabbitRetryTemplateCustomizer> retryTemplateCustomizers,
        ConnectionFactory connectionFactory) {
    PropertyMapper map = PropertyMapper.get();
    RabbitTemplate template = new RabbitTemplate(connectionFactory);
    messageConverter.ifUnique(template::setMessageConverter);
    template.setMandatory(determineMandatoryFlag(properties));
    RabbitProperties.Template templateProperties = properties.getTemplate();
    if (templateProperties.getRetry().isEnabled()) {
        template.setRetryTemplate(
                new RetryTemplateFactory(retryTemplateCustomizers.orderedStream().collect(Collectors.toList()))
                        .createRetryTemplate(templateProperties.getRetry(),
                                RabbitRetryTemplateCustomizer.Target.SENDER));
    }
    map.from(templateProperties::getReceiveTimeout).whenNonNull().as(Duration::toMillis)
            .to(template::setReceiveTimeout);
    map.from(templateProperties::getReplyTimeout).whenNonNull().as(Duration::toMillis)
            .to(template::setReplyTimeout);
    map.from(templateProperties::getExchange).to(template::setExchange);
    map.from(templateProperties::getRoutingKey).to(template::setRoutingKey);
    map.from(templateProperties::getDefaultReceiveQueue).whenNonNull().to(template::setDefaultReceiveQueue);
    return template;
}

我們這就來看一下properties這個配置文件類:

@ConfigurationProperties(prefix = "spring.rabbitmq")
public class RabbitProperties {

	/**
	 * RabbitMQ host.
	 */
	private String host = "localhost";

	/**
	 * RabbitMQ port.
	 */
	private int port = 5672;

	/**
	 * Login user to authenticate to the broker.
	 */
	private String username = "guest";

	/**
	 * Login to authenticate against the broker.
	 */
	private String password = "guest";

	/**
	 * SSL configuration.
	 */
	private final Ssl ssl = new Ssl();

	/**
	 * Virtual host to use when connecting to the broker.
	 */
	private String virtualHost;

	/**
	 * Comma-separated list of addresses to which the client should connect.
	 */
	private String addresses;

我們從這個配置類可以得到:

如果我們沒有配置host的時候默認爲本機localhost

如果我們沒有配置usernamepassword都是guest以及我們可以配置的一些其他屬性:虛擬地址virtualHost,服務端地址addresses

我們並且可以通過調試得到:通過自動配置後,已經生成好了exchange和routingKey爲空串的template對象:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-3RFL1CLu-1585154597541)(C:\Users\ouguangji\AppData\Roaming\Typora\typora-user-images\image-20200322152348783.png)]

配置完成後,再進入我們的test方法,設置exchange和routingKey還有message進行消息轉發。

@Autowired
RabbitTemplate rabbitTemplate;
@Test
public void test2() {
//        接收數據
    Object o=rabbitTemplate.receiveAndConvert("ogj1");
    assert o != null;
    System.out.println(o.getClass());
    System.out.println(o);
}

5、運行成功:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-qvrpRd2j-1585154597542)(C:\Users\ouguangji\AppData\Roaming\Typora\typora-user-images\image-20200322153215178.png)]

我們來看看在rabbitmq服務端中是否存在這個消息在queue中:

打開ip:15672,登陸:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-cLz3WjBA-1585154597543)(C:\Users\ouguangji\AppData\Roaming\Typora\typora-user-images\image-20200322153426317.png)]

我們發現,是序列化的形式,原因是rabbitmq自身使用的是jdk自帶的序列化機制,所以結果是一段不明代碼。

6、我們來用springboot 接收一下rabbitmq中的消息:

@Test
public void test2() {
//        接收數據
    Object o=rabbitTemplate.receiveAndConvert("ogj1");
    assert o != null;
    System.out.println(o.getClass());
    System.out.println(o);
}

運行結果:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-cKdy3Eib-1585154597545)(C:\Users\ouguangji\AppData\Roaming\Typora\typora-user-images\image-20200322154004652.png)]

成功接收到該queue中的消息。

問題:我們怎麼來自定義序列化? 當然是自定義config類啦!

我們 來看一下message的轉換類:

public interface MessageConverter {
    Message toMessage(Object var1, MessageProperties var2) throws MessageConversionException;

    default Message toMessage(Object object, MessageProperties messageProperties, @Nullable Type genericType) throws MessageConversionException {
        return this.toMessage(object, messageProperties);
    }

    Object fromMessage(Message var1) throws MessageConversionException;
}

rabbitmq默認使用的是jdk序列模式,我們可以從MessageConvert中選擇自定義爲JSON。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-sQKrxKLV-1585154597547)(C:\Users\ouguangji\AppData\Roaming\Typora\typora-user-images\image-20200322154942703.png)]

我們找到這個Json實現類,那麼我們下一步就是來自定義Config:

@Configuration
public class MyAmqConfig {

    @Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }
}

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-KG0XBNx4-1585154597550)(C:\Users\ouguangji\AppData\Roaming\Typora\typora-user-images\image-20200322155429229.png)]

我們就可以看到這個是一個json序列化對象了。

3)、RabittMQ的監聽機制:

使用註解:EnableRabbit+RabbitListener

@EnableRabbit //開啓基於註解的rabbitmq
@SpringBootApplication
public class DemoMybatisApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoMybatisApplication.class, args);
    }
}
/**
     * 監聽rabbitmq隊列中的消息,只要這個隊列中有message進入,那麼就一定會收到這個消息
     * @param user
     */
    @RabbitListener(queues = "ogj1")
    public void receiveMessage(User user){
        System.out.println(user.toString());
    }

發送消息到queue中:

@Autowired
    RabbitTemplate rabbitTemplate;
    @Test
    public void test1() {
//        點對點的發送:direct
        Map<String,Object> map=new HashMap<String, Object> ();
        map.put("msg","這是一個rabbitmq message");
        map.put("data", Arrays.asList("hello world",123,true));
        User user = new User();
        user.setUsername("歐光繼");
        user.setPasswd("123456");
        user.setAddress("重慶市");
        user.setId("1");
        user.setPhone("123456789");
        user.setYoubian("402460");
//        對象被默認的jdk序列化形式發送出去
        rabbitTemplate.convertAndSend("exchange.direct","ogj1", user);
        System.out.println("發送成功!");
    }

測試:

首先運行主程序。然後運行test單元測試程序,進行發送消息到queue。

結果:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-V4J6Zykx-1585154597552)(C:\Users\ouguangji\AppData\Roaming\Typora\typora-user-images\image-20200322162037375.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-wdjtBuyH-1585154597554)(C:\Users\ouguangji\AppData\Roaming\Typora\typora-user-images\image-20200322162048498.png)]

並且只要隊列有消息,那麼就會馬上收到該消息並打印。

4)、AmqpAdmin對於RabbitMQ的管理:

我們也可以使用AmqpAdmin來在程序中對rabbitmq進行管理操作。

/**
     * 使用amqpAdmin管理rabbitmq
     */
    @Autowired
    AmqpAdmin amqpAdmin;
    @Test
    public void createExchange() {
        //創建一個Exchange
        amqpAdmin.declareExchange(new DirectExchange("amqpadmin.exchange"));
    }
    @Test
    public void createQueue() {
        //創建一個隊列
        amqpAdmin.declareQueue(new Queue("amqpadmin.quque",true));
    }
    @Test
    public void binding() {
        //創建綁定規則
        amqpAdmin.declareBinding(new Binding("amqpadmin.queue",
                Binding.DestinationType.QUEUE,"amqpadmin.exchange",
                "amqp.haha",null));

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