rabbitmq使用總結

demo代碼在ssm-demo中

一、rabbitmq的幾種工作模式

1、simple

​ 三個對象:生產者、隊列、消費者

​ 代碼:

​ Sender:

package com.my.test.rabbitmq.simple;

import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * @author QinHe at 2019-09-18
 */
public class Send {

    static final String QUEUE_NAME = "test_simple_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        //獲取連接
        Connection connection = ConnectionUtils.getConnection();

        //從連接中創建一個管道
        Channel channel = connection.createChannel();

        //創建隊列聲明
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        String msg = "hello simple";
        channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());

        System.out.println();

        channel.close();
        connection.close();

    }
}

​ Receiver:

package com.my.test.rabbitmq.simple;

import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;

/**
 * @author QinHe at 2019-09-18
 */
public class Receive {

    public static void main(String[] args) throws IOException, TimeoutException {

        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare(Send.QUEUE_NAME, false, false, false, null);
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, StandardCharsets.UTF_8);
                System.out.println(Send.QUEUE_NAME + "receive:" + msg);
            }
        };

        channel.basicConsume(Send.QUEUE_NAME, true, consumer);
    }

}

​ 缺點:耦合性高,生產者一一對應消費者,如果我想有多個消費者消費隊列中的消息,這就不行了;如果對列名變更,這時要把生產者和消費者同時變更

2、work queue 工作隊列

  • 模型:

  • 爲什麼會出現工作隊列

  • simple隊列是一一對應的,而且實際開發中,生產者發送消息是毫不費力的,而消費者一般是要跟業務相結合的,消費者接收到消息之後就需要處理,可能會需要花費很多時間,這時候隊列可能會積壓很多消息

  • 代碼:

    sender:

package com.my.test.rabbitmq.workqueue;

import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * @author QinHe at 2019-09-29
 */
public class Send {
    protected final static String QUEUE_NAME = "test_work_queue";

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //獲取連接
        Connection connection = ConnectionUtils.getConnection();
        //獲取channel
        Channel channel = connection.createChannel();
        //聲明隊列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        for (int i = 0; i < 50; i++) {
            String msg = "hello " + i;
            channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
            TimeUnit.MICROSECONDS.sleep(i * 20);
        }
        channel.close();
        connection.close();
    }
}

receive1:

package com.my.test.rabbitmq.workqueue;

import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * @author QinHe at 2019-09-29
 */
public class Receive1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //獲取連接
        Connection connection = ConnectionUtils.getConnection();
        //獲取channel
        Channel channel = connection.createChannel();
        //聲明隊列
        channel.queueDeclare(Send.QUEUE_NAME, false, false, false, null);
        //定義消費者
        DefaultConsumer consumer1 = new DefaultConsumer(channel) {
            //消息到達 觸發這個方法
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, StandardCharsets.UTF_8);
                System.out.println("[1] receive msg :" + msg);
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[1] receive msg :" + msg);
                }
            }
        };

        boolean autoAck = true;
        channel.basicConsume(Send.QUEUE_NAME, autoAck, consumer1);
    }
}

receive2:

package com.my.test.rabbitmq.workqueue;

import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * @author QinHe at 2019-09-29
 */
public class Receive2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //獲取連接
        Connection connection = ConnectionUtils.getConnection();
        //獲取channel
        Channel channel = connection.createChannel();
        //聲明隊列
        channel.queueDeclare(Send.QUEUE_NAME, false, false, false, null);
        //定義消費者
        DefaultConsumer consumer1 = new DefaultConsumer(channel) {
            //消息到達 觸發這個方法
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, StandardCharsets.UTF_8);
                System.out.println("[2] receive msg :" + msg);
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        boolean autoAck = true;
        channel.basicConsume(Send.QUEUE_NAME, autoAck, consumer1);
    }
}

  • 現象:

​ 消費者1和消費者2處理的消息數是一樣的,消費者1都是偶數 消費者2都是奇數,這種方式是輪詢分發(round-robin)

​ 不管誰忙或者誰清閒,都不會多給一個消息,任務消息總是輪詢的

3、workqueue公平分發(fair dipatch)

  • 必須關閉自動應答ack,改成手動

  • 代碼:

    send:

package com.my.test.rabbitmq.workfairqueue;

import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * @author QinHe at 2019-09-29
 */
@SuppressWarnings("DuplicatedCode")
public class Send {
    final static String QUEUE_NAME = "test_work_queue";

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //獲取連接
        Connection connection = ConnectionUtils.getConnection();
        //獲取channel
        Channel channel = connection.createChannel();
        //聲明隊列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        /**
         * 每個消費者 發送確認消息之前,消息隊列不發送下一個消息到消費者,一次只處理一條消息
         */
        channel.basicQos(1);

        for (int i = 0; i < 50; i++) {
            String msg = "hello " + i;
            channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
            TimeUnit.MICROSECONDS.sleep(i * 20);
        }
        channel.close();
        connection.close();
    }
}

receive1:

package com.my.test.rabbitmq.workfairqueue;

import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * @author QinHe at 2019-09-29
 */
public class Receive1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //獲取連接
        Connection connection = ConnectionUtils.getConnection();
        //獲取channel
        Channel channel = connection.createChannel();
        //聲明隊列
        channel.queueDeclare(Send.QUEUE_NAME, false, false, false, null);

        /**
         * 每個消費者 發送確認消息之前,消息隊列不發送下一個消息到消費者,一次只處理一條消息
         */
        channel.basicQos(1);

        //定義消費者
        DefaultConsumer consumer1 = new DefaultConsumer(channel) {
            //消息到達 觸發這個方法
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, StandardCharsets.UTF_8);
                System.out.println("[1] receive msg :" + msg);
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[1] done");
                    /**
                     * 手動回執一個消息
                     * deliveryTag:該消息的index
                     * multiple:是否批量.true:將一次性ack所有小於deliveryTag的消息。
                     */
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };

        /**
         * 自動應答改成false
         */
        boolean autoAck = false;
        channel.basicConsume(Send.QUEUE_NAME, autoAck, consumer1);
    }
}

receive2:

package com.my.test.rabbitmq.workfairqueue;

import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * @author QinHe at 2019-09-29
 */
@SuppressWarnings("DuplicatedCode")
public class Receive2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //獲取連接
        Connection connection = ConnectionUtils.getConnection();
        //獲取channel
        Channel channel = connection.createChannel();
        //聲明隊列
        channel.queueDeclare(Send.QUEUE_NAME, false, false, false, null);
        //定義消費者
        DefaultConsumer consumer1 = new DefaultConsumer(channel) {
            //消息到達 觸發這個方法
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, StandardCharsets.UTF_8);
                System.out.println("[2] receive msg :" + msg);
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[1] done");
                    /**
                     * 手動回執一個消息
                     * deliveryTag:該消息的index
                     * multiple:是否批量.true:將一次性ack所有小於deliveryTag的消息。
                     */
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };

        /**
         * 自動應答改成false
         */
        boolean autoAck = false;
        channel.basicConsume(Send.QUEUE_NAME, autoAck, consumer1);
    }
}

現象:

消費之2處理的消息比消費者1多,能者多勞

  • autoAck消息應答:

    boolean autoAck = false;
    channel.basicConsume(Send.QUEUE_NAME, autoAck, consumer1);

    ①如果ack爲true

    自動確認模式,一旦rabbitmq把消息給到消費者,會自動從內存中吧消息刪除

    這種情況下,如果殺死正在執行的消費者,就會丟失正在處理的消息

    ②如果ack爲false

    手動確認模式,如果有一個消費者掛掉,就會交付給其他消費者,消費者執行成功後可以發送一個消息應答,告知mq已經消費完畢,可以刪除消息了

    消息應答默認是打開的,false

    Message Acknowkedgment

  • 消息的持久化

    如果mq掛了,因爲消息在內存中存儲,我們的消息仍然會丟失

    //聲明隊列
    channel.queueDeclare(QUEUE_NAME, false, false, false, null);

    queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,                             Map<String, Object> arguments) throws IOException;
    

    durable 就是是否持久化,生產端和消費端都要,聲明好的隊列不能修改,true就是持久化,這是隊列持久化

    channel.basicPublish(EXCHANGE_NAME,
                    routingKey,
                    MessageProperties.MINIMAL_PERSISTENT_BASIC//持久化消息
                    , msg.getBytes());
    

    增加properties,這個properties 就是消費端 callback函數中的properties

    delivery_mode = 2 持久化消息

4、訂閱模式(publish/subscribe)

RocketMQ的設計上,是不考慮消息去重的問題,即不考慮消息是否會重複的消費的問題,而是將這個問題拋給業務端自己去處理冪等的問題。

  • 模型:

​ work模式,一個生產者對應一個隊列 一個隊列對應多個消費者

​ 訂閱模式,一個生產者對應多個隊列 一個隊列對應一個消費者

​ 生產者沒有直接把消息發到隊列中,而是發送到了交換機

  • send:
package com.my.test.rabbitmq.publish_subscribe;

import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * @author QinHe at 2019-09-29
 */
@SuppressWarnings("DuplicatedCode")
public class Send {
    final static String EXCHANGE_NAME = "test_exchange_fanout";

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //獲取連接
        Connection connection = ConnectionUtils.getConnection();
        //獲取channel
        Channel channel = connection.createChannel();

        //聲明交換機
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");//分發

        //發送消息
        String msg = "hello ps";
        channel.basicPublish(EXCHANGE_NAME, "", null, msg.getBytes());
        System.out.println("send :" + msg);
        channel.close();
        connection.close();
    }
}

​ 消息哪裏去了?

​ 丟失了,rabbitmq中只有隊列有存儲能力,因爲這時候還沒有隊列綁定到這個交換機,所以消息就丟失了

  • receive:
package com.my.test.rabbitmq.publish_subscribe;

import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * @author QinHe at 2019-09-29
 */
@SuppressWarnings("DuplicatedCode")
public class Receive1 {
    final static String QUEUE_NAME = "test_ps_fanout_queue1";

    public static void main(String[] args) throws IOException, TimeoutException {
        //獲取連接
        Connection connection = ConnectionUtils.getConnection();
        //獲取channel
        Channel channel = connection.createChannel();
        //聲明隊列
        channel.queueDeclare(QUEUE_NAME, true, false, false, null);

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

        /**
         * 每個消費者 發送確認消息之前,消息隊列不發送下一個消息到消費者,一次只處理一條消息
         */
        channel.basicQos(1);

        //定義消費者
        DefaultConsumer consumer1 = new DefaultConsumer(channel) {
            //消息到達 觸發這個方法
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, StandardCharsets.UTF_8);
                System.out.println("[1] receive msg :" + msg);
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[1] done");
                    /**
                     * 手動回執一個消息
                     * deliveryTag:該消息的index
                     * multiple:是否批量.true:將一次性ack所有小於deliveryTag的消息。
                     */
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };

        /**
         * 自動應答改成false
         */
        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME, autoAck, consumer1);
    }
}

package com.my.test.rabbitmq.publish_subscribe;

import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * @author QinHe at 2019-09-29
 */
@SuppressWarnings("DuplicatedCode")
public class Receive2 {
    final static String QUEUE_NAME = "test_ps_fanout_queue2";

    public static void main(String[] args) throws IOException, TimeoutException {
        //獲取連接
        Connection connection = ConnectionUtils.getConnection();
        //獲取channel
        Channel channel = connection.createChannel();
        //聲明隊列
        channel.queueDeclare(QUEUE_NAME, true, false, false, null);

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

        /**
         * 每個消費者 發送確認消息之前,消息隊列不發送下一個消息到消費者,一次只處理一條消息
         */
        channel.basicQos(1);

        //定義消費者
        DefaultConsumer consumer1 = new DefaultConsumer(channel) {
            //消息到達 觸發這個方法
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, StandardCharsets.UTF_8);
                System.out.println("[2] receive msg :" + msg);
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[2] done");
                    /**
                     * 手動回執一個消息
                     * deliveryTag:該消息的index
                     * multiple:是否批量.true:將一次性ack所有小於deliveryTag的消息。
                     */
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };

        /**
         * 自動應答改成false
         */
        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME, autoAck, consumer1);
    }
}

  • Exchange 交換機 轉換器

    ①一方面是接收生產者的消息,另一方面是向隊列推送消息

    ②匿名轉發 exchange=""

    basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException;
    

    ③fanout(不處理路由鍵)只要是跟exchange綁定的隊列都可以接收到消息

    //聲明交換機
    channel.exchangeDeclare(EXCHANGE_NAME, "fanout");//分發
    

    ④direct(處理路由鍵)

5、路由模式

send:

package com.my.test.rabbitmq.routing;

import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * @author QinHe at 2019-09-29
 */
@SuppressWarnings("DuplicatedCode")
public class Send {
    final static String EXCHANGE_NAME = "test_exchange_direct";

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //獲取連接
        Connection connection = ConnectionUtils.getConnection();
        //獲取channel
        Channel channel = connection.createChannel();

        //聲明交換機
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);//direct

        //發送消息
        String msg = "hello direct";
        //routing key
        String routingKey = "error";
        channel.basicPublish(EXCHANGE_NAME, routingKey, null, msg.getBytes());
        System.out.println("send :" + msg);
        channel.close();
        connection.close();
    }
}

receive1:

package com.my.test.rabbitmq.routing;

import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * @author QinHe at 2019-09-29
 */
@SuppressWarnings("DuplicatedCode")
public class Receive1 {
    final static String QUEUE_NAME = "test_routing_queue1";

    public static void main(String[] args) throws IOException, TimeoutException {
        //獲取連接
        Connection connection = ConnectionUtils.getConnection();
        //獲取channel
        Channel channel = connection.createChannel();
        //聲明隊列
        channel.queueDeclare(QUEUE_NAME, true, false, false, null);

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

        /**
         * 每個消費者 發送確認消息之前,消息隊列不發送下一個消息到消費者,一次只處理一條消息
         */
        channel.basicQos(1);

        //定義消費者
        DefaultConsumer consumer1 = new DefaultConsumer(channel) {
            //消息到達 觸發這個方法
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, StandardCharsets.UTF_8);
                System.out.println("[1] receive msg :" + msg);
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[1] done");
                    /**
                     * 手動回執一個消息
                     * deliveryTag:該消息的index
                     * multiple:是否批量.true:將一次性ack所有小於deliveryTag的消息。
                     */
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };

        /**
         * 自動應答改成false
         */
        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME, autoAck, consumer1);
    }
}

receive2:

package com.my.test.rabbitmq.routing;

import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * @author QinHe at 2019-09-29
 */
@SuppressWarnings("DuplicatedCode")
public class Receive2 {
    final static String QUEUE_NAME = "test_routing_queue2";

    public static void main(String[] args) throws IOException, TimeoutException {
        //獲取連接
        Connection connection = ConnectionUtils.getConnection();
        //獲取channel
        Channel channel = connection.createChannel();
        //聲明隊列
        channel.queueDeclare(QUEUE_NAME, true, false, false, null);

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



        /**
         * 每個消費者 發送確認消息之前,消息隊列不發送下一個消息到消費者,一次只處理一條消息
         */
        channel.basicQos(1);

        //定義消費者
        DefaultConsumer consumer1 = new DefaultConsumer(channel) {
            //消息到達 觸發這個方法
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, StandardCharsets.UTF_8);
                System.out.println("[2] receive msg :" + msg);
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[2] done");
                    /**
                     * 手動回執一個消息
                     * deliveryTag:該消息的index
                     * multiple:是否批量.true:將一次性ack所有小於deliveryTag的消息。
                     */
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };

        /**
         * 自動應答改成false
         */
        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME, autoAck, consumer1);
    }
}

6、topic主題模式

  • 將路由和某個模式匹配

  • topic exchange

  • #:匹配一個或者多個

  • *:匹配一個

  • 商品 Goods.# (Goods.fruit)

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-KfiY97xd-1570516233843)(E:\anotes_and_information\一些後端服務\rabbitmq\Snipaste_2019-09-30_11-46-24.png)]

    send:

package com.my.test.rabbitmq.topic;

import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * @author QinHe at 2019-09-29
 */
@SuppressWarnings("DuplicatedCode")
public class Send {
    final static String EXCHANGE_NAME = "test_exchange_topic";

    public static void main(String[] args) throws IOException, TimeoutException {
        //獲取連接
        Connection connection = ConnectionUtils.getConnection();
        //獲取channel
        Channel channel = connection.createChannel();

        //聲明交換機
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);//topic

        //發送消息
        String msg = "商品......";
        //routing key
        String routingKey = "goods.add";
//        String routingKey = "goods.delete";
//        String routingKey = "goods.update";
        channel.basicPublish(EXCHANGE_NAME,
                routingKey,
                MessageProperties.MINIMAL_PERSISTENT_BASIC//持久化消息
                , msg.getBytes());
        System.out.println("------send :" + msg);
        channel.close();
        connection.close();
    }
}

receive1:

package com.my.test.rabbitmq.topic;

import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * @author QinHe at 2019-09-29
 */
@SuppressWarnings("DuplicatedCode")
public class Receive1 {
    private final static String QUEUE_NAME = "test_topic_queue1";

    public static void main(String[] args) throws IOException, TimeoutException {
        //獲取連接
        Connection connection = ConnectionUtils.getConnection();
        //獲取channel
        Channel channel = connection.createChannel();
        //聲明隊列
        channel.queueDeclare(QUEUE_NAME, true, false, false, null);

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

        /**
         * 每個消費者 發送確認消息之前,消息隊列不發送下一個消息到消費者,一次只處理一條消息
         */
        channel.basicQos(1);

        //定義消費者
        DefaultConsumer consumer1 = new DefaultConsumer(channel) {
            //消息到達 觸發這個方法
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, StandardCharsets.UTF_8);
                System.out.println("[1] receive msg :" + msg);
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[1] done");
                    /**
                     * 手動回執一個消息
                     * deliveryTag:該消息的index
                     * multiple:是否批量.true:將一次性ack所有小於deliveryTag的消息。
                     */
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };

        /**
         * 自動應答改成false
         */
        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME, autoAck, consumer1);
    }
}

receive2:

package com.my.test.rabbitmq.topic;

import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * @author QinHe at 2019-09-29
 */
@SuppressWarnings("DuplicatedCode")
public class Receive2 {
    /**
     * 如果和receive1是同一個隊列名,則每個消息只會有一個消費者消費
     */
    private final static String QUEUE_NAME = "test_topic_queue2";

    public static void main(String[] args) throws IOException, TimeoutException {
        //獲取連接
        Connection connection = ConnectionUtils.getConnection();
        //獲取channel
        Channel channel = connection.createChannel();
        //聲明隊列
        channel.queueDeclare(QUEUE_NAME, true, false, false, null);

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

        /**
         * 每個消費者 發送確認消息之前,消息隊列不發送下一個消息到消費者,一次只處理一條消息
         */
        channel.basicQos(1);

        //定義消費者
        DefaultConsumer consumer1 = new DefaultConsumer(channel) {
            //消息到達 觸發這個方法
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, StandardCharsets.UTF_8);
                System.out.println("[2] receive msg :" + msg);
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[2] done");
                    /**
                     * 手動回執一個消息
                     * deliveryTag:該消息的index
                     * multiple:是否批量.true:將一次性ack所有小於deliveryTag的消息。
                     */
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };

        /**
         * 自動應答改成false
         */
        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME, autoAck, consumer1);
    }
}

7、rabbitmq消息確認機制

在rabbitmq中,我們可以通過持久化數據 解決rabbitmq服務器的異常的數據丟失問題

問題:生產者將消息發送出去之後,消息到底有沒有到達rabbitmq服務器

兩種方式:

①、AMQP實現了事務機制

txSelect txCommit txRollback

txSelect :用於將當前channel設置成transaction模式

txCommit :用於提交事務

txRollback:回滾事務

send:

package com.my.test.rabbitmq.transaction;

import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * @author QinHe at 2019-09-18
 */
@SuppressWarnings("DuplicatedCode")
public class Send {

    static final String QUEUE_NAME = "test_simple_queue_tx";

    public static void main(String[] args) throws IOException, TimeoutException {
        //獲取連接
        Connection connection = ConnectionUtils.getConnection();

        //從連接中創建一個管道
        Channel channel = connection.createChannel();

        //創建隊列聲明
        channel.queueDeclare(QUEUE_NAME, true, false, false, null);

        String msg = "hello tx";

        try {
            /**
             * 開啓事務
             */
            channel.txSelect();

            /**
             * 發送消息
             */
            channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());

            /**
             * 提交事務
             */
            channel.txCommit();
            System.out.println("send " + msg);
        } catch (Exception e) {
            e.printStackTrace();
            /**
             * 回滾事務
             */
            channel.txRollback();
            System.out.println("send rollback " + msg);
        }

        channel.close();
        connection.close();

    }
}

receive:

package com.my.test.rabbitmq.transaction;

import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;

/**
 * @author QinHe at 2019-09-18
 */
@SuppressWarnings("DuplicatedCode")
public class Receive {

    public static void main(String[] args) throws IOException, TimeoutException {

        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare(Send.QUEUE_NAME, true, false, false, null);
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, StandardCharsets.UTF_8);
                System.out.println(Send.QUEUE_NAME + "receive:" + msg);
            }
        };

        channel.basicConsume(Send.QUEUE_NAME, true, consumer);
    }

}

②、confirm模式

生產者端confirm模式的實現原理:

  • 生產者將信道設置成confirm模式,一旦信道進入confirm模式,所有在該信道上發佈的消息都會被指派一個唯一的ID(從1開始),一旦消息被投遞到所有匹配的隊列之後,broker就會發送一個確認給生產者(包含消息的唯一ID),這就使得生產者知道消息已經正確到達目的隊列了,如果消息和隊列是可持久化的,那麼確認消息會將消息寫入磁盤之後發送給生產者。broker回傳給生產者的確認消息中deliver-tag域中包含了確認消息的序列號,此外broker也可以設置basic.ack的multiple域,表示到這個序列號之前的所有消息都已經得到了處理。

  • confirm模式最大的好處在於異步

開啓confirm模式:channel.confirmSelect()

編程模式:

​ 1、普通 發一條 waitForConfirms()

package com.my.test.rabbitmq.confirm;

import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * @author QinHe at 2019-09-18
 * 單條確認
 */
@SuppressWarnings("DuplicatedCode")
public class Send1 {

    static final String QUEUE_NAME = "test_queue_confirm1";

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //獲取連接
        Connection connection = ConnectionUtils.getConnection();

        //從連接中創建一個管道
        Channel channel = connection.createChannel();

        //創建隊列聲明
        channel.queueDeclare(QUEUE_NAME, true, false, false, null);

        /**
         * 將channel設爲confirm模式,注意不能同時設爲事務模式
         */
        channel.confirmSelect();

        String msg = "hello confirm";

        channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());

        if (!channel.waitForConfirms()) {
            System.out.println("message send failed");
        } else {
            System.out.println("send success " + msg);
        }

        channel.close();
        connection.close();

    }
}

​ 2、批量的 發一批 waitForConfirms()

package com.my.test.rabbitmq.confirm;

import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * @author QinHe at 2019-09-18
 * 批量確認
 */
@SuppressWarnings("DuplicatedCode")
public class Send2 {

    static final String QUEUE_NAME = "test_queue_confirm2";

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //獲取連接
        Connection connection = ConnectionUtils.getConnection();

        //從連接中創建一個管道
        Channel channel = connection.createChannel();

        //創建隊列聲明
        channel.queueDeclare(QUEUE_NAME, true, false, false, null);

        /**
         * 將channel設爲confirm模式,注意不能同時設爲事務模式
         */
        channel.confirmSelect();

        String msg = "hello confirm";

        //批量發送
        for (int i = 0; i < 10; i++) {
            channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
        }

        //確認
        if (!channel.waitForConfirms()) {
            System.out.println("message send failed");
        } else {
            System.out.println("send success " + msg);
        }

        channel.close();
        connection.close();

    }
}

​ 3、異步confirm模式 提供一個回調方法

​ channel對象提供的ConfirmListen()回調方法中質保函deliveryTag(當前channel發出的消息序號),我們需要自己爲每一個channel維護一個unconfirm的消息序號集合,每publish一條數據,集合中元素加1,每回調一次handleAck方法,unconfirm集合刪掉相應的一條(multiple=false) 或者多條(multiple=true)記錄。從程序運行效率上看,這個unconfirm集合最好採用有序集合SortedSet存儲結構

thor QinHe at 2019-09-18

  • 批量確認
    */
    @SuppressWarnings(“DuplicatedCode”)
    public class Send2 {

    static final String QUEUE_NAME = “test_queue_confirm2”;

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
    //獲取連接
    Connection connection = ConnectionUtils.getConnection();

     //從連接中創建一個管道
     Channel channel = connection.createChannel();
    
     //創建隊列聲明
     channel.queueDeclare(QUEUE_NAME, true, false, false, null);
    
     /**
      * 將channel設爲confirm模式,注意不能同時設爲事務模式
      */
     channel.confirmSelect();
    
     String msg = "hello confirm";
    
     //批量發送
     for (int i = 0; i < 10; i++) {
         channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
     }
    
     //確認
     if (!channel.waitForConfirms()) {
         System.out.println("message send failed");
     } else {
         System.out.println("send success " + msg);
     }
    
     channel.close();
     connection.close();
    

    }
    }


​		3、異步confirm模式  提供一個回調方法

​			channel對象提供的ConfirmListen()回調方法中質保函deliveryTag(當前channel發出的消息序號),我們需要自己爲每一個channel維護一個unconfirm的消息序號集合,每publish一條數據,集合中元素加1,每回調一次handleAck方法,unconfirm集合刪掉相應的一條(multiple=false) 或者多條(multiple=true)記錄。從程序運行效率上看,這個unconfirm集合最好採用有序集合SortedSet存儲結構













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