SpringCloud-数据流教程-四-

136 阅读37分钟

SpringCloud 数据流教程(四)

原文:Spring Cloud Data Flow

协议:CC BY-NC-SA 4.0

七、Spring CloudStream 绑定器

在前一章中,我向您展示了 Spring Cloud Stream 应用启动器如何作为独立的应用工作,可以轻松交付企业级解决方案。我向您展示了 Spring Cloud Stream 模型应用,以及主内核如何基于 Spring Integration 和 Spring Boot 实现简单的配置。您看到了如何使用 Spring Cloud 函数和 Spring Integration 非常容易地创建流。我还向您展示了一个特性(在我看来是最好的),叫做 binder ,它允许您的解决方案使用任何物理目的地或任何中间件消息代理。本章介绍如何创建自定义活页夹。

您将使用 NATS 服务器( https://nats.io ),一个用于构建分布式应用的代理,以提供实时流和大数据用例(见图 7-1 )。想知道为什么我选择了 NATS 而不是其他技术?以前在 Cloud Foundry 工作( www。cloudfoundry。org )项目,保持一些虚拟机存活的主要组件之一是使用 NATS,这是一个快速可靠的消息代理,易于使用。作为实验,我决定创建一个原型作为 binder 实现。我花了几个小时。是的,创建自定义活页夹非常容易。

让我们从讨论 NATS 技术背后的东西以及如何实现它开始。

img/337978_1_En_7_Fig1_HTML.jpg

图 7-1。

https://nats.io

绑定器

绑定器使用服务提供者接口(SPI)模式,该模式允许您通过启用功能或替换组件来扩展或添加额外的功能。自从 Java 编程语言的第一次迭代以来,这种模式就一直存在,并增加了插件功能。

Spring Cloud Stream 公开了几个接口和抽象、实用类;它还提供了允许您插入外部中间件的发现策略。这些接口和类帮助您非常容易地创建绑定器。一个典型的场景是生产者和消费者使用绑定器来产生和消费消息。绑定器负责连接、重试、会话或任何允许发布者和消费者在知道如何完成的情况下使用代理的事情。它隐藏了样板代码,避免了学习特定 API 的需要。

我们先来回顾一下主界面:org.springframework.cloud.stream.binder.Binder<T,C,P>。该接口提供输入和输出绑定目标。它给生产者和消费者都增加了属性;这些属性以类型安全的方式为所需的特定于代理的属性(如果有的话)提供支持(参见清单 7-1 )。

public interface Binder<T, C extends ConsumerProperties, P extends ProducerProperties> {
    Binding<T> bindConsumer(
                                 String bindingName, String group, T inboundBindTarget, C consumerProperties);
    Binding<T> bindProducer(String bindingName, T outboundBindTarget, P producerProperties);
}

Listing 7-1.org.springframework.cloud.stream.binder.Binder Interface

让我们回顾一下清单 7-1 。

  • binderConsumer。这个方法的第一个参数是目的地名称,它在内部创建必要的通道和代理中需要的任何目的对象,比如队列、主题等等。下一个参数是消费者接受消息的组(工人风格或发布/订阅模式)。第三个参数是目的地/通道实例,消费者在其中监听/订阅新的传入消息。第四个参数是属于消息的代理(特定的)和业务属性。

  • binderProducer。这个方法的第一个参数是目的地名称,它创建必要的通道和代理中需要的任何目的对象,比如主题、交换等等。下一个参数是生产者发送消息的目的地/通道实例。最后一个参数是包含特定于代理的属性和业务属性的任何属性。

我认为这些签名很容易理解。图 7-2 显示了一个活页夹的例子。

img/337978_1_En_7_Fig2_HTML.jpg

图 7-2。

绑定器抽象

实现绑定器

如果你想实现一个绑定,你必须遵循这些简单的规则。

  • 一个类必须实现Binder接口。

  • 一个@Configuration标记的类定义了一个绑定er bean 和创建中间件代理基础设施的步骤;它可能是一个连接、会话、某些凭证等等。

  • 有必要在类路径中创建一个包含一个或多个绑定器定义的META-INF/spring.binders文件。

正如您所看到的,实现绑定器非常简单,所以让我们开始使用 NATS 代理创建一个定制的绑定器。

暗夜之狐

创建自定义绑定器有助于开发人员加速开发;作为 binder 开发人员,您需要了解这个代理是如何工作的。

在开始实现 binder 之前,我认为有必要创建一个允许您生成和消费消息的库,以便以后可以重用它。在本章的最后,您将创建一个包含三个模块的nats-binder项目:nats-messaging (NATS 客户端)nats-messaging-binder (NAT 绑定实现),以及nats-messaging-test (NATS 绑定测试)。

下载 NATS 服务器( https://nats.io/ )并安装。本章使用了一个 NATS 码头工人的形象。您可以使用以下命令提取它。

$ docker pull nats

此命令下载一个 10 MB 的图像。

项目:nats-binder

为了使开发更容易,让我们为主pom.xml文件及其模块创建一个目录结构。创建一个名为nats-binder的文件夹,并将pom.xml文件添加到清单 7-2 中。

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.apress.nats</groupId>
    <artifactId>nats-binder</artifactId>
    <version>0.0.1</version>
    <packaging>pom</packaging>

    <properties>
        <java.version>1.8</java.version>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <modules>
        <module>nats-messaging-binder</module>
        <module>nats-messaging</module>
        <module>nats-messaging-test</module>
    </modules>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-messaging</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.2.6.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Listing 7-2.nats-binder/pom.xml

分析pom.xml和依赖关系。

接下来,创建模块,因为您使用的是 Spring Initializr,所以您可以在nats-binder文件夹中解压缩 ZIP 文件。

NATS 客户端:NATs-消息

打开浏览器,指向 https://start.spring.io 。使用以下元数据。

  • 组:com.apress.nats

  • 神器:nats-messaging

  • 包装:com.apress.nats

  • 从属关系:龙目岛

点击生成按钮下载一个 ZIP 文件。将其解压缩到nats-binder目录,并导入到您喜欢的 IDE 中(参见图 7-3 )。

img/337978_1_En_7_Fig3_HTML.jpg

图 7-3。

spring Initializr NAT-messaging

接下来,让我们添加使用 NATS 服务器所需的依赖项。使用开源技术的好处之一是它对社区开放。这个案例需要 NAT Java 客户端(https://nats.io/download/nats-io/nats.java/??github.com/nats-io/nats.java)。

打开pom.xml并用清单 7-3 中的内容替换。

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
                <groupId>com.apress.nats</groupId>
                <artifactId>nats-binder</artifactId>
                <version>0.0.1</version>
                <relativePath>..</relativePath>
        </parent>

        <packaging>jar</packaging>

        <groupId>com.apress.nats</groupId>
        <artifactId>nats-messaging</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>nats-messaging</name>
        <description>Demo project for Spring Boot</description>

        <dependencies>
                <dependency>
                        <groupId>io.nats</groupId>
                        <artifactId>jnats</artifactId>
                        <version>2.6.6</version>
                </dependency>

                <dependency>
                        <groupId>com.fasterxml.jackson.core</groupId>
                        <artifactId>jackson-databind</artifactId>
                </dependency>
        </dependencies>

</project>

Listing 7-3.nats-binder/nats-messaging/pom.xml

看一下pom.xml,注意父项目正在声明nats-binder主项目。记住,nats-messaging库是一个模块。复习一下,我们继续。

在撰写本文时,Java NATS 客户端版本是 2.6.6。让我们从创建NatsProperties类开始。这个类保存了关于服务器、端口等等的所有信息(参见清单 7-4 )。

package com.apress.nats;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Data
@ConfigurationProperties("spring.nats")
public class NatsProperties {

    private String host = "localhost";
    private Integer port = 4222;
}

Listing 7-4.src/main/java/com/apress/nats/NatsProperties.java

清单 7-4 显示的是NatsProperties类;如您所见,它非常简单,并且有默认值。请记住,您可以在application.properties/yml文件、命令行或环境变量等中覆盖这些属性。

接下来,创建NatsConnection类(参见清单 7-5 )。

package com.apress.nats;

import io.nats.client.Connection;
import io.nats.client.Nats;
import lombok.Data;

import java.io.IOException;

@Data
public class NatsConnection {
    private Connection connection;
    private NatsProperties natsProperties;
    private NatsConnection(){}

    public NatsConnection(NatsProperties natsProperties) throws IOException, InterruptedException {
        this.natsProperties = natsProperties;
        this.connection =
Nats.connect("nats://" + natsProperties.getHost() + ":" + natsProperties.getPort().toString());
    }

}

Listing 17-5.src/main/java/com/apress/nats/NatsConnection.java

清单 7-5 显示了NatsConnection类。这个类有 NATS Connection实例。在这里,当使用spring.nats.*属性时,您调用NatProperties来使用默认值或开发人员提供的值。如您所见,Nats类是静态的。您可以调用connect方法,传递 schemed ( nats://)、主机和端口,这是一种连接 NATS 服务器的非常简单的方法。

接下来,让我们创建NatsTemplate类(参见清单 7-6 )。

package com.apress.nats;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.log4j.Log4j2;
import org.springframework.messaging.Message;
import org.springframework.util.SerializationUtils;

import java.nio.charset.StandardCharsets;

@Log4j2
@AllArgsConstructor
@Data
public class NatsTemplate {

    private NatsConnection natsConnection;

    public void send(String subject, String message){
        assert this.natsConnection != null && subject != null && !subject.isEmpty() && message != null && !message.isEmpty();
        log.debug("Sending: {}", message);
        this.natsConnection.getConnection().publish(subject, message.getBytes(StandardCharsets.UTF_8));
    }

    public void send(String subject,Message<?> message){
        assert this.natsConnection != null && subject != null && !subject.isEmpty() && message != null;
        log.debug("Sending: {}", message);
        this.natsConnection.getConnection().publish(subject, SerializationUtils.serialize(message));
    }

}

Listing 7-6.src/main/java/com/apress/nats/NatsTemplate.java

清单 7-6 显示了NatsTemplate类。这个类删除了所有的样板文件,并提供了处理 NATS 服务器的所有操作。这是模板设计模式的一个实现;如果您正在使用 Spring 框架,您可以找到其中的几个,包括JmsTemplateRabbitTemplateKafkaTemplateJdbcTemplate

您只声明了两个重载方法,其中您总是接收主题(类似于主题)和消息。您正在使用org.springframework.messaging.Message界面。还要注意,您需要NatsConnection实例。为了发布消息,您使用连接(通过getConnection()方法调用)并调用publish方法。在send(String subject, Message<?> message)方法中,您使用一个 Spring 序列化实用程序将您的消息序列化为一个字节数组。NATS 协议要求消息是byte[]类型。

接下来,让我们创建NatMessageListener接口(参见清单 7-7 )。

package com.apress.nats;

public interface NatsMessageListener  {
    void onMessage(byte[] message);
}

Listing 7-7.src/main/java/com/apress/nats/NatsMessageListener.java

清单 7-7 显示了NatsMessageListener接口,它有一个带byte[]类型作为参数的onMessage方法。

接下来,让我们创建至少一个实现来委托侦听器。该类在 NATS 服务器上订阅主题(同一个主题)。

创建NatsMessageListenerAdapter类(参见清单 7-8 )。

package com.apress.nats;

import io.nats.client.Dispatcher;
import io.nats.client.Subscription;
import lombok.Data;
import lombok.extern.log4j.Log4j2;

@Log4j2
@Data
public class NatsMessageListenerAdapter {

    private NatsConnection natsConnection;
    private String subject;
    private NatsMessageListener adapter;
    private Subscription subscription;
    private Dispatcher dispatcher;

    public void start(){
        assert natsConnection != null && natsConnection.getConnection() != null && subject != null && adapter != null;
        log.debug("Creating Message Listener...");
        dispatcher = this.natsConnection.getConnection().createDispatcher((msg) -> {});
        subscription = dispatcher.subscribe(this.subject, (msg) -> {
            adapter.onMessage(msg.getData());
        });
        log.debug("Subscribed to: {}",this.subject);
    }

    public void stop(){
        assert dispatcher != null && subject != null;
        log.debug("Unsubscribing from: {}", subject);
        dispatcher.unsubscribe(subject,300);
    }
}

Listing 7-8.src/main/java/com/apress/nats/NatsMessageListenerAdapter.java

清单 7-8 展示了实现NatsMessageListenerNatMessageListenerAdapter类。在继续之前分析这个类。在 Java NATS 客户端中,有两种获取消息的方式:同步和异步。您正在实现异步方式。要使用它,您需要创建一个Dispatcher实例(基于连接)并订阅主题(与主题相同)。当您需要删除订阅时,您只需要从Dispatcher实例中调用unsubscribe方法。

Note

代码使用 Lombok 中的@Log4j2注释来注入日志。通常情况下,你不会这样使用它。您需要使用 AOP 来创建您的横切关注点。

现在您已经有了生产者(NatsTemplate)和消费者(NatsMessageListener),让我们来创建配置。创建NatsConfiguration类(参见清单 7-9 )。

package com.apress.nats;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;

@EnableConfigurationProperties(NatsProperties.class)
@Configuration
public class NatsConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public NatsConnection natsConnection(NatsProperties natsProperties) throws IOException, InterruptedException {
        return new NatsConnection(natsProperties);
    }

    @Bean
    @ConditionalOnMissingBean
    public NatsTemplate natsTemplate(NatsConnection natsConnection){
        return new NatsTemplate(natsConnection);
    }

}

Listing 7-9.src/main/java/com/apress/nats/NatsConfiguration.java

清单 7-9 显示了NatsConfiguration类,它创建了NatsConnectionNatsTemplateSpring bean。请注意,您使用的是@ConditionalOnMissingBean,当另一个使用该库的类创建自己的具有不同实现或值的 bean 时,这很有用,因此您可以避免拥有几个相同类型的 bean。

就这样。这是连接、产生和使用消息的 nats 消息库。现在,您可以用清单 7-10 中的代码测试它。您可以创建NatsProducerConsumer类,也可以将这段代码添加到NatsMessagingApplicationTest类中。

package com.apress.nats;

import lombok.extern.log4j.Log4j2;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.nio.charset.StandardCharsets;

@Log4j2
@Configuration
public class NatsProducerConsumer {

    @Bean(initMethod = "start",destroyMethod = "stop")
    public NatsMessageListenerAdapter natsMessageListenerAdapter(NatsConnection natsConnection){
        NatsMessageListenerAdapter adapter = new NatsMessageListenerAdapter();
        adapter.setNatsConnection(natsConnection);
        adapter.setSubject("test");
        adapter.setAdapter( message -> {
            log.info("Received: {}", new String(message, StandardCharsets.UTF_8));
        });
        return adapter;
    }
    @Bean
    public ApplicationRunner sendMessage(NatsTemplate natsTemplate){
        return args -> {
            natsTemplate.send("test","Hello There!");
        };
    }

}

Listing 7-10.src/main/java/com/apress/nats/NatsProducerConsumer.java

要运行此应用,您需要启动并运行 NATS 服务器。可以用下面的命令运行它(我用的是 Docker)。

$ docker run -d --rm --name nats -p 4222:4222 nats

现在,您可以在 IDE 中或通过使用以下命令行来执行该应用。

$ ./mvnw spring-boot:run

您应该在日志中看到以下内容。

                NatsTemplate     : Sending: Hello There!
NatsProducerConsumer     : Received: Hello There!

恭喜你!您已经创建了用于下一个模块的nats-messaging库。现在,您可以使用以下命令停止您的 NATS 服务器。

$ docker stop nats

停止你的应用。

Warning

在继续之前,注释掉所有NatProducerConsumer.java类的代码。

NATS 绑定器实现:nats-messaging-binder

让我们从 binder 实现开始。打开浏览器,指向 https://start.spring.io 。使用以下元数据。

  • 组:com.apress.nats

  • 神器:nats-messaging-binder

  • 包装:com.apress.nats

  • 从属关系:龙目岛

点击生成按钮下载一个 ZIP 文件。将其解压缩到nats-binder目录中,并导入到您喜欢的 IDE 中(参见图 7-4 )。

img/337978_1_En_7_Fig4_HTML.jpg

图 7-4。

spring Initializr NATs-messaging-binder

让我们首先打开pom.xml文件,用清单 7-11 中的内容替换它。

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
                <groupId>com.apress.nats</groupId>
                <artifactId>nats-binder</artifactId>
                <version>0.0.1</version>
                <relativePath>..</relativePath>
        </parent>

        <packaging>jar</packaging>

        <groupId>com.apress.nats</groupId>
        <artifactId>nats-messaging-binder</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>nats-messaging-binder</name>
        <description>Demo project for Spring Boot</description>

        <properties>
                <spring-cloud.version>Hoxton.SR3</spring-cloud.version>
        </properties>

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

                <dependency>
                        <groupId>com.apress.nats</groupId>
                        <artifactId>nats-messaging</artifactId>
                        <version>0.0.1-SNAPSHOT</version>
                </dependency>

                <dependency>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                </dependency>
                <dependency>
                        <groupId>org.springframework.cloud</groupId>
                        <artifactId>spring-cloud-stream-test-support</artifactId>
                        <scope>test</scope>
                </dependency>

        </dependencies>

        <dependencyManagement>
                <dependencies>
                        <dependency>
                                <groupId>org.springframework.cloud</groupId>
                                <artifactId>spring-cloud-dependencies</artifactId>
                                <version>${spring-cloud.version}</version>
                                <type>pom</type>
                                <scope>import</scope>
                        </dependency>
                </dependencies>
        </dependencyManagement>

</project>

Listing 7-11.nats-binder/nats-messaging-binder/pom.xml

清单 7-11 显示了nats-messaging-binder模块的pom.xml文件。注意,您将nats-messaging模块声明为一个依赖项。

接下来,让我们按照步骤创建一个新的活页夹

实现绑定器接口

如果你回顾一下Binder接口,你会发现在你实现它之前你需要几个类(见清单 7-1 )。您需要传递的参数之一是分别用于消费者和生产者方法的入站出站绑定目标。您可以为此创建所有的逻辑,并遵循创建不同类型的通道、消息传递支持、消息转换器等的实践,但是这需要太长的时间。如果您依赖于一些抽象实现,而这些实现已经去掉了需要通过通道等完成的底层基础设施,那该怎么办呢?

您可以使用org.springframework.cloud.stream.binder.AbstractMessageChannelBinder类,它扩展了实现org.springframework.cloud.stream.binder.Binder接口的org.springframework.cloud.stream.binder.AbstractBinder类。AbstractMessageChannelBinder类提供了为通道、连接、重试逻辑、目的地创建等创建基础设施所需的所有逻辑。这是要扩展的主要类。如果你查看它的签名,你会看到清单 7-12 中的代码。

public abstract class AbstractMessageChannelBinder<C extends ConsumerProperties, P extends ProducerProperties, PP extends ProvisioningProvider<C, P>>
                extends AbstractBinder<MessageChannel, C, P> implements
                PollableConsumerBinder<MessageHandler, C>, ApplicationEventPublisherAware
{
        // ...
}

Listing 7-12.org.springframeworl.cloud.stream.binder.AbstractMessageChannelBinder.java

清单 7-12 是AbstractMessageChannelBinder类的一个片段,它需要ConsumerPropertiesProducerProperties,ProvisioningProvider类。让我们从创建ProvisionProvider实现开始。创建NatsMessageBinderProvisioningProvider类(参见清单 7-13 )。

package com.apress.nats;

import org.springframework.cloud.stream.binder.ConsumerProperties;
import org.springframework.cloud.stream.binder.ProducerProperties;
import org.springframework.cloud.stream.provisioning.ConsumerDestination;
import org.springframework.cloud.stream.provisioning.ProducerDestination;
import org.springframework.cloud.stream.provisioning.ProvisioningException;
import org.springframework.cloud.stream.provisioning.ProvisioningProvider;

public class NatsMessageBinderProvisioningProvider implements ProvisioningProvider<ConsumerProperties, ProducerProperties> {

    @Override
    public ProducerDestination provisionProducerDestination(String name, ProducerProperties properties) throws ProvisioningException {
        return new NatsMessageBinderDestination(name);
    }

    @Override
    public ConsumerDestination provisionConsumerDestination(String name, String group, ConsumerProperties properties) throws ProvisioningException {
        return new NatsMessageBinderDestination(name);
    }
}

Listing 7-13.src/main/java/com/apress/nats/NatsMessageBinderProvisioningProvider.java

清单 7-13 显示了用ConsumerPropertiesProducerProperties具体类作为参数实现ProvisioningProviderNatsMessageBinderProvisioningProvider类。这些类帮助所有的spring.cloud.stream.bindings.[destinationName].[consumer|producer]属性。注意,在实现中,您正在发送一个新的NatsMessageBinderDestination类实例。所以,让我们创建它(见清单 7-14 )。

package com.apress.nats;

import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.cloud.stream.provisioning.ConsumerDestination;
import org.springframework.cloud.stream.provisioning.ProducerDestination;

@AllArgsConstructor
@Data
public class NatsMessageBinderDestination implements ProducerDestination, ConsumerDestination {

    private final String destination;

    @Override
    public String getName() {
        return this.destination.trim();
    }

    @Override
    public String getNameForPartition(int partition) {
        throw new UnsupportedOperationException("Partition not yet implemented for Nats Binder");
    }
}

Listing 7-14.src/main/java/com/apress/nats/NatsMessageBinderDestination.java

清单 7-14 显示了实现了ProducerDestinationConsumerDestination接口的NatsMessageBinderDestinationProducerDestination接口声明getName()getNameForPartitionConsumerDestination接口声明getName()。这为底层通道和集成基础设施创建了目的地和所有连线。请注意,您现在没有实现分区特性。

现在您已经有了ProvisioningProvider接口实现,您必须通过创建消费者端点和侦听器来消费来自 NATS 服务器的传入消息。这意味着你在AbstractMessageChannelBinder中覆盖了createConsumerEndpoint方法,这个方法需要返回MessageProducer。让我们使用一个实现所有必要逻辑并覆盖所需方法的类。其中一个类是MessageProducerSupport,它是生产者端点的支持类,用于创建输出通道;它有发送消息的方法。因此,让我们创建NatsMessageBinderProducer类(参见清单 7-15 )。

package com.apress.nats;

import lombok.extern.log4j.Log4j2;
import org.springframework.cloud.stream.provisioning.ConsumerDestination;
import org.springframework.integration.endpoint.MessageProducerSupport;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.SerializationUtils;

import java.nio.charset.StandardCharsets;

@Log4j2
public class NatsMessageBinderProducer extends MessageProducerSupport {

    private ConsumerDestination destination;
    private NatsMessageListenerAdapter adapter = new NatsMessageListenerAdapter();

    public NatsMessageBinderProducer(ConsumerDestination destination, NatsConnection natsConnection){
        assert destination != null && natsConnection != null;
        adapter.setSubject(destination.getName());
        adapter.setNatsConnection(natsConnection);
        adapter.setAdapter(messageListener);
    }

    @Override
    protected void doStart() {
        adapter.start();
    }

    @Override
    protected void doStop() {
        adapter.stop();
        super.doStop();
    }

    private NatsMessageListener messageListener = message -> {
        log.debug("[BINDER] Message received from NATS: {}",message);
        log.debug("[BINDER] Message Type received from NATS: {}",message.getClass().getName());
        this.sendMessage((Message<?>)SerializationUtils.deserialize(message));
    };
}

Listing 7-15.src/main/java/com/apress/nats/NatsMessageBinderProducer.java

清单 7-15 显示了扩展MessageProducerSupport类的NatsMessageBinderProducer类。您唯一可以覆盖的方法是doStart()doStop()。让类业务逻辑处理剩下的事情。在本课中,您需要设置连接到 NATS 服务器的侦听器。看看需要一个NatsConnection实例的构造函数。当底层引导调用doStart()方法时,您开始监听。当收到消息时,使用sendMessage方法将其反序列化为Messager<?>类型,这是一个包含头和有效负载的包装类。

现在是时候扩展AbstractMessageChannelBinder类了(绑定er实现)。创建NatsMessageBinder类(参见清单 7-16 )。

package com.apress.nats;

import lombok.extern.log4j.Log4j2;
import org.springframework.cloud.stream.binder.AbstractMessageChannelBinder;
import org.springframework.cloud.stream.binder.ConsumerProperties;
import org.springframework.cloud.stream.binder.ProducerProperties;
import org.springframework.cloud.stream.provisioning.ConsumerDestination;
import org.springframework.cloud.stream.provisioning.ProducerDestination;
import org.springframework.integration.core.MessageProducer;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;

@Log4j2
public class NatsMessageBinder extends AbstractMessageChannelBinder<ConsumerProperties, ProducerProperties,NatsMessageBinderProvisioningProvider> {

    private NatsTemplate natsTemplate;

    public NatsMessageBinder(String[] headersToEmbed, NatsMessageBinderProvisioningProvider provisioningProvider, NatsTemplate natsTemplate) {
        super(headersToEmbed, provisioningProvider);
        this.natsTemplate = natsTemplate;
    }

    @Override
    protected MessageHandler createProducerMessageHandler(ProducerDestination destination, ProducerProperties producerProperties, MessageChannel errorChannel) throws Exception {
        return message -> {
            assert natsTemplate != null;
            log.debug("[BINDER] Sending to NATS: {}",message);
            natsTemplate.send(destination.getName(),message);
        };
    }

    @Override
    protected MessageProducer createConsumerEndpoint(ConsumerDestination destination, String group, ConsumerProperties properties) throws Exception {
        assert natsTemplate != null;
        return new NatsMessageBinderProducer(destination, this.natsTemplate.getNatsConnection());
    }
}

Listing 7-16.src/main/java/com/apress/nats/NatsMessageBinder.java

清单 7-16 显示了NatsMessageBinder类,我们的主要绑定器实现。看一下构造函数,其中需要调用基类(AbstractMessageChannelBinder)传递消息头(例如,自定义消息头或与代理相关的消息头)、ProvisioningProvider ( NatsMessageBinderProvisioningProvider类)和发送消息的NatsTemplate

我们覆盖了createProducerMessageHandler,它返回MessageHandler.它有消息要发送到 NATS 服务器。这就是为什么使用NatsTemplate实例来获取目的地名称和消息。另外,我们覆盖了createConsumerEndPoint,它返回了NatMessageBinderProducer类的一个实例。记住,这个类是从接收消息的监听器开始的。

创建@配置 Beans

现在我们已经有了绑定器实现,是时候创建配置和执行绑定器自动配置的 Spring beans 了。创建NatsMessageBinderConfiguration类(参见清单 7-17 )。

package com.apress.nats;

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@EnableConfigurationProperties(NatsProperties.class)
@Import(NatsConfiguration.class)
@Configuration
public class NatsMessageBinderConfiguration {

    @Bean
    public NatsMessageBinderProvisioningProvider natsMessageBinderProvisioningProvider(){
        return new NatsMessageBinderProvisioningProvider();
    }

    @Bean
    public NatsMessageBinder natsMessageBinder(NatsMessageBinderProvisioningProvider natsMessageBinderProvisioningProvider, NatsTemplate natsTemplate){
        return new NatsMessageBinder(null,natsMessageBinderProvisioningProvider, natsTemplate);
    }
}

Listing 7-17.src/main/java/com/apress/nats/NatsMessageBinderConfiguration.java

清单 7-17 显示了NatsMessageBinderConfiguration类。这个类正在导入包含NatsTemplateNatsConnectionNatsConfiguration类(参见清单 7-9 )。这里我们定义了 bind ernatsMessageBinderProvisioningProvider,natsMessageBinder,Spring beans 需要它们来连接 bind 的所有东西。在natsMessageBinder方法中,我们返回一个有几个参数的NatsMessageBinder类的新实例。现在,您将把null传递给头部。你可以以后再处理他们。

创建 META-INF/spring.binders

接下来,您需要将配置添加到spring.binders文件中。在src/main/resources路径下创建META-INF文件夹,用清单 7-18 中的内容创建spring.binders文件。

nats:\
com.apress.nats.NatsMessageBinderConfiguration

Listing 7-18.src/main/resources/META-INF/spring.binders

清单 7-18 显示了spring.binders文件,这是自动配置工作所必需的。这意味着如果你添加这个模块作为一个依赖项,它将使用spring.binders来查找每个带有@Configuration注释类的类,并执行自动配置逻辑来设置绑定器或任何其他配置。

注意,您将这个绑定器命名为nats,这在一个流中使用多个绑定器时非常重要,这将在后面的小节中讨论。

NATS 绑定器试验

现在您已经有了nats-messagingnats-messaging-binder模块,是时候测试它了。当然,有专门的测试类,但是我想向您展示使用这个活页夹并为以后保存单元/集成测试是多么容易。

打开浏览器,指向 https://start.spring.io 。使用以下元数据。

  • 组:com.apress.nats

  • 神器:nats-messaging-test

  • 包装:com.apress.nats

点击生成按钮下载一个 ZIP 文件。将其解压缩到nats-binder目录,并导入到您喜欢的 IDE 中(参见图 7-5 )。

img/337978_1_En_7_Fig5_HTML.jpg

图 7-5

spring Initializr NATs-消息传递-测试

打开 pom.xml 并用清单 7-19 中的内容替换它。

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
                <groupId>com.apress.nats</groupId>
                <artifactId>nats-binder</artifactId>
                <version>0.0.1</version>
                <relativePath>..</relativePath><!-- lookup parent from repository -->
        </parent>

        <groupId>com.apress.nats</groupId>
        <artifactId>nats-messaging-test</artifactId>
        <version>0.0.1-SNAPSHOT</version>

        <name>nats-messaging-test</name>
        <description>Demo project for Spring Boot</description>

        <dependencies>
                <dependency>
                        <groupId>com.apress.nats</groupId>
                        <artifactId>nats-messaging-binder</artifactId>
                        <version>0.0.1-SNAPSHOT</version>
                </dependency>
        </dependencies>

        <build>
                <plugins>
                        <plugin>
                                <groupId>org.springframework.boot</groupId>
                                <artifactId>spring-boot-maven-plugin</artifactId>
                        </plugin>
                </plugins>
        </build>
</project>

Listing 7-19.nats-messaging-test/pom.xml

列表 7-19 显示pom.xml。注意,您只使用了nats-messaging-binder而没有其他依赖项,因为nats-messaging-binder提供了您需要的一切,包括nats-messaging模块。

接下来,让我们创建发送和接收消息的流。创建NatsStream类(参见清单 7-20 )。

package com.apress.nats;

import lombok.extern.log4j.Log4j2;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.context.annotation.Bean;
import org.springframework.integration.annotation.InboundChannelAdapter;
import org.springframework.integration.annotation.Poller;
import org.springframework.integration.core.MessageSource;
import org.springframework.messaging.support.GenericMessage;

@Log4j2
@EnableBinding({Source.class, Sink.class})
public class NatsStream {

    @Bean
    @InboundChannelAdapter(value = Source.OUTPUT, poller = @Poller(fixedRate = "10000", maxMessagesPerPoll = "1"))
    public MessageSource<String> timerMessageSource() {
        return () -> new GenericMessage<>("Hello Spring Cloud Stream");
    }

    @StreamListener(Sink.INPUT)
    public void process(Object message){
        log.info("Received and ProcessedClass: {}", message.getClass().getName());
        log.info("Received and Processed: {}", message);
    }
}

Listing 7-20.src/main/java/com/apress/nats/NatsStream.java

清单 7-20 显示了NatsStream类。看一下@EnableBinding声明。我们使用 2.x 编程风格,带有SourceSink接口。要生成消息,您可以使用 Spring Integration @InboundChannelAdapter注释适配器。这个适配器的底层实现包含一个可轮询的逻辑,它基于来自@Poller注释的fixedRated参数每 10 秒调用并执行一次该方法。请注意,此适配器使用输出通道发送消息;在这种情况下,一个GenericMessage<>类型(一个字符串)。如果您感到好奇,这与用于时间源应用启动器的逻辑相同。它使用@InboundChannelAdapter每隔 T 秒发送一条消息。

@StreamListener注释标记了一个从输入通道接收所有输入消息的方法。

接下来,让我们帮助活页夹命名频道/目的地并连接它们。如果不这样做,输入和输出通道/目的位置会被创建,但不会被连接。

打开application.properties并使用清单 7-21 中的内容。

# Nats Bindings
spring.cloud.stream.bindings.output.destination=movie
spring.cloud.stream.bindings.input.destination=movie

# Debugging
logging.level.org.springframework.cloud.stream.messaging=DEBUG
logging.level.com.apress.nats=DEBUG

Listing 7-21.src/main/resources/application.properties

列表 7-21 显示application.properties。请注意,我们使用的是 2.x 编程模型,命名约定基于输入/输出通道/目的地。所以,输入和输出通道必须有相同的名称来产生(源)和消耗(接收器);在这种情况下,您调用的是通道/目的地movie。请注意,您使用调试日志记录级别来了解正在发生的事情。

在运行测试之前,确保您已经启动并运行了 Docker NATS 服务器映像容器。如果它没有运行,您可以使用以下命令运行它。

$ docker run -d --rm --name nats -p 4222:4222 nats

现在,您可以从您的 IDE 中运行它。如果您的 IDE 是智能的,它已经知道了配置。但是如果想从命令行运行,需要在根项目中添加以下文件(nats-binder)。

$ cp -r nats-messaging/.mvn .
$ cp nats-messaging/mvnw* .

复制编译、安装和执行测试的 Maven 包装器。接下来,在项目(nats-binder)中执行以下内容。

$ ./mvnw clean compile install
$ ./mvnw spring-boot:run -pl nats-messaging-test

如果一切顺利,您应该每 10 秒得到以下输出。

Received and Processed: Hello Spring Cloud Stream

还要查看日志的开头,那里有来自其他类的调试信息。您应该看到创建消息监听器和订阅:电影消息。

恭喜你!您已经创建了一个 NATS 服务器活页夹。现在你可以在任何你需要使用 NATS 的地方使用它,而不用担心任何 API。

不要忘记停止你的 NAT 服务器。

Notes

你可以在本书的配套代码的ch07/nats-binder文件夹中找到所有的源代码。

多重绑定器

制作 NATS 活页夹很有趣,对吧?现在,让我们来看看当您需要多个活页夹时,如何解决一个特定的需求。到目前为止,您要么使用 Rabbit,要么使用 Kafka,但不是一起使用,或者可能与多个 Rabbit 代理一起使用,或者一个 Rabbit 作为源,一个 Kafka 作为处理器。

在本节中,您将学习如何使用多个活页夹,尤其是 NATS 活页夹和 RabbitMQ 活页夹。您创建了三个独立的项目:movie-file-source-nats,它向 NATS 服务器公开 JSON 电影消息,movie-filter-processor-nats-rabbit,它监听 NATS 并向 Rabbit 发送消息,最后,movie-log-sink-rabbit,它记录来自 RabbitMQ 的消息(参见图 7-6 )。

img/337978_1_En_7_Fig6_HTML.jpg

图 7-6。

多重绑定器

电影-文件-源-国家

这个流从一个文件中读取所有的 JSON 电影,并使用 NATS 绑定器将它们发送到下一个流。打开浏览器,指向 https://start.spring.io 。使用以下元数据。

  • 组:com.apress.cloud.stream

  • 神器:movie-file-source-nats

  • 包装:com.apress.cloud.stream.movie

  • 从属关系:龙目岛

点击生成按钮下载一个 ZIP 文件。将其解压缩并导入到您最喜欢的 IDE 中。记下包装名称(参见图 7-7 )。

img/337978_1_En_7_Fig7_HTML.jpg

图 7-7。

Spring Initializr 电影-文件-源-国家

打开您的pom.xml文件并添加以下两个依赖项。

<!-- NATS -->
<dependency>
        <groupId>com.apress.nats</groupId>
        <artifactId>nats-messaging-binder</artifactId>
        <version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- Spring Integration -->
<dependency>
        <groupId>org.springframework.integration</groupId>
        <artifactId>spring-integration-file</artifactId>
</dependency>

接下来,创建Movie类(参见清单 7-22 )。

package com.apress.cloud.stream.movie;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor
@Data
public class Movie {
    private String title;
    private String actor;
    private int year;
    private String genre;
}

Listing 7-22.src/main/java/com/apress/cloud/stream/movie/Movie.java

如您所见,Movie类与前几章中的一样。接下来,创建MovieStreamProperties类。这个类保存了关于目录(JSON 电影所在的位置)和名称模式的信息(参见清单 7-23 )。

package com.apress.cloud.stream.movie;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Data
@ConfigurationProperties(prefix = "movie.stream")
public class MovieStreamProperties {

    private String directory;
    private String namePattern;
}

Listing 7-23.src/main/java/com/apress/cloud/stream/movie/MovieStreamProperties.java

如你所见,这和其他章节中的是同一个类;没什么特别的。接下来,创建MovieStream类(参见清单 7-24 )。

package com.apress.cloud.stream.movie;

import lombok.AllArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.context.annotation.Bean;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.Pollers;
import org.springframework.integration.dsl.Transformers;
import org.springframework.integration.file.dsl.Files;
import org.springframework.integration.file.splitter.FileSplitter;

import java.io.File;

@AllArgsConstructor
@EnableConfigurationProperties(MovieStreamProperties.class)
@EnableBinding(Source.class)
public class MovieStream {

    private MovieStreamProperties movieStreamProperties;

    @Bean
    public IntegrationFlow fileFlow(){
        return IntegrationFlows.from(Files
                        .inboundAdapter(new File(this.movieStreamProperties.getDirectory()))
                        .preventDuplicates(true)
                        .patternFilter(this.movieStreamProperties.getNamePattern()),
                        e -> e.poller(Pollers.fixedDelay(5000L)))
                .split(Files.splitter().markers())
                .filter(p -> !(p instanceof FileSplitter.FileMarker))
                .transform(Transformers.fromJson(Movie.class))
                .channel(Source.OUTPUT)
                .get();
    }
}

Listing 7-24.src/main/java/com/apress/cloud/stream/movie/MovieStream.java

清单 7-24 显示了MovieStream类。请注意,您正在使用 2.x 版本的模型编程,其中您需要使用@EnableBinding注释并提供类型,在本例中,是一个Source类型。接下来,打开您的application.properties文件并添加清单 7-25 中的内容。

# Nats Bindings
# Programming Style version 2.x
spring.cloud.stream.bindings.output.destination=movie

# Movie Stream Properties
movie.stream.directory=.
movie.stream.name-pattern=movies-json.txt

# Debugging
logging.level.com.apress.nats=DEBUG
logging.level.org.springframework.cloud.stream.messaging.DirectWithAttributesChannel=DEBUG

Listing 7-25.src/main/resources/application.properties

请注意,您正在重命名输出目的地movie

这是这条小溪的水。我知道代码看起来有些重复,但是它可以帮助您更好地理解这个概念。如何使用反应式编程来实现这一点?你需要做一点小小的改变。首先,你可以在fileFlow中返回Publisher<Message<Movie>>,而不是调用get()方法(来获得IntegrationFlow实例),使用toReactivePublisher()。第二,你需要创建一个供应商。请记住,您需要订阅发布者。您需要为此声明一个Supplier方法。第三,您需要使用spring.cloud.stream.bindings.[suplier-method-name]-out-0.destination属性。

对于这个流,我在项目的根处添加了movies-json.txt。它包含以下内容。

{"title":"The Matrix","actor":"Keanu Reeves","year":1999,"genre":"fiction"}
{"title":"Memento","actor":"Guy Pearce","year":2000,"genre":"drama"}
{"title":"The Prestige","actor":"Christian Bale","year":2006,"genre":"drama"}
{"title":"Disturbia","actor":"Shia LaBeouf","year":2007,"genre":"drama"}

Note

您可以在 ch07/multiple 文件夹中找到所有源代码。你会发现注释掉了反应版本。

电影-过滤器-处理器-NATs-兔子

接下来,让我们创建一个根据电影的类型值过滤电影的处理器。这个流使用 NATS(用于输入)和 RabbitMQ(用于输出)绑定器。打开浏览器,指向 https://start.spring.io 。使用以下元数据。

  • 组:com.apress.cloud.stream

  • 神器:movie-filter-processor-nats-rabbit

  • 包装:com.apress.cloud.stream.movie

  • 依赖:CloudStream,龙目岛

点击生成按钮下载一个 ZIP 文件。将其解压缩并导入到您最喜欢的 IDE 中。记下包名。

打开pom.xml文件并添加以下依赖项。

<!-- NATS -->
<dependency>
        <groupId>com.apress.nats</groupId>
        <artifactId>nats-messaging-binder</artifactId>
        <version>0.0.1-SNAPSHOT</version>
</dependency>

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

接下来,创建Movie类。您可以使用与清单 7-22 中相同的代码。接下来,您需要创建MovieStream类(参见清单 7-26 )。

package com.apress.cloud.stream.movie;

import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Processor;
import org.springframework.integration.annotation.Filter;

@EnableBinding(Processor.class)
public class MovieStream {

    String GENRE_DRAMA = "drama";

    @Filter(inputChannel = Processor.INPUT,outputChannel = Processor.OUTPUT)
    public boolean onlyDrama(Movie movie) {
        return movie.getGenre().equals(GENRE_DRAMA);
    }
}

Listing 7-26.src/main/java/com/apress/cloud/stream/movie/MovieStream.java

这里您仍然使用 2.x 版本的模型,其中您需要声明@EnableBinding及其类型,在本例中,是带有输入和输出处理器的处理器。还要注意,您使用的 Spring Integration @Filter注释需要根据计算的表达式返回一个布尔值。这种情况下,你是在评估体裁是不是戏剧;如果是,就让它过去吧。还要注意,@Filter注释需要两个参数:inputChanneloutputChannel;在这种情况下,它们是处理器类型成员。

您可以在该项目的源代码中找到反应版本。

接下来,我们把application.properties改名为application.yaml。添加清单 7-27 中的内容。

spring:
  cloud:
    stream:
      bindings:
        input:
          binder: nats
          destination: movie
        output:
          binder: rabbit
          destination: log

Listing 7-27.src/main/resources/application.yaml

在这种情况下,您使用 YAML,因为它比属性更清晰易懂。请注意,您正在使用设置为natsspring.cloud.stream.binding.input.binder(该名称来自nats-messaging-binder模块的META-INF/spring.binders)。你把spring.cloud.stream.binding.input.destination设定为movie。将output.binder设置为rabbit,将output.destination设置为log

如果您从MovieStream类中启用反应部分,您需要使用绑定的命名约定。您可以在application.yaml文件中找到这段注释掉的代码。

电影-原木-水槽-兔子

接下来,是电影-原木-水槽-兔子流。对于这个流日志,您使用一个旧的样式和一个 Spring Integration XML 文件来创建日志接收器。打开浏览器,指向 https://start.spring.io 。使用以下元数据。

  • 组:com.apress.cloud.stream

  • 神器:movie-log-sink-rabbit

  • 包装:com.apress.cloud.stream.movie

  • 依赖:CloudStream,龙目岛

您可以按生成按钮下载一个 ZIP 文件。将其解压缩并导入到您最喜欢的 IDE 中。记下包名。

打开pom.xml文件并添加以下依赖项。

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

接下来,创建Movie类。你可以使用清单 7-22 中的代码。然后,创建MovieStream类(参见清单 7-28 )。

package com.apress.cloud.stream.movie;

import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;

@Configuration
@ImportResource({"/META-INF/spring/movie-log.xml"})
@EnableBinding(Sink.class)
public class MovieStream {
}

Listing 7-28.src/main/java/com/apress/cloud/stream/movie/MovieStream.java

您正在使用带有接收器类型作为参数的@EnableBinding。注意,您正在使用@ImportResource注释来加载一个遗留的 XML 文件。接下来,创建META-INF/spring/movie-log.xml文件(参见清单 7-29 )。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:int="http://www.springframework.org/schema/integration"

       xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/integration
    https://www.springframework.org/schema/integration/spring-integration.xsd">

    <int:json-to-object-transformer
            input-channel="input"
            output-channel="logger"
            type="com.apress.cloud.stream.movie.Movie"/>

    <int:logging-channel-adapter id="logger"
                                 logger-name="LOG"
                                 level="INFO"
                                 expression="payload"/>
</beans>

Listing 7-29.src/main/resources/META-INF/spring/movie-log.xml

清单 7-29 显示了遗留的 XML Spring Integration。我认为任何仍然使用 XML 方法的遗留 Spring 系统都可以很容易地现代化到 Spring Boot,因为您可以重用 XML 并利用性能优势(这个主题将在以后讨论)。

正如您所看到的,您正在使用json-to-object-transformer组件(因为来自 RabbitMQ 的数据是一个 application/JSON 类型)只是为了转换成一个对象并让toString()格式登录到控制台。注意,转换器将input-channel属性设置为input(绑定的名称),将output-channel属性设置为logger,?? 是logging-channel-adapter组件的 ID。

最后打开application.properties,添加以下内容。

# Binding RabbitMQ
spring.cloud.stream.bindings.input.destination=log

一起运行它们

您已经准备好运行所有的东西,但是要运行所有的流,您需要确保 NATS 服务器和 RabbitMQ 代理已经启动并且正在运行。我在源代码里加了一个docker-compose.yml文件。它包含两个服务器。您可以使用 Docker Compose 来代替手动启动它们(参见清单 7-30 )。

version: '3'

services:
  nats:
    image: 'nats:latest'
    container_name: nats
    ports:
      - '4222:4222'
      - '8222:8222'

  rabbit:
    image: 'rabbitmq:3.8.3-management-alpine'
    container_name: rabbit
    ports:
      - '15672:15672'
      - '5672:5672'

Listing 7-30.docker-compose.yml

打开终端窗口并转到该文件。运行以下命令启动服务器。

$ docker-compose up

转到您的 IDE 并运行您的流,从日志和处理器开始;或者您可以使用下面众所周知的 Maven 命令来运行这个流。

$ ./mvnw spring-boot-run.

您需要最后运行movie-file-source-nats来读取movies-json.txt文件,这是众所周知的项目根目录。运行它之后,movie-log-sink-rabbit 应该只播放三部戏剧类型的电影。

Movie(title=Memento, actor=Guy Pearce, year=2000, genre=drama)
Movie(title=The Prestige, actor=Christian Bale, year=2006, genre=drama)
Movie(title=Disturbia, actor=Shia LaBeouf, year=2007, genre=drama)

恭喜你!你用了多个经纪人!

当你有多个相同类型的经纪人时会发生什么?换句话说,您有一个处理器正在监听位于东海岸的 RabbitMQ 服务器,您需要处理消息并将其发送到西海岸的 RabbitMQ 服务器。使用相同的原则和命名约定,在application.yaml中使用以下配置。

spring:
  cloud:
    stream:
      bindings:
        input:
          destination: movie
          binder: rabbit1
        output:
          destination: log
          binder: rabbit2
      binders:
        rabbit1:
          type: rabbit
          environment:
            spring:
              rabbitmq:
                host: east-coast.mydomain.com
        rabbit2:
          type: rabbit
          environment:
            spring:
              rabbitmq:
                host: west-coast.mydomain.com
          username: admin
                password: {cipher}c789b2ee5bd

如你所见,添加多个相同类型的活页夹非常容易。

额外配置

尽管我在前一章讨论了配置,但是还有其他值得一提的属性。查看org.springframework.cloud.stream.config.BindingServiceProperties类和 Javadoc 以获得关于这些属性的更多信息。

摘要

在本章中,我向您展示了如何通过以下三个步骤创建一个定制的绑定:从Binder接口实现,添加创建绑定er,的配置,最后,将绑定配置添加到spring.binders文件中。当 Spring Boot 自动配置由于类路径中的发现而启动时,将使用该选项。

您创建了一个 NATS 活页夹,并在多个活页夹中使用。本章使用了不同的方法来创建流:使用 2.x 版,其中需要声明@Binding注释及其类型(源、处理器或接收器),使用 3.x 版,其中可以使用函数式和反应式编程,或者使用旧的 Spring Integration 注释和遗留 XML 中的一些代码。

在下一章,我将讨论 Spring CloudStream,以及 Spring Cloud Stream 和 binder 技术如何融入我们的解决方案。

八、Spring CloudStream:介绍和安装

在前面的章节中,您看到了作为独立微服务运行的 Spring CloudStream 应用。您可以创建一个 CloudStream 式应用的组合,以形成一个完整、健壮、可扩展的系统。您可以通过下载最新的优步-JAR 并执行java -jar命令来运行它们。你可以使用 Docker Compose 的 Docker 图片来运行它们。您了解了如何在 1.x 到 2.x 版本中使用注释创建自定义 CloudStream 式应用,以及在 3.x 版本中使用函数式或反应式编程创建自定义 CloudStream 式应用。

现在你有了多个 CloudStream 式应用,应该有一种技术来管理它们。您需要能够运行您的应用,在一个应用关闭时协调它们,管理复合流的版本,用触发器编排它们,添加响应特定事件的挂钩,添加安全性以安全地连接到外部系统,调度批处理作业,等等。

既然你用过 Docker Compose,Docker Swarm 可能是个不错的解决方案。是的,但是你需要一些东西来管理应用的生命周期和整个复合流。当新的参数被设置或多个部署到不同的平台时,您需要一些东西来处理版本。您需要一种方法来收集关于您的流的信息,如日志和指标,并管理您的解决方案的各个方面。说到可伸缩性、弹性和高可用性,您可以依赖 Cloud Foundry 或 Kubernetes 这样的云平台,甚至更好的是 Red Hat OpenShift。

本章讨论 Spring Cloud Data Flow,这是一种流生命周期技术,它提供了创建健壮的、基于流的微服务和批处理数据处理解决方案所需的一切。

SpringCloudStream

Spring Cloud Data Flow 是一种开源技术,它为流和批处理数据管道组成了复杂的拓扑结构。它使用前面章节中介绍的预构建微服务,并允许您开发和测试用于数据集成的微服务。它可以独立使用,也可以在任何云平台中使用,如 Cloud Foundry 或 Kubernetes,甚至更好的是,您可以使用 Red Hat OpenShift 中的所有附加功能。

特征

让我们回顾一下 Spring Cloud Data Flow 为解决复杂的流拓扑而提供的一些主要特性。

  • 编程模式。您在之前的章节中已经看到了这一点,在这些章节中,您使用了 Spring Cloud Stream 框架来创建流式应用。即使您还没有看到它的运行,您也可以使用 Spring Cloud Task framework 创建或触发一个批处理解决方案,该解决方案使用 Spring Batch 定义 ETL(提取、转换、加载)作业。正如您已经知道的,有老式的 Spring Integration(通过通道)、函数式(使用 Java 8+)和反应式编程(Kafka Streams)编程模型。

  • 多语种。将流式应用创建为微服务的一个好处是,您可以使用 Python、Groovy、。NET 或任何其他语言。Spring CloudStream 可以启动您的应用来连接您的流。

  • 消息代理绑定器。无论使用哪种消息中间件代理,您都可以将相同的代码与可插入绑定器一起使用。Spring Cloud Stream 团队支持 RabbitMQ 和 Kafka 开箱即用,但你可以在社区中找到多个 binder 实现。基于您在前面章节中所学的内容,您可以创建一个绑定器,并将其用于 Spring CloudStream。

  • 应用启动器。Spring CloudStream 使用带有 Docker 或 Maven 工件的 Spring Cloud 流式应用启动器来创建流解决方案。您可以看到,您可以轻松地注册您的定制流,并在仪表板中使用它,这样您就有了流的可视化表示。

  • 安全。我还没有谈到安全性,但是 Spring Cloud Data Flow 不仅允许您保护仪表板,还允许您使用 OAuth2 或 OpenID Connect 等安全标准进行身份验证和授权来保护所有的微服务。

  • 连续交货。使用 Spring CloudStream 的好处之一是在升级流时避免停机。这是通过将金丝雀或蓝绿色的部署实践应用到您的流中,并添加持续交付和持续集成工具来实现的。

  • 批处理。在 Spring Cloud Data Flow 中,您可以使用详细的状态报告和重新启动失败作业的方法来管理任何批处理作业的执行,因为它可以安装在任何版本的 Cloud Foundry 和 Kubernetes 中,所以您可以在 Spring Cloud Data Flow 仪表板中调度任何批处理作业。

  • 特定领域语言。Spring CloudStream 提供了一种特定于领域的语言(DSL ),使用| pipelines 直观地显示与下一个 app 的连接。

Spring CloudStream 是流拓扑/应用的编排者。它创建了强大的集成解决方案。它提供了通过使用 REST API、shell 工具或 GUI 仪表板来可视化、运行、部署、更改和管理流版本的方法。让我们从本地安装开始,这样您就可以看到 Spring CloudStream 的运行。

本地安装

您可以在本地或开发环境中运行 Spring CloudStream。本节中的说明不适用于生产环境。请记住,如果您需要生产级指令,您需要依赖一个能够带来可伸缩性、弹性、容错、高可用性、存储、监控等功能的平台,例如 Kubernetes、Cloud Foundry、Mesos 或 Yarn。

单台机器/服务器

在接下来的部分中,我将描述如何使用 RabbitMQ 和 Kafka 作为绑定器。在其他章节中,您可以重用前面章节中的 NATs 活页夹;现在,要么选择 RabbitMQ,要么选择 Kafka。值得一提的是,这里使用的服务器(Skipper 和数据流)默认使用 H2 作为持久性引擎。这个 DB 引擎是一个内存中的数据库,这意味着一旦您完成,任何创建的流、注册的作业或应用都将消失。因此,下面几节使用 MySQL 作为持久性引擎。如果您愿意,可以使用 PostgreSQL 或 Oracle 这是一个改变 Spring Data 源属性的问题。

使用 RabbitMQ 作为绑定器,使用 MySQL 作为持久性

为了使事情更简单,创建一个名为workspace-rabbit-mysql的文件夹。

要在单台机器上启动和运行 Spring CloudStream,需要执行以下步骤。workspace-rabbit-mysql是目录,RabbitMQ 是绑定器,MySQL 是持久性。有些步骤是可选的,但是它们为将来的测试设置了环境。

  1. Create a download.sh script in the workspace-rabbit-mysql folder, with the following content.

    #!/bin/sh
    wget https://repo.spring.io/release/org/springframework/cloud/spring-cloud-dataflow-server/2.6.0/spring-cloud-dataflow-server-2.6.0.jar
    wget https://repo.spring.io/release/org/springframework/cloud/spring-cloud-dataflow-shell/2.6.0/spring-cloud-dataflow-shell-2.6.0.jar
    wget https://repo.spring.io/release/org/springframework/cloud/spring-cloud-skipper-server/2.5.0/spring-cloud-skipper-server-2.5.0.jar
    
    

    在写这本书的时候,我对数据流服务器和 shell 使用了 2.6.0 版本,对 Skipper 服务器使用了 2.50 版本。使脚本可执行并执行它。注意,您下载了三个 jar、Skipper、数据流服务器和 shell。

  2. workspace-rabbit-mysql文件夹中,创建包含以下内容的docker-compose.yml文件。

    version: '3'
    
    services:
      mysql:
        image: mysql:5.7.25
        container_name: dataflow-mysql
        environment:
          MYSQL_DATABASE: dataflow
          MYSQL_USER: root
          MYSQL_ROOT_PASSWORD: rootpw
        ports:
          - "3306:3306"
    
      rabbitmq:
        image: rabbitmq:3.8.3-alpine
        container_name: dataflow-rabbitmq
        ports:
          - "5672:5672"
    
    
  3. In the workspace-rabbit-mysql folder, create the startup-skipper.sh script file with the following content and make it executable.

    #!/bin/sh
    java -jar spring-cloud-skipper-server-2.5.0.jar \
    --spring.datasource.url=jdbc:mysql://localhost:3306/dataflow \
    --spring.datasource.username=root \
    --spring.datasource.password=rootpw \
    --spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
    
    

    注意,您正在声明 Spring Data 源。如果不添加这些属性,Skipper 和数据流服务器将使用 H2 嵌入式引擎作为默认的持久性机制。注意,现在,我在每个属性中使用localhost,这意味着如果您有远程服务器,您可以更改它。

  4. In the workspace-rabbit-mysql folder, create the startup-dataflow.sh script file with the following content and make it executable.

    #!/bin/sh
    java -jar spring-cloud-dataflow-server-2.6.0.jar \
    --spring.datasource.url=jdbc:mysql://localhost:3306/dataflow \
    --spring.datasource.username=root \
    --spring.datasource.password=rootpw \
    --spring.datasource.driver-class-name=org.mariadb.jdbc.Driver \
    --spring.cloud.dataflow.applicationProperties.stream.spring.rabbitmq.host=localhost
    
    

    注意,您正在添加stream.spring.rabbitmq属性。如果你不指定它们,那么它使用 Kafka 作为默认的活页夹。

  5. 执行workspace-rabbit-mysql文件夹中的docker-compose命令。

    $ docker-compose up -d
    
    

    -d选项将docker-compose进程发送到后台。请记住,您需要稍后回到这里,因为要关闭服务。

  6. 在另一个终端窗口中,执行以下命令。首先启动 Skipper 服务器。

    $ ./startup-skipper.sh
    
    
  7. 在新的终端中,启动 Spring CloudStream 服务器。

    $ ./startip-dataflow.sh
    
    
使用 Kafka 作为绑定器,使用 MySQL 作为持久性

如果你想使用 Kafka 作为绑定器,创建一个workspace-kafka-mysql文件夹来保存配置。

使用workspace-kafka-mysql目录结构、Kafka 作为绑定器、MySQL 作为持久性,在单台机器上启动和运行 Spring CloudStream 需要以下步骤。

  1. 重用之前的download.sh脚本;你可以在这里复制。如果您已经执行了它,那么将 JARs 移动到workspace-kafka-mysql文件夹结构中。

  2. workspace-kafka-mysql文件夹中,创建包含以下内容的docker-compose.yml文件。

    version: '3'
    
    services:
      mysql:
        image: mysql:5.7.25
        container_name: dataflow-mysql
        environment:
          MYSQL_DATABASE: dataflow
          MYSQL_USER: root
          MYSQL_ROOT_PASSWORD: rootpw
        ports:
          - "3306:3306"
    
      zookeeper:
        image: 'bitnami/zookeeper:latest'
        container_name: zookeeper
        networks:
          - kafka-net
        ports:
          - '2181:2181'
        environment:
          - ALLOW_ANONYMOUS_LOGIN=yes
    
      kafka:
        image: 'bitnami/kafka:latest'
        container_name: kafka
        networks:
          - kafka-net
        ports:
          - '9092:9092'
        environment:
          - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
          - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092
          - ALLOW_PLAINTEXT_LISTENER=yes
        depends_on:
          - zookeeper
    
    networks:
      kafka-net:
    
    
  3. workspace-kafka-mysql文件夹中,创建startup-skipper.sh脚本文件。它和workspace-rabbit-mysql一样,所以你可以在这里复制它。

  4. In the workspace-kafka-mysql folder, create the startup-dataflow.sh script file with the following content and make it executable.

    java -jar spring-cloud-dataflow-server-2.6.0.jar \
    --spring.datasource.url=jdbc:mysql://localhost:3306/dataflow \
    --spring.datasource.username=root \
    --spring.datasource.password=rootpw \
    --spring.datasource.driver-class-name=org.mariadb.jdbc.Driver \
    --spring.cloud.dataflow.applicationProperties.stream.spring.cloud.stream.kafka.binder.brokers=PLAINTEXT://localhost:9092 \
    --spring.cloud.dataflow.applicationProperties.stream.spring.cloud.stream.kafka.streams.binder.brokers=PLAINTEXT://localhost:9092 \
    --spring.cloud.dataflow.applicationProperties.stream.spring.cloud.stream.kafka.binder.zkNodes=localhost:2181 \
    --spring.cloud.dataflow.applicationProperties.stream.spring.cloud.stream.kafka.streams.binder.zkNodes=localhost:2181
    
    

    请注意,您正在传递 Kafka 活页夹的所有必要信息。

  5. 执行workspace-kafka-mysql文件夹中的docker-compose命令。

    $ docker-compose up -d
    
    
  6. -d选项将docker-compose进程发送到后台。请记住,您需要稍后返回,因为要关闭服务。

  7. 在另一个终端窗口中,执行以下命令启动 Skipper 服务器。

    $ ./startup-skipper.sh
    
    
  8. 在新的终端中,启动 Spring CloudStream 服务器。

    $ ./startup-dataflow.sh
    
    

您是否注意到,无论您选择 Rabbit 还是 Kafka 作为绑定器,都声明了相同的持久性机制?你注意到两台服务器上的日志了吗?您是否看到正在尝试连接到端口 8888 中的 Spring Cloud 配置服务器?如果需要使用两个绑定器,如何解决 MySQL 的重复问题?

您可以使用 Spring Cloud Config 来设置常见的配置,比如 MySQL 的持久性和特定于 binder 的配置。不使用命令行或application.properties/yaml文件,您可以使用一个集中式服务器,这样 Skipper 和数据流服务器可以在启动时拥有这些配置。

使用 Spring Boot 配置功能

选择 RabbitMQ 或 Kafka,以避免将所有属性放在命令行中。您总是可以使用 Spring Boot 配置特性将应用的属性放在当前目录或一个config/目录中。

我将图 8-1 所示的结构用于优步罐。

img/337978_1_En_8_Fig1_HTML.jpg

图 8-1。

带有配置/应用.属性和优步-jar 的工作区

config/application.properties有以下内容。

## DataSource
spring.datasource.url=jdbc:mysql://localhost:3306/dataflow
spring.datasource.username=root
spring.datasource.password=rootpw
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver

## Binder
spring.cloud.dataflow.applicationProperties.stream.spring.rabbitmq.host=localhost
spring.cloud.dataflow.applicationProperties.stream.spring.rabbitmq.username=guest
spring.cloud.dataflow.applicationProperties.stream.spring.rabbitmq.password=guest

您可以用下面的代码运行任一 JAR。

$ java -jar spring-cloud-skipper-server-2.6.0.jar

在本例中,Spring Boot 获取config/application.properties并使用其内容连接到 MySQL 和 Rabbit。

Spring CloudStream 仪表板

完成本地环境设置后,打开浏览器并指向http://localhost:9393/dashboard。你的屏幕看起来应该如图 8-2 所示。

img/337978_1_En_8_Fig2_HTML.jpg

图 8-2

Spring CloudStream 仪表板:http://localhost:9393/dashboard

图 8-2 为仪表板。一切顺利。不要担心显示的选项卡或其他链接。当你创建你的第一个流流时,我会讨论它们。

注册 CloudStream 式应用启动器

一旦服务器启动并运行,就该注册 CloudStream 式应用启动器了。在主控制面板中,单击+添加应用按钮。接下来,选择批量导入申请(见图 8-3 )。

img/337978_1_En_8_Fig3_HTML.jpg

图 8-3。

添加应用:http://localhost:9393/dashboard/#/apps/add

接下来,添加 URI。最简单的方法是点击列出所有流式应用的部分。例如,如果你正在使用 RabbitMQ,点击流式应用(RabbitMQ/Maven) (见图 8-4 )。

img/337978_1_En_8_Fig4_HTML.jpg

图 8-4。

从 URI 添加批量应用:http://localhost:9393/dashboard/#/apps/add/import-from-uri

下面列出了应用的 URIs。

接下来,单击导入应用按钮。现在你应该已经导入了所有的应用(见图 8-5 )。

img/337978_1_En_8_Fig5_HTML.jpg

图 8-5。

导入的应用:http://localhost:9393/dashboard/#/apps

Note

我在源代码中添加了一些脚本,这些脚本可以在单台机器和 Spring CloudStream 服务器上本地启动。我假设你有docker-composecurlwget命令。

独立的服务器或代理

如果你想让一个独立的机器单独运行,你可以这样做,但是你需要用spring.cloud.skipper.client.serverUri属性告诉数据流服务器 Skipper 服务器在哪里。在命令行中将此属性设置为参数。

$ java -jar spring-cloud-dataflow-server-2.6.0.jar \
   --spring.cloud.skipper.client.serverUri=https://my-other-server:7577/api

如果是在代理后面,需要将server.use-forward-headers设置为true,或者用以下参数启动数据流服务器。

$ java -jar spring-cloud-dataflow-server-2.6.0.jar \
   --spring.cloud.skipper.client.serverUri=https://192.51.100.1:7577/api  \
   --server.use-forward-headers=true

这些是您需要在代理配置中使用的路径和 URL。

securityinfo:
  path: /security/**
  url: http://data-flow-server:9393/security
about:
  path: /about/**
  url: http://data-flow-server:9393/about
apps:
  path: /apps/**
  url: http://data-flow-server:9393/apps
dashboard:
  path: /dashboard/**
  url: http://data-flow-server:9393/dashboard
audit-records:
  path: /audit-records/**
  url: http://data-flow-server:9393/audit-records
jobs:
  path: /jobs/**
  url: http://data-flow-server:9393/jobs
streams:
  path: /streams/**
  url: http://data-flow-server:9393/streams
tasks:
  path: /tasks/**
  url: http://data-flow-server:9393/tasks
tools:
  path: /tools/**
  url: http://data-flow-server:9393/tools
runtime:
  path: /rutime/**
  url: http://data-flow-server:9393/runtime
completions:
  path: /completions/**
  url: http://data-flow-server:9393/completions

使用 Docker 合成

Docker 创建了本地开发和测试所需的基础设施。很好用,也很有效。所以,让我们从创建两个docker-compose文件开始。我建议创建一个保存这些文件的文件夹。一个文件使用兔子,另一个使用卡夫卡的活页夹。

用清单 8-1 中的内容创建docker-compose-rabbitmq.yml

version: '3'

services:
  mysql:
    image: mysql:5.7.25
    container_name: dataflow-mysql
    environment:
      MYSQL_DATABASE: dataflow
      MYSQL_USER: root
      MYSQL_ROOT_PASSWORD: rootpw
    expose:
      - 3306

  rabbitmq:
    image: rabbitmq:3.8.3-alpine
    container_name: dataflow-rabbitmq
    expose:
      - '5672'

  dataflow-server:
    image: springcloud/spring-cloud-dataflow-server:${DATAFLOW_VERSION:?DATAFLOW_VERSION variable needs to be set!}
    container_name: dataflow-server
    ports:
      - "9393:9393"
    environment:
      - spring.cloud.dataflow.applicationProperties.stream.spring.rabbitmq.host=rabbitmq
      - spring.cloud.skipper.client.serverUri=http://skipper-server:7577/api
      - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/dataflow
      - SPRING_DATASOURCE_USERNAME=root
      - SPRING_DATASOURCE_PASSWORD=rootpw
      - SPRING_DATASOURCE_DRIVER_CLASS_NAME=org.mariadb.jdbc.Driver
    depends_on:
      - rabbitmq
    entrypoint: "./wait-for-it.sh mysql:3306 -- java -jar /maven/spring-cloud-dataflow-server.jar"
    volumes:
      - ${HOST_MOUNT_PATH:-.}:${DOCKER_MOUNT_PATH:-/root/scdf}

  skipper-server:
    image: springcloud/spring-cloud-skipper-server:${SKIPPER_VERSION:?SKIPPER_VERSION variable needs to be set!}
    container_name: skipper
    ports:
      - "7577:7577"
      - "20000-20105:20000-20105"
    volumes:
      - ${HOST_MOUNT_PATH:-.}:${DOCKER_MOUNT_PATH:-/root/scdf}

  app-import:
    image: springcloud/openjdk:2.0.0.RELEASE
    container_name: dataflow-app-import
    depends_on:
      - dataflow-server
    command: >
      /bin/sh -c "
        ./wait-for-it.sh -t 180 dataflow-server:9393;
        wget -qO- 'http://dataflow-server:9393/apps' --post-data='uri=${STREAM_APPS_URI:-https://dataflow.spring.io/rabbitmq-maven-latest&force=true}';
        echo 'Stream apps imported'
        wget -qO- 'http://dataflow-server:9393/apps' --post-data='uri=${TASK_APPS_URI:-https://dataflow.spring.io/task-maven-latest&force=true}';
        echo 'Task apps imported'"

Listing 8-1.docker-compose-rabbitmq.yml

注意,您使用的是DATAFLOW_VERSIONSKIPPER_VERSION环境变量,需要对它们进行设置以获得映像版本。使用这种方法,您可以找到任何版本的可重用 Docker 合成文件。当然,你可以用同样的形式添加 Rabbit 和 MySQL 版本。每个服务都使用指向服务名称的环境变量,这很有用,因为 Docker Compose 创建了一个 DNS,使得 DevOps 人员更容易使用名称而不是 IP。

如果你查看清单 8-1 ,你会在最后看到app-import声明。即使你还没有看到 Spring Cloud Stream CloudStream 的组件,但是数据流服务器提供了一个 API,可以让你连接到它。在这种情况下,当您使用这个 Docker 合成文件时,您将发布一个 URI 来注册应用。

接下来,您可以用清单 8-2 中的内容创建docker-compose-kafka.yml文件。

version: '3'

services:
  mysql:
    image: mysql:5.7.25
    container_name: dataflow-mysql
    environment:
      MYSQL_DATABASE: dataflow
      MYSQL_USER: root
      MYSQL_ROOT_PASSWORD: rootpw
    expose:
      - 3306

  kafka-broker:
    image: confluentinc/cp-kafka:5.3.1
    container_name: dataflow-kafka
    expose:
      - "9092"
    environment:
      - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka-broker:9092
      - KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181
      - KAFKA_ADVERTISED_HOST_NAME=kafka-broker
      - KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1
    depends_on:
      - zookeeper

  zookeeper:
    image: confluentinc/cp-zookeeper:5.3.1
    container_name: dataflow-kafka-zookeeper
    expose:
      - "2181"
    environment:
      - ZOOKEEPER_CLIENT_PORT=2181

  dataflow-server:
    image: springcloud/spring-cloud-dataflow-server:${DATAFLOW_VERSION:?DATAFLOW_VERSION variable needs to be set!}
    container_name: dataflow-server
    ports:
      - "9393:9393"
    environment:
      - spring.cloud.dataflow.applicationProperties.stream.spring.cloud.stream.kafka.binder.brokers=PLAINTEXT://kafka-broker:9092
      - spring.cloud.dataflow.applicationProperties.stream.spring.cloud.stream.kafka.streams.binder.brokers=PLAINTEXT://kafka-broker:9092
      - spring.cloud.dataflow.applicationProperties.stream.spring.cloud.stream.kafka.binder.zkNodes=zookeeper:2181
      - spring.cloud.dataflow.applicationProperties.stream.spring.cloud.stream.kafka.streams.binder.zkNodes=zookeeper:2181
      - spring.cloud.skipper.client.serverUri=http://skipper-server:7577/api
      - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/dataflow
      - SPRING_DATASOURCE_USERNAME=root
      - SPRING_DATASOURCE_PASSWORD=rootpw
      - SPRING_DATASOURCE_DRIVER_CLASS_NAME=org.mariadb.jdbc.Driver
    depends_on:
      - kafka-broker
    entrypoint: "./wait-for-it.sh mysql:3306 -- java -jar /maven/spring-cloud-dataflow-server.jar"
    volumes:
      - ${HOST_MOUNT_PATH:-.}:${DOCKER_MOUNT_PATH:-/root/scdf}

  app-import:
    image: springcloud/openjdk:2.0.0.RELEASE
    container_name: dataflow-app-import
    depends_on:
      - dataflow-server
    command: >
      /bin/sh -c "
        ./wait-for-it.sh -t 180 dataflow-server:9393;
        wget -qO- 'http://dataflow-server:9393/apps' --post-data='uri=${STREAM_APPS_URI:-https://dataflow.spring.io/kafka-maven-latest&force=true}';
        echo 'Stream apps imported'
        wget -qO- 'http://dataflow-server:9393/apps' --post-data='uri=${TASK_APPS_URI:-https://dataflow.spring.io/task-maven-latest&force=true}';
        echo 'Task apps imported'"

  skipper-server:
    image: springcloud/spring-cloud-skipper-server:${SKIPPER_VERSION:?SKIPPER_VERSION variable needs to be set!}
    container_name: skipper

    ports:
      - "7577:7577"
      - "20000-20105:20000-20105"
    environment:
      - SPRING_CLOUD_SKIPPER_SERVER_PLATFORM_LOCAL_ACCOUNTS_DEFAULT_PORTRANGE_LOW=20000
      - SPRING_CLOUD_SKIPPER_SERVER_PLATFORM_LOCAL_ACCOUNTS_DEFAULT_PORTRANGE_HIGH=20100
      - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/dataflow
      - SPRING_DATASOURCE_USERNAME=root
      - SPRING_DATASOURCE_PASSWORD=rootpw
      - SPRING_DATASOURCE_DRIVER_CLASS_NAME=org.mariadb.jdbc.Driver
    entrypoint: "./wait-for-it.sh mysql:3306 -- java -Djava.security.egd=file:/dev/./urandom -jar /spring-cloud-skipper-server.jar"
    volumes:
      - ${HOST_MOUNT_PATH:-.}:${DOCKER_MOUNT_PATH:-/root/scdf}

Listing 8-2.docker-compose-kafka.yml

清单 8-2 显示的是docker-compose-kafka.yml文件;注意和 RabbitMQ 差不多,用的是 Kafka 和 Zookeeper。还要检查属性并确定您使用的是服务名。

接下来,通过对 RabbitMQ 使用以下命令来启动您的基础设施。

$ export DATAFLOW_VERSION=2.6.0
$ export SKIPPER_VERSION=2.5.0
$ docker-compose -f docker-compose-rabbitmq.yml up

或者你用下面这句话来形容卡夫卡。

$ export DATAFLOW_VERSION=2.6.0
$ export SKIPPER_VERSION=2.5.0
$ docker-compose -f docker-compose-kafka.yml up

接下来,转到您的浏览器并打开http://localhost:9393/dashboard。你的屏幕应该和图 8-5 一样。它应该已经导入了应用。

要关闭,如果您使用 RabbitMQ,请执行以下命令。

$ export DATAFLOW_VERSION=2.6.0
$ export SKIPPER_VERSION=2.5.0
$ docker-compose -f docker-compose-rabbitmq.yml down

如果您正在使用 Kafka,请执行以下命令。

$ export DATAFLOW_VERSION=2.6.0
$ export SKIPPER_VERSION=2.5.0
$ docker-compose -f docker-compose-kafka.yml down

请记住,shutdown 命令必须在 YAML 文件所在的目录中运行。

不可思议的安装

您还没有看到 Spring CloudStream 的强大功能以及它的能力。在接下来的章节中,您将创建可以占用多台机器的流。在某种程度上,你需要扩大规模,并且有能力洞察你的信息流发生了什么。当一些应用(在流程中)开始失败时会发生什么?您应该能够创建多个实例并对它们进行监控,如果一个实例关闭了,可以重新创建它。当然,您可以手动完成,但是当您有一个包含 100 个微服务的流程时,您需要依赖一个提供弹性、高可用性、可伸缩性和可见性的平台。

在开始之前,您需要安装并运行 Kubernetes。如果你有足够的 RAM 来运行 Spring CloudStream,或者在任何公共云中(IBM Cloud、Google、Amazon 或 Microsoft Azure)都有资源,你就可以使用你的电脑。

带有 Docker 桌面的个人电脑

Mac 或 Windows 用户可以安装 Docker Desktop。它可以启用 Kubernetes,但您的计算机上至少需要 16 GB 的内存。可以从 www.docker.com/products/docker-desktop 安装。

接下来,你需要做一些修改。打开 Docker 桌面偏好设置,进入资源,修改设置。您还需要启用 Kubernetes(参见图 8-6 和 8-7 )。

  • CPUs 个

  • RAM: 8 GB

  • 交换空间:1.5 GB

  • Disk image size: 30 GB

    img/337978_1_En_8_Fig7_HTML.jpg

    图 8-7。

    桌面坞站:首选项库

    img/337978_1_En_8_Fig6_HTML.jpg

    图 8-6。

    Docker 桌面:首选项➤资源

    如果您的计算机上有资源,请添加更多的 RAM 和 CPU。

更改这些设置后,您需要重新启动 Docker Desktop。然后,你就一切就绪了。

迷你库比

如果你用的是 Linux,可以安装 Minikube ( https://kubernetes.io/docs/tasks/tools/install-minikube/ )。遵循安装说明,或使用以下内容。

如果你在 Windows 上,使用 Chocolatey ( https://chocolatey.org )。安装完成后,在命令行或 PowerShell 中执行choco install minikube

如果你在 Mac OS 上,使用 Brew ( https://brew.sh/ )。安装后,在您的终端中执行brew install minikube

Minikube 可以在任何操作系统中使用,所以如果你喜欢在下面的章节中使用 Minikube,它应该没有任何问题;启动的时候记得给足内存和 CPU。

$ minikube start --cpus 4 --memory 8192

阅读关于可以传递哪些其他参数的文档;例如,要使用的驱动程序。

Note

如果你正在使用 Minikube,查看kubectl describe nodes命令。当您执行minikube start --cpus命令时,您应该有您指定的 CPUs 如果没有,可以先执行minikube stop && minikube delete,然后再执行minikube start --cpus 4 --memory 8192。如果你有更多的资源,比如 32 GB 的内存,你可以使用--cpus 6--memory 12288

在 Kubernetes 中安装 Spring CloudStream

既然您已经启动并运行了 Kubernetes 集群,那么是时候安装 Spring CloudStream 了。最好的方法之一是使用kubectl命令。你也可以使用 Helm,但是你需要知道如何修改你的图表来实现它;如果你对 Helm 感兴趣,我推荐你去参观 https://dataflow.spring.io/docs/installation/kubernetes/helm/

最简单的方法是从 Apress 网站下载源代码。即使我包括了 YAML 的文件,你的电脑上应该已经有了。你可以在ch08/kubernetes文件夹结构中找到一切(见图 8-8 )。

img/337978_1_En_8_Fig8_HTML.jpg

图 8-8。

来源代码:ch 08/kubrines

使用 kubectl

让我们按照分步说明来安装每个组件。我假设您的集群正在运行,并且您已经下载了配套书籍的源代码。

  1. It is important to know the versions that you are going to work on. When this book was written, the following versions were available.

    1. SpringCloud 数据流:springcloud/spring-cloud-dataflow-server:2.6.0

    2. SpringCloud 队长:springcloud/spring-cloud-skipper-server:2.5.0

    3. Grafana 形象:??]

    使用 Kubernetes 集群的一个好处是,您可以使用它的监控工具,但是有时您需要更多的信息,所以让我们在这一节中插入 Grafana 和 Prometheus。如果您运行的是 Minikube 或 Docker Desktop Kubernetes,则至少还需要 2 到 4 GB 的额外内存。

  2. 选择经纪人。记住 Spring Cloud Stream 提供 RabbitMQ 和 Kafka 作为代理,如果你想使用多个绑定器,你可以配置你的流。(接下来的章节将带回 NATs 代理。)选择一个绑定器,Rabbit 或 Kafka,并执行以下命令。

    1. 拉比特

      kubectl create -f rabbitmq/

      ch08/kubernetes/文件夹中,有两个文件:rabbitmq-deployment.yamlrabbitmq-svc.yaml(见清单 8-3 和 8-4 )。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: rabbitmq
  labels:
    app: rabbitmq
spec:
  replicas: 1
  selector:
    matchLabels:
      app: rabbitmq
  template:
    metadata:
      labels:
        app: rabbitmq
    spec:
      containers:
      - image: rabbitmq:3.8.3-alpine
        name: rabbitmq
        ports:
        - containerPort: 5672

Listing 8-3.rabbitmq-deployment.yaml

注意,您使用的是rabbitmq:3.8.3-alpine映像,端口是 5672。该应用被命名为 rabbitmq,这是 Kubernetes 中的一个简单机制,用于在必要时查找、编辑和删除 pod、服务或部署。

apiVersion: v1
kind: Service
metadata:
  name: rabbitmq
  labels:
    app: rabbitmq
spec:
  ports:
  - port: 5672
  selector:
    app: rabbitmq

Listing 8-4.rabbitmq-svc.yaml

您将服务命名为 rabbitmq,因此通过名称定位 RabbitMQ 服务器更容易。记住 Kubernetes 有一个 DNS,一个定位服务的强大工具。

  1. 卡夫卡

    kubectl create -f kafka/

    有四个文件。Kafka 依赖于 Zookeeper,所以你必须添加它的部署和服务(见清单 8-5 、 8-6 、 8-7 和 8-8 )。

apiVersion: v1
kind: Service
metadata:
  name: kafka
  labels:
    app: kafka
    component: kafka-broker
spec:
  ports:
  - port: 9092
    name: kafka-port
    targetPort: 9092
    protocol: TCP
  selector:
    app: kafka
    component: kafka-broker

Listing 8-8.kafka-svc.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: kafka-broker
  labels:
    app: kafka
    component: kafka-broker
spec:
  replicas: 1
  selector:
    matchLabels:
      app: kafka

  template:
    metadata:
      labels:
        app: kafka
        component: kafka-broker
    spec:
      containers:
      - name: kafka
        image: wurstmeister/kafka:2.12-2.3.0
        ports:
        - containerPort: 9092
        env:
          - name: ENABLE_AUTO_EXTEND
            value: "true"
          - name: KAFKA_RESERVED_BROKER_MAX_ID
            value: "999999999"
          - name: KAFKA_AUTO_CREATE_TOPICS_ENABLE
            value: "false"
          - name: KAFKA_PORT
            value: "9092"
          - name: KAFKA_ADVERTISED_PORT
            value: "9092"
          - name: KAFKA_ADVERTISED_HOST_NAME
            valueFrom:
              fieldRef:
                fieldPath: status.podIP

          - name: KAFKA_ZOOKEEPER_CONNECT
            value: kafka-zk:2181

Listing 8-7.kafka-deployment.yaml

apiVersion: v1
kind: Service
metadata:
  name: kafka-zk
  labels:
    app: kafka
    component: kafka-zk
spec:
  ports:
  - name: client
    port: 2181
    protocol: TCP
  - name: follower
    port: 2888
    protocol: TCP
  - name: leader
    port: 3888
    protocol: TCP
  selector:
    app: kafka-zk
    component: kafka-zk

Listing 8-6.kafla-zk-svc.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: kafka-zk
  labels:
    app: kafka
    component: kafka-zk
spec:
  replicas: 1
  selector:
    matchLabels:
      app: kafka-zk
  template:
    metadata:
      labels:
        app: kafka-zk
        component: kafka-zk
    spec:
      containers:
      - name: kafka-zk
        image: digitalwonderland/zookeeper
        ports:
        - containerPort: 2181
        env:
        - name: ZOOKEEPER_ID
          value: "1"
        - name: ZOOKEEPER_SERVER_1
          value: kafka-zk

Listing 8-5.kafka-zk-deployment.yaml

记住要么选兔子,要么选卡夫卡(暂时)。下一章混合了 RabbitMQ、Kafka 和 NATs 经纪人。

  1. 安装 MySQL(您可以安装任何适合您的环境或业务逻辑的工具;记得更改 YAML 文件中的属性以反映新的数据库引擎)。

    kubectl create -f mysql/

    在这个文件夹中,有四个文件。要使用 MySQL 服务器,您必须定义存储。在这种情况下,使用默认的存储类(您可以将其修改为适合您需求的类型,如 SSD 或可以根据需要增长的弹性存储)。需要添加用户名和密码,因此必须创建一个 Kubernetes 秘密对象(参见清单 8-9 、 8-10 、 8-11 和 8-12 )。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql
  labels:
    app: mysql
  annotations:
    volume.alpha.kubernetes.io/storage-class: default
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 8Gi

Listing 8-9.mysql-pvc.yaml

目前,您使用的是storage-class:default和 8 GB 的存储空间。如果你愿意,你可以修改它。

apiVersion: v1
kind: Secret
metadata:
  name: mysql
  labels:
    app: mysql
data:
  mysql-root-password: eW91cnBhc3N3b3Jk

Listing 8-10.mysql-secrets.yaml

当然,您可以将密码更改为适合您的解决方案的密码。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
  labels:
    app: mysql
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - image: mysql:5.7.25
        name: mysql
        env:
          - name: MYSQL_ROOT_PASSWORD
            valueFrom:
              secretKeyRef:
                key: mysql-root-password
                name: mysql
        ports:
          - containerPort: 3306
            name: mysql
        volumeMounts:
          - name: data
            mountPath: /var/lib/mysql
        args:
          - "--ignore-db-dir=lost+found"
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: mysql

Listing 8-11.mysql-deployment.yml

前面文件的一个主要属性是指向mysql-root-password秘密对象的secretKeyRef

  1. 如果需要监控,安装 Grafana 和 Prometheus。Spring CloudStream 使用微米应用监控 ( http://micrometer.io/ )来发送关于您的流的统计数据,例如内存和 CPU 消耗以及线程数量。
    1. Prometheus

      kubectl create -f prometheus/prometheus-clusterroles.yaml
      kubectl create -f prometheus/prometheus-clusterrolebinding.yaml
      kubectl create -f prometheus/prometheus-serviceaccount.yaml
      kubectl create -f prometheus-proxy/
      kubectl create -f prometheus/prometheus-configmap.yaml
      kubectl create -f prometheus/prometheus-deployment.yaml
      kubectl create -f prometheus/prometheus-service.yaml
      
      

      如您所见,需要添加集群角色、绑定和服务帐户。您必须添加一个代理来向 Grafana 发送信息,然后是ConfigMap、部署和服务(参见清单 8-13 、 8-14 、 8-15 、 8-16 、 8-17 、 8-18 、 8-19 、 8-20 、

apiVersion: v1
kind: Service
metadata:
  name: mysql
  labels:
    app: mysql
spec:
  ports:
    - port: 3306
  selector:
    app: mysql

Listing 8-12.mysql-svc.yaml

kind: ServiceAccount
apiVersion: v1
metadata:
  name: prometheus
  labels:
    app: prometheus
  namespace: default

Listing 8-15.prometheus-serviceaccount.yaml

kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: prometheus
  labels:
    app: prometheus
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: prometheus
subjects:
- kind: ServiceAccount
  name: prometheus
  namespace: default

Listing 8-14.prometheus-clusterrolebinding.yaml

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: prometheus
  labels:
    app: prometheus
rules:
- apiGroups: [""]
  resources:
  - nodes
  - nodes/proxy
  - services
  - endpoints
  - pods
  verbs: ["get", "list", "watch"]
- apiGroups:
  - extensions
  resources:
  - ingresses
  verbs: ["get", "list", "watch"]
- nonResourceURLs: ["/metrics"]
  verbs: ["get"]

Listing 8-13.prometheus-clusterroles.yaml

在这里,您将服务帐户分配给默认的名称空间,但是您可以对其进行更改以适应您的环境。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: prometheus-proxy
  labels:
    app: prometheus-proxy
spec:
#  replicas: 3
  selector:
    matchLabels:
      app: prometheus-proxy
  template:
    metadata:
      labels:
        app: prometheus-proxy
    spec:
      serviceAccountName: prometheus-proxy
      containers:
        - name: prometheus-proxy
          image: micrometermetrics/prometheus-rsocket-proxy:latest
          imagePullPolicy: Always
          ports:
            - name: scrape
              containerPort: 8080
            - name: rsocket
              containerPort: 7001
          resources:
            limits:
              cpu: 1.0
              memory: 2048Mi
            requests:
              cpu: 0.5
              memory: 1024Mi
      securityContext:
        fsGroup: 2000
        runAsNonRoot: true
        runAsUser: 1000

Listing 8-18.prometheus-proxy-deployment.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: prometheus-proxy
  labels:
    app: prometheus-proxy
  namespace: default

Listing 8-17.prometheus-proxy-serviceaccount.yaml

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: prometheus-proxy
  labels:
    app: prometheus-proxy
subjects:
  - kind: ServiceAccount
    name: prometheus-proxy
    namespace: default
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io

Listing 8-16.prometheus-proxy-clusterrolebinding.yaml

您正在使用普罗米修斯代理的最新图像:micrometermetrics/prometheus-rsocket-proxy

apiVersion: v1
kind: Service
metadata:
  name: prometheus-proxy
  labels:
    app: prometheus-proxy
spec:
  selector:
    app: prometheus-proxy
  ports:
    - name: scrape
      port: 8080
      targetPort: 8080
    - name: rsocket
      port: 7001
      targetPort: 7001
  type: LoadBalancer

Listing 8-19.prometheus-proxy-service.yaml

即使这里用了kubectl -f prometheus-proxy/,也不需要在意顺序。Prometheus 代理与我们的流式应用通信并从中获取所有指标。

apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus
  labels:
    app: prometheus
data:
  prometheus.yml: |-
    global:
      scrape_interval: 10s
      scrape_timeout: 9s
      evaluation_interval: 10s

    scrape_configs:
    - job_name: 'proxied-applications'
      metrics_path: '/metrics/connected'
      kubernetes_sd_configs:
        - role: pod
          namespaces:
            names:
              - default
      relabel_configs:
        - source_labels: [__meta_kubernetes_pod_label_app]
          action: keep
          regex: prometheus-proxy
        - source_labels: [__meta_kubernetes_pod_container_port_number]
          action: keep
          regex: 8080
    - job_name: 'proxies'
      metrics_path: '/metrics/proxy'
      kubernetes_sd_configs:
        - role: pod
          namespaces:
            names:
              - default
      relabel_configs:
        - source_labels: [__meta_kubernetes_pod_label_app]
          action: keep
          regex: prometheus-proxy
        - source_labels: [__meta_kubernetes_pod_container_port_number]
          action: keep
          regex: 8080
        - action: labelmap
          regex: __meta_kubernetes_pod_label_(.+)
        - source_labels: [__meta_kubernetes_pod_name]
          action: replace
          target_label: kubernetes_pod_name

Listing 8-20.prometheus-configmap.yaml

告知 Prometheus 从哪里获取度量信息。

apiVersion: v1
kind: Service
metadata:
  name: prometheus
  labels:
    app: prometheus
  annotations:
      prometheus.io/scrape: 'true'
      prometheus.io/path:   /
      prometheus.io/port:   '9090'
spec:
  selector:
    app: prometheus
  ports:
    - port: 9090
      targetPort: 9090

Listing 8-22.prometheus-service.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: prometheus
  name: prometheus
spec:
  selector:
    matchLabels:
      app: prometheus
  template:
    metadata:
      labels:
        app: prometheus
    spec:
      serviceAccountName: prometheus
      containers:
        - name: prometheus
          image: prom/prometheus:v2.12.0
          args:
            - "--config.file=/etc/prometheus/prometheus.yml"
            - "--storage.tsdb.path=/prometheus/"
            - "--web.enable-lifecycle"
          ports:
            - name: prometheus
              containerPort: 9090
          volumeMounts:
            - name: prometheus-config-volume
              mountPath: /etc/prometheus/
            - name: prometheus-storage-volume
              mountPath: /prometheus/
      volumes:
        - name: prometheus-config-volume
          configMap:
            name: prometheus
        - name: prometheus-storage-volume
          emptyDir: {}

Listing 8-21.prometheus-deployment.yaml

给出这个顺序而不是kubectl -f prometheus/是很重要的,因为它可以在没有分配集群角色的情况下开始部署。

  1. 添加数据流服务器角色、绑定和服务帐户。
    1. Data Flow roles, bindings, and service account

      kubectl create -f server/server-roles.yaml
      kubectl create -f server/server-rolebinding.yaml
      kubectl create -f server/service-account.yaml
      
      

      参见清单 8-27 、 8-28 和 8-29 。

apiVersion: v1
kind: Service
metadata:
  name: grafana
  labels:
    app: grafana
spec:
  selector:
    app: grafana
  type: LoadBalancer
  ports:
    - port: 3000
      targetPort: 3000

Listing 8-26.grafana-service.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: grafana
  name: grafana
spec:
  selector:
    matchLabels:
      app: grafana
  template:
    metadata:
      labels:
        app: grafana
    spec:
      containers:
        - image: springcloud/spring-cloud-dataflow-grafana-prometheus:2.5.2.RELEASE
          name: grafana
          env:
            - name: GF_SECURITY_ADMIN_USER
              valueFrom:
                secretKeyRef:
                  name: grafana
                  key: admin-username
            - name: GF_SECURITY_ADMIN_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: grafana
                  key: admin-password
          ports:
            - containerPort: 3000
          resources:
            limits:
              cpu: 500m
              memory: 2500Mi
            requests:
              cpu: 100m
              memory: 100Mi
          volumeMounts:
            - name: config
              mountPath: "/etc/grafana/provisioning/datasources/datasources.yaml"
              subPath: datasources.yaml
      volumes:
        - name: config
          configMap:
            name: grafana

Listing 8-25.grafana-deployment.yaml

apiVersion: v1
kind: Secret
type: Opaque
metadata:
  name: grafana
  labels:
    app: grafana
data:
  admin-username: YWRtaW4=
  admin-password: cGFzc3dvcmQ=

Listing 8-24.grafana-secret.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: grafana
  labels:
    app: grafana
data:
  datasources.yaml: |
    apiVersion: 1
    datasources:
    - name: ScdfPrometheus
      type: prometheus
      access: proxy
      org_id: 1
      url: http://prometheus:9090
      is_default: true
      version: 5
      editable: true
      read_only: false

Listing 8-23.grafana-configmap.yaml

  1. 格拉凡娜

    kubectl create -f grafana/
    
    

    使用 Grafana,您必须添加知道 Prometheus 的配置以及连接它的用户名和密码(参见清单 8-23 、 8-24 、 8-25 和 8-26 )。

  2. Add the cloud Skipper server. It is a key component because it deploys, keeps track of the Stream version, and much more.

    1. 船长

      1. 如果您使用 Rabbit,请执行以下命令。
    2. 如果你使用 Kafka,执行下面的代码。

    kubectl create -f skipper/skipper-config-rabbit.yaml
    
    
    kubectl create -f skipper/skipper-config-kafka.yaml
    
    

    记住你只需要选择一个(现在)(见清单 8-30 和 8-31 )。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: scdf-sa

Listing 8-29.service-account.yaml

kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: scdf-rb
subjects:
- kind: ServiceAccount
  name: scdf-sa
roleRef:
  kind: Role
  name: scdf-role
  apiGroup: rbac.authorization.k8s.io

Listing 8-28.server-rolebinding.yaml

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: scdf-role
rules:
  - apiGroups: [""]
    resources: ["services", "pods", "replicationcontrollers", "persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "create", "delete", "update"]
  - apiGroups: [""]
    resources: ["configmaps", "secrets", "pods/log"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apps"]
    resources: ["statefulsets", "deployments", "replicasets"]
    verbs: ["get", "list", "watch", "create", "delete", "update", "patch"]
  - apiGroups: ["extensions"]
    resources: ["deployments", "replicasets"]
    verbs: ["get", "list", "watch", "create", "delete", "update", "patch"]
  - apiGroups: ["batch"]
    resources: ["cronjobs", "jobs"]
    verbs: ["create", "delete", "get", "list", "watch", "update", "patch"]

Listing 8-27.server-roles.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: skipper
  labels:
    app: skipper
data:
  application.yaml: |-
    spring:
      cloud:
        skipper:
          server:
            platform:
              kubernetes:
                accounts:
                  default:
                    environmentVariables: 'SPRING_RABBITMQ_HOST=${RABBITMQ_SERVICE_HOST},SPRING_RABBITMQ_PORT=${RABBITMQ_SERVICE_PORT}'
                    limits:
                      memory: 1024Mi
                      cpu: 500m
                    readinessProbeDelay: 120

                    livenessProbeDelay: 90
      datasource:
        url: jdbc:mysql://${MYSQL_SERVICE_HOST}:${MYSQL_SERVICE_PORT}/skipper
        username: root
        password: ${mysql-root-password}
        driverClassName: org.mariadb.jdbc.Driver
        testOnBorrow: true
        validationQuery: "SELECT 1"

Listing 8-30.skipper-config-rabbit.yaml

请注意,您正在 ConfigMap 中添加数据源和 RabbitMQ 服务器和端口;所有这些都是环境变量。Kubernetes 为每个服务创建一个环境变量,并将它们添加到每个容器中,因此很容易知道其他服务在哪里;它通常设置 IP 和端口。

apiVersion: v1
kind: ConfigMap
metadata:
  name: skipper
  labels:
    app: skipper
data:
  application.yaml: |-
    spring:
      cloud:
        skipper:
          server:
            platform:
              kubernetes:
                accounts:
                  default:
                    environmentVariables: 'SPRING_CLOUD_STREAM_KAFKA_BINDER_BROKERS=${KAFKA_SERVICE_HOST}:${KAFKA_SERVICE_PORT},SPRING_CLOUD_STREAM_KAFKA_BINDER_ZK_NODES=${KAFKA_ZK_SERVICE_HOST}:${KAFKA_ZK_SERVICE_PORT}'
                    limits:
                      memory: 1024Mi
                      cpu: 500m
                    readinessProbeDelay: 120
                    livenessProbeDelay: 90
      datasource:
        url: jdbc:mysql://${MYSQL_SERVICE_HOST}:${MYSQL_SERVICE_PORT}/skipper
        username: root
        password: ${mysql-root-password}
        driverClassName: org.mariadb.jdbc.Driver
        testOnBorrow: true
        validationQuery: "SELECT 1"

Listing 8-31.skipper-config-kafka.yaml

选择代理后,添加部署和服务。

gkubectl create -f skipper/skipper-deployment.yaml
kubectl create -f skipper/skipper-svc.yaml

参见清单 8-32 和 8-33 。

apiVersion: v1
kind: Service
metadata:
  name: skipper
  labels:
    app: skipper
    spring-deployment-id: scdf
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 7577
  selector:
    app: skipper

Listing 8-33.skipper-svc.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: skipper
  labels:
    app: skipper
spec:
  selector:
    matchLabels:
      app: skipper
  replicas: 1
  template:
    metadata:
      labels:
        app: skipper
    spec:
      containers:
      - name: skipper
        image: springcloud/spring-cloud-skipper-server:2.6.0
        imagePullPolicy: Always
        ports:
        - containerPort: 80
        livenessProbe:
          httpGet:
            path: /actuator/health
            port: 7577
          initialDelaySeconds: 45
        readinessProbe:
          httpGet:
            path: /actuator/info
            port: 7577
          initialDelaySeconds: 45
        resources:
          limits:
            cpu: 1.0
            memory: 1024Mi
          requests:
            cpu: 0.5
            memory: 640Mi
        env:
        - name: SPRING_CLOUD_KUBERNETES_CONFIG_NAME
          value: skipper
        - name: SPRING_CLOUD_KUBERNETES_SECRETS_ENABLE_API

          value: 'true'
        - name: SPRING_CLOUD_KUBERNETES_SECRETS_NAME
          value: mysql
      initContainers:
      - name: init-mysql-wait
        image: busybox
        command: ['sh', '-c', 'until nc -w3 -z mysql 3306; do echo waiting for mysql; sleep 3; done;']
      - name: init-mysql-database
        image: mysql:5.6
        env:
        - name: MYSQL_PWD
          valueFrom:
            secretKeyRef:
              name: mysql
              key: mysql-root-password
        command: ['sh', '-c', 'mysql -h mysql -u root -e "CREATE DATABASE IF NOT EXISTS skipper;"']
      serviceAccountName: scdf-sa

Listing 8-32.skipper-deployment.yaml

当你的类型设置为LoadBalancer时,Skipper 可以从外部访问,但如果你不需要它,你可以使用NodePort来代替。使用 Minikube 或 Docker Desktop Kubernetes 时,NodePort很有用。

  1. Finally, add the Data Flow server.

    kubectl create -f server/server-config.yaml
    kubectl create -f server/server-deployment.yaml
    kubectl create -f server/server-svc.yaml
    
    

    注意,您需要一个配置声明(参见清单 8-34 、 8-35 和 8-36 )。

apiVersion: v1
kind: ConfigMap
metadata:
  name: scdf-server
  labels:
    app: scdf-server
data:
  application.yaml: |-
    spring:
      cloud:
        dataflow:
          applicationProperties:
            stream:
              management:
                metrics:
                  export:
                    prometheus:
                      enabled: true
                      rsocket:
                        enabled: true
                        host: prometheus-proxy
                        port: 7001
            task:
              management:
                metrics:
                  export:
                    prometheus:
                      enabled: true
                      rsocket:
                        enabled: true
                        host: prometheus-proxy
                        port: 7001
          grafana-info:
            url: 'https://grafana:3000'
          task:
            platform:
              kubernetes:
                accounts:
                  default:
                    limits:
                      memory: 1024Mi
      datasource:
        url: jdbc:mysql://${MYSQL_SERVICE_HOST}:${MYSQL_SERVICE_PORT}/mysql
        username: root
        password: ${mysql-root-password}
        driverClassName: org.mariadb.jdbc.Driver
        testOnBorrow: true
        validationQuery: "SELECT 1"

Listing 8-34.server-config.yaml

注意 Prometheus 和 Grafana 部分在ConfigMap中,但是如果您部署时没有这些监控工具,您应该删除流(整个块)、任务(整个块和prometheus-proxy)和grafana-info(整个块)。

kind: Service
apiVersion: v1
metadata:
  name: scdf-server
  labels:
    app: scdf-server
    spring-deployment-id: scdf
spec:
  # If you are running k8s on a local dev box or using minikube, you can use type NodePort instead
  type: LoadBalancer
  ports:
    - port: 80
      targetPort: 80
      name: scdf-server
  selector:
    app: scdf-server

Listing 8-36.server-svc.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: scdf-server
  labels:
    app: scdf-server
spec:
  selector:
    matchLabels:
      app: scdf-server
  replicas: 1
  template:
    metadata:
      labels:
        app: scdf-server
    spec:
      containers:
      - name: scdf-server
        image: springcloud/spring-cloud-dataflow-server:2.4.2.RELEASE
        imagePullPolicy: Always
        volumeMounts:
          - name: database
            mountPath: /etc/secrets/database
            readOnly: true
        ports:
        - containerPort: 80
        livenessProbe:
          httpGet:
            path: /management/health
            port: 80
          initialDelaySeconds: 45
        readinessProbe:
          httpGet:
            path: /management/info
            port: 80
          initialDelaySeconds: 45
        resources:
          limits:
            cpu: 1.0
            memory: 2048Mi
          requests:
            cpu: 0.5
            memory: 1024Mi
        env:
        - name: KUBERNETES_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: "metadata.namespace"
        - name: SERVER_PORT
          value: '80'
        - name: SPRING_CLOUD_CONFIG_ENABLED

          value: 'false'
        - name: SPRING_CLOUD_DATAFLOW_FEATURES_ANALYTICS_ENABLED
          value: 'true'
        - name: SPRING_CLOUD_DATAFLOW_FEATURES_SCHEDULES_ENABLED
          value: 'true'
        - name: SPRING_CLOUD_KUBERNETES_SECRETS_ENABLE_API
          value: 'true'
        - name: SPRING_CLOUD_KUBERNETES_SECRETS_PATHS
          value: /etc/secrets
        - name: SPRING_CLOUD_KUBERNETES_CONFIG_NAME
          value: scdf-server
        - name: SPRING_CLOUD_DATAFLOW_SERVER_URI
          value: 'http://${SCDF_SERVER_SERVICE_HOST}:${SCDF_SERVER_SERVICE_PORT}'
          # Provide the Skipper service location
        - name: SPRING_CLOUD_SKIPPER_CLIENT_SERVER_URI
          value: 'http://${SKIPPER_SERVICE_HOST}:${SKIPPER_SERVICE_PORT}/api'
          # Add Maven repo for metadata artifact resolution for all stream apps
        - name: SPRING_APPLICATION_JSON
          value: "{ \"maven\": { \"local-repository\": null, \"remote-repositories\": { \"repo1\": { \"url\": \"https://repo.spring.io/libs-snapshot\"} } } }"
      initContainers:
      - name: init-mysql-wait
        image: busybox
        command: ['sh', '-c', 'until nc -w3 -z mysql 3306; do echo waiting for mysql; sleep 3; done;']
      serviceAccountName: scdf-sa
      volumes:
        - name: database
          secret:
            secretName: mysql

Listing 8-35.server-deployment.yaml

您可以将NodePort(如果您使用 Minikube 或 Docker Desktop Kubernetes)用于本地环境,将LoadBalancer用于生产环境。

这些是创建 Spring CloudStream 及其组件的步骤。一旦完成部署,就可以使用下面的命令进行访问。

kubectl get svc scdf-server

寻找外部 IP。如果您正在使用NodePort,查看端口以转到您的本地 IP。如果您正在使用 Minikube,请执行以下命令。

minikube service scdf-server --url

如果你用的是 Docker Desktop Kubernetes,访问kubernetes.docker.internal,指向 127.0.0.1。

scdf-server获取 IP 后,打开浏览器,使用 IP 和端口(如果你使用了NodePort)以及/dashboard路径。您应该会看到如图 8-2 所示的相同仪表板。

让我们开始测试您的 Spring CloudStream 服务器。

Note

我在书的源代码中添加了install.shuninstall.sh脚本。你可以在ch08/kubernetes文件夹中找到它们。这些脚本仅适用于 Unix/Linux,您需要安装一个 jq 工具( https://stedolan.github.io/jq/ )。

用一个简单的流测试您的安装

在本节中,您将创建一个小的流。这是你能做什么的一瞥。这是非常简单的,并确保您的安装和活页夹的工作预期。

我在第一部分中使用了 Docker Compose(本地),在基础设施中使用了 Kubernetes 集群(生产),但是在这个例子中,您可以使用任何方法。

使用 Docker 合成

在本节中,让我们使用 Docker Compose 通过 RabbitMQ 和 MySQL 来设置 Spring CloudStream 基础设施。如果你想下载代码,YAML 文件在ch08/docker-compose文件夹中。执行以下命令。

export DATAFLOW_VERSION=2.6.0
export SKIPPER_VERSION=2.5.0
docker-compose -f docker-compose-rabbitmq.yml up -d

请注意,您正在将它发送到后台。打开浏览器并转到http://localhost:9393/dashboard。如果您使用 YAML 文件,您应该已经注册了应用。如果您没有这些应用,请按照前面的部分进行注册。记住,它们应该基于 RabbitMQ,以及 Maven 或 Docker。

接下来,单击左侧边栏中的 Streams 窗格(参见图 8-9 )。

img/337978_1_En_8_Fig9_HTML.jpg

图 8-9。

流:http://localhost:9393/dashboard/#/streams/definitions

接下来,单击 +创建流。这将打开您添加流定义的页面。在这里,您可以使用源、处理器和接收器应用来拖放和连接它们。在这种情况下,转到文本区域(显示“输入流定义”)并输入以下内容。

time | log

app 积木形式如图 8-10 所示。

img/337978_1_En_8_Fig10_HTML.jpg

图 8-10。

流定义:http://localhost:9393/dashboard/#/streams/create

接下来,单击 Create Stream 按钮,这将打开一个弹出对话框,要求输入名称和描述。在名称字段中输入简单,在描述字段中输入只是一个测试(见图 8-11 )。

img/337978_1_En_8_Fig11_HTML.jpg

图 8-11。

创建流

点击创建流按钮创建流,然后返回流部分。在那里你会看到一个流定义列表,你会看到你的“简单”流(见图 8-12 )。

img/337978_1_En_8_Fig12_HTML.jpg

图 8-12。

创建的流

接下来,单击流行末尾的播放按钮。这会将您带到“部署流定义”页面,在这里您可以向每个应用添加属性,并添加应用所需的 CPU、内存、磁盘空间和实例数量。在这种情况下,你会看到三列,一列用于全局,在这里你可以为两个应用添加通用设置(记住,timelog是两个不同的微服务应用,都是应用启动器的一部分),还有一列用于每个微服务——一列用于时间,一列用于日志(见图 8-13 )。

img/337978_1_En_8_Fig13_HTML.jpg

图 8-13。

部署流简单:http://localhost:9393/dashboard/#/streams/definitions/simple/deploy

在这种情况下,您没有设置任何新的属性,所以单击 Deploy stream 按钮。这将发送部署这两个应用的指令,并返回到 Streams 页面。看看你简单的流。您会看到部署状态。如果您点击列表顶部的刷新按钮,您会看到您的应用已经部署完毕(参见图 8-14 和 8-15 )。

img/337978_1_En_8_Fig15_HTML.jpg

图 8-15。

状态:已部署

img/337978_1_En_8_Fig14_HTML.jpg

图 8-14。

状态:正在部署

那么,你怎么知道这个流是否在工作呢?基于 DSL,“time | log”意味着您正在使用一个time源(带有一个 RabbitMQ 绑定器,time-source-rabbit)和一个 log-sink(带有一个 RabbitMQ 绑定器,log-sink-rabbit)。time源每秒发送一次,日志接收器将它记录到控制台中。如果您单击状态旁边的“I”(显示详细信息)按钮,将显示流的详细信息和两个应用的当前日志(参见图 8-16 )。

img/337978_1_En_8_Fig16_HTML.jpg

图 8-16。

详细信息:http://localhost:9393/dashboard/#/streams/definitions/simple/summary

如果您滚动,您会看到日志部分。选择simple.log-v1。您会看到日志接收器当前接收的日志和时间。您应该会看到如下所示的内容。

...
INFO [e.time.simple-1] log-sink  : 05/01/20 02:20:59
INFO [e.time.simple-1] log-sink  : 05/01/20 02:21:00
INFO [e.time.simple-1] log-sink  : 05/01/20 02:21:01
INFO [e.time.simple-1] log-sink  : 05/01/20 02:21:02
INFO [e.time.simple-1] log-sink  : 05/01/20 02:21:03
INFO [e.time.simple-1] log-sink  : 05/01/20 02:21:04
INFO [e.time.simple-1] log-sink  : 05/01/20 02:21:05
...

请注意,应用是使用这种模式命名的:<stream-name>.<app>-<version>。在这种情况下,你有“??”和“??”。史奇普干的。它会跟踪版本。如果您取消部署并重新部署(停止并重新启动)流,您应该看到版本增加到v2

你想知道你是否能看到实时日志?如果您点击运行时部分(在左窗格中),您将进入运行时应用页面,该页面列出了您的流和所有涉及的应用(参见图 8-17 )。

img/337978_1_En_8_Fig17_HTML.jpg

图 8-17。

运行时:http://localhost:9393/dashboard/#/runtime/apps

如果你点击simple.log-v1/v2,你会看到更多的信息(见图 8-18 )。

img/337978_1_En_8_Fig18_HTML.jpg

图 8-18。

运行时详细信息

图 8-18 显示了 app 的simple.log-v1/v2细节。注意stdout字段中的路径。在这种情况下,它就是/tmp/1588299625225/simple.log-v2/stdout_0.log

打开终端并执行以下命令。

docker exec skipper tail -f /tmp/1588299625225/simple.log-v2/stdout_0.log
... log-sink : 05/01/20 02:33:21
... log-sink : 05/01/20 02:33:22
... log-sink : 05/01/20 02:33:23
... log-sink : 05/01/20 02:33:24
... log-sink : 05/01/20 02:33:25
... log-sink : 05/01/20 02:33:26
... log-sink : 05/01/20 02:33:27
... log-sink : 05/01/20 02:33:28
... log-sink : 05/01/20 02:33:29
... log-sink : 05/01/20 02:33:30
... log-sink : 05/01/20 02:33:31
... log-sink : 05/01/20 02:33:32

你看到每一秒钟的时间都被打印出来。返回到流窗格并销毁流。

恭喜你!您刚刚使用 Docker Compose 创建了您的第一个流time | log。现在,您可以使用以下命令关闭您的基础设施。

docker-compose -f docker-compose-rabbitmq.yml down

接下来,让我们在 Kubernetes 中部署 Spring CloudStream。

使用库比涅斯

在本节中,您将使用 Kubernetes 部署相同的"time | log"流。不使用仪表板,而是使用 Spring CloudStream 外壳。如果你有源代码,可以添加install.shuninstall.sh脚本来设置 Kubernetes 中的一切。我使用 Minikube 安装了 Spring CloudStream,但是您可以使用任何其他 Kubernetes 集群实例。

我用了ch08/kubernetes/workspace-rabbit-mysql-monitoring。它安装了 RabbitMQ 作为绑定器,MySQL 作为持久性,Grafana 和 Prometheus 跟踪我的应用的任何指标。

转到该文件夹并执行./install.sh脚本。该脚本执行与您在前面几节中遵循的相同的逐步说明。因为我使用了 Minikube,所以我执行了下面的代码。

minikube service scdf-server --url
http://192.168.64.6:30724

如果我打开浏览器,我需要指向http://192.168.64.6:30724/dashboard。我发现没有应用,我需要像以前一样做同样的程序。点击 +添加应用按钮。在这种情况下,我使用 Spring CloudStream shell。

使用 Spring CloudStream 外壳

我还没有向您展示这个特殊的工具。在这一节中,您可以先睹为快 Spring CloudStream 外壳。您正在注册应用并创建同一个简单的“time | log”流。

在独立部分中,download.sh包含 shell,但是如果您不记得它了,创建一个workspace-shell文件夹,并在该文件夹中执行下面的命令。

wget https://repo.spring.io/release/org/springframework/cloud/spring-cloud-dataflow-shell/2.4.2.RELEASE/spring-cloud-dataflow-shell-2.4.2.RELEASE.jar

然后,执行以下命令。

java -jar spring-cloud-dataflow-shell-2.4.2.RELEASE.jar
...
...
server-unknown:>

一个提示说,“server-unknown”。最好的命令之一是help。如果你输入help并按回车键,你会看到所有可用的命令。如果您输入help dataflow config server,您会看到所有支持连接到数据流服务器的参数。

接下来,在 shell 中执行以下内容。

server-unknown:>dataflow config server --uri http://192.168.64.6:30724
Successfully targeted http://192.168.64.6:30724
dataflow:>

现在您看到了dataflow提示符。如果您键入app list,您会看到以下内容。

dataflow:>app list
No registered apps.
You can register new apps with the 'app register' and 'app import' commands.
dataflow:>

这意味着你需要注册你的应用。之前,我列出了包含所有应用启动器描述的 URL。为此,你用 https://dataflow.spring.io/rabbitmq-docker-latest

接下来,键入app import并通过 URL 传递uri参数。

dataflow:>app import --uri https://dataflow.spring.io/rabbitmq-docker-latest
Successfully registered 66 applications from ...
dataflow:>

如果执行应用列表,您会看到列出了源、处理器和接收器应用。

Note

如果您需要或忘记任何参数,数据流外壳支持制表符结束。

接下来,让我们通过执行以下命令来创建流。

dataflow:>stream create --name simple --definition "time | log"
Created new stream 'simple'
dataflow:>

前面的命令创建了以“time | log”为定义的simple流。如果您执行stream list,,您会看到创建的流列表及其状态。

dataflow:>stream list
...
simple   |   |  time | log  |The app or group is known to the system, but is not currently deployed.
...
dataflow>

接下来,我们用下面的来部署一下。

dataflow:>stream deploy --name simple
Deployment request has been sent for stream 'simple'
dataflow:>

您可以查看以下状态

dataflow:>stream info --name simple
...

或者用

dataflow:>stream list

它应该说“已部署”。

如何查看日志?在 Kubernetes 中做这件事很酷的一点是,它创建了带有时间和日志应用的 pods,所以你可以打开一个新的终端并执行以下操作。

$ kubectl get pods
NAME                                  READY   STATUS    RESTARTS   AGE
grafana-5b89747547-h5x7l              1/1     Running   0          29m
mysql-5c59b756db-rggjw                1/1     Running   0          29m
prometheus-67896dcc8-s4qls            1/1     Running   0          29m
prometheus-proxy-86f5fd556b-6m9jn     1/1     Running   0          29m
rabbitmq-5bf579759d-c5rtj             1/1     Running   0          29m
scdf-server-5888868f84-qmwt2          1/1     Running   0          28m
simple-log-v1-5f88b7d546-g7cmb        1/1     Running   0          2m8s
simple-time-v1-5d7d4bc86f-gwq7w       1/1     Running   0          2m8s
skipper-66c685ff74-vc7b5              1/1     Running   0          29m

注意 Skipper 创建了simple-logsimple-time;它的命名惯例是<stream-name>-<app-name>-<version>-<pod-id>。现在,您可以查看以下日志。

$ kubectl logs -f pod/simple-log-v1-5f88b7d546-g7cmb
... log-sink  : 05/01/20 03:20:13
... log-sink  : 05/01/20 03:20:14
... log-sink  : 05/01/20 03:20:15
... log-sink  : 05/01/20 03:20:16

恭喜你!您使用数据流 shell 创建了您的流。在你摧毁溪流之前,让我们先看看格拉法纳。转到您的浏览器并打开http://192.168.64.6:30724/dashboard。你会看到所有注册的应用。转到“流”面板。在顶部,您可以看到 Grafana 仪表板按钮。单击它转到 Grafana 登录页面。用户名是admin,密码是password。请注意,您已经配置了应用、流和任务仪表板。环顾四周,你会看到一些数据(见图 8-19 和 8-20 )。

img/337978_1_En_8_Fig20_HTML.jpg

图 8-20。

grafana Streams:http://192 . 168 . 64 . 6:30018/d/scdf-Streams/Streams?orgId=1&refresh=10s

img/337978_1_En_8_Fig19_HTML.jpg

图 8-19。

Grafana 应用:http://192 . 168 . 64 . 6:30018/d/scdf-applications/applications?orgId=1&refresh=10s#/apps

现在,您可以密切监控您的流和应用。

是时候关闭一切了。若要删除该流,请在数据流外壳中执行以下操作。

dataflow:>stream destroy --name simple
Destroyed stream 'simple'
dataflow:>

如果您在终端中再次执行kubectl get pods,这个简单的 pod 就消失了。您可以使用exit命令退出数据流 shell。

dataflow:>exit

恭喜你!您使用 Spring CloudStream 外壳来注册应用并创建和销毁您的流。更多的动作即将到来,所以不要忘记你还在使用数据流外壳。

清理

如果你想快速清理,撤销你所做的安装,用delete替换create,与它们被创建的顺序相反。

kubectl delete -f server/server-deployment.yaml
kubectl delete -f server/server-svc.yaml
kubectl delete -f server/server-config.yaml
kubectl delete -f skipper/skipper-svc.yaml
kubectl delete -f skipper/skipper-deployment.yaml
kubectl delete -f skipper/skipper-config-rabbit.yaml
kubectl delete -f server/service-account.yaml
kubectl delete -f server/server-rolebinding.yaml
kubectl delete -f server/server-roles.yaml
kubectl delete -f grafana/
kubectl delete -f prometheus/prometheus-service.yaml
kubectl delete -f prometheus/prometheus-deployment.yaml
kubectl delete -f prometheus/prometheus-configmap.yaml
kubectl delete -f prometheus-proxy/
kubectl delete -f prometheus/prometheus-serviceaccount.yaml
kubectl delete -f prometheus/prometheus-clusterrolebinding.yaml
kubectl delete -f prometheus/prometheus-clusterroles.yaml
kubectl delete -f mysql/
kubectl delete -f kafka/
kubectl delete -f rabbitmq/

当然,移除服务、部署和 pod 的方法更多,比如使用提供的标签;例如,您可以删除 Grafana。

kubectl delete all,cm,svc,secrets -l app=grafana

或者您可以以相反的顺序执行前面的所有命令。使用文件声明(YAML)将会成功并删除服务,但最终,这取决于您喜欢什么。

摘要

本章解释了 Spring CloudStream 的重要性,并向您展示了如何创建您的第一个流。我们将在下一章进行更详细的讨论,我将向您展示 Spring CloudStream 的内部结构,以及如何从中获得更多。我还向您展示了设置 Spring CloudStream 的不同方法。有几个支持的平台,但 Kubernetes 是云基础设施和容器编排的事实上的技术。