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); } }