springboot結合rocketmq的使用以及遇到的問題

妞妞猪發表於2024-03-18

rocketmq是一款低延遲、高併發、高可用、高可靠的分散式訊息中介軟體。訊息佇列 RocketMQ 可為分散式應用系統提供非同步解耦和削峰填谷的能力,同時也具備網際網路應用所需的海量訊息堆積、高吞吐、可靠重試等特性。

首先需要下載安裝rocketmq:

1.官網 https://rocketmq.apache.org/zh/docs/quickStart/01quickstart

2.點選下載二進位制包

下載完後進行解壓,根據資料夾的路徑進行下面的環境變數配置

3.配置環境變數

ROCKETMQ_HOME:E:\rocketmq-all-5.2.0\rocketmq-all-5.2.0

4.啟動mqnamesrv

在bin目錄下雙擊mqnamesrv.cmd

視窗不閃退,這樣就是啟動成功,不要關閉視窗

5.啟動mqbroker(有順序,先需要啟動mqnamesrv)

同樣在bin的目錄裡,我們輸入cmd

輸入 start mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true

注意:在這裡我遇到了一個問題

錯誤: 找不到或無法載入主類 Files\Java\jdk1.8.0_311\lib\dt.jar;C:\Program

根據度娘指引,需要修改runbroker.cmd檔案種 %CLASSPATH%為其加上雙引號

儲存修改,重新執行start mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true

會彈出一個新的視窗,執行成功 不要關閉視窗

到此,rocketmq服務就已經全部啟動了

接下來開始把rocketmq引入springboot

檔案目錄(我們測試程式只需要這四個檔案):

1.配置pom

<!--        rocketmq-->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.0.4</version>
</dependency>

<!-- 還有其它需要的jar包自由引入(注:fastjson不要使用低於1.2.60版本,會有安全漏洞) -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.22</version>
    </dependency>

2.application.properties配置

#rocketmq
rocketmq.name-server=127.0.0.1:9876
rocketmq.producer.group=Pro_Group
rocketmq.producer.send-message-timeout=3000
rocketmq.producer.retry-times-when-send-async-failed=3
rocketmq.producer.retry-times-when-send-failed=3

3.編寫實體類

package com.gykg.yizhichun.entity;

import lombok.Data;

@Data
public class User {
private String id;
private String name;
private Integer age;
private String sex;
private String desc;
}

4.編寫生產者類

package com.gykg.yizhichun.producer;

import com.alibaba.fastjson.JSON;
import com.gykg.yizhichun.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.messaging.support.MessageBuilder;

import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;

@Slf4j
@Component
public class MQProducerService {
@Value("${rocketmq.producer.send-message-timeout}")
private Integer messageTimeOut;

// 建議正常規模專案統一用一個TOPIC
private static final String topic = "RLT_TEST_TOPIC";

// 直接注入使用,用於傳送訊息到broker伺服器
@Autowired
private RocketMQTemplate rocketMQTemplate;

/**
* Tag:用於區分過濾同一主題下的不同業務型別的訊息,非常實用
* 普通傳送(這裡的引數物件User可以隨意定義,可以傳送個物件,也可以是字串等)
*/
public void send(User user) {
rocketMQTemplate.convertAndSend(topic + ":tag1", user);
// rocketMQTemplate.send(topic + ":tag1", MessageBuilder.withPayload(user).build()); // 等價於上面一行
}

/**
* 傳送同步訊息(阻塞當前執行緒,等待broker響應傳送結果,這樣不太容易丟失訊息)
* (msgBody也可以是物件,sendResult為返回的傳送結果)
*/
public SendResult sendMsg(String msgBody) {
SendResult sendResult = rocketMQTemplate.syncSend(topic, MessageBuilder.withPayload(msgBody).build());
log.info("【sendMsg】sendResult={}", JSON.toJSONString(sendResult));
return sendResult;
}

/**
* 傳送非同步訊息(透過執行緒池執行傳送到broker的訊息任務,執行完後回撥:在SendCallback中可處理相關成功失敗時的邏輯)
* (適合對響應時間敏感的業務場景)
*/
public void sendAsyncMsg(String msgBody) {
rocketMQTemplate.asyncSend(topic, MessageBuilder.withPayload(msgBody).build(), new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
// 處理訊息傳送成功邏輯
log.info("【sendMsg】sendResult={}", JSON.toJSONString(sendResult));
}

@Override
public void onException(Throwable throwable) {
// 處理訊息傳送異常邏輯
log.info("【sendMsg】sendResult={}", "傳送異常" + throwable.getMessage());
}
});
}

/**
* 傳送延時訊息(上面的傳送同步訊息,delayLevel的值就為0,因為不延時)
* 在start版本中 延時訊息一共分為18個等級分別為:1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
*/
public void sendDelayMsg(String msgBody, int delayLevel) {
rocketMQTemplate.syncSend(topic, MessageBuilder.withPayload(msgBody).build(), messageTimeOut, delayLevel);
}

/**
* 傳送單向訊息(只負責傳送訊息,不等待應答,不關心傳送結果,如日誌)
*/
public void sendOneWayMsg(String msgBody) {
rocketMQTemplate.sendOneWay(topic, MessageBuilder.withPayload(msgBody).build());
}

/**
* 傳送帶tag的訊息,直接在topic後面加上":tag"
*/
public SendResult sendTagMsg(String msgBody) {
return rocketMQTemplate.syncSend(topic + ":tag2", MessageBuilder.withPayload(msgBody).build());
}

/***
* 服務生產者,順序訊息
* 把訊息確保投遞到同一條queue
* 保證了訊息的順序性
*/
public void sendFIFOMsg(List<User> users) {
//順序訊息
//選擇器規則構建
rocketMQTemplate.setMessageQueueSelector((list, message, o) -> {
int id = Integer.valueOf((String) o);
int hash = (id % list.size());
return list.get(hash);
});
if (!CollectionUtils.isEmpty(users)) {
for (User user : users) {
MessageBuilder.withPayload(users.toString()).build();
rocketMQTemplate.sendOneWayOrderly(topic+":sendFIFOMsg", user, String.valueOf(user.getId()));
}
}
}
}

5.編寫消費者類

package com.gykg.yizhichun.consumer;

import com.alibaba.fastjson.JSON;
import com.gykg.yizhichun.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

@Slf4j
@Component
public class MQConsumerService {
// Tag:用於區分過濾同一主題下的不同業務型別的訊息,非常實用
// topic需要和生產者的topic一致,consumerGroup屬性是必須指定的,內容可以隨意
// selectorExpression的意思指的就是tag,預設為“*”,不設定的話會監聽所有訊息
@Service
@RocketMQMessageListener(topic = "RLT_TEST_TOPIC", selectorExpression = "tag1", consumerGroup = "Con_Group_One")
public class ConsumerSend implements RocketMQListener<User> {
// 監聽到訊息就會執行此方法
@Override
public void onMessage(User user) {
log.info("tag1監聽到訊息:user={}", JSON.toJSONString(user));
}
}


// 注意:這個ConsumerSend2和上面ConsumerSend在沒有新增tag做區分時,不能共存,
// 不然生產者傳送一條訊息,這兩個都會去消費,如果型別不同會有一個報錯,所以實際運用中最好加上tag,寫這只是讓你看知道就行
@Service
@RocketMQMessageListener(topic = "RLT_TEST_TOPIC", consumerGroup = "Con_Group_Two",selectorExpression = "xxx")
public class ConsumerSend2 implements RocketMQListener<String> {
@Override
public void onMessage(String str) {
log.info("ConsumerSend2監聽到訊息:str={}", str);
}
}

// MessageExt:是一個訊息接收萬用字元,不管傳送的是String還是物件,都可接收,當然也可以像上面明確指定型別(我建議還是指定型別較方便)
@Service
@RocketMQMessageListener(topic = "RLT_TEST_TOPIC", selectorExpression = "tag2", consumerGroup = "Con_Group_Three")
public class Consumer implements RocketMQListener<MessageExt> {
@Override
public void onMessage(MessageExt messageExt) {
byte[] body = messageExt.getBody();
String msg = new String(body);
log.info("tag2監聽到訊息:msg={}", msg);
}
}

/**
* 消費者順序消費訊息
* 順序消費
*/
@Service
@RocketMQMessageListener(consumerGroup = "Orderly-Consumer", topic = "RLT_TEST_TOPIC",selectorExpression = "sendFIFOMsg", consumeMode = ConsumeMode.ORDERLY)
public class OrderlyConsumer implements RocketMQListener<MessageExt> {
@Override
public void onMessage(MessageExt message) {
System.out.println("執行緒"+Thread.currentThread()+"內容為:"
+ new String(message.getBody())+
"佇列序號:"+message.getQueueId()+",訊息msgId:"+message.getMsgId());
}
}
}

6.編寫控制器類

package com.gykg.yizhichun.controller;

import com.gykg.yizhichun.entity.User;
import com.gykg.yizhichun.producer.MQProducerService;
import org.apache.rocketmq.client.producer.SendResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/rocketmq")
public class MqController {
@Autowired
private MQProducerService mqProducerService;

@GetMapping("/send")
public void send() {
User user = new User();
user.setAge(28);
user.setName("曹震");
user.setSex("男");
mqProducerService.send(user);
}

@GetMapping("/sendTag")
public ResponseEntity<SendResult> sendTag() {
SendResult sendResult = mqProducerService.sendTagMsg("帶有tag的字元訊息");
return ResponseEntity.ok(sendResult);
}

@GetMapping("/sendMsg")
public ResponseEntity<SendResult> sendMsg() {
SendResult sendResult = mqProducerService.sendMsg("曹震測試");
return ResponseEntity.ok(sendResult);
}

@GetMapping("/sendFIFOMsg")
public void sendFIFOMsg() {
List<User> users = new ArrayList<>();
User user = new User();
user.setId("1");
user.setSex("男");
user.setName("曹震");
user.setAge(28);
user.setDesc("建立訂單");
users.add(user);

User user1 = new User();
user1.setId("2");
user1.setSex("男");
user1.setName("賈耀旗");
user1.setAge(25);
user1.setDesc("建立訂單");
users.add(user1);

mqProducerService.sendFIFOMsg(users);
}
}

然後就是測試介面拉,這裡要注意的是:不要忘記把producer和consumer這兩個包加入掃描,不然是會獲取不到bean 的

我們執行起來,測試 /rocketmq/sendFIFOMsg 介面,輸出這個,測試成功

巨人的肩膀:https://blog.csdn.net/qq_23126581/article/details/132496439

https://blog.csdn.net/Messy_Cat/article/details/124108281

相關文章