RabbitMQ学习——常见概念详解
原创
©著作权归作者所有:来自51CTO博客作者愤怒的可乐的原创作品,请联系作者获取转载授权,否则将追究法律责任
文章目录
Exchange
描述
用于接收消息,并根据路由键转发消息到所绑定的队列
结构图
蓝色框表示发送消息,然后消息通过路由关系路由到Queue1或Queue2
绿色框表示接收消息,消费者与队列建立监听,去消费消息
黄色框表示路由键绑定关系
交换机属性
- Name:交换机名称
- Type:交换机类型direct、topic、fanout、headers
- Durablity:是否需要持久化 true|false
- Auto Delete:当最后一个绑定到Exchange上的队列删除后,自动删除该Exchange
- Internal:当前Exchange是否用于RabbitMQ内部使用,默认为false
- Arguments:扩展参数,用于扩展AMQP协议自制定使用
Direct Exchange
- 所有发送到Direct Exchange的消息被转发到RoutingKey中指定的Queue
如图所示,路由到队列名为Key的队列中去了
生产者代码:
public class Producer4DirectExchange {
public static void main(String[] args) throws IOException {
Connection connection = ConnectionUtil.getConn();
Channel channel = connection.createChannel();
//声明
String exchangeName = "test_direct_exchange";
String routingKey = "test.direct";
//发送
String msg = "Hello, I am Producer4DirectExchange";
channel.basicPublish(exchangeName,routingKey,null,msg.getBytes());
}
}
消费者代码:
public static void main(String[] args) throws IOException, InterruptedException {
Connection connection = ConnectionUtil.getConn();
Channel channel = connection.createChannel();
//声明
String exchangeName = "test_direct_exchange";
String exchangeType = "direct";
String queueName = "test_direct_queue";
String routingKey = "test.direct";
//声明Exchange
channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
//声明一个队列
channel.queueDeclare(queueName,false,false,false,null);
//建立一个绑定关系
channel.queueBind(queueName,exchangeName,routingKey);
//durable 是否持久化消息
QueueingConsumer consumer = new QueueingConsumer(channel);
//参数:队列名称,是否自动ACK,Consumer
channel.basicConsume(queueName,true,consumer);
while (true) {
//阻塞获取消息
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String msg = new String(delivery.getBody());
System.out.println("收到消息:"+msg);
}
}
公共类:
public class ConnectionUtil {
public static final String MQ_HOST = "192.168.222.101";
public static final String MQ_VHOST = "/";
public static final int MQ_PORT = 5672;
public static Connection getConn() {
//1. 创建一个ConnectionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(MQ_HOST);//配置host
connectionFactory.setPort(MQ_PORT);//配置port
connectionFactory.setVirtualHost(MQ_VHOST);//配置vHost
//2. 通过连接工厂创建连接
try {
return connectionFactory.newConnection();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
登录控制台,可以看到名为test_direct_exchange
的交换机通过路由键test.direct
绑定到test_direct_queue
Topic Exchange
- 所有发送到Topic Exchange的消息都被转发到所有关心RoutingKey中指定Topic的队列上
- Exchange将RoutingKey和某Topic进行模糊匹配,此时队列需要绑定一个Topic
可以使用通配符进行模糊匹配:
#
匹配一个或多个词(单词的意思,不是一个字符)
*
匹配一个词
log.#
能匹配到"log.info.a"
log.*
只匹配"log.error"
如上图,比如,user.news和user.weather能路由到第一个队列
生成者代码:
public class Producer4TopicExchange {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtil.getConn();
Channel channel = connection.createChannel();
//声明
String exchangeName = "test_topic_exchange";
String routingKey1 = "user.save";
String routingKey2 = "user.update";
String routingKey3 = "user.find.abc";
//发送
String msg = "Hello, I am Producer4TopicExchange";
channel.basicPublish(exchangeName,routingKey1,null,msg.getBytes());
channel.basicPublish(exchangeName,routingKey2,null,msg.getBytes());
channel.basicPublish(exchangeName,routingKey3,null,msg.getBytes());
CloseTool.closeElegantly(channel,connection);
}
}
消费者代码:
public class Consumer4TopicExchange {
public static void main(String[] args) throws IOException, InterruptedException {
Connection connection = ConnectionUtil.getConn();
Channel channel = connection.createChannel();
//声明
String exchangeName = "test_topic_exchange";
String exchangeType = "topic";
String queueName = "test_topic_queue";
String routingKey = "user.#";
//声明Exchange
channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
//声明一个队列
channel.queueDeclare(queueName,false,false,false,null);
//建立一个绑定关系
channel.queueBind(queueName,exchangeName,routingKey);
//durable 是否持久化消息
QueueingConsumer consumer = new QueueingConsumer(channel);
//参数:队列名称,是否自动ACK,Consumer
channel.basicConsume(queueName,true,consumer);
while (true) {
//阻塞获取消息
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String msg = new String(delivery.getBody());
System.out.println("收到消息:"+msg);
}
}
}
先启动消费者,然后发现下面两个绑定关系:
队列
再启动生产者,从消费者端可以看到如下输出:
收到消息:Hello, I am Producer4TopicExchange
收到消息:Hello, I am Producer4TopicExchange
收到消息:Hello, I am Producer4TopicExchange
说明3条消息都收到了,接下来,我们改一下消费者的路由键,改为:String routingKey = "user.*";
再次启动
收到消息:Hello, I am Producer4TopicExchange
收到消息:Hello, I am Producer4TopicExchange
收到消息:Hello, I am Producer4TopicExchange
奇怪,怎么还是收到3条,带着疑问我们去看控制台
可以看到,之前的路由规则绑定还在。因此可以解释为啥能收到3条。
点击unbind解绑"user.#"
然后继续操作一把,查看输出
收到消息:Hello, I am Producer4TopicExchange
收到消息:Hello, I am Producer4TopicExchange
此时,只收到两条了。
Fanout Exchange
- 不处理路由键,只需要简单的将队列绑定到交换机上
- 发送到交换机的消息都会被转发到与该交互机绑定的所有队列上
- 转发消息是最快的
消息不走路由键,只要队列与交换机有绑定关系就能收到。
生产者:
public class Producer4FanoutExchange {
public static void main(String[] args) throws IOException {
Connection connection = ConnectionUtil.getConn();
Channel channel = connection.createChannel();
//声明
String exchangeName = "test_fanout_exchange";
String routingKey = "nothing";
//发送
String msg = "Hello, I am Producer4FanoutExchange";
channel.basicPublish(exchangeName,routingKey,null,msg.getBytes());
CloseTool.closeElegantly(channel,connection);
}
}
消费者:
public class Consumer4FanoutExchange {
public static void main(String[] args) throws IOException, InterruptedException {
Connection connection = ConnectionUtil.getConn();
Channel channel = connection.createChannel();
//声明
String exchangeName = "test_fanout_exchange";
String exchangeType = "fanout";
String queueName = "test_fanout_queue";
String routingKey = "";//不设置路由键
//声明Exchange
channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
//声明一个队列
channel.queueDeclare(queueName,false,false,false,null);
//建立一个绑定关系
channel.queueBind(queueName,exchangeName,routingKey);
//durable 是否持久化消息
QueueingConsumer consumer = new QueueingConsumer(channel);
//参数:队列名称,是否自动ACK,Consumer
channel.basicConsume(queueName,true,consumer);
while (true) {
//阻塞获取消息
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String msg = new String(delivery.getBody());
System.out.println("收到消息:"+msg);
}
}
}
和
Binding
- Exchange和Exchange、Queue之间的连接关系
- Binding中可以包含RoutingKey或参数
Queue
属性
- Durability:是否持久化
- Auto delete: 若为yes,代表当最后一个监听被移除后,该Queue会自动被删除
Message
- 服务器和应用程序之间传递的数据
- 本质就是一段数据,由Properties和Payload(Body)组成
属性
- delivery mode
- headers(自定义属性放到这里面)
- content_type
- content_encoding
- priority
- correlation_id
- reply_to:指令消息失败返回的队列
- expiration:过期时间
- message_id:消息ID
…
Producer:
public class Producer {
public static final String MQ_HOST = "192.168.222.101";
public static final String MQ_VHOST = "/";
public static final int MQ_PORT = 5672;
public static void main(String[] args) throws IOException, TimeoutException {
//1. 创建一个ConnectionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(MQ_HOST);//配置host
connectionFactory.setPort(MQ_PORT);//配置port
connectionFactory.setVirtualHost(MQ_VHOST);//配置vHost
//2. 通过连接工厂创建连接
Connection connection = connectionFactory.newConnection();
//3. 通过connection创建一个Channel
Channel channel = connection.createChannel();
Map<String,Object> headers = new HashMap<>();
headers.put("var1","abc");
headers.put("var2","sdd");
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.deliveryMode(2) //2:持久化投递;1:非持久化(未消费的消息重启后就没了)
.contentEncoding("UTF-8")
.expiration("5000")//5s
.headers(headers)
.build();
//4. 通过Channel发送数据
for (int i = 0; i < 10; i++) {
String message = "Hello" + i;
//exchange为"",则通过routingKey取寻找队列
channel.basicPublish("","testQueue",properties,message.getBytes());
}
//5. 关闭连接
channel.close();
connection.close();
}
}
consumer:
public class Consumer {
public static final String QUEUE_NAME = "testQueue";
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtil.getConn();
//3. 通过connection创建一个Channel
Channel channel = connection.createChannel();
//4. 声明(创建)一个队列
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
//5. 创建消费者
QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
//6. 设置Channel
channel.basicConsume(QUEUE_NAME,true,queueingConsumer);
int num = 0;
//7. 获取消息
while (true) {
//nextDelivery 会阻塞直到有消息过来
QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println("收到:" + message);
Map<String,Object > headers = delivery.getProperties().getHeaders();
System.out.println("headers get va1 :" + headers.get("var1"));
}
}
}
Virtual host - 虚拟主机
- 虚拟地址,用于进行逻辑隔离,最上层的消息路由
- 一个Virtuall Host里面可以有若干个Exchange和Queue
- 同一个Virtual Host里面不能有相同名称的Exchange或Queue
项目代码
下载地址