RocketMQ 4.x入门到进阶

RocketMQ 4.x使用(持续更新中......每周至少两章,不更新我吃屎)

  • 第一章《安装搭建RocketMQ 4.x》
  • 第二章《搭建RocketMQ可视化管理后台》
  • 第三章《Springboot2.X整合RocketMQ4.X实战》
  • 第四章《RocketMQ 集群模式》
  • 第五章《RocketMQ 消息高可靠》
  • 第六章《2m-2s-async 集群搭建》

第一章《安装搭建RocketMQ 4.x》

安装搭建前置条件(参照官网https://rocketmq.apache.org/docs/quick-start/)

  • 64bit OS, Linux/Unix/Mac is recommended;
  • 64bit JDK 1.8+;
  • Maven 3.2.x;
  • Git;
  • 4g+ free disk for Broker server

安装步骤

  1. 下载安装包http://mirror.bit.edu.cn/apache/rocketmq/4.4.0/rocketmq-all-4.4.0-source-release.zip
  2. 解压缩4.4.0源代码版本并构建二进制工件。
  > unzip rocketmq-all-4.4.0-source-release.zip
  > cd rocketmq-all-4.4.0/
  > mvn -Prelease-all -DskipTests clean install -U
  > cd distribution/target/apache-rocketmq
  1. 以守护进程启动nameServer
  > nohup sh bin/mqnamesrv &
  > tail -f ~/logs/rocketmqlogs/namesrv.log
  The Name Server boot success...
  1. 以守护进程启动broker(-n指定nameServer地址,nameServer端口9876,broker默认端口10911)
  > nohup sh bin/mqbroker -n localhost:9876 &
  > tail -f ~/logs/rocketmqlogs/broker.log 
  The broker[%s, 172.30.30.233:10911] boot success...
  1. 关闭服务,注意关闭顺序
 > sh bin/mqshutdown broker
 The mqbroker(36695) is running...
 Send shutdown request to mqbroker(36695) OK

 > sh bin/mqshutdown namesrv
 The mqnamesrv(36664) is running...
 Send shutdown request to mqnamesrv(36664) OK

中间产生的问题
问题1:Please set the JAVA_HOME variable in your environment, We need java(x64)
解决:需要本地配置JAVA_HOME

JAVA_HOME="/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Con
export JAVA_HOME
CLASS_PATH="$JAVA_HOME/lib"
PATH=".$PATH:$JAVA_HOME/bin"

测试

第二章《搭建RocketMQ可视化管理后台》

安装步骤

  1. 下载 git clone https://github.com/apache/rocketmq-externals.git
  2. 修改‎⁨⁨rocketmq-externals⁩ ▸ ⁨rocketmq-console⁩ ▸ ⁨src⁩ ▸ ⁨main⁩ ▸ ⁨resources⁩▸ application.properties
  > rocketmq.config.namesrvAddr=127.0.0.1:9876
  1. 打包
  > cd rocketmq-externals/rocketmq-console
  > mvn clean package -Dmaven.test.skip=true
  1. 启动 java -jar target/rocketmq-console-ng-1.0.0.jar
  2. 访问 http://localhost:8080RocketMQ 4.x入门到进阶_第1张图片

中间产生的问题
问题1:打包失败
解决:检查pom文件

4.4.0-SNAPSHOT
调整为4.4.0

问题2:启动失败
RocketMQ 4.x入门到进阶_第2张图片
解决:检查pom文件Springboot版本

1.4.3.RELEASE
调整为1.5.4.RELEASE
然后重新打包,并启动

问题3:控制台报 连接10909错误
原因:Broker 默认开启了vip通道,端口为10911-2=10909(源码可查看)
解决:防火墙开启端口10909

第三章《Springboot2.X整合RocketMQ4.X实战》

源码地址

  • https://github.com/FaxBoy/rocketmq/tree/Chapter01

前置条件

  • 启动第一章讲到的nameServer和broker

开发步骤

  1. pom.xml添加RocketMq依赖

    
        org.apache.rocketmq
        rocketmq-client
        4.4.0
    
    
  2. yml增加配置信息

    server:
      port: 8888
    
    mouse:
      rocketmq:
        producer:
          groupName: pay_producer_group #发送同一类消息的设置为同一个group,保证唯一,默认不需要设置,rocketmq会使用ip@pid(pid代表jvm名字)作为唯一标示
          namesrvAddr: localhost:9876 #mq的nameserver地址
          maxMessageSize: 4096 #消息最大长度 默认1024*4(4M)
          sendMsgTimeout: 3000 #发送消息超时时间,默认3000
          retryTimesWhenSendFailed: 2 #发送消息失败重试次数,默认2
        consumer:
          isOnOff: on #on==true 踩过的坑
          groupName: pay_producer_group
          namesrvAddr: localhost:9876 #mq的nameserver地址
          topics: TopicTest~* #该消费者订阅的主题和tags("*"号表示订阅该主题下所有的tags),格式:topic~tag1||tag2||tag3;topic2~*;
          consumeThreadMin: 20
          consumeThreadMax: 64
          consumeMessageBatchMaxSize: 1 #设置一次消费消息的条数,默认为1条
    
    
  3. 增加生产者bean

    @SpringBootConfiguration
    public class MQProducerConfiguration {
    
        public static final Logger LOGGER = LoggerFactory.getLogger(MQProducerConfiguration.class);
    
        public static final String PREFIX = "mouse";
    
        /**
         * 发送同一类消息的设置为同一个group,保证唯一,默认不需要设置,rocketmq会使用ip@pid(pid代表jvm名字)作为唯一标示
         */
        @Value("${mouse.rocketmq.producer.groupName}")
        private String groupName;
    
        @Value("${mouse.rocketmq.producer.namesrvAddr}")
        private String namesrvAddr;
    
        /**
         * 消息最大大小,默认4M
         */
        @Value("${mouse.rocketmq.producer.maxMessageSize}")
        private Integer maxMessageSize ;
        /**
         * 消息发送超时时间,默认3秒
         */
        @Value("${mouse.rocketmq.producer.sendMsgTimeout}")
        private Integer sendMsgTimeout;
        /**
         * 消息发送失败重试次数,默认2次
         */
        @Value("${mouse.rocketmq.producer.retryTimesWhenSendFailed}")
        private Integer retryTimesWhenSendFailed;
    
        @Bean
        public DefaultMQProducer getRocketMQProducer()throws RocketMQException {
            if (StringUtils.isEmpty(this.groupName)) {
                throw new RocketMQException(RocketMQErrorEnum.PARAMM_NULL,"groupName is blank",false);
            }
            if (StringUtils.isEmpty(this.namesrvAddr)) {
                throw new RocketMQException(RocketMQErrorEnum.PARAMM_NULL,"nameServerAddr is blank",false);
            }
            DefaultMQProducer producer;
            producer = new DefaultMQProducer(this.groupName);
            producer.setNamesrvAddr(this.namesrvAddr);
            //如果需要同一个jvm中不同的producer往不同的mq集群发送消息,需要设置不同的instanceName
            //producer.setInstanceName(instanceName);
            if(this.maxMessageSize!=null){
                producer.setMaxMessageSize(this.maxMessageSize);
            }
            if(this.sendMsgTimeout!=null){
                producer.setSendMsgTimeout(this.sendMsgTimeout);
            }
            //如果发送消息失败,设置重试次数,默认为2次
            if(this.retryTimesWhenSendFailed!=null){
                producer.setRetryTimesWhenSendFailed(this.retryTimesWhenSendFailed);
            }
    
            try {
                producer.start();
    
                LOGGER.info(String.format("producer is start ! groupName:[%s],namesrvAddr:[%s]"
                        , this.groupName, this.namesrvAddr));
            } catch (MQClientException e) {
                LOGGER.error(String.format("producer is error {}"
                        , e.getMessage(),e));
                throw new RocketMQException(e);
            }
            return producer;
        }
    
    }
    
  4. 增加消费者监听

    @Component
    public class MQConsumeMsgListenerProcessor implements MessageListenerConcurrently{
        private static final Logger logger = LoggerFactory.getLogger(MQConsumeMsgListenerProcessor.class);
    
        /**
         *  默认msgs里只有一条消息,可以通过设置consumeMessageBatchMaxSize参数来批量接收消息
    * 不要抛异常,如果没有return CONSUME_SUCCESS ,consumer会重新消费该消息,直到return CONSUME_SUCCESS */ @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { if(CollectionUtils.isEmpty(msgs)){ logger.info("接受到的消息为空,不处理,直接返回成功"); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } MessageExt messageExt = msgs.get(0); try { String body = new String(messageExt.getBody(), "utf-8"); logger.info("接受到的消息body为:"+body); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } logger.info("接受到的消息为:"+messageExt.toString()); if(messageExt.getTopic().equals("你的Topic")){ if(messageExt.getTags().equals("你的Tag")){ //TODO 判断该消息是否重复消费(RocketMQ不保证消息不重复,如果你的业务需要保证严格的不重复消息,需要你自己在业务端去重) //TODO 获取该消息重试次数 int reconsume = messageExt.getReconsumeTimes(); if(reconsume ==3){//消息已经重试了3次,如果不需要再次消费,则返回成功 return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } //TODO 处理对应的业务逻辑 } } // 如果没有return success ,consumer会重新消费该消息,直到return success return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }
  5. 增加消费者bean

    @Configuration
    @ConditionalOnProperty(name = "mouse.rocketmq.consumer.isOnOff", havingValue = "true")
    public class MQConsumerConfiguration {
        public static final Logger LOGGER = LoggerFactory.getLogger(MQConsumerConfiguration.class);
        @Value("${mouse.rocketmq.consumer.namesrvAddr}")
        private String namesrvAddr;
        @Value("${mouse.rocketmq.consumer.groupName}")
        private String groupName;
        @Value("${mouse.rocketmq.consumer.consumeThreadMin}")
        private int consumeThreadMin;
        @Value("${mouse.rocketmq.consumer.consumeThreadMax}")
        private int consumeThreadMax;
        @Value("${mouse.rocketmq.consumer.topics}")
        private String topics;
        @Value("${mouse.rocketmq.consumer.consumeMessageBatchMaxSize}")
        private int consumeMessageBatchMaxSize;
    
        @Autowired
        private MQConsumeMsgListenerProcessor mqMessageListenerProcessor;
    
        @Bean
        public DefaultMQPushConsumer getRocketMQConsumer() throws RocketMQException {
            if (StringUtils.isEmpty(groupName)){
                throw new RocketMQException(RocketMQErrorEnum.PARAMM_NULL,"groupName is null !!!",false);
            }
            if (StringUtils.isEmpty(namesrvAddr)){
                throw new RocketMQException(RocketMQErrorEnum.PARAMM_NULL,"namesrvAddr is null !!!",false);
            }
            if(StringUtils.isEmpty(topics)){
                throw new RocketMQException(RocketMQErrorEnum.PARAMM_NULL,"topics is null !!!",false);
            }
            DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(groupName);
            consumer.setNamesrvAddr(namesrvAddr);
            consumer.setConsumeThreadMin(consumeThreadMin);
            consumer.setConsumeThreadMax(consumeThreadMax);
            consumer.registerMessageListener(mqMessageListenerProcessor);
            /**
             * 设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费
             * 如果非第一次启动,那么按照上次消费的位置继续消费
             */
            consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
            /**
             * 设置消费模型,集群还是广播,默认为集群
             */
            //consumer.setMessageModel(MessageModel.CLUSTERING);
            /**
             * 设置一次消费消息的条数,默认为1条
             */
            consumer.setConsumeMessageBatchMaxSize(consumeMessageBatchMaxSize);
            try {
                /**
                 * 设置该消费者订阅的主题和tag,如果是订阅该主题下的所有tag,则tag使用*;如果需要指定订阅该主题下的某些tag,则使用||分割,例如tag1||tag2||tag3
                 */
                String[] topicTagsArr = topics.split(";");
                for (String topicTags : topicTagsArr) {
                    String[] topicTag = topicTags.split("~");
                    consumer.subscribe(topicTag[0],topicTag[1]);
                }
                consumer.start();
                LOGGER.info("consumer is start !!! groupName:{},topics:{},namesrvAddr:{}",groupName,topics,namesrvAddr);
            }catch (MQClientException e){
                LOGGER.error("consumer is start !!! groupName:{},topics:{},namesrvAddr:{}",groupName,topics,namesrvAddr,e);
                throw new RocketMQException(e);
            }
            return consumer;
        }
    }
    
    
  6. 打印信息 访问http://localhost:8888/api/v1/pay?text=你好
    RocketMQ 4.x入门到进阶_第3张图片

中间产生的问题

问题1: org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException:
sendDefaultImpl call timeout
原因:因为服务器存在多个网卡需要指定,客户端可访问的ip地址
解决: conf/broker.conf 增加 brokerIP1=你的客户端访问的ip

问题2:MQClientException: No route info of this topic, TopicTest
原因:Broker 禁止自动创建topic,且用户没有手动创建topic,或者是broker和Nameserver网络不通,以及客户端版本与服务端版本不一致
解决: sh bin/mqbroker -m 查看配置,autoCreateTopicEnable=true 则是自动创建(生产环境建议false);防火墙查看;控制中心创建topic;版本查看比较

其他错误:

https://blog.csdn.net/qq_14853889/article/details/81053145
https://blog.csdn.net/wangmx1993328/article/details/81588217
https://www.jianshu.com/p/bfd6d849f156
https://blog.csdn.net/wangmx1993328/article/details/81588217

第四章《RocketMQ 集群模式》

背景
随着业务发展,越来越多的应用接入了同一个中间件,耦合性太强。也觉得越来越不安心,不稳心。怕哪天宕了后,所有业务也跟着受到牵连影响。为了组件高可用,决定新搭建一套集群,业务后续逐步迁移过来。

1.单节点
指单个Master节点
优点:配置简单,同步刷盘不会丢失消息
缺点:不可靠,宕机会导致整个服务不可用

2.主从
指一个Master节点和一个Slave节点(异步,同步双写)
优点:同步双写消息不会丢失,异步复杂会存在少量丢失情况。主节点宕机,从节点可以对外提供消息的消费,但不支持写入。
缺点:主从会有短暂的消息延迟,毫秒级,目前版本不支持自动切换

3.双主
指两个或两个以上个Master节点
优点:配置简单,单个Master 宕机或重启维护对应用无影响,在磁盘配置为RAID10 时,即使机器宕机不可恢复情况下,由与 RAID10磁盘非常可靠,消息也不会丢(异步刷盘丢失少量消息,同步刷盘一条不丢)。性能最高。
缺点:单台机器宕机期间,这台机器上未被消费的消息在机器恢复之前不可订阅,消息实时性会受到受到影响。

4.多主多从(异步复制)
每个Master配置一个Slave,有多对Master-Slave,HA采用异步复制方式,主备有短暂消息延迟,毫秒级。
优点:即使磁盘损坏,消息丢失的非常少,且消息实时性不会受影响,因为Master宕机后,消费者仍然可以从Slave消费,此过程对应用透明。不需要人工干预。性能同多Master模式几乎一样。
缺点:Master宕机,磁盘损坏情况,会丢失少量消息。

5.多主多从(同步复制)
每个Master配置一个Slave,有多对Master-Slave,HA采用同步双写方式,主备都写成功,向应用返回成功。
优点:数据与服务都无单点,Master宕机情况下,消息无延迟,服务可用性与数据可用性都非常高
缺点:性能比异步复制模式略低,大约低10%左右,发送单个消息的RT会略高。目前主宕机后,备机不能自动切换为主机,后续会支持自动切换功能。

第五章《RocketMQ 消息高可靠》

同步复制:数据安全性高,性能低
异步复杂:数据可能丢失,性能高

1.同步双写,异步刷盘(SYNC_MASTER + ASYNC_FLUSH)
对于消息丢失容忍度很低的应用,官方建议采用SYNC_MASTER + ASYNC_FLUSH的方式,这样只有master和slave在刷盘前同时挂掉,且都没有刷到磁盘,消息才会丢失。这样是一个兼顾性能和可靠性的较好平衡
2.异步双写,异步刷盘(ASYNC_MASTER+ASYNC_FLUSH)
对消息丢失容忍度较高的应用,数据实时性更高

第六章《2m-2s-async 集群搭建》

ROCKETMQ集群安装-多Master多Slave模式,异步复制(2m-2s-async)

前期准备

server1 ssh [email protected]
server1 ssh [email protected]

1.修改RocketMQ 内存大小(内存够大可忽略)

[root@localhost bin]# pwd
/usr/local/software/rocketmq/apache-rocketmq/bin
[root@localhost bin]# vim runserver.sh 
JAVA_OPT="${JAVA_OPT} -server -Xms512m -Xmx512m -Xmn256m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"

[root@localhost bin]# pwd
/usr/local/software/rocketmq/apache-rocketmq/bin
[root@localhost bin]# vim runbroker.sh 
JAVA_OPT="${JAVA_OPT} -server -Xms512m -Xmx512m -Xmn256m"

2.两台服务器分别启动nameserver

nohup sh bin/mqnamesrv &
[root@localhost apache-rocketmq]# jps
27764 Jps
27701 NamesrvStartup

3.修改配置文件

Master节点修改conf/2m-2s-async/broker-a.properties

namesrvAddr=10.37.129.3:9876;10.37.129.4:9876
brokerClusterName=DefaultCluster
brokerName=broker-a
brokerId=0
deleteWhen=04
fileReservedTime=48
brokerRole=ASYNC_MASTER
flushDiskType=ASYNC_FLUSH

Slave节点修改conf/2m-2s-async/broker-a-s.properties

namesrvAddr=10.37.129.3:9876;10.37.129.4:9876
brokerClusterName=DefaultCluster
brokerName=broker-a
brokerId=1
deleteWhen=04
fileReservedTime=48
brokerRole=ASYNC_MASTER
flushDiskType=ASYNC_FLUSH

4.指定配置文件启动broker

Master 节点启动

nohup sh bin/mqbroker -c conf/2m-2s-async/broker-a.properties &

Slave 节点启动

nohup sh bin/mqbroker -c conf/2m-2s-async/broker-a-s.properties &

2019-04-09 16:07:19 INFO brokerOutApi_thread_1 - register broker to name server 10.37.129.3:9876 OK
2019-04-09 16:07:19 INFO brokerOutApi_thread_2 - register broker to name server 10.37.129.4:9876 OK

至此服务端已经搭建完成

5.mq管控台
搭建步骤参照第二章
修改application.properties里面nameserver地址,并重新打包启动

#if this value is empty,use env value rocketmq.config.namesrvAddr  NAMESRV_ADDR | now, you can set it in ops page.default localhost:9876
rocketmq.config.namesrvAddr=10.37.129.3:9876;10.37.129.4:9876

RocketMQ 4.x入门到进阶_第4张图片
中间产生的问题

问题1: ERROR Unexpected error occurred in scheduled task.
java.lang.RuntimeException: org.apache.rocketmq.remoting.exception.RemotingConnectException: connect to <172.17.0.1:10909> failed
at com.google.common.base.Throwables.propagate(Throwables.java:160)
原因:因为服务器存在多个网卡需要指定,客户端可访问的ip地址
解决: conf/2m-2s-async/broker-a.properties 增加 brokerIP1=你的客户端访问的ip

你可能感兴趣的:(java,RocketMQ,分布式)