12-SpringCloud Stream

SpringCloud Stream

在实际的企业开发中,消息中间件是至关重要的组件之一。消息中间件主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构。不同的中间件其实现方式,内部结构是不一样的。如常见的RabbitMQ和Kafka,由于这两个消息中间件的架构上的不同,像RabbitMQ有exchange,kafka有Topic,partitions分区,这些中间件的差异性导致我们实际项目开发给我们造成了一定的困扰,我们如果用了两个消息队列的其中一种,后面的业务需求,我想往另外一种消息队列进行迁移,这时候无疑就是一个灾难性的,一大堆东西都要重新推倒重新做,因为它跟我们的系统耦合了,这时候 springcloud Stream 给我们提供了一种解耦合的方式。

概述

Spring Cloud Stream 是消息中间件组件,它集成了 kafka 和 rabbitmq

Spring Cloud Stream由一个中间件中立的核组成。应用通过Spring Cloud Stream插入的input(相当于消费者consumer,它是从队列中接收消息的)和output(相当于生产者producer,它是从队列中发送消息的。)通道与外界交流。通道通过指定中间件的Binder实现与外部代理连接。业务开发者不再关注具体消息中间件,只需关Binder对应用程序提供的抽象概念来使用消息中间件实现业务即可。

12-1

12-2

说明:最底层是消息服务,中间层是绑定层,绑定层和底层的消息服务进行绑定,顶层是消息生产者和消息消费者,顶层可以向绑定层生产消息和和获取消息消费

入门案例

  • 搭建一个生产者(basic_stream_producer工程),一个消费者(basic_stream_consumer工程)
  • 通过RabbitMQ作为消息中间件,完成SpringCloud Stream的案例
  • 先启动MQ

消息生产者

引入坐标

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

yml配置

server:
  port: 7001 #服务端口
spring:
  application:
    name: stream_producer #指定服务名
  rabbitmq:
    addresses: 127.0.0.1
    username: guest
    password: guest
  cloud:
    stream:
      bindings:
        output:
          destination: hwx-default  #指定消息发送的目的地,在rabbitmq中,发送到一个 hwx-default 的exchange交换机上
      binders:  #配置绑定器
        defaultRabbit:
          type: rabbit

配置启动类

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.MessageBuilder;


/**
 * 入门案例:
 *      1.引入依赖
 *      2.配置application.yml文件
 *      3.发送消息的话,定义一个通道接口,通过接口中内置的messagechannel
 *              springcloudstream中内置接口  Source
 *      4.@EnableBinding : 绑定对应通道
 *      5.发送消息的话,通过MessageChannel发送消息
 *           如果需要MessageChannel --> 通过绑定的内置接口获取
 */
@SpringBootApplication
@EnableBinding(Source.class)
public class ProducerBasicApplication implements CommandLineRunner {

	@Autowired
	private MessageChannel output;

	public static void main(String[] args) {
		SpringApplication.run(ProducerBasicApplication.class);
	}

	@Override
	public void run(String... args) throws Exception {
		//借助MessageBuilder工具类发送消息
		output.send(MessageBuilder.withPayload("你好 springcloud").build());
	}
}

消息消费者

引入坐标

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

yml配置

server:
  port: 7002 #服务端口
spring:
  application:
    name: rabbitmq-consumer #指定服务名
  rabbitmq:
    addresses: 127.0.0.1
    username: guest
    password: guest
  cloud:
    stream:
      bindings:
        input: #内置的获取消息的通道 , 从hwx-default中获取消息
          destination: hwx-default
      binders:
        defaultRabbit:
          type: rabbit

配置启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;

/**
 * 1.引入依赖
 * 2.配置application.yml
 * 3.需要配置一个通道的接口
 *    内置获取消息的通道接口 sink
 * 4.绑定通道
 * 5.配置一个监听方法 : 当程序从中间件获取数据之后,执行的业务逻辑方法
 *      需要在监听方法上配置@StreamListener
 */
@SpringBootApplication
@EnableBinding(Sink.class)
public class ConsumerBasicApplication {

	@StreamListener(Sink.INPUT)
	public void input(String msg){
		System.out.println("获取到的消息:"+msg);
	}
    
	public static void main(String[] args) {
		SpringApplication.run(ConsumerBasicApplication.class);
	}
    
}

测试

  • 启动消费者
  • 再启动生产者,启动时,观察消费者的控制台语句输出

自定义消息通道

自定义接口

消费者、生产者都要加入

import org.springframework.cloud.stream.annotation.Input;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;

/**
 * 自定义的消息通道
 **/
public interface MyProcessor {

	/**
	 * 消息生产者的配置
	 */
	String MYOUTPUT = "myoutput";

	@Output("myoutput")
	MessageChannel myoutput();

	/**
	 * 消息消费者的配置
	 */
	String MYINPUT = "myinput";

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

编写监听器配置

消费者中配置

@Component
@EnableBinding(MyProcessor.class)
public class MessageListener {

	//监听binding中的消息
	@StreamListener(MyProcessor.MYINPUT)
	public void input(String message) {
		System.out.println("获取到消息: " + message);
	}

}

编写发送消息配置

/**
 *负责向中间件发送数据
 **/
@Component
@EnableBinding(MyProcessor.class)
public class MessageSender {

	@Autowired
	@Qualifier(value = "myoutput")
	private MessageChannel myoutput;

	//发送消息
	public void send(Object obj) {
		myoutput.send(MessageBuilder.withPayload(obj).build());
	}
}

修改生产者yml

server:
  port: 7003 #服务端口
spring:
  application:
    name: stream_producer #指定服务名
  rabbitmq:
    addresses: 127.0.0.1
    username: guest
    password: guest
  cloud:
    stream:
      bindings:
        output:
          destination: hwx-default  #指定消息发送的目的地,在rabbitmq中,发送到一个 hwx-default 的exchange交换机上
        myoutput:
          destination: MQTest
      binders:  #配置绑定器
        defaultRabbit:
          type: rabbit

修改消费者yml

#====================
# 自定义消息通道配置
#====================
server:
  port: 7004 #服务端口
spring:
  application:
    name: rabbitmq-consumer #指定服务名
  rabbitmq:
    addresses: 127.0.0.1
    username: guest
    password: guest
  cloud:
    stream:
      bindings:
        input: #内置的获取消息的通道 , 从hwx-default中获取消息
          destination: hwx-default
        myinput:
          destination: MQTest
      binders:
        defaultRabbit:
          type: rabbit

测试

生产者工程中,编写测试类

import com.hwx.stream_producer.producer.MessageSender;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class SendMsgTest {

	@Autowired
	private MessageSender messageSender;

	@Test
	public void TestSend() {
		for (int i = 1; i <= 5; i++) {
			messageSender.send("自定义测试发送第" + i + "条");
		}
	}
}
  • 启动测试方法后,观察消费者的控制台输出

消息分组

概述

通常在生产环境,我们的每个服务都不会以单节点的方式运行在生产环境,当同一个服务启动多个实例的时候,这些实例都会绑定到同一个消息通道的目标主题(Topic)上。默认情况下,当生产者发出一条消息到绑定通道上,这条消息会产生多个副本被每个消费者实例接收和处理,但是有些业务场景之下,我们希望生产者产生的消息只被其中一个实例消费,这个时候我们需要为这些消费者设置消费组来实现这样的功能。

12-3

通俗理解:

一个消息正常情况下会被多个消费者消费,当需求是,多个消费者只有一个能够消费此条消息,就把多个消费者归纳到一个组里。在springcloud stream中一个组里的消费者只会有一个获取消费,并进行消息的使用

实现方式

  • 只需要在多个服务消费者端yml中设置 spring.cloud.stream.bindings.input.group 属性即可:
#====================
# 消息分组
# 一个组里的消费者只会有一个获取消费,并进行消息的使用
#====================
server:
  port: 7004 #服务端口 7004,7005
spring:
  application:
    name: rabbitmq-consumer #指定服务名
  rabbitmq:
    addresses: 127.0.0.1
    username: guest
    password: guest
  cloud:
    stream:
      bindings:
        input: #内置的获取消息的通道 , 从hwx-default中获取消息
          destination: hwx-default
        myinput:
          destination: MQTest
          group: group1 #设置消息的组名称(同名组中的多个消费者,只会有一个去消费消息)
      binders:
        defaultRabbit:
          type: rabbit
  • 启动多个消费者服务后,启动测试类,只有一个消费者控制台输出接收消息

消息分区

概述

有一些场景需要满足, 同一个特征的数据被同一个实例消费, 比如同一个id的传感器监测数据必须被同一个实例统计计算分析, 否则可能无法获取全部的数据。又比如部分异步任务,首次请求启动task,二次请求取消task,此场景就必须保证两次请求至同一实例。

12-4

生产者yml配置

server:
  port: 7001 #服务端口
spring:
  application:
    name: stream_producer #指定服务名
  rabbitmq:
    addresses: 127.0.0.1
    username: guest
    password: guest
  cloud:
    stream:
      bindings:
        output:
          destination: hwx-default  #指定消息发送的目的地,在rabbitmq中,发送到一个 hwx-default 的exchange交换机上
        myoutput:
          destination: MQTest
          producer:
            partition-key-expression: payload #分区关键字,可以是对象,对象id
            partition-count: 2  #分区大小
      binders:  #配置绑定器
        defaultRabbit:
          type: rabbit

yml中增加两个配置:

  1. spring.cloud.stream.bindings.output.producer.partitionKeyExpression :通过该参数指定了分区键的表达式规则,可以根据实际的输出消息规则来配置SpEL来生成合适的分区键
  2. spring.cloud.stream.bindings.output.producer.partitionCount :该参数指定了消息分区的数量。

多个消费者yml配置

server:
  port: 7002 #服务端口
spring:
  application:
    name: rabbitmq-consumer #指定服务名
  rabbitmq:
    addresses: 127.0.0.1
    username: guest
    password: guest
  cloud:
    stream:
      instanceCount: 2  #消费者总数
      instanceIndex: 1  #当前消费者的索引
      bindings:
        input: #内置的获取消息的通道 , 从hwx-default中获取消息
          destination: hwx-default
        myinput:
          destination: MQTest
          group: group1 #设置消息的组名称(同名组中的多个消费者,只会有一个去消费消息)
          consumer:
            partitioned: true #开启分区支持
      binders:
        defaultRabbit:
          type: rabbit

yml中增加三个配置:

  1. consumer: partitioned: true #开启分区支持
  2. stream: instanceCount: 2 #消费者总数
  3. stream: instanceIndex: 1 #当前消费者的索引

启动多个消费者服务,但需要注意的是要为消费者指定不同的实例索引号,这样当同一个消息被发给消费组时,我们可以发现只有一个消费实例在接收和处理这些相同的消息。

posted @ 2021-07-19 15:53  爱码士很优秀  阅读(94)  评论(0编辑  收藏  举报