周末之余学习了一下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
消费者A:0
消费者A:2
消费者A:4
消费者B:1
消费者B:3
代码示例
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
消费者A:0
消费者A:1
消费者A:2
消费者B:0
消费者B:1
消费者B:2
代码示例
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用到了该项目中,统计站点访问量,博客的阅读量等。