09.SpringCloudStream

一.简介

消息中间件对于服务解耦、流量削峰、消息延迟都是非常常用的技术手段。各种消息中间件支持的协议、使用方式各有不同,对于整合多种消息中间件将非常繁琐。SpringCloudStream通过增加绑定器中间层,隔离消息中间件与应用程序的耦合,通过向应用程序暴露统一的Channel通道,使应用程序不需要考虑不同的消息中间件。

SpringCloudStream架构:
在这里插入图片描述

二.实现

1.新建模块stream-provider Stream生产者

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-cloud</artifactId>
        <groupId>com.vincent</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>stream-provider</artifactId>

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

2.stream-provider 配置

server:
  port: 8070

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: admin
    password: admin
  cloud:
    stream:
      bindings:
        output:
          destination: test-stream-exechange
          contentType: application/json

3.stream-provider 启动类

package com.stream;



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;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

@SpringBootApplication
@EnableBinding(Source.class)
@RestController
public class StreamProviderApp {
    public static void main(String[] args) {
        SpringApplication.run(StreamProviderApp.class,args);
    }


    @Resource
    private MessageChannel output;


    @GetMapping("/send")
    public void send() {
        Map<String,Object> data = new HashMap<>();
        data.put("timestamp",System.currentTimeMillis());
        this.output.send(MessageBuilder.withPayload(data).build());
    }
}

@EnableBinding(Source.class) 启用消息发送Channel,其Source 接口中定义了发送管道配置。且spring.cloud.stream.bindings.output 的Channel名称就是接口中定义的output名称。

/*
 * Copyright 2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.stream.messaging;

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

/**
 * Bindable interface with one output channel.
 *
 * @see org.springframework.cloud.stream.annotation.EnableBinding
 * @author Dave Syer
 * @author Marius Bogoevici
 */
public interface Source {

	String OUTPUT = "output";

	@Output(Source.OUTPUT)
	MessageChannel output();

}

启动启动类并登录RabbitMQ WEB控制台,将能看到test-stream-exechange 的Exchange。
在这里插入图片描述

4.新建stream-consumer消费端模块

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-cloud</artifactId>
        <groupId>com.vincent</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>stream-consumer</artifactId>

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.stream.StreamConsumerApp</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

5.stream-consumer消费端配置

server:
  port: 8080

spring:
  cloud:
    stream:
      bindings:
        input:
          destination: test-stream-exechange
          contentType: application/json
  rabbitmq:
    host: localhost
    port: 5672
    username: admin
    password: admin

6.stream-consumer 启动类

package com.stream;


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;
import org.springframework.messaging.Message;
import org.springframework.web.bind.annotation.RestController;




@SpringBootApplication
@EnableBinding(Sink.class)
@RestController
public class StreamConsumerApp {
    public static void main(String[] args) {
        SpringApplication.run(StreamConsumerApp.class,args);
    }

    @StreamListener("input")
    public void handleMessage(Message<String> message){
        System.out.println(message.getPayload());
    }
}

@EnableBinding(Sink.class) 表示启用消息输入端,其Sink接口定义的使消息输入端。

/*
 * Copyright 2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.stream.messaging;

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

/**
 * Bindable interface with one input channel.
 *
 * @see org.springframework.cloud.stream.annotation.EnableBinding
 * @author Dave Syer
 * @author Marius Bogoevici
 */
public interface Sink {

	String INPUT = "input";

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

}

启动2个stream-consumer 服务(另一个服务 java -jar target\stream-consumer-0.0.1-SNAPSHOT.jar --server.port=8081)并查看RabbitMQ WEB 控制台将有2个队列与Exchange 绑定。
在这里插入图片描述

7.通过stream-provider http://localhost:8070/send 产生消息,stream-consumer模块控制台都将产生数据输出。
在这里插入图片描述

三.自定义消息通道

如果不使用spring-cloud-starter-stream-rabbit 提供的默认Source / Sink 通道,可以使用自定义的方式,只需要注意定义的通道名称和依赖注入的名称,并参照Source / Sink 定义即可轻松自定义通道。并在配置启动类上使用@EnableBinding 开启通道配置即可。

如下是Source 接口定义源码,输出通道需要返回MessageChannel,且@Output() 注解参数名称是容器中Bean 名称,@Input 定义输入通道,且返回是SubscribableChannel。

/*
 * Copyright 2015-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.stream.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.beans.factory.annotation.Qualifier;

/**
 * Indicates that an output binding target will be created by the framework.
 *
 * @author Dave Syer
 * @author Marius Bogoevici
 * @author Artem Bilan
 */

@Qualifier
@Target({ ElementType.FIELD, ElementType.METHOD,
		ElementType.ANNOTATION_TYPE, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Output {

	/**
	 * Specify the binding target name;
	 * used as a bean name for binding target
	 * and as a destination name by default.
	 * @return the binding target name
	 */
	String value() default "";

}

四.分组与持久化

上面程序的stream-consumer有多个实例就会有多个RabbitMQ Queue,导致同一个服务对同一个消息会被多次消费,并且队列属于临时消息,消息可能会没被消费者消费而丢失。通过对服务消费者配置为同一个分组即可实现一个分组内的一个服务实例消费信息,且消息是持久化的。

1.配置stream-consumer 消息分组,即配置spring.cloud.stream.bindings.input.group

 server:
  port: 8080

spring:
  cloud:
    stream:
      bindings:
        input:
          destination: test-stream-exechange
          contentType: application/json
          group: test-group
  rabbitmq:
    host: localhost
    port: 5672
    username: admin
    password: admin

2.分别在8080 / 8081 端口启动stream-consumer ,并查看RabbitMQ Queue 信息只会出现一个队列。
在这里插入图片描述

3.访问http://localhost:8070/send 生产消息可以观察到只有其中一个stream-consumer 会消费一个消息。

4.当停止所有stream-consumer服务后,RabbitMQ 中的队列依然存在,不会被删除。
在这里插入图片描述
且当我们生产消息后消息会被持久化:
在这里插入图片描述
在启动stream-consumer服务后消息被消费后才会被删除:
在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值