RabbitMQ——02重要概念与交换器

rabbitMQ处理消息原理图:

1. 相关概念

1.1 Message——消息

        消息是不具名的, 它由消息头消息体组成。 消息体是不透明的, 而消息头则由一系列可选属性组成, 这些属性包括:routing-key(路由键)、 priority(相对于其他消息的优先权)、 delivery-mode(指出消息可能持久性存储)等。

1.2 publisher——消息的生产者

也是一个向交换器发布消息的客户端应用程序。

1.3 Consumer——消息的消费者

表示一个从消息队列中取得消息的客户端应用程序。

1.4 Exchange——交换器

 用来接收生产者发送的消息并将这些消息路由给服务器中的队列。
三种常用的交换器类型
1. direct(发布与订阅 完全匹配)
2. fanout(广播)
3. topic(主题, 规则匹配)

1.5 Binding——绑定

        用于消息队列和交换器之间的关联。 一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则, 所以可以将交换器理解成一个由绑定构成的路由表

1.6 Queue——消息队列

        用来保存消息直到发送给消费者。 它是消息的容器, 也是消息的终点。 一个消息可投入一个或多个队列。 消息一直在队列里面, 等待消费者链接到这个队列将其取走。

1.7 Routing-key——路由键

        RabbitMQ 决定消息该投递到哪个队列的规则。队列通过路由键绑定到交换器。消息发送到 MQ 服务器时, 消息将拥有一个路由键, 即便是空的, RabbitMQ 也会将其和绑定使用的路由键进行匹配。如果相匹配, 消息将会投递到该队列。如果不匹配, 消息将会进入黑洞。

        简单的说,rabbitMQ会将拥有同一个路由键的消息放到一个队列中。

1.8 Connection——链接

指 rabbit 服务器和服务建立的 TCP 链接。

1.9 Channer——信道

1, Channel 中文叫做信道, 是 TCP 里面的虚拟链接。 例如: 电缆相当于 TCP, 信道是一个独立光纤束, 一条 TCP 连接上创建多条信道是没有问题的。
2, TCP 一旦打开, 就会创建 AMQP 信道。
3, 无论是发布消息、 接收消息、 订阅队列, 这些动作都是通过信道完成的。

1.10 Virtual Host——虚拟主机

         表示一批交换器, 消息队列和相关对象。 虚拟主机是共享相同的身份认证和加密环境的独立服务器域。 每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器, 拥有自己的队列、 交换器、 绑定和权限机制。 vhost 是 AMQP 概念的基础, 必须在链接时指定,RabbitMQ 默认的 vhost 是/

1.11 Borker

        表示消息队列服务器实体

1.12 交换器和队列的关系

        交换器是通过路由键和队列绑定在一起的, 如果消息拥有的路由键跟队列和交换器的路由键匹配, 那么消息就会被路由到该绑定的队列中。也就是说, 消息到队列的过程中, 消息首先会经过交换器, 接下来交换器在通过路由键匹配分发消息到具体的队列中。路由键可以理解为匹配的规则。

1.13 RabbitMQ 为什么需要信道? 为什么不是 TCP 直接通信?

1. TCP 的创建和销毁开销特别大。 创建需要 3 次握手, 销毁需要 4 次分手。
2. 如果不用信道, 那应用程序就会以 TCP 链接 Rabbit, 高峰时每秒成千上万条链接会造成资源巨大的浪费, 而且操作系统每秒处理 TCP 链接数也是有限制的, 必定造成性能瓶颈。
3. 信道的原理是一条线程一条通道, 多条线程多条通道同用一条 TCP 链接。 一条 TCP链接可以容纳无限的信道, 即使每秒成千上万的请求也不会成为性能的瓶颈

2. 交换器

所谓的交换器,就是mq根据生产发送过来的消息,经过交换器来指定并决定将这个消息存入到哪个队列中,然后消费者在通过信道,从相应的队列中取出消息。

2.1 Direct交换器——发布/订阅 完全匹配

需求:系统日志处理

1)微服务产生的日志,交给日志服务器处理

2)日志处理服务器有4个服务,分别为DEBUG、INFO、WARN、ERROR

3)服务直接通信采用Direct(发布订阅)

2.1.1 环境准备

创建两个项目,生产者和消费者并引入相关的坐标

1)消费者配置文件

server.port=9090

## 配置项目名称(非必须配置)
spring.application.name=springcloud-mq
## 配置mq服务器地址
spring.rabbitmq.host=192.168.25.130
## 配置端口号  注意:rabbitMQ的web监控端口是15672
spring.rabbitmq.port=5672
## 配置用户名
spring.rabbitmq.username=oldlu
## 配置密码
spring.rabbitmq.password=123456

### 设置交换器的名称
mq.config.exchange=log.direct

### 指定info队列的名称
mq.config.queue.info=log.info
### 指定进入info消息队列的路由键
mq.config.queue.info.routing.key=log.info.routing.key

### error队列名称
mq.config.queue.error=log.error
### 指定进入error消息队列的路由键
mq.config.queue.error.routing.key=log.error.routing.key

2)生产者配置文件

生产者通过路由键来决定消息发送到哪个队列中的,所以生产者也需要配置相应的路由键

server.port=9099

## 配置项目名称(非必须配置)
spring.application.name=springcloud-mq
## 配置mq服务器地址
spring.rabbitmq.host=192.168.25.130
## 配置端口号  注意:rabbitMQ的web监控端口是15672
spring.rabbitmq.port=5672
## 配置用户名
spring.rabbitmq.username=oldlu
## 配置密码
spring.rabbitmq.password=123456

### 设置交换器的名称
mq.config.exchange=log.direct

### 指定进入到info队列的路由键
mq.config.queue.info.routing.key=log.info.routing.key
### 指定进入到error队列的路由键
mq.config.queue.error.routing.key=log.error.routing.key

2.1.2 消费者代码编写

1)info消费者

package com.bjc.consumer;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
@RabbitListener(bindings = @QueueBinding(
				exchange = @Exchange(							// 1. 指定交换机
							value = "${mq.config.exchange}",	// 		1.1 指定交换机名称
							type = ExchangeTypes.DIRECT			//		1.2 指定交换机类型
						), 	
				value = @Queue(									// 2. 指定队列名
							value = "${mq.config.queue.info}",	//		2.1 指定队列名称
							autoDelete = "true"					//		2.2 是否是一个可删除的临时队列 
						),			
				key="${mq.config.queue.info.routing.key}"		// 3. 指定路由键
		))
public class InfoReceiver {
	
	/**接收消息的方法,采用消息队列的事件监听机制
	 * @param msg
	 */
	@RabbitHandler
	public void consumer(String msg) {
		System.out.println("info消息接收:" + msg);
	}
	
}

注意:

1. 注解@RabbitListener可以放在类上,表示该类是一个事件监听类,但是类中会有多个方法,所以需要配合某个方法上的@RabbitHandler注解结合使用。

2. 我们配置rabbitMQ有三要素,交换器、队列和路由键,三者的配置都是在@RabbitListener中配置的,同时用@RabbitListener的属性bindings来绑定三要素

3. 而绑定要素又是通过注解@QueueBinding来配置的。在@QueueBinding中,有三个属性exchange、value和key,分别表示交换机配置,消息队列配置与路由键配置

4. 交换器的配置又是由注解@Exchange来完成,其中@Exchange的value表示交换器的名称,通过表达式读取配置文件指定的名字,防止硬编码,通过type指定交换器的类型

5. 消息队列的配置是通过@Queue来完成的,其中@Queue的value表示消息队列的名称,通过表达式读取配置文件指定的队列名称

6. 路由的配置比较简单,直接读取配置文件的配置即可。

2)error消费者

package com.bjc.consumer;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
@RabbitListener(bindings = @QueueBinding(
				exchange = @Exchange(							// 1. 指定交换机
							value = "${mq.config.exchange}",	// 		1.1 指定交换机名称
							type = ExchangeTypes.DIRECT			//		1.2 指定交换机类型
						), 	
				value = @Queue(									// 2. 指定队列名
							value = "${mq.config.queue.error}",	//		2.1 指定队列名称
							autoDelete = "true"					//		2.2 是否是一个可删除的临时队列 
						),			
				key="${mq.config.queue.error.routing.key}"		// 3. 指定路由键
		))
public class ErrorReceiver {
	
	/**接收消息的方法,采用消息队列的事件监听机制
	 * @param msg
	 */
	@RabbitHandler
	public void consumer(String msg) {
		System.out.println("error消息接收:" + msg);
	}
	
}

2.1.3 生产者代码编写

        生产者需要根据交换器和路由键将消息发送到rabbitMQ中,然后rabbitMQ根据传递的消息,由交换器和路由键来决定将消息放到哪个队列中,所以,这里在生产者中需要指定交换器和路由键。

1)info生产者

package com.bjc.producer;

import javax.annotation.Resource;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * 消息发送者
 *
 */
@Component
public class Sender {
	
	@Resource
	private AmqpTemplate rabbitTemplate;
	
	@Value("${mq.config.queue.info.routing.key}")
	private String router;
	
	@Value("${mq.config.exchange}")
	private String exchange;
	
	public void sendMsg(String msg) {
		// 向消息队列发送消息
		// 参数一:交换器名称
		// 参数二:路由键
		// 参数三:消息
		rabbitTemplate.convertAndSend(exchange,router,msg);
	}
	
}

测试:

1. 先运行消费者

2. 在运行生产者测试类,测试类如下;

@Test
public void test() throws Exception {
	while(true) {
		Thread.sleep(1000);
		sender.sendMsg("你好MQ!");
	}
}

控制台开始发送消息,如图;

消息接收者一直是info,与预期相符,在看rabbitMQ控制台,log.info处于运行状态,log.error没运行,如图:

2)error生产者

将交换器和路由键改成error的即可

2.2 Topic交换器——主题, 规则匹配

需求:

Topic交换器可以根据路由键模糊匹配,然后决定将哪个消息发送到哪个队列中 

2.2.1 环境准备

新建两个工程,消费者和生产

1)生产者配置文件

server.port=9099

## 配置项目名称(非必须配置)
spring.application.name=springcloud-mq
## 配置mq服务器地址
spring.rabbitmq.host=192.168.25.130
## 配置端口号  注意:rabbitMQ的web监控端口是15672
spring.rabbitmq.port=5672
## 配置用户名
spring.rabbitmq.username=oldlu
## 配置密码
spring.rabbitmq.password=123456

### 设置交换器的名称
mq.config.exchange=log.topic

2)消费者配置文件

server.port=9090

## 配置项目名称(非必须配置)
spring.application.name=springcloud-mq
## 配置mq服务器地址
spring.rabbitmq.host=192.168.25.130
## 配置端口号  注意:rabbitMQ的web监控端口是15672
spring.rabbitmq.port=5672
## 配置用户名
spring.rabbitmq.username=oldlu
## 配置密码
spring.rabbitmq.password=123456

### 设置交换器的名称
mq.config.exchange=log.direct

### 指定info队列的名称
mq.config.queue.info=log.info

### error队列名称
mq.config.queue.error=log.error

### log队列名称
mq.config.queue.logs=log.all

2.2.2 生产者代码编写

1)User

package com.bjc.producer;

import javax.annotation.Resource;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * 消息发送者
 *
 */
@Component
public class UserSender {
	
	@Resource
	private AmqpTemplate rabbitTemplate;

	@Value("${mq.config.exchange}")
	private String exchange;
	
	public void sendMsg(String msg) {
		// 向消息队列发送消息
		// 参数一:交换器名称
		// 参数二:路由键
		// 参数三:消息
		rabbitTemplate.convertAndSend(exchange,"user.log.info","user.info:" + msg);
		rabbitTemplate.convertAndSend(exchange,"user.log.debug","user.debug:" + msg);
		rabbitTemplate.convertAndSend(exchange,"user.log.warn","user.warn:" + msg);
		rabbitTemplate.convertAndSend(exchange,"user.log.error","user.error:" + msg);
	}
	
}

2)product

package com.bjc.producer;

import javax.annotation.Resource;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * 消息发送者
 *
 */
@Component
public class ProductSender {
	
	@Resource
	private AmqpTemplate rabbitTemplate;

	@Value("${mq.config.exchange}")
	private String exchange;
	
	public void sendMsg(String msg) {
		// 向消息队列发送消息
		// 参数一:交换器名称
		// 参数二:路由键
		// 参数三:消息
		rabbitTemplate.convertAndSend(exchange,"product.log.info","product.info:" + msg);
		rabbitTemplate.convertAndSend(exchange,"product.log.debug","product.debug:" + msg);
		rabbitTemplate.convertAndSend(exchange,"product.log.warn","product.warn:" + msg);
		rabbitTemplate.convertAndSend(exchange,"product.log.error","product.error:" + msg);
	}
	
}

2.2.3 消费者代码

1)info

package com.bjc.consumer;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/**接收路由键为 *.log.info的消息
 * @author Administrator
 *
 */
@Component
@RabbitListener(bindings = @QueueBinding(
				exchange = @Exchange(							// 1. 指定交换机
							value = "${mq.config.exchange}",	// 		1.1 指定交换机名称
							type = ExchangeTypes.TOPIC			//		1.2 指定交换机类型
						), 	
				value = @Queue(									// 2. 指定队列名
							value = "${mq.config.queue.info}",	//		2.1 指定队列名称
							autoDelete = "true"					//		2.2 是否是一个可删除的临时队列 
						),			
				key="*.log.info"		// 3. 指定路由键 模糊匹配
		))
public class InfoReceiver {
	
	/**接收消息的方法,采用消息队列的事件监听机制
	 * @param msg
	 */
	@RabbitHandler
	public void consumer(String msg) {
		System.out.println("info消息接收:" + msg);
	}
	
}

2)error

package com.bjc.consumer;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
@RabbitListener(bindings = @QueueBinding(
				exchange = @Exchange(							// 1. 指定交换机
							value = "${mq.config.exchange}",	// 		1.1 指定交换机名称
							type = ExchangeTypes.TOPIC			//		1.2 指定交换机类型
						), 	
				value = @Queue(									// 2. 指定队列名
							value = "${mq.config.queue.error}",	//		2.1 指定队列名称
							autoDelete = "true"					//		2.2 是否是一个可删除的临时队列 
						),			
				key="*.log.error"		// 3. 指定路由键
		))
public class ErrorReceiver {
	
	/**接收消息的方法,采用消息队列的事件监听机制
	 * @param msg
	 */
	@RabbitHandler
	public void consumer(String msg) {
		System.out.println("error消息接收:" + msg);
	}
	
}

3)all

package com.bjc.consumer;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
@RabbitListener(bindings = @QueueBinding(
				exchange = @Exchange(							// 1. 指定交换机
							value = "${mq.config.exchange}",	// 		1.1 指定交换机名称
							type = ExchangeTypes.TOPIC			//		1.2 指定交换机类型
						), 	
				value = @Queue(									// 2. 指定队列名
							value = "${mq.config.queue.logs}",	//		2.1 指定队列名称
							autoDelete = "true"					//		2.2 是否是一个可删除的临时队列 
						),			
				key="*.log.*"		// 3. 指定路由键
		))
public class AllReceiver {
	
	/**接收消息的方法,采用消息队列的事件监听机制
	 * @param msg
	 */
	@RabbitHandler
	public void consumer(String msg) {
		System.out.println("log.all 消息接收:" + msg);
	}
	
}

2.2.4 测试类

package com.bjc;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.bjc.producer.ProductSender;
import com.bjc.producer.UserSender;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class TestMq {
	
	@Resource
	private UserSender userSender;
	@Resource
	private ProductSender productSender;
	
	@Test
	public void test() throws Exception {
		userSender.sendMsg("你好MQ!");
		productSender.sendMsg("你好MQ!");
	}
	
}

运行结果:

从中可以得出一个结论:

在topic类型的交换器中,支持模糊匹配,我们可以让同一类的消息走同一个队列。

2.3 Fanout交换器——广播模式

注意:该模式下,没有路由键

因为路由键的作用是区分消息发给哪个队列的,但是,fanout模式下的消息是广播模式,也就是给所有的队列都发送消息,所以,就不需要路由键了。

需求:

2.3.1 环境搭建

1)消费者配置文件

server.port=9090

## 配置项目名称(非必须配置)
spring.application.name=springcloud-mq
## 配置mq服务器地址
spring.rabbitmq.host=192.168.25.130
## 配置端口号  注意:rabbitMQ的web监控端口是15672
spring.rabbitmq.port=5672
## 配置用户名
spring.rabbitmq.username=oldlu
## 配置密码
spring.rabbitmq.password=123456

### 设置交换器的名称
mq.config.exchange=order.fanout

### 指定短信服务队列的名称
mq.config.queue.sms=order.sms

### 指定push服务队列的名称
mq.config.queue.push=order.push

2)生产者配置文件

server.port=9099

## 配置项目名称(非必须配置)
spring.application.name=springcloud-mq
## 配置mq服务器地址
spring.rabbitmq.host=192.168.25.130
## 配置端口号  注意:rabbitMQ的web监控端口是15672
spring.rabbitmq.port=5672
## 配置用户名
spring.rabbitmq.username=oldlu
## 配置密码
spring.rabbitmq.password=123456

### 设置交换器的名称
mq.config.exchange=order.fanout

2.3.2 消费者代码

1)sms服务

package com.bjc.consumer;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
@RabbitListener(bindings = @QueueBinding(
				exchange = @Exchange(							// 1. 指定交换机
							value = "${mq.config.exchange}",	// 		1.1 指定交换机名称
							type = ExchangeTypes.FANOUT			//		1.2 指定交换机类型
						), 	
				value = @Queue(									// 2. 指定队列名
							value = "${mq.config.queue.sms}",	//		2.1 指定队列名称
							autoDelete = "true"					//		2.2 是否是一个可删除的临时队列 
						)		
		))
public class SmsReceiver {
	
	/**接收消息的方法,采用消息队列的事件监听机制
	 * @param msg
	 */
	@RabbitHandler
	public void consumer(String msg) {
		System.out.println("sms消息接收:" + msg);
	}
	
}

2)push服务

package com.bjc.consumer;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
@RabbitListener(bindings = @QueueBinding(
				exchange = @Exchange(							// 1. 指定交换机
							value = "${mq.config.exchange}",	// 		1.1 指定交换机名称
							type = ExchangeTypes.FANOUT			//		1.2 指定交换机类型
						), 	
				value = @Queue(									// 2. 指定队列名
							value = "${mq.config.queue.push}",	//		2.1 指定队列名称
							autoDelete = "true"					//		2.2 是否是一个可删除的临时队列 
						)		
		))
public class PushReceiver {
	
	/**接收消息的方法,采用消息队列的事件监听机制
	 * @param msg
	 */
	@RabbitHandler
	public void consumer(String msg) {
		System.out.println("push消息接收:" + msg);
	}
	
}

2.3.3 生产者

package com.bjc.producer;

import javax.annotation.Resource;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * 消息发送者
 *
 */
@Component
public class Sender {
	
	@Resource
	private AmqpTemplate rabbitTemplate;
	
	@Value("${mq.config.exchange}")
	private String exchange;
	
	public void sendMsg(String msg) {
		// 向消息队列发送消息
		// 参数一:交换器名称
		// 参数二:路由键  Fanout广播模式不需要路由键,所以只需要给空串即可。
		// 参数三:消息
		rabbitTemplate.convertAndSend(exchange,"",msg);
	}
	
}

总结:三种类型的交换器,direct为点对点、Topic为发布订阅式,fanout为广播

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值