一 RabbitMQ的介绍
RabbitMQ是消息中间件的一种,消息中间件即分布式系统中完成消息的发送和接收的基础软件.这些软件有很多,包括ActiveMQ(apache公司的),RocketMQ(阿里巴巴公司的,现已经转让给apache).,kafka等;
二、消息中间件的应用场景
1、异步处理:在某些场景下我们其实有些操作是不需要同步执行的,异步的话就可以加快通讯效率,这个时候我们就可以用消息队列了。
2、应用解耦:什么叫做应用解耦呢?其实就是我们有的时候不同的系统之间调用正常是通过接口来的,这样的后果就是其中一个系统出现问题就会导致整个流程阻塞走不下去,如果用消息中间件的话,就只要一个系统发布消息,另外一个系统订阅进行处理。即使被调用的系统出现故障,等他恢复后消息还是回继续推送过去,直到消息被消费。这样就保证了消息的可靠性。
3、流量削峰:流量削峰一般在秒杀活动中应用广泛
场景:秒杀活动,一般会因为流量过大,导致应用挂掉,为了解决这个问题,一般在应用前端加入消息队列。 这样就可以降低服务器的压力。
三、代码实现
1、pom文件引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2、配置rabbitMQ
server:
servlet:
context-path: /rabbit-demo
port: 8080
#rabbitMQ配置
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
publisher-confirms: true #消息发送到交换机确认机制,是否确认回调
publisher-returns: true #消息发送到交换机确认机制,是否返回回调
listener:
simple:
acknowledge-mode: manual #采用手动应答
concurrency: 1 #最小消费数量
max-concurrency: 10 #最大并发消费数量
retry:
enabled: true #是否支持重试
3、编写config处理类,这里第一个用的是directExchange交换机,rabbitmq主要有四种类型的交换,direct/fanout/topic/header
direct:其实就是一个一对一的,一个生产者一个消费者
fanout:就类似于广播,交换机里面的队列全部都可以接收到消息
topic:和direct差不多,不过多了个匹配机制,*和#具体可以看下面代码
header:个人觉得运用场景应该不多这里不细讲了
/**
* @Author: cxw
* @CreateDate: 2019-05-27 15:24
* @Description: 最普通的一对一消息应答配置
*/
@Slf4j
@Configuration
public class RabbitConfig{
public static final String QUEUE = "my_queue";
@Resource
private RabbitTemplate rabbitTemplate;
/**
* 定义一个叫my_queue的队列
* @return Queue 可以有4个参数
* 1.队列名
* 2.durable 持久化消息队列 ,rabbitmq重启的时候不需要创建新的队列 默认true
* 3.autoDelete 表示消息队列没有在使用时将被自动删除 默认是false
* 4.exclusive 表示该消息队列是否只在当前connection生效,默认是false
*/
@Bean
public Queue queue() {
return new Queue(QUEUE, true);
}
@Bean
public DirectExchange directExchange(){
return new DirectExchange("directExchange");
}
@Bean
public Binding binding(){
return BindingBuilder.bind(queue()).to(directExchange()).with("direct_routing_key");
}
@Bean
public AmqpTemplate amqpTemplate(){
// 使用jackson 消息转换器
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
rabbitTemplate.setEncoding("UTF-8");
// 消息发送失败返回到队列中,yml需要配置 publisher-returns: true
rabbitTemplate.setMandatory(true);
rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
String correlationId = message.getMessageProperties().getCorrelationId();
log.info("消息:{} 发送失败, 应答码:{} 原因:{} 交换机: {} 路由键: {}", correlationId, replyCode, replyText, exchange, routingKey);
});
// 消息确认,yml需要配置 publisher-confirms: true
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
log.info("消息发送到exchange成功,id: {}", correlationData.getId());
} else {
log.info("消息发送到exchange失败,原因: {}", cause);
}
});
return rabbitTemplate;
}
}
@Slf4j
@Configuration
public class FanoutRabbitConfig {
public static final String QUEUE_A = "my_queue_a";
public static final String QUEUE_B = "my_queue_b";
@Bean
public Queue queuea() {
return new Queue(QUEUE_A);
}
@Bean
public Queue queueb() {
return new Queue(QUEUE_B);
}
/**
* 这里采用的广播模式交换机
* ExchangeTypes 总共有4种
* 1、direct
* 2、topic
* 3、fanout
* 4、headers
* @return
*/
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("fanoutExchange");
}
/**
* 一个交换机可以绑定多个消息队列,也就是消息通过一个交换机,可以分发到不同的队列当中去
* @return
*/
@Bean
public Binding binding_a(){
return BindingBuilder.bind(queuea()).to(fanoutExchange());
}
@Bean
public Binding binding_b(){
return BindingBuilder.bind(queueb()).to(fanoutExchange());
}
}
@Slf4j
@Configuration
public class TopicRabbitConfig {
public static final String QUEUE_A = "topic_queue_a";
public static final String QUEUE_B = "topic_queue_b";
@Bean
public Queue queuec() {
return new Queue(QUEUE_A);
}
@Bean
public Queue queued() {
return new Queue(QUEUE_B);
}
/**
* 这里采用的广播模式交换机
* ExchangeTypes 总共有4种
* 1、direct
* 2、topic
* 3、fanout
* 4、headers
* @return
*/
@Bean
public TopicExchange topicExchange(){
return new TopicExchange("topicExchange");
}
/**
* 一个交换机可以绑定多个消息队列,也就是消息通过一个交换机,可以分发到不同的队列当中去
* @return
*/
@Bean
public Binding binding_c(){
return BindingBuilder.bind(queuec()).to(topicExchange()).with("topic.message");
}
@Bean
public Binding binding_d(){
return BindingBuilder.bind(queued()).to(topicExchange()).with("topic.#");//*表示一个词,#表示零个或多个词
}
}
4.编写消息生产者
@Component
public class RabbitSender {
@Autowired
private RabbitTemplate rabbitTemplate;
public void send(){
for (int i =0; i< 10; i++) {
String msg = "RabbitSender, 序号: " + i;
System.out.println("RabbitSender, " + msg);
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend( "directExchange","direct_routing_key",msg,correlationData);
}
}
}
package com.cxw.rabbitmqdemo.sender;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.UUID;
/**
* @Author: cxw
* @CreateDate: 2019-05-27 16:30
* @Description:
*/
@Component
@Slf4j
public class FanoutRabbitSender implements RabbitTemplate.ReturnCallback {
@Autowired
private RabbitTemplate rabbitTemplate;
public void fanoutSend(){
for (int i =0; i< 10; i++) {
String msg = "FanoutRabbitSender, 序号: " + i;
System.out.println("FanoutRabbitSender, " + msg);
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend("fanoutExchange","",msg,correlationData);
}
}
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
System.out.println("sender return success" + message.toString()+"==="+i+"==="+s1+"==="+s2);
}
}
package com.cxw.rabbitmqdemo.sender;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.UUID;
/**
* @Author: cxw
* @CreateDate: 2019-05-27 16:30
* @Description:
*/
@Component
public class TopicRabbitSender {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* topic.message 既可以匹配到topic.message 也可以匹配到topic.#
*
*/
public void send(){
for (int i =0; i< 10; i++) {
String msg = "TopicRabbitSender1, 序号: " + i;
System.out.println("TopicRabbitSender1, " + msg);
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend( "topicExchange","topic.message",msg,correlationData);
}
}
/**
* 只能匹配到topic.#
*/
public void send2(){
for (int i =0; i< 10; i++) {
String msg = "TopicRabbitSender2, 序号: " + i;
System.out.println("TopicRabbitSender2, " + msg);
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend( "topicExchange","topic.messages",msg,correlationData);
}
}
}
5、编写消费者
@Component
public class RabbitReceiver {
@RabbitListener(queues ="my_queue")
public void receive(String json, Message message,Channel channel) {
try {
// 消息删除,如果不删除就会导致消息一直堆积
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
System.out.println("消费者收到了一个消息: " + new String(message.getBody()) + " " + new Date().getTime());
} catch (Exception e) {
throw new RuntimeException("处理消息失败");
}
}
}
@Component
public class TopicRabbitReceiver {
@RabbitListener(queues ="topic_queue_a")
public void receive1(String json, Message message,Channel channel) {
try {
// 消息删除,如果不删除就会导致消息一直堆积
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
System.out.println("消费者topic_queue_a收到了一个消息: " + new String(message.getBody()) + " " + new Date().getTime());
} catch (Exception e) {
throw new RuntimeException("处理消息失败");
}
}
@RabbitListener(queues ="topic_queue_b")
public void receive2(String json, Message message,Channel channel) {
try {
// 消息删除,如果不删除就会导致消息一直堆积
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
System.out.println("消费者topic_queue_b收到了一个消息: " + new String(message.getBody()) + " " + new Date().getTime());
} catch (Exception e) {
throw new RuntimeException("处理消息失败");
}
}
}
@Component
public class FanoutRabbitReceiverA {
@RabbitListener(queues ="my_queue_a")
public void receive(Message message,Channel channel) {
try {
// 消息删除,如果不删除就会导致消息一直堆积
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
System.out.println("FanoutRabbitReceiverA消费者收到了一个消息: " + new String(message.getBody()) + " | " + new Date().getTime());
} catch (Exception e) {
throw new RuntimeException("处理消息失败");
}
}
}
@Component
public class FanoutRabbitReceiverB {
@RabbitListener(queues ="my_queue_b")
public void receive(Message message,Channel channel) {
try {
// 消息删除,如果不删除就会导致消息一直堆积
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
System.out.println("FanoutRabbitReceiverB消费者收到了一个消息: " + new String(message.getBody()) + " | " + new Date().getTime());
} catch (Exception e) {
throw new RuntimeException("处理消息失败");
}
}
}
6、编写测试案例
@RunWith(SpringRunner.class)
@SpringBootTest
public class RabbitmqDemoApplicationTests {
@Autowired
private RabbitSender sender;
@Autowired
private FanoutRabbitSender fanoutSender;
@Autowired
private TopicRabbitSender topicRabbitSender;
@Test
public void send() {
sender.send();
}
@Test
public void fanoutSend() {
fanoutSender.fanoutSend();
}
@Test
public void topicSend() {
topicRabbitSender.send();
}
@Test
public void topicSend2() {
topicRabbitSender.send2();
}
}
这里写了三种交换机类型的消息例子。我们里面也用了消息确认机制,为甚我们要用消息确认呢?因为消息中间件其实也不是完全靠谱的,如果不进行确认我们无法知道到底是不是接收成功,万一消息丢失了。用了消息确认机制就相对安全些。这样就真的安全了么?肯定也不是的,不存在完美的系统,只有相对好的系统。比如确认回调的过程中出现问题,那么怎么处理呢?这些更加深入的问题就留给大家自己去了解。_
代码 :https://gitee.com/wangzaiwork/rabbitmq-demo.git
----------------------------写的不好,仅供参考---------------------------------------