RabbitMQ 客户端 com.rabbitmq:amqp-client:5.10.0

博文目录


Connection 和 Channel 的获取与关闭

package com.mrathena;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public final class CommonKit {

	private CommonKit() {}

	public static Channel getChannel() {
		try {
			ConnectionFactory factory = new ConnectionFactory();
			factory.setHost("116.62.162.48");
			factory.setPort(5672);
			// 所有测试都是用这一个账户
			factory.setUsername("mrathena");
			factory.setPassword("password");
			// 所有测试都是用这一个VirtualHost
			factory.setVirtualHost("test");
			// 获取TCP长连接
			Connection connection = factory.newConnection();
			// 创建通信“通道”,相当于TCP中的虚拟连接
			Channel channel = connection.createChannel();
			// channel.close();
			// connection.close();
			return channel;
		} catch (Throwable cause) {
			throw new RuntimeException(cause);
		}
	}

	public static void closeChannelAndConnection(Channel channel) {
		try {
			if (!channel.isOpen()) {
				return;
			}
			Connection connection = channel.getConnection();
			channel.close();
			if (connection.isOpen()) {
				connection.close();
			}
		} catch (Throwable cause) {
			cause.printStackTrace();
		}
	}

}

HelloWorld 简单模式 队列-单个消费者

package com.mrathena;

import com.mrathena.constant.Constant;
import com.mrathena.toolkit.ThreadKit;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.io.IOException;

@Slf4j
public class HelloWorldModeTest {

	private static final String QUEUE = "queue-hello-world";

	@Test
	public void consumerTest() {
		Channel channel = CommonKit.getChannel();
		try {
			// 声明一个队列(不存在则自动创建), 如果队列已存在, 则使用这个队列
			// 第一个参数: 队列名
			// 第二个参数: 是否持久化, false对应不持久化数据, MQ停掉数据就会丢失
			// 第三个参数: 是否队列私有化, false则代表所有消费者都可以访问, true代表只有第一次拥有它的消费者才能一直使用, 其他消费者不让访问
			// 第四个参数: 是否自动删除, false代表连接停掉后不自动删除掉这个队列
			// 第四个参数: 其他额外的参数, null
			// 注意: 如果队列已存在, 但是这里声明的队列和已存在队列的属性不一致, 则启动会报错, 如已存在队列是durable的, 而这里第二个参数是false, 启动的时候就会报错
			channel.queueDeclare(QUEUE, false, false, false, null);

			// channel.basicQos(1)指该消费者在接收到队列里的消息但没有返回确认结果之前,它不会将新的消息分发给它。
			// 假如有两个消费者,在处理消息的时候,分别休眠10,1000毫秒,默认的任务分发机制,会使两个消费者获取相同的消息数量。
			// 更合理的方式是,休眠少的消费者多劳,消费更多的消息,channel.basicQos(1)能使休眠多的消费者接受更少的消息数量,而不再是公平分发数量。
			// 无 channel.basicQos(1), MQ会将所有请求平均发送给所有消费者, 不管你消费的快还是慢
			// 有 channel.basicQos(1), MQ不再对消费者一次发送多个请求,而是消费者处理完一个消息后(确认后),再从队列中获取一个新的, 再消费
			// 每次推送多个的话, 是否可以配合basicAck(true)来使用
			channel.basicQos(1);

			// 创建一个消息消费者
			// 第一个参数: 队列名
			// 第二个参数: 代表是否自动确认收到消息, false代表手动编程来确认消息(消费者端), 这是MQ的推荐做法, 自动提交?
			// 第三个参数: 要传入DefaultConsumer的实现类, 居然可以不使用子类, 直接new本类并覆盖某一个方法
			channel.basicConsume(QUEUE, false, new DefaultConsumer(channel) {
				public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
					long deliveryTag = envelope.getDeliveryTag();
					log.info("收到消息: {}", new String(body));
					// false只确认签收当前的消息, 设置为true的时候则代表签收该消费者所有未签收的消息
					channel.basicAck(deliveryTag, false);
				}
			});
		} catch (Throwable cause) {
			cause.printStackTrace();
		}
		ThreadKit.sleepSecond(30);
		CommonKit.closeChannelAndConnection(channel);
	}

	@Test
	public void producerTest() {
		try {
			Channel channel = CommonKit.getChannel();
			channel.queueDeclare(QUEUE, false, false, false, null);
			for (int i = 0; i < 10; i++) {
				// 发布消息
				// exchange: 交换机,暂时用不到,在后面进行发布订阅时才会用到
				// routingKey: 这里可认为是队列名称?
				// props: 额外的设置属性
				// body: 消息的字节数组
				channel.basicPublish(Constant.EMPTY, QUEUE, null, String.valueOf(i + 1).getBytes());
			}
			CommonKit.closeChannelAndConnection(channel);
		} catch (Throwable cause) {
			cause.printStackTrace();
		}
	}

}

WorkQueue 工作队列模式 队列-多个消费者(竞争关系)

package com.mrathena;

import com.mrathena.constant.Constant;
import com.mrathena.toolkit.ThreadKit;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.io.IOException;

@Slf4j
public class WorkQueuesModeTest {

	private static final String QUEUE = "queue-work-queues";

	@Test
	public void consumerTest() {
		// 假装是两个客户端, 竞争关系
		Channel channel = CommonKit.getChannel();
		try {
			channel.queueDeclare(QUEUE, false, false, false, null);
			// 无 channel.basicQos(1), MQ会将所有请求平均发送给所有消费者, 不管你消费的快还是慢
			// 有 channel.basicQos(1), MQ不再对消费者一次发送多个请求,而是消费者处理完一个消息后(确认后),再从队列中获取一个新的, 再消费
			// 即每次从队列里拿多少个, 可以配合basicAck(true)的那种方式来使用
			channel.basicQos(1);
			channel.basicConsume(QUEUE, false, new DefaultConsumer(channel) {
				public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
					log.info("Consumer1, 收到消息: {}", new String(body));
					//false只确认签收当前的消息,设置为true的时候则代表签收该消费者所有未签收的消息
					channel.basicAck(envelope.getDeliveryTag(), false);
					ThreadKit.sleepMilli(1000);
				}
			});
		} catch (Throwable cause) {
			cause.printStackTrace();
		}
		ThreadKit.sleepSecond(30);
		CommonKit.closeChannelAndConnection(channel);
	}

	@Test
	public void consumer2Test() {
		// 假装是两个客户端, 竞争关系
		Channel channel = CommonKit.getChannel();
		try {
			channel.queueDeclare(QUEUE, false, false, false, null);
			channel.basicQos(1);
			channel.basicConsume(QUEUE, false, new DefaultConsumer(channel) {
				public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
					log.info("Consumer2, 收到消息: {}", new String(body));
					channel.basicAck(envelope.getDeliveryTag(), false);
//					ThreadKit.sleepMilli(100);
				}
			});
		} catch (Throwable cause) {
			cause.printStackTrace();
		}
		ThreadKit.sleepSecond(30);
		CommonKit.closeChannelAndConnection(channel);
	}

	@Test
	public void producerTest() {
		try {
			Channel channel = CommonKit.getChannel();
			channel.queueDeclare(QUEUE, false, false, false, null);
			for (int i = 0; i < 10; i++) {
				channel.basicPublish(Constant.EMPTY, QUEUE, null, String.valueOf(i + 1).getBytes());
			}
			CommonKit.closeChannelAndConnection(channel);
		} catch (Throwable cause) {
			cause.printStackTrace();
		}
	}

}

PublishSubscribe 发布订阅模式(广播模式) 绑定到交换机的队列都能收到消息

package com.mrathena;

import com.mrathena.constant.Constant;
import com.mrathena.toolkit.ThreadKit;
import com.rabbitmq.client.*;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.io.IOException;

@Slf4j
public class PublishSubscribeModeTest {

	/**
	 * 广播类型的交换机
	 */
	private static final String EXCHANGE_FANOUT = "exchange-fanout";
	private static final String QUEUE1 = "queue-publish-subscribe-1";
	private static final String QUEUE2 = "queue-publish-subscribe-2";

	@Test
	public void consumer1Test() {
		// 两个客户端, 分别连接两个队列, 然后这两个队列都绑定到同一个交换机, 交换机将每条消息都发送到两个队列
		Channel channel = CommonKit.getChannel();
		try {
			channel.queueDeclare(QUEUE1, false, false, false, null);
			// 声明一个交换机(不存在则自动创建)
			// exchange: 交换机名
			// type: 交换器的类型,常见的有direct,fanout,topic等
			// durable: 设置是否持久化。durable设置为true时表示持久化,反之非持久化.持久化可以将交换器存入磁盘,在服务器重启的时候不会丢失相关信息。
			// autoDelete: 设置是否自动删除。autoDelete设置为true时,则表示自动删除。自动删除的前提是至少有一个队列或者交换器与这个交换器绑定,之后,所有与这个交换器绑定的队列或者交换器都与此解绑。不能错误的理解—当与此交换器连接的客户端都断开连接时,RabbitMq会自动删除本交换器
			// internal: 设置是否内置的。如果设置为true,则表示是内置的交换器,客户端程序无法直接发送消息到这个交换器中,只能通过交换器路由到交换器这种方式。
			// arguments: 其他额外的参数, null
			// exchangeDeclareNoWait 相关方法, 意思是不需要服务器返回,返回值为void,而普通的exchangeDeclare返回的是Exchange.DeclareOk,客户端声明一交换器后,需要等待服务器的返回. nowait:在声明完一个交换器后,实际上服务器还未完成交换器的创建,那么客户端接着使用这个交换吕,必然发生异常,
			channel.exchangeDeclare(EXCHANGE_FANOUT, BuiltinExchangeType.FANOUT, false, false, false, null);
			// 队列绑定到交换机
			// queue: 队列
			// exchange: 交换机
			// routingKey: 广播模式的交换机无需routingKey, 传空字符串
			// 还有 exchangeBind 方法, 是将两个exchange绑定, 生产者发送消息到source交换器中,source根据路由键找到与其匹配的另一个交换器destination,并把消息转发到destination中,进而存储在destination绑定的队列queue中
			channel.queueBind(QUEUE1, EXCHANGE_FANOUT, Constant.EMPTY);
			channel.basicQos(1);
			channel.basicConsume(QUEUE1, false, new DefaultConsumer(channel) {
				public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
					log.info("Consumer1, 收到消息: {}", new String(body));
					channel.basicAck(envelope.getDeliveryTag(), false);
				}
			});
		} catch (Throwable cause) {
			cause.printStackTrace();
		}
		ThreadKit.sleepSecond(30);
		CommonKit.closeChannelAndConnection(channel);
	}

	@Test
	public void consumer2Test() {
		// 两个客户端, 分别连接两个队列, 然后这两个队列都绑定到同一个交换机, 交换机将每条消息都发送到两个队列
		Channel channel = CommonKit.getChannel();
		try {
			channel.queueDeclare(QUEUE2, false, false, false, null);
			channel.exchangeDeclare(EXCHANGE_FANOUT, BuiltinExchangeType.FANOUT, false, false, false, null);
			channel.queueBind(QUEUE2, EXCHANGE_FANOUT, Constant.EMPTY);
			channel.basicQos(1);
			channel.basicConsume(QUEUE2, false, new DefaultConsumer(channel) {
				public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
					log.info("Consumer2, 收到消息: {}", new String(body));
					channel.basicAck(envelope.getDeliveryTag(), false);
				}
			});
		} catch (Throwable cause) {
			cause.printStackTrace();
		}
		ThreadKit.sleepSecond(30);
		CommonKit.closeChannelAndConnection(channel);
	}

	@Test
	public void producerTest() {
		try {
			Channel channel = CommonKit.getChannel();
			channel.exchangeDeclare(EXCHANGE_FANOUT, BuiltinExchangeType.FANOUT, false, false, false, null);
			for (int i = 0; i < 10; i++) {
				// 发布消息
				// exchange: 交换机, 广播模式
				// routingKey: 这里可认为是队列名称?
				// props: 额外的设置属性
				// body: 消息的字节数组
				channel.basicPublish(EXCHANGE_FANOUT, Constant.EMPTY, null, String.valueOf(i + 1).getBytes());
			}
			CommonKit.closeChannelAndConnection(channel);
		} catch (Throwable cause) {
			cause.printStackTrace();
		}
	}

}

Routing 路由模式 绑定交换机且指定的队列才能收到消息

package com.mrathena;

import com.mrathena.toolkit.ThreadKit;
import com.rabbitmq.client.*;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Slf4j
public class RoutingModeTest {

	/**
	 * 定向类型的交换机
	 */
	private static final String EXCHANGE_DIRECT = "exchange-direct";
	private static final String QUEUE1 = "queue-routing-1";
	private static final String QUEUE2 = "queue-routing-2";
	private static final String KEY_FOO_1 = "routing.key.foo.1";
	private static final String KEY_FOO_2 = "routing.key.foo.2";
	private static final String KEY_BAR_1 = "routing.key.bar.1";
	private static final String KEY_BAR_2 = "routing.key.bar.2";
	private static final String KEY_OTHER = "routing.key.other.what.the.fuck";

	@Test
	public void consumer1Test() {
		// 两个客户端, 分别连接两个队列, 然后这两个队列都绑定到同一个交换机, 交换机按routingKey匹配将消息推送到不同队列
		Channel channel = CommonKit.getChannel();
		try {
			channel.queueDeclare(QUEUE1, false, false, false, null);
			channel.exchangeDeclare(EXCHANGE_DIRECT, BuiltinExchangeType.DIRECT, false, false, false, null);
			// queue-routing-1 绑定 exchange-direct, 且 routingKey是routing.key.foo.1, 只能收到该routingKey的消息
			// 注意: 一旦绑定过, 就会一直保持, 除非调用 channel.queueUnbind() 解除绑定, 具体的绑定关系可以从Web管理页面的Queue里查看
			channel.queueBind(QUEUE1, EXCHANGE_DIRECT, KEY_FOO_1);
			channel.basicQos(1);
			channel.basicConsume(QUEUE1, false, new DefaultConsumer(channel) {
				public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
					log.info("Consumer1, 收到消息: {}", new String(body));
					channel.basicAck(envelope.getDeliveryTag(), false);
				}
			});
		} catch (Throwable cause) {
			cause.printStackTrace();
		}
		ThreadKit.sleepSecond(30);
		CommonKit.closeChannelAndConnection(channel);
	}

	@Test
	public void consumer2Test() {
		// 两个客户端, 分别连接两个队列, 然后这两个队列都绑定到同一个交换机, 交换机按routingKey匹配将消息推送到不同队列
		Channel channel = CommonKit.getChannel();
		try {
			channel.queueDeclare(QUEUE2, false, false, false, null);
			channel.exchangeDeclare(EXCHANGE_DIRECT, BuiltinExchangeType.DIRECT, false, false, false, null);
			// queue-routing-1 绑定 exchange-direct, 且 routingKey绑定了3个, 能收到这3个routingKey的消息
			channel.queueBind(QUEUE2, EXCHANGE_DIRECT, KEY_BAR_1);
			channel.queueBind(QUEUE2, EXCHANGE_DIRECT, KEY_BAR_2);
			channel.queueBind(QUEUE2, EXCHANGE_DIRECT, KEY_OTHER);
			channel.basicQos(1);
			channel.basicConsume(QUEUE2, false, new DefaultConsumer(channel) {
				public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
					log.info("Consumer2, 收到消息: {}", new String(body));
					channel.basicAck(envelope.getDeliveryTag(), false);
				}
			});
		} catch (Throwable cause) {
			cause.printStackTrace();
		}
		ThreadKit.sleepSecond(30);
		CommonKit.closeChannelAndConnection(channel);
	}

	@Test
	public void consumer3Test() {
		// 测试consumer3和consumer2是否竞争消费同一个队列, 测试说明: 是的, 在有交换机参与的情况下, 仍然可以给某一个队列组WorkQueues
		Channel channel = CommonKit.getChannel();
		try {
			channel.queueDeclare(QUEUE2, false, false, false, null);
			channel.exchangeDeclare(EXCHANGE_DIRECT, BuiltinExchangeType.DIRECT, false, false, false, null);
			// queue-routing-1 绑定 exchange-direct, 且 routingKey绑定了3个, 能收到这3个routingKey的消息
			channel.queueBind(QUEUE2, EXCHANGE_DIRECT, KEY_BAR_1);
			channel.queueBind(QUEUE2, EXCHANGE_DIRECT, KEY_BAR_2);
			channel.queueBind(QUEUE2, EXCHANGE_DIRECT, KEY_OTHER);
			channel.basicQos(1);
			channel.basicConsume(QUEUE2, false, new DefaultConsumer(channel) {
				public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
					log.info("Consumer3, 收到消息: {}", new String(body));
					channel.basicAck(envelope.getDeliveryTag(), false);
				}
			});
		} catch (Throwable cause) {
			cause.printStackTrace();
		}
		ThreadKit.sleepSecond(30);
		CommonKit.closeChannelAndConnection(channel);
	}

	@Test
	public void producerTest() {
		Map<String, String> data = new HashMap<>();
		data.put(KEY_FOO_1, "value.foo.1");
		data.put(KEY_FOO_2, "value.foo.2");
		data.put(KEY_BAR_1, "value.bar.1");
		data.put(KEY_BAR_2, "value.bar.2");
		data.put(KEY_OTHER, "value.other");
		try {
			Channel channel = CommonKit.getChannel();
			channel.exchangeDeclare(EXCHANGE_DIRECT, BuiltinExchangeType.DIRECT, false, false, false, null);
			for (Map.Entry<String, String> entry : data.entrySet()) {
				channel.basicPublish(EXCHANGE_DIRECT, entry.getKey(), null, entry.getValue().getBytes());
			}
			CommonKit.closeChannelAndConnection(channel);
		} catch (Throwable cause) {
			cause.printStackTrace();
		}
	}

}

Topics 主题模式 绑定交换机且符合条件的队列才能收到消息

package com.mrathena;

import com.mrathena.constant.Constant;
import com.mrathena.toolkit.ThreadKit;
import com.rabbitmq.client.*;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Slf4j
public class TopicsModeTest {

	/**
	 * 主题类型的交换机
	 */
	private static final String EXCHANGE_TOPIC = "exchange-topic";
	private static final String QUEUE1 = "queue-topics-1";
	private static final String QUEUE2 = "queue-topics-2";
	private static final String QUEUE3 = "queue-topics-3";
	private static final String QUEUE4 = "queue-topics-4";
	private static final String QUEUE5 = "queue-topics-5";
	private static final String QUEUE6 = "queue-topics-6";
	private static final String KEY_FOO_1 = "routing.key.foo.1";
	private static final String KEY_FOO_2 = "routing.key.foo.2";
	private static final String KEY_BAR_1 = "routing.key.bar.1";
	private static final String KEY_BAR_2 = "routing.key.bar.2";
	private static final String KEY_OTHER = "routing.key.other.what.the.fuck";

	@Test
	public void consumer1Test() {
		// 两个客户端, 分别连接两个队列, 然后这两个队列都绑定到同一个交换机, 交换机按routingKey匹配将消息推送到不同队列
		Channel channel = CommonKit.getChannel();
		try {
			channel.queueDeclare(QUEUE1, false, false, false, null);
			channel.exchangeDeclare(EXCHANGE_TOPIC, BuiltinExchangeType.TOPIC, false, false, false, null);
			// queue-topics-1 绑定 exchange-topic, 且 routingKey是routing.key.foo.*, 只收 routing.key.foo.xxx 这种key的消息, * 代表一个分段, # 代表多个分段
			channel.queueBind(QUEUE1, EXCHANGE_TOPIC, "routing.key.foo.*");
			channel.basicQos(1);
			channel.basicConsume(QUEUE1, false, new DefaultConsumer(channel) {
				public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
					log.info("Consumer1, 收到消息: {}", new String(body));
					channel.basicAck(envelope.getDeliveryTag(), false);
				}
			});
		} catch (Throwable cause) {
			cause.printStackTrace();
		}
		ThreadKit.sleepSecond(30);
		CommonKit.closeChannelAndConnection(channel);
	}

	@Test
	public void consumer2Test() {
		// 两个客户端, 分别连接两个队列, 然后这两个队列都绑定到同一个交换机, 交换机按routingKey匹配将消息推送到不同队列
		Channel channel = CommonKit.getChannel();
		try {
			channel.queueDeclare(QUEUE2, false, false, false, null);
			channel.exchangeDeclare(EXCHANGE_TOPIC, BuiltinExchangeType.TOPIC, false, false, false, null);
			// queue-routing-1 绑定 exchange-direct, routingKey是routing.#.1, 只收 routing.#.1 这种key的消息, * 代表一个分段, # 代表多个分段
			channel.queueBind(QUEUE2, EXCHANGE_TOPIC, "routing.#.1");
			channel.basicQos(1);
			channel.basicConsume(QUEUE2, false, new DefaultConsumer(channel) {
				public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
					log.info("Consumer2, 收到消息: {}", new String(body));
					channel.basicAck(envelope.getDeliveryTag(), false);
				}
			});
		} catch (Throwable cause) {
			cause.printStackTrace();
		}
		ThreadKit.sleepSecond(30);
		CommonKit.closeChannelAndConnection(channel);
	}

	@Test
	public void consumer3Test() {
		// 两个客户端, 分别连接两个队列, 然后这两个队列都绑定到同一个交换机, 交换机按routingKey匹配将消息推送到不同队列
		Channel channel = CommonKit.getChannel();
		try {
			channel.queueDeclare(QUEUE3, false, false, false, null);
			channel.exchangeDeclare(EXCHANGE_TOPIC, BuiltinExchangeType.TOPIC, false, false, false, null);
			// queue-routing-1 绑定 exchange-direct, routingKey是routing.#.1, 只收 routing.key.*.* 这种key的消息, * 代表一个分段, # 代表多个分段
			channel.queueBind(QUEUE3, EXCHANGE_TOPIC, "routing.key.*.*");
			channel.basicQos(1);
			channel.basicConsume(QUEUE3, false, new DefaultConsumer(channel) {
				public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
					log.info("Consumer3, 收到消息: {}", new String(body));
					channel.basicAck(envelope.getDeliveryTag(), false);
				}
			});
		} catch (Throwable cause) {
			cause.printStackTrace();
		}
		ThreadKit.sleepSecond(30);
		CommonKit.closeChannelAndConnection(channel);
	}

	@Test
	public void consumer4Test() {
		// 两个客户端, 分别连接两个队列, 然后这两个队列都绑定到同一个交换机, 交换机按routingKey匹配将消息推送到不同队列
		Channel channel = CommonKit.getChannel();
		try {
			channel.queueDeclare(QUEUE4, false, false, false, null);
			channel.exchangeDeclare(EXCHANGE_TOPIC, BuiltinExchangeType.TOPIC, false, false, false, null);
			// queue-routing-1 绑定 exchange-direct, routingKey是routing.#.1, 只收 routing.key.# 这种key的消息, * 代表一个分段, # 代表多个分段
			channel.queueBind(QUEUE4, EXCHANGE_TOPIC, "routing.key.#");
			channel.basicQos(1);
			channel.basicConsume(QUEUE4, false, new DefaultConsumer(channel) {
				public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
					log.info("Consumer4, 收到消息: {}", new String(body));
					channel.basicAck(envelope.getDeliveryTag(), false);
				}
			});
		} catch (Throwable cause) {
			cause.printStackTrace();
		}
		ThreadKit.sleepSecond(30);
		CommonKit.closeChannelAndConnection(channel);
	}

	@Test
	public void consumer5Test() {
		// 两个客户端, 分别连接两个队列, 然后这两个队列都绑定到同一个交换机, 交换机按routingKey匹配将消息推送到不同队列
		Channel channel = CommonKit.getChannel();
		try {
			channel.queueDeclare(QUEUE5, false, false, false, null);
			channel.exchangeDeclare(EXCHANGE_TOPIC, BuiltinExchangeType.TOPIC, false, false, false, null);
			// queue-routing-1 绑定 exchange-direct, routingKey是明确的"routing.key.other.what.the.fuck", 测试说明topic模式下是支持明确RoutingKey的
			channel.queueBind(QUEUE5, EXCHANGE_TOPIC, KEY_OTHER);
			channel.basicQos(1);
			channel.basicConsume(QUEUE5, false, new DefaultConsumer(channel) {
				public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
					log.info("Consumer5, 收到消息: {}", new String(body));
					channel.basicAck(envelope.getDeliveryTag(), false);
				}
			});
		} catch (Throwable cause) {
			cause.printStackTrace();
		}
		ThreadKit.sleepSecond(30);
		CommonKit.closeChannelAndConnection(channel);
	}

	@Test
	public void consumer6Test() {
		// 两个客户端, 分别连接两个队列, 然后这两个队列都绑定到同一个交换机, 交换机按routingKey匹配将消息推送到不同队列
		Channel channel = CommonKit.getChannel();
		try {
			channel.queueDeclare(QUEUE6, false, false, false, null);
			channel.exchangeDeclare(EXCHANGE_TOPIC, BuiltinExchangeType.TOPIC, false, false, false, null);
			// queue-routing-1 绑定 exchange-direct, routingKey是明确的空字符串, 测试说明topic模式下是支持类似广播模式的效果, 不过需要发送时RoutingKey也是空字符串
			channel.queueBind(QUEUE6, EXCHANGE_TOPIC, Constant.EMPTY);
			channel.basicQos(1);
			channel.basicConsume(QUEUE6, false, new DefaultConsumer(channel) {
				public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
					log.info("Consumer6, 收到消息: {}", new String(body));
					channel.basicAck(envelope.getDeliveryTag(), false);
				}
			});
		} catch (Throwable cause) {
			cause.printStackTrace();
		}
		ThreadKit.sleepSecond(30);
		CommonKit.closeChannelAndConnection(channel);
	}

	@Test
	public void producerTest() {
		// 每次发送消息都会给所有绑定该交换机并且路由键匹配的队列发送消息, 有任何一个队列没有被消费, 则消息就会积压
		Map<String, String> data = new HashMap<>();
		data.put(KEY_FOO_1, "value.foo.1");
		data.put(KEY_FOO_2, "value.foo.2");
		data.put(KEY_BAR_1, "value.bar.1");
		data.put(KEY_BAR_2, "value.bar.2");
		data.put(KEY_OTHER, "value.other");
		try {
			Channel channel = CommonKit.getChannel();
			channel.exchangeDeclare(EXCHANGE_TOPIC, BuiltinExchangeType.TOPIC, false, false, false, null);
			CommonKit.closeChannelAndConnection(channel);
		} catch (Throwable cause) {
			cause.printStackTrace();
		}
	}

	@Test
	public void producer6Test() {
		Map<String, String> data = new HashMap<>();
		data.put(KEY_FOO_1, "value.foo.1");
		data.put(KEY_FOO_2, "value.foo.2");
		data.put(KEY_BAR_1, "value.bar.1");
		data.put(KEY_BAR_2, "value.bar.2");
		data.put(KEY_OTHER, "value.other");
		try {
			Channel channel = CommonKit.getChannel();
			channel.exchangeDeclare(EXCHANGE_TOPIC, BuiltinExchangeType.TOPIC, false, false, false, null);
			for (Map.Entry<String, String> entry : data.entrySet()) {
				channel.basicPublish(EXCHANGE_TOPIC, Constant.EMPTY, null, entry.getValue().getBytes());
			}
			CommonKit.closeChannelAndConnection(channel);
		} catch (Throwable cause) {
			cause.printStackTrace();
		}
	}

}

监听测试(Confirm/Return)

package com.mrathena;

import com.mrathena.toolkit.ThreadKit;
import com.rabbitmq.client.*;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.io.IOException;

/**
 * 消息确认机制测试(与消费者无关)
 * 会有两种状态
 * 消息有没有送到Broker(confirm)
 * - 送到了,返回ack(正常情况)
 * - 没送到,返回nack
 * 消息有没有被MQ送到Queue(return)
 * confirm一定是ack
 * - 送到了
 * - 送不到,回退给生产者(队列没有绑定匹配的路由键)
 */
@Slf4j
public class ConfirmTest {

	private static final String EXCHANGE_TOPIC_CONFIRM = "exchange-topic-confirm";
	private static final String EXCHANGE_TOPIC_CONFIRM_NOT_EXIST = "exchange-topic-confirm-not-exist";
	private static final String QUEUE_CONFIRM = "queue-confirm";
	private static final String KEY_CONFIRM = "routing.key.confirm";
	private static final String KEY_NOT_EXIST = "routing.key.not.exist";

	@Test
	public void test() {
		try {
			Channel channel = CommonKit.getChannel();
			// 开启 confirm 的监听
			channel.confirmSelect();
			// 添加 ConfirmListener 监听
			channel.addConfirmListener(new ConfirmListener() {
				@Override
				public void handleAck(long deliveryTag, boolean multiple) throws IOException {
					// multiple代表接收的数据是否为批量接收, 一般用不到
					log.info("ack true : {}", deliveryTag);
				}

				@Override
				public void handleNack(long deliveryTag, boolean multiple) throws IOException {
					// 不太好模拟
					log.info("ack false : {}", deliveryTag);
				}
			});
			// 添加 ReturnListener 监听
			channel.addReturnListener(new ReturnListener() {
				@Override
				public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
					log.info("return : {}", new String(body));
//					log.info("replyCode:{}, replyText:{}, exchange:{}, routingKey:{}, context:{}", replyCode, replyText, exchange, routingKey, new String(body));
				}
			});
			channel.queueDeclare(QUEUE_CONFIRM, false, false, false, null);
			channel.exchangeDeclare(EXCHANGE_TOPIC_CONFIRM, BuiltinExchangeType.TOPIC, false, false, false, null);
			channel.queueBind(QUEUE_CONFIRM, EXCHANGE_TOPIC_CONFIRM, KEY_CONFIRM);
			// 这里使用另一个发布的api
			// mandatory: true:如果消息无法正常投递到Queue则return给生产者, false:无法正常投递的消息直接丢弃
			channel.basicPublish(EXCHANGE_TOPIC_CONFIRM, KEY_CONFIRM, true, null, "value".getBytes());
			// 奇怪, exchange不存在的时候, confirm监听就失效了, 而且导致其他ok的也一起失效了
//			channel.basicPublish(EXCHANGE_TOPIC_CONFIRM_NOT_EXIST, KEY_CONFIRM, true, null, "value".getBytes());
			// exchange已接收消息, 但是没有找到对应的queue时(routingKey不匹配), 就会return
			channel.basicPublish(EXCHANGE_TOPIC_CONFIRM, KEY_NOT_EXIST, true, null, "value".getBytes());
			ThreadKit.sleepSecond(3);
			// 注意: 渠道和连接不能立即关闭, 不然就无法监听了
			CommonKit.closeChannelAndConnection(channel);
//			ThreadKit.sleepDay(1);
		} catch (Throwable cause) {
			cause.printStackTrace();
		}
	}

}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值