简单的RocketMQ生产者和消费者的开发示例demo(二)

上一篇文章介绍了RocketMQ双Master+双Slave集群+可视化控制台环境搭建,这篇主要介绍一下Producer和Consumer的简单开发示例

Producer

创建个SpringBoot项目,配置下maven依赖,用的是4.7.0的RocketMQ,所以Manve也用4.7.0(版本要对应上)

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

在application.yml配置文件下配置RocketMQ nameServer地址和Topic

apache:
  rocketmq:
    namesrvAddr: 192.168.135.73:9876
    producer:
      producerGroup: wxProducer
      topic: logTopic

之后我们来编写个Producer的启动类,在项目启动时自动执行Producer的start初始化方法

@Component
public class RocketMQProducer {

    private static Logger logger = LoggerFactory.getLogger(RocketMQProducer.class);
    /**
     * 生产者的组名
     */
    @Value("${apache.rocketmq.producer.producerGroup}")
    private String producerGroup;

    /**
     * NameServer 地址
     */
    @Value("${apache.rocketmq.namesrvAddr}")
    private String namesrvAddr;

    public DefaultMQProducer producer = null;

    /**
     * @PostContruct是java框架的注解,在方法上加该注解会在项目启动的时候执行该方法,也可以理解为在spring容器初始化的时候执行该方法。
     */
    @PostConstruct
    public void defaultMQProducer() {
        //生产者的组名
        producer = new DefaultMQProducer(producerGroup);
        //指定NameServer地址,多个地址以 ; 隔开
        producer.setNamesrvAddr(namesrvAddr);
        try {
            /**
             * Producer对象在使用之前必须要调用start初始化,初始化一次即可
             * 注意:切记不可以在每次发送消息时,都调用start方法
             */
            producer.start();
            logger.info("defaultMQProducer start ");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @PreDestroy
    public void closeMQProduce(){
        producer.shutdown();
        logger.info("defaultMQProducer shutdown ");
    }
}

准备工作已经完成,现在来编写message的底层发送方法

@Component
public class RocketSendEntranceToMessage {

    private static Logger logger = LoggerFactory.getLogger(RocketSendEntranceToMessage.class);

    /**
     * MQ消息发送类
     */
    @Autowired
    private RocketSendMessage rocketSendMessage;

    /**
     * MQ向外暴漏的总入口
     * @author wang xin.
     * @date 2021/05/18.
     * @param flag, commandRecords
     * @return void.
     */
    public void sendMqDisposeParam(String tag, String key, int flag, String context){
        //封装参数到POJO,调用发送MQ方法
        MQCommandRecord mqCommandRecord = new MQCommandRecord();
        try {
            mqCommandRecord.setTag(tag);//tag
            mqCommandRecord.setKey(key);//类型
            mqCommandRecord.setFlag(flag);//flag标识码
            mqCommandRecord.setContext(context);//备注
            sendMqToManage(mqCommandRecord);
        }catch (Exception e) {
            logger.error("",e);
        }
    }

    /**
     * MQ发送命令总入口
     * @author wang xin.
     * @date 2021/05/18.
     * @param mqCommandRecord
     * @return void.
     */
    private void sendMqToManage(MQCommandRecord mqCommandRecord){
        try {
            //发送Mq到应用层
            rocketSendMessage.sendMessage(mqCommandRecord.getTag(), mqCommandRecord.getKey(), mqCommandRecord.getFlag(), mqCommandRecord.getContext());
        }catch (Exception e) {
            logger.error("",e);
        }
    }
}
@Component
public class RocketSendMessage {
    private static Logger logger = LoggerFactory.getLogger(RocketSendMessage.class);

    @Autowired
    RocketMQProducer rocketMQProvider;

    /**
     * topic标识
     */
    @Value("${apache.rocketmq.producer.topic}")
    private String topic;

    /**
     *
     * @param Tag 类型
     * @param key 存储数据库key
     * @param context 存储记录的table
     * @return 提交mq的结果值
     */
    public boolean sendMessage(String Tag, String key, int flag, String context){
        boolean res = false;
        Message message;
        try {
            message = new Message(topic, Tag, key, flag, context.getBytes("UTF-8"), true);
            SendResult result = rocketMQProvider.producer.send(message);
            if(result.getSendStatus().compareTo(SendStatus.SEND_OK) == 0){
                res = true;
            }
            logger.info("MQProducer send message time = {},Tag = {} key = {} context = {}",System.currentTimeMillis(), Tag, key, context);
        }catch (Exception e){
            //mq发送异常处理
            logger.error("",e);
        }
        return res;
    }
}

一些参数介绍:

  1.     topic:表示消息要到的发送主题,必填
  2.     tags:表示消息的标签,消费者在消费时,可以根据标签进行过滤,需要注意的是,一个生产者,只能指定一个tag
  3.     keys:用于建立索引,之后可以通过命令工具/API/或者管理平台查询key,可以为一个消息设置多个key,用空格""进行分割
  4.     flag:选填,消息的标记,完全由应用设置,RocketMQ不做任何处理,类似于memcached中flag的作用。
  5.     body:消息的内容,这是一个字节数组,序列化方式由应用决定,例如你可以将一个json转为字节数组,也可以通过protol buffer、hessian编码转为字节数组。
  6.     waitStoreMsgOK:表示发送消息后,是否需要等待消息同步刷新到磁盘上。如果broker配置为ASYNC_MASTER,那么只需要消息在master上刷新到磁盘即可;如果配置为SYNC_MASTER,那么还需要等待slave也刷新到磁盘。需要注意的是,waitStoreMsgOK默认为false,只有将设置为true的情况下,才会等待刷盘成功再返回。

我们提交一个请求测试下

    @Autowired
    private RocketSendEntranceToMessage rocketSendEntranceToMessage;

    @GetMapping("/sendMQMessage")
    public Map sendMQMessage() {
        Map<String, Object> returnMap = new HashMap<>();
        //声明发送的MQ消息
        rocketSendEntranceToMessage.sendMqDisposeParam("123456789", "5", 1, JSON.toJSONString("我只是测试发送的MQ消息,嘿嘿"));
        return returnMap;
    }

控制台输出

测试的这条消息已经上传到RocketMQ集群下的broker-b下master和savle节点下,Today Consume Count为0,代表还未消费

用到的pojo类

public class MQCommandRecord implements Serializable{

    @Getter
    @Setter
    private String tag;

    @Getter
    @Setter
    private String key;

    @Getter
    @Setter
    private int flag;

    @Getter
    @Setter
    private String  context;

    @Override
    public String toString() {
        return "MQCommandRecord{" +
                "tag='" + tag + '\'' +
                ", key='" + key + '\'' +
                ", flag=" + flag +
                ", context='" + context + '\'' +
                '}';
    }
}

至此,producer的示例代码已经全部完成

Consumer

和producer流程相似,创建个SpringBoot项目,配置下maven依赖

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

在application.yml配置文件下配置RocketMQ nameServer地址和Topic

 
apache:
  rocketmq:
    namesrvAddr: 192.168.135.73:9876
    consumer:
      consumeGroup: wxConsume
      topic: logTopic

之后编写个Consumer的启动类,设置要消费的Topic和Tags,并且设置是以push形式消费数据还是pull形式,本示例是以push形式(其实pull形式的底层还是以push为实现)

@Component
public class RocketMQConsumer {

    private static Logger logger = LoggerFactory.getLogger(RocketMQConsumer.class);

    /**
     * 消费者的组名
     */
    @Value("${apache.rocketmq.consumer.consumeGroup}")
    private String consumerGroup;

    /**
     * NameServer 地址
     */
    @Value("${apache.rocketmq.namesrvAddr}")
    private String namesrvAddr;

    /**
     * topic标识
     */
    @Value("${apache.rocketmq.consumer.topic}")
    private String topic;

    private DefaultMQPushConsumer consumer;

    @Autowired
    private ParseMQCommand parseMQCommand;

    /**
     * @PostContruct是spring框架的注解,在方法上加该注解会在项目启动的时候执行该方法,也可以理解为在spring容器初始化的时候执行该方法。
     */
    @PostConstruct
    public void defaultMQPushConsumer() {
        //消费者的组名
        consumer = new DefaultMQPushConsumer(consumerGroup);
        //指定NameServer地址,多个地址以 ; 隔开
        consumer.setNamesrvAddr(namesrvAddr);
        try {
            //订阅PushTopic下Tag为push的消息
            consumer.subscribe(topic, "*");
            //设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费
            //如果非第一次启动,那么按照上次消费的位置继续消费
            consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
            consumer.registerMessageListener((MessageListenerConcurrently) (list, context) -> {
                try {
                    for (MessageExt messageExt : list) {
                        String paramContext = new String(messageExt.getBody(), "UTF-8");
                        MQCommandRecord mqCommandRecord = new MQCommandRecord();
                        mqCommandRecord.setTag(messageExt.getTags());
                        mqCommandRecord.setKey(messageExt.getKeys());
                        mqCommandRecord.setFlag(messageExt.getFlag());
                        mqCommandRecord.setContext(paramContext);
                        parseMQCommand.addMQCommandRecord(mqCommandRecord);
                        logger.info("MQ consume receive time = {} ,消息体 {}",System.currentTimeMillis(), mqCommandRecord.getContext());
                    }
                } catch (Exception e) {
                    logger.error("",e);
                    return ConsumeConcurrentlyStatus.RECONSUME_LATER; //稍后再试
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; //消费成功
            });
            consumer.start();
            logger.info(" defaultMQPushConsumer start ");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @PreDestroy
    public void closeDefaultMQPushConsumer(){
        consumer.shutdown();
        logger.info(" defaultMQPushConsumer shutdown");
    }
}
/**
 * 公共方法
 * 接受生产组发送的MQ消息
 * @author wang xin.
 * @version 1.0
 * @date 2021/05/18 11:08.
 * @Copyright:2021 汉王智远科技有限公司 All rights reserved.
 */
@Component
public class ParseMQCommand extends Thread {

    private static Logger logger = LoggerFactory.getLogger(ParseMQCommand.class);

    private ArrayDeque<MQCommandRecord> adqs = new ArrayDeque<>();

    private volatile boolean isWait = false;


    @Override
    public void run() {
        for(;;){
            if(adqs.isEmpty()){
                synchronized (ParseMQCommand.class){
                    try {
                        isWait = true;
                        ParseMQCommand.class.wait();
                    } catch (InterruptedException e) {
                        logger.error("",e);
                    }
                }
            }else{
                try {
                    MQCommandRecord mqCommandRecord = adqs.poll();
                    //具体业务处理
                } catch (Exception e) {
                    logger.error("",e);
                }
            }
        }
    }

    public void addMQCommandRecord(MQCommandRecord mqCommandRecord){
        adqs.addLast(mqCommandRecord);
        if(isWait){
            synchronized(ParseMQCommand.class){
                ParseMQCommand.class.notifyAll();
            }
        }
    }
}

用到的pojo类

public class MQCommandRecord implements Serializable{

    @Getter
    @Setter
    private String tag;

    @Getter
    @Setter
    private String key;

    @Getter
    @Setter
    private int flag;

    @Getter
    @Setter
    private String  context;

    @Override
    public String toString() {
        return "MQCommandRecord{" +
                "tag='" + tag + '\'' +
                ", key='" + key + '\'' +
                ", flag=" + flag +
                ", context='" + context + '\'' +
                '}';
    }
}

消费者端也已经完成,因为之前的生产者已经生成了一条消息到RocketMQ的集群,所以我们在启动消费者端的时候就会消费这条消息,会在控制台打印出来这条消息

并且RocketMQ的可视化控制台也显示了此条消息已经消费成功

至此,生产者端和消费者端的demo已经完成,这只是一个非常简单的示例demo,实际项目远比此复杂的多,需要考虑到重复消费,消息丢失,消息积压,机器的扩容方式,消息的峰值处理等等问题

 

 

 

 

 

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值