一、通过rabbitmq包来实现
1.1创建队列
public class RabbitMQUtil { public static Channel getChannel() throws IOException, TimeoutException { ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("127.0.0.1"); connectionFactory.setUsername("admin"); connectionFactory.setPassword("admin"); connectionFactory.setMaxInboundMessageBodySize(536870912); // 512 MB设置最大消息 Connection connection = connectionFactory.newConnection(); return connection.createChannel(); } }
1.2声明交换机或队列
交换机名
交换机类型
- 直接交换机(Direct Exchange): 根据精确匹配的路由键将消息发送到对应的队列。
- 主题交换机(Topic Exchange): 根据模式匹配的路由键将消息发送到一个或多个队列。
- 扇出交换机(Fanout Exchange): 将消息广播到绑定到该交换机的所有队列。
- 头交换机(Headers Exchange): 根据消息头属性来匹配并路由消息。
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
/* 队列和交换机绑定 队列名 是否持久化队列 是否独占队列 是否自动删除队列 队列参数(例如:死信交换机的名字和路由) */ Map<String, Object> params = new HashMap<>(); //死信交换机 params.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME); //死信路由 params.put("x-dead-letter-routing-key", DEAD_ROUTING_KEY); channel.queueDeclare(NORMAL_QUEUE, false, false, false, params);
1.3绑定
队列绑定
队列名 交换机名 路由名
channel.queueBind(DEAD_QUEUE, DEAD_EXCHANGE, DEAD_ROUTING);
1.4消费者消费队列
/* * 消费队列 自动应答 消费送达回调 消费取消回调 * */
channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback); 自动应答false设置手动应答
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
if (message.equals("message5")) {
System.out.println("普通消费者接收到消息:" + message + "并拒绝该消息");
channel.basicReject(delivery.getEnvelope().getDeliveryTag(), false);
} else {
//手动应答
System.out.println("普通消费者接收到消息:" + message);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
};
1.5生产者生产
交换机名 路由 AMQP.BasicProperties() 数据
channel.basicPublish(EXCHANGE_NAME, "normal_routing", null, pcdData);
AMQP例子
//设置TTL 单位是ms
AMQP.BasicProperties basicProperties = new AMQP.BasicProperties()
.builder().expiration("10000").build();
二、springboot完成rabbitmq
结构:
代码
通过死信交换机实现延时消息(以延时消息为例)
2.1配置文件
2.1.1配置基本名称
constants里面写
public class DelayQueueOneConstant {
/**
* 死信交换机实现延迟消息
*/
//普通交换机名称
public static final String NORMAL_EXCHANGE = "ne";
//死信交换机名称
public static final String DEAD_EXCHANGE = "de";
//普通队列名称
public static final String NORMAL_QUEUE_A = "nqA";
public static final String NORMAL_QUEUE_B = "nqB";
//死信队列名称
public static final String DEAD_QUEUE = "dq";
//普通队列路由
public static final String NORMAL_ROUTING_A = "nrA";
public static final String NORMAL_ROUTING_B = "nrB";
//死信队列路由
public static final String DEAD_ROUTING = "dr";
}
2.1.2配置绑定
@Configuration
public class QueueConfig {
//声明普通交换机
@Bean("normalExchange")
public DirectExchange normalExchange() {
return new DirectExchange(DelayQueueOneConstant.NORMAL_EXCHANGE);
}
//声明死信交换机
@Bean("deadExchange")
public DirectExchange deadExchange() {
return new DirectExchange(DelayQueueOneConstant.DEAD_EXCHANGE);
}
//声明队列A
@Bean("queueA")
public Queue queueA() {
Map<String, Object> args = new HashMap<>(3);
//声明当前队列绑定的死信交换机
args.put("x-dead-letter-exchange", DelayQueueOneConstant.DEAD_EXCHANGE);
//声明当前队列的死信路由 key
args.put("x-dead-letter-routing-key", DelayQueueOneConstant.DEAD_ROUTING);
//声明队列的 TTL
args.put("x-message-ttl", 10000);
return QueueBuilder.durable(DelayQueueOneConstant.NORMAL_QUEUE_A).withArguments(args).build();
}
//声明队列B
@Bean("queueB")
public Queue queueB() {
Map<String, Object> args = new HashMap<>(3);
//声明当前队列绑定的死信交换机
args.put("x-dead-letter-exchange", DelayQueueOneConstant.DEAD_EXCHANGE);
//声明当前队列的死信路由 key
args.put("x-dead-letter-routing-key", DelayQueueOneConstant.DEAD_ROUTING);
//声明队列的 TTL
args.put("x-message-ttl", 30000);
return QueueBuilder.durable(DelayQueueOneConstant.NORMAL_QUEUE_B).withArguments(args).build();
}
//声明死信队列
@Bean("queueD")
public Queue queueD() {
return QueueBuilder.durable(DelayQueueOneConstant.DEAD_QUEUE).build();
}
//声明队列A绑定普通交换机
@Bean
public Binding queueABinding(@Qualifier("queueA") Queue queueA,
@Qualifier("normalExchange") DirectExchange exchange) {
return BindingBuilder.bind(queueA).to(exchange).with(DelayQueueOneConstant.NORMAL_ROUTING_A);
}
//声明队列B绑定普通交换机
@Bean
public Binding queueBBinding(@Qualifier("queueB") Queue queueB,
@Qualifier("normalExchange") DirectExchange exchange) {
return BindingBuilder.bind(queueB).to(exchange).with(DelayQueueOneConstant.NORMAL_ROUTING_B);
}
//声明队列D绑定死信交换机
@Bean
public Binding queueDBinding(@Qualifier("queueD") Queue queueD,
@Qualifier("deadExchange") DirectExchange exchange) {
return BindingBuilder.bind(queueD).to(exchange).with(DelayQueueOneConstant.DEAD_ROUTING);
}
}
@Qualifier
-
指定注入的 Bean:当容器中有多个类型相同的
Queue
或DirectExchange
bean 时,@Qualifier
注解告诉 Spring 应该选择并注入名称为"queueC"
的Queue
实例,以及名称为"normalExchange"
的DirectExchange
实例。 -
避免歧义:如果没有使用
@Qualifier
注解,而容器中又存在多个相同类型的 bean,Spring 可能会抛出异常,表示无法确定要注入哪个 bean。@Qualifier
可以避免这种情况。
@Bean
-
定义 Bean:当你在方法上使用
@Bean
注解时,Spring 会将该方法的返回值作为一个 bean 来管理。这种方式通常用于显式地声明和配置第三方库中的类或通过 Java 配置类来替代传统的 XML 配置文件。 -
方法级别的注入:
@Bean
注解的方法通常位于配置类(用@Configuration
注解的类)中。Spring 会调用这些方法,并将返回的对象放入 Spring 容器中,这样其他组件可以通过@Autowired
或@Qualifier
等注解来注入这些 bean。 -
方法执行:Spring 每次需要该 bean 时,通常会调用
@Bean
注解的方法来获取实例。默认情况下,这些 bean 是单例的(即在整个应用上下文中只有一个实例),但可以通过参数指定为原型作用域或其他作用域。
2.2生产者(controller)
package com.rabbitmq.springbootrabbitmq.controller;
import com.rabbitmq.springbootrabbitmq.constants.DelayQueueOneConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalTime;
import java.util.Date;
@Slf4j
@RestController
@RequestMapping("delay")
public class SendMessageController {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 队列延迟
*/
@GetMapping("/send/{message}")
public void sendMessage(@PathVariable String message) {
log.info("当前时间:{},发送一条信息给两个 TTL 队列:{}", LocalTime.now(), message);
rabbitTemplate.convertAndSend(DelayQueueOneConstant.NORMAL_EXCHANGE,
DelayQueueOneConstant.NORMAL_ROUTING_A,
"消息来自 ttl 为 10S 的队列: " + message);
rabbitTemplate.convertAndSend(DelayQueueOneConstant.NORMAL_EXCHANGE,
DelayQueueOneConstant.NORMAL_ROUTING_B,
"消息来自 ttl 为 30S 的队列: " + message);
}
/**
* 队列延迟优化
*
* @param message
* @param ttl
*/
@GetMapping("/sendTtlMsg/{message}/{ttl}")
public void sendMsg(@PathVariable String message, @PathVariable String ttl) {
rabbitTemplate.convertAndSend(DelayQueueOneConstant.NORMAL_EXCHANGE,
DelayQueueOneConstant.NORMAL_ROUTING_C, message, correlationData -> {
correlationData.getMessageProperties().setExpiration(ttl);
return correlationData;
});
log.info("当前时间:{},发送一条时长{}毫秒 TTL 信息给队列 C:{}",
LocalTime.now(), ttl, message);
}
}
@Autowired
-
自动注入依赖:
@Autowired
可以让 Spring 自动地把应用上下文中定义的某个 bean 注入到标注了这个注解的字段、构造函数、或方法中,而无需手动去实例化这些 bean。Spring 会根据类型匹配来查找合适的 bean。 -
按类型注入:Spring 通过类型(而非名称)来查找和注入依赖。比如,如果标注的字段是
RabbitTemplate
类型,Spring 就会在应用上下文中寻找类型为RabbitTemplate
的 bean。 -
注入位置:
@Autowired
可以用在以下几个位置:- 字段上:直接在字段上标注
@Autowired
,Spring 会自动注入该字段。 - 构造函数上:在构造函数参数上使用
@Autowired
,Spring 会通过这个构造函数来实例化类并注入依赖。 - 方法上:在方法上使用
@Autowired
,Spring 会在调用该方法时注入参数。
- 字段上:直接在字段上标注
-
可选注入:可以将
@Autowired
与required
属性一起使用,例如@Autowired(required = false)
。这样如果没有找到合适的 bean,Spring 不会抛出异常,而是保持字段为null
。
这里的RabbitTemplate类可以通过下面的方式来查看如何自动注入的
跳转到类:
- 使用快捷键
Ctrl + Shift + N
(Mac 上是Command + Shift + O
),在弹出的搜索框中输入RabbitAutoConfiguration
,并按下回车。
查找内容:
- Windows/Linux:
Ctrl + F
- macOS:
Command + F
生产者主要用模板RabbitTemplate实现
private RabbitTemplate rabbitTemplate;
RabbitTemplate
类
RabbitTemplate
是 Spring AMQP 框架中的一个核心类,用于简化与 RabbitMQ 的交互。它提供了一种高级抽象,使得在 Spring 应用中发送和接收消息变得更加容易和一致。
主要功能
-
消息发送:
RabbitTemplate
提供了一系列方法来发送消息到 RabbitMQ 队列或交换机。你可以使用不同的参数来指定消息的路由键、交换机名称以及要发送的具体消息内容。 -
消息接收: 它还提供了接收消息的方法,可以从指定的队列中获取消息。你可以使用同步或异步方式接收消息,具体取决于应用需求。
-
消息转换:
RabbitTemplate
支持消息转换器,你可以使用消息转换器将对象转换为消息,再发送到队列中,或者将从队列接收到的消息转换为对象。常用的消息转换器包括Jackson2JsonMessageConverter
和SimpleMessageConverter
。 -
请求-回复模式: 通过
RabbitTemplate
,你可以实现同步的请求-回复模式,即发送一个消息并等待一个响应。这对于 RPC(远程过程调用)等场景非常有用。
rabbitTemplate.convertAndSend()函数
参数含义:交换机名 路由 消息本体
rabbitTemplate.convertAndSend(DelayQueueOneConstant.NORMAL_EXCHANGE,
DelayQueueOneConstant.NORMAL_ROUTING_A,
"消息来自 ttl 为 10S 的队列: " + message);
2.3消费者
package com.rabbitmq.springbootrabbitmq.consumer;
import com.rabbitmq.client.Channel;
import com.rabbitmq.springbootrabbitmq.constants.DelayQueueOneConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.time.LocalTime;
import java.util.Date;
@Slf4j
@Component
public class Consumer {
@RabbitListener(queues = DelayQueueOneConstant.DEAD_QUEUE)
public void receiveD(Message message, Channel channel) throws Exception {
String msg = new String(message.getBody());
log.info("当前时间:{},收到死信队列信息{}", LocalTime.now(), msg);
}
}
Log4j/SLF4J: log.info
是 Log4j 或 SLF4J 中常见的日志方法。SLF4J 是一种日志接口,支持多种不同的日志实现(如 Log4j、Logback 等)
@RabbitListener(queues = DelayQueueOneConstant.DEAD_QUEUE)监听队列名称
三、高级发布确认
3.1发生背景
在生产环境中由于一些不明原因,导致 rabbitmq 重启,在 RabbitMQ 重启期间生产者消息投递失败,导致消息丢失,需要手动处理和恢复。于是,我们开始思考,如何才能进行 RabbitMQ 的消息可靠投递呢?特别是在这样比较极端的情况,RabbitMQ 集群不可用的时候,无法投递的消息该如何处理。
机制图 :