springboot中使用stream

Spring Cloud Stream

Spring Cloud Stream 是一个用来为微服务应用构建消息驱动能力的框架。它可以基于Spring Boot 来创建独立的,可用于生产的Spring 应用程序。他通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动。Spring Cloud Stream 为一些供应商的消息中间件产品提供了个性化的自动化配置实现

Spring Cloud Stream相关概念简介

1、应用模型

应用程序通过 inputs 或者 outputs 来与 Spring Cloud Stream 中Binder 交互,通过我们配置来绑定,而 Spring Cloud Stream 的 Binder 负责与中间件交互。所以,我们只需要搞清楚如何与 Spring Cloud Stream 交互就可以方便使用消息驱动的方式。

 

  • Inputs 接收消息的通道
  • Output 发送消息的通道
  • Binder 可理解为一个抽象的中间件,应用通过在spring cloud stream中所注入的inputs,outputs通道来跟外界消息通信,而这些通道又是通过具体中间件的Binder实现来连接到消息队列的服务器上。有了Binder,甚至可以不改一行代码,就切换中间件的类型
  • Middleware 具体的消息中间件

3、发布/订阅

简单的讲就是一种生产者,消费者模式。发布者是生产,将输出发布到数据中心,订阅者是消费者,订阅自己感兴趣的数据。当有数据到达数据中心时,就把数据发送给对应的订阅者

 

4、消费组

直观的理解就是一群消费者一起处理消息。需要注意的是:每个发送到消费组的数据,仅由消费组中的一个消费者处理。

默认情况下,当生产者发出一条消息到绑定通道上,这条消息会产生多个副本被每个消费者实例接收和处理,这就很可能会出现重复消费的问题,在某些场景下,我们希望生产者产生的消息只被其中一个实例消费,这个时候我们需要为这些消费者设置消费组来实现这样的功能,实现的方式非常简单,我们只需要在服务消费者端设置spring.cloud.stream.bindings.{channel-name}.group属性即可。

通常情况下,当有一个应用绑定到目的地的时候,最好指定消费消费组。扩展Spring Cloud Stream应用程序时,必须为每个输入绑定指定一个使用者组。这样做可以防止应用程序的实例接收重复的消息,而且所有拥有订阅主题的消费组都是持久化的,除了匿名消费组(即不设置group)

 

5、分区

有的时候,我们可能需要相同特征的消息能够总是被发送到同一个消费者上去处理,在消费组中我们可以保证消息不会被重复消费,但是在同组下有多个实例的时候,我们无法确定每次处理消息的是不是被同一消费者消费,此时我们需要借助于消息分区,消息分区之后,具有相同特征的消息就可以总是被同一个消费者处理了

 

Spring Cloud Stream 示例

 

编写A项目代码

在A项目中定义一个输入通道一个输出通道,定义通道在接口中使用@Input和@Output注解定义,程序启动的时候Spring Cloud Stream会根据接口定义将实现类自动注入(Spring Cloud Stream自动实现该接口不需要写代码)。
A服务输入通道,通道名称ChatExchanges-A-Input,接口定义输入通道必须返回SubscribableChannel:

public interface ChatInput {

	String INPUT = "ChatExchanges-A-Input";

	@Input(ChatInput.INPUT)
	SubscribableChannel input();
}

A服务输出通道,通道名称ChatExchanges-A-Output,输出通道必须返回MessageChannel:

public interface ChatOutput {

	String OUTPUT = "ChatExchanges-A-Output";

	@Output(ChatOutput.OUTPUT)
	MessageChannel output();
}

定义消息实体类:

public class ChatMessage implements Serializable {

	private String name;
	private String message;
	private Date chatDate;

	//没有无参数的构造函数并行化会出错
	private ChatMessage(){}

	public ChatMessage(String name,String message,Date chatDate){
		this.name = name;
		this.message = message;
		this.chatDate = chatDate;
	}

	public String getName(){
		return this.name;
	}

	public String getMessage(){
		return this.message;
	}

	public Date getChatDate() { return this.chatDate; }

	public String ShowMessage(){
		return String.format("聊天消息:%s的时候,%s说%s。",this.chatDate,this.name,this.message);
	}
}

在业务处理类上用@EnableBinding注解绑定输入通道和输出通道,这个绑定动作其实就是创建并注册输入和输出通道的实现类到Bean中,所以可以直接是使用@Autowired进行注入使用,另外消息的串行化默认使用application/json格式(com.fastexml.jackson),最后用@StreamListener注解进行指定通道消息的监听:

//ChatInput.class的输入通道不在这里绑定,监听到数据会找不到AClient类的引用。
//Input和Output通道定义的名字不能一样,否则程序启动会抛异常。
@EnableBinding({ChatOutput.class,ChatInput.class})
public class AClient {

	private static Logger logger = LoggerFactory.getLogger(AClient.class);

	@Autowired
	private ChatOutput chatOutput;

	//StreamListener自带了Json转对象的能力,收到B的消息打印并回复B一个新的消息。
	@StreamListener(ChatInput.INPUT)
	public void PrintInput(ChatMessage message) {

		logger.info(message.ShowMessage());

		ChatMessage replyMessage = new ChatMessage("ClientA","A To B Message.", new Date());

		chatOutput.output().send(MessageBuilder.withPayload(replyMessage).build());
	}
}

到此A项目代码编写完成。

编写B项目代码

B项目使用Spring Integration实现消息的发布和消费,定义通道时我们要交换输入通道和输出通道的名称:

public interface ChatProcessor {

	String OUTPUT = "ChatExchanges-A-Input";
	String INPUT  = "ChatExchanges-A-Output";

	@Input(ChatProcessor.INPUT)
	SubscribableChannel input();

	@Output(ChatProcessor.OUTPUT)
	MessageChannel output();
}

消息实体类:

public class ChatMessage {
	private String name;
	private String message;
	private Date chatDate;

	//没有无参数的构造函数并行化会出错
	private ChatMessage(){}

	public ChatMessage(String name,String message,Date chatDate){
		this.name = name;
		this.message = message;
		this.chatDate = chatDate;
	}

	public String getName(){
		return this.name;
	}

	public String getMessage(){
		return this.message;
	}

	public Date getChatDate() { return this.chatDate; }

	public String ShowMessage(){
		return String.format("聊天消息:%s的时候,%s说%s。",this.chatDate,this.name,this.message);
	}
}

业务处理类用@ServiceActivator注解代替@StreamListener,用@InboundChannelAdapter注解发布消息:

@EnableBinding(ChatProcessor.class)
public class BClient {

	private static Logger logger = LoggerFactory.getLogger(BClient.class);

	//@ServiceActivator没有Json转对象的能力需要借助@Transformer注解
	@ServiceActivator(inputChannel=ChatProcessor.INPUT)
	public void PrintInput(ChatMessage message) {

		logger.info(message.ShowMessage());
	}

	@Transformer(inputChannel = ChatProcessor.INPUT,outputChannel = ChatProcessor.INPUT)
	public ChatMessage transform(String message) throws Exception{
		ObjectMapper objectMapper = new ObjectMapper();
		return objectMapper.readValue(message,ChatMessage.class);
	}

	//每秒发出一个消息给A
	@Bean
	@InboundChannelAdapter(value = ChatProcessor.OUTPUT,poller = @Poller(fixedDelay="1000"))
	public GenericMessage<ChatMessage> SendChatMessage(){
		ChatMessage message = new ChatMessage("ClientB","B To A Message.", new Date());
		GenericMessage<ChatMessage> gm = new GenericMessage<>(message);
		return gm;
	}
}

运行程序

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值