RabbitMQ的六种工作模式

87 阅读5分钟

周末之余学习了一下RabbitMQ的几种工作模式,写了一些Demo,特此记录。

前言

操作RabbitMQ需要先得到连接,下列所有例子均需要创建连接,为了方便先写一个工具类。

public class MQUtil {
	public static Connection getConnection() throws IOException, TimeoutException {
		//创建连接工厂
		ConnectionFactory factory = new ConnectionFactory();
		//设置IP
		factory.setHost("127.0.0.1");
		//端口 5672为服务端口,15672为webUI端口 25672为集群端口
		factory.setPort(5672);
		//设置虚拟主机
		factory.setVirtualHost("/");
		//设置 用户名 密码
		factory.setUsername("guest");
		factory.setPassword("guest");
		return factory.newConnection();
	}
}

简单模式

一个生产者,一个消费者。

public class SimpleMode {
	private static final String QUEUE_NAME = "test";
	//生产者
	@Test
	public void send() throws Exception {
		Connection connection = MQUtil.getConnection();
		//创建通道 通道是基于连接之上的,创建、销毁连接需要很大的开销,通道的方式利于提升性能
		Channel channel = connection.createChannel();
		/**
		 * 声明队列,各参数的作用:
		 * 消息队列名
		 * 是否持久化 默认否,为true则会将数据保存到erlang的mnesia数据库中
		 * 是否排外  即只允许该channel访问该队列   一般等于true的话用于一个队列只能有一个消费者来消费的场景
		 * 是否自动删除  消费完删除
		 * 其他属性
		 */
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);
		//发送的消息
		String message = "Hello World!";
		//发送消息,各参数作用:交换机、队列名、其他属性、消息body
		channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
		//关闭通道和连接
		channel.close();
		connection.close();
	}

	//消费者
	public static void main(String[] args) throws IOException, TimeoutException {
		Connection connection = MQUtil.getConnection();
		//创建通道 通道是基于连接之上的,创建、销毁连接需要很大的开销,通道的方式利于提升性能
		Channel channel = connection.createChannel();
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);
		DeliverCallback deliverCallback = (consumerTag, delivery) -> {
			String message = new String(delivery.getBody());
			System.out.println("消费者:" + message + "");
		};
		channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {});
		//不可关闭连接,否则无法触发回调 当前线程会阻塞,持续监听
	}
}

Work模式

一个生产者,多个消费者,单个消息只能被其中一个消费者消费。
使用工作模式,需要取消自动确认,只有在确认消息被成功消费时,才手动返回确认回执。

效果
生产者:0
生产者:1
生产者:2
生产者:3
生产者:4

消费者A0
消费者A2
消费者A4

消费者B1
消费者B3
代码示例
public class WorkMode {
	private static final String QUEUE_NAME = "test";

	@Test
	public void send() throws IOException, TimeoutException, InterruptedException {
		Connection connection = MQUtil.getConnection();
		Channel channel = connection.createChannel();
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);

		for (int i = 0; i < 5; i++) {
			channel.basicPublish("", QUEUE_NAME, null, String.valueOf(i).getBytes());
			System.out.println("生产者:" + i);
			Thread.sleep(1000);
		}
		channel.close();
		connection.close();
	}
}

class WorkReceiveA{
	public static void main(String[] args) throws IOException, TimeoutException {
		Connection connection = MQUtil.getConnection();
		Channel channel = connection.createChannel();
		channel.queueDeclare("test", false, false, false, null);

		DeliverCallback deliverCallback = (consumerTag, delivery) -> {
			String message = new String(delivery.getBody());
			System.out.println("消费者A:" + message + "");
			//手动消息确认 若不操作,消息将不会被删除。
			channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
		};
		boolean autoAck = false;
		channel.basicConsume("test", autoAck, deliverCallback, consumerTag -> {});
	}
}

class WorkReceiveB{
	public static void main(String[] args) throws IOException, TimeoutException {
		Connection connection = MQUtil.getConnection();
		Channel channel = connection.createChannel();
		channel.queueDeclare("test", false, false, false, null);

		DeliverCallback deliverCallback = (consumerTag, delivery) -> {
			String message = new String(delivery.getBody());
			System.out.println("消费者B:" + message + "");
			channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
		};
		boolean autoAck = false;
		channel.basicConsume("test", autoAck, deliverCallback, consumerTag -> {});
	}
}

订阅模式 Publish/Subscribe

1、一个生产者,多个消费者。消息能被同时被绑定相同交换机的所有消费者消费。
2、生产者并不会将消息直接发送到队列,而是发送到了交换机。
3、消费者需要将交换机和队列绑定,才能接收到消息。
4、交换机没有保存消息的能力,若没有队列绑定到交换机,消息将丢失。
5、该模式必须声明交换机,并且设置模式:channel.exchangeDeclare(EXCHANGE_NAME, “fanout”),fanout指分发模式(将每一条消息都发送到与交换机绑定的队列。
6、队列必须绑定交换机:channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, “”);

效果
生产者:0
生产者:1
生产者:2

消费者A0
消费者A1
消费者A2

消费者B0
消费者B1
消费者B2
代码示例
public class PublishSubscribe {
	private final static String EXCHANGE_NAME = "test_exchange_fanout";

	@Test
	public void send() throws Exception {
		Connection connection = MQUtil.getConnection();
		Channel channel = connection.createChannel();
		//声明交换机(分发:发布/订阅模式)
		channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
		for (int i = 0; i < 5; i++) {
			channel.basicPublish(EXCHANGE_NAME, "", null, String.valueOf(i).getBytes());
			System.out.println("生产者:"+i);
			Thread.sleep(1000);
		}
		//关闭通道和连接
		channel.close();
		connection.close();
	}
}

class ReceiveA{
	private static final String QUEUE_NAME = "testA";
	private final static String EXCHANGE_NAME = "test_exchange_fanout";
	public static void main(String[] args) throws IOException, TimeoutException {
		Connection connection = MQUtil.getConnection();
		Channel channel = connection.createChannel();
		channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
		//声明队列
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);
		//将队列绑定到交换机
		channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
		//每次只分发一个
		channel.basicQos(1);
		DeliverCallback deliverCallback = (consumerTag, delivery) -> {
			String message = new String(delivery.getBody());
			System.out.println("消费者A:" + message + "");
		};
		channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {});
	}
}

class ReceiveB{
	private static final String QUEUE_NAME = "testB";
	private final static String EXCHANGE_NAME = "test_exchange_fanout";
	public static void main(String[] args) throws IOException, TimeoutException {
		Connection connection = MQUtil.getConnection();
		Channel channel = connection.createChannel();
		channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
		//声明队列
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);
		//将队列绑定到交换机
		channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
		//每次只分发一个
		channel.basicQos(1);
		DeliverCallback deliverCallback = (consumerTag, delivery) -> {
			String message = new String(delivery.getBody());
			System.out.println("消费者B:" + message + "");
		};
		channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {});
	}
}

路由模式 Routing

1、和订阅模式一样,消息不直接发送到队列,而是发送到交换机。
2、发送消息到交换机时需要指定路由键Key ,消费者将队列绑定到交换机时也需要指定路由Key。
3、只有交换机相同,且路由Key相同时,才能接收到消息。
4、路由模式比订阅模式更加灵活。

代码示例 [只有消费者A能接收到消息]
public class RoutingMode {
	private static final String EXCHANGE_NAME = "test_exchange_direct";
	private static final String KEY = "testKey";

	public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
		Connection connection = MQUtil.getConnection();
		Channel channel = connection.createChannel();
		//声明交换机
		channel.exchangeDeclare(EXCHANGE_NAME, "direct");

		for (int i = 0; i < 5; i++) {
			//发布时 指定key
			channel.basicPublish(EXCHANGE_NAME,KEY,null, String.valueOf(i).getBytes());
			System.out.println("发送:"+i);
			Thread.sleep(1000);
		}
		System.out.println("发送成功");
		//消息发送到交换机时,交换机必须与队列绑定,否则消息会丢失。
		//交换机没有保存消息的能力
		channel.close();
		connection.close();
	}
}
class RoutingModeReceiveA{
	private static final String QUEUE_NAME = "testA";
	private final static String EXCHANGE_NAME = "test_exchange_direct";
	public static void main(String[] args) throws IOException, TimeoutException {
		Connection connection = MQUtil.getConnection();
		Channel channel = connection.createChannel();
		channel.exchangeDeclare(EXCHANGE_NAME, "direct");
		//声明队列
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);
		//将队列绑定到交换机 不绑定则无法接受消息
		channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "testKey");
		//每次只分发一个
		channel.basicQos(1);
		DeliverCallback deliverCallback = (consumerTag, delivery) -> {
			String message = new String(delivery.getBody());
			System.out.println("消费者A:" + message + "");
		};
		channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {});
	}
}

class RoutingModeReceiveB{
	private static final String QUEUE_NAME = "testB";
	private final static String EXCHANGE_NAME = "test_exchange_direct";
	public static void main(String[] args) throws IOException, TimeoutException {
		Connection connection = MQUtil.getConnection();
		Channel channel = connection.createChannel();
		channel.exchangeDeclare(EXCHANGE_NAME, "direct");
		//声明队列
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);
		//将队列绑定到交换机 路由Key不匹配则无法接收消息
		channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "errorKey");
		//每次只分发一个
		channel.basicQos(1);
		DeliverCallback deliverCallback = (consumerTag, delivery) -> {
			String message = new String(delivery.getBody());
			System.out.println("消费者B:" + message + "");
		};
		channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {});
	}
}

主题模式 Topic

1、顾名思义,只接收某一主题的消息,例如:日志类。
2、功能和路由模式相同,只是路由Key可以进行匹配。
3、“#”匹配一个或多个字符,“*”只匹配一个字符。

public class TopicMode {
	private static final String EXCHANGE_NAME = "test_exchange_topic";
	private static final String KEY = "log.info";

	public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
		Connection connection = MQUtil.getConnection();
		Channel channel = connection.createChannel();
		//声明交换机
		channel.exchangeDeclare(EXCHANGE_NAME, "topic");
		for (int i = 0; i < 5; i++) {
			//发布时 指定key
			channel.basicPublish(EXCHANGE_NAME,KEY,null, String.valueOf(i).getBytes());
			System.out.println("生产者:"+i);
			Thread.sleep(1000);
		}
		//消息发送到交换机时,交换机必须与队列绑定,否则消息会丢失。
		//交换机没有保存消息的能力
		channel.close();
		connection.close();
	}
}
class TopicReceiveA{
	private static final String QUEUE_NAME = "testA";
	private final static String EXCHANGE_NAME = "test_exchange_topic";
	public static void main(String[] args) throws IOException, TimeoutException {
		Connection connection = MQUtil.getConnection();
		Channel channel = connection.createChannel();
		channel.exchangeDeclare(EXCHANGE_NAME, "topic");
		//声明队列
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);
		//将队列绑定到交换机 不绑定则无法接受消息,可以匹配路由Key为log.开头的所有交换机
		channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "log.#");
		//每次只分发一个
		channel.basicQos(1);
		DeliverCallback deliverCallback = (consumerTag, delivery) -> {
			String message = new String(delivery.getBody());
			System.out.println("消费者A:" + message + "");
			System.out.println(delivery.getEnvelope().getRoutingKey());
		};
		channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {});
	}
}

RPC模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oDp2EwVM-1570843149714)(/blog/image/getImage/b673bd63-b4af-430b-85ed-a2995875a0b3)]

尾巴

学习后将RabbitMQ用到了该项目中,统计站点访问量,博客的阅读量等。