现在多大企业都使用到Spring Cloud的框架,本文介绍Sping Cloud接入RocketMQ消息队列中间件的示例
1、Spring Cloud Stream 介绍
Spring Cloud Stream 是一个用于构建基于消息的微服务应用框架,使用 Spring Integration 与 Broker 进行连接。
一般来说,消息队列中间件都有一个 Broker Server(代理服务器),消息中转角色,负责存储消息、转发消息。
例如说在 RocketMQ 中,Broker 负责接收从生产者发送来的消息并存储、同时为消费者的拉取请求作准备。另外,Broker 也存储消息相关的元数据,包括消费者组、消费进度偏移和主题和队列消息等。
Spring Cloud Stream 提供了消息中间件的统一抽象,推出了 publish-subscribe、consumer groups、partition 这些统一的概念。
Spring Cloud Stream 内部有两个概念:Binder 和 Binding。
① Binder,跟消息中间件集成的组件,用来创建对应的 Binding。各消息中间件都有自己的 Binder 具体实现。RocketMQ的具体实现为 RocketMQMessageChannelBinder
② Binding,包括 Input Binding 和 Output Binding。Binding 在消息中间件与应用程序提供的 Provider 和 Consumer 之间提供了一个桥梁,实现了开发者只需使用应用程序的 Provider 或 Consumer 生产或消费数据即可,屏蔽了开发者与底层消息中间件的接触。
2、项目demo
前提:准备好一个可运行的spingboot框架的web项目
导入关键依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
<exclusions>
<exclusion>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<groupId>org.apache.rocketmq</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>${rocketmq.starter.version}</version>
</dependency>
项目结构
配置文件
#防止注册成同一个服务,影响dev,本地自测时,在name后面加上自己的名字,如demo-service-local,修改后的名字请勿提交
spring.application.name: demo-service-local
#不启用外部化配置
spring.cloud.nacos.config.enabled: false
spring:
cloud:
stream:
#在有多个binder的情况下,所有binder要使用的binder的名称
default-binder: rocketmq
rocketmq:
binder:
#RocketMQ NameServer 地址
name-server: 127.0.0.1:9876
# 阿里云access-key,购买阿里云RocketMQ时配置
#access-key:
# 阿里云secret-key,购买阿里云RocketMQ时配置
#secret-key:
# 消息轨迹开启后存储的 Topic 名称
customized-trace-topic: rmq_TRACE_DATA_cn-publictest
bindings:
# 输出通道,对应下面自定义枚举 COMMON_OUTPUT
common_output:
producer:
# 可用来查询生产者组
group: GID_demo_qa
# 是否同步发送消息
sync: true
# 输入通道,对应下面自定义枚举 DEMO_TEST, 记住这个通道
demo_test:
consumer:
enabled: true
orderly: false
# 订阅哪个tag
tags: demo_test_tag
# 注意间隔,这里的bindings是和rocketmq同级的,绑定哪个topic,格式是什么
bindings:
# 输出通道,对应下面自定义枚举 COMMON_OUTPUT
common_output:
# 发送消息格式,这里使用JSON
content-type: application/json
# 目标,也就是topic
destination: demo
# 输入通道,对应下面自定义枚举 DEMO_TEST
demo_test:
# 接收消息格式,这里使用JSON
content-type: application/json
# 目标,也就是topic
destination: demo
# 对应哪个Group,因为一个topic下面可有多个Group
group: GID_demo_qa
消息发送和接收的POJO(Json格式传输)
@Data
public class DemoTestBody implements Serializable {
private static final long serialVersionUID = -761662493627579882L;
/**
* 名称
*/
private String name;
}
demo案例,只定义好了一个简单的POJO,实际可根据自己业务需要定义
消费者
@Slf4j
@Service
public class DemoTestConsumer {
@Autowired
private DemoService demoService;
@StreamListener(StreamConstant.DEMO_TEST)
public void onConsumer(@Payload DemoTestBody body, @Headers Map headers) {
long time = System.currentTimeMillis();
String msgId = headers.get(StreamConstant.ROCKET_MQ_MESSAGE_ID) == null ? "" : headers.get(StreamConstant.ROCKET_MQ_MESSAGE_ID).toString();
log.info("DemoTestConsumer 消费消息,msgId:{},body:{}", msgId, JSONObject.toJSONString(body));
try {
// 业务处理
demoService.consumerHandle(body);
log.info("DemoTestConsumer 消费消息成功,msgId:{},消费时长:{},msg:{}", msgId, System.currentTimeMillis() - time, JSONObject.toJSONString(body));
} catch (Exception e) {
log.error("DemoTestConsumer 消费失败,正在重试,错误信息:{},{}, info:{}", e, e.getStackTrace(), JSONObject.toJSONString(body));
throw new BusinessException(ServerCode.SERVER_ERROR, "消费失败:" + e.getMessage());
}
}
}
@StreamListener : 它主要定义在方法上, 作用是将被修饰的方法注册为消息中间件上数据流的事件监听器,配置文件中提到注意输入通道, 注解中的属性值对应了监听的消息通道名, 在上面的例子中,我们通过 @StreamListener(StreamConstant.DEMO_TEST) 注解将onConsumer方法注册为input消息通道的监听处理器,所以当我们向RocketMQ的中发布消息的时候,该方法会做出对应的响应动作。
@Payload : 进行反序列化成 POJO 对象
@Headers : 拿到消息头的信息
生产者
@Slf4j
@Service
public class RocketMessageQueueProducer {
@Autowired
private Source source;
/**
* 发送消息
*
* @param body : 消息体
* @param tagEnum : tag枚举
*/
public <T> void sendMessage(T body, MessageQueueTagEnum tagEnum) {
String msgJson = JSON.toJSONString(body);
log.info("sendMessage,tag:{},msgBody:{}", tagEnum.getTag(), msgJson);
try {
// 构建消息体
Message<T> message = MessageBuilder.withPayload(body)
.setHeader(MessageConst.PROPERTY_TAGS, tagEnum.getTag())
.setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON)
.build();
// 发送
source.commonOutput().send(message);
log.info("sendMessage succeed. tag:{},msgBody:{}", tagEnum.getTag(), msgJson);
} catch (Exception e) {
log.info("sendMessage failure. tag:{},msgBody:{},errorMsg:{}", tagEnum.getTag(), msgJson, e);
}
}
}
sendMessage(T body, MessageQueueTagEnum tagEnum),定义泛型参数,及标签参数,根据需求传入不同的消息及tag
setHeader(),设置消息头内容,可自定义内容,KV结构
自定义枚举
public class StreamConstant {
/**
* rocketmq消息id
*/
public static final String ROCKET_MQ_MESSAGE_ID = "rocketmq_MESSAGE_ID";
/**
* demo测试消息
*/
public static final String DEMO_TEST = "demo_test";
/**
* 公共输出通道
*/
public static final String COMMON_OUTPUT = "common_output";
}
标签枚举
@Getter
@AllArgsConstructor
public enum MessageQueueTagEnum {
/**
* 测试tag
*/
DEMO_TEST_TAG("demo_test_tag", "测试tag");
private final String tag;
private final String describe;
}
输入通道定义
public interface Sink {
/**
* demo项目输入通道
* @return channel
*/
@Input(StreamConstant.DEMO_TEST)
SubscribableChannel spiderServerReleaseTicket();
}
那么,我们是否要实现 Sink 接口呢?答案是不需要,还是全部交给 Spring Cloud Stream 的 BindableProxyFactory 来解决。BindableProxyFactory 会通过动态代理,自动实现 Sink 接口。 而 @Input 注解的方法的返回值,BindableProxyFactory 会扫描带有 @Input 注解的方法,自动进行创建。
输出通道定义
public interface Source {
/**
* demo项目输出通道
* @return channel
*/
@Output(StreamConstant.COMMON_OUTPUT)
MessageChannel commonOutput();
}
那么,我们是否要实现 Source 接口呢?答案是也不需要,全部交给 Spring Cloud Stream 的 BindableProxyFactory 来解决。BindableProxyFactory 会通过动态代理,自动实现 Source 接口。 而 @Output 注解的方法的返回值,BindableProxyFactory 会扫描带有 @Output 注解的方法,自动进行创建。
我们可以看到,Sink和Source中分别通过@Input和@Output注解定义了 输入通道和输出通道,另外, @Input和@Output 注解都还有一 个value属性,该属性可以用来设置消息通道的名称,这里Sink和Source中指定的消息通道名称分别为 input和output。如果我们直接使用这两个注解而没有指定具体的 value值,将默认使用方法名作为消息通道的名称 。需要 注意 一 点,当我们定义输出通道的时候,需要返回MessageChannel接口对象, 该接口定义了向消息通道发送消息的方法; 而定义输入通道时,需要返回SubscribableChannel接口对象,该接口 继承自MessageChannel接口,它定义了维护消息通道订阅者的方法。
成功
3、更多配置
RocketMQ Binder Properties
以 spring.cloud.stream.rocketmq.binder 为前缀
RocketMQ Consumer Properties
RocketMQ Provider Properties
如果觉得有收获,请点个赞吧