rabbit mq 學習

% rabbitMQ learn
% qijun
% 19/01/2018

mq 的一些概念

  • mq: mq 是一個message broker (消息中介)
  • AMQP (Advanced Message Queue ) 一個標準的消息隊列標準
  • RabbitMQ是一個由erlang開發的AMQP(Advanced Message Queue )的開源實現

rabbit mq 的一些概念

rabbit mq 的適用場景架構圖

image.png
  • Client A &Client B 為消息的producer 消息由payload 和 label 組成,label是exchange的名字或者說是一個tag,它描述了payload,而且RabbitMQ也是通過這個label來決定把這個Message發給哪個Consumer
  • client 1 & client2 & client3 消息的consumer, 消息的接受者 接收到的消息是去除label 的消息,緊包含消息的內容,消費者通過訂閱隊列獲取消息。
  • 中間是的 rabbit server 由 交換器,routingKey 和queue 組成,交換器和queue 通過routingKey 綁定,消息通過交換器和routingKey 路由到相應的queue
  • Connection: 就是一個TCP的連接。Producer和Consumer都是通過TCP連接到RabbitMQ Server的。程序的起始處就是建立這個TCP連接。
  • Channels: 虛擬連接。它建立在上述的TCP連接中。數據流動都是在Channel中進行的。也就是說,一般情況是程序起始建立TCP連接,第二步就是建立這個Channel。

四種交換器

由上面可知,消息通過交換器,通過對應的routekey 路由到queue, 交換器的類型一共有三種

  1. direct 如果 routing key 匹配, 那么Message就會被傳遞到相應的queue中
  2. fanout 廣播到所有綁定的queue(假設你有一個消息需要發送給a和b,如果現在還需要發送給c,使用fanout 交換器,只需要在c的代碼中創建一個隊列,然后綁定到fanout 交換器即可)
  3. topic 對key進行模式匹配,比如ab.1,ab.2都可以傳遞到所有routingkey 為ab.*的queue
    基于topic類型交換器的routing key不是唯一的,而是一系列詞,基于點區分。
    例如:"stock.usd.nyse", "nyse.vmw", "quick.orange.rabbit"
    binding key也是。*表示只匹配一個關鍵字 #可以匹配0或者多個關鍵字。
    比如*.a.b的隊列接受1.a.b 或者2.a.b等等
  4. header header交換器和 direct幾乎一樣,性能更差,基本不會用到

匿名交換器(默認)

事實上,你在代碼中不創建交換器也是可以通過rabbit mq 發送消息的,因為rabbit 提供了默認的交換器。

image.png

如圖中空白字符串名字的交換器為默認的交換器,類型為direct
本質上所有的消息發送都要送往exchange(可以沒有隊列,但不能沒有交換機,沒有隊列時消息直接被丟棄)。
RabbitMQ提供了一種直接向Queue發送消息的快捷方法:直接使用未命名的exchange,不用綁定routing_key,直接用它指定隊列名。

  channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        // 發送消息
        String message = "Hello World!";
        // basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
        // 參數1 exchange :交換器
        // 參數2 routingKey : 路由鍵
        // 參數3 props : 消息的其他參數
        // 參數4 body : 消息體
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes());

消息的確認和拒絕

使用ack確認Message的正確傳遞
默認情況下,如果Message 已經被某個Consumer正確的接收到了,那么該Message就會被從queue中移除。當然也可以讓同一個Message發送到很多的Consumer
如果一個queue沒被任何的Consumer Subscribe(訂閱),那么,如果這個queue有數據到達,那么這個數據會被cache,不會被丟棄。當有Consumer時,這個數據會被立即發送到這個Consumer,這個數據被Consumer正確收到時,這個數據就被從queue中刪除。
那么什么是正確收到呢?通過ack。每個Message都要被acknowledged(確認,ack)。我們可以顯示的在程序中去ack,也可以自動的ack。
如果在收到數據后處理數據時程序發生錯誤,無法正確處理數據,而是被reject。reject 參數設為true時RabbitMQ Server會把這個信息發送到下一個Consumer,設為false也可以從隊列中把這條消息刪除。
如果這個app有bug,忘記了ack,那么RabbitMQ Server不會再發送數據給它,因為Server認為這個Consumer處理能力有限。
而且ack的機制可以起到限流的作用(Benefitto throttling):在Consumer處理完成數據后發送ack,甚至在額外的延時后發送ack,將有效的balance Consumer的load。

在什么地方創建queue

Consumer和Procuder都可以通過 queue.declare 創建queue。對于某個Channel來說,Consumer不能declare一個queue,卻訂閱其他的queue。當然也可以創建私有的queue。這樣只有app本身才可以使用這個queue。queue也可以自動刪除,被標為auto-delete的queue在最后一個Consumer unsubscribe后就會被自動刪除。那么如果是創建一個已經存在的queue呢?那么不會有任何的影響。需要注意的是沒有任何的影響,也就是說第二次創建如果參數和第一次不一樣,那么該操作雖然成功,但是queue的屬性并不會被修改。

那么誰應該負責創建這個queue呢?是Consumer,還是Producer?

如果queue不存在,當然Consumer不會得到任何的Message。但是如果queue不存在,那么Producer Publish的Message會被丟棄。所以,還是為了數據不丟失,Consumer和Producer都try to create the queue!反正不管怎么樣,這個接口都不會出問題。
queue對load balance的處理是完美的。對于多個Consumer來說,RabbitMQ 使用循環的方式(round-robin)的方式均衡的發送給不同的Consumer。

VirtualHost

在RabbitMQ中可以虛擬消息服務器VirtualHost,每個VirtualHost相當月一個相對獨立的RabbitMQ服務器,每個VirtualHost之間是相互隔離的。exchange、queue、message不能互通。
在RabbitMQ中無法通過AMQP創建VirtualHost,可以通過以下命令來創建。
rabbitmqctl add_vhost [vhostname]

windows下如何安裝rabbit mq

  1. rabbit mq 運行于erlang之上,需要先安裝erlang http://www.erlang.org/downloads 下載,并使用管理員運行安裝
  2. 安裝rabbit mq https://www.rabbitmq.com/download.html
  3. 新增環境變量 ERLANG_HOME= C:\Program Files\erl9.2
    RABBITMQ_SERVER = C:\Program Files\RabbitMQ Server\rabbitmq_server-3.7.2
    配置環境變量
    Path=%ERLANG_HOME%\bin;%RABBITMQ_SERVER%\sbin
  4. 替換 erlang cookie
    拷貝C:\WINDOWS 下的.erlang.cookie (還有可能在C:\Windows\System32\config\systemprofile)文件替換 C:\Users%USERNAME%.erlang.cookie 或者 C:\Documents and
    Settings%USERNAME%.erlang.cookie
  5. 通過startMenu 啟動erlang 服務 和停止 rabbit mq 可以以服務的方式和按進程的方式啟動,建議使用服務方式啟動,然后在rabbit mq的命令行(RabbitMQ Command Prompt 開始菜單中) 執行 rabbitmq-plugins enable rabbitmq_management
    最后就可以通過 http://localhost:15672/ 賬號guest 密碼guest 訪問rabbit mq的控制臺 /是默認的VirtualHost

常用命令

停止 broker
查詢 broker 狀態 rabbitmqctl status
更多的命令請查閱 https://www.rabbitmq.com/man/rabbitmqctl.8.html

實戰

下面會通過兩個例子,演示如何使用rabbitmq,第一個原生的java api 使用direct 交換器演示 routing,第二個例子使用topic 交換器。spring mvc,spring boot 中的注解和接口本質上是對原生接口的包裝,spring 會隱藏一些操作,對理解rabbit mq的工作流程會造成阻礙,先使用原生api做演示一般的工作流程,而后結合springboot 演示在項目中如何使用rabbit mq。

rabbitmq 消費者和生產者兩端的在處理消息時經歷的步驟

  1. 創建連接工廠ConnectionFactory
  2. 通過連接獲取通信通道Channel
  3. 聲明交換機Exchange(可選)
  4. 申明隊列(可選)
  5. 綁定交換機和隊列(可選)
    之后生產者通過channel發送消息,消費者獲取并處理消息

rabbitmq comsumer 消息獲取方式

rabbitMQ中consumer通過建立到queue的連接,創建channel對象,通過channel通道獲取message,
Consumer可以聲明式的以API輪詢poll的方式主動從queue的獲取消息,也可以通過訂閱的方式被動的從Queue中消費消息。

使用原生rabbitmq api 的例子

代碼發送三種類型的日志到交換器,交換器通過routingkey 分發到不同的queue

maven 依賴

   <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>3.6.3</version>
        </dependency>

消息發送

public class EmitLogDirect {
    private static final String EXCHANGE_NAME = "direct_logs";
    private static final String[] LOG_LEVEL_ARR = {"debug", "info", "error"};

    public static void main(String[] args) throws IOException, TimeoutException {
        // 創建連接
        ConnectionFactory factory = new ConnectionFactory();
        // 設置 RabbitMQ 的主機名
        factory.setHost("localhost");
        // 創建一個連接
        Connection connection = factory.newConnection();
        // 創建一個通道
        Channel channel = connection.createChannel();
        // 指定一個交換器
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");
        // 發送消息
        for (int i = 0; i < 10; i++)  {
            int rand = new Random().nextInt(3);
            String severity  = LOG_LEVEL_ARR[rand];
            String message = "Qijun-MSG log : [" +severity+ "]" + UUID.randomUUID().toString();
            // 發布消息至交換器
            channel.basicPublish(EXCHANGE_NAME, severity, null, message.getBytes());
            System.out.println(" [x] Sent '" + message + "'");
        }
        // 關閉頻道和連接
        channel.close();
        connection.close();
    }
}

消息接收

public class ReceiveLogsDirect {
    private static final String EXCHANGE_NAME = "direct_logs";
    private static final String[] LOG_LEVEL_ARR = {"debug", "info", "error"};

    public static void main(String[] args) throws IOException, TimeoutException {
        // 創建連接
        ConnectionFactory factory = new ConnectionFactory();
        // 設置 RabbitMQ 的主機名
        factory.setHost("localhost");
        // 創建一個連接
        Connection connection = factory.newConnection();
        // 創建一個通道
        Channel channel = connection.createChannel();
        // 指定一個交換器
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");
        // 設置日志級別
        int rand = new Random().nextInt(3);

        // 創建三個非持久的、唯一的、自動刪除的隊列,分別接收不同的日志信息
        String debugQueueName = channel.queueDeclare().getQueue();
        String InfoQueueName = channel.queueDeclare().getQueue();
        String ErrorQueueName = channel.queueDeclare().getQueue();
        // 綁定交換器和隊列
        // queueBind(String queue, String exchange, String routingKey)
        // 參數1 queue :隊列名
        // 參數2 exchange :交換器名
        // 參數3 routingKey :路由鍵名
        channel.queueBind(debugQueueName, EXCHANGE_NAME, LOG_LEVEL_ARR[0]);
        channel.queueBind(InfoQueueName, EXCHANGE_NAME, LOG_LEVEL_ARR[1]);
        channel.queueBind(ErrorQueueName, EXCHANGE_NAME, LOG_LEVEL_ARR[2]);

        // rabbit mq 消息的推送支持poll 也支持訂閱,先創建一個poll 方式的comsumer
        QueueingConsumer pollConsumer = new QueueingConsumer(channel);
        channel.basicConsume(ErrorQueueName, true, pollConsumer);

        // 創建訂閱類型的消費者
        final 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");
                System.out.println("Received '" + message + "' from "+envelope.getRoutingKey()+ " by subscribe" );
            }
        };
        channel.basicConsume(debugQueueName, true, consumer);
        channel.basicConsume(InfoQueueName, true, consumer);

        // 通過 循環poll 獲取隊列中的所有消息  
        while (true) {
            QueueingConsumer.Delivery delivery = null;
            try {
                delivery = pollConsumer.nextDelivery();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String message = new String(delivery.getBody());
            String routingKey = delivery.getEnvelope().getRoutingKey();


            System.out.println("Received '" + message + "' from "+routingKey +" by poll");
        }

    }
}

源碼

springboot 中使用rabbit mq 的例子

maven 依賴

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

ConnectionFactory配置

// 項目中可通過配置文件讀取來獲取 connect 參數
 @Bean
    public CachingConnectionFactory rabbitConnectionFactory() {
        CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory();
        cachingConnectionFactory.setHost("localhost");
        cachingConnectionFactory.setPort(5672);
        cachingConnectionFactory.setUsername("guest");
        cachingConnectionFactory.setPassword("guest");
        cachingConnectionFactory.setVirtualHost("/");
        return cachingConnectionFactory;
    }

CachingConnectionFactory 內部通過com.rabbitmq.client.ConnectionFactory 去設置 connect的參數

public abstract class AbstractConnectionFactory implements ConnectionFactory, DisposableBean, BeanNameAware {
    private static final String BAD_URI = "setUri() was passed an invalid URI; it is ignored";
    protected final Log logger = LogFactory.getLog(this.getClass());
    private final com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory;

通過 RabbitAdmin 配置隊列,交換機和binding

   public static final String  ROUTER_KEY_1 = "*.orange.*";
 @Bean
    public RabbitAdmin rabbitAdmin() {
        RabbitAdmin rabbitAdmin = new RabbitAdmin(rabbitConnectionFactory());
       //申明一個 一個topic類型的交換機,routingkey 使用通配符
        TopicExchange topicExchange =(TopicExchange)ExchangeBuilder.topicExchange(QUEUE_EXCHANGE_NAME).durable(true).build();
        rabbitAdmin.declareExchange(topicExchange);
        Queue firstQueue = new Queue(QUEUE_NAME);
        rabbitAdmin.declareQueue(firstQueue);
        rabbitAdmin.declareBinding(BindingBuilder.bind(firstQueue).to(topicExchange).with(ROUTER_KEY_1));
        return rabbitAdmin;
    }

消息消費的兩種方法(推薦使用第二種,更靈活)

  1. 通過SimpleMessageListenerContainer 綁定特定的messageListener
@Bean
    MessageListenerAdapter listenerAdapter(Receiver receiver) {
        return new MessageListenerAdapter(receiver, "receive2");
    }
 @Bean
    SimpleMessageListenerContainer container(MessageListenerAdapter messageListenerAdapter) {
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(rabbitConnectionFactory());
        container.setQueueNames(QUEUE_NAME);
        container.setMessageListener(messageListenerAdapter);
        return container;
    }
@Service
public class Receiver {

    public void receiveMessage(String message) {
        System.out.println("Received<" + message + ">");
    }

    public void receive2(String in) throws InterruptedException {
        System.out.println("in message"+in);
    }
}
  1. 使用 SimpleRabbitListenerContainerFactory 和 @RabbitListener 方式接收mq 的消息
  @Bean
    public SimpleRabbitListenerContainerFactory myContainerFactory(
            SimpleRabbitListenerContainerFactoryConfigurer configurer,
            ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        //設置了每個消費者再不回ack的情況下最大可接收消息的條數
        factory.setPrefetchCount(100);
        configurer.configure(factory, connectionFactory);
        return factory;
    }
/**
 * @author 祁軍
 * 使用 SimpleRabbitListenerContainerFactory 和 @RabbitListener 方式接收mq 的消息
 */
@Service
public class Receiver1 {
    @RabbitListener(queues = "${rabbitConfiguration.queue}", containerFactory = "myContainerFactory")
    public void processMessage(String msg){
        System.out.println("Receiver1 got message" + msg);
    }
}

sender

@Service
public class Sender {
    private RabbitTemplate rabbitTemplate;

    @Autowired
    public Sender(RabbitTemplate rabbitTemplate) {
        this.rabbitTemplate = rabbitTemplate;
    }

    public void send() {
       // 發送兩次routing key不同 由于 是topic exchange routing key 為通配符可達到同一隊列
        System.out.println("sender is sending message");
        rabbitTemplate.convertAndSend(RabbitMQConfig.QUEUE_EXCHANGE_NAME,"aaa.orange.bbb", "hello,world1");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        rabbitTemplate.convertAndSend(RabbitMQConfig.QUEUE_EXCHANGE_NAME,"aaa.orange.ccc", "hello,world2");
    }
}

測試

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Application.class)
public class RabbitMQTest {

    @Autowired
    private Sender sender;

    @Test
    public void send() throws Exception {
        sender.send();
    }

}

源碼

rabbit mq 的其他應用場景

working queue

當有Consumer需要大量的運算時,RabbitMQ Server需要一定的分發機制來balance每個Consumer的load。試想一下,對于web application來說,在一個很多的HTTP request里是沒有時間來處理復雜的運算的,只能通過后臺的一些工作線程來完成。應用場景就是RabbitMQ Server會將queue的Message分發給不同的Consumer以處理計算密集型的任務。

image.png

RPC

MQ本身是基于異步的消息處理,前面的示例中所有的生產者(P)將消息發送到RabbitMQ后不會知道消費者(C)處理成功或者失?。ㄉ踔吝B有沒有消費者來處理這條消息都不知道)。
但實際的應用場景中,我們很可能需要一些同步處理,需要同步等待服務端將我的消息處理完成后再進行下一步處理。這相當于RPC(Remote Procedure Call,遠程過程調用)。在RabbitMQ中也支持RPC。

image.png

RabbitMQ中實現RPC的機制是:

  1. 客戶端發送請求(消息)時,在消息的屬性(MessageProperties,在AMQP協議中定義了14中properties,這些屬性會隨著消息一起發送)中設置兩個值replyTo(一個Queue名稱,用于告訴服務器處理完成后將通知我的消息發送到這個Queue中)和correlationId(此次請求的標識號,服務器處理完成后需要將此屬性返還,客戶端將根據這個id了解哪條請求被成功執行了或執行失?。?/li>
  2. 服務器端收到消息并處理
  3. 服務器端處理完消息后,將生成一條應答消息到replyTo指定的Queue,同時帶上correlationId屬性
  4. 客戶端之前已訂閱replyTo指定的Queue,從中收到服務器的應答消息后,根據其中的correlationId屬性分析哪條請求被執行了,根據執行結果進行后續業務處理

rabbitmq 消息的可靠性

  1. 發送端的comfirm 機制,通過注冊回調,我們可以知道消息是否已經發送到exchange 或者queue,如果沒有正確發送,我們可以通過replycode來判斷進行后續什么操作,然后根據業務場景
    比如發送告警,或者重發來應對。
  2. 消息的持久化,通過交換機,隊列和消息的持久化來實現
  3. rabbitmq 從queue 發消息給消費者,如果消費者選擇no ack 則queue每發一條消息,rabbitmq 就會把消息刪除,如果cosumer 由于某種問題消費消息出錯,rabbitmq也會把消息刪除。
    我們需要在comsumer 關閉自動ack,使用basic ack 手工應答保證消息被正確消費,如果消費失敗,basic nack 可以刪除隊列消息或者重新入原隊列,可能導致死循環
    如果不希望把有問題的消息刪除或者重新入原來的隊列,可以指定一個死信隊列,錯誤的消息重新入死信對列,然后再次被消費。

發送端的ack

rabbitmq提供了確認ack機制,可以用來確認消息是否到broker 或者queue。

/**confirmcallback用來確認消息是否到達broker*/     
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
    if (!ack) {
        //log error
    } else {
        //maybe delete msg in db
    }
});
 /**若消息不能正確的達到指定的隊列會調用 */
rabbitTemplate.setReturnCallback((message, replyCode, replyText, tmpExchange, tmpRoutingKey) -> {
    log.info("send message failed: " + replyCode + " " + replyText);
    // resend message
   
});

消息的持久化

// 交換機的持久化
// 參數1 name :交互器名
// 參數2 durable :是否持久化
// 參數3 autoDelete :當所有消費客戶端連接斷開后,是否自動刪除隊列
new TopicExchange(name, durable, autoDelete)

// 隊列是持久化
// 參數1 name :隊列名
// 參數2 durable :是否持久化
// 參數3 exclusive :僅創建者可以使用的私有隊列,斷開后自動刪除
// 參數4 autoDelete : 當所有消費客戶端連接斷開后,是否自動刪除隊列
new Queue(name, durable, exclusive, autoDelete);

springAMQP  的消息持久化是默認的

消費者端的手工確認

如果一直不回ack,mq會block 這個消費者

      @Bean
    SimpleMessageListenerContainer container() {
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(rabbitConnectionFactory());
        container.setQueueNames(QUEUE_NAME);
        //設定單次可分發給消費則的消息個數
        container.setPrefetchCount(1);
        container.setMaxConcurrentConsumers(1);
        container.setConcurrentConsumers(1);
        container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        container.setMessageListener(new ChannelAwareMessageListener() {

            @Override
            public void onMessage(Message message, Channel channel) throws Exception {
                byte[] body = message.getBody();
                try {
                    log.info("receive msg: " + new String(body));
                    //do something
                } catch (Exception e) {
                } finally {
//                    channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); //確認消息成功消費
                }

            }

        });
        return container;
    }

springAMQP 提供的確認方式

很明顯上述代碼提供的手工確認方式(使用ChannelAwareMessageListener)很不優雅,你需要創建多個bean 然后綁定queue。
當setDefaultRequeueRejected(true) (默認情況下),如果消息被正常消費,container 會ack,然后隊列刪除消息,如果消費者拋出異常,container會reject這個消息,然后這個消息會requeue到原來的消息隊列,如果業務一直處在這個異常情況下,requeue的消息會再次回到消費者,然后死循環,這種情況很顯然不行,spring AMQP 提供的替代方式:listener拋出AmqpRejectAndDontRequeueException,則這個消息會被拋棄,或者進入死信隊列,Listener拋出AmqpRejectAndDontRequeueException還可以通過配置factory 的ErrorHandler 把你拋出的異常 轉換為AmqpRejectAndDontRequeueException,如下式例,如果你的listener 拋出了XMLException 則這個消息會被discard(在沒有配置死信隊列的情況下)。

factory.setErrorHandler(new ConditionalRejectingErrorHandler(
                t -> t instanceof ListenerExecutionFailedException && t.getCause() instanceof XMLException));

factory.setDefaultRequeueRejected(false); 則只要listener 拋出異常,message就會被discard或者轉入死信隊列,如果需要針對不同的異常(比如可短時間內恢復的異常,需要重入原隊列,不可恢復的異常discard 或者入死信隊列)建議設置成true,然后配置ErrorHandler 如上

springAMQP 如何配置死信隊列

當然你可以通過創建一個死信隊列,然后在listener端消費時重新發送到死信隊列,但springAMQP 提供了更好的方式如下

@Bean
TopicExchange exchange()
{
    return new TopicExchange(DEFAULT_EXCHANGE);
}

@Bean
Queue deadLetterQueue()
{
    return new Queue(DEAD_LETTER_QUEUE,true);
}

@Bean
Queue queue()
{
    // 通過args參數為當前隊列綁定一個死信隊列
    Map<String, Object> args = new HashMap<String, Object>();
    args.put("x-dead-letter-exchange", DEFAULT_EXCHANGE);
    args.put("x-dead-letter-routing-key", DEAD_LETTER_QUEUE);
    return new Queue(WORKORDER_QUEUE,true,false,false,args);
}
@Bean
Binding binding(Queue queue, TopicExchange exchange)
{
    return BindingBuilder.bind(queue).to(exchange).with(WORKORDER_QUEUE);
}

@Bean
Binding bindingDeadLetter(Queue deadLetterQueue, TopicExchange exchange)
{
    return BindingBuilder.bind(deadLetterQueue).to(exchange).with(DEAD_LETTER_QUEUE);
}



消費者拋出AmqpRejectAndDontRequeueException 異常時則會進入死信隊列


  @RabbitListener(queues = RabbitConfig.WORKORDER_QUEUE)
    public void processMessage(String msg) throws Exception
    {
        
            throw new AmqpRejectAndDontRequeueException("to dead-letter");
        
    }

死信隊列的消費者

@Service
public class ErrorHandler {
    @RabbitListener(queues = "dead_queue", containerFactory = "myContainerFactory")
    public void handleError(Object message){
        System.out.println("XXXXXXX"+message);
    }
}

其他高級主題

rabbit mq的消息確認機制(包括producer到broker 和broker 到 consumer的確認),集群等等。

參考

https://www.rabbitmq.com/getstarted.html
https://github.com/rabbitmq/rabbitmq-tutorials/tree/master/spring-amqp
https://docs.spring.io/spring-amqp/reference/html/
http://blog.720ui.com/2017/springboot_06_mq_rabbitmq/
http://www.cnblogs.com/xingzc/p/5945030.html
https://www.cnblogs.com/diegodu/p/4971586.html
http://blog.csdn.net/column/details/rabbitmq.html
http://blog.csdn.net/u013256816/article/category/6532725/1

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380