学习Spring Boot:(二十六)使用 RabbitMQ 消息队列
前言
前面学习了 RabbitMQ 基础,现在主要记录下学习 Spring Boot 整合 RabbitMQ ,调用它的 API ,以及中间使用的相关功能的记录。
相关的可以去我的博客/RabbitMQ
正文
我这里测试都是使用的是 topic 交换器,Spring Boot 2.0.0, jdk 1.8
配置
Spring Boot 版本 2.0.0
在 pom.xml 文件中引入 AMQP 的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
在系统配置文件中加入连接属性
spring:
application:
name: RabbitMQ-Demo
rabbitmq:
host: k.wuwii.com
port: 5672
username: kronchan
password: 123456
#virtual-host: test
publisher-confirms: true # 开启确认消息是否到达交换器,需要设置 true
publisher-returns: true # 开启确认消息是否到达队列,需要设置 true
基本的使用
消费者
新增一个消费者类:
@Log
public class MessageReceiver implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
try {
byte[] body = message.getBody();
log.info(">>>>>>> receive: " + new String(body));
} finally {
// 确认成功消费,否则消息会转发给其他的消费者,或者进行重试
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
}
}
配置类
新增 RabbitMQ 的配置类,主要是对消费者的队列,交换器,路由键的一些设置:
@Configuration
public class RabbitMQConfig {
public final static String QUEUE_NAME = "springboot.demo.test1";
public final static String ROUTING_KEY = "route-key";
public final static String EXCHANGES_NAME = "demo-exchanges";
@Bean
public Queue queue() {
// 是否持久化
boolean durable = true;
// 仅创建者可以使用的私有队列,断开后自动删除
boolean exclusive = false;
// 当所有消费客户端连接断开后,是否自动删除队列
boolean autoDelete = false;
return new Queue(QUEUE_NAME, durable, exclusive, autoDelete);
}
/**
* 设置交换器,这里我使用的是 topic exchange
*/
@Bean
public TopicExchange exchange() {
// 是否持久化
boolean durable = true;
// 当所有消费客户端连接断开后,是否自动删除队列
boolean autoDelete = false;
return new TopicExchange(EXCHANGES_NAME, durable, autoDelete);
}
/**
* 绑定路由
*/
@Bean
public Binding binding(Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY);
}
@Bean
public SimpleMessageListenerContainer container(ConnectionFactory connectionFactory) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames(QUEUE_NAME);
container.setMessageListener(receiver());
//container.setMaxConcurrentConsumers(1);
//container.setConcurrentConsumers(1); 默认为1
//container.setExposeListenerChannel(true);
container.setAcknowledgeMode(AcknowledgeMode.MANUAL); // 设置为手动,默认为 AUTO,如果设置了手动应答 basicack,就要设置manual
return container;
}
@Bean
public MessageReceiver receiver() {
return new MessageReceiver();
}
}
生产者
@Component
public class MessageSender {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* logger
*/
private static final Logger log = LoggerFactory.getLogger(MessageSender.class);
public void send() {
// public void convertAndSend(String exchange, String routingKey, final Object object, CorrelationData correlationData)
// exchange: 交换机名称
// routingKey: 路由关键字
// object: 发送的消息内容
// correlationData:消息ID
CorrelationData correlationId = new CorrelationData(UUID.randomUUID().toString());
// ConfirmListener是当消息无法发送到Exchange被触发,此时Ack为False,这时cause包含发送失败的原因,例如exchange不存在时
// 需要在系统配置文件中设置 publisher-confirms: true
if (!rabbitTemplate.isConfirmListener()) {
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
log.info(">>>>>>> 消息id:{} 发送成功", correlationData.getId());
} else {
log.info(">>>>>>> 消息id:{} 发送失败", correlationData.getId());
}
});
}
// ReturnCallback 是在交换器无法将路由键路由到任何一个队列中,会触发这个方法。
// 需要在系统配置文件中设置 publisher-returns: true
rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
log.info("消息id:{} 发送失败", message.getMessageProperties().getCorrelationId());
});
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGES_NAME, RabbitMQConfig.ROUTING_KEY, ">>>>> Hello World", correlationId);
log.info("Already sent message.");
}
}
测试发送消息
先启动系统启动类,消费者开始订阅,启动测试类发送消息。
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootRabbitmqApplicationTests {
@Autowired
private MessageSender sender;
@Test
public void testReceiver() {
sender.send();
}
}
可以在消费者接收到信息,并且发送端将打出日志 成功发送消息的记录,也可以测试下 Publisher Confirms and Returns机制 主要是测试 ConfirmCallback 和 ReturnCallback 这两个方法。
* ConfirmCallback ,确认消息是否到达交换器,例如我们发送一个消息到一个你没有创建过的 交换器上面去,看看情况,
* ReturnCallback,确认消息是否到达队列,我们可以这样测试,定义一个路由键,不会被任何队列订阅到,最后查看结果就可以了。
使用注解的方式
引入依赖和连接参数
跟文章第一步的配置一样的。
消费者
@Component
@Log
public class MessageReceiver {
/**
* 无返回消息的
*
* @param message
*/
@RabbitListener(bindings = @QueueBinding(value = @Queue(value = Constant.QUEUE_NAME, durable = "true", exclusive = "false", autoDelete = "false"),
exchange = @Exchange(value = Constant.EXCHANGES_NAME, ignoreDeclarationExceptions = "true", type = ExchangeTypes.TOPIC, autoDelete = "false"),
key = Constant.ROUTING_KEY))
public void receive(byte[] message) {
log.info(">>>>>>>>>>> receive:" + new String(message));
}
/**
* 设置有返回消息的
* 需要注意的是,
* 1. 在消息的在生产者(发送消息端)一定要使用 SendAndReceive(……) 这种带有 receive 的方法,否则会抛异常,不捕获会死循环。
* 2. 该方法调用时会锁定当前线程,并且有可能会造成MQ的性能下降或者服务端/客户端出现死循环现象,请谨慎使用。
*
* @param message
* @return
*/
@RabbitListener(bindings = @QueueBinding(value = @Queue(value = Constant.QUEUE_NAME, durable = "true", exclusive = "false", autoDelete = "false"),
exchange = @Exchange(value = Constant.EXCHANGES_NAME, ignoreDeclarationExceptions = "true", type = ExchangeTypes.TOPIC, autoDelete = "false"),
key = Constant.ROUTING_REPLY_KEY))
public String receiveAndReply(byte[] message) {
log.info(">>>>>>>>>>> receive:" + new String(message));
return ">>>>>>>> I got the message";
}
}
主要是使用到 @RabbitListener,虽然看起来参数很多,仔细的你会发现这个和写配置类里面的基本属性是一摸一样的,没有任何区别。
需要注意的是我在这里多做了个有返回值的消息,这个使用异常的话,会不断重试消息,从而阻塞了线程。而且使用它的时候只能使用带有 receive 的方法给它发送消息。
生产者
生产者没什么变化。
@Component
public class MessageSender implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
/**
* logger
*/
private static final Logger log = LoggerFactory.getLogger(MessageSender.class);
private RabbitTemplate rabbitTemplate;
/**
* 注入 RabbitTemplate
*/
@Autowired
public MessageSender(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
this.rabbitTemplate.setConfirmCallback(this);
this.rabbitTemplate.setReturnCallback(this);
}
/**
* 测试无返回消息的
*/
public void send() {
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend(Constant.EXCHANGES_NAME, Constant.ROUTING_KEY, ">>>>>> Hello World".getBytes(), correlationData);
log.info(">>>>>>>>>> Already sent message");
}
/**
* 测试有返回消息的,需要注意一些问题
*/
public void sendAndReceive() {
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
Object o = rabbitTemplate.convertSendAndReceive(Constant.EXCHANGES_NAME, Constant.ROUTING_REPLY_KEY, ">>>>>>>> Hello World Second".getBytes(), correlationData);
log.info(">>>>>>>>>>> {}", Objects.toString(o));
}
/**
* Confirmation callback.
*
* @param correlationData correlation data for the callback.
* @param ack true for ack, false for nack
* @param cause An optional cause, for nack, when available, otherwise null.
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack) {
log.info(">>>>>>> 消息id:{} 发送成功", correlationData.getId());
} else {
log.info(">>>>>>> 消息id:{} 发送失败", correlationData.getId());
}
}
/**
* Returned message callback.
*
* @param message the returned message.
* @param replyCode the reply code.
* @param replyText the reply text.
* @param exchange the exchange.
* @param routingKey the routing key.
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
log.info("消息id:{} 发送失败", message.getMessageProperties().getCorrelationId());
}
}
测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootAnnotationApplicationTests {
@Autowired
private MessageSender sender;
@Test
public void send() {
sender.send();
}
@Test
public void sendAndReceive() {
sender.sendAndReceive();
}
}
学习Spring Boot:(二十六)使用 RabbitMQ 消息队列的更多相关文章
- Spring Boot(十四)RabbitMQ延迟队列
一.前言 延迟队列的使用场景:1.未按时支付的订单,30分钟过期之后取消订单:2.给活跃度比较低的用户间隔N天之后推送消息,提高活跃度:3.过1分钟给新注册会员的用户,发送注册邮件等. 实现延迟队列的 ...
- spring boot / cloud (十六) 分布式ID生成服务
spring boot / cloud (十六) 分布式ID生成服务 在几乎所有的分布式系统或者采用了分库/分表设计的系统中,几乎都会需要生成数据的唯一标识ID的需求, 常规做法,是使用数据库中的自动 ...
- Spring Boot(二十):使用spring-boot-admin对spring-boot服务进行监控
Spring Boot(二十):使用spring-boot-admin对spring-boot服务进行监控 Spring Boot Actuator提供了对单个Spring Boot的监控,信息包含: ...
- spring boot / cloud (十九) 并发消费消息,如何保证入库的数据是最新的?
spring boot / cloud (十九) 并发消费消息,如何保证入库的数据是最新的? 消息中间件在解决异步处理,模块间解耦和,和高流量场景的削峰,等情况下有着很广泛的应用 . 本文将跟大家一起 ...
- (十四)RabbitMQ消息队列-启用SSL安全通讯
原文:(十四)RabbitMQ消息队列-启用SSL安全通讯 如果RabbitMQ服务在内网中,只有内网的应用连接,我们认为这些连接都是安全的,但是个别情况我们需要让RabbitMQ对外提供服务.这种情 ...
- (六)RabbitMQ消息队列-消息任务分发与消息ACK确认机制(PHP版)
原文:(六)RabbitMQ消息队列-消息任务分发与消息ACK确认机制(PHP版) 在前面一章介绍了在PHP中如何使用RabbitMQ,至此入门的的部分就完成了,我们内心中一定还有很多疑问:如果多个消 ...
- openresty 学习笔记番外篇:python访问RabbitMQ消息队列
openresty 学习笔记番外篇:python访问RabbitMQ消息队列 python使用pika扩展库操作RabbitMQ的流程梳理. 客户端连接到消息队列服务器,打开一个channel. 客户 ...
- Linux学习之CentOS(二十六)--Linux磁盘管理:LVM逻辑卷的创建及使用
在上一篇随笔里面 Linux学习之CentOS(二十五)--Linux磁盘管理:LVM逻辑卷基本概念及LVM的工作原理,详细的讲解了Linux的动态磁盘管理LVM逻辑卷的基本概念以及LVM的工作原理, ...
- 【Java学习笔记之二十六】深入理解Java匿名内部类
在[Java学习笔记之二十五]初步认知Java内部类中对匿名内部类做了一个简单的介绍,但是内部类还存在很多其他细节问题,所以就衍生出这篇博客.在这篇博客中你可以了解到匿名内部类的使用.匿名内部类要注意 ...
- spring boot(二十)使用spring-boot-admin对服务进行监控
上一篇文章<springboot(十九):使用Spring Boot Actuator监控应用>介绍了Spring Boot Actuator的使用,Spring Boot Actuato ...
随机推荐
- HTML 图片轮播制作工具
下载地址:http://wowslider.com/download/wowslider-win-setup.zip?utm_source=free_downl_win&utm_medium= ...
- 控制反转IOC与依赖注入DI - 理论篇
学无止境,精益求精 十年河东十年河西,莫欺少年穷 昨天是五一小长假归来上班的第一天,身体疲劳,毫无工作热情.于是就看看新闻,喝喝茶,荒废了一天 也就在昨天,康美同事张晶童鞋让我学习下IOC的理论及实现 ...
- Linux常用命令行
实时查看日志runtime.log最后100行 tail -f -n 100 runtime.log
- netfilter/iptables 简介
netfilter 是 Linux 内置的一种防火墙机制,我们一般也称之为数据包过滤机制.iptables 则是一个命令行工具,用来配置 netfilter 防火墙.下图展示了一个带有防火墙的简单网络 ...
- 暴雪《争霸艾泽拉斯》*采用自适应 SSAO
在实时渲染过程中,屏幕空间环境光遮蔽 (SSAO) 常用于打造小范围环境光效果和接触阴影效果.它用于许多现代游戏,通常占用 5% 到 10% 的帧时间.在<争霸艾泽拉斯>* 游戏开发过程中 ...
- OpenStack构架知识梳理
OpenStack既是一个社区,也是一个项目和一个开源软件,提供开放源码软件,建立公共和私有云,它提供了一个部署云的操作平台或工具集,其宗旨在于:帮助组织运行为虚拟计算或存储服务的云,为公有云.私有云 ...
- js格式化时间
转自:https://blog.csdn.net/u010964869/article/details/51095827 显示格式为:yyyy-mm-dd hh:mi:ss function form ...
- Git科普来一发:【rebase】与【merge】
rebase 假设你现在基于远程分支"origin",创建一个叫"mywork"的分支. $ git checkout -b mywork origin 现在我 ...
- Some questions after Reading 《移山之道》
很少见到用故事的形式来写技术书籍的,这是我看到的第一本,书写得比较有趣,看了之后也是有一定的收获. 作者在此书中旁征博引,引用的东西虽不能一个一个查询是否正确,但是每次读到时候,感觉一种现代的软件工 ...
- SoftwareEngineering Individual Project - Word frequency program
说实话前面c#实在没怎么学过.这次写起来感觉非常陌生,就连怎么引用名空间都忘记了.在经过恶补后还是慢慢地适应了. 1.项目预计用时: 构建并写出大概的数据结构,程序框架及模块: 30min 实现文件夹 ...