详解RocketMQ的通信模型和应用

对于消息队列的通信模型,目前市面上主流的消息队列产品主要有两种形式,一种按照队列的数据结构设计出来的“队列模型”,另一则是在其基础上演化出来的“发布-订阅模型”,而RocketMQ采用的是发布订阅的方式。

一、通信模型

发布订阅,看成是生产者-消费者模型的一种形式就可以了。消息发送给到订阅了这个消息的消费者,那么势必系统需要知道生产者和消费者之间的关系。RocketMQ提供了Broker这个组件做为消息中间层,处理消息投递过程中的一些事务,例如:存储、转发。为了维护他们之间的关系,RocketMQ又引入了NamerServer提供类似注册中心的功能,只是此时注册的是所有Broker,生产者和消费者通过NamerServer找到对应消息投递和接收的Broker,整体的一个流程如下:
在这里插入图片描述
RocketMQ在设计上是参考kafka实现的,所以整体上就跟kafka的很像。

二、消息发送

RocketMQ对于消息提供了很多用法,包括:同步消息、异步消息、单向发送、顺序消息、延时消息、批量消息、过滤消息、事务消息等。添加依赖:

<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-client</artifactId>
    <version>4.3.0</version>
</dependency>

项目启动时init生成者信息:

@Component
public class RProducer {

    @Value("${rocketmq.namesrvAddr}")
    private String namesrvAddr;

    @Value("${rocketmq.retryTimesWhenSendFailed:3}")
    private int retryTimesWhenSendFailed;

    private DefaultMQProducer producer;

    @PostConstruct
    private void init() throws MQClientException {
        producer = new DefaultMQProducer("P_DEFAULT");
        producer.setNamesrvAddr(namesrvAddr);
        producer.setUnitName("P_NAME");
        producer.setSendLatencyFaultEnable(true);
        producer.setRetryTimesWhenSendFailed(retryTimesWhenSendFailed);
        producer.setSendMsgTimeout(60000);
        producer.start();
    }

    @PreDestroy
    public void shutdown() {
        if (producer != null) {
            producer.shutdown();
        }
    }

}
2.1 同步消息

同步消息关注的是数据的可靠性,对于发送的数据,RocketMQ会在发送消息的同时将消息写入到磁盘,最后返回发送状态。可以应用于重要的消息通知,短信通知等。

public SendResult send(String topic, String tag, String info) throws Exception {
    boolean failed = false;
    do {
        try {
            Message message = new Message(topic, tag, info.getBytes());
            return producer.send(message);
        }catch (Exception e){
            failed = true;
            TimeUnit.SECONDS.sleep(3);
        }
    }while (failed);
    return null;
}
2.2 异步消息

异步消息通常用在对响应时间比较敏感,同时又不需要及时的关注消息传递结果的场景,比如优惠券发放,秒杀等。

public void sendSync(String topic, String tag, String info) throws Exception{
    Message message = new Message(topic, tag, info.getBytes());
    producer.send(message, new SendCallback() {
        @Override
        public void onSuccess(SendResult sendResult) {}

        @Override
        public void onException(Throwable e) {}
    });
}
2.3 单向发送

这种方式主要用在不特别关注消息传递结果的场景,例如日志发送。

public void sendOneWay(String topic, String tag, String info) throws Exception{
    Message message = new Message(topic, tag, info.getBytes());
    producer.sendOneway(message);
}
2.4 顺序发送

RocketMQ默认是采用轮询的方式将消息发送到不同的队列里的,消费者拿到的数据其实是不能保证消息的有序性的。但如果控制消息都往同一个队列里发,或者是同一类的消息往同一个队列里发,也就可以实现消息全局有序或者局部有序。主要可以用于一些对顺序性有要求的数据,比如一个订单,假如流程是创建、付款、通知、完成,按照订单号维度,把相同的订单放到同一个队列里,那样就能保证对于一条订单来说,数据消费是有序的。

Message message = new Message(topic, tag, info.getBytes());
SendResult sendResult = producer.send(message, new MessageQueueSelector() {
    @Override
    public MessageQueue select(List<MessageQueue> mqs, Message mess, Object arg) {
        //根据订单id选择发送的队列
        int index = (int) arg % mqs.size();
        return mqs.get(index);
    }
}, id);
2.5 延迟发送

初始化消息的时候可以配置消息延迟发送的等级。RocketMQ默认有18个延迟等级:“1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h”。

Message message = new Message(topic, tag, info.getBytes());
// 延迟等级为2,延迟5s发送
message.setDelayTimeLevel(2);
2.6 批量发送

RocketMQ允许将一批数据组合成一个List一次性发送出去,减少不必要的网络传输,但需要注意发送数据量的大小。

public SendResult sendList(List<Message> messages) throws Exception{
    boolean failed = false;
    do {
        try {
            return producer.send(messages);
        }catch (Exception e){
            failed = true;
            TimeUnit.SECONDS.sleep(3);
        }
    }while (failed);
    return null;
}
2.7 过滤消息

构造Message的时候,需要指定发送的topic(主题)和tags,topic用于匹配消息的订阅,而tags主要用于消息的过滤。消费者可以通过订阅指定的tags,从而获取想要的内容。

DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("C_DEFAULT");
// *表示不过滤,多个tags用 “||”隔开
consumer.subscribe("TOPIC_TEST", "*");

也可以在message中设置一些属性,在消费者通过一些基本语法实现过滤,比如:接收id>100的可以这样实现:

// 生产者
Message message = new Message(topic, tag, info.getBytes());
message.putUserProperty("id", val);
// 消费者
consumer = new DefaultMQPushConsumer("C_DEFAULT");
consumer.subscribe("TOPIC_TEST", MessageSelector.bySql("id > 10"));

提供的语法有:

数值比较,比如:>,>=,<,<=,BETWEEN,=

字符比较,比如:=,<>,IN,IS NULL 或者 IS NOT NULL

逻辑符号,比如:AND,OR,NOT

2.8 事务消息

RocketMQ的事务消息是基于2pc进行设计的,共有三种状态,提交状态、回滚状态、中间状态,具体可以查看TransactionStatus的定义。事务提交之前,消息会先进行预发送,等到本地事务执行完后再根据结果决定发送消息还是回滚消息,从而实现分布式事务。

// 创建事务消息生产者
@Component
public class RTProduct {

    @Value("${rocketmq.namesrvAddr}")
    private String namesrvAddr;

    @Value("${rocketmq.retryTimesWhenSendFailed:3}")
    private int retryTimesWhenSendFailed;

    private TransactionMQProducer producer;

    @PostConstruct
    private void init() throws MQClientException {
        producer = new TransactionMQProducer("R_T_DEFAULT");
        producer.setNamesrvAddr(namesrvAddr);
        producer.setUnitName("P_NAME");
        // 设置监听的线程
        ExecutorService executorService = new ThreadPoolExecutor(2, 5, 20, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(2000), new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName("client-transaction-msg-check-thread");
                return thread;
            }
        });
        producer.setExecutorService(executorService);
        // 绑定事务监听
        producer.setTransactionListener(new RTListener());
        producer.setRetryTimesWhenSendFailed(retryTimesWhenSendFailed);
        producer.start();
    }
}
// 实现事务监听
public class RTListener implements TransactionListener {
    
    private ConcurrentHashMap<String, Integer> localTrans = new ConcurrentHashMap<>();

    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
        Integer status = localTrans.get(messageExt.getTransactionId());
        if (null != status) {
            switch (status) {
                case 0:
                    return LocalTransactionState.UNKNOW;
                case 1:
                    return LocalTransactionState.COMMIT_MESSAGE;
                case 2:
                    return LocalTransactionState.ROLLBACK_MESSAGE;
            }
        }
        return LocalTransactionState.COMMIT_MESSAGE;
    }
    
    @Override
    public LocalTransactionState executeLocalTransaction(Message message, Object o) {
        /*
        * TODO 执行本地事务
        **/
        localTrans.put(message.getTransactionId(), 1);
        return LocalTransactionState.UNKNOW;
    }
}

三、消息接收

RocketMQ消息的消费主要有2种模式,一种是消息队列主动推给消费者(push),一种是消费者定时去消息队列拉取消息(pull),不过推模式是RocketMQ本身在拉模式上做的封装,所以主要关注拉模式就可以了,同时消费过程中也要考虑消费有序、消息幂等、消息堆积等情况,后续再单独写一篇分享。感兴趣的可以关注我,一起学习!

  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芋圆在睡觉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值