RocketMQ 生产者详解(四)

1、消息的发送状态

FLUSH_DISK_TIMEOUT: 没有在规定时间内完成刷盘 (刷盘策略需要为SYNC_FLUSH 才会出这个错误)

FLUSH_SLAVE_TIMEOUT: 主从模式下,broker是SYNC_MASTER, 没有在规定时间内完成主从同步

SLAVE_NOT_AVAILABLE: 从模式下,broker是SYNC_MASTER, 但是没有找到被配置成Slave的Broker

SEND_OK: 发送成功,没有发生上面的三种问题

在这里插入图片描述

2、重试机制

2.1、生产者 (异步和SendOneWay下配置无效)
消息重投(保证数据的高可靠性),本身内部支持重试,默认次数是2,

producer.setRetryTimesWhenSendAsyncFailed(3);

2.2、消费者
消费重试只针对集群消费方式生效,重试间隔时间配置 ,默认每条消息最多重试 16 次。

messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h

2.3、测试
pom:

<dependencies>
    <dependency>
        <groupId>org.apache.rocketmq</groupId>
        <artifactId>rocketmq-client</artifactId>
        <version>4.4.0</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.6</version>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.8.1</version>
    </dependency>
</dependencies>
public class RocketMQConsts {
    //=========================Hello RocketMQ==========================
    public static final String HELLO_TOPIC = "helloTopic";
    public static final String HELLO_GROUP_PROVIDER = "helloGroupProvider";
    public static final String HELLO_TAG = "helloTag";
    public static final String HELLO_KEY = "helloKey";
    public static final String HELLO_NAMESERVER = "192.168.1.38:9876;192.168.1.39:9876";
    public static final String HELLO_GROUP_CUSTOMER = "helloGroupCustomer";
}

生产者

public class RocketMQProducer {

    public static void main(String[] args) throws Exception {
        //创建producer,设置NameServer地址, 关闭rocketmq 虚拟IP
        DefaultMQProducer producer = new DefaultMQProducer(RocketMQConsts.HELLO_GROUP_PROVIDER);
        producer.setNamesrvAddr(RocketMQConsts.HELLO_NAMESERVER);
        producer.setRetryTimesWhenSendAsyncFailed(3);
        producer.setVipChannelEnabled(false);
        producer.start();
        //发送消息
        Message msg = new Message(RocketMQConsts.HELLO_TOPIC, RocketMQConsts.HELLO_TAG,
                RocketMQConsts.HELLO_KEY, ("Hello RocketMQ ").getBytes());
        final SendResult send = producer.send(msg);
        System.out.println("发送消息" + send);
        //关闭
        producer.shutdown();
    }

}

消费者:

public class RocketMQCustomer {
    public static void main(String[] args) {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer();
        //配置组名 IP
        consumer.setNamesrvAddr(RocketMQConsts.HELLO_NAMESERVER);
        consumer.setConsumerGroup(RocketMQConsts.HELLO_GROUP_CUSTOMER);
        //最后偏移量读取消息
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
        try {
            //订阅主题,监听主题下哪些标签
            consumer.subscribe(RocketMQConsts.HELLO_TOPIC, "*");
            consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
                //获取重试次数
                MessageExt messageExt = msgs.get(0);
                int times = messageExt.getReconsumeTimes();

                try {
                    //默认一条一条消息消费
                    Message msg = msgs.get(0);
                    System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), new String(msgs.get(0).getBody()));
                    String topic = msg.getTopic();
                    String body = new String(msg.getBody(), "utf-8");
                    String tags = msg.getTags();
                    String keys = msg.getKeys();
                    System.out.println("topic=" + topic + ", tags=" + tags + ", keys=" + keys + ", msg=" + body);
                    if (keys.equals(RocketMQConsts.HELLO_KEY)) {
                        throw new Exception("失败重试");
                    }
                    //返回成功
                    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                } catch (Exception e) {
                    if (times > 2) {
                        System.out.println("写入数据库,发送短信给运维。。。。。。");
                        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                    }

                    e.printStackTrace();
                    //返回稍后进行投递
                    return ConsumeConcurrentlyStatus.RECONSUME_LATER;
                }
            });
            //开始
            consumer.start();
        } catch (MQClientException e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

3、发送模式

在这里插入图片描述

3.1、SYNC:同步,重要通知邮件、报名短信通知、营销短信系统等。

3.2、ASYNC:异步,可以支持更高的并发,回调成功触发相对应的业务,比如注册成功后通知积分系统发放优惠券(异步发送时,不能关闭发送者)。异步发送:不会重试,发送总次数等于1

demo:

public class RocketMQProducer {

    public static void main(String[] args) throws Exception {
        //创建producer,设置NameServer地址, 关闭rocketmq 虚拟IP
        DefaultMQProducer producer = new DefaultMQProducer(RocketMQConsts.HELLO_GROUP_PROVIDER);
        producer.setNamesrvAddr(RocketMQConsts.HELLO_NAMESERVER);
        producer.setRetryTimesWhenSendAsyncFailed(3);
        producer.setVipChannelEnabled(false);
        producer.start();
        //发送消息
        Message msg = new Message(RocketMQConsts.HELLO_TOPIC, RocketMQConsts.HELLO_TAG,
                RocketMQConsts.HELLO_KEY, ("Hello RocketMQ ").getBytes());
        //异步发送
        producer.send(msg, new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                System.out.println("发送结果:" + sendResult);
            }

            @Override
            public void onException(Throwable e) {
                e.printStackTrace();
            }
        });

        //关闭
        //producer.shutdown();
    }

}

在这里插入图片描述
3.3、ONEWAY : 无需要等待响应,主要是日志收集,适用于某些耗时非常短,但对可靠性要求并不高的场景, 也就是LogServer, 只负责发送消息,不等待服务器回应且没有回调函数触发,即只发送请求不等待应答

//建立消息
Message msg = new Message(RocketMQConsts.HELLO_TOPIC, RocketMQConsts.HELLO_TAG,
        RocketMQConsts.HELLO_KEY, ("Hello RocketMQ ").getBytes());
//发送Oneway
producer.sendOneway(msg);

4、延迟队列

Producer 将消息发送到消息队列 RocketMQ 服务端,但并不期望这条消息立马投递,而是推迟到在当前时间点之后的某一个时间投递到 Consumer 进行消费,该消息即定时消息,目前支持固定精度的消息,常用于订单。

#索引为1-18
1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h

发送者

public class RocketMQProducer {

    public static void main(String[] args) throws Exception {
        //创建producer,设置NameServer地址, 关闭rocketmq 虚拟IP
        DefaultMQProducer producer = new DefaultMQProducer(RocketMQConsts.HELLO_GROUP_PROVIDER);
        producer.setNamesrvAddr(RocketMQConsts.HELLO_NAMESERVER);
        producer.setRetryTimesWhenSendAsyncFailed(3);
        producer.setVipChannelEnabled(false);
        producer.start();
        //发送消息
        Message msg = new Message(RocketMQConsts.HELLO_TOPIC, RocketMQConsts.HELLO_TAG,
                RocketMQConsts.HELLO_KEY, ("Hello RocketMQ ").getBytes());
        //设置延迟队列,5秒后消费者消费消息
        msg.setDelayTimeLevel(2);
        //异步发送
        System.out.println("send msg:" + msg);
        producer.send(msg);
        //关闭
        producer.shutdown();
    }

}

消费者:

public class RocketMQCustomer {
    public static void main(String[] args) {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer();
        //配置组名 IP
        consumer.setNamesrvAddr(RocketMQConsts.HELLO_NAMESERVER);
        consumer.setConsumerGroup(RocketMQConsts.HELLO_GROUP_CUSTOMER);
        //最后偏移量读取消息
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
        try {
            //订阅主题,监听主题下哪些标签
            consumer.subscribe(RocketMQConsts.HELLO_TOPIC, "*");
            consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
                try {
                    //默认一条一条消息消费
                    Message msg = msgs.get(0);
                    System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), new String(msgs.get(0).getBody()));
                    String topic = msg.getTopic();
                    String body = new String(msg.getBody(), "utf-8");
                    String tags = msg.getTags();
                    String keys = msg.getKeys();
                    System.out.println("topic=" + topic + ", tags=" + tags + ", keys=" + keys + ", msg=" + body);
                    //返回成功
                    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                    //返回稍后进行投递
                    return ConsumeConcurrentlyStatus.RECONSUME_LATER;
                }
            });
            //开始
            consumer.start();
        } catch (MQClientException e) {
            e.printStackTrace();
        }
    }
}

5秒后收到消息:
在这里插入图片描述

5、MessageQueueSelector

MessageQueueSelector,投递到Topic下指定的queue

修改send方法,同步发送:

//给队列1发送消息
SendResult send = producer.send(msg, new MessageQueueSelector() {
    @Override
    public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
        Integer queueNum = (Integer) arg;
        return mqs.get(queueNum);
    }
}, 1);

在这里插入图片描述
异步发送(异步发送时,producer不要关闭):

//给队列1发送消息
producer.send(msg, new MessageQueueSelector() {
    @Override
    public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
        Integer queueNum = (Integer) arg;
        return mqs.get(queueNum);
    }
}, 1, new SendCallback() {
    @Override
    public void onSuccess(SendResult sendResult) {
        System.out.println(sendResult);
    }
    @Override
    public void onException(Throwable e) {
        //TODO 记录日志
        e.printStackTrace();
    }
});

在这里插入图片描述

6、顺序消息

6.1 什么是顺序消息:消息的生产和消费顺序一致
1)全局顺序:topic下面全部消息都要有序(少用)
性能要求不高,所有的消息严格按照 FIFO 原则进行消息发布和消费的场景,并行度成为消息系统的瓶颈, 吞吐量不够.
在证券处理中,以人民币兑换美元为例子,在价格相同的情况下,先出价者优先处理,则可以通过全局顺序的方式按照 FIFO 的方式进行发布和消费
2)局部顺序:只要保证一组消息被顺序消费即可(RocketMQ使用)
性能要求高
电商的订单创建,同一个订单相关的创建订单消息、订单支付消息、订单退款消息、订单物流消息、订单交易成功消息 都会按照先后顺序来发布和消费
(阿里巴巴集团内部电商系统均使用局部顺序消息,既保证业务的顺序,同时又能保证业务的高性能)
3) 顺序发布:对于指定的一个 Topic,客户端将按照一定的先后顺序发送消息
顺序消费:对于指定的一个 Topic,按照一定的先后顺序接收消息,即先发送的消息一定会先被客户端接收到。
4) 注意:
* 顺序消息暂不支持广播模式
* 顺序消息不支持异步发送方式,否则将无法严格保证顺序

6.2、生产者demo,有篮球,足球,羽毛球一堆消息,按FIFO的顺序把各个种类的球类分到不同的队列中。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {

    private Integer type;

    private String name;

}

生产者:

public class RocketMQProducer {


    public static void main(String[] args) throws Exception {
        //创建producer,设置NameServer地址, 关闭rocketmq 虚拟IP
        DefaultMQProducer producer = new DefaultMQProducer(RocketMQConsts.HELLO_GROUP_PROVIDER);
        producer.setNamesrvAddr(RocketMQConsts.HELLO_NAMESERVER);
        producer.setRetryTimesWhenSendAsyncFailed(3);
        producer.setVipChannelEnabled(false);
        producer.start();

        List<Order> orderList = getInitalList();

        for (int i = 0; i < orderList.size(); i++) {
            //建立消息
            Message msg = new Message(RocketMQConsts.HELLO_TOPIC, RocketMQConsts.HELLO_TAG,
                    RocketMQConsts.HELLO_KEY, orderList.get(i).toString().getBytes());
            //给指定队列发送消息
            SendResult send = producer.send(msg, new MessageQueueSelector() {
                @Override
                public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
                    Integer tempType = (Integer) arg;
                    int index = tempType % mqs.size();
                    return mqs.get(index);
                }
            }, orderList.get(i).getType());

            System.out.println(send.getMessageQueue() + ":" + orderList.get(i));
        }
    }

    public static List<Order> getInitalList() {
        List<Order> list = new ArrayList<>();
        list.add(new Order(0, "篮球1"));
        list.add(new Order(1, "足球1"));
        list.add(new Order(2, "羽毛球1"));
        list.add(new Order(0, "篮球2"));
        list.add(new Order(1, "足球2"));
        list.add(new Order(2, "羽毛球2"));
        list.add(new Order(0, "篮球3"));
        list.add(new Order(1, "足球3"));
        list.add(new Order(2, "羽毛球3"));
        return list;
    }

}

生产者,篮球都在队列0,并且按照1、2、3的顺序发送
在这里插入图片描述

6.3、消费者按顺序消费
MessageListenerOrderly
Consumer会平均分配queue的数量, 并不是简单禁止并发处理,而是为每个Consumer Quene加个锁,消费每个消息前,需要获得这个消息所在的Queue的锁,这样同个时间,同个Queue的
消息不被并发消费,但是不同Queue的消息可以并发处理。

public class RocketMQCustomer {
    public static void main(String[] args) {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer();
        //配置组名 IP
        consumer.setNamesrvAddr(RocketMQConsts.HELLO_NAMESERVER);
        consumer.setConsumerGroup(RocketMQConsts.HELLO_GROUP_CUSTOMER);
        //最后偏移量读取消息
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
        try {
            //订阅主题,监听主题下哪些标签
            consumer.subscribe(RocketMQConsts.HELLO_TOPIC, "*");
            consumer.registerMessageListener(new MessageListenerOrderly() {
                @Override
                public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
                    Message msg = msgs.get(0);
                    try {
                        System.out.println(Thread.currentThread().getName() + "receive msg" + new String(msg.getBody(), "utf-8"));
                        return ConsumeOrderlyStatus.SUCCESS;
                    } catch (Exception e) {
                        e.printStackTrace();
                        //等会在消费
                        return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
                    }
                }
            });
            //开始
            consumer.start();
        } catch (MQClientException e) {
            e.printStackTrace();
        }
    }
}

启动4个消费端:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

发布了140 篇原创文章 · 获赞 47 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/qq_34125999/article/details/99903204