RocketMQ实战-对于消息生产者使用的一些思考

513 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情

往期回顾

基于V4.9.4版本的RocketMQ源码解析专栏

在RocketMQ源码解析专栏的基础之后,新建了一个RocketMQ实战专栏,主要是作者本人对于这一款消息中间件使用过程中的一些思考并加以总结,将这些内容分享出来,希望能够帮助到有需要的人,致力于提升系统的稳定性。

消息生产者解析

RocketMQ支持三种模式的消息生产者,分别为同步模式、异步模块、单向模式,逻辑都封装在DefaultMQProducer这个生产者类中。

单向模式不关注发送的结果,一般主要用于日志的记录上面,跟业务关联性很小,本篇不再赘述。我们主要关注点在同步模式以及异步模式上面,具体内容如下:

同步模式

public SendResult send(Message msg,
    long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    msg.setTopic(withNamespace(msg.getTopic()));
    return this.defaultMQProducerImpl.send(msg, timeout);
}

同步发送消息模式,直接触发了发送消息的核心API,支持用户设定超时时间,默认值为3秒,同步模式支持3次重复发送。

一般情况下,生产的应用服务器与RocketMQ服务器在一个内部网络环境中,网络通信的流量以及延迟很小,而且RoccketMQ设计目标就是支持低延迟、高并发,所以在使用消息生产者发送消息时,大部分情况下使用同步模式即可。

异步模式

public void send(Message msg,
    SendCallback sendCallback) throws MQClientException, RemotingException, InterruptedException {
    msg.setTopic(withNamespace(msg.getTopic()));
    this.defaultMQProducerImpl.send(msg, sendCallback);
}

异步发送的模式,内部调用的是defaultMQProducerImpl的send方法,我们具体分析一下这个方法

public void send(final Message msg, final SendCallback sendCallback, final long timeout)
    throws MQClientException, RemotingException, InterruptedException {
    final long beginStartTime = System.currentTimeMillis();
    //获取异步发送消息的线程池
    ExecutorService executor = this.getAsyncSenderExecutor();
    try {
        executor.submit(new Runnable() {
            @Override
            public void run() {
                long costTime = System.currentTimeMillis() - beginStartTime;
                if (timeout > costTime) {
                    try {
                        sendDefaultImpl(msg, CommunicationMode.ASYNC, sendCallback, timeout - costTime);
                    } catch (Exception e) {
                        sendCallback.onException(e);
                    }
                } else {
                    sendCallback.onException(
                        new RemotingTooMuchRequestException("DEFAULT ASYNC send call timeout"));
                }
            }

        });
    } catch (RejectedExecutionException e) {
        throw new MQClientException("executor rejected ", e);
    }

}

异步发送消息的方法,内部通过线程池的方式,将消息发送的核心API(sendDefaultImpl)封装进了Runnable中,然后通过回调sendCallback的方法实现异步发送。

使用异步发送模式,一般是为了提高Broker端相关参数的级别,特别是对于数据持久化方面参数的设置。另外一方面,若在一次业务操作中需要发送多条MQ的消息,也可以使用异步发送,能够提高接口的性能,缩短系统的响应时间。

AsyncSenderExecutor默认的线程数为CPU的核心数,默认有限队列存储50000个发送任务,内部通过Semaphore来控制发送并发度,默认为65535,,重试次数默认值为2次。

消息生产者使用方式的个人总结

消息重试

不管是同步模式还是异步模式,RocketMQ都支持了重试机制,即在消息发送失败之后支持在重新发送该条消息,避免了网络抖动带来的一些问题。

消息的重实机制,值得注意的是,重试的超时时间是在用户指定或者系统默认的超时时间之内,例如:默认系统的超时时间的3秒,重试次数为3次,那就这3次的重试是在3秒之内进行重试。在实际使用过程中,建议增加发送的超时时间以及增加重试次数,因为实际的生产环境中,网络抖动,消息集群抖动的情况很多,可能是由许多问题引起的,为了应对这个情况,建议使用这种方式。

故障规避

RocketMQ提供了两种故障规避,一种是故障延迟机制,默认是不开启的,设置值为false,另一种是在重试时规避上一次发送失败的Broker。

这段逻辑就是在重试时规避上一次发送失败的Broker。

//lastBrokerName上一次发送消息失败的brokerName
public MessageQueue selectOneMessageQueue(final String lastBrokerName) {
    if (lastBrokerName == null) {
        return selectOneMessageQueue();
    } else {
        for (int i = 0; i < this.messageQueueList.size(); i++) {
            int index = this.sendWhichQueue.incrementAndGet();
            int pos = Math.abs(index) % this.messageQueueList.size();
            if (pos < 0)
                pos = 0;
            MessageQueue mq = this.messageQueueList.get(pos);
            //旋转队列时规避
            if (!mq.getBrokerName().equals(lastBrokerName)) {
                return mq;
            }
        }
        return selectOneMessageQueue();
    }
}

RocketMQ的defaultMQProducerImpl的updateFaultItem方法,是应用故障延迟机制的入口。

public void updateFaultItem(final String brokerName, final long currentLatency, boolean isolation) {
    this.mqFaultStrategy.updateFaultItem(brokerName, currentLatency, isolation);
}

对于故障延迟机制,在源码分析篇中给出了自己的一些思考,再重复说一下:

因为发送消息成功并不意味着足够健康,这意味着如果有两个Broker服务器,并且向它们发送了大量消息,并且所有消息都成功发送,但是,其中一个Broker处理速度很慢,因此延迟很高。对于这个高延迟的Broker,可能将其视为故障项,并将其隔离一段时间,以防坏的该Broker变得更糟,然后客户端将享受良好的发送体验

如果开启了故障延迟机制,假如集群的负载已经很高了,由于故障延迟机制又将某个Broker屏蔽了,会导致整个集群负载的程度更高,会引起一些列的问题,所以一般情况下使用第一种即可,足以满足应用的需求了。

异步发送

异步发送的编程模式较为复杂,其容错补偿机制应该注意:

在调用DefaultMQProducer.send异步发送方法时,应该捕获异常,并进行容错的处理。因为可能涉及RejectedExecutionException异常抛出,即异步线程池任务队列满了。