序言
RabbitMQ
是一个遵循AMQP协议,由面向高并发的erlanng
语言开发而成,用在实时的对可靠性要求比较高的消息传递上,支持多种语言客户端。支持 延迟队列(这是一个非常有用的功能)。
基础概念
Broker:
简单来说就是消息队列服务器实体
Exchange:
消息交换机,它指定消息按什么规则,路由到哪个队列
Queue:
消息队列载体,每个消息都会被投入到一个或多个队列
Binding:
绑定,它的作用就是把exchange
和queue
按照路由规则绑定起来
Routing Key:
路由关键字,exchange
根据这个关键字进行消息投递
vhost:
虚拟主机,一个broker
里可以开设多个vhost
,用作不同用户的权限分离
producer:
消息生产者,就是投递消息的程序
consumer:
消息消费者,就是接受消息的程序
channel:
消息通道,在客户端的每个连接里,可建立多个channel
,每个channel
代表一个会话任务
Exchange
在rabbitmq中可以添加 fanout
、direct
、topic
、headers
这四种,其中呢headers
这种几乎不用就不说这种了。其余三种就长话短说,做一个入门。
fanout
fanout可以绑定多个队列,只要想fanout中的队列一个发送消息,则这个交换机里的所有队列全部都会收到。
direct
可以实现JMS中点对点的功能,其发送消息的时候,是按照Routing Key
来匹配的,如果绑定的队列中全等,则发送。
topic
topic顾名思义就是发布订阅那种模式了,他和fanout的唯一的不同就是,可以按照 通配符匹配Routing Key
给部分队列发消息,#代表匹配多个单词,*表示匹配一个单词。
使用
加入如下依赖即可
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
然后在yml中配置
spring:
rabbitmq:
host: 127.0.0.1
listener:
simple:
acknowledge-mode: manual
port: 5672
username: fulin
password: wangle
virtual-host: /
先说如何创建队列、交换机、和绑定关系。这里有两种方式
使用AmqpAdmin
@Configuration
public class RabbitAdminConfig {
@Autowired
private AmqpAdmin amqpAdmin;
@Bean
public void createRabbitInit() {
//创建交换器
amqpAdmin.declareExchange(new DirectExchange("amqpadmin.exchange"));
//创建队列(如果存在同名,则不创建)
amqpAdmin.declareQueue(new Queue("amqpadmin.queue", true));
//创建绑定规则 new Binding(目的地,目的地类型,交换器名字,路由件,参数头)
amqpAdmin.declareBinding(new Binding("amqpadmin.queue", Binding.DestinationType.QUEUE, "amqpadmin.exchange", "amqp.haha", null));
//删除队列
//amqpAdmin.deleteQueue("amqpadmin.queue");
}
}
差不多就是这样然侯还可以使用bean配置
@Slf4j
@Configuration
public class RabbitConfig {
public static final String FULIN_QUEUE = "dev.fulin.queue";
public static final String FULIN_EXCHANGE = "dev.fulin.exchange";
public static final String FULIN_ROUTING_KEY = "all";
//创建交换器
@Bean
public TopicExchange topicExchange() {
return new TopicExchange(FULIN_EXCHANGE);
}
//创建队列
@Bean
public Queue queue() {
return new Queue(FULIN_QUEUE, true);
}
//绑定注解和交换器
@Bean
public Binding registerBookBinding() {
return BindingBuilder.bind(queue()).to(topicExchange()).with(FULIN_ROUTING_KEY);
}
}
好了东西都会创建了那到底怎么使用呢?然后呢SpringBoot封装了一个针对于amqp协议的一个模板类rabbitTemplate
可以方便进行操作。
@RestController
public class TestController {
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/queue")
public void defaultMessage(@RequestParam("value") String value) {
this.rabbitTemplate.convertAndSend("dev.fulin.exchange", "dev.fulin.queue", "my name is wangfulin");
}
}
然后创建一个监听类,同样呢也是加注解@RabbitListener(queues = "dev.fulin.queue")
里面写入队列名就行了。 默认情况下 spring-boot-data-amqp
是自动ACK
机制,就意味着 MQ 会在消息消费完毕后自动帮我们去ACK,这样依赖就存在这样一个问题:如果报错了,消息不会丢失,会无限循环消费,虽然可以配置消费的次数。。。推荐手动ACK然后将消费错误的消息转移到其它的消息队列中,做补偿处理。 由于我们需要手动控制ACK
,因此下面监听完消息后需要调用basicAck
通知rabbitmq
消息已被正确消费,可以将远程队列中的消息删除 。
@Component
@Slf4j
public class QueueHandler {
@RabbitListener(queues = "dev.fulin.queue")
public void devFulinQueue(String value, Message message, Channel channel) {
// TODO 如果手动ACK,消息会被监听消费,
// 但是消息在队列中依旧存在,如果
// 未配置 acknowledge-mode 默认是会在消费完毕后自动ACK掉
final long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
log.info(" dev.fulin.queue 消息监听是{}", value);
// TODO 通知 MQ 消息已被成功消费,可以ACK了
channel.basicAck(deliveryTag, false);
} catch (IOException e) {
try {
channel.basicRecover();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
至于fanout
、direct
、topic
大家多实现巩固,这些都是不难的。
延时队列
所谓延时消息
就是指当消息被发送以后,并不想让消费者立即拿到消息,而是等待指定时间后,消费者才拿到这个消息进行消费。 RabbitMQ队列
本身是没有直接实现支持延迟队列的功能,但可以通过它的 Time-To-Live Extensions
与 Dead Letter Exchange
的特性模拟出延迟队列的功能。 简称DLX
、DLK
。
Time-To-Live Extensions
RabbitMQ
支持为队列或者消息设置TTL(time to live 存活时间)。TTL表明了一条消息可在队列中存活的最大时间。当某条消息被设置了TTL或者当某条消息进入了设置了TTL的队列时,这条消息会在TTL时间后**死亡
成为Dead Letter
**。如果既配置了消息的TTL,又配置了队列的TTL,那么较小的那个值会被取用。
Dead Letter Exchange
死信交换机,上文中提到设置了 TTL 的消息或队列最终会成为Dead Letter
。如果为队列设置了Dead Letter Exchange(DLX)
,那么这些Dead Letter
就会被重新发送到Dead Letter Exchange
中,然后通过Dead Letter Exchange
路由到其他队列,即可实现延迟队列的功能。
来一个队列创建
@Slf4j
@Configuration
public class RabbitConfig {
//-- 延迟队列
//延迟队列 TTL 名称
private static final String FULIN_DELAY_QUEUE = "dev.fulin.delay.queue";
// DLX,dead letter发送到的 exchange
public static final String FULIN_DELAY_EXCHANGE = "dev.fulin.delay.exchange";
/**
* routing key 名称
*/
public static final String FULIN_DELAY_ROUTING_KEY = "";
//-- 普通队列
public static final String FULIN_QUEUE = "dev.fulin.queue";
public static final String FULIN_EXCHANGE = "dev.fulin.exchange";
public static final String FULIN_ROUTING_KEY = "all";
//-- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx关联
@Bean
public Queue delayProcessQueue() {
Map<String, Object> params = new HashMap<>();
// x-dead-letter-exchange 声明了队列里的死信转发到的DLX名称,
params.put("x-dead-letter-exchange", FULIN_EXCHANGE);
// x-dead-letter-routing-key 声明了这些死信在转发时携带的 routing-key 名称。
params.put("x-dead-letter-routing-key", FULIN_ROUTING_KEY);
return new Queue(FULIN_DELAY_QUEUE, true, false, false, params);
}
@Bean
public DirectExchange delayExchange() {
return new DirectExchange(FULIN_DELAY_EXCHANGE);
}
@Bean
public Binding dlxBinding() {
return BindingBuilder.bind(delayProcessQueue()).to(delayExchange()).with(FULIN_DELAY_ROUTING_KEY); }
//-- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx关联
@Bean
public TopicExchange registerBookTopicExchange() {
return new TopicExchange(FULIN_EXCHANGE);
}
@Bean
public Queue registerBookQueue() {
return new Queue(FULIN_QUEUE, true);
}
@Bean
public Binding registerBookBinding() {
return BindingBuilder.bind(registerBookQueue()).to(registerBookTopicExchange()).with(FULIN_ROUTING_KEY);
}
}
然后在controller中正常调用即可
@RestController
public class TestController {
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/time")
public void timeMessage(@RequestParam("value") String value) {
this.rabbitTemplate.convertAndSend(RabbitConfig.FULIN_DELAY_EXCHANGE, RabbitConfig.FULIN_DELAY_ROUTING_KEY, "my name is wangfulin", message -> {
message.getMessageProperties().setExpiration(10 * 1000 + "");
return message;
});
}
}
然后在写个监听类看看是不是几秒后发过去了
@RabbitListener(queues = "dev.fulin.queue")
public void devFulinQueue(String value, Message message, Channel channel) {
final long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
log.info(" test.fulin 消息监听是{}", value);
channel.basicAck(deliveryTag, false);
} catch (IOException e) {
try {
channel.basicRecover();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
本博文是基于springboot2.x 如果有什么不对的请在下方留言。