消息发布场景:

springcloudstram mq消息过滤 spring cloud stream 消息确认_Source

用户信息一般不会发生变化,所以我们把信息放入缓存里,不再每一次都去查库,一旦用户信息发生变化,user_service会发布变更事件给到相关的订阅者并更新缓存信息。

一.实现消息发布

主要实现Spring Cloud Stream创建Source组件,Binder组件的配置以及如何与user_service绑定;

pom依赖

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

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

使用 @EnableBinding 注解

对于user_service而言,它是消息发布者,扮演Source角色;所以要给此spring boot应用绑上source组件,使用@EnableBinding;

@SpringCloudApplication

@EnableBinding(Source.class)

public class UserApplication {

    public static void main(String[] args) {

        SpringApplication.run(UserApplication.class, args);

    }

}

@EnableBinding(Source.class) 该注解的作用是告诉spring cloud stream此应用是一个消息发布者,需要绑定到详细中间件。

定义事件

对于事件一般有通用的定义结构。事件类型,操作,事件模型

public class UserInfoChangedEvent{

    //事件类型

    private String type;

    //操作

    private String operation;

    //模型

    private User user;

}

创建Source

直接在使用时注入即可

@Component

public class UserInfoChangedSource {

    private Source source;


    @Autowired

    public UserInfoChangedSource(Source source){

        this.source = source;

    }

 

	private void publishUserInfoChangedEvent(String operation, User user){

        UserInfoChangedEvent change =  new UserInfoChangedEvent(

            UserInfoChangedEvent.class.getTypeName(),

            operation,

            user);

 

        source.output().send(MessageBuilder.withPayload(change).build());

    }

    


}

构建了event事件,使用MessageBuilder方法把事件转换成MessageChannel中可传输的对象。调用out()方法放入管道MessageChannel,再调用管道的send()方法发送出去。

 

配置Binder

为了将消息发出去并且发送至目的地址,就需要配置相关属性。

spring:

  cloud:

    stream:

      bindings:

        output:

          destination:  userInfoChangedTopic

          content-type: application/json

      kafka:

        binder:

          zk-nodes: localhost

	      brokers: localhost

配置队列名称userInfoChangedTopic;kafka相关注册在Zookeeper 上的地址;

集成服务:

最后要做的事就是集成UserInfoChangedSource 到我们的user_service;只需要在使用的时候注入我们的UserInfoChangedSource类就可以了,因为之前已经加了@Component.

二.在服务中添加消息消费者

与消息发布一样需要依赖spring-cloud-stream、spring-cloud-starter-stream-kafka或者rabbitMq。并在启动类绑定Sink组件。

@SpringCloudApplication

@EnableBinding(Sink.class)

public class OrderApplication{

    public static void main(String[] args) {

        SpringApplication.run(InterventionApplication.class, args);

    }

}

创建 Sink

负责具体处理消息的消费逻辑

import org.springframework.cloud.stream.annotation.EnableBinding;

import org.springframework.cloud.stream.annotation.StreamListener; 


public class UserInfoChangedSink {

    @Autowired

    private UserInfoRedis userInfoRedis;

 

    @StreamListener("input")
    public void handleChangedUserInfo(UserInfoChangedEvent userInfoChangedEvent) {

        
        if(userInfoChangedEvent.getOperation().equals("ADD")) {

            userInfoRedis.saveUser(userInfoChangedEvent.getUser());

        } else if(userInfoChangedEvent.getOperation().equals("UPDATE")) {

         userInfoRedis.updateUser(userInfoChangedEvent.getUser());            

        } else if(userInfoChangedEvent.getOperation().equals("DELETE")) {

         userInfoRedis.deleteUser(userInfoChangedEvent.getUser().getUserName());

        }

    }

}

新的注解@StreamListener,将该注解添加到方法上就可以接收处理流中的事件。上述代码中@StreamListener("input"),意味着流经input通道的消息都将会交由此方法来处理。UserInfoRedis 根据自己业务情况去集成操作Redis的组件。

配置 Binder

配置Binder的方式和消息发布者几乎一样,如果发布采用的是默认通道output,那么接收消息的只需要将配置改为input

spring:

  cloud:

    stream:

      bindings:

        input:

          destination:  userInfoChangedTopic

          content-type: application/json

      kafka:

        binder:

          zk-nodes: localhost

	      brokers: localhost

 

三.Spring Cloud Stream 高级主题

创建一个自定义的通道接口类

public interface MyChannel {
    String INPUT = "msg-input";
    String OUTPUT = "msg-output";
    @Output(OUTPUT)
    MessageChannel output();
    @Input(INPUT)
    SubscribableChannel input();
}

默认的 Sink 通道接口

public interface Sink {
    String INPUT = "input";

    @Input("input")
    SubscribableChannel input();
}

自定义类不仅有input还有output,因为消息的输出和输入需要配置。

创建消息自定义接收器

@EnableBinding(MyChannel.class)
public class MsgSink {
    private static final Logger logger = LoggerFactory.getLogger(MsgSink.class);

    @StreamListener(MyChannel.INPUT)
    public void receive(Object payload){
        logger.info("received:" + payload);
    }
}

在 application.properties 中配置自定义通道的 input 和 output 的 destination ,使其绑定在一起

spring.cloud.stream.bindings.msg-input.destination=payload-topic
spring.cloud.stream.bindings.msg-output.destination=payload-topic

都绑定到payload-topic队列中,注意上面的配置bindings后面的配置这里就是MyChannel 接口自定义的通道名称;注意到 @Input 和 @Output 注解使用通道名称作为参数,如果没有名称,会使用带注解的方法名字作为参数。可以看出MyChannel 可以自定义任意多个input和output 。只有名称被注解使用到的才会生效。

消费者分组

在集群部署的系统中,希望服务的不同实例属于竞争关系,不应该重复消费同一消息,一个消息只能被服务集群的某一个实例处理。要想达到这个效果只需要将相同的服务放在同一消费者组内即可。唯一要做的事情也是重构Binder配置.配置如下

spring:

  cloud:

    stream:

      bindings:

        msg-input:

          destination:  payloadTopic

          content-type: application/json

         group: orderGroup

      kafka:

        binder:

          zk-nodes: localhost

	      brokers: localhost

以上基于Kafka的配置信息中,“bindings”段中的通道名称使用了自定义的“msg-input”,并且在该配置项中设置了“group”为“orderGroup”。

消息分区

同时有多条同一个用户的数据,发送过来,我们需要根据用户统计,但是消息被分散到了不同的集群节点上了,这时我们就可以考虑消息分区了。
当生产者将消息数据发送给多个消费者实例时,保证同一消息数据始终是由同一个消费者实例接收和处理。

1.生产者配置

spring.application.name=stream-partition-sender
server.port=9060
#设置服务注册中心地址,指向另一个注册中心
eureka.client.serviceUrl.defaultZone=http://dpb:123456@eureka1:8761/eureka/,http://dpb:123456@eureka2:8761/eureka/

#rebbitmq 链接信息
spring.rabbitmq.host=192.168.88.150
spring.rabbitmq.port=5672
spring.rabbitmq.username=dpb
spring.rabbitmq.password=123
spring.rabbitmq.virtualHost=/

# 对应 MQ 是 exchange  outputProduct自定义的信息
spring.cloud.stream.bindings.outputProduct.destination=exchangeProduct

#通过该参数指定了分区键的表达式规则
spring.cloud.stream.bindings.outputProduct.producer.partitionKeyExpression=payload
#指定了消息分区的数量。 
spring.cloud.stream.bindings.outputProduct.producer.partitionCount=2

问题思考?分区建的表达式‘payload’ 是绑定的消息体计算出来的消费者实例分区index

2.消费者配置

实例A

spring.application.name=stream-partition-receiverA
server.port=9070
#设置服务注册中心地址,指向另一个注册中心
eureka.client.serviceUrl.defaultZone=http://dpb:123456@eureka1:8761/eureka/,http://dpb:123456@eureka2:8761/eureka/

#rebbitmq 链接信息
spring.rabbitmq.host=192.168.88.150
spring.rabbitmq.port=5672
spring.rabbitmq.username=dpb
spring.rabbitmq.password=123
spring.rabbitmq.virtualHost=/

# 对应 MQ 是 exchange  和消息发送者的 交换器是同一个
spring.cloud.stream.bindings.inputProduct.destination=exchangeProduct
# 具体分组 对应 MQ 是 队列名称 并且持久化队列  inputProduct 自定义
spring.cloud.stream.bindings.inputProduct.group=groupProduct999

#开启消费者分区功能
spring.cloud.stream.bindings.inputProduct.consumer.partitioned=true
#指定了当前消费者的总实例数量
spring.cloud.stream.instanceCount=2 
#设置当前实例的索引号,从 0 开始
spring.cloud.stream.instanceIndex=0

实例B

spring.application.name=stream-partition-receiverB
server.port=9071
#设置服务注册中心地址,指向另一个注册中心
eureka.client.serviceUrl.defaultZone=http://dpb:123456@eureka1:8761/eureka/,http://dpb:123456@eureka2:8761/eureka/

#rebbitmq 链接信息
spring.rabbitmq.host=192.168.88.150
spring.rabbitmq.port=5672
spring.rabbitmq.username=dpb
spring.rabbitmq.password=123
spring.rabbitmq.virtualHost=/

# 对应 MQ 是 exchange  和消息发送者的 交换器是同一个
spring.cloud.stream.bindings.inputProduct.destination=exchangeProduct
# 具体分组 对应 MQ 是 队列名称 并且持久化队列  inputProduct 自定义
spring.cloud.stream.bindings.inputProduct.group=groupProduct999

#开启消费者分区功能
spring.cloud.stream.bindings.inputProduct.consumer.partitioned=true
#指定了当前消费者的总实例数量
spring.cloud.stream.instanceCount=2 
#设置当前实例的索引号,从 1 开始
spring.cloud.stream.instanceIndex=1