RocketMq发送消息

RocketMQ提供同步、异步、单向三种发送方式,其中单向发送可能存在消息丢失。其高可用设计包括重试机制和Broker规避策略。在Broker规避中,启用故障延迟机制能有效避免故障Broker。RocketMQ支持普通、顺序、延时和事务消息,其中事务消息通过半消息和状态回查确保事务一致性。
摘要由CSDN通过智能技术生成

发送类型

Rocketmq提供三种方式可以发送普通消息:同步、异步、和单向发送。

  • 同步:发送方发送消息后,收到服务端响应后才发送下一条消息
  • 异步:发送一条消息后,不等服务端返回就可以继续发送消息或者后续任务处理。发送方通过回调接口接收服务端响应,并处理响应结果。
  • OneWay:发送方发送消息,不等待服务端返回响应且没有回调函数触发,即只发送请求不需要应答。

发送方式对比:发送吞吐量,单向>异步>同步。但单向发送可靠性差存在丢失消息可能,选型根据实际需求确定。

Demo演示

package com.company.mq;

@Slf4j
@RestController
public class Controller {
    /**
     * 生产者组
     */
    private final static String PRODUCE_RGROUP = "test_producer";
    /**
     * 创建生产者对象
     */
    private static DefaultMQProducer producer = null;

    static {
        producer = new DefaultMQProducer(PRODUCE_RGROUP);
        //不开启vip通道 开通口端口会减2
        producer.setVipChannelEnabled(false);
        //绑定name server
        producer.setNamesrvAddr("127.0.0.1:9876");
        try {
            producer.start();
        } catch (MQClientException e) {
            e.printStackTrace();
        }

    }

    @GetMapping("/message")
    public void message() throws Exception {
        //1、同步
        sync();
        //2、异步
        async();
        //3、单项发送
        oneWay();
    }

    /**
     * 1、同步发送消息
     */
    private void sync() throws Exception {
        //创建消息
        Message message = new Message("topic_family", ("  同步发送  ").getBytes());
        //同步发送消息
        SendResult sendResult = producer.send(message);
        log.info("Product-同步发送-Product信息={}", sendResult);
    }

    /**
     * 2、异步发送消息
     */
    private void async() throws Exception {
        //创建消息
        Message message = new Message("topic_family", ("  异步发送  ").getBytes());
        //异步发送消息
        producer.send(message, new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                log.info("Product-异步发送-输出信息={}", sendResult);
            }

            @Override
            public void onException(Throwable e) {
                e.printStackTrace();
                //补偿机制,根据业务情况进行使用,看是否进行重试
            }
        });
    }

    /**
     * 3、单项发送消息
     */
    private void oneWay() throws Exception {
        //创建消息
        Message message = new Message("topic_family", (" 单项发送 ").getBytes());
        //同步发送消息
        producer.sendOneway(message);
    }
}

消息发送高可用设计

在rocketmq topic的创建机制中,一个topic对应有多个消息队列,那么我们在发送消息时,是如何选择消息队列进行发送的?假如这时有broker宕机了,rocketmq是如何规避故障broker的?

rocketmq在发送消息时,由于nameserver检测broker是否还存活是有延迟的,在选择消息队列时难免会遇到已经宕机的broker,又或者因为网络原因发送失败的,因此rocketmq采取了一些高可用设计的方案,主要通过两个手段:重试与Broker规避

重试

在client端,发送消息的方式有:同步(SYNC)、异步(ASYNC)、单向(ONEWAY)。

那么可以知道,retryTimesWhenSendFailed决定同步方法重试次数,默认重试次数为3次。

重试机制提高了消息发送的成功率。

现在有个由两个broker节点组成的集群,有topic1,默认在每个broker上创建4个队列,分别是:master-a(q0,q1,q2,q3)、master-b(q0,q1,q2,q3),上一次发送消息到master-a的q0队列,此时master-a宕机了,如果继续发送topic1消息,rocketmq如果避免再次发送到master-a。

RocketMq选择队列有两种方式,通过sendLatencyFaultEnable的值来控制,默认值为false,不启动broker故障延迟机制,值为true时启用broker故障延迟机制。

默认机制

sendLatencyFaultEnable=false,消息发送选择队列调用以下方法:

TopicPublishInfo

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.getAndIncrement();
                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();
        }
    }

lastBrokerName指的是上一次执行消息发送时选择失败的broker,在重试机制下,第一次执行消息发送时,lastBrokerName=null,直接选择以下方法:

public MessageQueue selectOneMessageQueue() {
        int index = this.sendWhichQueue.getAndIncrement();
        int pos = Math.abs(index) % this.messageQueueList.size();
        if (pos < 0)
            pos = 0;
        return this.messageQueueList.get(pos);
    }

sendWhichQueue是一个利用ThreadLocal本地线程存储自增值的一个类,自增值第一次使用Random类随机取值,此后如果消息发送出发重试机制,那么每次自增取值。

public class ThreadLocalIndex {
    private final ThreadLocal<Integer> threadLocalIndex = new ThreadLocal<Integer>();
    private final Random random = new Random();

    public int getAndIncrement() {
        Integer index = this.threadLocalIndex.get();
        if (null == index) {
            index = Math.abs(random.nextInt());
            this.threadLocalIndex.set(index);
        }

        index = Math.abs(index + 1);
        this.threadLocalIndex.set(index);
        return index;
    }

    @Override
    public String toString() {
        return "ThreadLocalIndex{" +
            "threadLocalIndex=" + threadLocalIndex.get() +
            '}';
    }
}

此方法直接用sendWhichQueue自增获取值,再与消息队列的长度进行取模运算,取模目的是为了循环选择消息队列。

如果此时选择的队列发送消息失败了,此时重试机制在再次选择队列时lastBrokerName不为空,回到最开始的那个方法,还是利用sendWhichQueue自增获取值,但这里多了一个步骤,与消息队列的长度进行取模运算后,如果此时选择的队列所在的broker还是上一次选择失败的broker,则继续选择下一个队列。

我们再细想一下,如果此时有broker宕机了,在默认机制下很可能下一次选择的队列还是在已经宕机的broker,没有办法规避故障的broker,因此消息发送很可能会再次失败,重试发送造成了不必要的性能损失。

所以rocketmq采用Broker故障延迟机制来规避故障的broker。

Broker规避

sendLatencyFaultEnable=true,消息发送选择队列调用MQFaultStrategy#selectOneMessageQueue:

public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName) {
        if (this.sendLatencyFaultEnable) {
            try {
                int index = tpInfo.getSendWhichQueue().getAndIncrement();
                for (int i = 0; i < tpInfo.getMessageQueueList().size(); i++) {
                    // 队列位置值取模
                    int pos = Math.abs(index++) % tpInfo.getMessageQueueList().size();
                    if (pos < 0)
                        pos = 0;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值