【技术派后端篇】一文读懂 RabbitMQ:消息队列模式、原理与优化实践

1. 消息队列概述

1.1 消息队列模式

消息队列主要有两种模式:点对点模式发布/订阅模式

1.1.1 点对点模式

  • 特点:一个消息只能由一个消费者消费。多个生产者可以向同一个队列发送消息,但消息在被一个消费者处理时会被锁住或移除,其他消费者无法处理该消息。
  • 失败处理:如果消费者处理消息失败,消息系统通常会将消息放回队列,供其他消费者继续处理。

1.1.2 发布/订阅模式

  • 特点:单个消息可以被多个订阅者并发获取和处理。
  • 订阅类型
    • 临时订阅:消费者启动时存在,退出后订阅和未处理的消息会丢失。
    • 持久订阅:订阅会一直存在,除非主动删除。消费者退出后,消息系统会继续维护该订阅,后续消息可继续处理。

1.2 RabbitMQ 特征

  • 消息路由:支持通过不同交换器进行消息路由。
  • 消息有序:不支持。消费失败时,消息会被放回队列,可能导致消息无序。
  • 消息时序:非常好。支持延时队列、TTL等。
  • 容错处理:非常好。通过重试和死信交换器(DLX)处理消息故障。
  • 伸缩性:一般。由于只有一个主队列,负载集中,伸缩性较弱。
  • 持久化:不太好。未消费的消息支持持久化,但消费后的消息会被删除。
  • 消息回溯:不支持。消息不支持永久保存。
  • 高吞吐:中等。单机性能达不到十万级标准。

2. RabbitMQ 原理

2.1 基本概念

RabbitMQ 是基于 AMQP 协议的消息队列系统,使用 Erlang 语言开发。以下是 AMQP 协议中的关键概念:

  • Server:接收客户端连接,实现 AMQP 实体服务。
  • Connection:应用程序与 Server 的网络连接(TCP 连接)。
  • Channel:信道,消息读写等操作在信道中进行。客户端可建立多个信道。
  • Message:消息,由 Properties 和 Body 组成。
  • Virtual Host:虚拟主机,用于逻辑隔离。
  • Exchange:交换器,接收消息并根据路由规则将消息路由到队列。
  • Binding:交换器和队列之间的虚拟连接。
  • RoutingKey:路由键,用于指定路由规则。
  • Queue:消息队列,保存消息供消费者消费。

2.2 工作原理

AMQP 协议模型由生产者、消费者和服务端组成,工作流程如下:

  1. 生产者连接到 Server,建立连接并开启信道。
  2. 生产者声明交换器和队列,设置属性并通过路由键绑定交换器和队列。
  3. 消费者建立连接并开启信道以接收消息。
  4. 生产者发送消息到虚拟主机中的交换器。
  5. 交换器根据路由键将消息路由到队列。
  6. 消费者从队列中获取消息并消费。
    在这里插入图片描述

2.3 常用交换器

  • Direct Exchange:直连交换机,要求消息与特定路由键完全匹配,实现一对一的点对点发送。

在这里插入图片描述

  • Fanout Exchange:将消息转发到与该交换机绑定的所有队列,类似子网广播,实现发布订阅功能。

在这里插入图片描述

  • Topic Exchange:主题交换机,使用通配符(“*” 匹配一个词,“#” 匹配一个或多个词)进行路由匹配。

在这里插入图片描述

  • Headers Exchange:根据请求头中携带的键值进行路由,使用相对较少,创建队列需设置绑定的头部信息,有全部匹配和部分匹配两种模式。
    在这里插入图片描述

3. RabbitMQ 环境搭建

3.1 Windows版

  1. 下载安装 Erlang
  • 访问Erlang 官方下载页面,根据系统选择合适的版本,如 64 位系统可下载otp_win64_XX.YY.exe
    在这里插入图片描述

  • 运行安装程序,安装路径可自行选择,但不要出现中文或空格。安装完成后,右键点击 “此电脑”,选择 “属性”,进入 “高级系统设置”,点击 “环境变量”。在 “系统变量” 中新建变量,变量名设为 “ERLANG_HOME”,变量值为 Erlang 的安装地址。然后找到 “Path” 变量,点击 “编辑”,新建一个路径,输入 “%ERLANG_HOME%\bin”。

  • 按 “Win+R” 键,输入 “cmd”,打开命令提示符,输入 “erl”,若显示版本信息,则说明 Erlang 安装成功。
    在这里插入图片描述

  1. 安装 RabbitMQ
  • 前往RabbitMQ 官方下载页面,选择适合 Windows 的版本,如rabbitmq-server-4.0.8.exe
    在这里插入图片描述

  • 运行安装程序,选择安装路径,建议不要使用中文或空格路径,然后一路 “Next” 完成安装。

  1. 配置 RabbitMQ
  • 打开命令提示符,进入 RabbitMQ 的sbin文件夹,例如 “D:\rabbitMQ\rabbitmq_server-4.0.6\sbin”。输入命令rabbitmq-plugins enable rabbitmq_management来启用管理插件。
    在这里插入图片描述

  • 在与sbin文件夹同级目录下创建data文件夹。以管理员身份打开 “RabbitMQ Command Prompt”,依次输入以下命令:

    • rabbitmq-service.bat remove
    • set RABBITMQ_BASE=D:\rabbitMQ\rabbitmq_server-4.0.8\data(根据实际安装路径修改)
    • rabbitmq-service.bat install
  • 再次执行rabbitmq - plugins enable rabbitmq_management命令。

  1. 启动 RabbitMQ:在命令提示符中输入rabbitmq-server.bat start启动 RabbitMQ。也可以通过 “计算机管理” 中的 “服务” 找到 “RabbitMQ” 服务并启动。
  2. 访问管理界面:打开浏览器,输入 “http://localhost:15672”,使用默认账号 “guest” 和密码 “guest” 登录,即可进入 RabbitMQ 的管理界面。
    在这里插入图片描述
    在这里插入图片描述
  3. 创建管理员账户
  • 添加账号, 进入rabbitmq的sbin目录下

  • 添加一个账号密码均为admin且身份为超级管理员的用户

    rabbitmqctl.bat add_user admin admin
    rabbitmqctl.bat set_permissions -p "/" admin ".*" ".*" ".*"
    rabbitmqctl.bat set_user_tags admin administrator
    
  • 查看当前用户组有哪些, 结果如图

    rabbitmqctl.bat list_users
    

    在这里插入图片描述

3.2 Ubuntu版

在 Ubuntu 上安装 RabbitMQ 需先配置 Erlang 环境(RabbitMQ 基于 Erlang 开发),再安装 RabbitMQ 本身,以下是详细步骤:

  1. 更新软件包列表

    sudo apt-get update
    
  2. 安装 Erlang 环境

    sudo apt-get install erlang
    

    输入 Y 确认安装,等待安装完成。可通过 erl 命令验证(输入 halt(). 退出 Erlang 命令行)。
    在这里插入图片描述

  3. 安装 RabbitMQ 服务器

    sudo apt-get install rabbitmq-server
    

    同样输入 Y 确认,安装后服务会自动启动。可通过 systemctl status rabbitmq-server 检查状态。
    在这里插入图片描述

  4. 启用 RabbitMQ 管理界面

    rabbitmq-plugins enable rabbitmq_management
    

    在这里插入图片描述

    管理界面默认端口为 15672,若使用云服务器需在安全组开放该端口(同时开放消息通信端口 5672)。
    在这里插入图片描述

  5. 创建管理员用户(避免 guest 权限限制)

    # 添加用户(账号密码自定义,示例为 admin/admin)
    sudo rabbitmqctl add_user admin admin
    # 设置用户角色为超级管理员
    sudo rabbitmqctl set_user_tags admin administrator
    # 赋予用户对所有资源的完全权限
    sudo rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"
    

    在这里插入图片描述

  6. 访问管理界面
    在浏览器输入 http://服务器IP:15672,使用新创建的账号(如 admin)登录,即可管理 RabbitMQ。
    在这里插入图片描述

  7. 常用命令

    • 启动服务:sudo service rabbitmq-server start
    • 停止服务:sudo service rabbitmq-server stop
    • 重启服务:sudo service rabbitmq-server restart
    • 查看状态:systemctl status rabbitmq-server

4. RabbitMQ 集成

代码仓库https://github.com/itwanger/paicoding
代码分支feature/add_rabbitmq_20230506

4.1 前置工作

  • 引入依赖
<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.5.1</version>
</dependency>
  • 修改配置
rabbitmq:
  host: 127.0.0.1
  port: 5672 # 客户端默认端口:5672
  username: admin
  passport: admin
  virtualhost: /
  switch_flag: true # 默认为false,如果需要运行RabbitMQ,改为true
  • 在RabbitMQ后台手动添加一个Exchange
    在这里插入图片描述

4.2 代码实现

  • RabbitmqUtil:用于管理ConnectionFactory单例
public class RabbitmqUtil {
    // 用于存储每个key对应的连接工厂,保证每个key有自己独立的工厂实例
    private static Map<String, ConnectionFactory> executors = new ConcurrentHashMap<>();

    /**
     * 初始化一个连接工厂
     * @param host RabbitMQ服务器主机地址
     * @param port RabbitMQ服务器端口
     * @param username 用户名
     * @param passport 密码
     * @param virtualhost 虚拟主机名
     * @return 初始化好的ConnectionFactory对象
     */
    public static ConnectionFactory init(String host,
                                         Integer port,
                                         String username,
                                         String passport,
                                         String virtualhost) {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(host);
        factory.setPort(port);
        factory.setUsername(username);
        factory.setPassword(passport);
        factory.setVirtualHost(virtualhost);
        return factory;
    }

    /**
     * 获取或初始化连接工厂,如果不存在则创建并放入executors中
     * @param key 用于标识连接工厂的key
     * @param host RabbitMQ服务器主机地址
     * @param port RabbitMQ服务器端口
     * @param username 用户名
     * @param passport 密码
     * @param virtualhost 虚拟主机名
     * @return 对应的ConnectionFactory对象
     */
    public static ConnectionFactory getOrInitConnectionFactory(String key,
                                                               String host,
                                                               Integer port,
                                                               String username,
                                                               String passport,
                                                               String virtualhost) {
        ConnectionFactory connectionFactory = executors.get(key);
        if (null == connectionFactory) {
            synchronized (RabbitmqUtil.class) {
                connectionFactory = executors.get(key);
                if (null == connectionFactory) {
                    connectionFactory = init(host, port, username, passport, virtualhost);
                    executors.put(key, connectionFactory);
                }
            }
        }
        return connectionFactory;
    }
}
  • RabbitmqClient:包含获取连接工厂方法
/**
 * @author Louzai
 * @date 2023/5/10
 * 该类用于与RabbitMQ交互,实现消息的发送和消费
 */
@Component
public class RabbitmqClient {
    @Autowired
    private RabbitmqProperties rabbitmqProperties;

    /**
     * 创建一个连接工厂
     * @param key 用于标识连接工厂的key
     * @return 对应的ConnectionFactory对象
     */
    public ConnectionFactory getConnectionFactory(String key) {
        String host = rabbitmqProperties.getHost();
        Integer port = rabbitmqProperties.getPort();
        String userName = rabbitmqProperties.getUsername();
        String password = rabbitmqProperties.getPassport();
        String virtualhost = rabbitmqProperties.getVirtualhost();
        return RabbitmqUtil.getOrInitConnectionFactory(key, host, port, userName, password, virtualhost);
    }

    
}
  • RabbitmqServiceImpl:包含消息发送和消费的方法
@Component
public class RabbitmqServiceImpl implements RabbitmqService {
    @Autowired
    private RabbitmqClient rabbitmqClient;

	@Autowired
    private NotifyService notifyService;

 	/**
     * 发送消息到RabbitMQ
     * @param exchange 交换机名称
     * @param toutingKey 路由键
     * @param message 要发送的消息内容
     * @throws IOException 处理IO异常
     * @throws TimeoutException 处理连接超时异常
     */
     @Override
    public void publishMsg(String exchange,
    					   BuiltinExchangeType exchangeType,
                           String toutingKey,
                           String message) throws IOException, TimeoutException {
        ConnectionFactory factory = getConnectionFactory(toutingKey);
        // 创建连接
        Connection connection = factory.newConnection();
        // 创建消息信道
        Channel channel = connection.createChannel();
        // 声明交换机
        channel.exchangeDeclare(exchange, exchangeType, true, false, null);
        // 发布消息
        channel.basicPublish(exchange, toutingKey, null, message.getBytes());
        System.out.println("Publish msg:" + message);
        // 关闭信道和连接
        channel.close();
        connection.close();
    }

    /**
     * 消费RabbitMQ消息
     * @param exchange 交换机名称
     * @param queue 队列名称
     * @param routingKey 路由键
     * @throws IOException 处理IO异常
     * @throws TimeoutException 处理连接超时异常
     */
      @Override
    public void consumerMsg(String exchange,
                            String queue,
                            String routingKey) throws IOException, TimeoutException {
        ConnectionFactory factory = getConnectionFactory(routingKey);
        // 创建连接
        Connection connection = factory.newConnection();
        // 创建消息信道
        Channel channel = connection.createChannel();
        // 声明队列
        channel.queueDeclare(queue, true, false, false, null);
        // 绑定队列到交换机
        channel.queueBind(queue, exchange, routingKey);
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println("Consumer msg:" + message);
                // 获取Rabbitmq消息,并保存到DB
                // 说明:这里仅作为示例,如果有多种类型的消息,可以根据消息判定,简单的用if...else处理,复杂的用工厂+策略模式
                // 此处notifyService假设为处理消息业务逻辑的服务,实际需根据项目情况实现
                notifyService.saveArticleNotify(JsonUtil.toObj(message, UserFootDO.class), NotifyTypeEnum.PRAISE);
                // 手动确认消息已消费
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        // 取消自动ack,改为手动ack
        channel.basicConsume(queue, false, consumer);
    }

    /**
     * 非阻塞模式消费RabbitMQ消息(目前实现方式较简单粗暴,后续需改进)
     */
      @Override
    public void processConsumerMsg() {
        System.out.println("Begin to processConsumerMsg.");
        Integer stepTotal = 1;
        Integer step = 0;
        // 目前是简单的while循环消费,后续需优化为阻塞I/O模式
        while (true) {
            step++;
            try {
                System.out.println("processConsumerMsg cycle.");
                consumerMsg(CommonConstants.EXCHANGE_NAME_DIRECT, CommonConstants.QUERE_NAME_PRAISE,
                        CommonConstants.QUERE_KEY_PRAISE);
                if (step.equals(stepTotal)) {
                    try {
                        Thread.sleep(10000);
                        step = 0;
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

4.3 调用入口

  • 点赞消息走 RabbitMQ
// 点赞消息走RabbitMQ,其它走Java内置消息机制
if (notifyType.equals(NotifyTypeEnum.PRAISE) && rabbitmqProperties.getSwitchFlag()) {
    rabbitmqService.publishMsg(
            CommonConstants.EXCHANGE_NAME_DIRECT,
            BuiltinExchangeType.DIRECT,
            CommonConstants.QUERE_KEY_PRAISE,
            JsonUtil.toStr(foot));
} else {
    Optional.ofNullable(notifyType).ifPresent(notify -> SpringUtil.publishEvent(new NotifyMsgEvent<>(this, notify, foot)));
}
  • 启动时消费消息
@Override
public void run(ApplicationArguments args) {
    // 设置类型转换, 主要用于mybatis读取varchar/json类型数据据,并写入到json格式的实体Entity中
    JacksonTypeHandler.setObjectMapper(new ObjectMapper());
    // 应用启动之后执行
    GlobalViewConfig config = SpringUtil.getBean(GlobalViewConfig.class);
    if (webPort != null) {
        config.setHost("http://127.0.0.1:" + webPort);
    }
    // 启动RabbitMQ进行消费
    if (rabbitmqProperties.getSwitchFlag()) {
        taskExecutor.execute(() -> rabbitmqService.processConsumerMsg());
    }
    log.info("启动成功,点击进入首页: {}", config.getHost());
}

4.4 实际效果

  • 对文章多次点赞,触发RabbitMQ发送消息:
    在这里插入图片描述
  • 通过控制台打印的日志,查看发送和消费的消息
    在这里插入图片描述

4.5 存在问题

查看RabbitMQ后台,会发现大量未关闭的connections和channel,会占用大量内存。
在这里插入图片描述

在这里插入图片描述
如何解决呢?答案是使用连接池。

以下是整理出的问题:

  • Connection 方面:未添加连接池,存在内存持续消耗风险,可能导致机器性能受影响。
  • RabbitMQ 消费方式方面:现有的 while + sleep 消费方式简单粗暴,需要改造。
  • RabbitMQ 任务处理方面:缺少消费任务挂掉后的重启消费机制。
  • 机器故障应对方面:机器重启后,要保证 RabbitMQ 内部消息不丢失。

5. 改进版本:RabbitMQ连接池

5.1 前置工作

  • 修改配置

    rabbitmq:
      host: 127.0.0.1
      port: 5672
      username: admin
      passport: admin
      virtualhost: /
      switch_flag: true
      pool_size: 5 # 连接池大小为5
    

5.2 加入连接池

之前我们给 ConnectionFactory 加了个单例工厂,不过由于现在有了连接池,这个单例工厂就可以废弃了。

  • RabbitMQ 连接类
public class RabbitmqConnection {

    private Connection connection;

    public RabbitmqConnection(String host, int port, String userName, String password, String virtualhost) {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(host);
        connectionFactory.setPort(port);
        connectionFactory.setUsername(userName);
        connectionFactory.setPassword(password);
        connectionFactory.setVirtualHost(virtualhost);
        try {
            connection = connectionFactory.newConnection();
        } catch (IOException | TimeoutException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取链接
     *
     * @return
     */
    public Connection getConnection() {
        return connection;
    }

    /**
     * 关闭链接
     *
     */
    public void close() {
        try {
            connection.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • RabbitMQ 连接池类
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class RabbitmqConnectionPool {
    private static BlockingQueue<RabbitmqConnection> pool;

    public static void initRabbitmqConnectionPool(String host, int port, String userName, String password,
                                                  String virtualhost, Integer poolSize) {
        pool = new LinkedBlockingQueue<>(poolSize);
        for (int i = 0; i < poolSize; i++) {
            pool.add(new RabbitmqConnection(host, port, userName, password, virtualhost));
        }
    }

    public static RabbitmqConnection getConnection() throws InterruptedException {
        return pool.take();
    }

    public static void returnConnection(RabbitmqConnection connection) {
        pool.add(connection);
    }

    public static void close() {
        pool.forEach(RabbitmqConnection::close);
    }
}

5.3 RabbitMQ 发送、消费消息

  • RabbitMQ 发送消息的流程为:

从连接池拿到连接 -> 创建通道 -> 声明交换机 -> 发送消息 -> 将连接归还连接池

以下是发送消息的代码:

@Override
public void publishMsg(String exchange,
                       BuiltinExchangeType exchangeType,
                       String toutingKey,
                       String message) {
    try {
        //创建连接
        RabbitmqConnection rabbitmqConnection = RabbitmqConnectionPool.getConnection();
        Connection connection = rabbitmqConnection.getConnection();
        //创建消息通道
        Channel channel = connection.createChannel();
        // 声明exchange中的消息为可持久化,不自动删除
        channel.exchangeDeclare(exchange, exchangeType, true, false, null);
        // 发布消息
        channel.basicPublish(exchange, toutingKey, null, message.getBytes());
        System.out.println("Publish msg:" + message);
        channel.close();
        RabbitmqConnectionPool.returnConnection(rabbitmqConnection);
    } catch (InterruptedException | IOException | TimeoutException e) {
        e.printStackTrace();
    }

}
  • RabbitMQ 消费消息的流程是:

从连接池拿到连接 -> 创建通道 -> 确定消息队列 -> 绑定队列到交换机 -> 接受并消费消息 -> 将连接归还连接池

消费消息的代码如下:

@Override
public void consumerMsg(String exchange,
                        String queueName,
                        String routingKey) {

    try {
        //创建连接
        RabbitmqConnection rabbitmqConnection = RabbitmqConnectionPool.getConnection();
        Connection connection = rabbitmqConnection.getConnection();
        //创建消息信道
        final Channel channel = connection.createChannel();
        //消息队列
        channel.queueDeclare(queueName, true, false, false, null);
        //绑定队列到交换机
        channel.queueBind(queueName, exchange, routingKey);

        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println("Consumer msg:" + message);

                // 获取Rabbitmq消息,并保存到DB
                // 说明:这里仅作为示例,如果有多种类型的消息,可以根据消息判定,简单的用 if...else 处理,复杂的用工厂 + 策略模式
                notifyService.saveArticleNotify(JsonUtil.toObj(message, UserFootDO.class), NotifyTypeEnum.PRAISE);

                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        // 取消自动ack
        channel.basicConsume(queueName, false, consumer);
        channel.close();
        RabbitmqConnectionPool.returnConnection(rabbitmqConnection);
    } catch (InterruptedException | IOException | TimeoutException e) {
        e.printStackTrace();
    }
}

这里要注意,代码存在连接取出后没有归还的问题。因为连接池使用的是 BlockingQueue,如果连接全部取出且不归还,当新的请求过来时,请求就会卡住,导致界面操作阻塞。

5.4 调用入口

  • 点赞消息走 RabbitMQ

    // 点赞消息走 RabbitMQ,其它走 Java 内置消息机制
    if (notifyType.equals(NotifyTypeEnum.PRAISE) && rabbitmqProperties.getSwitchFlag()) {
        rabbitmqService.publishMsg(
                CommonConstants.EXCHANGE_NAME_DIRECT,
                BuiltinExchangeType.DIRECT,
                CommonConstants.QUERE_KEY_PRAISE,
                JsonUtil.toStr(foot));
    } else {
        Optional.ofNullable(notifyType).ifPresent(notify -> SpringUtil.publishEvent(new NotifyMsgEvent<>(this, notify, foot)));
    }
    
  • 启动时消费消息

@Override
public void run(ApplicationArguments args) {
    // 设置类型转换, 主要用于mybatis读取varchar/json类型数据据,并写入到json格式的实体Entity中
    JacksonTypeHandler.setObjectMapper(new ObjectMapper());
    // 应用启动之后执行
    GlobalViewConfig config = SpringUtil.getBean(GlobalViewConfig.class);
    if (webPort != null) {
        config.setHost("http://127.0.0.1:" + webPort);
    }
    // 启动 RabbitMQ 进行消费
    if (rabbitmqProperties.getSwitchFlag()) {
        String host = rabbitmqProperties.getHost();
        Integer port = rabbitmqProperties.getPort();
        String userName = rabbitmqProperties.getUsername();
        String password = rabbitmqProperties.getPassport();
        String virtualhost = rabbitmqProperties.getVirtualhost();
        Integer poolSize = rabbitmqProperties.getPoolSize();
        RabbitmqConnectionPool.initRabbitmqConnectionPool(host, port, userName, password, virtualhost, poolSize);
        taskExecutor.execute(() -> rabbitmqService.processConsumerMsg());
    }
    log.info("启动成功,点击进入首页: {}", config.getHost());
}

5.5 实际效果

  • 对文章多次点赞,触发RabbitMQ发送消息:
    在这里插入图片描述

  • 通过控制台打印的日志,查看发送和消费的消息
    在这里插入图片描述

  • 查看RabbitMQ后台,发现只有5个连接,跟连接池的大小一致
    在这里插入图片描述

  • 再看看channel,每次都会关闭,所以也都没有了
    在这里插入图片描述

5.6 bug修复

  • 问题:报错 AlreadyClosedException: channel is already closed 表示操作时 channel 已被关闭(日志显示为正常关闭 clean channel shutdown),通常由 channel 生命周期管理不当导致。
    在这里插入图片描述
    Consumer处理消息是一个异步的过程,消息还没处理完,channel就被关闭了,执行到channel.basicAck(envelope.getDeliveryTag(), false)会报异常。

  • 解决方案:延迟channel的关闭时间。

    channel.basicConsume(queueName, false, consumer);
    Thread.sleep(10000); // 延迟channel关闭,否则会报channel is not open的错误
    channel.close();
    RabbitmqConnectionPool.returnConnection(rabbitmqConnection);
    

6 总结

本文文章围绕 RabbitMQ 展开了详细介绍:

  1. 消息队列模式:介绍了点对点和发布 / 订阅两种模式及其特点和失败处理方式。
  2. RabbitMQ 原理:阐述了其基于的 AMQP 协议关键概念、工作原理和常用交换器类型。
  3. 环境搭建:分别说明了 Windows 版和Linux版安装 Erlang、RabbitMQ 及配置、启动和访问管理界面的步骤。
  4. 集成与问题:展示了集成代码实现,指出存在连接和 channel 未关闭、消费方式简单等问题。
  5. 改进版本:引入连接池改进,介绍了具体代码及效果,还修复了 channel 关闭相关的 bug。

7 参考链接

  1. 技术派消息队列RabbitMQ
  2. 技术派消息队列RabbitMQ连接池
  3. 解锁RabbitMQ的秘密:消息队列为何如此强大?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值