RabbitMQ线程和连接模型详解

1. 线程、信道、连接、请求的概念

客户端(生产者)和服务端(服务端)之间建立连接。例如TCP连接,是一个长连接,也是较为稳定的连接,开销也较大。一般而言主客户端之间需要一个连接。但服务器需要连接多个客户端。

客户端的消息(请求)需要进过经过信道到达服务端。信道是一种逻辑上的连接通道,多个信道复用了同一个连接。(RabbitMQ默认的最大通道数2047)

2. RabbitMQ的线程模型

rabbitmq.client源码:

通过断点查找发现原来是 ConsumerWorkService MQ工作线程池这个类控制的,工作线程池和心跳线程会在消费者(服务端)启动时初始化。

这个类构造函数里有一个executor参数,当这个参数为空时,就会创建一个Executors.newFixedThreadPool,代码如下:

amqp-client的package com.rabbitmq.client.impl下有类

java 复制代码
final public class ConsumerWorkService {
    private static final Logger LOGGER = LoggerFactory.getLogger(ConsumerWorkService.class);
    private static final int MAX_RUNNABLE_BLOCK_SIZE = 256;
    private static final int DEFAULT_NUM_THREADS = Math.max(1, Utils.availableProcessors());
    private final ExecutorService executor;
    private final boolean privateExecutor;
    private final WorkPool<Channel, Runnable> workPool;
    private final int shutdownTimeout;

    public ConsumerWorkService(ExecutorService executor, ThreadFactory threadFactory, int queueingTimeout, int shutdownTimeout) {
        this.privateExecutor = (executor == null);
        if (executor == null) {
            LOGGER.debug("Creating executor service with {} thread(s) for consumer work service", DEFAULT_NUM_THREADS);
            this.executor = Executors.newFixedThreadPool(DEFAULT_NUM_THREADS, threadFactory);
        } else {
            this.executor = executor;
        }
        this.workPool = new WorkPool<>(queueingTimeout);
        this.shutdownTimeout = shutdownTimeout;
    }

这个类定义RabbitMQ服务端(消费者)的线程模型,底层也是一个ExecutorService的线程池模型,默认的线程数量是可用的CPU核数(处理器数)。

3. 浅浅的看看连接模型

从一般性的客户端的rabbitmq的使用出发

java 复制代码
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(ip);
factory.setPort(5672);
factory.setUsername("root");
factory.setPassword("root");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
String message = "RabbitMQ Demo Test:" + System.currentTimeMillis();
channel.basicPublish(EXCHANGE_NAME, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
channel.close();
connection.close();

建立连接时一般会用到ConnectionFactory的newConnection()

java 复制代码
public Connection newConnection(Address[] addrs) throws IOException, TimeoutException {
    return newConnection(this.sharedExecutor, Arrays.asList(addrs), null);
}

连接会传入一个共享的线程池,broker服务端和客户端的连接共享这个连接池,这个属性表示内部使用共享的唯一一个ExecutorService

设置这个属性就可以一直传到ConsumerWorkService中。

Set the executor to use for consumer operation dispatch by default for newly created connections. All connections that use this executor share it. It's developer's responsibility to shut down the executor when it is no longer needed.

默认情况下,为新创建的连接设置用于消费者操作调度的线程池。所有使用此线程池的连接都共享它。当不再需要线程池时,关闭它是开发人员的责任。

通过设置shareExecutorService,无论多少个channel,都可以统一控制线程数量、队列数量,根据实际情况进行配置。

也可以传入其他的executor。

4. RabbitMQ连接的具体过程

rabbitmq采用的amqp协议,是一个高级的应用层协议。

  1. 将 AMQP 0-9-1 的连接头写入底层套接字,包含指定的版本信息(客户端告诉 broker 自己使用的协议及版本,底层使用 java 自带的 socket)。

  2. 客户端等待 broker 发送的 Connection.Start (broker 告诉客户端 通信的协议和版本、SASL认证机制(详细见)、语言环境以及RabbitMQ的版本信息和支持能力)。

  3. 客户端接收后 发送 Connection.StartOk (客户端告诉 broker 连接使用的帐号和密码、认证机制、语言环境、客户的信息以及能力)。

  4. 客户端等待 broker 发送的 Connection.Tune (broker 与 客户端 进行参数协商)。

  5. 客户端接收后 发送 Connection.TuneOk (客户端 参数 [ChannelMax、FrameMax、Heartbeat] 协商完成后告诉 broker)。

  6. 客户端发送 Connection.Open (客户端 告诉 broker 打开一个连接,并请求设置_virtualHost [vhost])。

  7. broker 接收到后返回 Connection.OpenOk (client 对 vhost 进行验证,成功则返回如下此信息)。

  8. 客户端发送 Channel.Open,broker 接收到后返回 Channel.OpenOk (客户端 创建通道;broker 收到并创建通道完成)。

  9. 客户端发送 Confirm.Select,broker 接收到后返回 Confirm.SelectOk(客户端告诉 broker 消息需要使用 confirm的机制,broker收到并回复)。。

  10. 客户端发送消息 Basic.Publish,broker 应答返回 Basic.Ack。

  11. 期间 客户端和 broker 会相互检查彼此的心跳 heartbeat。

  12. 客户端 关闭通道 Channel.Close,broker 应答返回 Channel.CloseOk。

  13. 客户端 关闭连接 Connection.Close,broker 应答返回 Connection.CloseOk。

rabbitmq具体源码细节和spring中的是类似的,可以参考我的另一篇文章,分析地很详细。敲详细的springframework-amqp-rabbit源码解析

不同在于消息底层是用帧来包装,有分心跳帧和携带方法或信息的帧。Consumer也是带有queue的。

客户端单个connection对应的单个channel实际上是单线程的,每次收到Socket消息都触发处理逻辑,从任务队列里面取出一定的任务进行依次处理,如果一个channel订阅了多个topic的话也是单线程依次处理的。

相关推荐
夏天的味道٥3 小时前
使用 Java 执行 SQL 语句和存储过程
java·开发语言·sql
TiDB_PingCAP5 小时前
海量数据融合互通丨TiDB 在安徽省住房公积金监管服务平台的应用实践
分布式·tidb·htap
冰糖码奇朵5 小时前
大数据表高效导入导出解决方案,mysql数据库LOAD DATA命令和INTO OUTFILE命令详解
java·数据库·sql·mysql
好教员好5 小时前
【Spring】整合【SpringMVC】
java·spring
程序员的世界你不懂6 小时前
Kafka 推送消息,移动端自动化测试,数据驱动测试
分布式·kafka·linq
浪九天6 小时前
Java直通车系列13【Spring MVC】(Spring MVC常用注解)
java·后端·spring
堕落年代7 小时前
Maven匹配机制和仓库库设置
java·maven
功德+n7 小时前
Maven 使用指南:基础 + 进阶 + 高级用法
java·开发语言·maven
香精煎鱼香翅捞饭7 小时前
java通用自研接口限流组件
java·开发语言
ChinaRainbowSea8 小时前
Linux: Centos7 Cannot find a valid baseurl for repo: base/7/x86_64 解决方案
java·linux·运维·服务器·docker·架构