1.简单介绍
MQ
全称(Message Queue)又名消息队列,是一种异步通讯的中间件。可以将它理解成邮局,发送者将消息传递到邮局,然后由邮局帮我们发送给具体的消息接收者(消费者),具体发送过程与时间我们无需关心,它也不会干扰我进行其它事情。常见的MQ
有kafka
、activemq
、zeromq
、rabbitmq
等等。
RabbitMQ
是一个遵循AMQP
协议,由面向高并发的erlanng
语言开发而成,用在实时的对可靠性要求比较高的消息传递上,支持多种语言客户端。
基础概念
Broker
:简单来说就是消息队列服务器实体Exchange
:消息交换机,它指定消息按什么规则,路由到哪个队列Queue
:消息队列载体,每个消息都会被投入到一个或多个队列Binding
:绑定,它的作用就是把exchange
和queue
按照路由规则绑定起来Routing Key
:路由关键字,exchange
根据这个关键字进行消息投递vhost
:虚拟主机,一个broker
里可以开设多个vhost
,用作不同用户的权限分离producer
:消息生产者,就是投递消息的程序consumer
:消息消费者,就是接受消息的程序channel
:消息通道,在客户端的每个连接里,可建立多个channel
,每个channel
代表一个会话任务
常见引用场景
- 邮箱发送:用户注册后投递消息到
rabbitmq
中,由消息的消费方异步的发送邮件,提升系统响应速度 - 流量削峰:一般在秒杀活动中应用广泛,秒杀会因为流量过大,导致应用挂掉,为了解决这个问题,一般在应用前端加入消息队列。用于控制活动人数,将超过此一定阀值的订单直接丢弃。缓解短时间的高流量压垮应用。
- 订单超时:利用
rabbitmq
的延迟队列,可以很简单的实现订单超时的功能,比如用户在下单后30分钟未支付取消订单
2.准备RabbitMQ环境
由于RabbitMQ
安装麻烦,这里测试以docker环境为例。首先Linux
安装docker,不会的可以参考docker安装/ docker常用命令
然后下载并启动RabbitMQ
docker run -d --hostname my-rabbit --name pikachues-rabbit -p 5672:5672 -p15672:15672 rabbitmq:3-management
3.测试
RabbitMQ
有四种消息交换机。分别为direct(默认),fanout, topic, 和headers,不同类型的交换机转发消息的策略有所区别。下面用springboot
测试四种消息交换机。
0.准备测试环境
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
配置文件
spring.rabbitmq.host=rabbitmq安装的ip地址
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.port=5672
1.direct
直接交换器,工作方式类似于单播,Exchange
会将消息发送完全匹配ROUTING_KEY
的Queue。
direct配置
@Configuration
public class RabbitDirectConfig {
public final static String DIRECTNAME = "pikachues-direct";
@Bean
Queue queue() {
return new Queue("hello.pikachues");
}
@Bean
DirectExchange directExchange(){
return new DirectExchange(DIRECTNAME,true,false);
}
@Bean
Binding binding(){
return BindingBuilder.bind(queue()).to(directExchange()).with("direct");
}
}
消息接收
@Component
public class DirectReceiver {
@RabbitListener(queues = "hello.pikachues")
public void handler1(String msg){
System.out.println(">>>handler"+msg);
}
}
测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class RabbitmqDemoApplicationTests {
@Autowired
RabbitTemplate rabbitTemplate;
@Test
public void direct() {
rabbitTemplate.convertAndSend("hello.pikachues","hello pikachues");
}
}
2.fanout
广播是式交换器,不管消息的ROUTING_KEY
设置为什么,Exchange
都会将消息转发给所有绑定的Queue
。
fanout配置
@Configuration
public class RabbitFanoutConfig {
public static final String FANOUTNAME = "pikachues-fanout";
@Bean
Queue queueOne(){
return new Queue("queue-one");
}
@Bean
Queue queueTwo(){
return new Queue("queue-two");
}
@Bean
FanoutExchange fanoutExchange(){
return new FanoutExchange(FANOUTNAME,true,false);
}
@Bean
Binding bindingOne(){
return BindingBuilder.bind(queueOne()).to(fanoutExchange());
}
@Bean
Binding bindingTwo(){
return BindingBuilder.bind(queueTwo()).to(fanoutExchange());
}
}
消息接收
@Component
public class FanoutReceiver {
@RabbitListener(queues = "queue-one")
public void handler1(String msg){
System.out.println("FanoutReceiver:handler1"+msg);
}
@RabbitListener(queues = "queue-two")
public void handler2(String msg){
System.out.println("FanoutReceiver:handler2"+msg);
}
}
测试
@Test
public void testFanout(){
rabbitTemplate.convertAndSend(RabbitFanoutConfig.FANOUTNAME,null,"hello Fanout");
}
3.topic
主题交换器,工作方式类似于组播,Exchange
会将消息转发和ROUTING_KEY
匹配模式相同的所有队列,比如,ROUTING_KEY
为user.stock
的Message
会转发给绑定匹配模式为 * .stock,user.stock, * . * 和#.user.stock.#的队列。( * 表是匹配一个任意词组,#表示匹配0个或多个词组)
topic配置
@Configuration
public class RabbitTopicConfig {
public static final String TOPICNAME = "pikachues-topic";
@Bean
TopicExchange topicExchange(){
return new TopicExchange(TOPICNAME,true,false);
}
@Bean
Queue xiaomi(){
return new Queue("xiaomi");
}
@Bean
Queue huaiwei(){
return new Queue("huawei");
}
@Bean
Queue phone(){
return new Queue("phone");
}
@Bean
Binding xiaomiBinding(){
return BindingBuilder.bind(xiaomi()).to(topicExchange()).with("xiaomi.#");
}
@Bean
Binding huaiweiBinding(){
return BindingBuilder.bind(huaiwei()).to(topicExchange()).with("huawei.#");
}
@Bean
Binding phoneBinding(){
return BindingBuilder.bind(phone()).to(topicExchange()).with("#.phone.#");
}
}
消息接收
@Component
public class TopicReceiver {
@RabbitListener(queues = "xiaomi")
public void handler1(String msg) {
System.out.println("TopicReceiver:handler1:" + msg);
}
@RabbitListener(queues = "huawei")
public void handler2(String msg) {
System.out.println("TopicReceiver:handler2:" + msg);
}
@RabbitListener(queues = "phone")
public void handler3(String msg) {
System.out.println("TopicReceiver:handler3:" + msg);
}
}
测试
@Test
public void testTopic(){
rabbitTemplate.convertAndSend(RabbitTopicConfig.TOPICNAME,"xiaomi.news","小米新闻");
rabbitTemplate.convertAndSend(RabbitTopicConfig.TOPICNAME,"vivo.phone","小米手机");
rabbitTemplate.convertAndSend(RabbitTopicConfig.TOPICNAME,"huawei.phone","华为手机");
}
4.headers
消息体的header匹配(ignore)
headers配置
@Configuration
public class RabbitHeaderConfig {
public static final String HEADERNAME = "pikachues-header";
@Bean
HeadersExchange headersExchange(){
return new HeadersExchange(HEADERNAME, true,false);
}
@Bean
Queue queueName(){
return new Queue("name-queue");
}
@Bean
Queue queueAge(){
return new Queue("age-queue");
}
@Bean
Binding bindingName(){
Map<String,Object> map = new HashMap<>();
map.put("name","pikachues");
return BindingBuilder.bind(queueName()).to(headersExchange()).whereAny(map).match();
}
@Bean
Binding bindingAge(){
return BindingBuilder.bind(queueAge()).to(headersExchange()).where("age").exists();
}
}
消息接收
@Component
public class HeaderReceiver {
@RabbitListener(queues = "name-queue")
public void handler1(byte[] msg) {
System.out.println("HeaderReceiver:handler1:" + new String(msg, 0, msg.length));
}
@RabbitListener(queues = "age-queue")
public void handler2(byte[] msg) {
System.out.println("HeaderReceiver:handler2:" + new String(msg, 0, msg.length));
}
}
测试
@Test
public void testHeaders(){
Message nameMsg = MessageBuilder.withBody("hello pikachues".getBytes()).setHeader("name","pikachues").build();
Message ageMsg = MessageBuilder.withBody("hello 99".getBytes()).setHeader("age","99").build();
rabbitTemplate.convertAndSend(RabbitHeaderConfig.HEADERNAME,null,nameMsg);
rabbitTemplate.convertAndSend(RabbitHeaderConfig.HEADERNAME,null,ageMsg);
}
4.其他
如果想了解RabbitMQ
的基本原理请看这篇文章或者springboot官网介绍。