rocketmq安装、启动、测试

rocketmq官网:http://rocketmq.apache.org

简介:

     RcoketMQ 是一款低延迟、高可靠、可伸缩、易于使用的消息中间件。具有以下特性:

  1. 支持发布/订阅(Pub/Sub)和点对点(P2P)消息模型
  2. 在一个队列中可靠的先进先出(FIFO)和严格的顺序传递
  3. 支持拉(pull)和推(push)两种消息模式
  4. 单一队列百万消息的堆积能力
  5. 支持多种消息协议,如 JMS、MQTT 等
  6. 分布式高可用的部署架构,满足至少一次消息传递语义
  7. 提供 docker 镜像用于隔离测试和云集群部署
  8. 提供配置、指标和监控等功能丰富的 Dashboard

1.下载

wget http://mirrors.hust.edu.cn/apache/rocketmq/4.2.0/rocketmq-all-4.2.0-source-release.zip

2.切换到下载目录进行解压到rocketmq-all-4.2.0目录

unzip  rocketmq-all-4.2.0-source-release.zip -d rocketmq-all-4.2.0

3.启动(切换到bin目录

3.1 启动namesrv:

      nohup sh mqnamesrv &

     查看启动日志:tail -f ~/logs/rocketmqlogs/namesrv.log

     启动成功:The Name Server boot success…

3.2 启动mqbroker

       nohup sh mqbroker -n localhost:9876 autoCreateTopicEnable=true &

      查看日志:tail -f ~/logs/rocketmqlogs/broker.log

      启动成功:register brok

4.专业术语介绍

producer

消息生产者,生产者的作用就是将消息发送到 MQ,生产者本身既可以产生消息,如读取文本信息等。也可以对外提供接口,由外部应用来调用接口,再由生产者将收到的消息发送到 MQ。

producer group

生产者组,简单来说就是多个发送同一类消息的生产者称之为一个生产者组。在这里可以不用关心,只要知道有这么一个概念即可。

consumer

消息消费者,简单来说,消费 MQ 上的消息的应用程序就是消费者,至于消息是否进行逻辑处理,还是直接存储到数据库等取决于业务需要。

consumer group

消费者组,和生产者类似,消费同一类消息的多个 consumer 实例组成一个消费者组。

topic

Topic 是一种消息的逻辑分类,比如说你有订单类的消息,也有库存类的消息,那么就需要进行分类,一个是订单 Topic 存放订单相关的消息,一个是库存 Topic 存储库存相关的消息。

message

Message 是消息的载体。一个 Message 必须指定 topic,相当于寄信的地址。Message 还有一个可选的 tag 设置,以便消费端可以基于 tag 进行过滤消息。也可以添加额外的键值对,例如你需要一个业务 key 来查找 broker 上的消息,方便在开发过程中诊断问题。

tag

标签可以被认为是对 Topic 进一步细化。一般在相同业务模块中通过引入标签来标记不同用途的消息。

broker

Broker 是 RocketMQ 系统的主要角色,其实就是前面一直说的 MQ。Broker 接收来自生产者的消息,储存以及为消费者拉取消息的请求做好准备。

name server

Name Server 为 producer 和 consumer 提供路由信息。

RocketMQ 架构

由这张图可以看到有四个集群,分别是 NameServer 集群、Broker 集群、Producer 集群和 Consumer 集群:

  1. NameServer: 提供轻量级的服务发现和路由。 每个 NameServer 记录完整的路由信息,提供等效的读写服务,并支持快速存储扩展。
  2. Broker: 通过提供轻量级的 Topic 和 Queue 机制来处理消息存储,同时支持推(push)和拉(pull)模式以及主从结构的容错机制。
  3. Producer:生产者,产生消息的实例,拥有相同 Producer Group 的 Producer 组成一个集群。
  4. Consumer:消费者,接收消息进行消费的实例,拥有相同 Consumer Group 的
    Consumer 组成一个集群。

简单说明一下图中箭头含义,从 Broker 开始,Broker Master1 和 Broker Slave1 是主从结构,它们之间会进行数据同步,即 Date Sync。同时每个 Broker 与
NameServer 集群中的所有节
点建立长连接,定时注册 Topic 信息到所有 NameServer 中。

Producer 与 NameServer 集群中的其中一个节点(随机选择)建立长连接,定期从 NameServer 获取 Topic 路由信息,并向提供 Topic 服务的 Broker Master 建立长连接,且定时向 Broker 发送心跳。Producer 只能将消息发送到 Broker master,但是 Consumer 则不一样,它同时和提供 Topic 服务的 Master 和 Slave
建立长连接,既可以从 Broker Master 订阅消息,也可以从 Broker Slave 订阅消息。

5.测试

 生产者:Producer.java

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.*;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

/**
 * rocketmq 生产者
 * create by zhaoyl at 2018-07-23
 */
@Service
public class Producer {
    private static final Logger LOGGER = LoggerFactory.getLogger(MQProducer.class);

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

    /**
     * 声明并初始化一个producer
     * 需要一个producerGroup 名字作为构造方法的参数,这里为grampus-order
     */
    private final DefaultMQProducer producer = new DefaultMQProducer("grampus-order");

    /**
     * 启动生产者
     */
    @PostConstruct
    public void start() {
        //生产者延在消费者之后执行
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                try {
                    LOGGER.info("MQ:启动生产者");
                    /**
                     * 设置NameServer地址
                     * 此处应改为实际NameServer地址,多个地址之间用;分隔
                     * NameServer的地址必须有,不一定非得写死在代码里,这里通过配置文件获取
                     */
                    producer.setNamesrvAddr(namesrvAddr);
                    /**
                     * 发送失败重试次数
                     */
                    producer.setRetryTimesWhenSendFailed(10);
                    /**
                     * 调用start()方法启动一个producer实例
                     */
                    producer.start();
                    timer.cancel();
                } catch (MQClientException e) {
                    LOGGER.error("MQ:启动生产者失败:{}-{}", e.getResponseCode(), e.getErrorMessage());
                    throw new RuntimeException(e.getMessage(), e);
                }
            }
        },2000);

    }

    /**
     * 发送消息
     *
     * @param data  消息内容
     * @param topic 主题
     * @param tags  标签 如不需要消费topic下面的所有消息,通过tag进行消息过滤
     * @param keys  唯一主键
     */
    public void sendMessage(String data, String topic, String tags, String keys) {
        try {
            byte[] messageBody = data.getBytes(RemotingHelper.DEFAULT_CHARSET);

            Message mqMsg = new Message(topic, tags, keys, messageBody);

            producer.send(mqMsg, new SendCallback() {
                @Override
                public void onSuccess(SendResult sendResult) {
                    LOGGER.info("MQ: 生产者发送消息 {}", sendResult);
                }

                @Override
                public void onException(Throwable throwable) {
                    LOGGER.error(throwable.getMessage(), throwable);
                }
            });
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
        }
    }

    /**
     * 发送消息根据orderId到指定队列,解决消息的顺序问题
     * @param data
     * @param topic
     * @param tags
     * @param keys
     */
    public void sendMessageByOrderId(String data, String topic, String tags, String keys) {
        try {
            if(StringUtils.isBlank(data)){
                return;
            }
            JSONObject jsonObject = JSONObject.parseObject(data);

            //获取订单id
            String orderId = jsonObject.getString("orderId");
            byte[] messageBody = data.getBytes(RemotingHelper.DEFAULT_CHARSET);
            Message mqMsg = new Message(topic, tags, keys, messageBody);
            producer.send(mqMsg, new MessageQueueSelector() {
                @Override
                public MessageQueue select(List<MessageQueue> list, Message msg, Object arg) {
                    //按照订单号发送到固定的队列
                    int index = arg.hashCode() % list.size();
                    return list.get(index);
                }
            },orderId,new SendCallback() {
                @Override
                public void onSuccess(SendResult sendResult) {
                    LOGGER.info("MQ: 生产者发送消息 {}", sendResult);
                }

                @Override
                public void onException(Throwable throwable) {
                    LOGGER.error(throwable.getMessage(), throwable);
                }
            });

        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
        }
    }

    @PreDestroy
    public void stop() {
        if (producer != null) {
            producer.shutdown();
            LOGGER.info("MQ:关闭生产者");
        }
    }
}

 消费者:Consumer.java

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.mryx.grampus.order.api.IGOrderDetailService;
import com.mryx.grampus.order.api.IGOrderService;
import com.mryx.grampus.order.pojo.param.OrderQueryBean;
import com.mryx.grampus.order.pojo.result.OrderBase;
import com.mryx.grampus.order.pojo.result.OrderInfo;
import com.mryx.grampus.saas.domain.SaasOrder;
import com.mryx.grampus.saas.rpc.ISaasCooperatorService;
import com.mryx.grampus.saas.rpc.ISaasOrderDetailService;
import com.mryx.grampus.saas.rpc.ISaasOrderService;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * rocketmq 消费者
 * create by zhaoyl at 2018-07-23
 */
@Service
public class Consumer{
    private static final Logger LOGGER = LoggerFactory.getLogger(Consumer.class);

    ExecutorService threadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

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

    @Resource
    private IGOrderService gOrderService;

    @Resource
    private IGOrderDetailService gOrderDetailService;

    @Resource
    private ISaasOrderService saasOrderService;

    @Resource
    private ISaasOrderDetailService saasOrderDetailService;

    @Resource
    private ISaasCooperatorService saasCooperatorService;

    private final DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("grampus-order");
    /**
     * 初始化
     *
     * @throws MQClientException
     */
    @PostConstruct
    public void start() {
        try {
            LOGGER.info("MQ:启动消费者");
            /**
             * NameServer的地址和端口,多个逗号分隔开,达到消除单点故障的目的
             */
            consumer.setNamesrvAddr(namesrvAddr);
            /**
            *  批量消费的数量
            *  1.如果consumer先启动,producer发一条consumer消费一条
            *  2.如果consumer后启动,mq堆积数据,consumer每次消费设置的数量
            */
            consumer.setConsumeMessageBatchMaxSize(5);
            /**
             * consumer的消费策略
             * CONSUME_FROM_LAST_OFFSET 默认策略,从该队列最尾开始消费,即跳过历史消息
             * CONSUME_FROM_FIRST_OFFSET 从队列最开始开始消费,即历史消息(还储存在broker的)全部消费一遍
             * CONSUME_FROM_TIMESTAMP 从某个时间点开始消费,和setConsumeTimestamp()配合使用,默认是半个小时以前
             */
            consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
            /**
             * 消费模式:
             * 1.CLUSTERING:集群,默认
             *   同一个Group里每个consumer只消费订阅消息的一部分内容,也就是同一groupName,所有消费的内容加起来才是订阅topic内容的整体,达到负载均衡的目的
             * 2.BROADCASTING:广播模式
             *   同一个Group里每个consumer都能消费到所订阅topic的全部消息,也就是一个消息会被分发多次,被多个consumer消费
             *   广播消息只发送一次,没有重试
             */
           consumer.setMessageModel(MessageModel.CLUSTERING);
            /**
             * 设置consumer所订阅的Topic和Tag,*代表全部的Tag
             */
            consumer.subscribe(TopicEnum.ORDER.getValue(), "*");
            // 注册消息监听器
            consumer.registerMessageListener(new MessageListenerConcurrently() {
                /**
                 * 消费消息
                 * @param msgs
                 * @param context
                 * @return
                 */
                @Override
                public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                    int index = 0;
                    try {
                        for (; index < msgs.size(); index++) {
                            MessageExt msg = msgs.get(index);
                            String messageBody = new String(msg.getBody(), RemotingHelper.DEFAULT_CHARSET);
                            LOGGER.info("收到消息:" + messageBody);
                            /*threadPool.execute(new Runnable() {
                                @Override
                                public void run() {
                                    if(TopicEnum.ORDER.getValue().equals(msg.getTopic())){
                                        //订单消息处理
                                        handelOrderMsg(messageBody);
                                    }
                                }
                            });*/
                        }
                    } catch (Exception e) {
                        LOGGER.error(e.getMessage(), e);
                        /**
                         * 重试机制(consumer),仅限于CLUSTERING模式
                         * 1.exception的情况,一般重复16次 10s、30s、1分钟、2分钟、3分钟等等
                         *   获取重试次数:msgs.get(0).getReconsumeTimes()
                         * 2.超时的情况,这种情况MQ会无限制的发送给消费端
                         *   就是由于网络的情况,MQ发送数据之后,Consumer端并没有收到导致超时。也就是消费端没有给我返回return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;这样的就认为没有到达Consumer端
                         */
                        return ConsumeConcurrentlyStatus.RECONSUME_LATER;
                    } finally {
                        if (index < msgs.size()) {
                            context.setAckIndex(index + 1);
                        }
                    }
                    /**
                     * 返回消费状态:
                     * CONSUME_SUCCESS 消费成功
                     * RECONSUME_LATER 消费失败,需要稍后重新消费
                     */
                    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                }
            });
            // 启动消费端
            consumer.start();
        } catch (MQClientException e) {
            LOGGER.error("MQ:启动消费者失败:{}-{}", e.getResponseCode(), e.getErrorMessage());
            throw new RuntimeException(e.getMessage(), e);
        }

    }

    @PreDestroy
    public void stop() {
        if (consumer != null) {
            consumer.shutdown();
            LOGGER.error("MQ:关闭消费者");
        }
    }

猜你喜欢

转载自blog.csdn.net/ntotl/article/details/81181484