一.安装
1.1 安装
官网地址:RocketMQ · 官方网站 | RocketMQ 下载直接解压
a) RocketMQ 是基于Java开发的,所以需要配置相应的jdk环境变量才能运行
b) RocketMQ也需要配置RocketMQ环境变量才可在windows上运行
1.2 基本概念简述
Producer
消息生产者,负责消息的产生,由业务系统负责产生
Consumer:
消息消费者,负责消息消费,由后台业务系统负责异步消费
Topic
消息的逻辑管理单位(消息的一个属性,并且每个消息都一定有这个属性)
这三者是RocketMq中最最基本的概念。Producer是消息的生产者。Consumer是消息的消费者。
消息通过Topic进行传递。Topic存放的是消息的逻辑地址。
具体来说是Producer将消息发往具体的Topic。Consumer订阅Topic,主动拉取或被动接受消息,如果Consumer消费消息失败则默认会重试16次
二.启动
- 首先启动注册中心nameserver ,默认启动在9876端口,打开cmd命令窗口,进入bin目录,执行命令
start mqnamesrv.cmd //windows
sh ./mqnamesrv //Linux
出现下面日志表示启动成功
- 启动RocketMQ服务,进入bin目录,cmd执行命令
start mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true //windows
sh ./mqbroker -n 127.0.0.1:9876 autoCreateTopicEnable=true //Linux
autoCreateTopicEnable=true这个设置表示开启自动创建topic功能,真实生产环境不建议开启。出现下面日志表示启动成功
如果写完代码之后运行抛出以下错误org.apache.rocketmq.client.exception.MQClientException: No route info of this topic, xxx那么表示你的rocketMQ中没有创建这个topic,表示开启autoCreateTopicEnable失效了,这个时候需要手动创建topic,还是进入bin目录,执行命令:
mqadmin updateTopic -n localhost:9876 -b localhost:10911 -t topicName //windows
sh ./mqadmin updateTopic -n localhost:9876 -b localhost:10911 -t topicName //linux
三.整合
3.1 导包
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.4.0</version>
</dependency>
3.2 普通消息
- 消息生产者
public static void main(String[] args){
// 新增消息生产者
DefaultMQProucer producer = new DefaultMQProucer("producer_group");
// 配置注册中心
producer.setNamesrvAddr("localhost:9876");
// 启动
producer.start();
// 新建消息对象,指定主题topicA和message
Message message = new Message("topicA","message".context.getBytes(Charset.forName("utf-8")));
// 发送消息
producer.send(message);
}
- 消息消费者
public static void main(String[] args) throws MQClientException {
DefaultMQPushConsumer mqConsumer = new DefaultMQPushConsumer("consumer_group");
mqConsumer.setNamesrvAddr("localhost:9876");
mqConsumer.subscribe("topicA", "*");
// 设置消息监听器
mqConsumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt>msgs, ConsumeConcurrentlyContext context) {
MessageExt message = msgs.get(0);
//获取消息内容
byte[] body = message.getBody();
});
mqConsumer.start();
3.3 Demo
3.3.1 准备一个消息类,用来封装消息
FinancialMessage
@Data
public class FinancialMessage {
private String mchId; // 商户ID
private BigDecimal orderAmount; // 订单金额
private Long channelId; // 通道ID
private BigDecimal mchServiceCharge; // 商户服务费
private BigDecimal channelServiceCharge; // 通道服务费
private String tradeNo; // 交易号
private String mchOrderSn; // 商户订单号
private String mchName; // 商户名称
private Date NotifyDate; // 通知日期
private BigDecimal merchantBalance; // 商户账户余额
private String channelName; // 通道名称
private BigDecimal channelBalance; // 通道账户余额
private Integer supplementState; // 补单状态
}
3.3.2 controller层
封装两种消息,根据请求参数的不同调用service发送不同的消息
package com.example.demo.controller;
import com.example.demo.entity.FinancialMessage;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.Date;
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
UserService userService;
@GetMapping("/test")
public String test(String str) {
//准备消息(实际开发会从请求参数中获取)
FinancialMessage financialMessage = new FinancialMessage();
financialMessage.setChannelId(100L);
financialMessage.setChannelName("测试");
financialMessage.setMchId("测试");
financialMessage.setMchName("测试");
financialMessage.setOrderAmount(BigDecimal.valueOf(10000));
financialMessage.setMchServiceCharge(BigDecimal.valueOf(10000));
financialMessage.setTradeNo("2222222");
financialMessage.setMchOrderSn("333333");
financialMessage.setNotifyDate(new Date());
financialMessage.setMerchantBalance(BigDecimal.valueOf(12312312));
financialMessage.setSupplementState(3);
financialMessage.setChannelBalance(BigDecimal.valueOf(54156454));
financialMessage.setChannelServiceCharge(BigDecimal.valueOf(100000));
boolean isSuccess = false;
//根据请求的不同,模拟两种消息
if ("1".equals(str)) {
isSuccess = userService.test(financialMessage);
} else if ("2".equals(str)) {
financialMessage.setSupplementState(1);
isSuccess = userService.test(financialMessage);
}
if (isSuccess) {
return "消息消费成功了";
}
return "消息消费失败了";
}
}
3.3.3 service层
注入消息生产者,调用发送消息方法
package com.example.demo.service.impl;
import com.example.demo.entity.FinancialMessage;
import com.example.demo.mq.BasicProducer;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class UserServiceImpl implements UserService {
@Autowired
BasicProducer producer; //注入消息生产者,还没实现
@Override
public boolean test(FinancialMessage financialMessage) {
boolean isSuccess = producer.sendMessage(financialMessage);
return isSuccess;
}
}
3.3.4 producer
负责生产消息,并发送到MQ
package com.example.demo.mq;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONUtil;
import com.example.demo.entity.FinancialMessage;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.exception.RemotingException;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.nio.charset.Charset;
@Component
@Slf4j
public class BasicProducer {
private DefaultMQProducer producer;
@PostConstruct
public void init(){
//指定生产者组
producer = new DefaultMQProducer("p-group");
//指定注册中心
producer.setNamesrvAddr("127.0.0.1:9876");
try {
producer.start();
} catch (MQClientException e) {
e.printStackTrace();
}
}
public boolean sendMessage(FinancialMessage financialMessage) {
//1.准备消息
Message message = new Message();
//1.1 设置主题topic
message.setTopic("topic1");
//1.2 设置消息body,将消息对象转换成jsonStr,再转换成byte
JSON financialMsgJson = JSONUtil.parse(financialMessage);
String jsonStr = JSONUtil.toJsonStr(financialMsgJson);
byte[] msgBytes = jsonStr.getBytes(Charset.forName("utf-8"));
message.setBody(msgBytes);
//2.初始化结果集
SendResult sendResult = null;
Exception e = null;
try {
//3.发送消息
sendResult = producer.send(message);
} catch (MQClientException ex) {
e=ex;
} catch (RemotingException ex) {
e=ex;
} catch (MQBrokerException ex) {
e=ex;
} catch (InterruptedException ex) {
e=ex;
}
if (e!=null){
return false;
}
//结果集不为空,并且SendStatus为SEND_OK,返回成功,否则返回失败
if (sendResult != null && SendStatus.SEND_OK.equals(sendResult.getSendStatus())){
return true;
}
return false;
}
}
3.3.5 consumer
负责消费消息,拿到消息后进行处理
package com.example.demo.mq;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.example.demo.entity.FinancialMessage;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.UnsupportedEncodingException;
import java.util.List;
@Component
@Slf4j
public class BasicConsumer {
private DefaultMQPushConsumer consumer;
@PostConstruct
public void init(){
//1.指定消费者组
consumer = new DefaultMQPushConsumer("c-group");
//2.指定nameserver
consumer.setNamesrvAddr("127.0.0.1:9876");
//3.获取消息监听器
consumer.setMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
//3.1 拿到消息对象,获取消息
MessageExt messageExt = list.get(0);
byte[] body = messageExt.getBody();
try {
//3.2 解析消息,将jsonstr映射到目标消息对象
String financialMsgJson = new String(body, 0, body.length, "utf-8");
JSONObject jsonObject = JSONUtil.parseObj(financialMsgJson);
FinancialMessage financialMessage = JSONUtil.toBean(jsonObject, FinancialMessage.class);
//3.3 通过拿到的消息实现1个简单的方法
test(financialMessage);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
//消息消费失败,默认重试16次
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
//消息消费成功
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
private void test(FinancialMessage financialMessage) {
System.out.println("-----------测试方法-----------"+ financialMessage.getSupplementState());
System.out.println(financialMessage);
}
});
try {
//4.订阅主题
consumer.subscribe("topic1","*");
//5.启动
consumer.start();
} catch (MQClientException e) {
e.printStackTrace();
}
}
}
3.3.6 application.yml
server:
port: 7101
spring:
application:
name: test-mq
datasource:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.2.11:3306/ry-config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
username: root
password: root123