RocketMQ入门到精通

MQ简介

MQ全称为Message Queue(消息队列),是在消息传输中保存消息的容器。多用于分布式系统之间进行通信。
Queue:数据结构的一种,特征为”先进先出“
在这里插入图片描述

优势

  • 应用解耦
    假设我现在还要添加一个大数据系统 就不需要修改上游系统的逻辑。
    在这里插入图片描述

  • 异步提速
    多个消息队列下游系统并行执行逻辑比顺序执行逻辑要快
    在这里插入图片描述

  • 削峰填谷
    流量值过大的时候可以缓存消息,让下游系统平稳处理数据,避免流量过大系统崩溃
    在这里插入图片描述

劣势

  • 系统可用性降低
    一旦MQ宕机,就会对所有下游系统造成影响。如何保证MQ高可用?

  • 复杂度提高
    以前的系统是同步的远程调用,现在通过MQ调用,如何保证重复消费?怎么处理消息丢失情况?如何保证消息传递的顺序性

  • 一致性问题
    A系统处理完业务,通过MQ给A,B,C三个系统发送消息,如果B、C系统处理成功但是A系统异常,如何保证给消息数据处理的一致性?

RocketMQ

RocketMQ是阿里开源的一款非常优秀的中间件产品,后捐赠给apache基金会的顶级项目,能承受住双十一极致的场景压力(双十一峰值达到万亿级)

基础感念

在这里插入图片描述

Windows安装

  1. 下载rocketmq4.8.0版本
  2. 配置环境变量
    变量名:ROCKETMQ_HOME
    变量值:环境路径

注:部分电脑重启环境变量才会生效

在这里插入图片描述
3. 启动nameserve:

D:\rocketmq-all-4.8.0-bin-release\bin>start mqnamesrv.cmd
  1. 启动broker:
#autoCreateTopicEnable=true 自动创建topic
D:\rocketmq-all-4.8.0-bin-release\bin>start mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true -c ../conf/broker.conf

Linux安装

自行百度

Java整合RocketMQ

入门案例

生产者
  1. 引入依赖
 <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-client</artifactId>
            <version>4.8.0</version>
</dependency>
  1. 编写代码
public static void main(String[] args) throws Exception {

       //1.谁来发
        DefaultMQProducer producer = new DefaultMQProducer("group1");
        //2.发给谁
        producer.setNamesrvAddr("127.0.0.1:9876");
        //3.怎么发
        //4.发什么
        producer.start();
        String content = "hello rocketmq";
        Message message = new Message("topic1", "tag1", content.getBytes());
        SendResult send = producer.send(message);
        //5.发送的结果是什么
        System.out.println(send);
        //6.打扫战场
        //关闭连接
        producer.shutdown();
    }
消费者
  1. 引入依赖
 <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-client</artifactId>
            <version>4.8.0</version>
</dependency>
  1. 编写代码
public static void main(String[] args) throws Exception {

        //1.谁来收
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
        //2.从哪里收消息
        consumer.setNamesrvAddr("127.0.0.1:9876");
        //3.监听那个消息队列
        consumer.subscribe("topic1", "*");
        //处理业务,注册实时监听器
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt data : list) {
                    byte[] body = data.getBody();
                    System.out.println(new String(body));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        consumer.start();
    }

多消费模式

这里存在一个问题 为了保证消费者系统高可用部署了集群,那么消费者会处理(集群*消息数)翻倍的消息数量吗

生产者

生产者发送十条消息

 //1.谁来发
        DefaultMQProducer producer = new DefaultMQProducer("group1");
        //2.发给谁
        producer.setNamesrvAddr("127.0.0.1:9876");
        //3.怎么发
        //4.发什么
        producer.start();
        for (int i = 0; i < 10; i++) {
            String content = "hello rocketmq";
            Message message = new Message("topic2", "tag1", content.getBytes());
            SendResult send = producer.send(message);
            //5.发送的结果是什么
            System.out.println(send);
        }
        //6.打扫战场
        //关闭连接
        producer.shutdown();

    }
消费者

启动两个消费者服务

public static void main(String[] args) throws Exception {

        //1.谁来收
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
        //2.从哪里收消息
        consumer.setNamesrvAddr("127.0.0.1:9876");
        //3.监听那个消息队列
        consumer.subscribe("topic2", "*");
        //处理业务,注册实时监听器
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt data : list) {
                    byte[] body = data.getBody();
                    System.out.println(new String(body));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        consumer.start();
    }

消费者1
在这里插入图片描述
消费者2
在这里插入图片描述
为什么会这样呢,这样就涉及到group的概念?

group的概念

group的作用就是用来做负载均衡,假设生产者生产了10条消息,处在相同group的消费者就会平分消息,处在不同group的消费者就会接收全量的消息
在这里插入图片描述
上图:Consumer1接受5条,Consumer2接受5条,Consumer3接受10条

当然也可以不修改group来使处在相同组的消费者接受全量的消息

 //默认是负载均衡模式,MessageModel.BROADCASTING就变成全量接受
 consumer.setMessageModel(MessageModel.BROADCASTING);

同步消息

特征:即使性较强,重要的消息,且必须有回执的消息,例如短信,通知

SendResult send = producer.send(message);

异步消息

特征:即时性较弱,但需要有回执的消息,例如订单中的某些信息,吞吐量较高

 //注:异步消息不能关闭mq的连接
 producer.send(message, new SendCallback() {
                //异步回调成功
                public void onSuccess(SendResult sendResult) {
                    System.out.println(sendResult);
                }
                //异步回调失败
                public void onException(Throwable throwable) {
                    System.out.println(throwable.toString());
                }
            });

单向消息

特征:不需要回执的消息,例如日志消息

 producer.sendOneway(message);

延时消息

//1(1s) 2(5s) 3(10s) 4(30s) .. 自行百度等级对应的具体时间
message.setDelayTimeLevel(1);

批量消息

每次生产者和MQ建立连接都是非常消耗性能的(要经历三握四挥)。所以就需要建立一次连接传输全量的数据

         List<Message> data = new ArrayList<Message>();
        for (int i = 0; i < 10; i++) {
            String content = "hello rocketmq";
            Message message1 = new Message("topic5", "tag1", content.getBytes());
            data.add(message1);
        }
        producer.send(data);

注:

  1. 批量消息应该有相同的topic
  2. 相同的对象字段类型
  3. 不能是延时消息
  4. 消息内容总长度不超过4M

Tag过滤

  //接收全部
  consumer.subscribe("topic2", "*");
  //接受单个
  consumer.subscribe("topic2", "tag1");
  //接受多个tag
  consumer.subscribe("topic2", "tag1 || tag2");

SQL过滤

像写SQL一样在消费方过滤不想要的数据

  1. 开启SQL过虑支持(默认不开启)
#找到conf/broker.conf 追加以下配置
enablePropertyFilter=true
  1. 重启broker
start mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true -c ../conf/broker.conf
  1. 生产者设置Message
 public static void main(String[] args) throws Exception {

        //1.谁来发
        DefaultMQProducer producer = new DefaultMQProducer("group1");
        //2.发给谁
        producer.setNamesrvAddr("127.0.0.1:9876");
        //3.怎么发
        //4.发什么
        producer.start();
        for (int i = 0; i < 10; i++) {
            String content = "hello rocketmq"+i;
            Message message = new Message("topic10", "tag1", content.getBytes());
            //设置sql过滤的追加属性
            message.putUserProperty("age", Integer.toString(i));
            SendResult send = producer.send(message);
            //5.发送的结果是什么
            System.out.println(send);
        }
        //6.打扫战场
        //关闭连接
        producer.shutdown();
    }
  1. 消费者设置
public static void main(String[] args) throws Exception {

        //1.谁来收
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
        //2.从哪里收消息
        consumer.setNamesrvAddr("127.0.0.1:9876");
        //3.监听那个消息队列 SQL过滤
        consumer.subscribe("topic10", MessageSelector.bySql("age >= 5 "));
        //处理业务,注册实时监听器
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt data : list) {
                    byte[] body = data.getBody();
                    System.out.println(new String(body));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        consumer.start();
    }

这里可以发现MQ已经过滤掉了age>=5 的数据
在这里插入图片描述

SpringBoot整合

  1. Idea创建项目
    在这里插入图片描述
  2. 勾选SpringMVC改为Web项目
    在这里插入图片描述
  3. 添加RocketMQ依赖
<dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-spring-boot-starter</artifactId>
            <version>2.0.3</version>
        </dependency>
  1. 编写配置文件
rocketmq:
  # 配置nameserver地址
  name-server: localhost:9876
  # 配置生产者组名
  producer:
    group: group1
  1. 编写生产者代码
@RestController
public class SendController {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;


    @GetMapping(value = "test")
    public String send() {
        Map map = new HashMap();
        map.put("name", "范源鑫");
        map.put("age", "18");
        rocketMQTemplate.convertAndSend("topic1:tag1", map);
        return "success";
    }

}
  1. 编写消费者代码
@Service
@RocketMQMessageListener(topic = "topic1", selectorType = SelectorType.TAG, selectorExpression = "tag1", consumerGroup = "group1")
public class Consumer implements RocketMQListener<Map> {
    @Override
    public void onMessage(Map map) {
        System.out.println("我是消费者A" + map.toString());
    }
}

消息类型案例

生产者的消息类型案例
        //同步消息
        SendResult topic1 = rocketMQTemplate.syncSend("topic1:2", map);
        //异步消息
        rocketMQTemplate.asyncSend("topic1:tag2", map, new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                System.out.println("处理成功" + sendResult.toString());

            }

            @Override
            public void onException(Throwable throwable) {
                System.out.println("处理失败" + throwable.getMessage());
            }
        });
        //发送单向消息
        rocketMQTemplate.sendOneWay("topic1:2", map);

        //发送延时消息
        rocketMQTemplate.syncSend("topic1:2", MessageBuilder.withPayload(map).build(), 1, 2);

        //发送批量消息
        ArrayList<Map> list = Lists.newArrayList();
        list.add(map);
        SendResult data = rocketMQTemplate.syncSend("topic1:2", list);
        
消费者根据SQL过滤
@RocketMQMessageListener(topic = "topic1", selectorType = SelectorType.SQL92, selectorExpression = "age >= 10 ", consumerGroup = "group1")
消费者默认的负载改为广播模式
@RocketMQMessageListener(topic = "topic1", selectorType = SelectorType.SQL92, selectorExpression = "age >= 10 ", consumerGroup = "group1",messageModel = MessageModel.BROADCASTING)
消费顺序

假设一个订单的流程 是 创建->付款->完成 之后用消息队列发送会产生什么影响呢

  1. 创建生产者模拟数据
 private void getData() {
        ArrayList<OrderStep> objects = Lists.newArrayList();
        objects.add(new OrderStep(1L, "创建"));
        objects.add(new OrderStep(2L, "创建"));
        objects.add(new OrderStep(1L, "付款"));
        objects.add(new OrderStep(3L, "创建"));
        objects.add(new OrderStep(2L, "付款"));
        objects.add(new OrderStep(3L, "付款"));
        objects.add(new OrderStep(2L, "完成"));
        objects.add(new OrderStep(3L, "完成"));
        objects.add(new OrderStep(1L, "完成"));
        return objects;
       //同步发送消息
       for (OrderStep datum : data) {
            SendResult topic1 = rocketMQTemplate.syncSend("topic1:tag2", datum);
        }
    }
  1. 创建两条消费者A,B
@Service
@RocketMQMessageListener(topic = "topic1", selectorType = SelectorType.TAG, selectorExpression = "tag2", consumerGroup = "group1")
public class ConsumerA implements RocketMQListener<OrderStep> {
    @Override
    public void onMessage(OrderStep map) {
        System.out.println("消费者A" + map.toString());
    }
}

@Service
@RocketMQMessageListener(topic = "topic1", selectorType = SelectorType.TAG, selectorExpression = "tag2", consumerGroup = "group1")
public class ConsumerB implements RocketMQListener<OrderStep> {
    @Override
    public void onMessage(OrderStep map) {
        System.out.println("消费者B" + map.toString());
    }
}

运行会发现订单2先完成了。这是什么情况呢?
在这里插入图片描述
是因为一个broker默认有4个队列(可修改)消息发送时每个消息可能会被分配到不同的队列中 导致多个消者消费时会可能先读取到队列2的订单完成订单。那怎么保证顺序消费呢,就是在生产者吧同个订单分配到相同的队列内部就可以了
在这里插入图片描述

解决:将订单相同的放进同一个队列中

  1. 发送同步消息
//通过Orderly接口实现顺序发送 关键点是hashcode参数 hashcode参数相同分配的队列就相同
//这里是根据订单的id当作hashcode
SendResult sendResult = rocketMQTemplate.syncSendOrderly("topic1:tag2", orderStep, hashcode);
  1. 消费方设置同步消息模式(consumeMode = ConsumeMode.ORDERLY)
@RocketMQMessageListener(topic = "topic1", selectorType = SelectorType.TAG, selectorExpression = "tag2", consumerGroup = "group1",consumeMode = ConsumeMode.ORDERLY)
事务消息
  • 红色是正常事务
  • 蓝色为事务补偿过程
    在这里插入图片描述

注:事务和消费者没有关系

状态:
提交状态:允许进入队列,此消息与非事务消息无区别(属于第四步的提交操作)
回滚状态:不允许进入队列,此消息等同于未发送过消息(属于第四步的回滚操作)
中间状态:完成了half消息的发送,没有对mq进行二次确认(broker主动询问生产者消息事务的状态)

  1. 创建本地事务消息监听类
package com.fyx.rocketmq.transaction;

import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Service;

@Service
@RocketMQTransactionListener(txProducerGroup = "tx_order")
public class LocalTrsaction implements RocketMQLocalTransactionListener {
    //正常事务处理
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {

        if (true) {
            //业务成功把消息提交到broker
            System.out.println("事务正常提交");
            return RocketMQLocalTransactionState.COMMIT;
        }else{
            //业务失败就回滚不发送消息到broker
            System.out.println("事务异常提交");
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }

    //事务补偿处理
    //正常事务处理返回UNKNOWN时候进入这个方法进行重新进行业务操作
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
    if (true) {
            //业务成功把消息提交到broker
            System.out.println("事务正常提交");
            return RocketMQLocalTransactionState.COMMIT;
        }else{
            //业务失败就回滚不发送消息到broker
            System.out.println("事务异常提交");
            return RocketMQLocalTransactionState.ROLLBACK;
        }
        
    }
}

  1. 生产者发送事务消息
        Message<OrderStep> message = MessageBuilder.withPayload(new OrderStep(1L, "创建")).build();
        TransactionSendResult sendResult = rocketMQTemplate.sendMessageInTransaction("tx_order", "tx_topic:" + "tx_tag", message, message.getPayload().getOrderId());
        String localTXState = sendResult.getLocalTransactionState().name();
        System.out.println("事务名:"+localTXState);
  1. 消费者接受消息
@Service(value = "consumer3")
@RocketMQMessageListener(topic = "tx_topic", selectorType = SelectorType.TAG, selectorExpression = "tx_tag", consumerGroup = "tx_group")
public class Consumer implements RocketMQListener<OrderStep> {


    @Override
    public void onMessage(OrderStep map) {
        System.out.println(map.toString());
    }
}

消息重试

当消费者处理完业务没有向broker正常返回处理成功的状态后,boker会对这一条消息进行重复消费消息
大致分为两种消息重试机制,顺序消息无序消息

  1. 顺序消息(同步消息)
  • 当消费者消费失败后,rocketmq会自动进行消息重试(每次间隔时间1秒)

注:应用会出现消息消费被阻塞的情况,因此,要对顺序消息的消费情况给进行监控,避免阻塞现象的发生

  1. 无序消息(普通消息、定时消息、延时消息、事务消息)
  • 无序消息重试仅适用于负载均衡(集群)模型下的消息消费,不适用于广播模式下的消息消费
  • 为保障无序消息的消费,MQ设定了合理的消息重试间隔时常

死信队列

当消息重试达到了指定次数(默认16次)后,MQ将无法被正常消费的消息称为死信消息,死信消息不会被直接抛弃,而是保存到了一个全新的队列中,该队列称为死信队列(Dead-Letter Queue)

死信队列处理:在监控平台中查找死信队列,获取死信的MessageId,然后通过id对死信进行精准消费

重复消费

可能因为网络闪断、生产者宕机、broker重启、订阅方应用重启 会对同一笔订单做多次业务操作(例:如对同一笔订单扣除多次库存)

解决方案:

  • 使用业务id作为消息的key使用
  • 在消费消息时,客户端对key做判断,未使用过放行,使用过抛弃
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
一、rocketmq入门精通视频教程目录大纲 001-001_RocketMQ_简介 002-002_RocketMQ_核心概念详解 003-003_RocketMQ_集群构建模型详解(一) 004-004_RocketMQ_集群构建模型详解(二) 005-005_RocketMQ_双主模式集群环境搭建 006-006_RocketMQ_控制台使用讲解 007-007_RocketMQ_Broker配置文件详解 008-008_RocketMQ_helloworld示例讲解 009-009_RocketMQ_整体架构概述详解 010-010_RocketMQ_Producer_API详解 011-011_RocketMQ_Producer_顺序消费机制详解 012-012_RocketMQ_Producer_事务消息机制详解 013-013_RocketMQ_Consumer_Push和Pull模式及使用详解 014-014_RocketMQ_Consumer_配置参数详解 015-015_RocketMQ_Consumer_重试策略详解 016-016_RocketMQ_Consumer_幂等去重策略详解 017-017_RocketMQ_消息模式及使用讲解 018-018_RocketMQ_双主双从集群环境搭建与使用详解 019-019_RocketMQ_FilterServer机制及使用详解 020-020_RocketMQ_管理员命令 二、rocketmq实战视频教程目录大纲 01_rocketmq_实战项目介绍 02_rocketMQ实战项目设计(一) 03_rocketMQ实战项目设计(二) 04_rocketMQ实战-环境搭建(一) 05_rocketMQ实战-环境搭建(二) 06_rocketMQ实战-生产者与spring结合 07_rocketMQ实战-消费者与spring结合 08_rocketMQ实战-数据库模型设计 09_rocketMQ实战-数据库DAO代码生成 10_rocketMQ实战-远程RPC接口设计与实现(一) 11_rocketMQ实战-远程RPC接口设计与实现(二) 12_rocketMQ实战-远程RPC接口设计与实现(三) 13_rocketMQ实战-下单流程(一) 14_rocketMQ实战-下单流程(二) 15_rocketMQ实战-下单流程(三) 16_rocketMQ实战-下单流程(四) 17_rocketMQ实战-下单流程(五) 18_rocketMQ实战-下单流程(六) 19_rocketMQ实战-下单流程(七) 20_rocketMQ实战-下单流程(八)-商品库存 21_rocketMQ实战-下单流程(九)-商品库存 22_rocketMQ实战-下单流程(十)-支付模块 23_rocketMQ实战-整体联调

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值