Spring Cloud Stream 实战

本文结合实际项目经验,重新对Spring Cloud Stream做一个总结。

一: 目的:

Spring cloud集成Kafka,RabitMQ, 可以在不改变业务代码的情况下,自由切换消息中间件,仅需改变配置即可,类似于Hibernate,可以自由在各种数据库之间进行切换。

 二: 架构

 

 

 

 三:基础示例一:

1.  jar 包依赖以及版本:

     本示例所采用的 spring-boot-starter-parent的版本为2.3.6.RELEASE。spring-cloud-starter-stream-rabbit或者spring-cloud-stream-binder-rabbit(二选一即可)的版本为3.0.9.RELEASE。

 1 <!--        <dependency>-->
 2 <!--            <groupId>org.springframework.cloud</groupId>-->
 3 <!--            <artifactId>spring-cloud-starter-stream-rabbit</artifactId>-->
 4 <!--            <version>3.0.9.RELEASE</version>-->
 5 <!--        </dependency>-->
 6         <dependency>
 7             <groupId>org.springframework.cloud</groupId>
 8             <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
 9             <version>3.0.9.RELEASE</version>
10         </dependency>

 2.  系统相关类之间的依赖关系:

    理解各个接口之间的关系,可以为理解配置的写法以及后面的实战重构代码打下基础。

      

   3.  生产者的简单实现:

    生产者通过Source接口的output()向MessageChannel发送消息:

 1 @Component
 2 @EnableBinding(Source.class)
 3 public class MessageProducer {
 4     private static final Logger LOGGER = LoggerFactory.getLogger(MessageProducer.class);
 5 
 6     @Autowired
 7     private Source source;
 8 
 9     public void sendMessage(){
10         LOGGER.info("sendMessage");
11         source.output().send(MessageBuilder.withPayload("Hello World from Provider").build());
12     }
13 }

 

   4. 消费者的简单实现:

    消息的消费端通过StreamListener来监听Sink接口接收消息:

 1 @EnableBinding(Sink.class)
 2 @Component
 3 public class MessageConsumer {
 4     private static final Logger LOGGER = LoggerFactory.getLogger(MessageConsumer.class);
 5 
 6     @StreamListener(Sink.INPUT)
 7     public void receive(String message){
 8         LOGGER.info("received message: {} from MQ", message);
 9     }
10 }

 

   5. 配置文件:

    注:Output和input分别为Output和Input接口中的值。

 1 spring:
 2   cloud:
 3     stream:
 4       bindings:
 5         #message send channel, Output is the same name with org.springframework.cloud.stream.messaging.Source @Output("Output") value
 6         Output:
 7           destination: test.exchange
 8           binder: test_binder
 9         Input:
10           destination: test.exchange
11       binders:
12         test_binder:
13           type: rabbit
14           environment:
15             spring:
16               rabbitmq:
17                 host: 127.0.0.1
18                 port: 5672
19                 username: guest
20                 password: guest
21                 virtual-host: /

   6. 测试:

   通过log模拟消息的发送和接受的过程:

   2022-07-04 11:55:28.520 INFO 8236 --- [nio-8080-exec-4] c.a.s.stream.provider.MessageProducer : sendMessage

   2022-07-04 11:55:28.523 INFO 8236 --- [Z22pN_dOiB4zg-1] c.a.s.stream.consumer.MessageConsumer : received message: Hello World from Provider from MQ

 

 四. 自定义Channel(单个)

     在实际项目中,我们回有多种不同类型的消息的生产者和消费者,就回用到不同的消息队列对消息进行处理,故需要子定义channel来处理。

    1. 自定义channel的依赖

       

 

    2. 自定义Channel的接口

       如上图所示,我们需要重写Source和Sink的接口。

    3. 使用自定义Channel的生产者

 1 @Component
 2 @EnableBinding(MySource.class)
 3 public class MessageProducer {
 4     private static final Logger LOGGER = LoggerFactory.getLogger(MessageProducer.class);
 5 
 6     @Autowired
 7     private MySource source;
 8 
 9     public void sendMessage(){
10         LOGGER.info("sendMessage");
11         source.output().send(MessageBuilder.withPayload("Hello World from Provider").build());
12     }
13 }

 

    4. 使用自定义Channel的消费者

 1 @EnableBinding(MySink.class)
 2 @Component
 3 public class MessageConsumer {
 4     private static final Logger LOGGER = LoggerFactory.getLogger(MessageConsumer.class);
 5 
 6     @StreamListener(MySink.MY_INPUT)
 7     public void receive(String message){
 8         LOGGER.info("received message: {} from MQ", message);
 9     }
10 }

 

    5. 使用自定义Channel的配置修改

 1 spring:
 2   cloud:
 3     stream:
 4       bindings:
 5         #message send channel, Output is the same name with org.springframework.cloud.stream.messaging.Source @Output("Output") value
 6         my_output:
 7           destination: test.exchange
 8           binder: test_binder
 9         my_input:
10           destination: test.exchange
11       binders:
12         test_binder:
13           type: rabbit
14           environment:
15             spring:
16               rabbitmq:
17                 host: 127.0.0.1
18                 port: 5672
19                 username: guest
20                 password: guest
21                 virtual-host: /

 

五. 多个Channel综合模拟实例 

    1. 功能流程

      

    2. 关键代码实现.

     MessageChannelConstants定义各种要用到的消息交换渠道:     

1 public class MessageChannelConstants {
2     public static final String NORMAL_MESSAGE_PROCUDER = "normalMessageProducer";
3     public static final String SMS_MESSAGE_PROCUDER = "smsMessageProducer";
4     public static final String EMAIL_MESSAGE_PROCUDER = "emailMessageProducer";
5 
6     public static final String NORMAL_MESSAGE_CONSUMER = "normalMessageConsumer";
7     public static final String SMS_MESSAGE_CONSUMER = "smsMessageConsumer";
8     public static final String EMAIL_MESSAGE_CONSUMER = "emailMessageConsumer";
9 }

ChannelFactory,使用工厂方法获取MessageChannel:
@Component("rabbitChannelFactory")
public class ChannelFactory {
    private MessageChannel normalMessageChannel;
    private MessageChannel smsMessageChannel;
    private MessageChannel emailMessageChannel;

    public ChannelFactory(@Qualifier(MessageChannelConstants.NORMAL_MESSAGE_PROCUDER) MessageChannel normalMessageChannel,
                          @Qualifier(MessageChannelConstants.SMS_MESSAGE_PROCUDER)MessageChannel smsMessageChannel,
                          @Qualifier(MessageChannelConstants.EMAIL_MESSAGE_PROCUDER)MessageChannel emailMessageChannel) {
        this.normalMessageChannel = normalMessageChannel;
        this.smsMessageChannel = smsMessageChannel;
        this.emailMessageChannel = emailMessageChannel;
    }

    public MessageChannel getChannelByMessageType(MessageType messageType){
        switch (messageType){
            case NORAL:
                return this.normalMessageChannel;
            case EMAIL:
                return this.emailMessageChannel;
            case SMS:
                return this.smsMessageChannel;
            default:
                return null;
        }
    }
}

 

自定义@Input和@Output的接口(注:要配置Configuration@EnableBinding({ApplicationProcessorOutput.class, ApplicationProcessorInput.class})):

public interface ApplicationProcessorInput {
    @Input(MessageChannelConstants.NORMAL_MESSAGE_CONSUMER)
    SubscribableChannel normalMessageInput();
    @Input(MessageChannelConstants.SMS_MESSAGE_CONSUMER)
    SubscribableChannel smsMessageInput();
    @Input(MessageChannelConstants.EMAIL_MESSAGE_CONSUMER)
    SubscribableChannel emailMessageInput();
}

public interface ApplicationProcessorOutput {

    @Output(MessageChannelConstants.NORMAL_MESSAGE_PROCUDER)
    MessageChannel normalMessageOutput();

    @Output(MessageChannelConstants.SMS_MESSAGE_PROCUDER)
    MessageChannel smsMessageOutput();

    @Output(MessageChannelConstants.EMAIL_MESSAGE_PROCUDER)
    MessageChannel emailMessageOutput();
}

 

消息发送端的公共类ApplicationEventPublisher,具体发消息时候,只用传入对应的消息类型既可:

@Component
public class ApplicationEventPublisher implements Publisher{

    private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationEventPublisher.class);

    private ChannelFactory channelFactory;

    public ApplicationEventPublisher(@Qualifier("rabbitChannelFactory") ChannelFactory channelFactory) {
        this.channelFactory = channelFactory;
    }


    @Override
    public <T> void publish(Map<String, Object> headers, T material, MessageType messageType) {
        MessageChannel channel = channelFactory.getChannelByMessageType(messageType);
        if (!ObjectUtils.isEmpty(channel)){
            LOGGER.info("publish() {}", material.toString());
            channel.send(MessageBuilder.createMessage(material, new MessageHeaders(headers)));
        }
    }
}

 

配置文件:

spring:
  cloud:
    stream:
      bindings:
        #message send channel, Output is the same name with org.springframework.cloud.stream.messaging.Source @Output("Output") value
        normalMessageProducer:
          destination: noral.message.exchange
          binder: message_binder
          group: group_normal_message
        smsMessageConsumer:
          destination: sms.message.exchange
          binder: message_binder
          group: group_sms_message
        emailMessageConsumer:
          destination: email.message.exchange
          binder: message_binder
          group: group_email_message

 

3. 消息分组和分区

    在微服务的架构中,同一个服务回被部署成多份,就会监听同一个消息队列。如果不进行分组,就会导致重复消费。

 

4. 测试
   localhost:8080/sendMessage
 
  log: 

2022-10-01 12:21:55.250  INFO 48204 --- [nio-8080-exec-7] c.aaron.rabbitmq.service.MessageService  : normal message is: qbz@163.com, Jingle Bells
2022-10-01 12:21:55.251  INFO 48204 --- [nio-8080-exec-7] c.a.r.m.p.ApplicationMessagePublisher    : publish qbz@163.com, Jingle Bells to channel NORMAL
2022-10-01 12:21:55.254  INFO 48204 --- [LS44twisLVGNg-1] c.a.r.m.receiver.NormalMessagerReceiver  : received the normal message qbz@163.com, Jingle Bells
2022-10-01 12:21:55.255  INFO 48204 --- [LS44twisLVGNg-1] c.a.r.m.p.ApplicationMessagePublisher    : publish qbz@163.com to channel EMAIL
2022-10-01 12:21:55.256  INFO 48204 --- [LS44twisLVGNg-1] c.a.r.m.p.ApplicationMessagePublisher    : publish  Jingle Bells to channel TEXT
2022-10-01 12:21:55.260  INFO 48204 --- [j-HoU_ypdqwJw-1] c.a.r.mq.receiver.EmailMessagerReceiver  : received the email message qbz@163.com
2022-10-01 12:21:55.260  INFO 48204 --- [Q2Mw58fe_Td7A-1] c.a.r.mq.receiver.TextMessagerReceiver   : received the text message  Jingle Bells

 

 5. 项目结构:

 

 

posted @ 2022-07-05 17:34  梅花瘦  阅读(666)  评论(0)    收藏  举报