整合 Spring boot 提供的 spring-boot-starter-amqp
,实现消息发送、消息消费、确认
介绍使用 Docker 方式安装,Docker 安装可以参考 https://blog.csdn.net/gongm24/article/details/86357866
docker pull rabbitmq:management
设置默认用户名及密码
docker run --name rabbitmq \
-p 15672:15672 \
-p 5672:5672 \
-e RABBITMQ_DEFAULT_USER=admin \
-e RABBITMQ_DEFAULT_PASS=admin \
-d rabbitmq:management
访问地址: http://[宿主机IP]:15672
,使用用户名密码 admin/admin
进行登录
引入 Spring Boot Starter 父工程
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.0.5.RELEASEversion>
parent>
添加 spring-boot-starter-amqp
的依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
添加后的整体依赖如下
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: admin
password: admin
#虚拟host 可以不设置,使用server默认host
# virtual-host: JCcccHost
@Configuration
public class DirectConfig {
@Bean
public Queue testDirectQueue() {
return new Queue("TestDirectQueue",true);
}
@Bean
DirectExchange testDirectExchange() {
return new DirectExchange("TestDirectExchange");
}
@Bean
Binding bindingDirect() {
return BindingBuilder.bind(testDirectQueue()).to(testDirectExchange()).with("TestDirectRouting");
}
}
引入 spring-boot-starter-amqp
时,会自动注册 RabbitTemplate 到 Spring 容器,
消息发送可以借助其提供的 convertAndSend
方法
@AllArgsConstructor
@RestController
public class MqController {
private RabbitTemplate rabbitTemplate;
@PostMapping("/sendDirectMessage")
public String sendDirectMessage(@RequestBody String msgData) {
String msgId = String.valueOf(UUID.randomUUID());
String sendTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Map<String,Object> map = new HashMap<>(4);
map.put("msgId", msgId);
map.put("msgData", msgData);
map.put("sendTime", sendTime);
rabbitTemplate.convertAndSend("TestDirectExchange", "TestDirectRouting", map);
return "ok";
}
}
@SpringBootApplication
public class SenderApplication {
public static void main(String[] args) {
SpringApplication.run(SenderApplication.class, args);
}
}
执行测试用例,执行完成后,去 RabbitMQ 管理后台查看,
在 Exchanges 标签页中,可以看到使用的交换器 TestDirectExchange
,
在 Queues 标签页中,可以看到队列 TestDirectQueue
,并且 Ready 值为 1,表示有一条数据待处理
@RunWith(SpringRunner.class)
@WebAppConfiguration
@SpringBootTest(classes = SenderApplication.class)
public class MqTest {
private MockMvc mvc;
@Autowired
private WebApplicationContext webApplicationContext;
@Before
public void setUp() {
mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
@Test
public void testSendDirectMessage() throws Exception {
MvcResult mvcResult = mvc.perform(
MockMvcRequestBuilders
.post("/sendDirectMessage")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content("test send direct message")
)
.andDo(MockMvcResultHandlers.print())
.andReturn();
Assert.assertEquals(200, mvcResult.getResponse().getStatus());
}
}
@Configuration
public class Config {
@Bean
public Queue TestDirectQueue() {
return new Queue("TestDirectQueue",true);
}
}
@Component
@RabbitListener(queues = "TestDirectQueue")
public class MqConsumer {
@RabbitHandler
public void process(Map testMessage) {
System.out.println("DirectReceiver消费者收到消息 : " + testMessage.toString());
}
}
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
使用 ConsumerApplication 类启动项目,在日志中可以看到消费消息时产生的日志
DirectReceiver消费者收到消息 : {msgId=ccf1f1c0-f8c5-483a-933e-ed3d77d59333, msgData=null, sendTime=2020-01-07 19:50:23}
去 RabbitMQ 管理后台,查看 Queues 标签页,可以看到队列 TestDirectQueue
的 Ready 值变成了 0,表示消息已经被消费。
消息接收的确认机制主要存在三种模式:
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: admin
password: admin
# 虚拟host 可以不设置,使用server默认host
#virtual-host: JCcccHost
# 发送方消息确认:已发送到交换机(Exchange)
publisher-confirms: true
# 发送方消息确认:已发送到队列(Queue)
publisher-returns: true
# 消费方消息确认:手动确认
listener:
type: simple
simple:
acknowledge-mode: manual
basicReject 的第二个参数是 requeue,意思是是否重新加入队列,
如果为 true,则表示本次消费不成功,并将当前消息重新加入至当前队列,
如果为 false,则表示本次消费不成功,并将当前消息丢弃,如果有设置死信队列,则会进入死信队列(关于死信队列,在下一章会讲)
@Component
@RabbitListener(queues = "TestDirectQueue")
public class ConsumerDirectQueue {
@RabbitHandler
public void process(Map obj, Channel channel, Message message) throws IOException {
try {
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
System.out.println("DirectQueue消费者收到消息并ACK返回 : " + obj.toString());
} catch (Exception e) {
e.printStackTrace();
channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
}
}
}
本章源码 : https://gitee.com/gongm_24/spring-boot-tutorial.git
消息队列是实际生产中是必备组件,用于保证系统高可用、高性能、可扩展
Fanout 交换器没有 Routing,直接将队列与交换器进行关联即可
@Configuration
public class FanoutConfig {
@Bean
public Queue fanoutQueueA() {
return new Queue("FanoutQueueA",true);
}
@Bean
public Queue fanoutQueueB() {
return new Queue("FanoutQueueB",true);
}
@Bean
FanoutExchange fanoutExchange() {
return new FanoutExchange("TestFanoutExchange");
}
@Bean
Binding bindingQueueA() {
return BindingBuilder.bind(fanoutQueueA()).to(fanoutExchange());
}
@Bean
Binding bindingQueueB() {
return BindingBuilder.bind(fanoutQueueB()).to(fanoutExchange());
}
}
也是调用 convertAndSend 方法,只是 routing 参数传值 null
@PostMapping("/sendFanoutMessage")
public String sendFanoutMessage(@RequestBody String msgData) {
String msgId = String.valueOf(UUID.randomUUID());
String sendTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Map<String,Object> map = new HashMap<>(4);
map.put("msgId", msgId);
map.put("msgData", msgData);
map.put("sendTime", sendTime);
rabbitTemplate.convertAndSend("TestFanoutExchange", null, map);
return "ok";
}
@Configuration
public class TopicConfig {
@Bean
public Queue topicQueueMan() {
return new Queue("TopicQueue.man",true);
}
@Bean
public Queue topicQueueWoman() {
return new Queue("TopicQueue.woman",true);
}
@Bean
TopicExchange topicExchange() {
return new TopicExchange("TestTopicExchange");
}
@Bean
Binding bindingQueueMan() {
return BindingBuilder.bind(topicQueueMan()).to(topicExchange()).with("TopicQueue.man");
}
@Bean
Binding bindingQueueWoman() {
return BindingBuilder.bind(topicQueueWoman()).to(topicExchange()).with("TopicQueue.#");
}
}
跟使用 direct 交换器一样,只是 topic 交互器会根据 routing 进行匹配,然后决定将消息发送至哪些队列
@PostMapping("/sendTopicMessage")
public String sendTopicMessage(@RequestBody String msgData) {
String msgId = String.valueOf(UUID.randomUUID());
String sendTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Map<String,Object> map = new HashMap<>(4);
map.put("msgId", msgId);
map.put("msgData", msgData);
map.put("sendTime", sendTime);
rabbitTemplate.convertAndSend("TestFanoutExchange", "TopicQueue.woman", map);
return "ok";
}