RocketMQ在Springboot项目中的应用

注意

看此篇之前,你需要了解RocketMQ在本地是如何工作的,如果还不会请先去找找资源学习一下哦

配置文件

application.yml中关于RocketMQ的配置如下:

bg:
  rocketmq:
    #   namesrvAddr地址
    #    namesrvAddr: 0.0.0.0:9876
    namesrvAddr: 192.168.2.68:9876
    #  生产者group名称
    producerGroupName: producerGroupName
    #  事务生产者group名称
    transactionProducerGroupName: transactionProducerGroupName
    #  消费者group名称
    consumerGroupName: consumerGroupName
    #  生产者实例名称
    producerInstanceName: producerInstanceName
    #  消费者实例名称
    consumerInstanceName: consumerInstanceName
    #  事务生产者实例名称
    producerTranInstanceName: producerTranInstanceName
    #  一次最大消费多少数量消息
    consumerBatchMaxSize: 1
    #  广播消费
    consumerBroadcasting: false
    #  消费的topic:tag
    subscribe[0]: TopicTest1:TagA
    #  启动的时候是否消费历史记录
    enableHisConsumer: false
    #  启动顺序消费
    enableOrderConsumer: false

SpringBoot自动配置(核心)

需要写一个专门去读以bg.rockmq开头的配置并且需要注解

@Data
@ConfigurationProperties(RocketmqProperties.PREFIX)
public class RocketmqProperties {
    public static final String PREFIX = "dg.rocketmq";
    private String namesrvAddr;
    private String producerGroupName;
    private String transactionProducerGroupName;
    private String consumerGroupName;
    private String producerInstanceName;
    private String consumerInstanceName;
    private String producerTranInstanceName;
    private int consumerBatchMaxSize;
    private boolean consumerBroadcasting;
    private boolean enableHisConsumer;
    private boolean enableOrderConsumer;
    private List<String> subscribe = new ArrayList<>();
  }

根据SpringBoot的自动注入的原理还需要一个类来搞这个事

/**
 * Producer:消息生产者,负责生产消息,一般由业务系统负责生产消息。
 *
 *   Consumer:消息消费者,负责消费消息,一般是后台系统负责异步消费。
 *
 *   Push Consumer:Consumer的一种,应用通常向Consumer对象注册一个Listener接口,一旦收到消息,Consumer对象立刻回调Linsener接口方法
 *
 *   Pull Consumer:Consumer的一种,应用通常主动调用Consumer的拉消息方法从Broker拉消息,主动权由应用控制
 *
 *   Consumer Group:一类Consumer的集合名称,这类Consumer通常消费一类消息,且消费逻辑一致。
 *
 *   Broker:消息中转角色,负责存储消息,转发消息,一般也称为Server,在JMS规范中成为Provider
 *
 *   Topic: 一个Topic有四个Queue
 */

@Configuration("MQConfiguration")
@EnableConfigurationProperties(RocketmqProperties.class)
@ConditionalOnProperty(prefix = RocketmqProperties.PREFIX, value = "namesrvAddr")
public class RocketmqAutoConfiguration {
	private static final Logger log = LogManager.getLogger(RocketmqAutoConfiguration.class);
	@Autowired
	private RocketmqProperties properties;
	@Autowired
	private ApplicationEventPublisher publisher;

	private static boolean isFirstSub = true;

	private static long startTime = System.currentTimeMillis();

	/**
	 * 初始化向rocketmq发送普通消息的生产者
	 *
	 * @throws Exception
	 */
    @Bean("DefaultMQProducer")
	@ConditionalOnProperty(prefix = RocketmqProperties.PREFIX, value = "producerInstanceName")
	public DefaultMQProducer defaultProducer() throws Exception {
		/**
		 * 一个应用创建一个Producer,由应用来维护此对象,可以设置为全局对象或者单例<br>
		 * 注意:ProducerGroupName需要由应用来保证唯一<br>
		 * ProducerGroup这个概念发送普通的消息时,作用不大,但是发送分布式事务消息时,比较关键,
		 * 因为服务器会回查这个Group下的任意一个Producer
		 */
		DefaultMQProducer producer = new DefaultMQProducer(properties.getProducerGroupName());
		producer.setNamesrvAddr(properties.getNamesrvAddr());
		producer.setInstanceName(properties.getProducerInstanceName());
		producer.setVipChannelEnabled(false);
		producer.setRetryTimesWhenSendAsyncFailed(10);
		producer.setRetryTimesWhenSendFailed(10);
		/**
		 * Producer对象在使用之前必须要调用start初始化,初始化一次即可<br>
		 * 注意:切记不可以在每次发送消息时,都调用start方法
		 */
		producer.start();
		log.info("RocketMq defaultProducer Started.");
//		pubRegister(properties.getProducerGroupName());
		return producer;
	}

	/**
	 * 初始化向rocketmq发送事务消息的生产者
	 *
	 * @throws Exception
	 */
	@Bean("TransactionMQProducer")
	@ConditionalOnProperty(prefix = RocketmqProperties.PREFIX, value = "producerTranInstanceName")
	public TransactionMQProducer transactionProducer() throws Exception {
		/**
		 * 一个应用创建一个Producer,由应用来维护此对象,可以设置为全局对象或者单例<br>
		 * 注意:ProducerGroupName需要由应用来保证唯一<br>
		 * ProducerGroup这个概念发送普通的消息时,作用不大,但是发送分布式事务消息时,比较关键,
		 * 因为服务器会回查这个Group下的任意一个Producer
		 */
		TransactionMQProducer producer = new TransactionMQProducer(properties.getTransactionProducerGroupName());
		producer.setNamesrvAddr(properties.getNamesrvAddr());
		producer.setInstanceName(properties.getProducerTranInstanceName());
		producer.setRetryTimesWhenSendAsyncFailed(10);
		producer.setRetryTimesWhenSendFailed(10);
		TransactionListener transactionListener = new TransactionListenerImpl();
		ExecutorService executorService = new ThreadPoolExecutor(2, 5, 100, 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.setVipChannelEnabled(false);
		producer.setExecutorService(executorService);
        producer.setTransactionListener(transactionListener);

		/**
		 * Producer对象在使用之前必须要调用start初始化,初始化一次即可<br>
		 * 注意:切记不可以在每次发送消息时,都调用start方法
		 */
		producer.start();

		log.info("RocketMq TransactionMQProducer Started.");
//		pubRegister(properties.getProducerTranInstanceName());
		return producer;
	}

	/**
	 * 初始化rocketmq消息监听方式的消费者
	 *
	 * @throws Exception
	 */
	@Bean
	@ConditionalOnProperty(prefix = RocketmqProperties.PREFIX, value = "consumerInstanceName")
	public DefaultMQPushConsumer pushConsumer() throws Exception {
		DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(properties.getConsumerGroupName());
		consumer.setNamesrvAddr(properties.getNamesrvAddr());
		consumer.setInstanceName(properties.getConsumerInstanceName());
		//判断消息消费是否为广播模式
		if (properties.isConsumerBroadcasting()) {
			consumer.setMessageModel(MessageModel.BROADCASTING);
		}
		consumer.setConsumeMessageBatchMaxSize(
				properties.getConsumerBatchMaxSize() == 0 ? 1 : properties.getConsumerBatchMaxSize());// 设置批量消费,以提升消费吞吐量,默认是1
		consumer.setVipChannelEnabled(false);
		/**
		 * 订阅指定topic下tags
		 */
		List<String> subscribeList = properties.getSubscribe();
		for (String subscribe : subscribeList) {
			consumer.subscribe(subscribe.split(":")[0], subscribe.split(":")[1]);
//			subRegister(properties.getConsumerGroupName(), subscribe.split(":")[0]);
		}
		if (properties.isEnableOrderConsumer()) {
			consumer.registerMessageListener((List<MessageExt> msgs, ConsumeOrderlyContext context) -> {
				try {
					context.setAutoCommit(true);
					msgs = filter(msgs);
					if (msgs.size() == 0)
						return ConsumeOrderlyStatus.SUCCESS;
					this.publisher.publishEvent(new RocketmqEvent(msgs, consumer));
				} catch (Exception e) {
					e.printStackTrace();
					return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
				}
				// 如果没有return success,consumer会重复消费此信息,直到success。
				return ConsumeOrderlyStatus.SUCCESS;
			});
		} else {
			consumer.registerMessageListener((List<MessageExt> msgs, ConsumeConcurrentlyContext context) -> {
				try {
					msgs = filter(msgs);
					if (msgs.size() == 0)
						return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
					this.publisher.publishEvent(new RocketmqEvent(msgs, consumer));
				} catch (Exception e) {
					e.printStackTrace();
					return ConsumeConcurrentlyStatus.RECONSUME_LATER;
				}
				// 如果没有return success,consumer会重复消费此信息,直到success。
				return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
			});
		}
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					Thread.sleep(5000);// 延迟5秒再启动,主要是等待spring事件监听相关程序初始化完成,否则,回出现对RocketMQ的消息进行消费后立即发布消息到达的事件,然而此事件的监听程序还未初始化,从而造成消息的丢失
					/**
					 * Consumer对象在使用之前必须要调用start初始化,初始化一次即可<br>
					 */
					try {
						consumer.start();
					} catch (Exception e) {
						log.info("RocketMq pushConsumer Start failure!!!.");
						log.error(e.getMessage(), e);
					}
					log.info("RocketMq pushConsumer Started.");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}

		}).start();

		return consumer;
	}

	private List<MessageExt> filter(List<MessageExt> msgs) {
		if (isFirstSub&& !properties.isEnableHisConsumer()) {
			msgs = msgs.stream().filter(item -> startTime - item.getBornTimestamp() < 0).collect(Collectors.toList());
		}
		if (isFirstSub && msgs.size() > 0) {
			isFirstSub = false;
		}
		return msgs;
	}

}

工具类

上面用到的一个工具类RocketMqEvent,主要是把<msgs,consumer>封装成对象然后一起publishEvent

public class RocketmqEvent extends ApplicationEvent {
	private static final long serialVersionUID = -4468405250074063206L;
	private DefaultMQPushConsumer consumer;
	private List<MessageExt> msgs;

	public RocketmqEvent(List<MessageExt> msgs, DefaultMQPushConsumer consumer) throws Exception {
		super(msgs);
		this.consumer = consumer;
		this.setMsgs(msgs);
	}
}

而publishEvent是Spring的异步监听操作,所以紧跟着的是进入监听所以肯定有个监听器,配套起来看就是发布的时候发的参数是我们工具类RocketmqEvent对象,监听器收到的也是该对象
在这里插入图片描述
监听器会拿到该对象,之后通过对象里面的msgs属性来来记录日志

@Component
public class RocketMQListener implements ApplicationListener<RocketmqEvent> {
    private static final Logger LOGGER = LoggerFactory.getLogger(RocketMQListener.class);

    @Override
    public void onApplicationEvent(RocketmqEvent event) {
        List<MessageExt> msgs = event.getMsgs();
        for (MessageExt messageExt: msgs) {
            handle(messageExt);
        }
    }

    private void handle(MessageExt messageExt) {
        String key = messageExt.getKeys();
        byte[] body = messageExt.getBody();
        String value = new String(body, StandardCharsets.UTF_8);
        String topic = messageExt.getTopic();
        String tags = messageExt.getTags();
        LOGGER.info("Received message, key : {}, value : {}", key, value);
        System.out.println("topic:" + topic + ", tags:"+ tags);
    }
}

两个请求规则(pojo类)

MQRequestData

@Data
public class MQRequestData {
    /**
     * 消息主题
     */
    private String msgTopicName;

    /**
     * 消息主题标签
     */
    private String msgTagName;

    /**
     * 消息key
     */
    private String msgKey;

    /**
     * 消息内容主体
     */
    private String msgBody;
 }

MQResponseData

@Data
public class MQResponseData<T> {
	//相应码
    private String code;
    //响应信息
    private String msg;
    //响应体
    private T data;
}

目录

在这里插入图片描述

配置完毕之后Controller的架子就可以这么写让Service的实现类去具体操作消息队列(下一篇讲)
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

商朝

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

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

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

打赏作者

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

抵扣说明:

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

余额充值