RabbitMQ不同模型样例
这篇样例代码都是基于这个包
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.10.0</version>
</dependency>
工作队列—轮询策略样例
-
轮询策略
生产端能力大于消费端能力,增加多几个消费节点
缺点:如果某个消费端消费能力不足处理时间过长会导致一个消息使用卡在那里等他消费完才会去找其他的消费节点,如同一个消费阻塞在那里其他节点在旁边干看着帮不上。
// 消费者代码 一共写两个消费者代码 public class Recv1 { private final static String QUEUE_NAME = "work_mq"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); // 连接信息 factory.setHost("20.18.24.143"); factory.setUsername("admin"); factory.setPassword("123456"); factory.setVirtualHost("devTest"); factory.setPort(5672); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); channel.queueDeclare(QUEUE_NAME, false, false, false, null); System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); // 消费 Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { super.handleDelivery(consumerTag, envelope, properties, body); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } // 一般固定,一般称为会话名称 /*System.out.println("consumerTag: "+consumerTag); // 可以获取交换机、路由键等信息 System.out.println("envelope: "+envelope); // System.out.println("properties: "+properties);*/ // System.out.println("body: "+new String(body,"utf-8")); // 手工确认消息,不是多条确认 channel.basicAck(envelope.getDeliveryTag(),false); } }; channel.basicConsume(QUEUE_NAME,false,consumer); } }
// 生产者代码 为测试轮询需要发送多条消息 做了10次发送 public class Send { // 队列名称 private final static String QUEUE_NAME = "work_mq"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); // 连接信息 factory.setHost("20.18.24.143"); factory.setUsername("admin"); factory.setPassword("123456"); factory.setVirtualHost("devTest"); factory.setPort(5672); // 继承 try (Connection connection = factory.newConnection(); // 创建信道 Channel channel = connection.createChannel()) { channel.queueDeclare(QUEUE_NAME, false, false, false, null); // 循环 发送10次消息 for (int i = 0; i < 10; i++) { String message = "Work_Mq Test"+i; channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8)); System.out.println(" [x] Sent '" + message + "'"); } } } }
运行结果:
#消费端1 打印结果 body: Work_Mq Test0 body: Work_Mq Test2 body: Work_Mq Test4 body: Work_Mq Test6 body: Work_Mq Test8 #消费端2 打印结果 body: Work_Mq Test1 body: Work_Mq Test3 body: Work_Mq Test5 body: Work_Mq Test7 body: Work_Mq Test9
工作队列—公平策略样例
-
公平策略
解决消费端消费能力不足的问题,降低消费时间
代码如下:
// 消费者代码 一共写两个消费者代码 一个Recv1一个Recv2区别仅在手动sleep的时间上 public class Recv1 { private final static String QUEUE_NAME = "work_mq_fair"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); // 连接信息 factory.setHost("20.18.24.143"); factory.setUsername("admin"); factory.setPassword("123456"); factory.setVirtualHost("devTest"); factory.setPort(5672); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); channel.queueDeclare(QUEUE_NAME, false, false, false, null); // 限制消费,每次消费一个,消费完成在进行下一个 channel.basicQos(1); System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); // 消费 Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { super.handleDelivery(consumerTag, envelope, properties, body); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } // 一般固定,一般称为会话名称 /* System.out.println("consumerTag: "+consumerTag); // 可以获取交换机、路由键等信息 System.out.println("envelope: "+envelope); // System.out.println("properties: "+properties);*/ // System.out.println("body: "+new String(body,"utf-8")); // 手工确认消息,不是多条确认 channel.basicAck(envelope.getDeliveryTag(),false); } }; channel.basicConsume(QUEUE_NAME,false,consumer); } }
// 生产者代码 public class Send { // 队列名称 private final static String QUEUE_NAME = "work_mq_fair"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); // 连接信息 factory.setHost("20.18.24.143"); factory.setUsername("admin"); factory.setPassword("123456"); factory.setVirtualHost("devTest"); factory.setPort(5672); // 继承 try (Connection connection = factory.newConnection(); // 创建信道 Channel channel = connection.createChannel()) { channel.queueDeclare(QUEUE_NAME, false, false, false, null); // 循环 发送10次消息 for (int i = 0; i < 10; i++) { String message = "work_mq_fair Test"+i; channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8)); System.out.println(" [x] Sent '" + message + "'"); } } } }
运行结果:
// 消费者1 打印结果 [*] Waiting for messages. To exit press CTRL+C body: work_mq_fair Test0 body: work_mq_fair Test4 body: work_mq_fair Test7 // 消费者2 打印结果 [*] Waiting for messages. To exit press CTRL+C body: work_mq_fair Test1 body: work_mq_fair Test2 body: work_mq_fair Test3 body: work_mq_fair Test5 body: work_mq_fair Test6 body: work_mq_fair Test8 body: work_mq_fair Test9 #手动设置了不同的sleep时间来模拟不同消费能力,体现公平策略 #我这里设置消费者1 sleep 5 秒 消费者2 sleep 2秒
工作队列—发布 订阅模型
什么是rabbitmq的发布订阅模式?
-
发布-订阅模型中,消息生产者不再是直接面对queue(队列名称),而是直面exchange,都需要经过exchange来进行消息的发送, 所有发往同一个fanout交换机的消息都会被所有监听这个交换机的消费者接收到
-
发布订阅-消息模型引入fanout交换机
通过把消息发送给交换机,交互机转发给对应绑定的队列
交换机绑定的队列是排它独占队列,自动删除
代码如下:
// 消费者 public class Recv1 { private final static String EXCHANGE_NAME = "exchange_fanout"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); // 连接信息 factory.setHost("20.18.24.143"); factory.setUsername("admin"); factory.setPassword("123456"); factory.setVirtualHost("devTest"); factory.setPort(5672); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); // 绑定交换机 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT); // 获取队列 String queueName = channel.queueDeclare().getQueue(); // 绑定交换机和队列 fanout交换机不用routingKey channel.queueBind(queueName,EXCHANGE_NAME,""); Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { super.handleDelivery(consumerTag, envelope, properties, body); System.out.println("body: "+new String(body,"utf-8")); // 手工确认消息,不是多条确认 channel.basicAck(envelope.getDeliveryTag(),false); } }; channel.basicConsume(queueName,false,consumer); } }
// 生产者public class Send { // 队列名称 private final static String EXCHANGE_NAME = "exchange_fanout"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); // 连接信息 factory.setHost("20.18.24.143"); factory.setUsername("admin"); factory.setPassword("123456"); factory.setVirtualHost("devTest"); factory.setPort(5672); // 继承 try (Connection connection = factory.newConnection(); // 创建信道 Channel channel = connection.createChannel()) { // 绑定交换机 fanout扇形 广播 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT); String message = "rabbitMQ 广播方式测试"; channel.basicPublish(EXCHANGE_NAME,"",null,message.getBytes(StandardCharsets.UTF_8)); System.out.println("广播消息发送成功"); } }}
运行结果:
#recv1 结果打印body: rabbitMQ 广播方式测试#recv2 结果打印body: rabbitMQ 广播方式测试
工作队列—路由模式
什么是rabbitmq的路由模式
-
交换机类型是Direct
-
队列和交换机绑定,需要指定一个路由key( 也叫Bingding Key)
-
消息生产者发送消息给交换机,需要指定routingKey
-
交换机根据消息的路由key,转发给对应的队列
可用于日志分类采集
代码如下:
// 消费者public class Recv1 { private final static String EXCHANGE_NAME = "exchange_direct"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); // 连接信息 factory.setHost("20.18.24.143"); factory.setUsername("admin"); factory.setPassword("123456"); factory.setVirtualHost("devTest"); factory.setPort(5672); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); // 绑定交换机 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); // 获取队列 String queueName = channel.queueDeclare().getQueue(); // 绑定交换机和队列 直连交换机 绑定routingKey channel.queueBind(queueName,EXCHANGE_NAME,"errorRoutingKey"); channel.queueBind(queueName,EXCHANGE_NAME,"infoRoutingKey"); channel.queueBind(queueName,EXCHANGE_NAME,"debugRoutingKey"); Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { super.handleDelivery(consumerTag, envelope, properties, body); System.out.println("body: "+new String(body,"utf-8")); // 手工确认消息,不是多条确认 channel.basicAck(envelope.getDeliveryTag(),false); } }; // 关闭消息自动确认 channel.basicConsume(queueName,false,consumer); }}
// 消费者2的代码只绑定error 只接收error的// 获取队列String queueName = channel.queueDeclare().getQueue();// 绑定交换机和队列 直连交换机 绑定routingKeychannel.queueBind(queueName,EXCHANGE_NAME,"errorRoutingKey");
// 生产者代码public class Send { // 队列名称 private final static String EXCHANGE_NAME = "exchange_direct"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); // 连接信息 factory.setHost("20.18.24.143"); factory.setUsername("admin"); factory.setPassword("123456"); factory.setVirtualHost("devTest"); factory.setPort(5672); // 继承 try (Connection connection = factory.newConnection(); // 创建信道 Channel channel = connection.createChannel()) { // 绑定交换机 直连交换机 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); String error = "订单服务错误日志error"; String info = "订单服务错误日志info"; String debug = "订单服务错误日志debug"; channel.basicPublish(EXCHANGE_NAME,"errorRoutingKey",null,error.getBytes(StandardCharsets.UTF_8)); channel.basicPublish(EXCHANGE_NAME,"infoRoutingKey",null,info.getBytes(StandardCharsets.UTF_8)); channel.basicPublish(EXCHANGE_NAME,"debugRoutingKey",null,debug.getBytes(StandardCharsets.UTF_8)); System.out.println("直连消息发送成功"); } }}
控制台绑定信息如下:
运行结果:
#recv1 结果打印body: 订单服务错误日志errorbody: 订单服务错误日志infobody: 订单服务错误日志debug#recv2 结果打印body: 订单服务错误日志error#一组日常日志队列 一组报错日志队列
工作队列—topic主题通配符
什么是rabbitmq的主题模式
-
交换机是 topic, 可以实现发布订阅模式fanout和路由模式Direct 的功能,更加灵活,支持模式匹配,通配符等
-
交换机同过通配符进行转发到对应的队列,* 代表一个词,#代表1个或多个词,一般用#作为通配符居多。
-
注意
- 交换机和队列绑定时用的binding使用通配符的路由健
- 生产者发送消息时需要使用具体的路由健
匹配规则示例:
quick.orange.rabbit 只会匹配 *.orange.* 和 *.*.rabbit ,进到Q1和Q2 lazy.orange.elephant 只会匹配 *.orange.* 和 lazy.#,进到Q1和Q2 quick.orange.fox 只会匹配 *.orange.*,进入Q1 lazy.brown.fox 只会匹配azy.#,进入Q2 lazy.pink.rabbit 只会匹配 lazy.#和*.*.rabbit ,同个队列进入Q2(消息只会发一次) quick.brown.fox 没有匹配,默认会被丢弃,可以通过回调监听二次处理 lazy.orange.male.rabbit,只会匹配 lazy.#,进入Q2
代码样例:
-
一个队列采集error日志 ,order.log.error
-
一个队列采集全部级别日志,* .log. *
//消费者1 代码public class Recv1 { private final static String EXCHANGE_NAME = "exchange_topic"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); // 连接信息 factory.setHost("20.18.24.143"); factory.setUsername("admin"); factory.setPassword("123456"); factory.setVirtualHost("devTest"); factory.setPort(5672); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); // 绑定交换机 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC); // 获取队列 String queueName = channel.queueDeclare().getQueue(); // 绑定交换机和队列 直连交换机 绑定routingKey channel.queueBind(queueName,EXCHANGE_NAME,"order.log.error"); Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { super.handleDelivery(consumerTag, envelope, properties, body); System.out.println("body: "+new String(body,"utf-8")); // 手工确认消息,不是多条确认 channel.basicAck(envelope.getDeliveryTag(),false); } }; // 关闭消息自动确认 channel.basicConsume(queueName,false,consumer); }}
//消费者2 代码中只有绑定的路由key和消费者1不一样// 绑定交换机和队列 直连交换机 绑定routingKeychannel.queueBind(queueName,EXCHANGE_NAME,"*.log.*");
// 生产者 发送两组订单的日志 和一组商品的日志public class Send { // 队列名称 private final static String EXCHANGE_NAME = "exchange_topic"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); // 连接信息 factory.setHost("20.18.24.143"); factory.setUsername("admin"); factory.setPassword("123456"); factory.setVirtualHost("devTest"); factory.setPort(5672); // 继承 try (Connection connection = factory.newConnection(); // 创建信道 Channel channel = connection.createChannel()) { // 绑定交换机 topic交换机 支持通配符模式 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC); String error = "订单服务错误日志error"; String info = "订单服务错误日志info"; String debug = "商品服务错误日志debug"; channel.basicPublish(EXCHANGE_NAME,"order.log.error",null,error.getBytes(StandardCharsets.UTF_8)); channel.basicPublish(EXCHANGE_NAME,"order.log.info",null,info.getBytes(StandardCharsets.UTF_8)); channel.basicPublish(EXCHANGE_NAME,"product.log.debug",null,debug.getBytes(StandardCharsets.UTF_8)); System.out.println("topic消息发送成功"); } }}
rabbit控制台绑定信息如下:
运行结果:
#recv1 打印信息body: 订单服务错误日志error#recv2 打印信息body: 订单服务错误日志errorbody: 订单服务错误日志infobody: 商品服务错误日志debug
实际应用中一般使用topic主题模型,topic模型更灵活