官方网址:https://github.com/alibaba/spring-cloud-alibaba/wiki/RocketMQ
你可以在这个地址上下载到相关示例项目,配置项等相关信息
spring-cloud-stream 文档(这个地址似乎只有集合kafaka和rabbit的示例): https://docs.spring.io/spring-cloud-stream/docs/3.2.6/reference/html/
spring-cloud-stream-rocketmq文档 :https://spring-cloud-alibaba-group.github.io/github-pages/2021/en-us/index.html
无语的文档,太简陋了
Spring Cloud Stream 是一个用于构建基于消息的微服务应用框架。它基于 SpringBoot 来创建具有生产级别的单机 Spring 应用,并且使用 Spring Integration
与 Broker 进行连接。
Spring Cloud Stream 提供了消息中间件配置的统一抽象,推出了 publish-subscribe、consumer groups、partition 这些统一的概念。
Spring Cloud Stream 内部有两个概念:Binder 和 Binding。
比如 Kafka
的实现 KafkaMessageChannelBinder
,RabbitMQ
的实现 RabbitMessageChannelBinder
以及 RocketMQ
的实现 RocketMQMessageChannelBinder
。
Binding 在消息中间件与应用程序提供的 Provider 和 Consumer 之间提供了一个桥梁,实现了开发者只需使用应用程序的 Provider 或 Consumer 生产或消费数据即可,屏蔽了开发者与底层消息中间件的接触。
下图是 Spring Cloud Stream 的架构设计。
大白话:直接理解成和日志门面一个slfj4一个概念即可,这是一个消息的门面,然我们可以替换消息的底层实现,而不用修改代码
这是Spring Cloud Stream Rocket MQ Binder的实现架构:
Rocket MQ Binder的实现依赖于Rocket MQ-Spring框架。RocketMQ Spring框架是Rocket MQ和Spring Boot的集成。它提供了三个主要功能:
@RocketMQTemplate:发送消息,包括同步、异步和事务消息。
@RocketMQTransactionListener:侦听并检查事务消息。
@RocketMQMessageListener:使用消息。
RocketMQMessageChannelBinder是Binder的标准实现,它将在内部构建RocketMQInboundChannelAdapterr和RocketMQMessageHandler。
RocketMQMessageHandler将基于binding配置构造RocketMQTemplate。RocketMQTemplate将转换org.springframework.message(spring消息模块的消息消息类)转换为Rocket MQ消息类org.apache.rocketmq。common.message,然后发送出去。
RocketMQInboundChannelAdapter还将基于binding配置构造RocketMQListenerBindingContainer,RocketMQListenerBindingContainer将启动Rocket q Consumer以接收消息。
官方示例包包含了的部分示例,你需要了解相关概念,部分我也是懵逼,大致了解就行,先把示例跑起来
https://blog.csdn.net/weixin_41667076/article/details/121701303
一个接口,其实现类对应的bean被spring管理后,会在项目启动时执行
@Component //此类一定要交给spring管理
@Order(value=2) //其次执行
public class ConsumerRunnerB implements ApplicationRunner{
@Override
public void run(ApplicationArgumers args) throws Exception{
//代码
System.out.println("需要在springBoot项目启动时执行的代码2---");
}
}
https://www.cainiaojc.com/java/java8-functional-interfaces.html
一个函数式接口定义是在接口上加上注解@FunctionalInterface,java自行实现的函数式接口位于java.util.function,函数式接口可以隐式的转换成lmabda 表达式
一般会有一个唯一的未实现方法,我们一般使用lmabda来构建对应函数式接口的实现类
Predicate入参是泛型指定,可以接收任意类型的参数,返回类型是boolean
package cn.sry1201.recketmq.config;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
public class Java8Tester {
public static void main(String args[]){
List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
// Predicate predicate = new Predicate() {
// @Override
// public boolean test(Integer integer) {
// return false;
// }
// };
// Predicate predicate = n -> true
// n 是一个参数传递到 Predicate 接口的 test 方法
// n 如果存在则 test 方法返回 true
System.out.println("输出所有数据:");
// 传递参数 n 这里其实就可以理解为定义这个函数的实现类,效果和上方注释代码等同
eval(list, n->true);
// Predicate predicate1 = n -> n%2 == 0
// n 是一个参数传递到 Predicate 接口的 test 方法
// 如果 n%2 为 0 test 方法返回 true
System.out.println("输出所有偶数:");
eval(list, n-> n%2 == 0 );
// Predicate predicate2 = n -> n > 3
// n 是一个参数传递到 Predicate 接口的 test 方法
// 如果 n 大于 3 test 方法返回 true
System.out.println("输出大于 3 的所有数字:");
eval(list, n-> n > 3 );
}
public static void eval(List list, Predicate predicate) {
for(Integer n: list) {
// 调用函数实现的方法
if(predicate.test(n)) {
System.out.println(n + " ");
}
}
}
}
包含了函数式接口funtion的默认方法的使用
public class FunctionExample2 {
public static void main(String[] args) {
// 定义两个函数
Function function1 = t -> (t - 5);
Function function2 = t -> (t * 2);
//Using andThen() method 组合成一个符合函数,先执行第一个函数,再执行第二个函数,得到90
int a = function1.andThen(function2).apply(50);
System.out.println(a);
//Using compose function compose构建一个复合函数,先执行函数2,再执行函数一
int c = function1.compose(function2).apply(50);
System.out.println(c);
}
}
Consumer void accept(T t) 有入参,无返回值 消费型接口
Supplier T get() 无入参,有返回值 供给型接口
Function
待定
反应式编程相关: https://zhuanlan.zhihu.com/p/356997738
https://zhuanlan.zhihu.com/p/95966853
官方文档 :https://spring.io/projects/spring-cloud-function#learn
Spring Cloud Function是一个具有以下高级目标的项目:通过功能促进业务逻辑的实现。将业务逻辑的开发生命周期与任何特定的运行时目标分离,以便相同的代码可以作为web端点、流处理器或任务运行。支持跨无服务器提供商的统一编程模型,以及独立运行(本地或在PaaS中)的能力。在无服务器提供者上启用Spring Boot功能(自动配置、依赖注入、度量)。它抽象了所有的传输细节和基础设施,允许开发人员保留所有熟悉的工具和流程,并牢牢地关注业务逻辑。
需要引入依赖spring-cloud-function-context
Spring Cloud Stream - functional and reactive
这里引入了springcloud alibaba 管理的版本,以下两个依赖引入一个即可
com.alibaba.cloud
spring-cloud-starter-stream-rocketmq
需要说明的是这个也不是最新的客户端,可以考虑排除再引入
spring:
cloud:
stream:
rocketmq:
binder:
name-server: k8s-master:9876 # rcoketmq 命名服务地址
# RocketMQ Consumer 相关的配置。
bindings:
# 通道名称,和spring.cloud.stream.bindings 下的通道名称对应
producer-out-0:
producer:
enable: true # 是否启用producer
group: output_1 # producer分组
bindings:
producer-out-0:
destination: num # 对于rocketmq,此处定义的是消息的topic
@RestController
@Slf4j
public class MqController {
public static final String TOPIC = "TopicTest";
public static final String TAG = "TagA";
public static final String[] tags = new String[] { "TagA", "TagB", "TagC", "TagD",
"TagE" };
@Autowired
private StreamBridge streamBridge;
// http://127.0.0.1:8013/rocketmq-application/send/msg?msg=abc
@RequestMapping("/send/msg")
public String hello(@RequestParam(name = "msg", defaultValue = "hello world") String msg) {
for (int i = 0; i < 100; i++) {
String key = "KEY" + i;
Map headers = new HashMap<>();
headers.put(MessageConst.PROPERTY_KEYS, key);
headers.put(MessageConst.PROPERTY_TAGS, tags[i % tags.length]);
headers.put(MessageConst.PROPERTY_ORIGIN_MESSAGE_ID, i);
Message message = new GenericMessage(
new SimpleMsg(msg + " : " + i), headers);
streamBridge.send("producer-out-0", message);
}
return "消息发送成功";
}
}
通道名称和函数名称涉及到约定配置
官方文档:https://docs.spring.io/spring-cloud-stream/docs/3.2.6/reference/html/spring-cloud-stream.html#_functional_binding_names
input - + -in- +
output - + -out- +
你可以指定函数绑定的通道名称
--spring.cloud.stream.function.bindings.uppercase-in-0=input
--spring.cloud.stream.bindings.input.destination=my-topic
spring:
cloud:
stream:
function:
definition: producer
# RocketMQ Consumer 相关的配置。
rocketmq:
binder:
name-server: k8s-master:9876 # rcoketmq 命名服务地址
group: output_common # 多个通道生产消息,生产者组在此处配置,否则多个通道生产者组不一致报错spring.cloud.stream.rocketmq.bindings.《channelname》.producer.group中配置的无效,
bindings:
producer-out-0:
destination: num
大致是spring cloud stream 根据配置中的名称拿到了容器中的函数实例,调用Supplier实例的get方法,会被循环调用,每调用一次,发送一次消息,消息的主题
@Configuration
@Slf4j
public class RocketMQComprehensive {
@Bean
public Supplier> producer() {
return () -> Flux.interval(Duration.ofSeconds(2)).map(id -> {
User user = new User();
user.setId(id.toString());
user.setName("freeman");
user.setMeta(new StringObjectMapBuilder()
.put("hobbies", Arrays.asList("movies", "songs")).put("age", 21)
.get());
return user;
}).log();
}
}
@StreamListener @Input 等注解按照显示已经被弃用,而且使用起来报错,所以消费者示例就这一个
spring:
cloud:
stream:
function:
definition: consumer
rocketmq:
binder:
name-server: k8s-master:9876 # rcoketmq 命名服务地址
group: output_common # 要求相同角色的消费者拥有完全相同的订阅和消费者组,以正确地实现负载平衡。它必须是全球独一无二的。生产者组在概念上聚合完全相同角色的所有生产者实例,这在涉及事务消息时尤其重要。对于非事务性消息,只要每个进程都是惟一的就没关系。进一步讨论请参见这里。
# RocketMQ Consumer 相关的配置。
bindings:
# 通道名称,和spring.cloud.stream.bindings 下的通道名称对应
consumer-in-0:
enable: true # 是否启用consumer,默认true
bindings:
consumer-in-0:
destination: num
group: consumer_group
@Configuration
@Slf4j
public class RocketMQComprehensive {
@Bean
public Consumer consumer() {
return num -> {
log.info("接收到消息:"+ num.toString());
};
}
}
spring:
cloud:
stream:
function:
definition: producer;consumer;processor
rocketmq:
binder:
name-server: k8s-master:9876 # rcoketmq 命名服务地址
group: output_common # 要求相同角色的消费者拥有完全相同的订阅和消费者组,以正确地实现负载平衡。它必须是全球独一无二的。生产者组在概念上聚合完全相同角色的所有生产者实例,这在涉及事务消息时尤其重要。对于非事务性消息,只要每个进程都是惟一的就没关系。进一步讨论请参见这里。
# RocketMQ Consumer 相关的配置。
bindings:
# 通道名称,和spring.cloud.stream.bindings 下的通道名称对应
producer_out_0:
producer:
group: output_1 # producer分组 ,建议配置spring.cloud.stream.rocketmq.binder.group(优先更高)
processor-out-0:
producer:
group: output_2
bindings:
producer-out-0:
destination: num
processor-out-0:
destination: square
processor-in-0:
destination: num
group: processor_group
consumer-in-0:
destination: square
group: consumer_group
@Configuration
@Slf4j
public class RocketMQComprehensive {
@Bean
public Supplier> producer() {
return () -> Flux.interval(Duration.ofSeconds(2)).map(id -> {
User user = new User();
user.setId(id.toString());
user.setName("freeman");
user.setMeta(new StringObjectMapBuilder()
.put("hobbies", Arrays.asList("movies", "songs")).put("age", 21)
.get());
return user;
}).log();
}
@Bean
public Function, Flux> processor() {
return flux -> flux.map(user -> {
log.info("用户信息:{}" ,user.toString());
user.setId(String.valueOf(
Long.parseLong(user.getId()) * Long.parseLong(user.getId())));
return user;
});
}
@Bean
public Consumer consumer() {
return num -> {
log.info("接收到消息:"+ num.toString());
};
}
}
1、定义了三个函数,在配置文件中函数producer和通道producer-out-0进行绑定,发送消息到num这个主题上,
2、rocessor和processor-in-0还有processor-out-0 绑定,接收来自num的消息,又再次发布到square这个主题上
3、 consumer和 consumer-in-0绑定,接收square的消息
spring:
cloud:
stream:
function:
definition: producer
# RocketMQ Consumer 相关的配置。
rocketmq:
binder:
name-server: k8s-master:9876 # rcoketmq 命名服务地址
group: output_common
bindings:
producer-out-0:
producer:
group: output_1
messageQueueSelector: orderlyMessageQueueSelector # MessageQueue选择器
bindings:
producer-out-0:
destination: orderly
选择器代码
@Component
@Slf4j
public class OrderlyMessageQueueSelector implements MessageQueueSelector {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
Integer id = (Integer) ((MessageHeaders) arg)
.get(MessageConst.PROPERTY_ORIGIN_MESSAGE_ID);
//mqs.size()是这个topic总共的messagequeue的个数,我这里是8,
//id % OrderlyExample.tags.length 获取1 2 3 4 5
// 那么 1 6 11 16 21 。。。 应该是在同一个messageQueue上,并且tag都是tagA
int index = id % OrderlyExample.tags.length % mqs.size();
return mqs.get(index);
}
}
消息发送代码
@Configuration
public class OrderlyExample {
public static final String[] tags = new String[] { "TagA", "TagB", "TagC", "TagD",
"TagE" };
@Autowired
private StreamBridge streamBridge;
@Bean
public ApplicationRunner producer() {
return args -> {
for (int i = 0; i < 100; i++) {
String key = "KEY" + i;
Map headers = new HashMap<>();
headers.put(MessageConst.PROPERTY_KEYS, key);
headers.put(MessageConst.PROPERTY_TAGS, tags[i % tags.length]);
headers.put(MessageConst.PROPERTY_ORIGIN_MESSAGE_ID, i);
Message msg = new GenericMessage(
new SimpleMsg("Hello RocketMQ " + i), headers);
streamBridge.send("producer-out-0", msg);
}
};
}
}
spring:
cloud:
stream:
function:
definition: consumer
rocketmq:
binder:
name-server: k8s-master:9876 # rcoketmq 命名服务地址
group: output_common # 要求相同角色的消费者拥有完全相同的订阅和消费者组,以正确地实现负载平衡。它必须是全球独一无二的。生产者组在概念上聚合完全相同角色的所有生产者实例,这在涉及事务消息时尤其重要。对于非事务性消息,只要每个进程都是惟一的就没关系。进一步讨论请参见这里。
# RocketMQ Consumer 相关的配置。
bindings:
consumer-in-0:
consumer:
# tag: {@code tag1||tag2||tag3 }; sql: {@code 'color'='blue' AND 'price'>100 } .
subscription: 'TagA || TagC || TagD'
push:
orderly: true
bindings:
consumer-in-0:
destination: orderly
group: orderly-consumer
sql的过滤延时,仅做记录,非测试配置
# 对应的需要发消息时在请求头里添加相关配置
# consumer-in-0:
# consumer:
# tag: {@code tag1||tag2||tag3 }; sql: {@code 'color'='blue' AND 'price'>100 } .
# subscription: sql:(color in ('red1', 'red2', 'red4') and price>3)
@Configuration
@Slf4j
public class OrderlyExample {
// 主要是观察是否接收到过滤条件之外的数据,
// 然后接收到的统一tags的数据是否从小到达有序排列
@Bean
public Consumer<Message<SimpleMsg>> consumer() {
return msg -> {
String tagHeaderKey = RocketMQMessageConverterSupport
.toRocketHeaderKey(MessageConst.PROPERTY_TAGS).toString();
log.info(Thread.currentThread().getName() + " Receive New Messages: "
+ msg.getPayload().getMsg() + " TAG:"
+ msg.getHeaders().get(tagHeaderKey).toString());
try {
Thread.sleep(100);
}
catch (InterruptedException ignored) {
}
};
}
}
似乎没啥好说的,看到上面的配置和注释吧
事务消息是消息发送者端配置,用于保证发送消息和本地事务的原子性
rocketmq:
binder:
name-server: localhost:9876
bindings:
producer-out-0:
producer:
group: output_1
transactionListener: myTransactionListener # 指定监听器
# 设置消息为事务消息,原始的api使用专门的事务消息生产者TransactionMQProducer
producerType: Trans
@Component("myTransactionListener")
public class TransactionListenerImpl implements TransactionListener {
/**
* 执行本地事务,由当前发送消息的线程执行
*/
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
Object num = msg.getProperty("test");
if ("1".equals(num)) {
System.out.println("executer: " + new String(msg.getBody()) + " unknown");
return LocalTransactionState.UNKNOW;
}
else if ("2".equals(num)) {
System.out.println("executer: " + new String(msg.getBody()) + " rollback");
return LocalTransactionState.ROLLBACK_MESSAGE;
}
System.out.println("executer: " + new String(msg.getBody()) + " commit");
return LocalTransactionState.COMMIT_MESSAGE;
}
/**
* broker确定本地事务是否成功的回调接口,这个是单独配置的线程池中的线程执行
*/
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
System.out.println("check: " + new String(msg.getBody()));
return LocalTransactionState.COMMIT_MESSAGE;
}
}
@Bean
public ApplicationRunner producer() {
return args -> {
for (int i = 1; i <= 4; i++) {
MessageBuilder builder = MessageBuilder
.withPayload(new SimpleMsg("Hello Tx msg " + i));
builder.setHeader("test", String.valueOf(i)).setHeader(
MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON);
// 不太确定这行代码是否有作用,以及改怎么配置
builder.setHeader(RocketMQConst.USER_TRANSACTIONAL_ARGS, "binder");
Message<SimpleMsg> msg = builder.build();
streamBridge.send("producer-out-0", msg);
System.out.println("send Msg:" + msg.toString());
}
};
}
@Bean
public ApplicationRunner producerDelay() {
return args -> {
for (int i = 0; i < 100; i++) {
String key = "KEY" + i;
Map<String, Object> headers = new HashMap<>();
headers.put(MessageConst.PROPERTY_KEYS, key);
headers.put(MessageConst.PROPERTY_ORIGIN_MESSAGE_ID, i);
// 主要是这一行设置延时级别
headers.put(MessageConst.PROPERTY_DELAY_TIME_LEVEL, 2);
headers.put("a", "userproperties");
Message<SimpleMsg> msg = new GenericMessage(
new SimpleMsg("Delay RocketMQ " + i), headers);
streamBridge.send("producer-out-0", msg);
}
};
}
rocketmq5.0支持设置到具体是时刻, 其直接代码是这样的
message.setDeliverTimeMs(System.currentTimeMillis() + 10_000L);
可以尝试排除rocketmq4.9.4的依赖,然后引入5.0的依赖,大概率可能不行,有可能会是这个配置
MessageConst.PROPERTY_CONSUME_START_TIMESTAMP
默认是集群模式,而不是广播模式,这个实在消费者端设置的
rocketmq:
binder:
name-server: localhost:9876
bindings:
consumer-in-0:
consumer:
messageModel: BROADCASTING
消费端配置
上一级配置是rocketmq
consumer-in-0:
consumer:
## According to the configured number of `max-reconsume-times`,
## the server will re-push the message according to whether the client's consumption is successful or not
push:
max-reconsume-times: 3
@Bean
public Consumer<Message<SimpleMsg>> consumer() {
return msg -> {
throw new RuntimeException("mock exception.");
};
}
配置来自官网说明的配置项,地址前面已经提供,使用这个配置你可能启动不了应用,仅做参考
spring:
cloud:
stream:
rocketmq:
binder:
name-server: k8s-master:9876 # rcoketmq 命名服务地址
enable-msg-trace: true # 是否为 Producer 和 Consumer 开启消息轨迹功能 默认true,感觉还需要结合服务端配置使用
access-key: #阿里云账号 AccessKey。
secret-key: #阿里云账号 SecretKey。消息轨迹开启后存储的 topic 名称。
customized-trace-topic: # 消息轨迹开启后存储的 topic 名称。 默认RMQ_SYS_TRACE_TOPIC
# RocketMQ Consumer 相关的配置。
bindings:
# 通道名称,和spring.cloud.stream.bindings 下的通道名称对应
producer-out-0:
producer:
enable: true # 是否启用producer
group: output_1 # producer分组
maxMessageSize: 8249344 #消息发送的最大字节数。默认8249344
transactional: false # 是否发送事务消息 默认false
sync: false # 是否使用同步方式发送消息,就是发送完需要等发送的结果
vipChannelEnabled: true # 默认true 是否走vip通道发送消息,也就是broker fastRemotingServer端口发送消息
compressMessageBodyThreshold: 4096 # 消息体压缩阈值,默认超过4k会压缩
retryTimesWhenSendFailed: 2 # 在同步发送消息的模式下,消息发送失败的重试次数。默认2
retryTimesWhenSendAsyncFailed: 2 # 在异步发送消息的模式下,消息发送失败的重试次数。默认2
retryNextServer: false # 消息发送失败的情况下是否重试其它的 broker。
consumer:
enable: true # 是否启用consumer
tags: tagA||tagB # Consumer 基于 TAGS 订阅,多个 tag 以 || 分割。
sql: "TAGS is not null and TAGS in ('TagA', 'TagB')" # 基于sql过来消息
broadcasting: false # Consumer 是否是广播消费模式。如果想让所有的订阅者都能接收到消息,可以使用广播模式。默认值false
orderly: false # 是否有序消费消息,需要客户端发送消息到同一个message queue
delayLevelWhenNextConsume: 0 #异步消费消息模式下消费失败重试策略 -1,不重复,直接放入死信队列 0,broker 控制重试策略 >0,client 控制重试策略 默认值: 0.
suspendCurrentQueueTimeMillis: 3000 # 顺序消息消费失败后,再次消费的时间间隔
processor-out-0:
producer:
group: output_2
bindings:
producer-out-0:
destination: num # 对于rocketmq,此处定义的是消息的topic
processor-out-0:
destination: square
processor-in-0:
destination: num
group: processor_group
consumer-in-0:
destination: square
group: consumer_group
org.springframework.cloud.stream.config.BindingProperties
@EnableBinding
@StreamListener # 按照示例使用报错,找不到对应的bean
@Input # 同样失败
Dispatcher has no subscribers for channel 'rocketmq-application.processor-in-0'.;
---- Dispatcher has no subscribers,
将function的配置定义在bindings前面,或者手动指定绑定关系(不确定)
function:
definition: producer;consumer;processor
就最终的解决来看,你可以往前翻看一下报错,我这里是由于消息接收后处理异常,大致是id接收了一个string类型的,所以异常了,然后导致后续问题,比如可能因为异常,所以消息订阅者就直接关闭了,详细的话可能还需要了解源码或原理
多个通道生产者组的名称统一配置
Exception thrown while building outbound endpoint
Property 'group' is required - producerGroup
就我当前这个版本而言,如果定义了多个生产者,那么生产者组需要统一定义,但是不影响消费者组的定义
rocketmq:
binder:
name-server: k8s-master:9876 # rcoketmq 命名服务地址
group: output_common # 要求相同角色的消费者拥有完全相同的订阅和消费者组,以正确地实现负载平衡。它必须是全球独一无二的。生产者组在概念上聚合完全相同角色的所有生产者实例,这在涉及事务消息时尤其重要。对于非事务性消息,只要每个进程都是惟一的就没关系。进一步讨论请参见这里。
# RocketMQ Consumer 相关的配置。
n.RemotingTooMuchRequestException: sendDefaultImpl call timeout
我看下载的示例项目中有这样的配置
bindings:
consumer-in-0:
consumer:
messageModel: BROADCASTING
可能是对应rokcetMq 消费者类org.apache.rocketmq.client.consumer.DefaultMQPushConsumer的一个属性
private MessageModel messageModel;
然后如果以后有找不到的配置,可以尝试这样,当然仅仅是猜测,这块我没试验
还有就是官方示例中的配置部分和官方文档上写明的配置不一样,可能够有效吧,由于部分示例我这边没有进行测试,所以如果示例中的配置不好使,可以参考本文中比较全的配置文件
还有springCloud bus 结合rocketmq的架构,这个之后再说吧,https://blog.csdn.net/weixin_43847283/article/details/122419187