使用RabbitMQ实现判题功能

这次主要选用RabbitMQ消息队列来对判题服务和题目服务解耦,题目服务只需要向消息队列发送消息,判题服务从消息队列中取信息去执行判题,然后异步更新数据库即可。

五一宝宝请快点跑~~~~~

先回顾一下RabbitMQ

(1)引入依赖

复制代码
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-amqp -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
    <version>2.7.2</version>
</dependency>

(2)在yml中引入配置

复制代码
spring:
    rabbitmq:
        host: localhost
        port: 5672
        password: guest
        username: guest

(3)创建交换机和队列

复制代码
/**
 * 用于创建测试程序用到的交换机和队列(只用在程序启动前执行一次)
 */
public class MqInitMain {

    public static void main(String[] args) {
        try {
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("localhost");
            Connection connection = factory.newConnection();
            Channel channel = connection.createChannel();
            String EXCHANGE_NAME = "code_exchange";
            channel.exchangeDeclare(EXCHANGE_NAME, "direct");

            // 创建队列,随机分配一个队列名称
            String queueName = "code_queue";
            channel.queueDeclare(queueName, true, false, false, null);
            channel.queueBind(queueName, EXCHANGE_NAME, "my_routingKey");
        } catch (Exception e) {

        }

    }
}

(4)生产者代码

复制代码
@Component
public class MyMessageProducer {

    @Resource
    private RabbitTemplate rabbitTemplate;

    public void sendMessage(String exchange, String routingKey, String message) {
        rabbitTemplate.convertAndSend(exchange, routingKey, message);
    }

}

(5)消费者代码

复制代码
@Component
@Slf4j
public class MyMessageConsumer {

    // 指定程序监听的消息队列和确认机制
    @SneakyThrows
    @RabbitListener(queues = {"code_queue"}, ackMode = "MANUAL")
    public void receiveMessage(String message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
        log.info("receiveMessage message = {}", message);
        channel.basicAck(deliveryTag, false);
    }

}

(6)单元测试执行

复制代码
@SpringBootTest
class MyMessageProducerTest {

    @Resource
    private MyMessageProducer myMessageProducer;

    @Test
    void sendMessage() {
        myMessageProducer.sendMessage("code_exchange", "my_routingKey", "你好呀");
    }
}

项目实践

要传递的消息是什么?题目提交 id

题目服务中,把原本的本地异步执行改为向消息队列发送消息:

复制代码
// 发送消息
myMessageProducer.sendMessage("code_exchange", "my_routingKey", String.valueOf(questionSubmitId));
// 执行判题服务
//        CompletableFuture.runAsync(() -> {
//            judgeFeignClient.doJudge(questionSubmitId);
//        });

判题服务中,监听消息,执行判题:

复制代码
@Component
@Slf4j
public class MyMessageConsumer {

    @Resource
    private JudgeService judgeService;

    // 指定程序监听的消息队列和确认机制
    @SneakyThrows
    @RabbitListener(queues = {"code_queue"}, ackMode = "MANUAL")
    public void receiveMessage(String message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
        log.info("receiveMessage message = {}", message);
        long questionSubmitId = Long.parseLong(message);
        try {
            judgeService.doJudge(questionSubmitId);
            channel.basicAck(deliveryTag, false);
        } catch (Exception e) {
            channel.basicNack(deliveryTag, false, false);
        }
    }

}

消息确认机制:basicAck basicNack

在 RabbitMQ 中,消息从生产者发送到队列,然后被消费者消费。为了确保消息被正确处理,RabbitMQ 提供了一种机制,让消费者可以确认消息是否已经被成功处理。这就是 消息确认机制。

basicAck:

作用:消费者告诉 RabbitMQ,我已经成功处理了这条消息。

参数:

(1)deliveryTag:消息的唯一标识符,RabbitMQ 用它来跟踪每条消息。

(2)multiple:布尔值,表示是否确认多个消息。如果为 true,则确认所有小于等于deliveryTag 的消息;如果为 false,则只确认当前的 deliveryTag 消息。

示例:

复制代码
channel.basicAck(deliveryTag, false);

这行代码的意思是:"我成功处理了 deliveryTag 指定的消息,请从队列中移除它。"

basicNack:

作用:消费者告诉 RabbitMQ,我无法处理这条消息。

参数:

(1)deliveryTag:消息的唯一标识符。

(2)multiple:布尔值,表示是否拒绝多个消息。如果为 true,则拒绝所有小于等于 deliveryTag 的消息;如果为 false,则只拒绝当前的 deliveryTag 消息。

(3)requeue:布尔值,表示是否将消息重新放回队列。如果为 true,则将消息重新放回队列,等待其他消费者处理;如果为 false,则丢弃这条消息。

示例:

复制代码
channel.basicNack(deliveryTag, false, false);

这行代码的意思是:"我无法处理 deliveryTag 指定的消息,请丢弃它。"

总结

basicAck:确认消息已成功处理,从队列中移除。

basicNack:确认消息处理失败,可以选择重新放回队列或丢弃。

相关推荐
Linux实战笔记1 小时前
记一次消息队列选型对比线上排查:Kafka/RabbitMQ/Pulsar 技术选型
kafka·rabbitmq·高并发·pulsar·消息队列选型
Norris Huang2 小时前
Kafka Connect 与 Flink CDC 写入 Iceberg:Upsert 的代价,以及 S3 Tables 的真正价值
分布式·flink·kafka
张彦峰ZYF2 小时前
一条消息的丢与重:Kafka 与 RabbitMQ 可靠投递的确认、位点、死信与幂等全链路
后端·kafka·消息队列·rabbitmq·isr·解耦与削峰·丢失与重复
我就是不信2 小时前
集群高并发环境下如何保证分布式唯一全局ID生成?
分布式
黄俊懿1 天前
【架构师从入门到进阶】第六章:服务集群优化——第八节:线程数公式
java·服务器·数据库·分布式·性能优化·架构·系统架构
大菠萝爱上小西瓜1 天前
基于 Spark + Iceberg 湖仓一体的 Netflix 流媒体用户行为全链路数仓与 ALS 智能推荐决策系统(含穿透下钻大屏)
大数据·分布式·spark
做个文艺程序员1 天前
MQ第12篇:AI原生消息架构演进:从MQ到事件驱动的全链路设计
架构·消息队列·wpf·事件驱动架构·ai-native·cqrs
吴建旭 智宅焕1 天前
智能家居售后服务体系的架构设计:分布式售后风险的集中式兜底方案
分布式·智能家居
lisanmengmeng1 天前
Ganglia分布式集群监控系统安装手册
分布式
我不会起名字3222 天前
Kafka 分区数到底设几个:吞吐量、顺序性与 rebalance 代价的取舍
kafka·消息队列·调优·分区