RabbitMQ客户端源码 (Java)

374 阅读8分钟

RabbitMQ客户端源码 (Java)

客户端的使用

// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置连接参数
factory.setHost("");
factory.setPort(5672);
factory.setUsername("user");
factory.setPassword("password");
factory.setVirtualHost("/");
// 创建连接
Connection connection = factory.newConnection();
// 创建一个Channel 由同一个连接创建的Connection是会共享一个Socket的这个后面会看到
// 一个Channel是可以单独的和服务端交互的一个单位,各个Channel之间不会互相影响
Channel channel = connection.createChannel();
// 创建一个交换器
channel.exchangeDeclare(exchange, "topic",true, false, null);
// 推送消息
channel.basicPublish(exchange, toutingKey, null, message.getBytes());
// 创建一个队列
channel.queueDeclare("queue", true, false, false, null);
// 将队列和转换机通过routingKey绑定
channel.queueBind("queue", "exchange", "routingKey");
// 创建一个默认消费者来处理接收到的消息
DefaultConsumer consumer = new DefaultConsumer(channel){
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        // 回复ack
        channel.basicAck(envelope.getDeliveryTag(),false);
    }
};
// 将消费者绑定到queue 第二个参数是true就是自动ack不然就要手动
channel.basicConsume(queue, false, consumer);
// 关闭
channel.close();
connection.close();
// 事务
// 开启事务
channel.txSelect();  
// 发送消息  
channel.basicPublish(exchange, routingKey, props, body);  
// 事务提交  
channel.txCommit();
// 事务回滚  
channel.txRollback();
/**
 * 创建一个交换器.
 * @param exchange 交换器的名字
 * @param type 交换器的类型(fanout:绑定的Queue全推送;direct:完全匹配才推送;topic:规则匹配推送)
 * @param durable true的话就是持久性exchange,该exchange将在服务器重新启动后继续运行
 * @param autoDelete true的话就是自动删除,当没有Queue或者Exchange与当前Exchange绑定时,就会自动删除
 * @param arguments 交换器的其他属性(构造参数)
 */
Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete,
        Map<String, Object> arguments) throws IOException;
/**
 * 创建一个队列
 * @param queue 队列名
 * @param durable 如果我们声明的是持久队列,则为true(该队列将在服务器重新启动后继续存在)
 * @param exclusive 多个消费者绑定到多个队列时,RabbitMQ会采用轮询进行投递。如果想独占就设置为true
 * @param autoDelete 当消费者断开连接时,队列将会被删除。
 * @param arguments 队列的其他属性(构造参数)
 */
Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,
        Map<String, Object> arguments) throws IOException;

创建连接

public Connection newConnection(ExecutorService executor, List<Address> addrs, String clientProvidedName)
        throws IOException, TimeoutException {
    // make sure we respect the provided thread factory
    FrameHandlerFactory fhFactory = createFrameHandlerFactory();
    // 把连接信息封装成一个类
    ConnectionParams params = params(executor);
    // 设置clientProvidedName
    if (clientProvidedName != null) {
        Map<String, Object> properties = new HashMap<String, Object>(params.getClientProperties());
        properties.put("connection_name", clientProvidedName);
        params.setClientProperties(properties);
    }
    // 判断是否启动自动恢复 启动的话就生成一个自动恢复connection
    if (isAutomaticRecoveryEnabled()) {
        AutorecoveringConnection conn = new AutorecoveringConnection(params, fhFactory, addrs);
        conn.init();
        return conn;
    } else {
        IOException lastException = null;
        for (Address addr : addrs) {
            try {
                // 创建一个包裹着socket的handler
                FrameHandler handler = fhFactory.create(addr);
                // 创建连接
                AMQConnection conn = new AMQConnection(params, handler);
                // 启动
                conn.start();
                return conn;
            }
        }
}
// FrameHandlerFactory
public FrameHandler create(Address addr) throws IOException {
    String hostName = addr.getHost();
    int portNumber = ConnectionFactory.portOrDefault(addr.getPort(), ssl);
    Socket socket = null;
    try {
        // 创建一个正常socket
        socket = factory.createSocket();
        // 关闭Nagle算法 设置TCP无延时
        configurator.configure(socket);
        socket.connect(new InetSocketAddress(hostName, portNumber),
                connectionTimeout);
        // 返回了一个SocketFrameHandler
        return create(socket);
    } catch (IOException ioe) {
        quietTrySocketClose(socket);
        throw ioe;
    }
}
// SocketFrameHandler控制了输入输出
public SocketFrameHandler(Socket socket) throws IOException {
    _socket = socket;
    _inputStream = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
    _outputStream = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
}
// 再看connection的start()
public void start() {
    // 初始化工作线程 // 默认是newFixedThreadPool数量是2倍处理器
    initializeConsumerWorkService();
    // 初始化心跳
    initializeHeartbeatSender();
    // 控制MainLoop能否循环执行
    this._running = true;
    AMQChannel.SimpleBlockingRpcContinuation connStartBlocker =
    new AMQChannel.SimpleBlockingRpcContinuation();
    // 把当前任务改为connStartBlocker 加锁阻塞
    // 客户端会先发送请求头,然后等待connStart的应答
    // 告诉channel0设置一个阻塞rpc任务
    _channel0.enqueueRpc(connStartBlocker);
    _frameHandler.setTimeout(handshakeTimeout);
    // 发送Protocol-Header 0-9-1
    _frameHandler.sendHeader();
    // 开启MainLoop // 这个就循环去处理发送过来的请求 比如ACK
    MainLoop loop = new MainLoop();
    final String name = "AMQP Connection " + getHostAddress() + ":" + getPort();
    mainLoopThread = Environment.newThread(threadFactory, loop, name);
    mainLoopThread.start();
    AMQP.Connection.Start connStart;
    AMQP.Connection.Tune connTune = null;
    // 这里是需要等待服务端返回 connStart就服务端返回的数据
    connStart =
        (AMQP.Connection.Start) connStartBlocker.getReply(handshakeTimeout/2).getMethod();
do {
    // 创建Connection.StartOk
    Method method = (challenge == null)
                            ? new AMQP.Connection.StartOk.Builder()
                            .clientProperties(_clientProperties)
                            .mechanism(sm.getName())
                            .response(response)
                            .build()
 : new AMQP.Connection.SecureOk.Builder().response(response).build();
    try {
        Method serverResponse = _channel0.rpc(method, handshakeTimeout/2).getMethod();
        if (serverResponse instanceof AMQP.Connection.Tune) {
            // 拿到返回就跳出循环
            connTune = (AMQP.Connection.Tune) serverResponse;
        } else {
            challenge = ((AMQP.Connection.Secure) serverResponse).getChallenge();
            response = sm.handleChallenge(challenge, this.username, this.password);
        }
} while (connTune == null);
    int channelMax =
    negotiateChannelMax(this.requestedChannelMax,
                      connTune.getChannelMax());
    // 生成一个ChannelManager 管理除_channel0 外的通信channel
    _channelManager = instantiateChannelManager(channelMax, threadFactory);
    int frameMax =
    negotiatedMaxValue(this.requestedFrameMax,
                     connTune.getFrameMax());
    this._frameMax = frameMax;
    int heartbeat =
    negotiatedMaxValue(this.requestedHeartbeat,
                     connTune.getHeartbeat());
    setHeartbeat(heartbeat);
    // 发送TuneOk
    _channel0.transmit(new AMQP.Connection.TuneOk.Builder()
                  .channelMax(channelMax)
                  .frameMax(frameMax)
                  .heartbeat(heartbeat)
                .build());
    // 发送Open
    _channel0.exnWrappingRpc(new AMQP.Connection.Open.Builder()
                        .virtualHost(_virtualHost)
                      .build());
}

解释下一些东西,可以看到_channel0是由connection一开始创建的,它是负责这个连接和服务端的交互的,比如一开始发送的Header和接收到Start然后回复。这里_channel0是有好几个方法的。

Channel方法

在这里先提一些东西,RabbitMQ客户端和服务端是通过一种叫Frame格式是传送消息的,可以说Frame是传送的最小单元,但是多个Frame可以拼接成一条完整的信息。其实就是手动拆包,来应对可能很大的消息内容。

// AMQChannel的一些方法
// 排队设置当前的Rpc,当然这个队是java的条件队列
public void enqueueRpc(RpcContinuation k)
{
    // _channelMutex相当于这个channel上的锁 
    synchronized (_channelMutex) {
        boolean waitClearedInterruptStatus = false;
        while (_activeRpc != null) {
            try {
                _channelMutex.wait();
            } catch (InterruptedException e) {
                waitClearedInterruptStatus = true;
            }
        }
        if (waitClearedInterruptStatus) {
            Thread.currentThread().interrupt();
        }
        _activeRpc = k;
    }
}
// 向客户端发送当前Method(比如StartOk这种)
public AMQCommand rpc(Method m)
    throws IOException, ShutdownSignalException
{
    return privateRpc(m);
}
// 同上但是有超时时间
public AMQCommand rpc(Method m, int timeout)
        throws IOException, ShutdownSignalException, TimeoutException {
    return privateRpc(m, timeout);
}
// 可能会有异常的情况类似上面的rpc
public AMQCommand exnWrappingRpc(Method m)
    throws IOException
{
    try {
        return privateRpc(m);
    } catch (AlreadyClosedException ace) {
        throw ace;
    } catch (ShutdownSignalException ex) {
        throw wrap(ex);
    }
}
// RPC具体实现
private AMQCommand privateRpc(Method m, int timeout)
        throws IOException, ShutdownSignalException, TimeoutException {
    // 这个东西下面分析
    SimpleBlockingRpcContinuation k = new SimpleBlockingRpcContinuation();
    // rpc发送
    rpc(m, k);
    // 阻塞等待有超时时间
    return k.getReply(timeout);
}
// 发送
public void rpc(Method m, RpcContinuation k)
    throws IOException
{
    synchronized (_channelMutex) {
        // 检查状态是不是开启
        ensureIsOpen();
        quiescingRpc(m, k);
    }
}
public void quiescingRpc(Method m, RpcContinuation k)
    throws IOException
{
    synchronized (_channelMutex) {
        // 第一个方法,抢占当前Rpc
        enqueueRpc(k);
        // 排队发送信息
        quiescingTransmit(m);
    }
}
// 发送消息
public void transmit(Method m) throws IOException {
    synchronized (_channelMutex) {
        transmit(new AMQCommand(m));
    }
}
public void transmit(AMQCommand c) throws IOException {
    synchronized (_channelMutex) {
        ensureIsOpen();
        // 排队发送消息
        quiescingTransmit(c);
    }
}

BlockingRpcContinuation

到这里必须先看一下BlockingRpcContinuation这个东西了

public static abstract class BlockingRpcContinuation<T> implements RpcContinuation {
    public final BlockingValueOrException<T, ShutdownSignalException> _blocker =
        new BlockingValueOrException<T, ShutdownSignalException>();
    // 会把transforReply方法的结果交给_blocker 唤醒等待值的阻塞
    public void handleCommand(AMQCommand command) {
        _blocker.setValue(transformReply(command));
    }
    // 给定一个值结束掉阻塞
    public void handleShutdownSignal(ShutdownSignalException signal) {
        _blocker.setException(signal);
    }
    // 阻塞获取值
    public T getReply() throws ShutdownSignalException
    {
        return _blocker.uninterruptibleGetValue();
    }
    // 有超时时间的获取值
    public T getReply(int timeout)
        throws ShutdownSignalException, TimeoutException
    {
        return _blocker.uninterruptibleGetValue(timeout);
    }
    public abstract T transformReply(AMQCommand command);
}

Channel方法续

// 这时候再回头看这个getReply,这个就是代表发送后一直等待响应
private AMQCommand privateRpc(Method m, int timeout)
        throws IOException, ShutdownSignalException, TimeoutException {
    // 这个东西下面分析
    SimpleBlockingRpcContinuation k = new SimpleBlockingRpcContinuation();
    // rpc发送
    rpc(m, k);
    // 阻塞等待有超时时间
    return k.getReply(timeout);
}
// 处理消息
public void handleFrame(Frame frame) throws IOException {
    AMQCommand command = _command;
    // 这个handleFrame();是拼接Frame的,可以把Frame拼接成完成的信息放入command
    if (command.handleFrame(frame)) { // a complete command has rolled off the assembly line
        _command = new AMQCommand(); // prepare for the next one
        // 处理command
        handleCompleteInboundCommand(command);
    }
}
// 
public void handleCompleteInboundCommand(AMQCommand command) throws IOException {
    // 是不是被统一处理了,统一处理就直接返回了
    if (!processAsync(command)) {
        // 不处理就说明可以k.getReply(),也就是会唤醒上面privateRpc里面的getReply操作
        nextOutstandingRpc().handleCommand(command);
        markRpcFinished();
    }
}
// 这个是一个抽象方法
public abstract boolean processAsync(Command command) throws IOException;
//  子类ChannelN(也就是正常创建的channel)的处理通信相关消息的处理
public boolean processAsync(Command command) throws IOException
{
    // 只列出几个
    if (method instanceof Basic.Deliver) {
        // 处理推送给消费者消息的
        processDelivery(command, (Basic.Deliver) method);
        return true;
    } else if (method instanceof Basic.Ack) {
        // 发送出消息的Ack
        Basic.Ack ack = (Basic.Ack) method;
        callConfirmListeners(command, ack);
        handleAckNack(ack.getDeliveryTag(), ack.getMultiple(), false);
        return true;
    }
}
// 这是_channel0的实现
private final AMQChannel _channel0 = new AMQChannel(this, 0) {
    @Override public boolean processAsync(Command c) throws IOException {
        return getConnection().processControlCommand(c);
    }
};
// 点进
public boolean processControlCommand(Command c) throws IOException {
    // 处理一些和Connection有关的
    // 不全列出
    if (method instanceof AMQP.Connection.Close) {
        handleConnectionClose(c);
        return true;
    }
}

再看一下创建过程

public Channel createChannel() throws IOException {
    ensureIsOpen();
    // Channel的管理类
    ChannelManager cm = _channelManager;
    if (cm == null) return null;
    return cm.createChannel(this);
}
// 创建channel
public ChannelN createChannel(AMQConnection connection) throws IOException {
    ChannelN ch;
    synchronized (this.monitor) {
        // 分配编号
        int channelNumber = channelNumberAllocator.allocate();
        if (channelNumber == -1) {
            return null;
        } else {
            // 新创建一个通道
            ch = addNewChannel(connection, channelNumber);
        }
    }
    ch.open(); // now that it's been safely added
    return ch;
}

Channel总结

使用rpc(Method m)这种方式就是发送消息阻塞,然后等待回复。 使用transmit(Method m)这种方式就是发送消息不需要等待回复

Channel分两种,一种是正常的手动创建的,一种是伴随着Connection的_channel0,两者的编号是不同的,0号负责的是整个连接的信息交互。不同的Channel通过同一个Socket通信但是靠唯一的编号来控制信息不会出现问题。 我们点几个用到的看看

// 开启事务
public Tx.SelectOk txSelect()
    throws IOException
{
    // 可以看到是阻塞的那种
    return (Tx.SelectOk) exnWrappingRpc(new Tx.Select()).getMethod();
}
// 这个就不是阻塞,就是发出去不需要回复的
// 所以不开启事务的话,消息是无法确保发送到服务端的
public void basicPublish(String exchange, String routingKey,
                         boolean mandatory, boolean immediate,
                         BasicProperties props, byte[] body)
    throws IOException
{
    if (nextPublishSeqNo > 0) {
        unconfirmedSet.add(getNextPublishSeqNo());
        nextPublishSeqNo++;
    }
    BasicProperties useProps = props;
    if (props == null) {
        useProps = MessageProperties.MINIMAL_BASIC;
    }
    transmit(new AMQCommand(new Basic.Publish.Builder()
                                    .exchange(exchange)
                                    .routingKey(routingKey)
                                    .mandatory(mandatory)
                                    .immediate(immediate)
                                    .build(),
                                   useProps, body));
}

消费者

// 按照刚刚阻塞的讲的方式,看一下消费消息
public String basicConsume(String queue, boolean autoAck, String consumerTag,
                           boolean noLocal, boolean exclusive, Map<String, Object> arguments,
                           final Consumer callback)
    throws IOException
{
    // 这个就是接到服务端应答要触发的
    // 这个会在收到消息后调用的
    BlockingRpcContinuation<String> k = new BlockingRpcContinuation<String>() {
        public String transformReply(AMQCommand replyCommand) {
            String actualConsumerTag = ((Basic.ConsumeOk) replyCommand.getMethod()).getConsumerTag();
             // 看到返回了一个Tag 以他为key放到了map里面
            _consumers.put(actualConsumerTag, callback);
            // 这里调用了callback的handleConsumeOk
            // 默认实现是把tag放到callback里面
            dispatcher.handleConsumeOk(callback, actualConsumerTag);
            return actualConsumerTag;
        }
    };
    // RPC,会阻塞在这
    rpc(new Basic.Consume.Builder()
         .queue(queue)
         .consumerTag(consumerTag)
         .noLocal(noLocal)
         .noAck(autoAck)
         .exclusive(exclusive)
         .arguments(arguments)
        .build(),
        k);
    try {
        return k.getReply();
    } catch(ShutdownSignalException ex) {
        throw wrap(ex);
    }
}
// 使用线程池执行传入的方法
public void handleConsumeOk(final Consumer delegate,
                            final String consumerTag) {
    executeUnlessShuttingDown(
    new Runnable() {
        public void run() {
            try {
                delegate.handleConsumeOk(consumerTag);
            } catch (Throwable ex) {
                connection.getExceptionHandler().handleConsumerException(
                        channel,
                        ex,
                        delegate,
                        consumerTag,
                        "handleConsumeOk");
            }
        }
    });
}

注意注意,这里使用的是rpc,但是这个推送的消息却是统一处理的消息,也就是不会有释放的时候,这个channel就只能等待推送消息了。