文章目录
RabbitMQ
RabbitMQ
是一个消息中间件
。它实现了高级消息队列协议(AMQP),用于在分布式系统中传递消息。RabbitMQ 允许应用程序、服务或系统组件之间通过消息传递进行通信,而不需要它们直接相互调用,从而实现解耦和异步处理。
RabbitMQ 的核心概念
- Producer(生产者): 发送消息到消息队列的应用程序。
- Consumer(消费者): 从消息队列接收并处理消息的应用程序。
- Queue(队列): 消息的存储区。生产者将消息发送到队列,消费者从队列中接收消息。
- Exchange(交换机): 接受来自生产者的消息,并根据绑定规则将消息路由到一个或多个队列。
- Binding(绑定): 定义交换机与队列之间的关系及路由规则。
- Routing Key(路由键): 在交换机路由消息时使用的键值。
RabbitMQ 的优点
- 高可用性: RabbitMQ 可以通过集群和镜像队列实现高可用性。
- 多协议支持: 除了 AMQP 外,RabbitMQ 还支持 MQTT、STOMP 等协议。
- 扩展性: RabbitMQ 支持多种插件,可以扩展其功能,如管理界面、监控工具等。
- 强大的消息路由: 通过交换机类型(直连、主题、头、扇出)实现复杂的消息路由。
RabbitMQ 实现异步调用
异步调用的基本原理
在传统的同步调用
中,调用方需要等待被调用方完成任务后才能继续执行。而在异步调用
中,调用方可以立即继续执行,而不需要等待被调用方完成任务。异步调用通过消息队列来实现,生产者发送消息到队列,消费者从队列中接收并处理消息。
生产者代码
在生产者端,我们将使用 RabbitMqHelper
发送消息。RabbitMqHelper工具类见后面
@RestController
@RequestMapping
public class ProducerController {
private final RabbitMqHelper rabbitMqHelper;
@GetMapping("/send")
public String sendMessage(@RequestParam String message) {
// 交换机名字 路由key 生产者消费者需要保持一致
String exchange = "my_exchange";
String routingKey = "routing_key";
rabbitMqHelper.send(exchange, routingKey, message);
return "Message sent!";
}
@GetMapping("/sendAsync")
public String sendAsyncMessage(@RequestParam String message, @RequestParam(required = false) Long delay) {
String exchange = "my_exchange";
String routingKey = "routing_key";
rabbitMqHelper.sendAsyn(exchange, routingKey, message, delay);
return "Async message sent!";
}
}
消费者代码
在消费者端,我们使用 @RabbitListener
注解从队列中接收消息并进行处理。
@Component
@Slf4j
public class Consumer {
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(name = "my_queue", durable = "true"),
exchange = @Exchange(name = "my_exchange", type = ExchangeTypes.TOPIC),
key = "routing_key")
)
public void receiveMessage(String message) {
log.info("Received message: {}", message);
// 处理消息的业务逻辑
}
}
RabbitMQ 配置类
配置 RabbitMQ 的交换机、队列、绑定关系、消息转换、错误处理和辅助工具,以便于消息的生产与消费管理。
@Configuration
@ConditionalOnClass(value = {MessageConverter.class, AmqpTemplate.class})
public class MqConfig implements EnvironmentAware {
private static final String ERROR_EXCHANGE = "error.direct";
private static final String ERROR_KEY_PREFIX = "error.";
private static final String ERROR_QUEUE_TEMPLATE = "{}.error.queue";
private static final String REQUEST_ID_HEADER = "requestId";
private String defaultErrorRoutingKey;
private String defaultErrorQueue;
@Override
public void setEnvironment(Environment environment) {
String appName = environment.getProperty("spring.application.name");
this.defaultErrorRoutingKey = ERROR_KEY_PREFIX + appName;
this.defaultErrorQueue = StringUtils.replace(ERROR_QUEUE_TEMPLATE, "{}", appName);
}
@Bean(name = "rabbitListenerContainerFactory")
@ConditionalOnProperty(prefix = "spring.rabbitmq.listener", name = "type", havingValue = "simple", matchIfMissing = true)
SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(
SimpleRabbitListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory,
ObjectProvider<ContainerCustomizer<SimpleMessageListenerContainer>> simpleContainerCustomizer) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
configurer.configure(factory, connectionFactory);
simpleContainerCustomizer.ifUnique(factory::setContainerCustomizer);
factory.setAfterReceivePostProcessors(message -> {
Object header = message.getMessageProperties().getHeader(REQUEST_ID_HEADER);
if (header != null) {
MDC.put(REQUEST_ID_HEADER, header.toString());
}
return message;
});
return factory;
}
@Bean
public MessageConverter messageConverter(ObjectMapper mapper) {
return new Jackson2JsonMessageConverter(mapper);
}
@Bean
@ConditionalOnClass(MessageRecoverer.class)
@ConditionalOnMissingBean
public MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate) {
return new RepublishMessageRecoverer(rabbitTemplate, ERROR_EXCHANGE, defaultErrorRoutingKey);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnClass(RabbitTemplate.class)
public RabbitMqHelper rabbitMqHelper(RabbitTemplate rabbitTemplate) {
return new RabbitMqHelper(rabbitTemplate);
}
@Bean
public DirectExchange errorMessageExchange() {
return new DirectExchange(ERROR_EXCHANGE);
}
@Bean
public Queue errorQueue() {
return new Queue(defaultErrorQueue, true);
}
@Bean
public Binding errorBinding(Queue errorQueue, DirectExchange errorMessageExchange) {
return BindingBuilder.bind(errorQueue).to(errorMessageExchange).with(defaultErrorRoutingKey);
}
@Bean
public TopicExchange myExchange() {
return new TopicExchange("my_exchange");
}
@Bean
public Queue myQueue() {
return new Queue("my_queue", true);
}
@Bean
public Binding binding(Queue myQueue, TopicExchange myExchange) {
return BindingBuilder.bind(myQueue).to(myExchange).with("routing_key");
}
}
- setEnvironment 方法用来获取应用程序的名称,并设置默认的错误路由键和错误队列名称。
- simpleRabbitListenerContainerFactory 方法配置了 RabbitMQ 的 SimpleRabbitListenerContainerFactory。
- messageConverter 方法配置了消息转换器,使用 Jackson 进行 JSON 转换。
- republishMessageRecoverer 方法配置了消息恢复策略,将处理失败的消息重新发布到错误交换机和队列。
- rabbitMqHelper 方法配置了 RabbitMqHelper 工具类的 Bean。
- errorMessageExchange、errorQueue 和 errorBinding 方法配置了处理失败消息的交换机、队列和绑定。
- myExchange、myQueue 和 binding 方法配置了应用程序的交换机、队列和绑定。
RabbitMqHelper 工具类
你提供的 RabbitMqHelper
工具类代码如下:
@Slf4j
public class RabbitMqHelper {
private final RabbitTemplate rabbitTemplate;
private final MessagePostProcessor processor = new BasicIdMessageProcessor();
private final ThreadPoolTaskExecutor executor;
public RabbitMqHelper(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
executor = new ThreadPoolTaskExecutor();
//配置核心线程数
executor.setCorePoolSize(10);
//配置最大线程数
executor.setMaxPoolSize(15);
//配置队列大小
executor.setQueueCapacity(99999);
//配置线程池中的线程的名称前缀
executor.setThreadNamePrefix("mq-async-send-handler");
// 设置拒绝策略:当pool已经达到max size的时候,如何处理新任务
// CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//执行初始化
executor.initialize();
}
/**
* 根据exchange和routingKey发送消息
*/
public <T> void send(String exchange, String routingKey, T t) {
log.debug("准备发送消息,exchange:{}, RoutingKey:{}, message:{}", exchange, routingKey, t);
// 1.设置消息标示,用于消息确认,消息发送失败直接抛出异常,交给调用者处理
String id = UUID.randomUUID().toString();
CorrelationData correlationData = new CorrelationData(id);
// 2.设置发送超时时间为500毫秒
rabbitTemplate.setReplyTimeout(500);
// 3.发送消息,同时设置消息id
rabbitTemplate.convertAndSend(exchange, routingKey, t, processor, correlationData);
}
/**
* 根据exchange和routingKey发送消息,并且可以设置延迟时间
*/
public <T> void sendDelayMessage(String exchange, String routingKey, T t, Duration delay) {
// 1.设置消息标示,用于消息确认,消息发送失败直接抛出异常,交给调用者处理
String id = UUID.randomUUID().toString();
CorrelationData correlationData = new CorrelationData(id);
// 2.设置发送超时时间为500毫秒
rabbitTemplate.setReplyTimeout(500);
// 3.发送消息,同时设置消息id
rabbitTemplate.convertAndSend(exchange, routingKey, t, new DelayedMessageProcessor(delay), correlationData);
}
/**
* 根据exchange和routingKey 异步发送消息,并指定一个延迟时间
*
* @param exchange 交换机
* @param routingKey 路由KEY
* @param t 数据
* @param <T> 数据类型
*/
public <T> void sendAsyn(String exchange, String routingKey, T t, Long time) {
String requestId = MDC.get("requestId");
CompletableFuture.runAsync(() -> {
try {
MDC.put("requestId", requestId);
if (time != null && time > 0) {
Thread.sleep(time);
}
send(exchange, routingKey, t);
} catch (Exception e) {
log.error("推送消息异常,t:{}", t, e);
}
}, executor);
}
/**
* 根据exchange和routingKey 异步发送消息
*
* @param exchange 交换机
* @param routingKey 路由KEY
* @param t 数据
* @param <T> 数据类型
*/
public <T> void sendAsyn(String exchange, String routingKey, T t) {
sendAsyn(exchange, routingKey, t, null);
}
}
例子-用户注册时发送确认邮件
在用户注册时,系统会立即响应用户的注册请求,同时通过消息队列异步发送一封确认邮件。具体流程如下:用户提交注册信息后,系统保存用户数据,并将用户信息发送到消息队列。邮件服务从消息队列中获取用户信息,生成并发送确认邮件。这样,系统能够迅速响应用户请求,同时确保邮件发送的可靠性和可扩展性。
❤觉得有用的可以留个关注~❤