spring boot 整合rabbit MQ
rabbit MQ
rabbit MQ 是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ服务器是用[Erlang]语言编写的,而集群和故障转移是构建在[开放电信平台]框架上的。所有主要的编程语言均有与代理接口通讯的客户端[库]
来自百度百科 rabbit MQ
- rabbit 和其他消息中间件不同的是,rabbit有一个exchange路由的概念
- exchange 交换机,就相当于一个路由一样,把我们的消息分发到不通的queue ,rabbit支持三种exchange
- direct 顾名思义,就是直连,相当于点对点的模式
- topic 广播订阅模式,但是它可以根据我们的路由key的不通,转发到不通的人queue
- fanout 广播订阅模式 ,这种模式下,路由key会失效,它会将消息发送到所有绑定的queue
与spring boot 的整合
直接参照spring.io的官方文档,讲的很清楚简单,贴一下链接
-
引入相关依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
-
启动类
@SpringBootApplication public class SpringBootRabbitProduceApplication { public static void main(String[] args) { SpringApplication.run(SpringBootRabbitProduceApplication.class, args); }
-
配置服务器信息
spring: rabbitmq: host: 127.0.0.1 port: 5672 username: guest password: guest
-
rabbit 配置类,这里具体解析下
-
Queue queue() 注册一个bean,类型是一个
queue
对象, -
TopicExchange topicExchange() ,注册一个
topic exchange
-
Binding bindingQueueA() 对queue和exchange进行绑定
BindingBuilder.bind(queueA()).to(topicExchange()).with("topic.a")
表示绑定queueA 这个队列到topicExchange 上,路由key是topic.a
,表示只有匹配topic.a的消息转发到 queueA这个队列上BindingBuilder.bind(queueB()).to(topicExchange()).with("topic.#")
表示绑定queueB到topicExchange 上,路由key是topic.#"
,.#
表示topic开头的,无论后面是什么都会转发到绑定的queue上面 -
FanoutExchange 上面介绍过,绑定在FanoutExchange上面的,会忽略路由key,所有的详细都会发送到其绑定的所有queue上面
@Configuration public class RabbitMQConfig { @Bean public Queue queue(){ return new Queue("testQueue"); } //------------------------测试topic-exchange-------------------------------------------------------------// @Bean public Queue queueA(){ return new Queue("queueA"); } @Bean public Queue queueB(){ return new Queue("queueB"); } @Bean public TopicExchange topicExchange(){ return new TopicExchange("topic"); } @Bean public Binding bindingQueueA(){ //绑定 topic.a 才会路由到queueA return BindingBuilder.bind(queueA()).to(topicExchange()).with("topic.a"); } @Bean public Binding bindingQueueB(){ //绑定 topic.# 只要是topic.开头的都路由到queueB return BindingBuilder.bind(queueB()).to(topicExchange()).with("topic.#"); } //------------------------测试 fanout exchange--------------------------------------------------------------// @Bean public Queue queueC(){ return new Queue("queueC"); } @Bean public Queue queueD(){ return new Queue("queueD"); } @Bean public FanoutExchange fanoutExchange(){ return new FanoutExchange("fanout"); } @Bean public Binding bindingQueueC(){ return BindingBuilder.bind(queueC()).to(fanoutExchange()); } @Bean public Binding bindingQueueD(){ return BindingBuilder.bind(queueD()).to(fanoutExchange()); } }
-
-
消费
直接在类或者方法上加上
@RabbitListener(queues = "queueName")
注解,queues表示要消费的队列/** * 消费 topic exchange queueA 消息 * @param message */ // @RabbitListener(queues = "queueA") public void consumerQueueA(Object message){ try { log.info("----------------------------收到queueA消息:{}",message); }catch (Exception e){ log.info("--",e); } } /** * 消费 topic exchange queueB 消息 * @param message */ // @RabbitListener(queues = "queueB") public void consumerQueueB(Object message){ try { log.info("----------------------------收到queueB消息:{}",message); }catch (Exception e){ log.info("--",e); } } /** * 消费 topic exchange queueB 消息 * @param message */ @RabbitListener(queues = "queueC") public void consumerQueueC(Object message){ try { log.info("----------------------------queueC收到消息:{}",message); }catch (Exception e){ log.info("--",e); } } /** * 消费 topic exchange queueB 消息 * @param message */ @RabbitListener(queues = "queueD") public void consumerQueueD(Object message){ try { log.info("----------------------------queueD收到消息:{}",message); }catch (Exception e){ log.info("--",e); } }
-
发送消息
-
AmqpTemplate 直接注入AmqpTemplate 就可以发送消息,
-
amqpTemplate.convertAndSend(“topic”,“topic.a”,"–topic.a-- -----测试消息");
第一个参数
topic
表示要发送到那个exchange第二个参数
"topic.a
表示消息的路由key,第三个参数表示消息的内容,可以是String,也可以是object,注意需要实现序列化接口
@Autowired private AmqpTemplate amqpTemplate; @Test public void testSendTopic(){ //这里,第一条消息会发送到 queueA和queueB ,而下面两条只会发送到queueB amqpTemplate.convertAndSend("topic","topic.a","--topic.a-- -----测试消息"); amqpTemplate.convertAndSend("topic","topic.b","--topic.b-- -----测试消息"); amqpTemplate.convertAndSend("topic","topic.c","--topic.c-- -----测试消息"); } @Test public void testSendFanout(){ //这里,消息会发送到所有绑定的queue amqpTemplate.convertAndSend("fanout","fanout.a","--topic.a-- -----测试消息"); amqpTemplate.convertAndSend("fanout","fanout.b","--topic.b-- -----测试消息"); amqpTemplate.convertAndSend("fanout","fanout.c","--topic.c-- -----测试消息"); }
-
消息消费的手动ack
ack就是消息消费完成之后,消费端给服务器一个回复,告诉服务器这条消息消费完成,服务器就会从对应的queue上面删除消费完成的消息,如果客户端没有完成ack,服务端会在这条消息加一个标示unackd,当unackd过多解释就行影响我们的消息的消费
消息的消费默认是客户端自动ack的,某些业务场景可能需要我们手动确认消息的消费,比如如果我们的消息消费失败之后,客户端发送一个nack给服务器,服务器就会吧这条消息重新回到ready状态,之后就可以重新去消费这条消息
配置消息手动ack
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
listener:
simple:
acknowledge-mode: manual
手动ack消息
channel.basicAck(tag,false)
手动的确认消息的消费成功,注意第二个个参数如果为true就会吧前面没有ack的消息一起ack了channel.basicNack(tag,false,true)
消息消费失败,手手动发送消息消费失败- 出现异常,或者消息消费失败 Nack 之后消息就会重新回到ready状态,可以重新进行消费
- 第三个参数设为false,rabbit会在nack到达某个阈值时把这条消息放到死信队列中Dead Letter,
- 死信队列也是一个队列,也可以通过客户端来消息,来对异常的消息做特殊处理
/**
* 消费 topic exchange queueB 消息
* channel client注册的通道
* tag amqp_deliveryTag 消息的tag
* @param message
*/
@RabbitListener(queues = "queueD")
public void consumerQueueD(Object message, Channel channel,@Header(AmqpHeaders.DELIVERY_TAG) long tag){
try {
log.info("----------------------------queueD收到消息:{}",message);
//手动确认消息的消费,注意第二个个参数如果为true就会吧前面没有ack的消息一起ack了
channel.basicAck(tag,false);
}catch (Exception e){
log.info("--",e);
//出现异常,或者消息消费失败 Nack 之后消息就会重新回到ready状态,可以重新进行消费
//第三个参数设为false,rabbit会在nack到达某个阈值时把这条消息放到死信队列中Dead Letter,
//死信队列也是一个队列,也可以通过客户端来消息
try {
channel.basicNack(tag,false,true);
} catch (IOException e1) {
log.info("--",e);
}
}
}