SpringBoot如何优雅的使用RocketMQ

MQ,是一种跨进程的通信机制,用于上下游传递消息。在传统的互联网架构中通常使用MQ来对上下游来做解耦合。

举例:当A系统对B系统进行消息通讯,如A系统发布一条系统公告,B系统可以订阅该频道进行系统公告同步,整个过程中A系统并不关系B系统会不会同步,由订阅该频道的系统自行处理。

什么是RocketMQ?

官方说明:

随着使用越来越多的队列和虚拟主题,ActiveMQ IO模块遇到了瓶颈。我们尽力通过节流,断路器或降级来解决此问题,但效果不佳。因此,我们那时开始关注流行的消息传递解决方案Kafka。不幸的是,Kafka不能满足我们的要求,特别是在低延迟和高可靠性方面。

看到这里可以很清楚的知道RcoketMQ 是一款低延迟、高可靠、可伸缩、易于使用的消息中间件。

具有以下特性:

  • 支持发布/订阅(Pub/Sub)和点对点(P2P)消息模型
  • 能够保证严格的消息顺序,在一个队列中可靠的先进先出(FIFO)和严格的顺序传递
  • 提供丰富的消息拉取模式,支持拉(pull)和推(push)两种消息模式
  • 单一队列百万消息的堆积能力,亿级消息堆积能力
  • 支持多种消息协议,如 JMS、MQTT 等
  • 分布式高可用的部署架构,满足至少一次消息传递语义

RocketMQ环境安装

下载地址:https://rocketmq.apache.org/dowloading/releases/

从官方下载二进制或者源码来进行使用。源码编译需要Maven3.2x,JDK8

在根目录进行打包:

mvn -Prelease-all -DskipTests clean packager -U

distribution/target/apache-rocketmq文件夹中会存在一个文件夹版,zip,tar三个可运行的完整程序。

使用rocketmq-4.6.0.zip:

  1. 启动名称服务 mqnamesrv.cmd
  2. 启动数据中心 mqbroker.cmd -n localhost:9876

SpringBoot环境中使用RocketMQ

SpringBoot 入门:https://www.cnblogs.com/SimpleWu/p/10027237.html

SpringBoot 常用start:https://www.cnblogs.com/SimpleWu/p/9798146.html

当前环境版本为:

  • SpringBoot 2.0.6.RELEASE
  • SpringCloud Finchley.RELEASE
  • SpringCldod Alibaba 0.2.1.RELEASE
  • RocketMQ 4.3.0

    在项目工程中导入:
<!-- MQ Begin -->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>${rocketmq.version}</version>
</dependency>
<!-- MQ End -->

由于我们这边已经有工程了所以就不在进行创建这种过程了。主要是看看如何使用RocketMQ。

创建RocketMQProperties配置属性类,类中内容如下:

@ConfigurationProperties(prefix = "rocketmq")
public class RocketMQProperties {
private boolean isEnable = false;
private String namesrvAddr = "localhost:9876";
private String groupName = "default";
private int producerMaxMessageSize = 1024;
private int producerSendMsgTimeout = 2000;
private int producerRetryTimesWhenSendFailed = 2;
private int consumerConsumeThreadMin = 5;
private int consumerConsumeThreadMax = 30;
private int consumerConsumeMessageBatchMaxSize = 1;
//省略get set
}

现在我们所有子系统中的生产者,消费者对应:

isEnable 是否开启mq

namesrvAddr 集群地址

groupName 分组名称

设置为统一已方便系统对接,如有其它需求在进行扩展,类中我们已经给了默认值也可以在配置文件或配置中心中获取配置,配置如下:

#发送同一类消息的设置为同一个group,保证唯一,默认不需要设置,rocketmq会使用ip@pid(pid代表jvm名字)作为唯一标示
rocketmq.groupName=please_rename_unique_group_name
#是否开启自动配置
rocketmq.isEnable=true
#mq的nameserver地址
rocketmq.namesrvAddr=127.0.0.1:9876
#消息最大长度 默认1024*4(4M)
rocketmq.producer.maxMessageSize=4096
#发送消息超时时间,默认3000
rocketmq.producer.sendMsgTimeout=3000
#发送消息失败重试次数,默认2
rocketmq.producer.retryTimesWhenSendFailed=2
#消费者线程数量
rocketmq.consumer.consumeThreadMin=5
rocketmq.consumer.consumeThreadMax=32
#设置一次消费消息的条数,默认为1条
rocketmq.consumer.consumeMessageBatchMaxSize=1

创建消费者接口 RocketConsumer.java 该接口用户约束消费者需要的核心步骤:

/**
* 消费者接口
*
* @author SimpleWu
*
*/
public interface RocketConsumer { /**
* 初始化消费者
*/
public abstract void init(); /**
* 注册监听
*
* @param messageListener
*/
public void registerMessageListener(MessageListener messageListener); }

创建抽象消费者 AbstractRocketConsumer.java:

/**
* 消费者基本信息
*
* @author SimpelWu
*/
public abstract class AbstractRocketConsumer implements RocketConsumer { protected String topics;
protected String tags;
protected MessageListener messageListener;
protected String consumerTitel;
protected MQPushConsumer mqPushConsumer; /**
* 必要的信息
*
* @param topics
* @param tags
* @param consumerTitel
*/
public void necessary(String topics, String tags, String consumerTitel) {
this.topics = topics;
this.tags = tags;
this.consumerTitel = consumerTitel;
} public abstract void init(); @Override
public void registerMessageListener(MessageListener messageListener) {
this.messageListener = messageListener;
} }

在类中我们必须指定这个topics,tags与消息监听逻辑

public abstract void init();该方法是用于初始化消费者,由子类实现。

接下来我们编写自动配置类RocketMQConfiguation.java,该类用户初始化一个默认的生产者连接,以及加载所有的消费者。

@EnableConfigurationProperties({ RocketMQProperties.class }) 使用该配置文件

@Configuration 标注为配置类

@ConditionalOnProperty(prefix = "rocketmq", value = "isEnable", havingValue = "true") 只有当配置中指定rocketmq.isEnable = true的时候才会生效

核心内容如下:

/**
* mq配置
*
* @author SimpleWu
*/
@Configuration
@EnableConfigurationProperties({ RocketMQProperties.class })
@ConditionalOnProperty(prefix = "rocketmq", value = "isEnable", havingValue = "true")
public class RocketMQConfiguation { private RocketMQProperties properties; private ApplicationContext applicationContext; private Logger log = LoggerFactory.getLogger(RocketMQConfiguation.class); public RocketMQConfiguation(RocketMQProperties properties, ApplicationContext applicationContext) {
this.properties = properties;
this.applicationContext = applicationContext;
} /**
* 注入一个默认的消费者
* @return
* @throws MQClientException
*/
@Bean
public DefaultMQProducer getRocketMQProducer() throws MQClientException {
if (StringUtils.isEmpty(properties.getGroupName())) {
throw new MQClientException(-1, "groupName is blank");
} if (StringUtils.isEmpty(properties.getNamesrvAddr())) {
throw new MQClientException(-1, "nameServerAddr is blank");
}
DefaultMQProducer producer;
producer = new DefaultMQProducer(properties.getGroupName()); producer.setNamesrvAddr(properties.getNamesrvAddr());
// producer.setCreateTopicKey("AUTO_CREATE_TOPIC_KEY"); // 如果需要同一个jvm中不同的producer往不同的mq集群发送消息,需要设置不同的instanceName
// producer.setInstanceName(instanceName);
producer.setMaxMessageSize(properties.getProducerMaxMessageSize());
producer.setSendMsgTimeout(properties.getProducerSendMsgTimeout());
// 如果发送消息失败,设置重试次数,默认为2次
producer.setRetryTimesWhenSendFailed(properties.getProducerRetryTimesWhenSendFailed()); try {
producer.start();
log.info("producer is start ! groupName:{},namesrvAddr:{}", properties.getGroupName(),
properties.getNamesrvAddr());
} catch (MQClientException e) {
log.error(String.format("producer is error {}", e.getMessage(), e));
throw e;
}
return producer; } /**
* SpringBoot启动时加载所有消费者
*/
@PostConstruct
public void initConsumer() {
Map<String, AbstractRocketConsumer> consumers = applicationContext.getBeansOfType(AbstractRocketConsumer.class);
if (consumers == null || consumers.size() == 0) {
log.info("init rocket consumer 0");
}
Iterator<String> beans = consumers.keySet().iterator();
while (beans.hasNext()) {
String beanName = (String) beans.next();
AbstractRocketConsumer consumer = consumers.get(beanName);
consumer.init();
createConsumer(consumer);
log.info("init success consumer title {} , toips {} , tags {}", consumer.consumerTitel, consumer.tags,
consumer.topics);
}
} /**
* 通过消费者信心创建消费者
*
* @param consumerPojo
*/
public void createConsumer(AbstractRocketConsumer arc) {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(this.properties.getGroupName());
consumer.setNamesrvAddr(this.properties.getNamesrvAddr());
consumer.setConsumeThreadMin(this.properties.getConsumerConsumeThreadMin());
consumer.setConsumeThreadMax(this.properties.getConsumerConsumeThreadMax());
consumer.registerMessageListener(arc.messageListenerConcurrently);
/**
* 设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费 如果非第一次启动,那么按照上次消费的位置继续消费
*/
// consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
/**
* 设置消费模型,集群还是广播,默认为集群
*/
// consumer.setMessageModel(MessageModel.CLUSTERING); /**
* 设置一次消费消息的条数,默认为1条
*/
consumer.setConsumeMessageBatchMaxSize(this.properties.getConsumerConsumeMessageBatchMaxSize());
try {
consumer.subscribe(arc.topics, arc.tags);
consumer.start();
arc.mqPushConsumer=consumer;
} catch (MQClientException e) {
log.error("info consumer title {}", arc.consumerTitel, e);
} } }

然后在src/main/resources文件夹中创建目录与文件META-INF/spring.factories里面添加自动配置类即可开启启动配置,我们只需要导入依赖即可:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.xcloud.config.rocketmq.RocketMQConfiguation

接下来在服务中导入依赖,然后通过我们的抽象类获取所有必要信息对消费者进行创建,该步骤会在所有消费者初始化完成后进行,且只会管理是Spring Bean的消费者。

下面我们看看如何创建一个消费者,创建消费者的步骤非常简单,只需要继承AbstractRocketConsumer然后再加上Spring的@Component就能够完成消费者的创建,我们可以在类中自定义消费的主题与标签。

在项目可以根据需求当消费者创建失败的时候是否继续启动工程。

创建一个默认的消费者 DefaultConsumerMQ.java

@Component
public class DefaultConsumerMQ extends AbstractRocketConsumer {
/**
* 初始化消费者
*/
@Override
public void init() {
// 设置主题,标签与消费者标题
super.necessary("TopicTest", "*", "这是标题");
//消费者具体执行逻辑
registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
msgs.forEach(msg -> {
System.out.printf("consumer message boyd %s %n", new String(msg.getBody()));
});
// 标记该消息已经被成功消费
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
}
}

super.necessary("TopicTest", "*", "这是标题"); 是必须要设置的,代表该消费者监听TopicTest主题下所有tags,标题那个字段是我自己定义的,所以对于该配置来说没什么意义。

我们可以在这里注入Spring的Bean来进行任意逻辑处理。

创建一个消息发送类进行测试

@Override
public String qmtest(@PathVariable("name")String name) throws MQClientException, RemotingException, MQBrokerException, InterruptedException, UnsupportedEncodingException {
Message msg = new Message("TopicTest", "tags1", name.getBytes(RemotingHelper.DEFAULT_CHARSET));
// 发送消息到一个Broker
SendResult sendResult = defaultMQProducer.send(msg);
// 通过sendResult返回消息是否成功送达
System.out.printf("%s%n", sendResult);
return null;
}

我们来通过Http请求测试:

http://localhost:10001/demo/base/mq/hello  consumer message boyd hello
http://localhost:10001/demo/base/mq/嘿嘿嘿嘿嘿 consumer message boyd 嘿嘿嘿嘿嘿

好了到这里简单的start算是设计完成了,后面还有一些:顺序消息生产,顺序消费消息,异步消息生产等一系列功能,官人可参照官方去自行处理。

  • ActiveMQ 没经过大规模吞吐量场景的验证,社区不高不活跃。
  • RabbitMQ 集群动态扩展麻烦,且与当前程序语言不至于难以定制化。
  • kafka 支持主要的MQ功能,功能无法达到程序需求的要求,所以不使用,且与当前程序语言不至于难以定制化。
  • rocketMQ 经过全世界的女人的洗礼,已经很强大;MQ功能较为完善,还是分布式的,扩展性好;支持复杂MQ业务场景。(业务复杂可做首选)

SpringBoot如何优雅的使用RocketMQ的更多相关文章

  1. SpringBoot如何优雅关闭(SpringBoot2.3&Spring Boot2.2)

    SpringBoot如何优雅关闭(SpringBoot2.3&Spring Boot2.2) 优雅停止&暴力停止 暴力停止:像日常开发过程中,测试区或者本地开发时,我们并不会考虑项目关 ...

  2. SpringBoot:如何优雅地处理全局异常?

    之前用springboot的时候,只知道捕获异常使用try{}catch,一个接口一个try{}catch,这也是大多数开发人员异常处理的常用方式,虽然屡试不爽,但会造成一个问题,就是一个Contro ...

  3. SpringBoot Validation优雅的全局参数校验

    前言 我们都知道在平时写controller时候,都需要对请求参数进行后端校验,一般我们可能会这样写 public String add(UserVO userVO) { if(userVO.getA ...

  4. springboot mybatis优雅的添加多数据源

    springboot的原则是简化配置,本文试图不通过xml配置,使用configuration配置数据源,并进行简单的数据访问. 并且配置了多数据源,在开发过程中这种场景很容易遇到. 1.依赖 spr ...

  5. SpringBoot实现优雅的关机

    最近在公司使用了 Springboot 项目, 发现在   linux  上 通过 java -jar 命令可以十分安全的运行, 但是 当我们需要关闭它的时候呢? 难道  登陆服务器 kill 线程? ...

  6. Springboot如何优雅的解决ajax+自定义headers的跨域请求

    1.什么是跨域 由于浏览器同源策略(同源策略,它是由Netscape提出的一个著名的安全策略.现在所有支持JavaScript 的浏览器都会使用这个策略.所谓同源是指,域名,协议,端口相同.),凡是发 ...

  7. 五、springboot 简单优雅是实现邮件服务

    前言 spring boot 的项目放下小半个月没有更新了,终于闲下来可以开心的接着写啦. 之前我们配置好mybatis 多数据源的,接下来我们需要做一个邮件服务.比如你注册的时候,需要输入验证码来校 ...

  8. 六、springboot 简单优雅是实现短信服务

    前言 上一篇讲了 springboot 集成邮件服务,接下来让我们一起学习下springboot项目中怎么使用短信服务吧. 项目中的短信服务基本上上都会用到,简单的注册验证码,消息通知等等都会用到.所 ...

  9. 七、springBoot 简单优雅是实现文件上传和下载

    前言 好久没有更新spring Boot 这个项目了.最近看了一下docker 的知识,后期打算将spring boot 和docker 结合起来.刚好最近有一个上传文件的工作呢,刚好就想起这个脚手架 ...

随机推荐

  1. 2019-8-31-C#-通过-probing-指定-dll-寻找文件夹

    title author date CreateTime categories C# 通过 probing 指定 dll 寻找文件夹 lindexi 2019-08-31 16:55:58 +0800 ...

  2. 巨蟒python全栈开发-第11阶段 ansible3_2入门八个模块

    大纲: 1.file模块 2.fetch模块 3.yum&&pip模块 4.service模块 5.cron模块 6.user模块 7.group模块

  3. [HLSL]HLSL 入门参考 (dx11龙书附录B译文)

    原文:[HLSL]HLSL 入门参考 (dx11龙书附录B译文) HLSL 高级着色语言 参考文档 龙书DirectX12现已推出中文版,其附录B的高级着色器语言参考的翻译质量比本文更高,有条件的读者 ...

  4. java代码简单实现栈

    1. 基于数组简单实现 /** * @author <a herf="mailto:yanwu0527@163.com">XuBaofeng</a> * @ ...

  5. ros自定义消息

    ros自定义消息可以根据自身项目需求定义和封装想要的数据类型和数据结构.具体可以参考维基百科关于ros自定义消息部分 这里我只是记录自定义消息的要点部分: 1.首先要在工作空间下功能包中创建一个msg ...

  6. jmter正则表达式提取器

    1.若返回的body内容为空,仅有 header值,则: \s代表为空 2.使用Debug来调试

  7. HSV 和 HLS颜色空间

    颜色空间 颜色空间是特定的颜色组织:它提供了将颜色分类,并以数字图像表示的方法. RGB 是红绿蓝颜色空间.你可以将其视为 3D 空间,在这种情况下是立方体,其中任何颜色都可以用 R.G 和 B 值的 ...

  8. c++中单引号和双引号的区别

    在C++中单引号表示字符,双引号表示字符串. 例如 :在定义一个数组的时候string a [5]={"nihao","henhao","good&q ...

  9. OP_REQUIRES failed at conv_ops.cc:386 : Resource exhausted: OOM when allocating tensor with shape..

    tensorflow-gpu验证准确率是报错如上: 解决办法: 1. 加入os.environ['CUDA_VISIBLE_DEVICES']='2' 强制使用CPU验证-----慢 2.'batch ...

  10. HDU1686 Oulipo 题解 KMP算法

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1686 题目大意:给你一个子串t和一个母串s,求s中有多少个子串t. 题目分析:KMP模板题. cal_ ...