java-web系列(八)---RabbitMQ在java-web中的簡單應用

前言

這個項目的github地址:extensible項目的github地址

extensible項目當前功能模塊如下:

java-web系列(一)—搭建一個基於SSM框架的java-web項目
java-web系列(二)—以dockerfile的方式發佈java-web項目
java-web系列(三)—(slf4j + logback)進行日誌分層
java-web系列(四)—幾種常見的加密算法
java-web系列(五)—SpringBoot整合Redis
java-web系列(六)—Mybatis動態多數據源配置
java-web系列(七)—SpringBoot整合Quartz實現多定時任務
java-web系列(八)—RabbitMQ在java-web中的簡單應用

如對該項目有疑問,可在我的博客/github下面留言,也可以以郵件的方式告知。
我的聯繫方式:[email protected]

RabbitMQ的使用場景

MQ,是Message Queue(消息隊列)的簡寫。簡而言之,RabbitMQ就是將消息儲存在隊列中。

在項目的實際開發過程中,可以將一些無需即時返回結果且耗時的操作提取出來,進行異步處理。這種處理方式能夠大大節省服務器的請求響應時間,從而提高系統的吞吐量。

比如:以去年雙十一淘寶成交額爲例

2017年雙十一成交額

當天每秒下訂單筆數超過32.5萬筆,支付筆數超過25.6萬筆。

也就是說,需要阿里的服務器每秒進行32.5萬個“生成訂單”的操作,還要進行25.6萬個“支付訂單”的操作。

而這些與money相關的,都是一些耗時的操作。如果要求即時返回這些操作的處理結果,服務器的壓力太大。

實際能夠進行優化後的做法是:把兩種不同的操作,放到兩個不同的隊列中延後進行處理。

如:“剁手黨”之一的我在當天進行了一個下單操作,服務器會將這個操作的具體邏輯放到“order_queue”隊列中,就告知我下單成功,然後服務器會在空閒時處理並生成相應的訂單。這種做法,不僅提升了用戶的使用體驗(我覺得很快就下單成功了),還能夠緩解服務器的壓力。

常用的消息隊列還有:ZeroMQ,ActiveMQ,Kafka等等,有興趣的可以自行了解。這篇博客主要是學習RabbitMQ的簡單應用,這裏就不探討這幾種消息隊列的優劣勢了。

在Windows下安裝RabbitMQ

RabbitMQ是使用Erlang語言編寫的一個開源的消息隊列。

下面的這兩個安裝包也可以在我的網盤中下載,提取碼爲:bs7t

  1. 首先,在Erlang官網下載對應版本的Erlang平臺。
    erlang依賴
  2. 然後運行可執行文件(otp_win64_21.1)。按默認配置進行安裝,更改一下文件的存放目錄即可。出現如下圖說明Erlang平臺安裝成功。
    erlang平臺安裝成功
  3. 再然後在RabbitMQ官網下載最新版本的RabbitMQ Server。
    RabbitMQ Server下載
  4. 繼續按默認配置進行安裝,出現如下圖說明RabbitMQ Server安裝成功。
    RabbitMQ Server安裝成功
  5. 這種做法,是將RabbitMQ暴露爲Window的一個服務。在這裏啓動RabbitMQ後,可以在15672端口用默認賬戶密碼進行登錄(guest/guest)。
    默認賬戶guest登錄RabbitMQ Server
  6. 出現如下圖說明登錄成功。
    RabbitMQ Server主頁

RabbitMQ中的幾個重要概念

  • Producer

Producer,生產者。消息的發送方即爲生產者。

  • Consumer

Consumer,消費者。消息的接收方即爲消費者。

  • Connection

Connection,獲取RabbitMQ(消息隊列)服務的長連接。得到長連接實例的方式有兩種:

  1. 手動設置相關參數(host/port etc…)
/**
  * 不設置的話,會使用默認的參數(userName:guest,password:guest,virtualHost:/,hostName:localhost,portNumber:5672)
  */
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername(userName);
factory.setPassword(password);
factory.setVirtualHost(virtualHost);
factory.setHost(hostName);
factory.setPort(portNumber);
Connection conn = factory.newConnection();
  1. URI設置相關參數
/**
  * 不設置的話,會使用默認的參數(userName:guest,password:guest,virtualHost:/,hostName:localhost,portNumber:5672)
  */
ConnectionFactory factory = new ConnectionFactory();
factory.setUri("amqp://userName:password@hostName:portNumber/virtualHost");
Connection connection = factory.newConnection();
  • Channel

Channel,通道。通過通道來決定“生產者如何往隊列中發送消息”、“消費者如何從隊列中接收消息”。獲取通道實例的方式如下:

Channel channel = connection.createChannel();
  • Exchange

Exchange,交換機。生產者往隊列中發送消息,實際不是直接發送給隊列,而是先發送給交換機,由交換機決定實際將消息發送給哪個隊列。

  • Queue

Queue,隊列。存儲消息的地方。交換機和隊列都是通過通道實例來聲明,而且交換機與隊列直接是存在對應關係的。具體的源碼如下:

channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT, true);
channel.queueDeclare(queueName,true,false,false,null);
channel.queueBind(queueName, exchangeName, routingKey);

具體的代碼解釋如下:

通過ChannelIN.exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable)來聲明交換機實例,第一個參數是交換機的名稱,第二個參數是交換機的類型(direct/fanout/topic/headers),第三個參數是該交換機是否持久化。

通過ChannelIN.queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)來聲明隊列實例,第一個參數是隊列的名稱,第二個參數是該隊列是否持久化,第三個參數是是否只有自己能夠看到該對列(排他性),第四個參數是當沒有消費者佔用該隊列時是否刪除該隊列。

通過queueBind(String queue, String exchange, String routingKey)綁定該交換機和隊列。routingKey是綁定隊列和交換機之間的路由規則。

這些概念之間的關係結構圖如下:

RabbitMQ的元素結構圖

RabbitMQ在java-web中的應用

  • 必須要導入RabbitMQ Server的POM依賴
<dependency>
  <groupId>com.rabbitmq</groupId>
  <artifactId>amqp-client</artifactId>
  <version>4.0.3</version>
</dependency>
  • 發送消息的生產者測試代碼如下:
/**
 * RabbitMQ中生產者測試代碼
 * @author zhenye 2018/9/29
 */
@Slf4j
public class ProducerTest {

    private final static String EXCHANGE_NAME = "MY_EXCHANGE";
    private final static String QUEUE_NAME = "MY_QUEUE";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT,true);
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        String routingKey = "123";
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,routingKey);
        String message = "Hello RabbitMQ, I will send some message to the consumer.";
        channel.basicPublish(EXCHANGE_NAME,routingKey,null,message.getBytes("UTF-8"));
        log.info("Producer send message, the content is :" + message);
        channel.close();
        connection.close();
    }
}
  • 接收消息的消費者測試代碼如下:
/**
 * RabbitMQ中消費者測試代碼
 * @author zhenye 2018/9/29
 */
@Slf4j
public class ConsumerTest {
    private final static String QUEUE_NAME = "MY_QUEUE";
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        log.info("Consumer is waiting!");
        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = new String(body,"UTF-8");
                log.info("Consumer received message, the content is:" + message);
            }
        };
        channel.basicConsume(QUEUE_NAME,true,consumer);
    }
}
  • 演示過程

連續運行5次ProducerTest.main(),即有5個消費者往隊列(MY_QUEUE)中發送了消息。

RabbitMQ Server主頁顯示如下:

主頁效果圖1

結果表明,在RabbitMQ的所有隊列中,還有5條待處理的消息。

接着運行ConsumerTest.main(),效果圖如下:

主頁效果圖2

結果表明,該消費者一次性處理完了5條消息。

  • 流程說明

生產者是通過ChannelIN.basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)來發送消息,第一個參數是交換機名稱,第二個參數是路由規則Key,第三個參數是全局屬性,第四個參數是消息實體。第一、二個參數是可以確定要保存消息的隊列的。

消費者是通過DefaultConsumer.handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)來接收並處理消息的。由於消費者是直接從隊列中獲取消息,只要保證生產者存入消息的隊列(交換機名稱、路由Key確定的隊列),與消費者接收消息的隊列(直接指定QUEUE_NAME)相同,就能正確地取出消息。

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