RabbitMQ知识点分享

[TOC]

# 一、功能介绍
RabbitMQ是一种消息中间件,用于异步处理来自客户端的请求。服务端将要发送的信息存入队列池中,接收端根据RabbitMQ配置的转发机制进行接收。RabbitMQ依据指定的转发规则进行消息的转发、缓冲和持久化操作,主要用在多服务器间或单服务器的子系统间进行通信,是分布式系统标准的配置。
# 二、普通队列
## (一)程序流转
![1](./rabbitMQ%E9%98%9F%E5%88%97.png)
从图中可以看出RabbitMQ主要由Exchange和Queue两部分组成,然后通过RoutingKey关联起来,消息投递到Exchange然后通过Queue接收。
## (二)结构组成
### 1. RabbitMQ Server
也叫broker server,它是一种传输服务。他的角色就是维护一条从Producer到Consumer的路线,保证数据能够按照指定的方式进行传输。
### 2. Producer
消息生产者,如图A、B、C,数据的发送方。消息生产者连接RabbitMQ服务器然后将消息投递到Exchange。
### 3. Consumer
消息消费者,如图1、2、3,数据的接收方。消息消费者订阅队列,RabbitMQ将Queue中的消息发送到消息消费者。当有Message到达Queue后,RabbitMQ把它发送给它的某个订阅者即Consumer。当然可能会把同一个Message发送给很多的Consumer。
### 4. Exchange
交换器,生产者将消息发送到Exchange,由Exchange将消息路由到一个或多个Queue中(或者丢弃)。Exchange并不存储消息。RabbitMQ中的Exchange有fanout、direct、topic、headers四种类型,每种类型对应不同的路由规则,后面详细介绍这四种类型。
### 5. Queue
队列,是RabbitMQ的内部对象,用于存储消息。消息消费者就是通过订阅队列来获取消息的,RabbitMQ中的消息都只能存储在Queue中,生产者生产消息并最终投递到Queue中,消费者可以从Queue中获取消息并消费。多个消费者可以订阅同一个Queue,这时Queue中的消息会被平均分摊给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理。
### 6. RoutingKey
生产者在将消息发送给Exchange的时候,一般会指定一个routing key,来指定这个消息的路由规则,而这个routing key需要与Exchange Type及binding key联合使用才能最终生效。在Exchange Type与binding key固定的情况下(在正常使用时一般这些内容都是固定配置好的),我们的生产者就可以在发送消息给Exchange时,通过指定routing key来决定消息流向哪里。RabbitMQ为routing key设定的长度限制为255 bytes。
### 7. Connection
连接,Producer和Consumer都是通过TCP连接到RabbitMQ Server的。以后我们可以看到,程序的起始处就是建立这个TCP连接。
### 8. Channels
信道,它建立在上述的TCP连接中。数据流动都是在Channel中进行的。也就是说,一般情况是程序起始建立TCP连接,第二步就是建立这个Channel。
## (三)交换器类型
### 1. fanout
它会把生产者发送到该Exchange的所有消息路由到所有与它绑定的Queue中,最终被多个消费者消费。
### 2. direct
它会把消息路由到那些binding key与routing key完全匹配的Queue中。
### 3. Default Exchange
这种是特殊的Direct Exchange,是rabbitmq内部默认的一个交换机。该交换机的name是空字符串,所有queue都默认binding 到该交换机上。所有binding到该交换机上的queue,routing-key都和queue的name一样。
### 4. topic
它与direct类型的Exchage相似,也是将消息路由到binding key与routing key相匹配的Queue中,但direct是完全匹配,而通过topic可以进行模糊匹配
```
routing key为一个句点号“.”分隔的字符串,如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”
binding key与routing key一样也是句点号“.”分隔的字符串,binding key中可以存在两种特殊字符“*”与“#”,用于做模糊匹配,其中“*”用于匹配一个单词,“#”用于匹配多个单词(可以是零个),如 “*.usd.nyse”、“nyse.#”、“*.orange.*”
```
### 5. headers
headers类型的Exchange不依赖于routing key与binding key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配。
在绑定Queue与Exchange时指定一组键值对;当消息发送到Exchange时,RabbitMQ会取到该消息的headers(也是一个键值对的形式),对比其中的键值对是否完全匹配Queue与Exchange绑定时指定的键值对;如果完全匹配则消息会路由到该Queue,否则不会路由到该Queue。
# 三、延迟队列
## (一)延时队列应用于什么场景
- 延时队列,即放置在该队列里面的消息是不需要立即消费的,而是等待一段时间之后取出消费。
- 适用案例:
  - 网上商城下订单后30分钟后没有完成支付,取消订单(如:淘宝、去哪儿网)
  - 系统创建了预约之后,需要在预约时间到达前一小时提醒被预约的双方参会
## (二)实现方案
### 1. 常规做法
- 首先将数据存储数据库中,设定执行时间,然后开启定时线程,如每1小时定时抓取下1小时内需要处理的信息列表,如果有则进行处理。
- 优点:
  - 实现简单
- 缺点:
  - 事件的执行时间无法精确设定,获得的为当前时间端内所有需要处理的事件,可能提前10分钟执行,也可能延后。
  - 线程会一直存在,定时每小时扫一次数据,占用资源
### 2. RabbitMQ实现延时队列
- 通过RabbitMQ延时队列,使用延时消息推送,特定消费者消费后,发起提醒。
- 优点:
  - 比较适合短时间的延时推送,且提醒时间准确
- 缺点:
  - 当出现以月、年为周期的数据时,会出现MQ消息积压
  - 解决方案
    - 尽量将延时时间控制在24小时内
    - 当延时间隔<24小时,直接发送延时消息
    - 当延时间隔>24小时,每天晚上12点用定时任务统计下一天需要处理的延时消息,并发送延时消息
## (三)RabbitMQ延迟队列实现方式
### 1. 利用TTL DLX实现延迟队列的方式
- Time To Live(TTL)
  - RabbitMQ可以针对队列设置x-expires(则队列中所有的消息都有相同的过期时间)或者针对Message设置x-message-ttl(对消息进行单独设置,每条消息TTL可以不同),来控制消息的生存时间,如果超时(两者同时设置以最先到期的时间为准),则消息变为dead letter(死信)
- Dead Letter Exchanges(DLX)
  - RabbitMQ的Queue可以配置x-dead-letter-exchange和x-dead-letter-routing-key(可选)两个参数,如果队列内出现了dead letter,则按照这两个参数重新路由转发到指定的队列。
    - x-dead-letter-exchange:出现dead letter之后将dead letter重新发送到指定exchange
    - x-dead-letter-routing-key:出现dead letter之后将dead letter重新按照指定的routing-key发送
- 缺点:会产生消费阻塞
  - 当发送三条消息:A(ttl:10000)、B(ttl:5000)、C(ttl:1000)时,B和C的消费会被阻塞,只有当A被消费后,B、C才会同时消费。
  - 为什么会出现这样的现象呢?
    - 我们知道利用TTL DLX特性实现的方式,实际上在第一个延时队列C里面设置了dlx,生产者生产了一条带ttl的消息放入了延时队列C中,等到延时时间到了,延时队列C中的消息变成了死信,根据延时队列C中设置的dlx的exchange的转发规则,转发到了实际消费队列D中,当该队列中的监听器监听到消息时就会正式开始消费。那么实际上延时队列中的消息也是放入队列中的,队列满足先进先出,而延时大的消息A还没出队,所以B消息也不能顺利出队。
### 2. 利用Rabbitmq的插件x-delay-message实现延时队列的方式
为了解决上面的问题,Rabbitmq实现了一个插件x-delay-message来实现延时队列。
```
安装插件:
1.rabbit官网下载插件
https://www.rabbitmq.com/community-plugins.html
2.找到这个插件
3.下载下来复制到D:\RabbitMQ Server\rabbitmq_server-3.7.8\plugins中
4.doc运行:rabbitmq-plugins enable rabbitmq_delayed_message_exchange
```
# 四、并发处理
RabbitMQ监听者默认是单线程监听队列
- 优点:保证消费者消费顺序,与生产者生产顺序一致
- 缺点: 单线程处理消息, 当消息队列有多个任务时消费端监听队列每次只消费一个消息 , 容易引起消息堆积 , 处理效率慢
 
# 五、代码案例
ConnectionUtil
public static Connection getConnection() throws Exception {
    //定义连接工厂
    ConnectionFactory factory = new ConnectionFactory();
    //设置服务地址
    factory.setHost("127.0.0.1");
    //端口
    factory.setPort(5672);
    //设置账号信息,用户名、密码、vhost
    factory.setVirtualHost("/zuul");
    factory.setUsername("guest");
    factory.setPassword("guest");
    // 通过工程获取连接
    Connection connection = factory.newConnection();
    /*if (connection.isOpen()) {
        System.out.println("连接成功");
    } else {
        System.out.println("连接失败");
    }*/
    return connection;
}

DirectSend

/**
 * 普通消息演示,Direct
 */
public class DirectSend {
    private final static String EXCHANGE_NAME = "e_test_01";
    private final static String QUEUE_NAME = "q_test_01";
    private final static String KEY_NAME = "k_test_01";

    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();

        /**
         * 声明交换机
         *
         * exchange: 交换器的名称
         * type:交换器的类型,如Direct Topic Headers Fanout
         *     Direct Exchange – 处理路由键。需要将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全匹配。
         *     Fanout Exchange – 不处理路由键。消息都会被转发到与该交换机绑定的所有队列上。(Fanout交换机转发消息是最快的)。
         *     Topic Exchange – 将路由键和某模式进行匹配。此时队列需要绑定要一个模式上。符号“#”匹配一个或多个词,符号“*”匹配不多不少一个词。
         *     因此“audit.#”能够匹配到“audit.irs.corporate”,但是“audit.*” 只会匹配到“audit.irs”。
         * durable:设置是否持久化,true持久化,以保证服务器重启,不会丢失相关信息
         * autoDelete:是否自动删除。true为自动删除,删除的前提是至少有一个队列或者交换器与这个交换器绑定,之后所有与这个交换器绑定的队列或者交换器都与此解绑(并不是当与此交换器连接的客户端都断开时自动删除)
         * internal:是否内置,true表示内置交换器,客户端程序无法直接发送消息到这个交换器中,只能通过交换器路由到交换器这种方式
         * argument:其它一些结构化参数
         */
        channel.exchangeDeclare(EXCHANGE_NAME, "direct", true, false, false, null);
        /**
         * 指定队列
         *
         * queue: 队列名称
         * durable: 是否持久化, 队列的声明默认是存放到内存中的,如果rabbitmq重启会丢失,如果想重启之后还存在就要使队列持久化,
         *     保存到Erlang自带的Mnesia数据库中,当rabbitmq重启之后会读取该数据库
         * exclusive:排他队列,
         *     如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除。这里需要注意三点:
         *     其一,排他队列是基于连接可见的,同一连接的不同信道是可以同时访问同一个连接创建的排他队列的。
         *     其二,“首次”,如果一个连接已经声明了一个排他队列,其他连接是不允许建立同名的排他队列的,这个与普通队列不同。
         *     其三,即使该队列是持久化的,一旦连接关闭或者客户端退出,该排他队列都会被自动删除的。
         *     这种队列适用于只限于一个客户端发送读取消息的应用场景。
         * autoDelete:自动删除,true为自动删除,删除的前提是至少有一个消费者连接这个队列,之后所有与这个队列连接的消费者都断开时都会自动删除(并不是当连接此队列的所有客户端都断开时自动删除)
         * arguments: x-message-ttl(消息过期时间)、
         *            x-max-length(最大积压消息个数)、
         *            x-dead-letter-exchange(消息过期后投递的exchange)
         *            x-dead-letter-routing-key(消息过期后按照指定的routingkey重新发送)、
         *            x-max-priority(队列优先级,值越大优先级超高,优先级高的消息具备优先被消费的特权)
         *            x-expires(控制队列如果在多长时间未使用则会被删除,毫秒为单位)、
         *            x-max-length-bytes
         */
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        /**
         * 交换机和队列进行绑定
         *
         * queue:队列名称
         * exchange:交换器的名称
         * routingKey:用来绑定队列和交换器的路由键
         * argument:定义绑定的一些参数
         */
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, KEY_NAME, null);
        /**
         * 生产者获取没有被正确路由到合适队列的消息,通过添加ReturnListener来实现
         */
        channel.addReturnListener(new ReturnListener() {
            @Override
            public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("没有被正确路由到合适队列:" + new String(body));
            }
        });
        /**
         * 生产者获取没有被正确到达EXCHANGE的消息,通过Confirm确认机制来实现
         */
        SortedSet<Long> confirmSet = Collections.synchronizedSortedSet(new TreeSet<Long>());
        channel.confirmSelect();
        channel.addConfirmListener(new ConfirmListener() {
            //确认OK
            @Override
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("=================>成功到达EXCHANGE中,deliveryTag:" + deliveryTag + ", multiple:" + multiple);
                if (multiple) {
                    confirmSet.headSet(deliveryTag - 1).clear();
                } else {
                    confirmSet.remove(deliveryTag);
                }
            }

            //失败重发
            @Override
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("=================>未到达EXCHANGE中,deliveryTag:" + deliveryTag + ", multiple:" + multiple);
                if (multiple) {
                    confirmSet.headSet(deliveryTag - 1).clear();
                } else {
                    confirmSet.remove(deliveryTag);
                }
            }
        });
        /**
         * 消息内容
         **/
        String message = "Hello World!";
        /**
         * 发送消息
         *
         * exchange:交换器名称,如果设置为空字符串,则消息会被发送到RabbitMQ默认的交换器中。
         * routingKey:指定路由键,交换器根据路由键将消息存储到相应的队列之中
         * mandatory:为true则当exchange找不到相应的queue时,会调用basic.return方法将消息返还给生产者,否则丢弃
         * props:消息为持久化  —— MessageProperties.PERSISTENT_TEXT_PLAIN
         * body:msg字节
         */
        channel.basicPublish(EXCHANGE_NAME, KEY_NAME, true, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
        System.out.println(" [x] Sent '" + message + "'");
        TimeUnit.SECONDS.sleep(5);
        /**
         * 关闭通道和连接
         **/
        channel.close();
        connection.close();
    }
}
DirectRecv
/**
 * 普通消息演示,Direct
 */
public class DirectRecv {
    private final static String EXCHANGE_NAME = "e_test_01";
    private final static String QUEUE_NAME = "q_test_01";
    private final static String KEY_NAME = "k_test_01";

    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(EXCHANGE_NAME, "direct", true, false, false, null);
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, KEY_NAME, null);

        /**
         * 公平转发
         * 设置客户端最多接收未被ack的消息的个数,只有在消费者空闲的时候会发送下一条信息,同一时间每次发给一个消息给一个worker。
         * 一个生产者与多个消费者时,避免RabbitMQ服务器可能一直发送多个消息给一个worker,而另一个可能几乎不做任何事情。
         */
        channel.basicQos(1);

        /**
         * 实现消费者
         */
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("[x] recv msg:" + new String(body));
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                /**
                 * 发送应答ack
                 *
                 * DeliveryTag:消息的编号,是一个64位长整型值
                 * multiple为true时,则表示拒绝deliveryTag之前所有未被当前消费者确认的消息,false表示单个
                 */
                channel.basicAck(envelope.getDeliveryTag(), false);
                /**
                 * 单个拒绝
                 *
                 * DeliveryTag:消息的编号
                 * requeue为true时,交换器将重新发送消息,false时,则交换器丢弃消息
                 */
                //channel.basicReject(envelope.getDeliveryTag(), true);
                /**
                 * 批量拒绝
                 *
                 * DeliveryTag:消息的编号
                 * multiple为true时,则表示拒绝deliveryTag之前所有未被当前消费者确认的消息,false表示单个
                 * requeue为true时,交换器将重新发送消息,false时,则交换器丢弃消息
                 */
                //channel.basicNack(envelope.getDeliveryTag(), false, true);
            }
        };

        /**
         * 为队列指定消费者
         * queue: 队列
         * ack:自动应答,手动应答
         * consumer:消费者
         */
        channel.basicConsume(QUEUE_NAME, false, consumer);
        //等待回调函数执行完毕之后,关闭资源
        TimeUnit.SECONDS.sleep(5);
    }
}
DelaySend
/**
 * 延迟消息演示,生产者
 */
public class DelaySend {
    public static final String IMMEDIATE_QUEUE = "queue.demo.immediate";//立即消费的队列名称
    public static final String IMMEDIATE_EXCHANGE = "exchange.demo.immediate";//立即消费的exchange
    public static final String IMMEDIATE_ROUTING_KEY = "routingkey.demo.immediate";//立即消费的routing-key 名称
    public static final String DELAY_QUEUE = "queue.demo.delay";//延时消费的队列名称
    public static final String DEAD_LETTER_EXCHANGE = "exchange.demo.delay";//延时消费的exchange
    public static final String DELAY_ROUTING_KEY = "routingkey.demo.delay";//延时消费的routing-key名称

    /**
     * 延迟消息演示
     */
    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();

        //创建立即消费队列
        channel.queueDeclare(IMMEDIATE_QUEUE, true, false, false, null);
        //创建延迟队列
        Map<String, Object> map = new HashMap<String, Object>();
//        map.put("x-message-ttl", 10000);//消息过期时间
//        map.put("x-max-length", 500000);//最大积压的消息个数
        map.put("x-dead-letter-exchange", IMMEDIATE_EXCHANGE); // 声明了队列里的死信转发到的DLX名称,
        map.put("x-dead-letter-routing-key", IMMEDIATE_ROUTING_KEY); //声明了这些死信在转发时携带的 routing-key 名称。
        channel.queueDeclare(DELAY_QUEUE, true, false, false, map);

        //创建立即消费交换机
        channel.exchangeDeclare(IMMEDIATE_EXCHANGE, "direct", true, false, null);
        //创建延迟交换机
        channel.exchangeDeclare(DEAD_LETTER_EXCHANGE, "direct", true, false, null);

        //将立即消费队列和交换机进行绑定
        channel.queueBind(IMMEDIATE_QUEUE, IMMEDIATE_EXCHANGE, IMMEDIATE_ROUTING_KEY);
        channel.queueBind(DELAY_QUEUE, DEAD_LETTER_EXCHANGE, DELAY_ROUTING_KEY);

        //==========================================================================================

        String msg = "hello word11";
        //设置延迟属性
        AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder().expiration("10000").deliveryMode(2).build();
        channel.basicPublish(DEAD_LETTER_EXCHANGE, DELAY_ROUTING_KEY, properties, msg.getBytes());
        System.out.println("send msg:" + msg + "," + DateUtils.format(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss"));

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


    /**
     * 延迟消息演示(消息阻塞演示)
     */
    public static void main2(String[] args) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();

        String msg1 = "hello word1111";
        AMQP.BasicProperties properties1 = new AMQP.BasicProperties.Builder().expiration("10000").deliveryMode(2).build();
        channel.basicPublish(DEAD_LETTER_EXCHANGE, DELAY_ROUTING_KEY, properties1, msg1.getBytes());
        System.out.println("send msg1:" + msg1 + "," + DateUtils.format(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss"));

        String msg2 = "hello word222";
        AMQP.BasicProperties properties2 = new AMQP.BasicProperties.Builder().expiration("5000").deliveryMode(2).build();
        channel.basicPublish(DEAD_LETTER_EXCHANGE, DELAY_ROUTING_KEY, properties2, msg2.getBytes());
        System.out.println("send msg2:" + msg2 + "," + DateUtils.format(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss"));

        String msg3 = "hello word333";
        AMQP.BasicProperties properties3 = new AMQP.BasicProperties.Builder().expiration("1000").deliveryMode(2).build();
        channel.basicPublish(DEAD_LETTER_EXCHANGE, DELAY_ROUTING_KEY, properties3, msg3.getBytes());
        System.out.println("send msg3:" + msg3 + "," + DateUtils.format(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss"));

        channel.close();
        connection.close();
    }
}
DelayRecv
/**
 * 延迟消息演示,消费者
 */
public class DelayRecv {
    public static final String IMMEDIATE_QUEUE = "queue.demo.immediate";//立即消费的队列名称
    public static final String IMMEDIATE_EXCHANGE = "exchange.demo.immediate";//立即消费的exchange
    public static final String IMMEDIATE_ROUTING_KEY = "routingkey.demo.immediate";//立即消费的routing-key 名称
    public static final String DELAY_QUEUE = "queue.demo.delay";//延时消费的队列名称
    public static final String DEAD_LETTER_EXCHANGE = "exchange.demo.delay";//延时消费的exchange
    public static final String DELAY_ROUTING_KEY = "routingkey.demo.delay";//延时消费的routing-key名称

    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();

        //创建立即消费队列
        channel.queueDeclare(IMMEDIATE_QUEUE, true, false, false, null);
        //创建延迟队列
        Map<String, Object> map = new HashMap<String, Object>();
//        map.put("x-message-ttl", 10000);//消息过期时间
//        map.put("x-max-length", 500000);//最大积压的消息个数
        map.put("x-dead-letter-exchange", IMMEDIATE_EXCHANGE); // 声明了队列里的死信转发到的DLX名称,
        map.put("x-dead-letter-routing-key", IMMEDIATE_ROUTING_KEY); //声明了这些死信在转发时携带的 routing-key 名称。
        channel.queueDeclare(DELAY_QUEUE, true, false, false, map);

        //创建立即消费交换机
        channel.exchangeDeclare(IMMEDIATE_EXCHANGE, "direct", true, false, null);
        //创建延迟交换机
        channel.exchangeDeclare(DEAD_LETTER_EXCHANGE, "direct", true, false, null);

        //将立即消费队列和交换机进行绑定
        channel.queueBind(IMMEDIATE_QUEUE, IMMEDIATE_EXCHANGE, IMMEDIATE_ROUTING_KEY);
        channel.queueBind(DELAY_QUEUE, DEAD_LETTER_EXCHANGE, DELAY_ROUTING_KEY);

        //==========================================================================================

        channel.basicQos(1);
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("recv msg:" + new String(body) + "," + DateUtils.format(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss"));
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                /**
                 * 发送应答
                 */
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        channel.basicConsume(IMMEDIATE_QUEUE, false, consumer);
        //等待回调函数执行完毕之后,关闭资源
        TimeUnit.SECONDS.sleep(5);
    }

}
DelayXSend
/**
 * 延迟消息插件演示,生产者
 */
public class DelayXSend {
    public static final String IMMEDIATE_QUEUE_XDELAY = "queue.xdelay.immediate";//立即消费的队列名称
    public static final String DELAYED_EXCHANGE_XDELAY = "exchange.xdelay.delayed";//延时的exchange
    public static final String DELAY_ROUTING_KEY_XDELAY = "routingkey.xdelay.delay";//

    /**
     * 延迟消息演示(延迟插件)
     */
    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();

        //创建立即消费队列
        channel.queueDeclare(IMMEDIATE_QUEUE_XDELAY, true, false, false, null);
        //创建延迟交换机
        Map<String, Object> arguments = new HashMap<>();
        arguments.put("x-delayed-type", "direct");
        channel.exchangeDeclare(DELAYED_EXCHANGE_XDELAY, "x-delayed-message", true, false, arguments);
        //把立即消费的队列和延时消费的exchange绑定在一起
        channel.queueBind(IMMEDIATE_QUEUE_XDELAY, DELAYED_EXCHANGE_XDELAY, DELAY_ROUTING_KEY_XDELAY);

        //==========================================================================================

        String msg1 = "hello word1111";
        Map<String, Object> headers1 = new HashMap<>();
        headers1.put("x-delay", 10000);
        AMQP.BasicProperties.Builder builder1 = new AMQP.BasicProperties.Builder().headers(headers1);
        channel.basicPublish(DELAYED_EXCHANGE_XDELAY, DELAY_ROUTING_KEY_XDELAY, builder1.build(), msg1.getBytes());
        System.out.println("send msg1:" + msg1 + "," + DateUtils.format(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss"));

        String msg2 = "hello word2222";
        Map<String, Object> headers2 = new HashMap<>();
        headers2.put("x-delay", 5000);
        AMQP.BasicProperties.Builder builder2 = new AMQP.BasicProperties.Builder().headers(headers2);
        channel.basicPublish(DELAYED_EXCHANGE_XDELAY, DELAY_ROUTING_KEY_XDELAY, builder2.build(), msg1.getBytes());
        System.out.println("send msg1:" + msg2 + "," + DateUtils.format(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss"));

        String msg3 = "hello word3333";
        Map<String, Object> headers3 = new HashMap<>();
        headers3.put("x-delay", 1000);
        AMQP.BasicProperties.Builder builder3 = new AMQP.BasicProperties.Builder().headers(headers3);
        channel.basicPublish(DELAYED_EXCHANGE_XDELAY, DELAY_ROUTING_KEY_XDELAY, builder3.build(), msg1.getBytes());
        System.out.println("send msg1:" + msg3 + "," + DateUtils.format(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss"));

        channel.close();
        connection.close();
    }
}
DelayXRecv
/**
 * 延迟消息插件演示,消费者
 */
public class DelayXRecv {
    public static final String IMMEDIATE_QUEUE_XDELAY = "queue.xdelay.immediate";//立即消费的队列名称
    public static final String DELAYED_EXCHANGE_XDELAY = "exchange.xdelay.delayed";//延时的exchange
    public static final String DELAY_ROUTING_KEY_XDELAY = "routingkey.xdelay.delay";//

    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();

        //创建立即消费队列
        channel.queueDeclare(IMMEDIATE_QUEUE_XDELAY, true, false, false, null);
        //创建延迟交换机
        Map<String, Object> arguments = new HashMap<>();
        arguments.put("x-delayed-type", "direct");
        channel.exchangeDeclare(DELAYED_EXCHANGE_XDELAY, "x-delayed-message", true, false, arguments);
        //把立即消费的队列和延时消费的exchange绑定在一起
        channel.queueBind(IMMEDIATE_QUEUE_XDELAY, DELAYED_EXCHANGE_XDELAY, DELAY_ROUTING_KEY_XDELAY);

        //==========================================================================================

        channel.basicQos(1);
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("recv msg:" + new String(body) + "," + DateUtils.format(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss"));
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                /**
                 * 发送应答
                 */
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        channel.basicConsume(IMMEDIATE_QUEUE_XDELAY, false, consumer);
        //等待回调函数执行完毕之后,关闭资源
        TimeUnit.SECONDS.sleep(5);
    }

}
PTSend
/**
 * 并发演示
 */
public class PTSend {
    private final static String QUEUE_NAME = "b_test_01";

    public static void main(String[] args) throws Exception {
        Thread threadExtends = new ThreadExtends();
        threadExtends.start(); // 注意,这里一定要使用start方法,它是启动线程的方法!

        Thread threadExtends2 = new ThreadExtends();
        threadExtends2.start(); // 注意,这里一定要使用start方法,它是启动线程的方法!
    }

    static class ThreadExtends extends Thread {
        @Override
        public void run() {
            int i = 0;
            while (i < 5) {
                try {
                    toMQ();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                i++;
            }
        }
    }

    public static void toMQ() throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 消息内容
        String message = "Hello World!" + RandomStringUtils.randomNumeric(10);
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
        System.out.println(" [x] Sent '" + message + "'");
        //关闭通道和连接
        channel.close();
        connection.close();
    }
}
PTRecv
/**
 * 并发演示
 */
public class PTRecv {
    private final static String QUEUE_NAME = "b_test_01";

    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        channel.basicQos(1);
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("recv msg:" + new String(body) + "," + DateUtils.format(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss"));
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                /**
                 * 发送应答
                 */
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        channel.basicConsume(QUEUE_NAME, false, consumer);
        //等待回调函数执行完毕之后,关闭资源
        TimeUnit.SECONDS.sleep(5);
    }

}

  

 
 
 
 
posted @ 2022-08-18 10:08  yifanSJ  阅读(149)  评论(0编辑  收藏  举报