安装RocketMQ,并集成到Springboot

RocketMQ 是什么

RocketMQ是一个分布式的消息中间件,具有低延迟、高性能、高可靠性,兆级能力和灵活的可扩展性。

安装RocketMQ

这里选择安装当前最新的发布版本:4.9.0

安装到CentOS 7

我使用的Cent OS 版本为 7.9.2009,64位。

(一) 依赖的运行环境

  1. 64位Linux操作系统
  2. 64位 JDK 1.8+
  3. 为Broker服务准备4G 以上的磁盘空间

(二) 下载安装包并解压

官网下载地址:https://rocketmq.apache.org/release_notes/release-notes-4.9.0/
在这里插入图片描述
选择Binary,进入下载页面,下载zip压缩包,并复制到CentOS的/home 目录下
执行以下命令:

cd /home
unzip ./rocketmq-all-4.9.0-bin-release.zip -d /usr/local
cd /usr/local
mv ./rocketmq-all-4.9.0-bin-release/ ./rocketmq
cd ./rocketmq
ls

可以看到解压后的rocketmq目录

benchmark  bin  conf  lib  LICENSE  NOTICE  README.md

(三) 修改启动脚本并启动

由于启动脚本中的JVM参数设置的内存太大,超出了我CentOS虚拟机分配的内存,所以启动之前先修改脚本中的JVM参数。

  1. 启动NameServer
    进入bin目录,编辑 runserver.sh

    cd ./bin/
    vim runserver.sh
    

    找到 JAVA_OPT= 开头的这一行,修改其中Xms、Xmx、Xmn,然后保存并退出vim编辑器

    JAVA_OPT="${JAVA_OPT} -server -Xms2g -Xmx2g -Xmn1g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
    

    启动NameServer

    nohup ./bin/mqnamesrv &
    

    查看网络端口,看到有9876端口就说明启动成功了

    netstat -ntlp
    Active Internet connections (only servers)
    Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name 
    tcp6       0      0 :::9876                 :::*                    LISTEN      5554/java
    
  2. 启动broker
    编辑 runbroker.sh

    vim runbroker.sh
    

    找到 JAVA_OPT="${JAVA_OPT} -server 开头的这一行,修改其中Xms、Xmx、Xmn,然后保存并退出vim编辑器

    JAVA_OPT="${JAVA_OPT} -server -Xms4g -Xmx4g -Xmn2g"
    

    启动Broker

    nohup ./mqbroker -n localhost:9876 &
    

    查看网络端口,看到有10911、10912这两个端口就说明启动成功了

    netstat -ntlp
    Active Internet connections (only servers)
    Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name 
    tcp6       0      0 :::9876                 :::*                    LISTEN      5554/java
    tcp6       0      0 :::10909                :::*                    LISTEN      5602/java
    tcp6       0      0 :::10911                :::*                    LISTEN      5602/java           
    tcp6       0      0 :::10912                :::*                    LISTEN      5602/java  
    

集成RocketMQ到Spring Boot

我们现有的项目是基于Spring Boot搭建的,要在现有项目中使用RocketMQ,并让Spring托管生产者、消费者。目前了解到两种比较合适的方案。

方法一:手动实现

这里演示一下如何在现有的Spring Boot项目中,通过@Bean注解、@PostConstruct注解将RocketMQ的生产者、消费者托管到Spring Boot并使用它们。

(一) 引入依赖,添加RocketMQ相关配置

POM文件引入依赖rocketmq-client

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

application.yml配置文件添加rocketmq相关配置项

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

定义一个RocketMqConfigProperties类,映射yml文件中的配置项

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

定义一个RocketMqConfig类,用于根据配置项生成生产者和消费者

@Slf4j
@Configuration
@EnableAutoConfiguration
public class RocketMqConfig {
    @Resource
    ApplicationContext applicationContext;

    @Resource
    RocketMqConfigProperties rocketMqConfigProperties;

	// 生成生产者和消费者
	// ……
}

(二) 集成RocketMQ生产者

在RocketMqConfig中添加一个方法,用于注入一个默认的生产者

    /**
     * 注入一个默认的生产者
     *
     * @return
     * @throws MQClientException
     */
    @Bean
    public DefaultMQProducer defaultMQProducer() throws MQClientException {
        if (StringUtils.isEmpty(rocketMqConfigProperties.getGroupName())) {
            throw new MQClientException(-1, "groupName is blank");
        }

        if (StringUtils.isEmpty(rocketMqConfigProperties.getNamesrvAddr())) {
            throw new MQClientException(-1, "nameServerAddr is blank");
        }
        DefaultMQProducer producer;
        producer = new DefaultMQProducer(rocketMqConfigProperties.getGroupName());

        producer.setNamesrvAddr(rocketMqConfigProperties.getNamesrvAddr());

        // 自动创建mq集群中不存在的topic
        producer.setCreateTopicKey(TopicValidator.AUTO_CREATE_TOPIC_KEY_TOPIC);

        // 如果需要同一个jvm中不同的producer往不同的mq集群发送消息,需要设置不同的instanceName
        // producer.setInstanceName(instanceName);

        producer.setMaxMessageSize(rocketMqConfigProperties.getProducerMaxMessageSize());
        producer.setSendMsgTimeout(rocketMqConfigProperties.getProducerSendMsgTimeout());
        // 如果发送消息失败,设置重试次数,默认为2次
        producer.setRetryTimesWhenSendFailed(rocketMqConfigProperties.getProducerRetryTimesWhenSendFailed());

        try {
            producer.start();
            log.info("producer is start! groupName:{}, namesrvAddr:{}", rocketMqConfigProperties.getGroupName(),
                    rocketMqConfigProperties.getNamesrvAddr());
        } catch (MQClientException e) {
            log.error(String.format("producer is error {}", e.getMessage(), e));
            throw e;
        }
        return producer;
    }

添加一个Controller,用于调用生产者,并添加一条消息到RocketMQ

@RestController
@RequestMapping("/log")
@Slf4j
public class LogController {
    @Resource
    DefaultMQProducer defaultMQProducer;

    @RequestMapping("add")
    public String add(@RequestParam("log") String value){
        Message msg = null;
        try {
            // 创建一条消息
            msg = new Message("TopicTest", "tags1", value.getBytes(RemotingHelper.DEFAULT_CHARSET));
            // 发送消息到一个Broker
            SendResult sendResult = defaultMQProducer.send(msg);
            // 通过sendResult返回消息是否成功送达
            log.warn(sendResult.toString());
            return "success";
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return "fail";
    }
}

(三) 集成RocketMQ消费者

添加AbstractConsumerConfig类,这是一个抽象的消费者配置类

@Data
public abstract class AbstractConsumerConfig {
    // 订阅的主题
    private String topic;

    // 订阅哪些tag
    private String tags;

    // listener
    private MessageListener messageListener;

    // 消费者
    private MQPushConsumer mqPushConsumer;

    // 自定义一个消费者标题,方便调试时候查看
    private String consumerTitle;

    /**
     * 设置topic、tags、title这三个基本信息
     * @param topic 订阅的主题
     * @param tags 订阅的标签.只支持“逻辑或”运算符的表达式 例如 "tag1 || tag2 || tag3"
     * 如果未 null 或 表达式“*”,表示订阅所有的tag
     * @param title initConfig
     */
    protected void initConfig(String topic, String tags, String title) {
        this.topic = topic;
        this.tags = tags;
        this.consumerTitle = title;
    }

    /**
     * 设置消息监听器
     * @param messageListener 消息监听器
     */
    public void registerMessageListener(MessageListener messageListener) {
        this.messageListener = messageListener;
    }
    
    /**
     * 初始化消费者
     */
    public abstract void init();
}

添加LogConsumerConfig,实现init()方法

@Component
@Slf4j
public class LogConsumerConfig extends AbstractConsumerConfig {
    private static final String DEFAULT_TOPIC = "TopicTest", DEFAULT_TAGS = "*", DEFAULT_TITLE = "logConsumer";

    @Override
    public void initConfig(String topic, String tags, String title) {
        super.initConfig(topic, tags, title);
    }

    @Override
    public void init() {
        // 设置主题,标签与消费者标题
        if(StringUtils.isEmpty(this.getTopic())) {
            this.setTopic(DEFAULT_TOPIC);
        }
        if(StringUtils.isEmpty(this.getTags())) {
            this.setTags(DEFAULT_TAGS);
        }
        if(StringUtils.isEmpty(this.getConsumerTitle())) {
            this.setConsumerTitle(DEFAULT_TITLE);
        }
        //消费者具体执行逻辑
        registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                msgs.forEach(msg -> {
                	// 消费消息的具体逻辑
                    log.info("consumer get a message. body: {}", new String(msg.getBody()));
                });
                // 标记该消息已经被成功消费
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
    }
}

RocketMqConfig类下添加以下两个方法,用于获取消费者配置对象,并使用消费者配置对象创建、启动消费者

	/**
     * 启动时获取所有托管在Spring的消费者配置对象,并根据这些配置对象创建消费者
     */
    @PostConstruct
    public void initConsumer() {
        Map<String, AbstractConsumerConfig> configMap = applicationContext.getBeansOfType(AbstractConsumerConfig.class);
        if (configMap == null || configMap.size() == 0) {
            log.info("init rocket consumer 0");
        }

        for (Map.Entry<String, AbstractConsumerConfig> entry : configMap.entrySet()) {
            AbstractConsumerConfig config = entry.getValue();
            config.init();
            createAndStartConsumer(config);
            log.info("init success consumer title {} , toips {} , tags {}", config.getConsumerTitle(), config.getTopic(),
                    config.getTags());
        }
    }

    /**
     * 通过消费者配置创建消费者,并start
     * @param consumerConfig 消费者配置对象
     */
    public void createAndStartConsumer(AbstractConsumerConfig consumerConfig) {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(this.rocketMqConfigProperties.getGroupName());
        consumer.setNamesrvAddr(this.rocketMqConfigProperties.getNamesrvAddr());
        consumer.setConsumeThreadMin(this.rocketMqConfigProperties.getConsumerConsumeThreadMin());
        consumer.setConsumeThreadMax(this.rocketMqConfigProperties.getConsumerConsumeThreadMax());
        consumer.registerMessageListener(consumerConfig.getMessageListener());

        // 设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费 如果非第一次启动,那么按照上次消费的位置继续消费
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);

        // 设置消费模型,集群还是广播,默认为集群
        consumer.setMessageModel(MessageModel.CLUSTERING);

        // 设置一次消费消息的条数,默认为1条
        consumer.setConsumeMessageBatchMaxSize(this.rocketMqConfigProperties.getConsumerConsumeMessageBatchMaxSize());

        try {
            consumer.subscribe(consumerConfig.getTopic(), consumerConfig.getTags());
            consumer.start();
            consumerConfig.setMqPushConsumer(consumer);
        } catch (MQClientException e) {
            log.error("info consumer title {}", consumerConfig.getConsumerTitle(), e);
        }

    }

(四)测试

启动项目,在浏览器中输入 http://localhost:8080/log/add?log=ThisIsFirstLog,添加一条消息
浏览器显示返回信息
在这里插入图片描述
查看控制台,控制台打印出以下信息

2021-07-29 16:12:31.887  WARN 3000 --- [nio-8080-exec-2] com.example.controller.LogController     : SendResult [sendStatus=SEND_OK, msgId=7F0000010BB818B4AAC293AC710C0007, offsetMsgId=C0A8FF8000002A9F00000000000006F8, messageQueue=MessageQueue [topic=TopicTest, brokerName=localhost.localdomain, queueId=2], queueOffset=3]
2021-07-29 16:12:31.890  INFO 3000 --- [MessageThread_3] c.e.config.rocketmq.LogConsumerConfig    : consumer get a message. body: ThisIsFirstLog

消费者已经成功消费了这一条消息

方法二:使用rocketmq-spring-boot-starter

apache上有个叫做rocketmq-spring的项目,将RocketMQ和Spring Boot的集成,封装成了一个rocketmq-spring-boot-starter jar包。借助这个包,我们能简单的通过配置文件、注解和一些必要代码在现有的Spring Boot项目中使用RocketMQ。

(一) 引入依赖,添加RocketMQ相关配置

POM文件引入依赖rocketmq-spring-boot-starter
现在rocketmq-spring-boot-starter的版本是2.2.0,依赖rocketmq-client版本是4.8.0。经测试,服务器上安装了4.9.0的RocketMQ服务,依然可以正常使用。

        <!-- 最新版本2.2.0,对应rocketmq-client版本是4.8.0 -->
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>

(二) 集成RocketMQ生产者

在application.yml配置文件中,添加生产者的相关配置

server:
  port: 8081                          # http服务端口

rocketmq:
  name-server: 192.168.255.128:9876   # mq的nameserver地址
  producer:
    group: enterprise_cloud_log       # 发送同一类消息的设置为同一个group,保证唯一。默认不需要设置,rocketmq会使用ip@pid(pid代表jvm名字)作为唯一标示
    sendMessageTimeout: 300000        # 发送消息超时时间

添加一个MsgController类,之后调用通过http接口去使用生产者发送消息
rocketmq-spring-boot-starter 提供了一个RocketMQTemplate类的对象。RocketMQTemplate自动装配DefaultMQProducer对象,并使用它即发送消息。
因此我们只需要引入RocketMQTemplate的对象,就能发送消息到RocketMQ:

@RestController
@RequestMapping("/msg")
@Slf4j
public class MsgController {
    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @RequestMapping("/send")
    public Object send() {
        // 同步发送消息,底层调用的syncSend方法
        rocketMQTemplate.convertAndSend("TopicTest", "Hello, World! I was sent by convertAndSend");
        
        // 发送一个Spring Message
        rocketMQTemplate.send("TopicTest", MessageBuilder.withPayload("Hello, World! I'm from spring message").build());
        
        // 异步发送消息
        Map<String,Object> obj = new HashMap<>();
        obj.put("id",1);
        obj.put("name","Long");
        rocketMQTemplate.asyncSend("TopicTest", obj, new SendCallback() {
            @Override
            public void onSuccess(SendResult var1) {
                log.warn("async onSucess SendResult={}", var1);
            }
            @Override
            public void onException(Throwable var1) {
                log.warn("async onException Throwable={}", var1);
            }
        });
        
        // 有序发送消息 - sync
        rocketMQTemplate.syncSendOrderly("OrderlyTopicTest",MessageBuilder.withPayload("Message 1. I was sent by syncSendOrderly").build(),"hashkey1");
        
        // 有序发送消息 - async
        rocketMQTemplate.asyncSendOrderly("OrderlyTopicTest",MessageBuilder.withPayload("Message 2. I was sent by asyncSendOrderly").build(),"hashkey1",new SendCallback() {
            @Override
            public void onSuccess(SendResult var1) {
                log.warn("async orderly onSucess SendResult={}", var1);
            }

            @Override
            public void onException(Throwable var1) {
                log.warn("async orderly onException Throwable={}", var1);
            }

        });
        // 注意:  只要调用了rocketMQTemplate的destroy方法, 就无法使用这个rocketMQTemplate发送消息了。注入的rocketMQTemplate需要特别注意。
        //rocketMQTemplate.destroy();
        return "success";
    }
}

(三) 集成RocketMQ消费者

在application.yml配置文件中,配置rocketmq的name-server即可

rocketmq:
  name-server: 192.168.255.128:9876   # mq的nameserver地址

rocketmq-spring提供了一个RocketMQListener接口,我们通过实现这个接口以及其中的onMessage方法,来生成消费者。消费者订阅的主题、消费者分组、消费方式(并行、顺序)、TAG表达式等,都通过注解去配置。

@Slf4j
@Service
@RocketMQMessageListener(topic = "TopicTest", consumerGroup = "my-consumer_test-topic-2",consumeMode = ConsumeMode.CONCURRENTLY)
public class MyConcurrentlyConsumer implements RocketMQListener<String> {
    @Override
    public void onMessage(String msg) {
        log.info("concurrently consumer get a message. body: {}", msg);
    }
}

(四)测试

启动项目,在浏览器中输入 http://localhost:8081/msg/send,添加了一些消息
在这里插入图片描述

查看控制台,控制台打印出以下信息

2021-08-03 10:46:30.619  INFO 29752 --- [MessageThread_1] c.e.c.rocketmq.MyConcurrentlyConsumer    : concurrently consumer get a message. body: {"name":"Long","id":1}
2021-08-03 10:46:30.619  INFO 29752 --- [MessageThread_2] c.e.c.rocketmq.MyConcurrentlyConsumer    : concurrently consumer get a message. body: Hello, World! I was sent by convertAndSend
2021-08-03 10:46:30.633  INFO 29752 --- [MessageThread_3] c.e.c.rocketmq.MyConcurrentlyConsumer    : concurrently consumer get a message. body: Hello, World! I'm from spring message
RocketMQ是一款开源的分布式消息中间件,主要用于高发、低延迟的消息传输场景。Spring Boot是一个流行的Java框架,用于简化新项目的初始搭建过程。将RocketMQ集成到Spring Boot项目中可以让你轻松地处理消息队列,利用Spring Boot的依赖注入和自动配置功能。 以下是异步集成Spring Boot和RocketMQ的基本步骤: 1. 添加依赖:首先,在Spring Boot项目的pom.xml文件中添加RocketMQ的客户端依赖,例如: ```xml <dependency> <groupId>org.apache.rocketmq</groupId> <artifactId>rocketmq-client</artifactId> <version>4.x.y</version> </dependency> ``` 2. 配置Spring:在application.properties或application.yml中设置 RocketMQ 的配置,包括 broker地址、topic等: ```properties rocketmq.broker.address=your-broker-address:9876 rocketmq.namesrv.address=your-nameserver-address:9876 ``` 3. 创建消费者:在Spring Boot的组件中创建RocketMQ的消息消费者,通过`RocketMQTemplate`发送和接收消息,如: ```java @Component public class MyConsumer { private final RocketMQTemplate template; @Autowired public MyConsumer(RocketMQTemplate template) { this.template = template; } @RabbitListener(queues = "myQueue") public void consumeMessage(String message) { // 处理接收到的消息 } } ``` 4. 发布消息:在需要发送消息的地方,通过`RocketMQTemplate`或者自定义生产者进行消息发布: ```java @Service public class MyProducer { private final RocketMQTemplate template; @Autowired public MyProducer(RocketMQTemplate template) { this.template = template; } public void sendMessage(String message, String topic) { template.send(topic, message); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值