RabbitMQ------四种模式
一. 工作队列模式
1. 消息生产能力大于消费能力,增加多几个消费节点
2. 默认策略:轮训(round robin)
缺点:存在部分节点消费过快,部分节点消费过慢,导致不能合理处理消息
例子:生产者发送了10条消息,有2个消费者,则每个消费者都处理5条消息
3. 公平策略
优点:解决消费者消费能力不足问题,降低消费时间
例子:能者多劳,消费者处理完当前的1条消息后,再处理下1条。生产者发送了6(a~f)条消息 时间/s 1 2 3 4 5 消费者1 a c d f 消费者2 b e
如图:
p:生产者 红色:队列 c:消费者
代码:
1.添加pom.xml依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
2.添加消息生产者代码
import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; class sender{ private static final String QUEUE_Name = "hello"; public static void main(String[] args){ ConnectionFactory factory = new ConnectionFactory(); factory.setHost("127.0.0.1"); factory.setUsername("guest"); factory.setPassword("123456"); factory.setVirtualHost("/dev"); try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()){ channel.queueDeclare(QUEUE_Name, false, false, false, null); //一次发送10条消息 for(Integer i = 0; i<10; i++){ String msg = "hi hutao " + i; System.out.println(msg); channel.basicPublish("", QUEUE_Name, null, msg.getBytes("utf-8")); } } catch (Exception e){ } } }
3.添加消息消费者代码
公平策略代码:1表示消费者每次处理完1条消息再从生产者处获取消息
channel.basicQos(1);
import com.rabbitmq.client.*; import java.io.IOException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** * 可以拷贝整个class代码模拟多个消费者接收消息 * 再通过修改TimeUnit.SECONDS.sleep模拟消费者处理消息速度 * * */ class receiver { private static final String QUEUE_Name = "hello"; public static void main(String[] args) throws IOException, TimeoutException { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("127.0.0.1"); factory.setUsername("guest"); factory.setPassword("123456"); factory.setVirtualHost("/dev"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); channel.queueDeclare(QUEUE_Name, false, false, false, null); //公平策略,能让处理消息快的消费者处理更多消息 //限制消费者每次消费1个,处理完再消费下一个 channel.basicQos(1); // 官网的测试方法 // DeliverCallback deliverCallback = (consumerTag, delivery) -> { // String message = new String(delivery.getBody(), "UTF-8"); // System.out.println(" [x] Received '" + message + "'"); // }; //自定义的测试方法 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 (Exception e){} String msg = new String(body, "utf-8"); System.out.println(msg); //手工确认消息 不是多条确认 channel.basicAck(envelope.getDeliveryTag(), false); } }; //消费 多个消费者时要关闭消息自动确认 channel.basicConsume(QUEUE_Name, false, consumer); } }
二. 发布/订阅模式
1. 生产者将消息发送到交换机(Exchange),再由交换机转发到队列
2. 交换机只负责转发消息,不具备存储消息能力,如果没有队列和交换机绑定,或者没有符合的路由规则,则消息会被丢失
3. 交换机常见四种类型:
备注:路由键(RoutingKey)
Direct Exchange(定向)
a. 将一个队列绑定交换机,要求该消息与一个特定的路由键完全匹配
b. 处理路由键
例子:
一个队列绑定交换机的路由键是aaa.bb,则只有被标记为aaa.bb的消息才会被转发,标记为a.bb,aaa.b的都不会被转发
Fanout Exchange(广播)
a. 只需要将队列绑定交换机,则发送到交换机上的消息都会被转发到与之绑定的队列中
b. 速度是最快的
c. 不处理路由键
Topic Exchange(通配符)
a. topic交换机是一种发布/订阅模式,结合了direct交换机和fanout交换机的特点 b. 将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上 c. 符号“#”匹配一个或者多个词,“*”只匹配一个词 例子: abc.#可以匹配到abc.aa,abc.ef,abc.ccc.dd abc.*可以匹配到abc.aa,abc.ef
Headers Exchange(基本不用)
如图:
4.
a. 发布/订阅模式中消息生产者不再直接面对队列(queque),而是面对交换机(exchange),消息都需要经过交换机进行发送,
b. 所有发往同一个fanout交换机的消息都会被所有监听这个交换机的消费者接收到
c. 应用场景
微信公众号,新浪微博关注等等
如图:
代码:
1.添加生产者代码
import com.rabbitmq.client.BuiltinExchangeType; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import java.nio.charset.StandardCharsets; public class sender { private static final String EXCHANGE_NAME = "exchange_fanout"; public static void main(String[] args){ ConnectionFactory factory = new ConnectionFactory(); factory.setHost("127.0.0.1"); factory.setUsername("guest"); factory.setPassword("123456"); factory.setVirtualHost("/dev"); //创建连接 //jdk7之后版本会自动关闭 try (Connection connection = factory.newConnection(); //创建信道 Channel channel = connection.createChannel()){ //绑定交换机,fanout扇形,即广播模式 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT); String msg = "测试广播模式"; channel. basicPublish(EXCHANGE_NAME, "", null, msg.getBytes(StandardCharsets.UTF_8)); } catch (Exception e){ } } }
2.添加消费者代码
import com.rabbitmq.client.*; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.concurrent.TimeoutException; /** * 可以拷贝多个此类来模拟多个消费者 * * */ public class recvier { //交换机名称 private static final String EXCHANGE_NAME = "exchange_fanout"; public static void main(String[] args) throws IOException, TimeoutException { //配置连接rabbitmq ConnectionFactory factory = new ConnectionFactory(); factory.setHost("127.0.0.1"); factory.setUsername("guest"); factory.setPassword("12345"); factory.setVirtualHost("/dev"); //创建连接和信道 Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); //绑定交换机,fanout扇形,即广播模式 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT); //获取队列,会自动分配队列 //可以在rabbitmq管理界面 http://localhost:15672/ // Queues -> Overview列表 -> Name中查看 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); String msg = new String(body, StandardCharsets.UTF_8); //控制台打印发布者传过来的消息 System.out.println(msg); //手工确认消息 不是多条确认 channel.basicAck(envelope.getDeliveryTag(), false); } }; //消费 多个消费者时要关闭消息自动确认 channel.basicConsume(queueName, false, consumer); } }
三. 路由模式
1. 交换机类型是Direct
2. 队列和交换机绑定,需要指定路由键(RoutingKey)
3. 生产者发送消息给交换机,需要指定路由键
4. 交换机根据路由键,将消息发送给指定的队列
如图:
代码:
1.添加生产者代码
import com.rabbitmq.client.BuiltinExchangeType; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import java.nio.charset.StandardCharsets; public class sender { private static final String EXCHANGE_NAME = "exchange_direct"; public static void main(String[] args){ ConnectionFactory factory = new ConnectionFactory(); factory.setHost("127.0.0.1"); factory.setUsername("guest"); factory.setPassword("123456"); factory.setVirtualHost("/dev"); //创建连接 //jdk7之后版本会自动关闭 try (Connection connection = factory.newConnection(); //创建信道 Channel channel = connection.createChannel()){ //绑定交换机,直连交换机 direct channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); String msgError = "错误信息"; String msgInfo = "提示信息"; String msgDebug = "调试信息"; //路由模式需要指定routingKey channel. basicPublish(EXCHANGE_NAME, "errorRoutingKey", null, msgError.getBytes(StandardCharsets.UTF_8)); channel. basicPublish(EXCHANGE_NAME, "infoRoutingKey", null, msgInfo.getBytes(StandardCharsets.UTF_8)); channel. basicPublish(EXCHANGE_NAME, "debugRoutingKey", null, msgDebug.getBytes(StandardCharsets.UTF_8)); } catch (Exception e){ } } }
2.添加消费者代码
import com.rabbitmq.client.*; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.concurrent.TimeoutException; /** * 可以拷贝多个此类来模拟只接收info消息的消费者 * 绑定交换机和队列时,不绑定errorRoutingKey和debugRoutingKey即可 * * */ public class recvier { //交换机名称 private static final String EXCHANGE_NAME = "exchange_direct"; public static void main(String[] args) throws IOException, TimeoutException { //配置连接rabbitmq ConnectionFactory factory = new ConnectionFactory(); factory.setHost("127.0.0.1"); factory.setUsername("guest"); factory.setPassword("123456"); factory.setVirtualHost("/dev"); //创建连接和信道 Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); //绑定交换机,直连交换机(direct) channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); //获取队列,会自动分配队列 //可以在rabbitmq管理界面 http://localhost:15672/ // Queues -> Overview列表 -> Name中查看 String queueName = channel.queueDeclare().getQueue(); //绑定交换机和队列 直连模式direct需要指定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); String msg = new String(body, StandardCharsets.UTF_8); //控制台打印发布者传过来的消息 System.out.println(msg); //手工确认消息 不是多条确认 channel.basicAck(envelope.getDeliveryTag(), false); } }; //消费 多个消费者时要关闭消息自动确认 channel.basicConsume(queueName, false, consumer); } }
四. 主题模式
1. 交换机是topic
2. 可以实现发布订阅模式(fanout)和路由模式(direct)功能,更加灵活,支持模式匹配,通配符等
3. 交换机通过通配符转发对对应队列
#代表一个或者多个词
*代表一个词
例子:
order.#会匹配order.log,order.log.info等等
order.*会匹配order.log,order.new等等
4.
生产者发送消息需要指定具体路由键
交换机和队列绑定使用通配符路由键
如图:
代码:
1.添加生产者代码
import com.rabbitmq.client.BuiltinExchangeType; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import java.nio.charset.StandardCharsets; public class sender { private static final String EXCHANGE_NAME = "exchange_topic"; public static void main(String[] args){ ConnectionFactory factory = new ConnectionFactory(); factory.setHost("127.0.0.1"); factory.setUsername("guest"); factory.setPassword("123456"); factory.setVirtualHost("/dev"); //创建连接 //jdk7之后版本会自动关闭 try (Connection connection = factory.newConnection(); //创建信道 Channel channel = connection.createChannel()){ //绑定交换机,topic交换机 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC); String msgError = "订单错误信息"; String msgInfo = "订单提示信息"; String msgDebug = "商品调试信息"; //topic模式需要指定routingKey channel. basicPublish(EXCHANGE_NAME, "order.log.error", null, msgError.getBytes(StandardCharsets.UTF_8)); channel. basicPublish(EXCHANGE_NAME, "order.log.info", null, msgInfo.getBytes(StandardCharsets.UTF_8)); channel. basicPublish(EXCHANGE_NAME, "product.log.debug", null, msgDebug.getBytes(StandardCharsets.UTF_8)); } catch (Exception e){ } } }
2.添加消费者代码
import com.rabbitmq.client.*; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.concurrent.TimeoutException; /** * 可以拷贝多个此类来模拟只接收订单日志错误信息的消费者 * 绑定交换机和队列时,只要将routingKey改为order.log.error即可 * * */ public class recvier { //交换机名称 private static final String EXCHANGE_NAME = "exchange_topic"; public static void main(String[] args) throws IOException, TimeoutException { //配置连接rabbitmq ConnectionFactory factory = new ConnectionFactory(); factory.setHost("127.0.0.1"); factory.setUsername("guest"); factory.setPassword("123456"); factory.setVirtualHost("/dev"); //创建连接和信道 Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); //绑定交换机,topic交换机 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC); //获取队列,会自动分配队列 //可以在rabbitmq管理界面 http://localhost:15672/ // Queues -> Overview列表 -> Name中查看 String queueName = channel.queueDeclare().getQueue(); //绑定交换机和队列 topic模式需要指定routingKey //这里获取所有匹配*log.*的日志信息 channel.queueBind(queueName, EXCHANGE_NAME, "*.log.*"); 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); String msg = new String(body, StandardCharsets.UTF_8); //控制台打印发布者传过来的消息 System.out.println(msg); //手工确认消息 不是多条确认 channel.basicAck(envelope.getDeliveryTag(), false); } }; //消费 多个消费者时要关闭消息自动确认 channel.basicConsume(queueName, false, consumer); } }