JavaEE 企业级分布式高级架构师(十六)RabbitMQ消息中间件(2)

RabbitMQ进阶

概述

  • 如何保证消息的成功投递
  • 幂等性概念
  • 在海量订单产生的业务高峰期,如何避免消息的重复消费
  • Confirm 确认消息 & Return 返回消息
  • 自定义消费者
  • 消息的 ACK 与重回队列
  • 消息的限流
  • TTL 消息
  • 死信队列

如何保证消息的成功投递

什么是生产端的可靠性投递?

  • 保证消息的成功发出
  • 保障 MQ 节点的成功接收
  • 发送端收到 MQ 节点确认应答
  • 完善的消息补偿机制

生产端可靠性投递-常见解决方案

方案一
  • 消息信心落库,对消息转改进行打标
    在这里插入图片描述
  • 疑问:那这种方式在高并发的场景下是否合适?
  • 隐含问题:有两次数据持久化的操作,第一次要保存业务消息,第二次对数据进行记录。数据 IO 磁盘,每次都需要读两次,数据库容易遇到瓶颈。
  • 解决办法:其实我们只需要对业务进行入库即可。
方案二

消息的延迟投递,做二次确认,回调检查:互联网大公司常用的方式,但也不一定能 100% 保证可靠性投递。极端情况,需要人工进行补偿。主要目的:减少数据库的操作。在项目核心链路中,每一次持久化操作,都要精心考量花费时间太多,可能会造成核心链路中最大瓶颈。
在这里插入图片描述

  • 图解:很复杂,但能够最大限度节省我们数据信息落库的操作【蓝色:上游服务(生产端);红色:下游服务(消费端);Callback:回调服务】
步骤

在这里插入图片描述

  • 第一步&第二步:把消息落库完了之后,才能 step 1 进行发送消息。还要记住,互联网大公司不会加任何事务,事务的性能会造成很严重的性能瓶颈。注意:这一次,在生产端它会一次生成两条消息。也就是执行完了 step 1 发送消息后,还会执行 step 2 做消息延迟检查,可以 2~5 分钟之后。
  • 第三步:监听&接收消息之后,就进行处理。
  • 第四步:当消费端中消息处理成功之后,还需生成一个 确认消息 。
  • 第五步:Callback 服务,通过监听器,监听确认消息。当确认了之后,就对消息做最终的存储。
  • 第六步:假设 5 分钟后,延迟投递检查消息,发送过来了。callback 服务,监听这个检查细节,会有单独的监听器监听。然后就去检查 MSG DB 数据库:(1)如果发现刚刚下游已经把数据处理好了,那么就没问题了;(2)如果刚刚没有返回,或者返回失败,出现异常了,这时 callback 需要做补偿,因为 callback 在监听延迟消息,当 callback 发现 message 并不存在,则会主动发起 RPC 通信,给上游反馈延迟检查的内容,并没有找到,然后,再次发送一次数据。
    这么做的目的少做一次 DB 存储,能节省一步,就节省一步,可以进行异步去补偿;在高并发场景中,最应该关注并保证性能,保证能扛得住庞大的订单量。

幂等性

幂等性是什么

我们可以借鉴数据库的乐观锁机制

举例

比如我们执行一条更新库存的 SQL 语句

update tb_pro set count = count - 1, version = version + 1 where version = 1
  • 比如有很多商品,卖了一件,就减 1
  • 如果只剩下一件了,减了 1,就卖完了,没办法继续卖了
  • 如果此时碰上并发,两个请求同时过来,有可能 count 就变成 -1,这肯定是不行的
  • 解决方式,就是加上 version 版本号,还可带上商品 id
  • 像 elsaticsearch 中也是使用了这种严格的幂等性
总结

可能你对一件事情进行操作,这个操作你可能执行非常多次,操作的结果也是相同的,这个就是幂等性保障。

消费端幂等性保障

在海量订单产生的业务高峰期,如何避免消息的重复消费问题
  • 在高并发的情况下,会有很多信息到达 MQ,消费端可能要监听大量的消息,难免会出现消息的重复投递,或者网络闪断,导致 Broker 端重发消息。
  • 消费端实现幂等性,就意味着,我们的消息永远不会消费多次,即使我们收到了多条一样的消息。
  • 有可能代码会执行多次,但数据库只会执行这一步操作。
  • 金融公司,最重视。
业界主流的幂等性操作
唯一 ID + 指纹码机制,利用数据库主键去重
  • 唯一 ID + 指纹码机制,利用数据库主键去重:有些用户就有可能在某一瞬间进行多次消费,比如刚刚转了一笔钱,接着又马上又转了一笔。指纹码,某些业务规则或者生成的信息拼接而成。
  • select count(1) from tb_order where id = 唯一 ID + 指纹码:如果已经有记录,代表已经被操作了。
  • 好处:实现简单。
  • 坏处:高并发下有数据库写入的性能瓶颈。
  • 解决方案:根据 ID 进行分库分表进行算法路由,实现分压分流的机制。
利用 redis 的原子性去实现
  • 使用 redis 进行幂等,需要考虑的问题:比如我们 set 一个key,如果第二次还 set,就会更新为最新值。也可以做一个预先判断,exsit() 操作,存在就不更新了。最简单的自增,也是可以保障的。
  • 我们是否要进行数据落库,如果落库的话,关键解决的问题是数据库和缓存如何做到原子性?
  • 如果不进行落库,那么都存储到缓存中,如何设置定时同步的策略?

Confirm确认消息

理解 Confirm 消息确认机制

  • 消息的确认,是指生产者投递消息后,如果 Broker 收到消息,则会给我们生产者一个应答。
  • 生产者进行接收应答,用来确定这条消息是否正常的发送到 Broker,这种方式也是消息的可靠性投递的核心保障。
  • 确认机制流程图:是异步操作
    在这里插入图片描述

如何实现 Confirm 确认消息

  • 步骤1:在 channel 上开启确认模式,channel.confirmSelect()。
  • 步骤2:在 channel 上添加监听,addConfirmListener。监听成功和失败的返回结果,根据具体的结果对消息进行重新发送、或记录日志等后续处理。
案例demo
  • 生产者
public class ConfirmProducer {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个 ConnectionFactory 工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(RABBITMQ_HOST_IP);
        factory.setPort(RABBITMQ_HOST_PORT);
        factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);

        // 2. 创建一个 Connection 连接
        Connection conn = factory.newConnection();

        // 3. 获取一个 Channel 信道
        Channel channel = conn.createChannel();

        // 4. 指定消息的投递模式:消息的确认模式
        channel.confirmSelect();

        // 5. 发送一条消息
        String msg = "Hello Confirm Message";
        channel.basicPublish(RABBITMQ_CONFIRM_EXCHANGE, RABBITMQ_CONFIRM_ROUTING_KEY_PRODUCER, null, msg.getBytes());

        // 6. 添加一个确认监听器
        channel.addConfirmListener(new ConfirmListener() {
            @Override
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("========收到ACK========");
            }

            @Override
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("========没有ACK========");
            }
        });
    }
}
  • 消费者
public class ConfirmConsumer {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个 ConnectionFactory 工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(RABBITMQ_HOST_IP);
        factory.setPort(RABBITMQ_HOST_PORT);
        factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);

        // 2. 创建一个 Connection 连接
        Connection conn = factory.newConnection();

        // 3. 获取一个 Channel 信道
        Channel channel = conn.createChannel();

        // 4. 声明一个交换机
        channel.exchangeDeclare(RABBITMQ_CONFIRM_EXCHANGE, BuiltinExchangeType.TOPIC, true);

        // 5. 声明&绑定队列
        channel.queueDeclare(RABBITMQ_CONFIRM_QUEUE, true, false, false, null);
        channel.queueBind(RABBITMQ_CONFIRM_QUEUE, RABBITMQ_CONFIRM_EXCHANGE, RABBITMQ_CONFIRM_ROUTING_KEY_CONSUMER);

        // 6. 消费消息
        while (true) {
            boolean autoAck = false;
            String consumerTag = "";
            channel.basicConsume(RABBITMQ_CONFIRM_QUEUE, autoAck, consumerTag, new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    // 获取 routingKey & contentType
                    String routingKey = envelope.getRoutingKey();
                    String contentType = properties.getContentType();
                    Map<String, Object> headers = properties.getHeaders();
                    System.out.println("消费的 Routing Key:" + routingKey + " \n消费的 Content Type:" + contentType);
                    System.out.println("消费的 headers:" + JSON.toJSONString(headers));

                    // 获取传送标签
                    long deliveryTag = envelope.getDeliveryTag();

                    // 确认消息
                    channel.basicAck(deliveryTag, false);

                    System.out.println("消费的 Body:" + new String(body, UTF_8));
                }
            });
        }
    }
}
  • BaseInfo
public interface BaseInfo {
    String RABBITMQ_HOST_IP = "192.168.254.106";
    int RABBITMQ_HOST_PORT = 5672;
    String RABBITMQ_VIRTUAL_HOST = "/";
    String RABBITMQ_CONFIRM_EXCHANGE = "confirm-exchange";
    String RABBITMQ_CONFIRM_ROUTING_KEY_PRODUCER = "confirm.save";
    String RABBITMQ_CONFIRM_ROUTING_KEY_CONSUMER = "confirm.#";
    String RABBITMQ_CONFIRM_QUEUE = "confirm-queue";
}

Return消息机制

  • Return Listener 用于处理一些不可路由的消息。
  • 我们的消息生产者,通过指定一个 Exchange 和 Routing Key,把消息送达某一个队列中去,然后我们的消费者监听队列,进行消费处理操作。
  • 但是在某些情况下,如果我们在发送消息的时候,当前的 Exchange 不存在或者指定的路由 Key 路由不到,这个时候如果我们需要监听这种不可达的消息,就要使用 Return Listener。
  • 在基础 API 中有一个关键的配置项 Mandatory:如果为 true,则监听器会接收到路由不可达的消息,然后进行后续处理。如果为 false,那么 broker 端自动删除该消息。
  • 图示:
    在这里插入图片描述

案例demo

  • 生产者
public class ReturnProducer {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个 ConnectionFactory 工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(RABBITMQ_HOST_IP);
        factory.setPort(RABBITMQ_HOST_PORT);
        factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);

        // 2. 创建一个 Connection 连接
        Connection conn = factory.newConnection();

        // 3. 获取一个 Channel 信道
        Channel channel = conn.createChannel();

        // 4. 发送一条消息
        String msg = "Hello Return Message";
        boolean mandatory = true;

        // 5. 添加一个return监听器
        channel.addReturnListener(new ReturnListener() {
            @Override
            public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.err.println("========return 机制========");
                System.err.println("replyCode : " + replyCode);
                System.err.println("replyText : " + replyText);
                System.err.println("exchange : " + exchange);
                System.err.println("routingKey : " + routingKey);
                System.err.println("properties : " + properties);
                System.err.println("body : " + new String(body));
            }
        });
//        channel.basicPublish(RABBITMQ_RETURN_EXCHANGE, RABBITMQ_RETURN_ROUTING_KEY_PRODUCER, mandatory,null, msg.getBytes());
        channel.basicPublish(RABBITMQ_RETURN_EXCHANGE, RABBITMQ_RETURN_ROUTING_KEY_ERROR, mandatory, null, msg.getBytes());
    }
}
  • 消费者
public class ReturnConsumer {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个 ConnectionFactory 工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(RABBITMQ_HOST_IP);
        factory.setPort(RABBITMQ_HOST_PORT);
        factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);

        // 2. 创建一个 Connection 连接
        Connection conn = factory.newConnection();

        // 3. 获取一个 Channel 信道
        Channel channel = conn.createChannel();

        // 4. 声明一个交换机
        channel.exchangeDeclare(RABBITMQ_RETURN_EXCHANGE, BuiltinExchangeType.TOPIC, true, false, null);

        // 5. 声明&绑定队列
        channel.queueDeclare(RABBITMQ_RETURN_QUEUE, true, false, false, null);
        channel.queueBind(RABBITMQ_RETURN_QUEUE, RABBITMQ_RETURN_EXCHANGE, RABBITMQ_RETURN_ROUTING_KEY_CONSUMER);

        // 6. 消费消息
        while (true) {
            boolean autoAck = false;
            String consumerTag = "";
            channel.basicConsume(RABBITMQ_RETURN_QUEUE, autoAck, consumerTag, new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    // 获取 routingKey & contentType
                    String routingKey = envelope.getRoutingKey();
                    String contentType = properties.getContentType();
                    Map<String, Object> headers = properties.getHeaders();
                    System.out.println("消费的 Routing Key:" + routingKey + " \n消费的 Content Type:" + contentType);
                    System.out.println("消费的 headers:" + JSON.toJSONString(headers));

                    // 获取传送标签
                    long deliveryTag = envelope.getDeliveryTag();

                    // 确认消息
                    channel.basicAck(deliveryTag, false);

                    System.out.println("消费的 Body:" + new String(body, UTF_8));
                }
            });
        }
    }
}
  • BaseInfo
public interface BaseInfo {
    String RABBITMQ_HOST_IP = "192.168.254.106";
    int RABBITMQ_HOST_PORT = 5672;
    String RABBITMQ_VIRTUAL_HOST = "/";
    String RABBITMQ_RETURN_EXCHANGE = "return-exchange";
    String RABBITMQ_RETURN_ROUTING_KEY_PRODUCER = "return.save";
    String RABBITMQ_RETURN_ROUTING_KEY_ERROR = "error.save";
    String RABBITMQ_RETURN_ROUTING_KEY_CONSUMER = "return.#";
    String RABBITMQ_RETURN_QUEUE = "return-queue";
}

消费端自定义

  • 我们之前都是在代码中编写 while 循环,进行 consumer.nextDelivery() 方法获取下一条消息,然后进行消费处理。
  • 但其实我们还可以使用自定义的 Cusumer,它更加方便,解耦性更强,也是在实际工作中最常使用的方式
  • 自定义消费端的实现只需要继承 DefaultConsumer 类重写 handleDelivery 方法即可。

案例demo

  • 生产者
public class CustomProducer {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个 ConnectionFactory 工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(RABBITMQ_HOST_IP);
        factory.setPort(RABBITMQ_HOST_PORT);
        factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);

        // 2. 创建一个 Connection 连接
        Connection conn = factory.newConnection();

        // 3. 获取一个 Channel 信道
        Channel channel = conn.createChannel();

        // 4. 发送一条消息
        String msg = "Hello Custom Message";
        channel.basicPublish(RABBITMQ_CUSTOM_EXCHANGE, RABBITMQ_CUSTOM_ROUTING_KEY_PRODUCER, true, null, msg.getBytes());
    }
}
  • 自定义消费处理
public class MyConsumer extends DefaultConsumer {
    public MyConsumer(Channel channel) {
        super(channel);
    }

    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        // consumerTag: 内部生成的消费标签  properties: 消息属性  body: 消息内容
        System.err.println("-----------consume message----------");
        System.err.println("consumerTag: " + consumerTag);
        // envelope包含属性:deliveryTag(标签), redeliver, exchange, routingKey
        // redeliver是一个标记,如果设为true,表示消息之前可能已经投递过了,现在是重新投递消息到监听队列的消费者
        System.err.println("envelope: " + envelope);
        System.err.println("properties: " + properties);
        System.err.println("body: " + new String(body));
    }
}
  • 消费者
public class CustomConsumer {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个 ConnectionFactory 工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(RABBITMQ_HOST_IP);
        factory.setPort(RABBITMQ_HOST_PORT);
        factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);

        // 2. 创建一个 Connection 连接
        Connection conn = factory.newConnection();

        // 3. 获取一个 Channel 信道
        Channel channel = conn.createChannel();

        // 4. 声明一个交换机
        channel.exchangeDeclare(RABBITMQ_CUSTOM_EXCHANGE, BuiltinExchangeType.TOPIC, true, false, null);

        // 5. 声明&绑定队列
        channel.queueDeclare(RABBITMQ_CUSTOM_QUEUE, true, false, false, null);
        channel.queueBind(RABBITMQ_CUSTOM_QUEUE, RABBITMQ_CUSTOM_EXCHANGE, RABBITMQ_CUSTOM_ROUTING_KEY_CONSUMER);

        // 6. 设置channel,使用自定义消费者
        channel.basicConsume(RABBITMQ_CUSTOM_QUEUE, true, new MyConsumer(channel));
    }
}
  • BaseInfo
public interface BaseInfo {
    String RABBITMQ_HOST_IP = "192.168.254.106";
    int RABBITMQ_HOST_PORT = 5672;
    String RABBITMQ_VIRTUAL_HOST = "/";
    String RABBITMQ_CUSTOM_EXCHANGE = "custom-exchange";
    String RABBITMQ_CUSTOM_ROUTING_KEY_PRODUCER = "custom.save";
    String RABBITMQ_CUSTOM_ROUTING_KEY_CONSUMER = "custom.#";
    String RABBITMQ_CUSTOM_QUEUE = "custom-queue";
}

消费端限流

什么是消费端限流

  • 假设一个场景:首先,我们RabbitMQ 服务器上有上万条未处理的消息,我们随便打开一个消费者客户端,会出现下面的情况:巨大量的消息瞬间全部推送过来,但是我们单个客户端无法同时处理这么多数据。
  • RabbitMQ 提供了一种 qos(服务质量保证)功能,即在非自动确认消息的前提下,如果一定数目的消息(通过基于 consumer 或者 channel 设置 Qos 的值)未被确认前,不进行消费新的消息。
void BasicQos(uint prefetchSize, ushort prefetchCount, bool global);
  • prefetchSize:0,预先处理多少条
  • prefetchCount:会告诉 RabbitMQ 不要同时给一个消费者推送多于 N 个消息,即一旦有 N 个消息还没有 ack,则该 consumer 将 block 掉,直到有消息 ack
  • global:true / false 是否将上面设置应用于 channel,简单说,就是上面限制是 channel 级别的还是 consumer 级别。

prefetchSize 和 global 这两项,rabbimq 没有实现,暂且不研究。prefetch_count 在 no_ask = false 的情况下生效,即在自动应答的情况下这两个值是不生效的。

案例demo

  • 生产者
public class LimitProducer {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个 ConnectionFactory 工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(RABBITMQ_HOST_IP);
        factory.setPort(RABBITMQ_HOST_PORT);
        factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);

        // 2. 创建一个 Connection 连接
        Connection conn = factory.newConnection();

        // 3. 获取一个 Channel 信道
        Channel channel = conn.createChannel();

        // 4. 发送消息
        String msg = "hello rabbitmq qos message";
        for (int i = 0; i < 6; i++) {
            channel.basicPublish(RABBITMQ_LIMIT_EXCHANGE, RABBITMQ_LIMIT_ROUTING_KEY_PRODUCER, true, null, msg.getBytes());
        }
    }
}
  • 自定义消费处理
public class MyConsumer extends DefaultConsumer {
    private Channel channel;

    public MyConsumer(Channel channel) {
        super(channel);
        this.channel = channel;
    }

    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        // consumerTag: 内部生成的消费标签  properties: 消息属性  body: 消息内容
        System.err.println("-----------consume message----------");
        System.err.println("consumerTag: " + consumerTag);
        // envelope包含属性:deliveryTag(标签), redeliver, exchange, routingKey
        // redeliver是一个标记,如果设为true,表示消息之前可能已经投递过了,现在是重新投递消息到监听队列的消费者
        System.err.println("envelope: " + envelope);
        System.err.println("properties: " + properties);
        System.err.println("body: " + new String(body));

        // 第二个参数设置为false,因为消费者设置的 prefetchCount = 1
//        channel.basicAck(envelope.getDeliveryTag(), false);
    }
}
  • 消费者
public class LimitConsumer {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个 ConnectionFactory 工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(RABBITMQ_HOST_IP);
        factory.setPort(RABBITMQ_HOST_PORT);
        factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);

        // 2. 创建一个 Connection 连接
        Connection conn = factory.newConnection();

        // 3. 获取一个 Channel 信道
        Channel channel = conn.createChannel();

        // 4. 声明一个交换机
        channel.exchangeDeclare(RABBITMQ_LIMIT_EXCHANGE, BuiltinExchangeType.TOPIC, true, false, null);

        // 5. 声明&绑定队列
        channel.queueDeclare(RABBITMQ_LIMIT_QUEUE, true, false, false, null);
        channel.queueBind(RABBITMQ_LIMIT_QUEUE, RABBITMQ_LIMIT_EXCHANGE, RABBITMQ_LIMIT_ROUTING_KEY_CONSUMER);

        // 6. 限流设置
        // 参数一 prefetchSize: 消息的大小不做任何限制
        // 参数二 prefetchCount: 服务器给的最大的消息数,这里是一条一条的消费
        // 参数三 global: 级别为consumer
        // prefetchCount 为 1, 一次消费一条消息,如果消费者没有确认消费,将不会接受生产者给的消息
        channel.basicQos(0, 1, false);

        // 7. 设置channel,使用自定义消费者
        // 消费者要想做限流必须将自动ack设置为false
        channel.basicConsume(RABBITMQ_LIMIT_QUEUE, false, new MyConsumer(channel));
    }
}
  • BaseInfo
public interface BaseInfo {
    String RABBITMQ_HOST_IP = "192.168.254.106";
    int RABBITMQ_HOST_PORT = 5672;
    String RABBITMQ_VIRTUAL_HOST = "/";
    String RABBITMQ_LIMIT_EXCHANGE = "limit-exchange";
    String RABBITMQ_LIMIT_ROUTING_KEY_PRODUCER = "limit.key";
    String RABBITMQ_LIMIT_ROUTING_KEY_CONSUMER = "limit.#";
    String RABBITMQ_LIMIT_QUEUE = "limit-queue";
}
  • 测试一:将消费端的确认给注释以后的结果
    在这里插入图片描述
  • 测试二:将代码放开结果演示
    在这里插入图片描述
    在这里插入图片描述

消费端ack与重回队列

消费端的手工 ack 和 nack

  • 消费端进行消费的时候,如果由于业务异常我们可以进行日志的记录,然后进行补偿。
  • 如果由于服务器宕机等严重问题,那我们就需要手工进行 ack 保障消费端消费成功。

消费端的重回队列

  • 消费端重回队列是为了对没有处理成功的消息,把消息重新传回给 Broker。
  • 一般我们在实际应用中,都会关闭重回队列,也就是设置为 False。

案例demo

  • 生产者
public class AckProducer {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个 ConnectionFactory 工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(RABBITMQ_HOST_IP);
        factory.setPort(RABBITMQ_HOST_PORT);
        factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);

        // 2. 创建一个 Connection 连接
        Connection conn = factory.newConnection();

        // 3. 获取一个 Channel 信道
        Channel channel = conn.createChannel();

        // 4. 发送消息
        String msg = "hello rabbitmq consumer ack";
        for (int i = 0; i < 6; i++) {
            HashMap<String, Object> headers = new HashMap<>();
            headers.put("num", i);
            AMQP.BasicProperties props = new AMQP.BasicProperties().builder()
                    .deliveryMode(2)    // 常用模式【2: 持久化投递】
                    .contentEncoding("UTF-8")
                    .headers(headers).build();
            channel.basicPublish(RABBITMQ_ACK_EXCHANGE, RABBITMQ_ACK_ROUTING_KEY_PRODUCER, true, props, msg.getBytes());
        }
    }
}
  • 消费者
public class AckConsumer {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个 ConnectionFactory 工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(RABBITMQ_HOST_IP);
        factory.setPort(RABBITMQ_HOST_PORT);
        factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);

        // 2. 创建一个 Connection 连接
        Connection conn = factory.newConnection();

        // 3. 获取一个 Channel 信道
        Channel channel = conn.createChannel();

        // 4. 声明一个交换机
        channel.exchangeDeclare(RABBITMQ_ACK_EXCHANGE, BuiltinExchangeType.TOPIC, true, false, null);

        // 5. 声明&绑定队列
        channel.queueDeclare(RABBITMQ_ACK_QUEUE, true, false, false, null);
        channel.queueBind(RABBITMQ_ACK_QUEUE, RABBITMQ_ACK_EXCHANGE, RABBITMQ_ACK_ROUTING_KEY_CONSUMER);

        // 6. 手工签收。必须要关闭 autoAck =false
        channel.basicConsume(RABBITMQ_ACK_QUEUE, false, new MyConsumer(channel));
    }
}
  • 自定义消费处理
public class MyConsumer extends DefaultConsumer {
    private Channel channel;

    public MyConsumer(Channel channel) {
        super(channel);
        this.channel = channel;
    }

    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        // consumerTag: 内部生成的消费标签  properties: 消息属性  body: 消息内容
        System.err.println("-----------consume message----------");
        System.err.println("consumerTag: " + consumerTag);
        // envelope包含属性:deliveryTag(标签), redeliver, exchange, routingKey
        // redeliver是一个标记,如果设为true,表示消息之前可能已经投递过了,现在是重新投递消息到监听队列的消费者
        System.err.println("envelope: " + envelope);
        System.err.println("properties: " + properties);
        System.err.println("body: " + new String(body));
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (0 == (Integer) properties.getHeaders().get("num")) {
            // requeue  为true 表示重新回到队列尾部
            channel.basicNack(envelope.getDeliveryTag(), false, true);
        } else {
            channel.basicAck(envelope.getDeliveryTag(), false);
        }

    }
}
  • BaseInfo
public interface BaseInfo {
    String RABBITMQ_HOST_IP = "192.168.254.106";
    int RABBITMQ_HOST_PORT = 5672;
    String RABBITMQ_VIRTUAL_HOST = "/";
    String RABBITMQ_ACK_EXCHANGE = "ack-exchange";
    String RABBITMQ_ACK_ROUTING_KEY_PRODUCER = "ack.key";
    String RABBITMQ_ACK_ROUTING_KEY_CONSUMER = "ack.#";
    String RABBITMQ_ACK_QUEUE = "ack-queue";
}
  • 运行测试
    在这里插入图片描述
智能网联汽车的安全员高级考试涉及多个方面的专业知识,包括但不限于自动驾驶技术原理、车辆传感器融合、网络安全防护以及法律法规等内容。以下是针对该主题的一些核心知识解析: ### 关于智能网联车安全员高级考试的核心内容 #### 1. 自动驾驶分级标准 国际自动机工程师学会(SAE International)定义了六个级别的自动驾驶等级,从L0到L5[^1]。其中,L3及以上级别需要安全员具备更高的应急处理能力。 #### 2. 车辆感知系统的组成与功能 智能网联车通常配备多种传感器,如激光雷达、毫米波雷达、摄像头和超声波传感器等。这些设备协同工作以实现环境感知、障碍物检测等功能[^2]。 #### 3. 数据通信与网络安全 智能网联车依赖V2X(Vehicle-to-Everything)技术进行数据交换,在此过程中需防范潜在的网络攻击风险,例如中间人攻击或恶意软件入侵[^3]。 #### 4. 法律法规要求 不同国家和地区对于无人驾驶测试及运营有着严格的规定,考生应熟悉当地交通法典中有关自动化驾驶部分的具体条款[^4]。 ```python # 示例代码:模拟简单决策逻辑 def decide_action(sensor_data): if sensor_data['obstacle'] and not sensor_data['emergency']: return 'slow_down' elif sensor_data['pedestrian_crossing']: return 'stop_and_yield' else: return 'continue_driving' example_input = {'obstacle': True, 'emergency': False, 'pedestrian_crossing': False} action = decide_action(example_input) print(f"Action to take: {action}") ``` 需要注意的是,“同学”作为特定平台上的学习资源名称,并不提供官方认证的标准答案集;建议通过正规渠道获取教材并参加培训课程来准备此类资格认证考试
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

讲文明的喜羊羊拒绝pua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值