RabbitMQ 即消息队列,实现应用程序之间的异步与解耦。同时起到消息的缓冲与分发的作用。
解耦:
中间件的标准用法是:生产者生产消息到消息队列,消费者从消息队列拿取消息并处理。生产者不用关心谁来消费消息,消费者不用关系谁来生产消息。从而达到解耦目的。小伙伴对这一概念理解没有?
目的:
RabbitMQ是为了实现系统之间的双向解耦而实现的。当生产者大量产生数据时,而消费者无法快速消费消息,这是需要一个中间层,保存这些数据。
RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。
RabbitMQ 有如下4个概念:
- 虚拟主机
- 交换器
- 绑定器
- 队列
一个虚拟主机包含有:交换器,绑定器和队列。
一般生产者生产出消息,不是直接发送到队列的,而是,先发送到交换器,然后通过交换器路由到指定队列。
交换器
用来转发消息,不做存储,如果没有队列绑定到交换器的话,他会直接丢弃掉生产者发送过来的消息。
这里有一个重要概念就是:路由键。消息到交换器的时候,交换器会转发到相应的队列,究竟要转发到那个队列,就有路由键决定。
绑定:也就是将队列与交换器进行绑定。
交换器有4种类型:
direct、topic、fanout 和 headers。 - Direct:direct 类型的行为是”先匹配, 再投送”. 即在绑定时设定一个 routing_key, 消息的routing_key
匹配时, 才会被交换器投送到绑定的队列中去. - Topic:按规则转发消息(最灵活)
- Headers:设置header attribute参数类型的交换机
- Fanout:转发消息到所有绑定队列
Direct Exchange是RabbitMQ默认的交换机模式,也是最简单的模式
x到Q1只有一个绑定器,名字为orange。x到Q2有俩个绑定器,叫black,green。那么消息发送到了类型为direct交换器时,交换器到底要将消息发送到那个C1队列还是C2队列呢,这里有,消息中的路由键决定。当路由键和绑定器(binding key)对应上时,就发送到那个队列去。
Topic Exchange
Topic Exchange 转发消息主要是根据通配符。 在这种交换机下,队列和交换机的绑定会定义一种路由模式,那么,通配符就要在这种路由模式和路由键之间匹配后交换机才能转发消息。
在这种交换机模式下:
路由键必须是一串字符,用句号(.) 隔开,比如说 agreements.us,或者 agreements.eu.stockholm 等。
路由模式必须包含一个 星号(),主要用于匹配路由键指定位置的一个单词,比如说,一个路由模式是这样子:agreements…b.,那么就只能匹配路由键是这样子的:第一个单词是 agreements,第四个单词是 b。 井号(#)就表示相当于一个或者多个单词,例如一个匹配模式是agreements.eu.berlin.#,那么,以agreements.eu.berlin开头的路由键都是可以的。
具体代码发送的时候还是一样,第一个参数表示交换机,第二个参数表示routing key,第三个参数即消息。如下:
rabbitTemplate.convertAndSend("testTopicExchange","key1.a.c.key2", " this is RabbitMQ!");
Headers Exchange
headers 也是根据规则匹配, 相较于 direct 和 topic 固定地使用 routing_key , headers 则是一个自定义匹配规则的类型.
在队列与交换器绑定时, 会设定一组键值对规则, 消息中也包括一组键值对( headers 属性), 当这些键值对有一对, 或全部匹配时, 消息被投送到对应队列.
Fanout Exchange
Fanout Exchange 消息广播的模式,不管路由键或者是路由模式,会把消息发给绑定给它的全部队列,如果配置了routing_key会被忽略。
springboot集成RabbitMQ
1.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2.配置相关信息
spring.application.name=spirng-boot-rabbitmq
spring.rabbitmq.host=localhost
#端口默认是5672
spring.rabbitmq.port=5672
#用户名和密码默认是guest
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
3.定义一个队列配置类
@Configuration
public class RabbitConfig {
public static final String ES_QUEUE="es_queue"; //给通道或者队列一个名称
public static final String ES_CHANGE="es_change"; //给交换器一个名称
public static final String ES_BINDING_KEY="es_binding_key";//给绑定器一个名称
@Bean
public Queue Queue() {
return new Queue(ES_QUEUE);
}
/**
* RabbitMQ常用的交换器类型有direct、topic、fanout、headers四种。
* @return
*/
@Bean
DirectExchange directExchange(){
return new DirectExchange(ES_CHANGE);
}
@Bean
Binding binding(Queue queue,DirectExchange directExchange){
//将通道与交换机进行绑定,给绑定器一个es_binding_key名称
return BindingBuilder.bind(queue).to(directExchange).with(ES_BINDING_KEY);
}
}
4.发送消息
AmqpTemplate是springboot 提供的默认实现
public class MySender{
@Autowired
private AmqpTemplate rabbitTemplate;
public void send(){
String context="nihao"+new Date();
//这里的 RabbitMqConfig.ES_BINDING_KEY就是我设置的路由键,与绑定器的名称相同,将会发送到与之绑定的队列中去
this.rabbitTemplate.convertAndSend(RabbitMqConfig.ES_CHANGE,RabbitMqConfig.ES_BINDING_KEY,context);
}
}
5.接受消息
@Component
@RabbitListener(queues = "es_queue")
public class HelloReceiver {
@RabbitHandler
public void process(String aa) {
System.out.println("Receiver : " + aa);
}
}
高级的使用:对象的接受
springboot以及完美的支持对象的发送和接收,不需要格外的配置。
//发送者
public void send(User user) {
System.out.println("Sender object: " + user.toString());
this.rabbitTemplate.convertAndSend("object", user);
}
//接收者
@RabbitListener(queues = RabbitMqConfig.object)
public void process(User user) {
System.out.println("Receiver object : " + user);
}
Fanout Exchange
Fanout 就是我们熟悉的广播模式或者订阅模式,给Fanout交换机发送消息,绑定了这个交换机的所有队列都收到这个消息。
@Configuration
public class FanoutRabbitConfig {
@Bean
public Queue AMessage() {
return new Queue("fanout.A");
}
@Bean
public Queue BMessage() {
return new Queue("fanout.B");
}
@Bean
public Queue CMessage() {
return new Queue("fanout.C");
}
@Bean
FanoutExchange fanoutExchange() {
return new FanoutExchange("fanoutExchange");
}
@Bean
Binding bindingExchangeA(Queue AMessage,FanoutExchange fanoutExchange) {
return BindingBuilder.bind(AMessage).to(fanoutExchange);
}
@Bean
Binding bindingExchangeB(Queue BMessage, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(BMessage).to(fanoutExchange);
}
@Bean
Binding bindingExchangeC(Queue CMessage, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(CMessage).to(fanoutExchange);
}
}
这里使用了A、B、C三个队列绑定到Fanout交换机上面,发送端的routing_key写任何字符都会被忽略:
public void send() {
String context = "hi, fanout msg ";
System.out.println("Sender : " + context);
this.rabbitTemplate.convertAndSend("fanoutExchange","", context);
}