RabbitMQ(1)

Ubutun安裝使用命令:

  1. apt-get install erlang-nox     # 安裝erlang 需要erlang環境  erl    # 查看relang語言版本,成功執行則說明relang安裝成功

  2. apt-get install rabbitmq-server  #安裝成功自動啓動

  3. service rabbitmq-server start    # 啓動 service rabbitmq-server stop     # 停止 service rabbitmq-server restart  # 重啓

  4. rabbitmq-plugins enable rabbitmq_management   # 啓用插件 service rabbitmq-server restart    # 重啓

  5. 啓動後,mq只能在本地登陸web端控制

  6. rabbitmqctl list_users  #查看用戶

  7. rabbitmqctl add_user admin yourpassword

  8. rabbitmqctl set_user_tags admin administrator    # 給普通用戶分配管理員角色

    注意RabbitMQ的端口爲15672,如果虛擬在在遠程的話,需要開放端口

    http://39.108.254.46:15672/就可以訪問了.

一:基本消息模型

如何避免消息丟失?

1種方案):

消費者消息的確認機制

問題:消息一旦被消費者接收,隊列中的消息會被刪除,那麼RabbitMQ怎麼知道消息被接受了?

這就需要通過消息確認機制

如果消費者領取消息後,還沒執行操作就掛掉了呢?或者拋出了異常?消息消費失敗,但是RabbitMQ無從得知,這樣消息就丟失了!

 

因此,RabbitMQ有一個ACK機制。當消費者獲取消息後,會向RabbitMQ發送回執ACK,告知消息已經被接收。不過這種回執ACK分兩種情況:

- 自動ACK:消息一旦被接收,消費者自動發送ACK

- 手動ACK:消息接收後,不會發送ACK,需要手動調用

- 如果消息不太重要,丟失也沒有影響,那麼自動ACK會比較方便

- 如果消息非常重要,不容丟失。那麼最好在消費完成後手動ACK,否則接收消息後就自動ACK,RabbitMQ就會把消息從隊列中刪除。如果此時消費者宕機,那麼消息就丟失了。

         <dependencies>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.3.2</version>
		</dependency>
		<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>
	</dependencies>
public class ConnectionUtil {
    /**
     * 建立與RabbitMQ的連接
     * @return
     * @throws Exception
     */
    public static Connection getConnection() throws Exception {
        //定義連接工廠
        ConnectionFactory factory = new ConnectionFactory();
        //設置服務地址
        factory.setHost("39.108.254.46");
        //端口
        factory.setPort(5672);
        //設置賬號信息,用戶名、密碼、vhost
        factory.setVirtualHost("/kf");
        factory.setUsername("kf");
        factory.setPassword("kf");
        // 通過工程獲取連接
        Connection connection = factory.newConnection();
        return connection;
    }
}
public class Send {

    private final static String QUEUE_NAME = "simple_queue";

    public static void main(String[] argv) throws Exception {
        // 獲取到連接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        // 從連接中創建通道,這是完成大部分API的地方。
        Channel channel = connection.createChannel();

        // 聲明(創建)隊列,必須聲明隊列才能夠發送消息,我們可以把消息發送到隊列中。
        // 聲明一個隊列是冪等的 - 只有當它不存在時纔會被創建
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 消息內容
        String message = "Hello World!";
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
        System.out.println(" [x] Sent '" + message + "'");

        //關閉通道和連接
        channel.close();
        connection.close();
    }
}
public class Recv {
private final static String QUEUE_NAME = "simple_queue";

    public static void main(String[] argv) throws Exception {
        // 獲取到連接
        Connection connection = ConnectionUtil.getConnection();
        // 創建通道
        final Channel channel = connection.createChannel();
        // 聲明隊列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        // 定義隊列的消費者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            // 獲取消息,並且處理,這個方法類似事件監聽,如果有消息的時候,會被自動調用
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
                    byte[] body) throws IOException {
                // body 即消息體
                String msg = new String(body);
                System.out.println(" msg : " + msg + "!");
                // 手動進行ACK
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        //  監聽隊列,第二個參數false,手動進行ACK
        channel.basicConsume(QUEUE_NAME, false, consumer);
    }
 }

二:WORK消息模型

生產消息過快,消費消息過慢,導致消息堆積

避免消息堆積?

1) 採用workqueue,多個消費者監聽同一隊列。

2)接收到消息以後,而是通過線程池,異步消費。

public class Send {
    //循環發送50條消息
    private final static String QUEUE_NAME = "test_work_queue";

    public static void main(String[] argv) throws Exception {
        // 獲取到連接
        Connection connection = ConnectionUtil.getConnection();
        // 獲取通道
        Channel channel = connection.createChannel();
        // 聲明隊列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        // 循環發佈任務
        for (int i = 0; i < 50; i++) {
            // 消息內容
            String message = "task .. " + i;
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
            System.out.println(" [x] Sent '" + message + "'");

            Thread.sleep(i * 2);
        }
        // 關閉通道和連接
        channel.close();
        connection.close();
    }
}
消費者:
添加:channel.basicQos(1);//一條一條處理消息,並不是原來的RabbitMQ默認平均分配消息,能者多勞。

// 消費者1
public class Recv {
    private final static String QUEUE_NAME = "test_work_queue";

    public static void main(String[] argv) throws Exception {
        // 獲取到連接
        Connection connection = ConnectionUtil.getConnection();
        // 獲取通道
        final Channel channel = connection.createChannel();
        // 聲明隊列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        // 設置每個消費者同時只能處理一條消息
        channel.basicQos(1);
        // 定義隊列的消費者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            // 獲取消息,並且處理,這個方法類似事件監聽,如果有消息的時候,會被自動調用
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
                    byte[] body) throws IOException {
                // body 即消息體
                String msg = new String(body);
                System.out.println(" [消費者1] received : " + msg + "!");
                try {
                    // 模擬完成任務的耗時:1000ms
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                }
                // 手動ACK
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        // 監聽隊列。
        channel.basicConsume(QUEUE_NAME, false, consumer);
    }
}

//消費者2
public class Recv2 {
    private final static String QUEUE_NAME = "test_work_queue";

    public static void main(String[] argv) throws Exception {
        // 獲取到連接
        Connection connection = ConnectionUtil.getConnection();
        // 獲取通道
        final Channel channel = connection.createChannel();
        // 聲明隊列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        // 設置每個消費者同時只能處理一條消息
        channel.basicQos(1);
        // 定義隊列的消費者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            // 獲取消息,並且處理,這個方法類似事件監聽,如果有消息的時候,會被自動調用
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
                    byte[] body) throws IOException {
                // body 即消息體
                String msg = new String(body);
                System.out.println(" [消費者2] received : " + msg + "!");
                // 手動ACK
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        // 監聽隊列。
        channel.basicConsume(QUEUE_NAME, false, consumer);
    }
}

其他模型:

1、1個生產者,多個消費者

2、每一個消費者都有自己的一個隊列

3、生產者沒有將消息直接發送到隊列,而是發送到了交換機

4、每個隊列都要綁定到交換機

5、生產者發送的消息,經過交換機到達隊列,實現一個消息被多個消費者獲取的目的

X(Exchanges):交換機一方面:接收生產者發送的消息。另一方面:知道如何處理消息,例如遞交給某個特別隊列、遞交給所有隊列、或是將消息丟棄。到底如何操作,取決於Exchange的類型。

Exchange類型有以下幾種:

         Fanout:廣播,將消息交給所有綁定到交換機的隊列

         Direct:定向,把消息交給符合指定routing key 的隊列

         Topic:通配符,把消息交給符合routing pattern(路由模式) 的隊列

三.訂閱模型-Fanout

在廣播模式下,消息發送流程是這樣的:

- 1)  可以有多個消費者

- 2)  每個**消費者有自己的queue**(隊列)

- 3)  每個**隊列都要綁定到Exchange**(交換機)

- 4)  **生產者發送的消息,只能發送到交換機**,交換機來決定要發給哪個隊列,生產者無法決定。

- 5)  交換機把消息發送給綁定過的所有隊列

- 6)  隊列的消費者都能拿到消息。實現一條消息被多個消費者消費。

實現demo:

public class Send {

    private final static String EXCHANGE_NAME = "fanout_exchange_test";

    public static void main(String[] argv) throws Exception {
        // 獲取到連接
        Connection connection = ConnectionUtil.getConnection();
        // 獲取通道
        Channel channel = connection.createChannel();
        
        // 聲明exchange,指定類型爲fanout
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
        
        // 消息內容
        String message = "Hello everyone";
        // 發佈消息到Exchange
        channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
        System.out.println(" [生產者] Sent '" + message + "'");

        channel.close();
        connection.close();
    }
}
// 消費者2
public class Recv2 {
    private final static String QUEUE_NAME = "fanout_exchange_queue_2";

    private final static String EXCHANGE_NAME = "fanout_exchange_test";

    public static void main(String[] argv) throws Exception {
        // 獲取到連接
        Connection connection = ConnectionUtil.getConnection();
        // 獲取通道
        Channel channel = connection.createChannel();
        // 聲明隊列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 綁定隊列到交換機
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
        
        // 定義隊列的消費者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            // 獲取消息,並且處理,這個方法類似事件監聽,如果有消息的時候,會被自動調用
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
                    byte[] body) throws IOException {
                // body 即消息體
                String msg = new String(body);
                System.out.println(" [消費者2] received : " + msg + "!");
            }
        };
        // 監聽隊列,手動返回完成
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }
}
//消費者1
public class Recv {
    private final static String QUEUE_NAME = "fanout_exchange_queue_1";

    private final static String EXCHANGE_NAME = "fanout_exchange_test";

    public static void main(String[] argv) throws Exception {
        // 獲取到連接
        Connection connection = ConnectionUtil.getConnection();
        // 獲取通道
        Channel channel = connection.createChannel();
        // 聲明隊列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 綁定隊列到交換機
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");

        // 定義隊列的消費者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            // 獲取消息,並且處理,這個方法類似事件監聽,如果有消息的時候,會被自動調用
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
                    byte[] body) throws IOException {
                // body 即消息體
                String msg = new String(body);
                System.out.println(" [消費者1] received : " + msg + "!");
            }
        };
        // 監聽隊列,自動返回完成
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }
}

 

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