Spring Cloud Stream 构建消息驱动微服务

  Spring Cloud Stream 进一步封装了消息队列,可以做到代码层面对消息队列无感知。

  

    添加依赖

  消息队列是 RabbitMQ ,如果你是用的是kafka,换成对应的spring-cloud-starter-stream-kafka依赖即可

    <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
        </dependency>    

  配置文件配置RabbitMQ的地址信息

  spring-cloud-starter-stream-rabbit是Spring Cloud Stream对RabbitMQ的封装,包含对RabbitMQ的自动化配置,比如连接的RabbitMQ的默认地址localhost,默认端口5672,默认用户guest,默认密码guest,如果采用的是如上默认配置,可以不用修改配置。

  配置文件放到了远端的Git,通过config server 拉取配置

server:
  port: 8081

spring:
   application:
      name: order
   datasource:
     driver-class-name: com.mysql.jdbc.Driver
     url: jdbc:mysql://localhost:3306/o2o_order?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false
     username: root
     password: root
   jpa:
     show-sql: true 
   rabbitmq: 
     host: 192.168.223.139
     username: guest
     password: guest
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8762/eureka/

env:
  dev-test

  接口定义

  Sink和Source两个接口分别定义输入通道和输出通道,Processor通过继承Source和Sink,同时具有输入通道和输出通道。就模仿Sink和Source,自定义一个消息通道

public interface MySink {
    // 同一个服务里面的通道名字不能一样,在不同的服务里可以相同名字的通道
    // 否则启动抛出如下异常  bean definition with this name already exists
    String INPUT = "MyMsgInput";
  
   @Input(MySink.INPUT)
    SubscribableChannel input();

}

  定义一个名为MyMsgInput的消息输入通道,@Input注解的参数则表示消息通道的名称

       接收方 @EnableBinding @StreamListener

       StreamReceive 用来接收RabbitMQ发送来的消息

    第一步: 使用了@EnableBinding注解实现对消息通道的绑定,在该注解中还传入一个参数MySink.class,MySink是一个自定义接口,主要功能是实现对输入消息通道绑定的定义

    第二步:在StreamReceiver 类中定义processStreamMsg方法,重点是在该方法上添加@StreamListener注解,该注解表示该方法为消息中间件上数据流的事件监听器MySink.INPUT参数表示这是input消息通道上的监听处理器。

@Component
// Step1 注解  绑定消息输入的接口
@EnableBinding(MySink.class)
@Slf4j
public class StreamReceive {

    @StreamListener(MySink.INPUT)
    @SendTo(Source.output)
    public String onMessage(Object object){
        log.info("Input Stream receive message :{}",object);
        return "receive ok";
    }
}

  

@RestController
@Slf4j
public class MsgStreamController {

@Autowired
private MySink mySink;

@GetMapping("/sendMsgByStream")
public void sendMsgByStream(){
String msg="I am one message from Spring Cloud Stream";
Message<String> message = MessageBuilder.withPayload(msg).setHeader("contentType","text/plain").build();
mySink.input().send(message);
}
}

  通过 @Autowired自动注入刚才的Sink接口,然后调用 sink.input().send方法发送消息即可,启动服务,观察RabbitMQ上的队列 ,自动创建了一个

  访问: http://localhost:8081/sendMsgByStream

消费组

  需求: 由于服务可能会有多个实例同时在运行,我们只希望消息被一个实例所接收

  启动多个服务实例

  为了多启动几个节点,需要把定义在远端Git上的要加载到bootstrap.yml中的端口信息给注释掉,否则第二个端口因端口冲突起不来。

查看在Eureka Server上的注册情况

 

  看看RabbitMQ的消息队列情况:

  消息执回

  消费者收到消息后给发送方一个ACK确认,该如何做呢?

@Component
// Step1 注解  绑定消息输入的接口
@EnableBinding(MySink.class)
@Slf4j
public class StreamReceive {

    /**
     * @StreamListener - 绑定监听到指定的Exchange。
     * @param object 接收到的消息内容。此参数类型必须是可序列化的。
     */
    @StreamListener(MySink.INPUT)
    @SendTo(Source.output)
    public String onMessage(Object object){
        log.info("receive message :{}",object);
        return "receive ok";
    }
}

  接收到消息后,返回给Source.OUTPUT一个消息,直接使用@SendTo直接即可,就会将返回的字符串发送给Source.OUTPUT通道

public interface Source {
    String output="myMsgOutput";

    @Output(output)
    MessageChannel output();
}

  写一个该消息的接收方

@Component
@Slf4j
@EnableBinding(Source.class)
public class StreamRecevie2 {

    @StreamListener(Source.output)
    public void onMessage(String message) {
        log.info("Output Stream Receive:{}",message);
    }
}

  访问:http://localhost:7001/sendObject

 

   

 

 

   

  

 

posted on 2019-09-25 23:11  溪水静幽  阅读(225)  评论(0)    收藏  举报