Rabbit MQ实现异步调用

RabbitMQ

RabbitMQ 是一个消息中间件。它实现了高级消息队列协议(AMQP),用于在分布式系统中传递消息。RabbitMQ 允许应用程序、服务或系统组件之间通过消息传递进行通信,而不需要它们直接相互调用,从而实现解耦和异步处理。

RabbitMQ 的核心概念

  1. Producer(生产者): 发送消息到消息队列的应用程序。
  2. Consumer(消费者): 从消息队列接收并处理消息的应用程序。
  3. Queue(队列): 消息的存储区。生产者将消息发送到队列,消费者从队列中接收消息。
  4. Exchange(交换机): 接受来自生产者的消息,并根据绑定规则将消息路由到一个或多个队列。
  5. Binding(绑定): 定义交换机与队列之间的关系及路由规则。
  6. Routing Key(路由键): 在交换机路由消息时使用的键值。

RabbitMQ 的优点

  1. 高可用性: RabbitMQ 可以通过集群和镜像队列实现高可用性。
  2. 多协议支持: 除了 AMQP 外,RabbitMQ 还支持 MQTT、STOMP 等协议。
  3. 扩展性: RabbitMQ 支持多种插件,可以扩展其功能,如管理界面、监控工具等。
  4. 强大的消息路由: 通过交换机类型(直连、主题、头、扇出)实现复杂的消息路由。

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);
    }
}

例子-用户注册时发送确认邮件

在用户注册时,系统会立即响应用户的注册请求,同时通过消息队列异步发送一封确认邮件。具体流程如下:用户提交注册信息后,系统保存用户数据,并将用户信息发送到消息队列。邮件服务从消息队列中获取用户信息,生成并发送确认邮件。这样,系统能够迅速响应用户请求,同时确保邮件发送的可靠性和可扩展性。

在这里插入图片描述


❤觉得有用的可以留个关注~❤

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值