1、 Stream introduction
Springcloud stream is developed on the basis of spring messaging and spring integration projects. We may use multiple MQ in the project, or we need to replace MQ at a certain stage of the project. If we use stream, we can just change the configuration without modifying the code for sending and receiving messages. Moreover, the messaging model defined by stream makes our code more concise and understandable.
For detailed introduction documents, please refer to:
Introduction to spring cloud stream system and principle (qq.com)
2、 Introductory case
First we start withstart.spring.io
Download a brand-new project. The required dependencies include web and Lombok. After downloading, introduce the stream rocketmq dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
<version>2021.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
Then we start creating producers:
@Slf4j
@Component
@EnableBinding(Source.class)
public class MyMessageProducer {
@Autowired
private Source source;
public void send(String message){
Log Info ("producer starts sending messages!");
source.output().send(MessageBuilder.withPayload(message).build());
}
}
We use the controller to send messages to the departure producer:
@RestController
public class ProducerController {
@Autowired
private MyMessageProducer producer;
@GetMapping("/sendMessage")
public String sendMessage(){
producer.send("hello spring cloud stream!");
return "OK";
}
}
Then we create consumers:
@Slf4j
@Component
@EnableBinding(Sink.class)
public class MyMessageConsumer {
@StreamListener(Sink.INPUT)
public void receive(String message){
Log Info ("the message content listened to is: {}", message);
}
}
Finally, the most important content of our configuration file:
server:
port: 8081
spring:
application:
name: stream-demo
cloud:
stream:
bindings:
output:
destination: myRocketMQTopic
group: test-stream
input:
destination: myRocketMQTopic
group: test-stream
rocketmq:
binder:
name-server: 127.0.0.1:9876
On the premise of starting the MQ service and starting our project, we can successfully run a QuickStart case of stream rocketmq. Our browser accesses the link so that the producer sends a message, and then the consumer can listen to the message.
2021-07-16 09:35:26.805 info 16920 -- [nio-8081-exec-1] c.e.s.producer Mymessageproducer: the producer starts sending messages!
2021-07-16 09:35:49.737 info 16920 -- [messagethread_1] c.e.s.consumer Mymessageconsumer: the message content is: Hello spring cloud stream!
2021-07-16 09:36:14.371 info 16920 -- [nio-8081-exec-3] c.e.s.producer Mymessageproducer: the producer starts sending messages!
2021-07-16 09:36:14.379 info 16920 -- [messagethread_1] c.e.s.consumer Mymessageconsumer: the message content is: Hello spring cloud stream!
3、 Custom channel
In the above introductory case, we use the output and input channels of the stream, and we can customize our own channels by imitating them.
public interface MyOutputChannel {
@Output("myOutput")
MessageChannel myOutput();
}
public interface MyInputChannel {
@Input("myInput")
SubscribableChannel myInput();
}
Then we create a new producer and use a custom output channel.
@Slf4j
@Component
@EnableBinding(MyOutputChannel.class)
public class MyMessageProducer2 {
@Autowired
private MyOutputChannel myOutputChannel;
public void send(String message){
Log Info ("mymessageproducer2 producer starts sending messages!");
myOutputChannel.myOutput().send(MessageBuilder.withPayload(message).build());
}
}
Create a new consumer to listen to the customized input channel.
@Slf4j
@Component
@EnableBinding(MyInputChannel.class)
public class MyMessageConsumer2 {
@StreamListener("myInput")
public void receive(String message){
Log Info ("myinput listens to the message content as: {}", message);
}
}
Create a new controller to trigger sending messages:
@RestController
public class ProducerController2 {
@Autowired
private MyMessageProducer2 producer;
@GetMapping("/sendMessage2")
public String sendMessage(){
producer.send("hello spring cloud stream using customize channel!");
return "OK";
}
}
Finally, we need to add our customized output and input channels in the configuration file:
server:
port: 8081
spring:
application:
name: stream-demo
cloud:
stream:
bindings:
output:
destination: myRocketMQTopic
group: test-stream
myOutput:
destination: myRocketMQTopic2
group: test-stream2
input:
destination: myRocketMQTopic
group: test-stream
myInput:
destination: myRocketMQTopic2
group: test-stream2
rocketmq:
binder:
name-server: 127.0.0.1:9876
Thus, the channels in our two examples can work at the same time.
2021-07-16 10:16:46.495 info 18580 -- [nio-8081-exec-3] c.e.s.producer Mymessageproducer2: mymessageproducer2 producer starts sending messages!
2021-07-16 10:16:52.220 info 18580 -- [nio-8081-exec-2] c.e.s.producer Mymessageproducer2: mymessageproducer2 producer starts sending messages!
2021-07-16 10:16:52.808 info 18580 -- [nio-8081-exec-4] c.e.s.producer Mymessageproducer2: mymessageproducer2 producer starts sending messages!
2021-07-16 10:16:55.610 info 18580 -- [nio-8081-exec-5] c.e.s.producer Mymessageproducer: the producer starts sending messages!
2021-07-16 10:16:55.729 info 18580 -- [messagethread_1] c.e.s.consumer Mymessageconsumer: the message content is: Hello spring cloud stream!
2021-07-16 10:16:56.306 info 18580 -- [messagethread_1] c.e.s.consumer Mymessageconsumer2: myinput listens to the message: Hello spring cloud stream using customize channel!
2021-07-16 10:16:56.306 info 18580 -- [messagethread_1] c.e.s.consumer Mymessageconsumer2: myinput listens to the message: Hello spring cloud stream using customize channel!
2021-07-16 10:16:58.249 info 18580 -- [nio-8081-exec-6] c.e.s.producer Mymessageproducer: the producer starts sending messages!
2021-07-16 10:16:58.256 info 18580 -- [messagethread_1] c.e.s.consumer Mymessageconsumer: the message content is: Hello spring cloud stream!
2021-07-16 10:16:59.310 info 18580 -- [messagethread_1] c.e.s.consumer Mymessageconsumer2: myinput listens to the message: Hello spring cloud stream using customize channel!
4、 Message filtering
There are two methods to filter messages. The first is to set the consumption tag of a cosumer so that the specified tag will be consumed, and the messages of unspecified tags will be filtered; The other is to filter according to the customized header content.
To distinguish the above examples, we re create a channel:
public interface NewChannel {
String INPUT = "newInput";
String OUTPUT = "newOutput";
@Output(NewChannel.INPUT)
MessageChannel output();
@Input(NewChannel.INPUT)
SubscribableChannel input();
}
Then there are producers and consumers:
@Slf4j
@Component
@EnableBinding(NewChannel.class)
public class MyMessageProducer3 {
@Autowired
private NewChannel newChannel;
public void send(Person person){
Log Info ("mymessageproducer3 producer starts sending messages!");
Message<Person> message1 = MessageBuilder.withPayload(person)
//Specify the tag of messages in rocketmq
.setHeader(RocketMQHeaders.TAGS, "animal")
//Custom headers are used for filtering by consumer listeners
.setHeader("MyTag", "TAG1")
.build();
Message<Person> message2 = MessageBuilder.withPayload(person)
.setHeader(RocketMQHeaders.TAGS, "person")
.setHeader("MyTag", "TAG2")
.build();
Message<Person> message3 = MessageBuilder.withPayload(person)
.setHeader(RocketMQHeaders.TAGS, "person")
.setHeader("MyTag", "TAG3")
.build();
newChannel.output().send(message1);
newChannel.output().send(message2);
newChannel.output().send(message3);
}
}
@Slf4j
@Component
@EnableBinding(NewChannel.class)
public class MyMessageConsumer3 {
@StreamListener(value = NewChannel.INPUT, condition = "headers['MyTag']=='TAG2'")
public void receive(Message<Person> message){
Log Info ("mymessageconsumer3 listens that the message content is: {}", message);
Log Info ("payload of mymessageconsumer3 message:{}", message.getpayload());
Log Info ("mymessageconsumer3 message header:{}", message.getheaders() Get ("mytag");
}
}
The entity class of the message is:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
private String name;
private Integer age;
}
Create a new controller to send messages:
@RestController
public class ProducerController3 {
@Autowired
private MyMessageProducer3 producer;
@GetMapping("/sendMessage3")
public String sendMessage(){
Person tom = new Person("tom", 21);
producer.send(tom);
return "OK";
}
}
The configuration file is modified to:
server:
port: 8081
spring:
application:
name: stream-demo
cloud:
stream:
bindings:
newInput:
destination: newRocketMQTopic
group: test-stream3
newOutput:
destination: newRocketMQTopic
group: test-stream3
rocketmq:
binder:
name-server: 127.0.0.1:9876
enable-msg-trace: true
bindings:
newInput:
consumer:
#Accept only messages with the specified Tags
tags: person || plant
When the application is restarted, we can access the controller interface and send three messages at the same time.
In the configuration file, we specify that consumers only consume messages with tags of person and plant, and messages with tags of animal will be displayedCONSUMED_BUT_FILTERED
;
In the consumer consumption configuration code, our annotation @streamlistener specifies that the condition is consumption onlyheaders['MyTag']=='TAG2'
Message of; The message of tag3 is also considered, but the corresponding consumer is not found, so the console displays:
Cannot find a @StreamListener matching for message with id: 48260ac4-772b-54b8-2ae0-8b91635729a9
5、 Functional programming
Since springcloud has been upgraded to version 3.1 or above, the command-line programming annotations @enablebinding, @output, @input, etc. in the above example have been marked as abandoned. The abandonment instructions are as follows:
@deprecated as of 3.1 in favor of functional programming model
So how can we use functional programming to transform the above example.
5.1 timing drive model
We need to re create a project, the parent project stream function, and introduce the following dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-stream-rocketmq -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
<version>2021.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
Then create a sub project consumer and create a producer class:
@Slf4j
@Component
public class FunctionConsumer {
@Bean
public Consumer<Date> mySink(){
return (message) -> {
Log Info ("received message is: {}", message);
};
}
}
Add profile:
server:
port: 8082
spring:
application:
name: stream-demo-consumer
cloud:
stream:
bindings:
#Format: method name - type - sequence number
#The method name represents the name of the method that consumes the message
#Type in indicates message receiving, and out indicates message sending
#Serial number is used to distinguish different consumers
mySink-in-0:
# topic
destination: functionRocketMQTopic
group: test-stream4
rocketmq:
binder:
name-server: 127.0.0.1:9876
enable-msg-trace: true
function:
#Name of the method by which the consumer processes the message
definition: mySink
At this point, the consumer is finished, can start running, and will start listening for messages on the functionrocketmqtopic topic.
Then we need to create a sub project producer and create a new producer class:
@Slf4j
@Component
public class FunctionProducer {
@Bean
public Supplier<Date> mySource(){
return () -> {
Log Info ("start of sending message!");
return new Date();
};
}
}
Then add the following configuration files:
server:
port: 8081
spring:
application:
name: stream-demo-producer
cloud:
stream:
bindings:
#Format: method name - type - sequence number
#The method name represents the name of the method that consumes the message
#Type in indicates message receiving, and out indicates message sending
#Serial number is used to distinguish different consumers
mySource-out-0:
# topic
destination: functionRocketMQTopic
group: test-stream4
rocketmq:
binder:
name-server: 127.0.0.1:9876
enable-msg-trace: true
function:
#Name of the method by which the producer generated the message
definition: mySource
In this way, our producer is also configured and can run after startup.
5.2 streambridge model
This model is mainly used to meet the scenarios where business actions trigger message sending. We just need to create a new producer class:
@Slf4j
@Service
public class StreamBridgeProducer {
@Autowired
private StreamBridge streamBridge;
public void sendDateMessage(){
Message<Date> message = MessageBuilder.withPayload(new Date()).build();
Log Info ("start sending message!");
streamBridge.send("mySource-out-1", message);
}
}
Then create a new controller to call the trigger message sending:
@RestController
public class SendController {
@Autowired
private StreamBridgeProducer streamBridgeProducer;
@GetMapping("/sendMessage")
public String sendMessage(){
streamBridgeProducer.sendDateMessage();
return "OK";
}
}
Finally, the configuration file is changed as follows:
server:
port: 8081
spring:
application:
name: stream-demo-producer
cloud:
stream:
bindings:
mySource-out-1:
destination: functionRocketMQTopic
group: test-stream4
rocketmq:
binder:
name-server: 127.0.0.1:9876
enable-msg-trace: true
In this way, we can start the producer, and the message can be sent successfully when the interface is called.
5.3 multi channel configuration
We can configure multiple producers and consumers in a project at the same time, even both producers and consumers.
Only the configuration file is given here. For the rest, please refer to the example given above.
server:
port: 8081
spring:
application:
name: stream-demo-producer
cloud:
stream:
bindings:
#When there are multiple producer channels, the timing message mode destination can be the same, but the streambridge mode destination cannot be the same
mySource-out-0:
destination: functionRocketMQTopic0
mySource-out-1:
destination: functionRocketMQTopic1
mySource-out-2:
destination: functionRocketMQTopic2
mySource2-out-3:
destination: functionRocketMQTopic3
#Consumer channel
mySink-in-0:
destination: functionRocketMQTopic0
group: test-stream4
rocketmq:
binder:
name-server: 127.0.0.1:9876
enable-msg-trace: true
function:
definition: mySource;mySource2;mySink
6、 Message partition
Springcloudstream message partitioning ensures that messages with the same characteristics are consumed by the same consumer in some scenarios. It can help some message queues that do not support message partitioning to achieve message partitioning.
The producer’s profile is changed to:
server:
port: 8084
spring:
application:
name: stream-demo-producer
cloud:
stream:
bindings:
mySource-out-0:
destination: functionRocketMQTopic
producer:
#Expression rules for specifying partition keys
partition-key-expresion: payload
#Specify the number of partitions
partition-count: 2
rocketmq:
binder:
name-server: 127.0.0.1:9876
enable-msg-trace: true
function:
definition: mySource
The profile of multiple consumers is changed to:
server:
port: 8082
spring:
application:
name: stream-demo-consumer2
cloud:
stream:
#Specify the current number of consumer partitions
instance-count: 2
#Specify the index of the current consumer
instance-index: 0
bindings:
mySink-in-0:
destination: functionRocketMQTopic
group: test-stream4
consumer:
#Open partition
partitioned: true
rocketmq:
binder:
name-server: 127.0.0.1:9876
enable-msg-trace: true
function:
definition: mySink
server:
port: 8083
spring:
application:
name: stream-demo-consumer2
cloud:
stream:
#Specify the current number of consumer partitions
instance-count: 2
#Specify the index of the current consumer
instance-index: 1
bindings:
mySink-in-0:
destination: functionRocketMQTopic
group: test-stream4
consumer:
#Open partition
partitioned: true
rocketmq:
binder:
name-server: 127.0.0.1:9876
enable-msg-trace: true
function:
definition: mySink
To be verified by experiment.
7、 Multi message queue
We can use stream to configure multiple MQS of different types. Specific examples will be added
Springcloudstream self study document – zhihu.com
8、 Other features
In fact, we have already talked about the usage of stream. The rest is mainly about some configuration properties of rocketmq binder, such as:
- consumer
- Tags message filtering
- Start of broadcast mode
- Sequential consumption
- producer
- Start transaction
- Send synchronously
- Send timeout setting and message maximum setting
- retry count
For other features, please refer to the rocketmq documentation on GitHub. This article will not list them one by one.
RocketMQ en · alibaba/spring-cloud-alibaba Wiki · GitHub
9、 Supplement
Sometimes, sending an MQ message shows that the message was not sent successfully. A very strange problem occurs. The solution found on the Internet is to close the VIP channel.
spring:
cloud:
stream:
rocketmq:
default:
producer:
#Close the VIP channel, or the message will fail to be sent
vipChannelEnabled: false
After trying, it really works.