SpringCloud 数据流教程(五)
九、Spring CloudStream 内部
在第八章中,您简要地看了一下 Spring CloudStream。本章深入讨论了这项技术能做什么。我回顾了您可以用流和批处理做什么,并向您展示了 Spring Cloud Task 如何使用 Spring Batch 创建有限的工作负载。
让我们开始挖掘 Spring CloudStream。
Spring CloudStream 架构
Spring CloudStream 编排和编排流式应用和批处理作业。它依靠云平台来管理我们应用的可扩展性和高可用性,以及底层基础设施(虚拟机、物理机)的弹性和容错能力。您需要关注业务逻辑。
Spring Cloud Data Flow 有几个组件用于编排和编排流式应用和批处理作业。它识别应用类型,并基于其 DSL(特定于域的语言)部署它,类似于执行 shell 命令时的类 Unix 语法。应用类型包括源、处理器、接收器和任务。我将在本章的后面介绍 Spring Cloud 任务。
在服务器端,Spring CloudStream 有两个主要组件。
- 数据流服务器。这个服务器公开了一个 REST API,它是仪表板和外壳的主要入口点。数据流服务器可以在几个云平台上运行,比如 Kubernetes、Cloud Foundry、Mesos、Yarn 和你的本地机器(独立运行或者使用 Docker 或 Docker Compose)。数据流服务器使用关系数据库(MySQL、PostgreSQL、Oracle、DB2、MS SQL Server、H2 或 HSQLDB)跟踪流及其状态。你只需要在
spring.datasource.*属性中声明正确的驱动程序。如果没有定义datasource属性,默认情况下,使用 H2 嵌入式数据库将所有内容保存在内存中。数据流服务器负责以下工作。-
注册 jar(独立的、HTTP 或在 Maven 坐标中)和 Docker 图像等工件
-
基于 DSL 解析流或批处理作业定义(这是类似 Unix 的 shell 命令(如
time | filter | transform | log)。您将在剩余的章节中了解更多关于 DSL 的知识。) -
使用关系数据库验证和保存流和任务应用以及批处理作业定义
-
将批处理作业定义部署到一个或多个云平台
-
查询任务和批处理作业执行历史
-
将流部署委托给 Skipper
-
将作业调度委托给云平台
-
当部署到云平台时,向流添加额外的信息,包括附加属性,例如应用的配置(端口、通道名称、使用 SpEL 的表达式、应用属性特定)、输入和输出、绑定器属性、要运行的实例、内存和 CPU 分配以及分区组
-
增加身份验证和授权的安全性(OAuth 2.0、SSO、LDAP 或添加您自己的安全提供者),通过 SSL 访问,并保护您的仪表板
-
尝试在端口 8888 连接到 Spring Cloud 配置服务器,但是您可以通过覆盖属性来使用自己的配置
-
配置 Maven 存储库和 Docker 注册表,以便轻松访问您的流和任务应用
-
默认情况下,使用端口 9393 提供广泛的 RESTful API 和 web 仪表板;它可以通过使用属性来重写
-
它可以在没有 Skipper 的情况下使用,但是您删除了升级或进行回滚的能力(进行蓝/绿部署的能力)、部署历史和一些其他功能。
-
Skipper 服务器。Skipper 服务器负责将流部署到一个或多个平台。它可以通过跟踪发布的流版本来进行升级和回滚。它使用状态机算法进行蓝绿色部署。您还可以为身份验证和授权添加安全性(OAuth 2.0、SSO 或添加您自己的安全提供者),并使用 SSL 进行安全通信。
Skipper 服务器使用关系数据库(可以使用 MySQL、PostgreSQL、Oracle、DB2、MS SQL Server、H2 和 HSQLDB)跟踪任何流及其状态和流的版本。你只需要在
spring.datasource.*属性中声明正确的驱动程序。如果没有定义datasource属性,默认情况下,使用 H2 嵌入式数据库将所有内容保存在内存中。它在端口 8888 上有一个到 Spring Cloud config 服务器的默认连接,但是您可以很容易地覆盖它并添加您自己的连接。
在客户端,Spring CloudStream 有几个组件。
- 仪表盘。仪表板是数据流服务器的一部分,它的服务器是端口 9393。它确实停止了对数据流服务器的调用。这是一个可视化流的 GUI(它使用了 Spring Flo 项目,
https://spring.io/projects/spring-flo)。通过 Flo,您可以利用仪表盘上的以下功能。-
使用 DSL 或画布创建、管理和监控您的流管道
-
注册您的应用,单个或批量
-
停止、启动和销毁流和任务应用
-
使用 Grafana 和 Prometheus(如果已配置)监控您的流和应用
-
查看您的流的分析,并使用正确的图表来表示您的数据流和逻辑
-
停止和恢复作业(仪表板和 Flo 是 Spring Batch GUI 版本的下一个迭代,用于管理任务和批处理作业)
-
使用内容辅助和自动完成功能
-
调整网格布局,以便更有效地查看和配置管道
-
查看分布式部署的可视化表示
-
为部署者添加元数据,例如 CPU、实例和内存
-
您可以在 https://github.com/spring-cloud/spring-cloud-dataflow-ui 获得数据流仪表盘的源代码。
图 9-1。
Spring CloudStream 组件
-
外壳。这是另一个通过 REST API 调用与数据流服务器通信的组件。它使用 Spring Shell 项目。以下是它的一些特点。
-
选项卡完成、着色和脚本执行
-
与 bean 验证 API 集成,因此您可以通过对象执行您的逻辑
-
基于特定于域的标准动态启用命令
-
与仪表板功能相同,但使用类似命令行的界面
-
轻松扩展到使用编程模型来创建自定义命令
-
-
Java 客户端。您可以使用
spring-cloud-dataflow-rest-client模块以编程方式创建您的流。它提供了StreamBuilder、StreamDefinition、Stream、StreamApplication和DataFlowTemplateDSL 类,这些类提供了流畅的 API 以便于开发(参见图 9-1 )。
在深入每个组件之前,让我们先讨论一下应用类型。你已经知道,符合流的 app 有 SpringCloudStream 和 SpringCloud 任务 app,但各有重要区别。
-
长寿命应用有输入和输出,需要一个代理进行通信。一些应用有多个输入和输出,在其他情况下,它们不需要代理来进行通信。大多数时候,你会发现那些长寿的应用是使用 Spring Cloud Stream 模块创建的。
-
短期应用有一个有限的持续时间。它们开始、处理和完成。这些应用是运行的任务,可以将它们的状态保存在数据流数据库中。通常使用 Spring Cloud 任务。这些应用的扩展使用 Spring Batch。它保存状态,从故障停止的地方重新启动,等等。
您不需要使用 Spring 来创建长期或短期的应用。你几乎可以使用任何编程语言。在创建这些应用时,您可以将它们打包成一个 Spring Boot·优步-JAR,可以使用 Maven(托管在 Maven 存储库中)作为文件或 HTTP 进行访问,或者作为托管在 Docker 注册表中的 Docker 映像进行访问。
客户端工具:cURL 命令、数据流外壳、Java 客户端、仪表板
在这一节中,我将通过创建简单的示例来介绍连接到 Spring Data 流服务器的所有可用方法,在接下来的章节中,您可以使用任何工具来实现复杂的解决方案。让我们首先启动并运行 Spring CloudStream 的基础设施(数据流服务器、Skipper 服务器、MySQL、RabbitMQ 或 Kafka );您可以使用 Docker Compose 或 Kubernetes。同样,让我们定义我们的管道 DSL。
在使用客户端工具之前,请确保您有这套额外的工具来帮助您。
-
jq(https://stedolan.github.io/jq/)处理 JSON 对象。 -
yq(https://mikefarah.gitbook.io/yq/)处理 YAML 对象。 -
httpie(https://httpie.org/)。HTTP 客户端。该工具依赖于 Python 3.6 或更高版本。如果这些工具对你没有帮助,安装
cURL命令。如果你用的是 Windows OS,可以从https://curl.haxx.se/windows/下载。也可以用 Postman (www.postman.com/downloads/)。
接下来,让我们通过本章来定义您使用的 DSL。将其称为电影流管道 DSL 或流管道(见图 9-2 )。
图 9-2。
电影流 DSL
图 9-2 以图表形式显示了流管道。请记住,每个区块都是独立于其他区块的 Spring Cloud Stream 应用。他们通过你选择的任何一个经纪人进行交流(RabbitMQ、Kafka、NATS 等)。).部署时,每个应用可以有一个或多个实例。由于 Spring CloudStream 服务器运行在云环境/云平台(Docker Compose—local 或 Kubernetes、Cloud Foundry、Mesos、Yarn)中,因此它具有可扩展性、高可用性、容错性以及对任何微服务架构的可见性(监控)。让我们回顾一下你对 Spring Cloud Stream 应用的了解。
http。首先,创建一个http应用来监听任何传入的请求。这是一个来源。您发送的样本数据在清单 9-1 中列出。
{
"movies": [
{
"id": "tt0133093",
"title": "The Matrix",
"actor": "Keanu Reeves",
"year": 1999,
"genre": "fiction",
"stars": 5
},
{
"id": "tt0209144",
"title": "Memento",
"actor": "Guy Pearce",
"year": 2000,
"genre": "drama",
"stars": 4
},
{
"id": "tt0482571",
"title": "The Prestige",
"actor": "Christian Bale",
"year": 2006,
"genre": "drama",
"stars": 3
},
{
"id": "tt0486822",
"title": "Disturbia",
"actor": "Shia LaBeouf",
"year": 2007,
"genre": "drama",
"stars": 3
}
]
}
Listing 9-1.Sample data
分析数据(你发送的是一个id,和一组电影,而不是一个)。
-
splitter。splitter是一个消息传递模式。根据给定的表达式,可以将一条消息分割成几部分,在这种情况下,您可以一次分割一部电影。在这种情况下,splitter应用产生四条消息。splitter是一个处理器。 -
groovy-transform。您在前面的章节中使用了这一点。这一次,groovy-transform应用通过外部系统获得“真实评级”。您使用 RapidAPI (https://rapidapi.com)网站,该网站提供了几个 API,包括 IMDB(互联网电影数据库,www.imdb.com),并发送 ID 以获得电影的全球评级。一旦它返回 IMDB 评级,它就会通过添加rating和ratingCount属性来增强我们的电影信息。groovy-transformapp 是另一个处理器。 -
filter。有两个filter应用:一个来自splitter应用,这意味着它接收与groovy-transform应用相同的消息,它使用star属性只允许超过三颗星的电影通过。另一个filter应用只允许高评分的电影通过(> 8.0)。filter应用是处理器应用。 -
jdbc。一个jdbc应用将电影信息保存在数据库中。该消息包括原始消息和由groovy-transform应用收集的新属性。这是一件非常简单的事情。这个jdbc应用是一个接收器。 -
log。log应用记录了从filter应用中释放的内容。请注意,您可以使用不同的接收器(兔子,HDFS,蒙哥,FTP,S3 等。).一个logapp 就是一个水槽。
如何为这个例子创建流管道?Spring CloudStream 提供了一种简单的方法来公开您的应用,并使用类似 Unix 的 DSL 语法将它们连接起来。Spring Cloud Stream 应用(你可以将它们视为 Unix 命令)可以通过使用|符号(Unix |管道)与其他应用进行通信,并使用>符号进行重定向。我们的例子应该很容易在流管道 DSL 中实现(参见清单 9-2 )。
movie = http | splitter | groovy-transform | jdbc
stars = :movie.splitter > filter | log
imdb-high-rating = :movie.groovy-transform > filter | log
Listing 9-2.Movie Stream Pipeline DSL Simple Form
清单 9-2 显示了电影流管道。我们来分析一下。
-
http | splitter | groovy-transform | jdbc。想象这是一个 Unix 命令(像cat myfile | grep "hello" | awk ‘{print $2}’ ...)。你看到一些相似之处了吗?你所说的是,使用http应用将消息发送到splitter应用,然后splitter应用将多条消息(一次一条)发送到收集一些信息的groovy-transform应用,它将消息发送到将记录保存到数据库中的jdbc应用。|符号用作下一个应用(Unix 管道)的连接器或信息通道。movie =声明可以被视为 DSL 及其定义的名称(就像一个名为 movie、值为http | splitter | groovy-transform | jdbc的变量,比如 Unix)。 -
splitter > filter | log。也就是说,从splitter获取消息的副本,并将其发送到filter应用,然后发送到log应用。>符号是一个重定向。不用太担心。当您使用>和:符号时,您会学到更多。stars = :move.splitter声明意味着您正在创建一个 tap(名为stars)。您将到达第一个定义movie =(或者引用变量movie,如果您想到 Unix shell 术语的话)并使用声明:movie.splitter访问splitter应用;这意味着您正在创建一个 wire tap (消息模式,消息的副本)并将其发送到带有>符号的filter应用。 -
groovy-transform > filter | log。这部分说从groovy-transform获取消息的副本(一旦它完成处理消息),发送到filter应用,然后发送到log应用。imdb-high-rating = :movie.groovy-transform声明意味着您正在创建一个 tap(名为imdb-high-rating),并且您正在到达第一个定义movie =并访问groovy-transform;这意味着你正在创建一个窃听装置,一旦groovy-transform返回,它就获取消息的副本,并将它发送到带有>符号的filter应用。
=和:符号(还有更多)具有我稍后在讨论标签、抽头和目的地时描述的含义;现在,把它们看作是命名流的一部分的方法,以及访问应用和应用其他符号的方法,就像 Unix shell 一样。
每个应用可以有多个属性(或者参数/自变量,如果您从 Unix shell 的角度考虑的话)。要么在 DSL 中声明它们,要么拥有一个外部属性文件(本地或远程云配置服务器)。您现在在同一个 DSL 中使用它们。
现在您已经了解了 DSL,是时候测试它并观察它的运行了。让我们从命令行开始。
使用 cURL、Httpie 和 jq
Spring CloudStream 服务器公开了一个 REST API,可以通过任何 REST 客户端或任何命令行实用程序进行编程访问,比如cURL、wget或Httpie。在这一节中,我将向您展示如何使用命令行实用程序创建管道 DSL。让我们一步一步来。
-
确保您有一个命令实用程序。我将使用
cURL和Httpie向您展示这些命令。因为 Spring CloudStream 服务器以 JSON 格式公开其数据,所以安装 jq 实用程序是值得的。 -
确保您已经运行了 Spring Cloud Data Flow 和 Spring Cloud Data Flow Skipper 服务器。您可以使用任何方法——使用 Docker Compose 的本地设置或 Kubernetes 集群。通过使用一个
LoadBalancer类型公开 Spring CloudStream 服务器,确保您可以到达。默认情况下,它使用端口 9393 来公开 REST API 和 GUI 的路径/dashboard;但是你必须看服务和港口(例如,kubectl get svc -o wide)。(我用的是 Docker Compose,用 RabbitMQ 和 MySQL 做持久化。我正在测试localhost。$ curl -s http://localhost:9393 | jq .Use Httpie if you don’t need to use the jq tool to format the output.
$ http :9393如果使用的是本地基础设施(比如 Docker Compose),就不需要使用服务器名;默认情况下,它会转到本地主机。使用前面的命令,您可以看到所有公开的 API,所以现在您知道该做什么了。
-
使用查看是否已经注册了应用
$ curl -s http://localhost:9393/apps | jq .or
$ http :9393/apps如果没有任何 app,可以进入下一步;如果有可以跳过。
-
注册应用。根据您使用的代理和基础设施的类型,选择以下选项之一。
-
使用 Maven
-
Using Docker
-
https://dataflow.spring.io/task-docker-latestYou can execute the following command (I’m using Maven and RabbitMQ).
$ curl -s -X POST \ -d "uri=https://dataflow.spring.io/rabbitmq-maven-latest" \ -d "force=true" \ localhost:9393/apps | jq .Or you can use this next one.
$ http -f POST \ :9393/apps \ uri=https://dataflow.spring.io/rabbitmq-maven-latest \ force=true通过执行上一步中的命令,确定应用是否已注册。
-
-
有一个
jdbcapp,这意味着所有的电影对象都被发送到这个 sink,它需要准备好接受传入的行。你需要建立数据库。访问 MySQL 数据库非常重要。如果您使用的是 Kubernetes 和一个不同于数据流或 Skipper 使用的实例,则必须将正确的服务器名称添加到属性中,并确保您可以访问它。如果您使用的是 Docker Compose(以及本书源代码中的docker-compose.yml文件),您可以通过以下方式获得访问权限。$ docker exec -it dataflow-mysql -uroot -prootpwYou need to create the
reviewsdatabases and themoviestable .mysql> create database reviews; mysql> use reviews mysql> create table movies( id varchar(10) primary key, title varchar(200), actor varchar(200), year int, genre varchar(25), stars int, rating decimal(2,1), ratingcount int); -
其中一个流式应用是
groovy-transform。这个应用调用外部 REST API。你使用 RapidAPI (https://rapidapi.com/)网站。请,花点时间开个新账户(免费);要使用的服务是https://rapidapi.com/apidojo/api/imdb8。您需要为此获取 API 密钥。您使用的最后一个 URL 是https://imdb8.p.rapidapi.com/title/get-ratings?tconst=${movie.id},在这里您传递电影的 ID。一旦你得到了你的 API 密匙,把它写下来,因为你接下来会用到它。 -
groovy-transform应用需要一个脚本来执行。这个脚本在您的 Git 存储库中(您可以使用任何 Git 服务器—GitHub、GitLab、BitBucket 等。).为了方便起见,它必须是公共的。创建一个新的 repo 并添加以下 Groovy 脚本;将其命名为movie-transform.groovy(见清单 9-3 )。
import groovy.json.JsonSlurper
import groovy.json.JsonOutput
def jsonSlurper = new JsonSlurper()
def movie = jsonSlurper.parseText(new String(payload))
def connection = new URL( "https://imdb8.p.rapidapi.com/title/get-ratings?tconst=${movie.id}")
.openConnection() as HttpURLConnection
connection.setRequestProperty( 'x-rapidapi-host', 'imdb8.p.rapidapi.com' )
connection.setRequestProperty( 'x-rapidapi-key', 'YOURKEY')
connection.setRequestProperty( 'Accept', 'application/json' )
connection.setRequestProperty( 'Content-Type', 'application/json')
if ( connection.responseCode == 200 ) {
def imdb = connection.inputStream.withCloseable { inStream ->
new JsonSlurper().parse( inStream as InputStream )
}
movie.imdb = [ "rating": imdb.rating, "ratingCount": imdb.ratingCount ]
} else {
println connection.responseCode + ": " + connection.inputStream.text
}
JsonOutput.toJson(movie)
Listing 9-3.movie-transform.groovy
在继续之前,请分析脚本。请注意,它会转到 RapidAPI URL 并获取信息。它通过添加包含rating和ratingCount属性的imdb部分来增强消息。这些属性来自对外部调用的响应。另外,请注意,您需要在x-rapidapi-key标题中添加您的密钥,并使用提到的 URL。以原始格式访问这个脚本很重要。比如在 GitHub 中,可以在 https://raw.githubusercontent.com/<your-user-id>/<your-repo>/master/movie-transform.groovy 访问。请注意这个 URL。
-
是时候创建电影管道 DSL 了。这是您使用的最终版本(参见清单 9-4 )。
movie=http --port=9001 | splitter --expression="#jsonPath(payload, '$.movies')" | groovy-transform --script="https://raw.githubusercontent.com/<user>/<repository>/master/movie-transform.groovy" | jdbc --columns="id:id,title:title,actor:actor,year:year,genre:genre,stars:stars,rating:imdb.rating,ratingcount:imdb.ratingCount" --table-name="movies" --password="rootpw" --driver-class-name="org.mariadb.jdbc.Driver" --username="root" --url="jdbc:mysql://mysql:3306/reviews?autoReconnect=true&useSSL=false" stars=:movie.splitter > filter --expression="#jsonPath(payload,'$.stars') > 3" | log imdb-high-rating=:movie.groovy-transform > filter --expression="#jsonPath(payload,'$.imdb.rating') > 8.0" | log Listing 9-4.Movie Pipeline DSLThis movie pipeline DSL is composed of three statements.
-
movie = http | splitter | groovy-transform | jdbc。movie是流的名称,在接下来的语句中很有用。注意,这个定义使用了像--port这样的属性,因为它在寻找一个本地基础设施。如果您使用 Kubernetes 方法,您需要向http应用添加一个带有LoadBalancer类型的服务来获得一个 IP 并访问它。splitter应用根据movies集合将消息分割成几个movie对象。JSON send 包含一组movies。groovy-transform的脚本属性指向 Git 存储库/服务器中的原始文件(确保它是一个原始文件)。最后,它转到jdbc应用,该应用将增强的电影对象(JSON)从groovy-transform保存到review数据库和movies表中。再看--columns属性。它定义了映射 JSON 结果的表列。 -
stars = :movie.splitter > filter | log。stars是流的名称。你稍后会看到这个,因为你称它为龙头。:movie.splitter说的是“在movie流和splitter应用之后创建一个窃听”——换句话说,一旦你做了分割,就获得一份信息的副本。你从movies(JSON)的集合中收集一个movie对象(JSON)的副本;然后,您将那个movie对象(JSON)传递给filter应用。请注意,您使用的是>符号而不是|,因为这是点击消息所需的语法。tap 是 wire tap 消息集成模式实现。filter应用使用一个 JSON 表达式来获取星星,如果大于三个,它会将其传递给 log 应用。日志应用写入控制台。 -
imdb-high-rating = :movie.groovy-transform > filter | log。imdb-high-rating是流的名称,它在groovy-transform应用处获得电影流的副本,并重定向到评估imdb.rating,的filter,如果它大于 8.0,则将其传递给log应用。log应用写入控制台。You execute three different commands, one per statement.
$ curl -s -X POST \ --form 'name=movie' \ --form 'definition=movie=http --port=9001 | splitter --expression="#jsonPath(payload,'\''$.movies'\'')" | groovy-transform --script=https://raw.githubusercontent.com/<user>/<repo>/master/movie-transform.groovy | jdbc --columns=id:id,title:title,actor:actor,year:year,genre:genre,stars:stars,rating:imdb.rating,ratingcount:imdb.ratingCount --table-name=movies --password=rootpw --driver-class-name=org.mariadb.jdbc.Driver --username=root --url=jdbc:mysql://mysql:3306/reviews?autoReconnect=true&useSSL=false' \ localhost:9393/streams/definitions | jq . $ curl -s -X POST \ --form 'name=stars' \ --form 'definition=stars= :movie.splitter > filter --expression="#jsonPath(payload,'\''$.stars'\'') > 3" | log' \ http://localhost:9393/streams/definitions | jq . $ curl -s -X POST \ --form 'name=imdb-high-rating' \ --form 'definition=imdb-high-rating= :movie.groovy-transform > filter --expression="#jsonPath(payload,'\''$.imdb.rating'\'') > 8.0" | log' \ http://localhost:9393/streams/definitions | jq .在每个命令中,您都需要
name和definition参数。不要忘记为您的 Git 服务器使用您自己的user和repository。如果你复制这段文字,一定要注意单引号和双引号以及单引号之间的区别。记住,我使用的是本地 Docker Compose。这就是为什么 http 应用中的第一个 DSL 流定义有
--port=9001,这意味着我发布了一些指向该端口的电影。如果你正在使用 Kubernetes,你不需要(你可以删除那个属性),然后确保你可以通过给它添加一个带有LoadBalancer类型的服务或者转发端口来到达http应用。
-
Note
在本书的配套源代码的ch09/streams文件夹中,有一个名为curl-movie-stream-pipeline的文件,其中包含正确的字符。您可以复制它们或执行文件。
-
通过执行以下命令来验证我们的定义是否存在。
$ curl -s localhost:9393/streams/definitions | jq .在这个命令中,您可以看到所有的流定义:
movie、stars、imdb-high-rating。 -
部署流。为此,您需要执行以下命令。
$ curl -s -X POST \ http://localhost:9393/streams/deployments/movie | jq . $ curl -s -X POST \ http://localhost:9393/streams/deployments/stars | jq . $ curl -s -X POST \ http://localhost:9393/streams/deployments/imdb-high-rating | jq . -
Now that the movie stream is deployed, it is time to send some data.
$ curl -s -X POST \ -H "Content-Type: application/json" \ -d '{"movies":[{"id":"tt0133093","title":"The Matrix","actor":"Keanu Reeves","year":1999,"genre":"fiction","stars":5},{"id":"tt0209144","title":"Memento","actor":"Guy Pearce","year":2000,"genre":"drama","stars":4},{"id":"tt0482571","title": "The Prestige","actor":"Christian Bale","year":2006,"genre":"drama","stars":3},{"id":"tt0486822","title":"Disturbia","actor":"Shia LaBeouf","year":2007,"genre":"drama","stars":3}]}' \ http://localhost:9001 | jq .Note that you are sending a set of movies. If you are using Docker Compose (and
docker-compose.ymlfrom the source code), the Skipper server is where all the apps are running. To send the data, you need to enter the following in theskipperdocker container.$ docker exec -it skipper bash $ curl -s -X POST ....如果你用的是 Kubernetes,需要暴露
httpapp 或者做一个端口转发然后做 POST。 -
看看日志。要查看是否一切正常,请获取应用的运行时信息。执行以下命令。
$ curl -s localhost:9393/runtime/apps | jq .This command gives you all the information about the logs (for the
starsand theimdb-high-rating streams). You should find the name (deploymentId) asstars.log-v1andimdb-high-rating.log-v1and thestdoutproperty. If you are using the Docker Compose (anddocker-compose.ymlfrom the book’s source code), then you must go to theskipper-dockercontainer and do a tail over the path from thestdoutproperty.$ docker exec skipper tail -n 500 -f /tmp/1590548505908/stars.log-v1/stdout_0.logIf you are using Kubernetes, you should see all the apps as pods, with the same naming convention; to see the logs for
start-logs-v1, you should execute the following.$ kubectl get pods $ kubectl logs -f pod/star-logs-v1-xxxxxxxx 是添加到 pod 的 ID。
If you have access to the MySQL database, you should see something similar to the following.
mysql> select * from movies \G *************************** 1\. row *************************** id: tt0133093 title: The Matrix actor: Keanu Reeves year: 1999 genre: fiction stars: 5 rating: 8.7 ratingcount: 1609934 *************************** 2\. row *************************** id: tt0209144 title: Memento actor: Guy Pearce year: 2000 genre: drama stars: 4 rating: 8.4 ratingcount: 1084378 *************************** 3\. row *************************** id: tt0482571 title: The Prestige actor: Christian Bale year: 2006 genre: drama stars: 3 rating: 8.5 ratingcount: 1136643 *************************** 4\. row *************************** id: tt0486822 title: Disturbia actor: Shia LaBeouf year: 2007 genre: drama stars: 3 rating: 6.8 ratingcount: 214786
恭喜你!您使用 REST API 创建了一个电影流管道 DSL。
如果您想要取消部署您的流,您可以使用下面的命令一个接一个地完成。
$ curl -s -X DELETE \
http://localhost:9393/streams/deployments/movie | jq .
$ curl -s -X DELETE \
http://localhost:9393/streams/deployments/stars | jq .
$ curl -s -X DELETE \
http://localhost:9393/streams/deployments/imdb-high-rating | jq .
或者同时使用以下内容。
$ curl -s -X DELETE \
http://localhost:9393/streams/deployments | jq .
如果您想删除流,您可以一次删除一个,如下所示。
$ curl -s -X DELETE \
http://localhost:9393/streams/definitions/movie | jq .
$ curl -s -X DELETE \
http://localhost:9393/streams/definitions/stars | jq .
$ curl -s -X DELETE \
http://localhost:9393/streams/definitions/imdb-high-rating | jq .
或者一次完成,如下所示。
$ curl -s -X DELETE \
http://localhost:9393/streams/definitions | jq .
接下来,交互式地使用 Spring CloudStream shell,更实际地创建流。
使用 Spring CloudStream 外壳
创建流有更多的选项,在本节中,您将看到 Spring CloudStream 外壳的运行。你可以把这个客户端看作一个交互式的基于文本的/终端的工具,在这里你可以做测试。首先,确保您删除了任何流 DSL 定义,并停止/重启您的 Spring CloudStream 服务器,因为您需要再次注册所有应用。
使用 Spring CloudStream 外壳有两种选择。如果您的基础设施使用本地部署,比如 Docker Compose ( ch09/docker-compose),您可以重用 Spring CloudStream 服务器。启动基础设施后,可以按如下方式使用数据流外壳。
$ docker exec -it dataflow-server java -jar shell.jar
你的屏幕应该看起来如图 9-3 所示。
图 9-3。
Spring CloudStream 外壳
另一个选择是从 https://repo.spring.io/release/org/springframework/cloud/spring-cloud-dataflow-shell/2.6.0/spring-cloud-dataflow-shell-2.6.0.jar 下载优步罐。下载后,执行下面的命令。
$ java -jar spring-cloud-dataflow-shell-2.6.0.jar --help
该命令显示了您可以覆盖的所有参数(参见图 9-4 )。
图 9-4。
带- help 参数的 Spring CloudStream 外壳
在继续之前,检查所有参数。数据流外壳(客户端)有默认值,比如试图连接到localhost:9393的uri参数。
现在再次执行该命令,但不带--help参数。
$ java -jar spring-cloud-dataflow-shell-2.6.0.jar
如果您仍在使用 Docker Compose 和 exposed 端口 9393,则连接成功,但如果您的数据流服务器运行在 Kubernetes 中,则必须为该服务器分配 IP 或域。因为连接localhost:9393失败,所以给你显示一个server-unknown提示。在这种情况下,您可以使用以下命令。
server-unknown:>dataflow config server https://my-scdf-server
它伸出手,连接到你的数据流服务器(见图 9-5 )。
图 9-5。
Spring CloudStream 外壳:服务器未知
如果您使用 Docker Compose,我推荐使用下面的命令来启动数据流 shell,因为它更容易将数据发送到我们的http应用。
$ docker exec -it dataflow-server java -jar shell.jar
使用优步-JAR 需要暴露 Skipper 服务器端口 9001,这意味着您需要在docker-compose.yml文件中添加ports属性。
数据流 shell 提供了 60 多个带制表符补全的命令,允许您查看、列出、创建、删除、销毁以及更多关于流和任务的 Uri。Spring CloudStream shell 是一个 REST API 客户端,但是比简单的cURL或Httpie命令行实用程序具有更大的容量。
让我们从查看一些可以从数据流外壳执行的命令开始。我认为您在数据流 shell 中最好的朋友之一是help命令。键入 help 并按回车键,您会看到您可以执行的 60 多个命令(参见图 9-6 )。
图 9-6。
Spring CloudStream 外壳:使用帮助命令
dataflow:>help
您也可以使用help [command [options]]语法。例如,您使用stream命令,这样您就可以进行输入。
dataflow:>help stream
前面的命令显示了stream命令的可用选项列表,您可以执行:
dataflow:>help stream create
分析输出,注意它附带了创建流所需的描述。数据流 shell 附带了TAB完成功能,因此您可以执行stream命令并双击TAB键来获得可用选项,如all、info、create、list、deploy、undeploy等等。
当您开始创建流时,您会看到每个命令都有选项,这些选项都有需要以双破折号开头的参数。举个例子,
dataflow:>stream deploy --name=movie
有时你需要用单引号(')或双引号(")来传递值,你需要非常小心。当然,你会发现你需要躲避一些角色,但是不要担心,时间到了。我告诉你怎么做。其他规则在这里没有涉及,但是你可以在 https://docs.spring.io/spring-cloud-dataflow/docs/current/reference/htmlsingle/#_shell_rules 阅读。
Spring Data 流外壳的另一个好处是它解析 DSL 的方式。您必须选择一种有效的方法来添加您的表达式,而不是组合它们。例如,filter应用需要传递一个表达式参数(正如您之前看到的cURL / Httpie命令),并且您必须选择以下任意一个。
filter --expression=payload>5.0
filter --expression=#jsonPath(payload,'$.imdb.rating')>5.0
filter --expression='#jsonPath(payload,''$.imdb.rating'') > 5.0'
前面的表达式使用了 SpEL (Spring Expression Language)的#jsonPath和通用对象,比如payload。注意空格和单引号。没有双引号。您仍然可以添加双单引号。此外,您可以用反斜杠\来转义字符。你可以在这里得到更多的感觉, https://docs.spring.io/spring-cloud-dataflow/docs/current/reference/htmlsingle/#_dsl_parsing_rules 和这里 https://docs.spring.io/spring-cloud-dataflow/docs/current/reference/htmlsingle/#_spel_syntax_and_spel_literals 。
谈到属性,您可以使用外部属性文件传递所有参数。通常,该文件可以是 JAR 被执行的地方。参数不应带有任何单引号或双引号。这些属性的语法是app.<app-name>.<parameter>=<value>。例如,
app.filter.expression=#jsonPath(payload, '$imdb.rating') > 5.0
为了更好地理解这些特性,让我们从创建相同的电影流管道 DSL 开始。
-
注册应用。在提示符下,执行以下命令。
dataflow:>app import --uri https://dataflow.spring.io/rabbitmq-maven-latestRemember that you need the
http,filter,jdbc,log,splitter,andgroovy-transformapps. Also, you need to use one broker, in the preceding command I’m assuming you are using RabbitMQ. You can use Kafka as well. Once you import them, you can have them listed withdataflow:>app list -
创建电影流。首先,让我们看看溪流
dataflow:>stream listIt should be empty. Next, let’s add the first stream. Don’t forget to change your Git server’s
userandrepository.dataflow:> stream create --name movie --definition "http --port=9001 | splitter --expression=#jsonPath(payload,'$.movies')| groovy-transform --script=https://raw.githubusercontent.com/<user>/<repo>/master/movie-transform.groovy | jdbc --columns=id:id,title:title,actor:actor,year:year,genre:genre,stars:stars,rating:imdb.rating,ratingcount:imdb.ratingCount --table-name=movies --password=rootpw --driver-class-name=org.mariadb.jdbc.Driver --username=root --url=jdbc:mysql://mysql:3306/reviews?autoReconnect=true&useSSL=false" dataflow:> stream create --name stars --definition ":movie.splitter > filter --expression=\"#jsonPath(payload,'$.stars')>3\" | log" dataflow:> stream create --name imdb-high-rating --definition ":movie.groovy-transform > filter --expression=\"#jsonPath(payload,'$.imdb.rating') > 8.0\" | log"In the scaped characters, note the
\". You can look at the definitions with the following.dataflow:> stream list信息中没有转义字符。
-
It’s time to deploy. Execute the following commands.
dataflow:>stream deploy --name movie dataflow:>stream deploy --name stars dataflow:>stream deploy --name imdb-high-ratingYou can look see the status using the following.
dataflow:> stream info --name movie dataflow:> stream info --name stars dataflow:> stream info --name imdb-high-rating状态应该是已部署。
-
It’s time to send some data. The Spring Data Flow shell comes with a REST API client that you can use right there in the shell. The command is
http. You can usehelp httpto find out which parameters are important to send information. To send some data, you can execute the following command.dataflow:> http post --target http://<change-me>:9001 --data '{"movies":[{"id": "tt0133093","title": "The Matrix","actor": "Keanu Reeves", "year": 1999,"genre":"fiction","stars": 5},{"id": "tt0209144","title": "Memento","actor": "Guy Pearce","year": 2000,"genre": "drama","stars": 4},{"id": "tt0482571","title": "The Prestige","actor": "Christian Bale","year": 2006,"genre": "drama","stars": 3 },{"id": "tt0486822","title": "Disturbia","actor": "Shia LaBeouf","year": 2007,"genre": "drama","stars": 3}]}' --contentType "application/json"如果你查看
http post命令,有一个--target参数。这是http应用运行的地方;不可能是localhost。如果你用的是 Kubernetes,那很容易,因为你要用一个LoadBalancer类型暴露httpapp;但是如果你使用的是来自书的源代码(ch09/docker-compose)的 Docker Compose (docker-compose.yml),你需要确保skipper服务器是可达的;要么使用ports并在docker-compose.yml中暴露"9001:9001"(在skipper服务中),要么使用dataflow-server中的docker shell.jar。换句话说,您需要知道哪个 IP 被分配给了skipper容器。To get the IP of the
skippercontainer, you can run.$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' skipper -
该检查日志了。为此,您需要使用运行时应用命令。
dataflow:>runtime appsThis command shows you the
name,status,and some other properties such asstdoutandstderr. You are interested in thestdoutproperty that is showing the logs. If you are using Docker Compose, then you can execute the following command.$ docker exec skipper tail -n 500 -f /tmp/1590548505908/stars.log-v1/stdout_0.logIf you are using Kubernetes, it is easier; check out the pods and use the logs command.
$ kubectl get pods $ kubectl logs start-log-v1-xxxx -
查看 MySQL 数据库并执行 SELECT 语句。
恭喜你!您使用了 Spring CloudStream 外壳。
To remove and delete everything, you can use the same
streamcommand .dataflow:> stream destroy --name imdb-high-rating dataflow:> stream destroy --name stars dataflow:> stream destroy --name movie
现在,您可以使用 exit 命令退出。如果你愿意,你可以关闭你的基础设施。接下来,让我们使用仪表板再做一次。
使用仪表板
尽管您已经了解了 dashboard,但是现在是时候使用它来部署电影流管道 DSL 了。对于这一节,从头开始就好,所以请停止/重启你的基础设施;要么使用 Docker Compose,要么使用 Kubernetes 实例。如果你正在使用 Kubernetes,试着重建 MySQL 数据库,这样它就不会有应用和流的记录。我们开始吧。
-
确保 Spring CloudStream 和 Skipper 服务器已经启动并运行。如果您使用的是 Kubernetes,请确保使用 LoadBalancer 类型公开 Spring CloudStream 服务器,这样您就可以有一个物理 IP 地址进行访问。另外,不要忘记在 MySQL 实例中创建
reviews数据库和movies表。 -
Open your browser and go to http://:[9393]/dashboard (see Figure 9-7).
图 9-7。
仪表盘
图 9-7 为仪表板。在左窗格中,您会发现以下内容。
-
应用。这是你注册所有可用应用的地方,包括我们自己的(你稍后会看到)。请记住,应用基于活页夹和 Maven 或 Docker 坐标。
-
运行时(您可以看到所有部署)
-
流
-
任务
-
乔布斯
-
审计记录
-
-
让我们注册应用。请记住,您可以在 Rabbit 或 Kafka 以及 Maven 或 Docker 坐标之间进行选择。稍后,您将看到如何使用我们的定制流式应用和 NATs broker,并与其他应用一起使用。
Click the Apps pane, and in the right pane, click the + Add Application(s) button/link. Then, select the Bulk import application coordinates from an HTTP URI location option. Then, you can pre-fill the URI with one of the items in the list by clicking it. Choose either Rabbit or Kafka (depending on your primary broker) and either Maven or Docker. Then you can click Import application(s). This imports the apps, and it takes you to the main page. You should have something like Figure 9-8.
图 9-8。
仪表板:导入的应用
-
In the left pane, click the Streams option. Then in the right pane, click the + Create Stream(s) button/link . This take you to the GUI part where you can drag-n-drop apps into the canvas or you can write the DSL. To make things easier, you can add the following DSL.
movie=http --port=9001 | splitter --expression="#jsonPath(payload, '$.movies')" | groovy-transform --script="https://raw.githubusercontent.com/felipeg48/scdf-scripts/master/movie-transform.groovy" | jdbc --columns="id:id,title:title,actor:actor,year:year,genre:genre,stars:stars,rating:imdb.rating,ratingcount:imdb.ratingCount" --table-name="movies" --password="rootpw" --driver-class-name="org.mariadb.jdbc.Driver" --username="root" --url="jdbc:mysql://mysql:3306/reviews?autoReconnect=true&useSSL=false" stars=:movie.splitter > filter --expression="#jsonPath(payload,'$.stars') > 3" | log imdb-high-rating=:movie.groovy-transform > filter --expression="#jsonPath(payload,'$.imdb.rating') > 8.0" | logNote that you added enclosing double quotes per parameter. When you add this pipeline DSL, you see the graph displayed (see Figure 9-9).
图 9-9。
仪表板:电影管道 DSL
You can now play with it by removing some of the components, or in the DSL area, you can remove all the properties and have in plain sight what the DSL looks like without parameters (see Figure 9-10).
图 9-10。
仪表板:不带参数的 DSL
movie= http | splitter | groovy-transform | jdbc stars= :movie.splitter > filter | log imdb-high-rating= :movie.groovy-transform > filter | logIf you want to add parameters without adding them to the DSL area because you want to avoid any double/single quote confusion, you can select any app from the graph, and it shows you the Options and Delete links. You can select Options. A pop-up window appears, where you can add the parameter value. In the case of the
httpapp, you can add port value 9001 (see Figures 9-11 and 9-12).图 9-12。
仪表板:http 应用参数
图 9-11。
仪表板:已选择 http 应用
如果您单击 Update,它会更新 DSL,您会在 DSL 区域看到
--port=9001。 -
Add the DSL where you have all the parameters set. Click the Create Stream(s) button, which opens a pop-up where you need to add any description (this is optional) and a name that by default is the one you added already into the DSL (see Figure 9-13).
图 9-13。
仪表板:创建流
You can click Create the three streams, which takes you back to where the streams are listed. In that list, you see the streams’ definitions. At the end of every row are the status and three icons. The first icon shows the stream’s details. The second icon (the Play icon) deploys the stream, and the last icon (a caret facing down) shows you options like Show details, Deploy, Undeploy, Destroy stream. If you click the > icon next to each stream’s definition, you find a graph related to the stream (see Figures 9-14, 9-15, and 9-16).
图 9-16。
仪表板:流图
图 9-15。
仪表板:流图标
图 9-14。
仪表板流列表
-
It’s time to deploy one by one. You start first with the
moviestream. Then you can select any of the other in any order. In the end, thestarsandimdb-high-ratingstreams depend on themovieto start first. You can click the Play icon to deploy. When you do, another pop-up appears where there are extra settings that can help with the deployment, and there are more related to the platform where the streams are deployed. Also, you have another chance to change the existing parameters if you needed them to (see Figure 9-17).图 9-17。
仪表板:流-部署属性
The parameters are separated by the following.
-
Platform。通常,您可以选择部署哪个平台,但这是一个混合选项,可以部署到 Cloud Foundry、Kubernetes 或本地实例。 -
Generic deployer。您可以在其中添加一些资源约束,以便为任何其他应用节省资源,如内存、CPU、磁盘和实例数量。 -
Deployment platform。它与 Spring Cloud 和 Java VM 属性相关,比如 java-opts、debug-port、java-command(一个带有特殊标志来运行 Java 的命令)、端口范围等等。 -
Application properties。在那里你为应用定义了所有的参数,比如http port、jdbc table-name等等。Note that you have the same properties for each app defined in the
movie stream. And if you are okay with everything, click the Deploy Stream button. This takes you to the main stream list. The Status column shows that your stream is being deployed (see Figure 9-18).
图 9-18。
仪表板:流列表–状态:正在部署
You can repeat the same for the
starsandimdb-high-ratingstreams (see Figure 9-19).图 9-19。
仪表板:流列表
-
-
Once all the streams have a Deployed status, there are several ways to see information about your app. Click
movie stream. The information icon (i) shows the details (see Figure 9-20).图 9-20。
仪表板:显示细节-摘要-电影流
If you scroll down, you get more information about every app defined in the movie stream, and if you keep going scrolling down, you can see the logs. You have a dropdown list and choose the logs you want to see. At the top of this page, you see the Summary, Graph, and History tabs. You can check them out. The graph has the entire DSL definition, including the taps (see Figure 9-21).
图 9-21。
仪表板:显示详细信息-摘要-电影流-日志
-
Another way to see more information is to get into the runtime. Click Runtime in the left pane. It shows you everything about your apps, such as process ID, port, instance name, version, and logs for
stdoutandstderr. You can click any of the boxes (see Figures 9-22 and 9-23).图 9-23。
仪表板:运行时–流式应用
图 9-22。
仪表板:运行时–流式应用,命名约定---
-
是时候发一些数据了。这里您需要使用任何 REST 客户端。你可以使用 cURL 命令,或者如果你喜欢图形界面,你可以使用 Postman (
www.postman.com/downloads/)或者失眠症(https://insomnia.rest/)来发送数据。您需要发送之前的 JSON 数据(参见清单 9-1 )。 -
例如,您可以返回到流列表并单击
star流的详细信息。在摘要中,向下滚动到日志并选择stars.log-v1,,在底部,您应该会看到一些关于您刚刚发送的数据的结果。您还可以检查 MySQL 数据库,看看记录是否在那里。
恭喜你!您已经使用仪表板部署了电影流 DSL。
要删除流,请转到流列表,选择流,单击上下颠倒的插入符号,然后选择销毁流。出现提示后,如果您确定,点击销毁流定义(见图 9-24 )。
图 9-24。
仪表板:销毁流
仪表板中有更多的特性,我将在后面的任务、作业和监控中讨论。
以编程方式创建流
创建和部署流的另一种方法是通过 Spring CloudStream 模块公开的 Java DSL APIs。使用这种方法,您可以根据您的业务需求,创建一种动态的方法来集成流的生命周期。本节将向您展示如何使用这个 API 及其关键类。
Spring CloudStream API 公开了两种风格的 Java DSL APIs。
-
定义风格。这种风格允许您使用已经在前面章节中部署的 DSL 定义。例如,
Stream.builder(dataFlowOperations) .name("simple-stream") .definition("http | log") .create(); -
流畅的风格。这种风格提供了一种使用流畅的方法链(如
source.processor.sink)来创建流式应用的方式,以开发更动态的解决方案。例如,Stream.builder(dataFlowOperations) .name("simple-stream") .source(httpSource) .sink(logSink) .create();
Java DSL API:定义风格
让我们使用 Spring CloudStream Java DSL API 创建电影流管道 DSL,从定义样式开始。
图 9-25。
Spring Initializr 电影-dsl 项目
-
Open your browser and point to
https://start.spring.io. Complete the metadata with the following information.-
组:
com.apress.cloud.stream -
神器:
movie-dsl -
包名:
com.apress.cloud.stream.movie -
依赖:CloudStream,龙目岛
单击生成按钮下载 ZIP 文件。您可以在您选择的 IDE 中解压缩并导入它(参见图 9-25 )。
-
-
Open the
pom.xmlfile and add the following and required dependency.<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dataflow-rest-client</artifactId> <version>2.6.0</version> </dependency>在撰写本书时,版本是
spring-cloud-rest-client依赖项的2.6.0。这个模块提供了新的类,我将很快讨论这些类。 -
创建一个
MovieDslStream枚举。它保存了关于定义及其名称的信息(参见清单 9-5 )。
package com.apress.cloud.stream.movie;
public enum MovieDslStream {
MOVIE("movie",
"http --port=9001 | splitter --expression=\"#jsonPath(payload, '$.movies')\" | " +
"groovy-transform --script=\"https://raw.githubusercontent.com/felipeg48/scdf-scripts/master/movie-transform.groovy\" | " +
"jdbc --columns=\"id:id,title:title,actor:actor,year:year,genre:genre,stars:stars,rating:imdb.rating,ratingcount:imdb.ratingCount\" " +
"--table-name=\"movies\" --password=\"rootpw\" --driver-class-name=\"org.mariadb.jdbc.Driver\" --username=\"root\" " +
"--url=\"jdbc:mysql://mysql:3306/reviews?autoReconnect=true&useSSL=false\""),
STARS("stars",":movie.splitter > filter --expression=\"#jsonPath(payload,'$.stars') > 3\" | log"),
IMDB("imdb-high-rating",":movie.groovy-transform > filter --expression=\"#jsonPath(payload,'$.imdb.rating') > 8.0\" | log");
private String name;
private String definition;
MovieDslStream(String name, String definition){
this.name = name;
this.definition = definition;
}
public String getName(){
return this.name;
}
public String getDefinition() {
return this.definition;
}
}
Listing 9-5.src/main/java/com/apress/cloud/stream/movie/MovieDslStream.java
清单 9-5 显示了MOVIE、STARS、IMDB枚举类型和它们的两个值:name ( getName()方法)和definition ( getDefinition()方法)。
- 创建保存一个字段的
MovieDslProperties(参见清单 9-6 )。
package com.apress.cloud.stream.movie;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "movie")
public class MovieDslProperties {
private String action = "create"; // create | deploy | destroy
}
Listing 9-6.src/main/java/com/apress/cloud/stream/movie/MovieDslProperties.java
这个类包含一个可以是create、deploy,或destroy的动作。它调用正确的方法来执行和创建、部署或销毁流。尽管它只是一个属性,但是您可以添加更多的行为来对一些值做出反应。
- 创建
MovieDslService(参见清单 9-7 )。
package com.apress.cloud.stream.movie;
import lombok.AllArgsConstructor;
import org.springframework.cloud.dataflow.rest.client.DataFlowOperations;
import org.springframework.cloud.dataflow.rest.client.dsl.DeploymentPropertiesBuilder;
import org.springframework.cloud.dataflow.rest.client.dsl.Stream;
@AllArgsConstructor
public class MovieDslService {
private DataFlowOperations dataFlowOperations;
public void create(){
java.util.stream.Stream.of(MovieDslStream.values()).forEach( c -> {
createStream(c.getName(),c.getDefinition());
});
}
public void deploy(){
java.util.stream.Stream.of(MovieDslStream.values()).forEach( c -> {
deployStream(c.getName());
});
}
public void destroy(){
java.util.stream.Stream.of(MovieDslStream.values()).forEach( c -> {
destroyStream(c.getName());
});
}
private void createStream(String name, String definition){
Stream.builder(dataFlowOperations)
.name(name)
.definition(definition)
.create();
}
private void deployStream(String name){
dataFlowOperations.streamOperations().deploy(name,
new DeploymentPropertiesBuilder().build());
}
private void destroyStream(String name){
dataFlowOperations.streamOperations().destroy(name);
}
}
Listing 9-7.src/main/java/com/apress/cloud/stream/movie/MovieDslService.java
清单 9-7 显示了定义样式。createStream方法使用带有 fluent API 的Stream类,在本例中,您使用 definition 方法来创建、部署或销毁。在这种情况下,您通过使用一个定义来创建 DSL。注意你有一个DataFlowOperations接口。DataFlowTemplate类实现了这个接口。DataFlowTemplate类基于模板模式,与 Spring CloudStream 服务器 REST API 交互。看看其他方法,你会发现这非常简单明了。注意,create、deploy和destroy方法正在迭代MovieDslStream枚举,并使用getName()和getDefinition()来获取值。
-
创建
MovieDslConfiguration类来连接所有必要的 Spring beans(参见清单 9-8 )。package com.apress.cloud.stream.movie; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.dataflow.rest.client.DataFlowOperations; import org.springframework.cloud.dataflow.rest.client.DataFlowTemplate; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.lang.reflect.Method; import java.net.URI; @EnableConfigurationProperties(MovieDslProperties.class) @Configuration public class MovieDslConfiguration { @Bean public DataFlowOperations dataFlowOperations(){ URI dataFlowUri = URI.create("http://localhost:9393"); DataFlowOperations dataFlowOperations = new DataFlowTemplate(dataFlowUri); dataFlowOperations.appRegistryOperations().importFromResource( "https://dataflow.spring.io/rabbitmq-maven-latest", true); return dataFlowOperations; } @Bean public CommandLineRunner actions(MovieDslService movieDslService, MovieDslProperties movieDslProperties){ return args -> { Method method = movieDslService.getClass() .getMethod(movieDslProperties.getAction(),null); assert method != null; method.invoke(movieDslService,null); }; } @Bean public MovieDslService movieDslService(DataFlowOperations dataFlowOperations){ return new MovieDslService(dataFlowOperations); } } Listing 9-8.src/main/java/com/apress/cloud/stream/movie/MovieDslConfiguration.java清单 9-8 显示了您使用的配置。我们来分析一下。
-
dataflowOperations。这个方法使用DataFlowTemplate类在这个基础上创建了一个DataFlowOperations接口的实例;这个类指向数据流服务器 REST API(本例中为 http://localhost:9393)。它还使用`rabbitmq-maven-latest` URI 导入应用。 -
movieDslService。这个声明创建了通过DataFlowOperations接口的 Spring BeanmovieDslService。 -
actions。当 Spring 应用容器准备好执行程序时,就会执行这个方法。这个方法有movieDSL和movieDslProperties参数。请注意,您正在使用 Java 反射 API 来执行 MovieDslService 方法,这是一个基于给定属性的命令模式的小型实现:create、deploy 或 destroy。
-
-
Add the following content to the
application.propertiesfile .## Movie properties # action = create, deploy, destroy movie.action=create运行程序时,可以使用创建、部署或销毁。
-
在运行这个项目之前,您需要确保使用 Docker Compose 或 Kubernetes 运行您的基础设施。还要确保重新创建保存电影的数据库。
-
运行您的项目。完成后,您可以在浏览器中查看
/dashboard路径,看到应用已注册,三个电影 DSL 流已创建(如果您在浏览器中转至流部分)。 -
Change the
movie.actionproperty todeployand run the App. Your pipeline DSL has been deployed. And you can send a movie set with the following.curl -s -X POST \ -H "Content-Type: application/json" \ -d '{"movies":[{"id":"tt0133093","title":"The Matrix","actor":"Keanu Reeves","year":1999,"genre":"fiction","stars":5},{"id":"tt0209144","title":"Memento","actor":"Guy Pearce","year":2000,"genre":"drama","stars":4},{"id":"tt0482571","title": "The Prestige","actor":"Christian Bale","year":2006,"genre":"drama","stars":3},{"id":"tt0486822","title":"Disturbia","actor":"Shia LaBeouf","year":2007,"genre":"drama","stars":3}]}' \ http://localhost:9001根据您的部署更改地址(Docker Compose 或 Kubernetes)。接下来,查看日志和数据库(参考前面的章节)。
-
运行之后,部署并检查日志和数据库。您可以将
movie.action属性更改为destroy,运行项目,并在仪表板中看到您的 DSL 被删除。
恭喜你!您已经使用 Java DSL 定义样式以编程方式创建、部署和销毁了电影流管道 DSL。
Java DSL API:流畅风格
本节介绍 Java DSL 流畅风格。
-
您可以使用以下元数据创建另一个项目。
-
组:
com.apress.cloud.stream -
神器:
movie-dsl-fluent -
包名:
com.apress.cloud.stream.movie -
依赖:CloudStream,龙目岛
-
-
单击生成按钮下载一个 ZIP 文件。解压缩并导入到您喜欢的 IDE 中。
-
你可以复制
MovieDslStream枚举和MovieDslProerties类,以及application.properties文件,你可以重用它们。 -
创建
MovieDslService类(参见清单 9-9 )。
package com.apress.cloud.stream.movie;
import lombok.AllArgsConstructor;
import org.springframework.cloud.dataflow.rest.client.DataFlowOperations;
import org.springframework.cloud.dataflow.rest.client.dsl.DeploymentPropertiesBuilder;
import org.springframework.cloud.dataflow.rest.client.dsl.Stream;
import org.springframework.cloud.dataflow.rest.client.dsl.StreamApplication;
import org.springframework.cloud.dataflow.rest.client.dsl.StreamBuilder;
@AllArgsConstructor
public class MovieDslService {
private DataFlowOperations dataFlowOperations;
private StreamApplication httpSource;
private StreamApplication splitterProcessor;
private StreamApplication groovyTransformProcessor;
private StreamApplication jdbcSink;
public void create(){
createFluentStream(MovieDslStream.MOVIE.getName());
java.util.stream.Stream.of(MovieDslStream.values()).filter(c -> !c.getName().equals(MovieDslStream.MOVIE.getName())).forEach( c -> {
createStream(c.getName(),c.getDefinition());
});
}
public void deploy(){
java.util.stream.Stream.of(MovieDslStream.values()).forEach( c -> {
deployStream(c.getName());
});
}
public void destroy(){
java.util.stream.Stream.of(MovieDslStream.values()).forEach( c -> {
destroyStream(c.getName());
});
}
private void createFluentStream(String name){
Stream.builder(dataFlowOperations)
.name(name)
.source(httpSource)
.processor(splitterProcessor)
.processor(groovyTransformProcessor)
.sink(jdbcSink)
.create();
}
private void createStream(String name, String definition){
Stream.builder(dataFlowOperations)
.name(name)
.definition(definition)
.create();
}
private void deployStream(String name){
dataFlowOperations.streamOperations().deploy(name,new DeploymentPropertiesBuilder().build());
}
private void destroyStream(String name){
dataFlowOperations.streamOperations().destroy(name);
}
}
Listing 9-9.src/main/java/com/apress/cloud/stream/movie/MovieDslService.java
清单 9-9 显示了MovieDslService类。这里重要的部分是createFluentStream方法;请注意,您没有使用该定义。您可以使用 source、processor 或 sink 方法。
- 创建
MovieDslConfiguration类(参见清单 9-10 )。
package com.apress.cloud.stream.movie;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.dataflow.core.ApplicationType;
import org.springframework.cloud.dataflow.rest.client.DataFlowOperations;
import org.springframework.cloud.dataflow.rest.client.DataFlowTemplate;
import org.springframework.cloud.dataflow.rest.client.dsl.StreamApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
import java.net.URI;
@EnableConfigurationProperties(MovieDslProperties.class)
@Configuration
public class MovieDslConfiguration {
@Bean
public CommandLineRunner actions(MovieDslService movieDslService, MovieDslProperties movieDslProperties){
return args -> {
Method method = movieDslService.getClass()
.getMethod(movieDslProperties.getAction(),null);
assert method != null;
method.invoke(movieDslService,null);
};
}
@Bean
public MovieDslService movieDslService(DataFlowOperations dataFlowOperations,
StreamApplication httpSource,StreamApplication splitterProcessor,StreamApplication groovyTransformProcessor,
StreamApplication jdbcSink, StreamApplication logSink){
return new MovieDslService(dataFlowOperations,httpSource,splitterProcessor,groovyTransformProcessor,jdbcSink);
}
@Bean
public DataFlowOperations dataFlowOperations(){
URI dataFlowUri = URI.create("http://localhost:9393");
DataFlowOperations dataFlowOperations = new DataFlowTemplate(dataFlowUri);
dataFlowOperations.appRegistryOperations().register("http", ApplicationType.source,
"maven://org.springframework.cloud.stream.app:http-source-rabbit:2.1.4.RELEASE",
"maven://org.springframework.cloud.stream.app:http-source-rabbit:jar:metadata:2.1.4.RELEASE",
true);
dataFlowOperations.appRegistryOperations().register("splitter", ApplicationType.processor,
"maven://org.springframework.cloud.stream.app:splitter-processor-rabbit:2.1.3.RELEASE",
"maven://org.springframework.cloud.stream.app:splitter-processor-rabbit:jar:metadata:2.1.3.RELEASE",
true);
dataFlowOperations.appRegistryOperations().register("groovy-transform", ApplicationType.processor,
"maven://org.springframework.cloud.stream.app:groovy-transform-processor-rabbit:2.1.3.RELEASE",
"maven://org.springframework.cloud.stream.app:groovy-transform-processor-rabbit:jar:metadata:2.1.3.RELEASE",
true);
dataFlowOperations.appRegistryOperations().register("filter", ApplicationType.processor,
"maven://org.springframework.cloud.stream.app:filter-processor-rabbit:2.1.3.RELEASE",
"maven://org.springframework.cloud.stream.app:filter-processor-rabbit:jar:metadata:2.1.3.RELEASE",
true);
dataFlowOperations.appRegistryOperations().register("jdbc", ApplicationType.sink,
"maven://org.springframework.cloud.stream.app:jdbc-sink-rabbit:2.1.6.RELEASE",
"maven://org.springframework.cloud.stream.app:jdbc-sink-rabbit:jar:metadata:2.1.6.RELEASE",
true);
dataFlowOperations.appRegistryOperations().register("log", ApplicationType.sink,
"maven://org.springframework.cloud.stream.app:log-sink-rabbit:2.1.4.RELEASE",
"maven://org.springframework.cloud.stream.app:log-sink-rabbit:jar:metadata:2.1.4.RELEASE",
true);
return dataFlowOperations;
}
@Bean
public StreamApplication httpSource(){
return new StreamApplication("http")
.addProperty("port",9001);
}
@Bean
public StreamApplication splitterProcessor(){
return new StreamApplication("splitter")
.addProperty("expression","\"#jsonPath(payload,'$.movies')\"");
}
@Bean
public StreamApplication groovyTransformProcessor(){
return new StreamApplication("groovy-transform")
.addProperty("script","\"https://raw.githubusercontent.com/felipeg48/scdf-scripts/master/movie-transform.groovy\"");
}
@Bean
public StreamApplication jdbcSink(){
return new StreamApplication("jdbc")
.addProperty("columns","\"id:id,title:title,actor:actor,year:year,genre:genre,stars:stars,rating:imdb.rating,ratingcount:imdb.ratingCount\"")
.addProperty("table-name","\"movies\"")
.addProperty("username","\"root\"")
.addProperty("password","\"rootpw\"")
.addProperty("driver-class-name","\"org.mariadb.jdbc.Driver\"")
.addProperty("url","\"jdbc:mysql://mysql:3306/reviews?autoReconnect=true&useSSL=false\"");
}
}
Listing 9-10.src/main/java/com/apress/cloud/stream/movie/MovieDslConfiguration.java
清单 9-10 显示了 MovieDslConfiguration 类。花点时间分析一下,看看有什么不同。注意,在这堂课上,我用的是旧版本。您可以修复此问题并使用最新版本。你应该没事的。
在这个类中,您发现DataFlowOperations只注册您使用的应用;这是另一种替代而不是全部。请注意,您在注册中使用了name、ApplicationType和maven坐标。另外,查看创建源、处理器和接收器的StreamApplicationbean,以及定义传递给应用的参数的addProperty方法的用法。
- 您可以运行项目并发送一些数据。
我知道这个项目可能只有一个,但我想把它和另一个分开,这样你就可以清楚地知道选择什么风格。
恭喜你!您使用流畅的风格创建了一个电影流管道 DSL。
Note
记住所有的代码都在 Apress 网站的ch09文件夹中。
摘要
本章介绍了 Spring CloudStream 组件,并解释了如何部署流。我向您展示了创建流的不同方法,从命令行实用程序到使用 Java DSL 创建动态流。
下一章将讨论流处理,您将添加自己的 NATs 代理。您将看到 Spring Cloud Task,并了解它如何创建批处理。你可以认为下一章是这一章的延续。
十、使用 Spring CloudStream 定制流式应用
在前一章中,我向您展示了 Spring CloudStream 组件,以及它们如何协同工作来创建用于处理数据的流解决方案。我通过指向公开 REST API 的 Spring CloudStream 服务器,向您展示了如何使用简单的 cURL 命令创建流。我向您展示了数据流 shell 如何通过创建、部署和销毁流来工作。我讨论了如何通过使用 Java DSL 创建流定义来以编程方式创建动态解决方案。我说过,你可以在任何云平台上运行相同的流,比如 Kubernetes,并利用高可用性和负载平衡等特性。
本章使用自定义流式应用,并解释如何将它们插入流定义。您正在使用自定义的 NATs 活页夹。首先,我们来复习一些概念。
定制流式应用:快速回顾
流被定义为在没有交互或中断的情况下处理数据,这对于接近实时的用例非常有用,例如预测分析、信用卡欺诈检测、垃圾邮件、商业智能,如果您使用机器学习过程插入这些流,那么您就有了一个非常强大的解决方案。
Spring Cloud Stream 提供了一个解决方案,将流作为独立的应用,这些应用通常是事件驱动的,可以通过任何消息中间件连接起来。通过连接这些应用,您可以创建一个由流数据管道组成的运行时环境,它可以是线性的,也可以是非线性的,这取决于您的业务逻辑。有了 Spring Cloud Stream,你可以使用任何适合你的基础设施的中间件;你不需要学习特定的 API 来从一个应用向另一个应用发送或接收消息。你可以使用任何来自社区或者 Spring Cloud Stream 团队支持的绑定器,比如 RabbitMQ 和 Kafka。在本章中,您将使用前几章中的自定义代理。Spring CloudStream 及其组件充当了一个编排器,可以使用任何云平台基础设施创建、部署、更新和销毁复杂的流。
在本章中,您将创建一个自定义流并使用一个自定义绑定器,因此您需要记住创建自定义 Spring CloudStream 应用的一些要点。
-
您需要选择正在创建的流的类型:源、处理器、接收器或任务。(任务应用将在下一章讨论。)
-
您需要选择
spring-cloud-stream依赖项。如果您使用的是 Spring Cloud Stream 团队支持的代理,您可以添加spring-cloud-stream-binder-rabbit(用于 RabbitMQ)或spring-cloud-stream-binder-kafka-streams(用于 Kafka 依赖)。 -
您可以选择不同的编程风格。您可以使用
@EnableBinding(Source、Processor、Sink、@StreamListener(用于接收消息)、@SendTo(用于回复/响应,通常是一个Processor利用这一点),或者您可以进行函数式编程并使用java.util.functionJava 包中的Supplier、Function或Consumer接口以及带有Mono或Flux接口的项目反应器。 -
你需要配置所有的流 app 输入和输出,也就是所谓的目的地;以及你需要的任何其他属性。
请记住,Spring Cloud Stream 团队创建了独立和开箱即用的流式应用,您可以使用它们来创建流数据管道解决方案。
现在,您已经回忆起了所有这些,是时候创建自定义流式应用,使用自定义 NATs 绑定器,并使用 Spring CloudStream 仪表板或数据流外壳来编排管道 DSL 了。
Spring CloudStream 中的自定义流式应用
本节中的所有内容都将 Docker Compose 技术用于本地环境,但是如果您已经在自己的 Kubernetes 集群中运行了 Spring CloudStream 组件,那么无论如何,您都可以使用它来创建、更新和部署您的流数据管道。
让我们定义最终的管道(见图 10-1 )。
图 10-1 说明了我们将要做的事情。当用 DSL 表达时,它看起来像下面这样。
图 10-1。
自定义流管道
movie-web | splitter | movie-imdb | movie-log
尽管这是一个简单的流管道,我想向您展示如何使用 GUI 或数据流外壳的自定义应用,以及如何使用自定义绑定器。如果你仔细看看 DSL, splitter app starter 结合了三个自定义流式应用。
电影网络应用:电影-来源
这个应用公开了一个 REST API,您可以在其中发送一组电影。我们先打开浏览器,进入 Spring Initializr ( https://start.spring.io )网站。使用以下数据。
-
组:
com.apress.cloud.stream -
神器:
movie-source -
包名:
com.apress.cloud.stream.movie -
依赖:CloudStream,春网,龙目岛
点击生成按钮下载一个 ZIP 文件。将其解压缩,并将项目导入到您喜欢的 IDE 中(参见图 10-2 )。
图 10-2。
Spring 初始化电影-来源
接下来,打开pom.xml并添加以下依赖项。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
<!-- WebJars -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>4.5.0</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.5.1</version>
</dependency>
如果查看依赖项,首先使用 RabbitMQ 绑定器。此外,还包括一个 WebJar 来创建一个index.html页面来发送电影,而不是任何 cURL 命令或另一个 REST API 客户端。我希望您看到命令行以外的解决方案。
接下来修改版本,去掉快照工作,所以如下。
<version>0.0.1</version>
这对部署很重要(我稍后会解释)。接下来,让我们创建模型。创建Movie类(参见清单 10-1 )。
package com.apress.cloud.stream.movie;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Movie {
private String id;
private String title;
private String actor;
private int year;
private String genre;
private int stars;
}
Listing 10-1.src/main/java/com/apress/cloud/stream/movie/Movie.java
您已经知道了Movie类,所以让我们继续创建MovieRequest类(参见清单 10-2 )。
package com.apress.cloud.stream.movie;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class MovieRequest {
String action;
Iterable<Movie> movies;
LocalDateTime created;
}
Listing 10-2.src/main/java/com/apress/cloud/stream/movie/MovieRequest.java
清单 10-2 显示了MovieRequest类。如你所见,这很简单。通常,当您想要公开一个 API 时,作为一种最佳实践,您应该包装您的数据。当使用审计工具时,这有助于查看请求何时发生、谁做的等等。接下来,您需要一个MovieResponse类(参见清单 10-3 )。
package com.apress.cloud.stream.movie;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class MovieResponse {
Integer code;
String message;
LocalDateTime responseTime;
}
Listing 10-3.src/main/java/com/apress/cloud/stream/movie/MovieResponse.java
清单 10-3 显示了MovieResponse类。尽管这个类很简单,但您可以使用它来报告对您的业务逻辑有意义的特殊代码,并发送一条暴露任何问题的消息。接下来,让我们创建MovieController类(参见清单 10-4 )。
package com.apress.cloud.stream.movie;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.Collection;
@Log4j2
@AllArgsConstructor
@RequestMapping("/v1/api")
@RestController
public class MovieController {
private StreamBridge streamBridge;
@PostMapping("/movies")
@ResponseStatus(HttpStatus.ACCEPTED)
public ResponseEntity<MovieResponse> toMovieBinding(@RequestBody MovieRequest movieRequest) {
assert movieRequest != null;
movieRequest.setCreated(LocalDateTime.now());
log.debug("Sending: {} ", movieRequest);
assert streamBridge != null;
streamBridge.send("movie-out-0", movieRequest);
return ResponseEntity
.accepted()
.body(new MovieResponse(HttpStatus.OK.value(),"Movies processed: " + ((Collection)movieRequest.getMovies()).size(), LocalDateTime.now()) );
}
}
Listing 10-4.src/main/java/com/apress/cloud/stream/movie/MovieController.java
清单 10-4 显示了MovieController类。重要的部分是使用StreamBridge类将数据发送到输出绑定。send方法使用了movie-out-0绑定。主 API 端点是/v1/api,而MovieRequest端点是/v1/api/movies。
接下来,让我们创建index.html和 JavaScript 来创建一个发送到/v1/api/movies端点的主页(参见清单 10-5 和 10-6 )。
function getMovieRequest(){
return `{
"MovieRequest": {
"action": "create",
"movies": [
{
"id": "tt0133093",
"title": "The Matrix",
"actor": "Keanu Reeves",
"year": 1999,
"genre": "fiction",
"stars": 5
},
{
"id": "tt0209144",
"title": "Memento",
"actor": "Guy Pearce",
"year": 2000,
"genre": "drama",
"stars": 4
}
]
}
}
`;
}
$(function(){
$('#movieRequest').val(getMovieRequest());
$('#sendRequest').click(function (){
$.ajax
({
type: "POST",
url: '/v1/api/movies',
dataType: 'json',
async: false,
contentType: 'application/json',
data: $('#movieRequest').val(),
success: function (data) {
alert(data.MovieResponse.message);
}
})
});
});
Listing 10-6.src/main/resources/static/js/main.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="/webjars/jquery/3.5.1/jquery.min.js"></script>
<script src="/webjars/bootstrap/4.5.0/js/bootstrap.min.js"></script>
<link rel="stylesheet"
href="/webjars/bootstrap/4.5.0/css/bootstrap.min.css" />
<title>Title</title>
</head>
<body>
<div class="jumbotron jumbotron-fluid">
<div class="container">
<h1 class="display-4">Movie API</h1>
<p class="lead">This is a Movie API Stream App.</p>
</div>
</div>
<div class="container">
<!-- Example row of columns -->
<div class="row">
<div class="col-md-6">
<h2>Movies</h2>
<p>You can send this JSON movie request, or modify it accordingly.</p>
<div class="form-group">
<textarea class="form-control" id="movieRequest" rows="15"></textarea>
</div>
<p><a class="btn btn-primary btn-lg" href="#" role="button" id="sendRequest">Send</a></p>
</div>
</div>
<hr>
</div>
<script src="js/main.js"></script>
</body>
</html>
Listing 10-5.src/main/resources/static/index.html
正如你从之前的文件中看到的,这些非常简单——没有什么复杂的,只是一个使用$.ajax(来自 jQuery)的帖子。注意,您已经用 movies JSON 有效负载填充了文本区域。例如,这可能是一个 SPA(单页应用)。
接下来,我们打开application.properties文件。添加清单 10-7 中的内容。
# Server
server.port=8080
# Jackson Root Properties
spring.jackson.serialization.wrap-root-value=true
spring.jackson.deserialization.unwrap-root-value=true
# Spring Cloud Stream
spring.cloud.stream.source=movie
spring.cloud.stream.bindings.movie-out-0.destination=movie
# Logging
logging.level.com.apress.cloud.stream.movie=DEBUG
Listing 10-7.src/main/resource/application.properties
清单 10-7 显示了application.properties文件。注意,您添加了spring.jackson.*属性来将MovieRequest和MovieResponse对象包装到一个 JSON 对象中。此外,要使用StreamBridge类,您需要定义源的名称,在本例中是movie。此外,有必要创建基于命名约定的绑定,命名约定是movie-out-0(您在前面的章节中已经看到了)。
你可以通过连接到log-app-starter或splitter-app-starter来测试这个流式应用,看看它是如何工作的。
Note
在源代码中,ch10/app-starters文件夹包含一个setup.sh脚本,用于下载拆分器和日志应用启动器,并设置application.properties文件来测试movie-source项目。需要 RabbitMQ 来测试。
电影 IMDB 应用:电影处理器
这个应用接收 JSON 格式的电影。它使用电影的 ID 去第三方 API 服务( https://rapidapi.com )。请记住,您需要登录(RapidAPI 是免费的)并使用 IMDB 服务。您正在使用免费服务( https://imdb8.p.rapidapi.com )和/title/get-ratings端点来获取评级。这个流式应用与您在前一章中创建的groovy-transform脚本非常相似。这个流式应用使用 NATs 服务器和 RabbitMQ 绑定器。
打开浏览器,进入 Spring Initializr ( https://start.spring.io )网站。使用以下数据。
-
组:
com.apress.cloud.stream -
神器:
movie-processor -
包名:
com.apress.cloud.stream.movie -
依赖:CloudStream,龙目岛
点击生成按钮下载一个 ZIP 文件。将其解压缩并将项目导入到您喜欢的 IDE 中(参见图 10-3 )。
图 10-3。
Spring Initializr 电影处理器
接下来,打开pom.xml并添加以下依赖项。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
<!-- NATs Server -->
<dependency>
<groupId>com.apress.nats</groupId>
<artifactId>nats-messaging-binder</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- Apache Commons -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.12</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
您正在添加 NATs 绑定器依赖项和执行请求的httpclient。(您可以使用 Spring Web 提供的RestClient,但我想使用一种替代方法)。此外,您正在将configuration-processor添加到您自己的属性中。
在同一个pom.xml文件的 build/plugins 部分,添加以下插件。
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-app-starter-metadata-maven-plugin</artifactId>
<version>2.0.0.RELEASE</version>
<executions>
<execution>
<id>aggregate-metadata</id>
<phase>compile</phase>
<goals>
<goal>aggregate-metadata</goal>
</goals>
</execution>
</executions>
</plugin>
这将创建元数据 JAR,其中包含设置流所需的所有属性信息。接下来,修改版本并删除快照工作,这样它就
<version>0.0.1</version>
这对部署很重要。
接下来,您需要创建保存新信息的模型。您正在增强Movie对象。创建Movie和MovieImdb类(参见清单 10-8 和 10-9 )。
package com.apress.cloud.stream.movie;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class MovieImdb {
Float rating;
Integer ratingCount;
}
Listing 10-9.src/main/java/com/apress/cloud/stream/movie/MovieImdb.java
package com.apress.cloud.stream.movie;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Movie {
private String id;
private String title;
private String actor;
private int year;
private String genre;
private int stars;
private MovieImdb imdb;
}
Listing 10-8.src/main/java/com/apress/cloud/stream/movie/Movie.java
清单 10-9 显示了MovieImdb类。该类保存来自 IMDB 服务的信息。接下来,让我们创建MovieProperties类,它保存关于 IMDB 服务的信息,比如主机和调用所需的一些头(参见清单 10-10 )。
package com.apress.cloud.stream.movie;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "movie")
public class MovieProperties {
String apiServer;
String headerHost;
String headerKey;
}
Listing 10-10.src/main/java/com/apress/cloud/stream/movie/MovieProperties.java
接下来,让我们创建MovieStream类(参见清单 10-11 )。
package com.apress.cloud.stream.movie;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.log4j.Log4j2;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Flux;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.function.Function;
@Log4j2
@EnableConfigurationProperties(MovieProperties.class)
@Configuration
public class MovieStream {
private MovieProperties movieProperties;
private final CloseableHttpClient httpclient = HttpClients.createDefault();
private final HttpGet getRequest = new HttpGet();
public MovieStream(MovieProperties movieProperties) {
this.movieProperties = movieProperties;
getRequest.addHeader("Accept", "application/json");
getRequest.addHeader("x-rapidapi-host", movieProperties.getHeaderHost());
getRequest.addHeader("x-rapidapi-key", movieProperties.getHeaderKey());
getRequest.addHeader("Content-Type", "application/json");
}
@Bean
public Function<Flux<Movie>, Flux<Movie>> movieProcessor(ObjectMapper objectMapper) {
return movieFlux -> movieFlux.map(
movie -> {
try {
getRequest.setURI(new URI(movieProperties.getApiServer().replace("ID", movie.getId())));
HttpEntity entity = httpclient.execute(getRequest).getEntity();
movie.setImdb(objectMapper.readValue(EntityUtils.toString(entity, StandardCharsets.UTF_8), MovieImdb.class));
} catch (IOException | URISyntaxException e) {
e.printStackTrace();
}
log.debug("About ot send: {}", movie);
return movie;
});
}
@Bean
public ObjectMapper objectMapper(){
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);
return objectMapper;
}
}
Listing 10-11.src/main/java/com/apress/cloud/stream/movie/MovieStream.java
清单 10-11 显示了MovieStream类。我们来分析一下这个类。请注意,您在movieProcessor method中使用了Flux<Movie>。因为它被声明为 Spring bean,所以它识别处理传入流的主要方法,并将数据发送到正确的绑定。您正在使用httpclient实例来调用带有正确数据、电影 ID 和头(如主机和密钥)的服务。看看entity的例子。您正在执行请求并让一个实体映射回MovieImdb类。映射器忽略响应中缺少的属性来映射类。
接下来,让我们创建一个包含spring-configuration-metadata.json文件的src/main/resources/META-INF文件夹,。这个文件有关于每个属性的有用信息,这有助于其他开发人员重用和配置这个流(参见清单 10-12 )。
{
"groups": [
{
"name": "movie",
"type": "com.apress.cloud.stream.movie.MovieProperties",
"sourceType": "com.apress.cloud.stream.movie.MovieProperties"
},
{
"name": "spring.nats",
"type": "com.apress.nats.NatsProperties",
"sourceType": "com.apress.nats.NatsProperties"
}
],
"properties": [
{
"name": "spring.nats.host",
"type": "java.lang.String",
"description": "This NATs Server host. Default to localhost.",
"sourceType": "com.apress.nats.NatsProperties",
"defaultValue": "localhost"
},
{
"name": "spring.nats.port",
"type": "java.lang.Integer",
"description": "This NATs Server port. Default to 4222.",
"sourceType": "com.apress.nats.NatsProperties",
"defaultValue": 4222
},
{
"name": "movie.api-server",
"type": "java.lang.String",
"description": "Default to: https://imdb8.p.rapidapi.com/title/get-ratings?tconst=ID. The ID will be replaced, so it's necessary",
"sourceType": "com.apress.cloud.stream.movie.MovieProperties",
"defaultValue": "https://imdb8.p.rapidapi.com/title/get-ratings?tconst=ID"
},
{
"name": "movie.header-host",
"type": "java.lang.String",
"description": "Default to: imdb8.p.rapidapi.com.",
"sourceType": "com.apress.cloud.stream.movie.MovieProperties",
"defaultValue": "imdb8.p.rapidapi.com
"
},
{
"name": "movie.header-key",
"type": "java.lang.String",
"description": "This header-key can be obtain in your https://rapidapi.com/ profile.",
"sourceType": "com.apress.cloud.stream.movie.MovieProperties"
}
],
"hints": []
}
Listing 10-12.src/main/resource/META-INF/spring-configuration-metadata.json
接下来,在同一个META-INF/文件夹中,添加包含用@ConfigurationProperties注释标记的类的spring-configuration-metadata-whitelist.properties文件。这将生成元数据以获取关于流式应用的信息(参见清单 10-13 )。
configuration-properties.classes=\
com.apress.nats.NatsProperties,\
com.apress.cloud.stream.movie.MovieProperties
configuration-properties.names=movie.api-server,movie.header-host,movie.header-key,spring.nats.host,spring.nats.port
Listing 10-13.src/main/resource/META-INF/spring-configuration-metadata-whitelist.json
请注意,您正在添加我们的 NATs 属性,因为这些属性对于告知 NATs 代理在哪里是必需的。此外,您将正在使用的属性列入白名单。如果你需要类似server.port的东西,这是必要的。对于这个示例,您不需要它们,有了类就足够了,但是为了说明这一点,您可以包含一些其他属性,这些属性已经作为整个 stream 应用的一部分包含在内。
接下来,打开application.properties文件并添加清单 10-14 中的内容。
# Server
server.port=8082
# IMDB API
movie.api-server=https://imdb8.p.rapidapi.com/title/get-ratings?tconst=ID
movie.header-host=imdb8.p.rapidapi.com
movie.header-key=YOUR-KEY
# Binders
spring.cloud.stream.bindings.movieProcessor-in-0.binder=rabbit
spring.cloud.stream.bindings.movieProcessor-out-0.binder=nats
# Bindings - Nats - RabbitMQ
spring.cloud.stream.bindings.movieProcessor-in-0.destination=imdb
spring.cloud.stream.bindings.movieProcessor-out-0.destination=log
# Logging
logging.level.com.apress.cloud.stream.movie=DEBUG
Listing 10-14.src/main/resource/META-INF/application.properties
清单 10-14 显示了application.properties。注意,这个文件有movie.*属性。有些是违约。还要查看绑定器(用于传入的流;代理使用它们进行流式传输)并注意您需要如何输入输入和输出目的地。记住采用方法名(movieProcessor)并添加-in-0和-out-0终止的命名约定。
电影日志应用:电影接收器
这个流式应用记录带有 IMDB 评级的增强的Movie对象。一个非常简单的应用。打开浏览器,进入 Spring Initializr 网站( https://start.spring.io )。使用以下数据。
-
组:
com.apress.cloud.stream -
神器:
movie-sink -
包名:
com.apress.cloud.stream.movie -
依赖:CloudStream,龙目岛
点击生成按钮下载一个 ZIP 文件。将其解压缩,并在您喜欢的 IDE 中导入项目(参见图 10-4 )。
图 10-4。
Spring 初始化电影池
打开pom.xml并添加以下依赖项。
<!-- NATs Server -->
<dependency>
<groupId>com.apress.nats</groupId>
<artifactId>nats-messaging-binder</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
这个流式应用使用 NATs 绑定器,所以这是你唯一需要的依赖。接下来,修改版本并删除快照工作,这样它就
<version>0.0.1</version>
接下来,复制/粘贴先前的Movie和MovieImdb类(参见清单 10-8 和 10-9 )。
接下来,创建MovieStream类(参见清单 10-15 )。
package com.apress.cloud.stream.movie;
import lombok.extern.log4j.Log4j2;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Flux;
import java.util.function.Consumer;
@Log4j2
@Configuration
public class MovieStream {
@Bean
public Consumer<Flux<Movie>> log() {
return movie -> movie.subscribe(actual -> log.info(actual));
}
}
Listing 10-15.src/main/java/com/apress/cloud/stream/movie/MovieStream.java
清单 10-15 显示了MovieStream类。如你所见,这很简单;您只需订阅和接收Movie对象。
接下来,打开application.properties文件,添加清单 10-16 中的内容。
# Server
server.port=8083
# Bindings
spring.cloud.stream.bindings.log-in-0.destination=log
Listing 10-16.src/main/resources/application.properties
打包和部署流式应用
现在,你已经准备好使用 Spring CloudStream 来创建一个流管道,但是首先,你需要决定如何打包每一个流 app。最后,您可以拥有优步-JARs 并独立运行它们。但是您需要一些东西来协调管道,而不是手动完成所有事情。对于所有这些,您需要选择您希望 Spring CloudStream 服务器如何使用您的流式应用。Spring CloudStream 服务器可以通过提供 Maven 或 Docker 坐标以及本地 JAR 来使用流式应用(但这应该仅用于开发)。
您在这里使用的是 Maven 坐标,为此,您需要打包和部署您的流式应用。如果您是一名经验丰富的开发人员,并且已经知道如何将 Maven 工件打包并部署到 Maven 存储库中,那么您可以跳过这一节。
要打包您的应用,进入每个项目的根目录并执行以下命令。
./mvnw clean -DskipTests package
该命令在target/文件夹中生成您需要的文件。如果你看一下movie-processor项目的target/文件夹,你会发现movie-processor-0.0.1-metadata.jar中有spring-configuration-metadata.*文件,当你需要关于这个应用的信息时,这些文件很有用。
接下来,有必要将这些工件部署到 Maven 存储库中。让我告诉你,创建 Maven repo 有很多解决方案,比如使用 Docker images 或 Apache Archiva、Nexus 或 JFrog。您还可以将 Git 服务器用作 Maven repo。在本例中,我使用了一个名为 Bintray ( https://bintray.com )的开源 Maven 回购。可以免费报名(见图 10-5 )。
图 10-5。
垃圾箱
设置帐户后,您需要创建一个存储库和一个包来保存您的流式应用。我创建了scdf存储库,包名是movie-streams。我最终的 Maven 回购网址是 https://bintray.com/felipeg48/scdf (见图 10-6 )。
图 10-6。
https://bintray.com/felipeg48/scdf
一旦您完成了 Maven repo 和包名的设置,您需要向pom.xml文件添加一些凭证和依赖项,以便您可以进行部署。首先,在 https://bintray.com/profile/edit 进入你的个人资料。从左侧菜单中选择 API 键。请将其复制到安全的地方,因为您稍后会用到它(参见图 10-7 )。
图 10-7。
https://bintray.com/profile/edit
在您的主目录中查找(~/.m2文件夹。打开或创建~/.m2/settings.xml并添加以下内容。
<?xml version='1.0' encoding='UTF-8'?>
<settings xsi:schemaLocation='http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd'
xmlns:='http://maven.apache.org/SETTINGS/1.0.0' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
<servers>
<server>
<id>bintray-USERNAME-scdf</id>
<username>YOUR_USERNAME</username>
<password>YOUR_KEY</password>
</server>
</servers>
</settings>
<id>标签应该与下面的配置相同。接下来打开每个项目的pom.xml文件,添加以下内容(这是我的个人信息)。
<distributionManagement>
<repository>
<id>bintray-felipeg48-scdf</id>
<name>felipeg48-scdf</name>
<url>https://api.bintray.com/maven/felipeg48/scdf/movie-streams/;publish=1</url>
</repository>
</distributionManagement>
<id> 必须与settings.xml中设置的相同;<name>可以是你想要的任何东西。知道 URL 是这种格式的 API 是很重要的。
https://api.bintray.com/maven/<username>/<repository>/<package-name>/;publish=1
此外,它必须在末尾包含用户名、存储库和包名。我的情况是这样的。
https://api.bintray.com/maven/felipeg48/scdf/movie-streams/;publish=1
如果你对此有任何问题,垃圾箱的网页上有一个设置我!按钮(见图 10-6 )。点击它可以找到更多关于设置 Maven 上传的信息。
现在,您可以通过在每个项目的根目录中执行以下操作来上传应用。
./mvnw -DskipTests deploy
该命令上传所有文件。您可以通过查看您的回购/包装来验证这一点(参见图 10-8 )。
图 10-8。
https://bintray.com/<username>/scdf/movie-streams#files/com/apress/cloud/stream
你可以检查每个文件夹,看看你的应用在那里。在movie-processor应用中,你可以看到元数据罐。现在,您可以在 Spring CloudStream 中使用它们了。要知道这个存储库是个人的,并不对社区开放,所以你需要告诉 Spring CloudStream 服务器如何找到它。您将在下一节看到这一点。
注册流式应用
现在您已经做好了一切准备,是时候在 Spring CloudStream 服务器中使用您的应用了。您必须添加 Maven repo 来告诉 Spring CloudStream 服务器在哪里可以找到您的应用。使用以下属性指向您的 Maven 存储库。
maven.remote-repositories.repo1.url=https://dl.bintray.com/felipeg48/scdf
这个属性必须在 Spring CloudStream 服务器启动之前设置。如果您正在使用 Kubernetes,您可以将该属性添加到 YAML 文件的env部分,在那里设置容器。
...
env:
- name: maven.remote-repositories.repo1.url
- value: https://dl.bintray.com/felipeg48/scdf
...
我从 Bintray 主页的右上角复制了这个 URL(见图 10-6 )。如果使用 Docker Compose,可以在 environment 部分添加相同的环境变量。
...
environment:
- maven.remote-repositories.repo1.url=https://dl.bintray.com/felipeg48/scdf
...
如果您的公司使用私有 Maven repo,并且需要用户名和密码,请添加以下属性。
maven.remote-repositories.repo1.auth.username=<your-username>
maven.remote-repositories.repo1.auth.password=<your-password>
如果您有多个 Maven 回购,请将repo1更改为repo2..repoN。
...
maven.remote-repositories.repo1.url=...
maven.remote-repositories.repo2.url=..
maven.remote-repositories.repo3.url=..
maven.remote-repositories.repo2.auth.username=...
...
现在,您可以重启/启动您的服务器。注册应用有几个选项。
-
您可以像在前面章节中一样使用 cURL 命令进行注册。
-
您可以使用 Spring CloudStream shell 来注册它们。
-
可以使用 Spring CloudStream 仪表盘。
-
您可以通过编程方式注册应用。
我将向您展示如何使用 Spring Data 流外壳或仪表板来实现这一点。首先,确保您的 Spring CloudStream 组件已经启动并运行。请记住,Spring Data 流服务器必须从指向您的 Maven repo 的 Maven repo 环境变量开始。注册应用时,了解类型(源、处理器或接收器)、Spring Data 流服务器识别的应用名称以及 Maven 坐标(如下表所示)非常重要。
maven://<groupId>:<artifactId>:<version>
如果您创建了spring-configuration-metadata.json属性,那么您需要以下面的形式注册您的元数据工件。
maven://<group>:artifactId>:jar:metadata:<version>
使用 Spring CloudStream 外壳来注册自定义应用
如果您想使用 Spring CloudStream shell,请确保您的服务器正在运行,然后启动您的 shell。您可以通过多种方式进行连接。如果你有一个优步罐,你可以指向 Spring Data 流服务器,就是这样。
java -jar spring-cloud-dataflow-shell-VERSION.RELEASE.jar \
--dataflow.uri=http://my-server:9393 \
--dataflow.username=my_username \
--dataflow.password=my_password \
--skip-ssl-validation=true
如果你已经在了,你看到了“server-unknown”,你可以用dataflow关键词连接。
server-unknown:>dataflow config server --uri http://my-server:9393 --username=my_username
接下来,用下面的 shell 命令注册应用。
dataflow:>app register --name movie-imdb --type source --uri maven://com.apress.cloud.stream:movie-source:0.0.1
dataflow:>app register --name movie-imdb --type processor --uri maven://com.apress.cloud.stream:movie-processor:0.0.1 --metadata-uri maven://com.apress.cloud.stream:movie-processor:jar:metadata:0.0.1
dataflow:>app register --name movie-log --type sink --uri maven://com.apress.cloud.stream:movie-sink:0.0.1
执行这个 shell 命令后,列出如下应用。
dataflow:>app list
您应该会看到您的自定义流被列出。对于您正在创建的管道 DSL,您需要 splitter 应用,因此您可以按如下方式注册。
dataflow:>app register --name splitter --type processor --uri maven://org.springframework.cloud.stream.app:splitter-processor-rabbit:2.1.2.RELEASE --metadata-uri maven://org.springframework.cloud.stream.app:splitter-processor-rabbit:jar:metadata:2.1.2.RELEASE
也许你想知道是否有更好的注册方式——不需要一个接一个地添加你的应用。在上一章中,您看到了通过使用 https://dataflow.spring.io/rabbitmq-maven-latest URI 可以使用批量选项。如果您下载该文件,您会看到以下格式的坐标。
<type>.<name>[.metadata]=maven://<groupId>:<artifactId>[:jar:metadata]:<version>
Note
请记住,您可以使用 Docker 坐标。在这种情况下,您需要从您的自定义流式应用创建一个图像,并将其推送到注册表,可以是像hub.docker.com这样的公共注册表,也可以是您的私有注册表。坐标有点像docker://<your-docker-id>/<your-image>:<version>。
如果您想要了解您的应用的信息,您可以执行以下命令。
dataflow:>app info --name movie-imdb --type processor
使用这个命令,您应该可以看到所有的movie.*和spring.nats.*属性及其定义。
使用仪表板注册自定义应用
在这一节中,我将通过一个简单的过程向您展示如何使用仪表板。在 h ttp://<your-server>[:9393]/dashboard打开您的仪表板。转到应用选项卡,点击 +添加应用按钮。选择第三个选项,批量导入申请。在作为属性的应用字段中,复制并粘贴以下内容。
source.movie-web=maven://com.apress.cloud.stream:movie-source:0.0.1
processor.movie-imdb=maven://com.apress.cloud.stream:movie-processor:0.0.1
processor.movie-imdb.metadata=maven://com.apress.cloud.stream:movie-processor:jar:metadata:0.0.1
processor.splitter=maven://org.springframework.cloud.stream.app:splitter-processor-rabbit:2.1.2.RELEASE
processor.splitter.metadata=maven://org.springframework.cloud.stream.app:splitter-processor-rabbit:jar:metadata:2.1.2.RELEASE
sink.movie-log=maven://com.apress.cloud.stream:movie-sink:0.0.1
请注意,splitter 应用已经包含在内。分析命名约定。您可以拥有一个外部文件并将其导入(参见图 10-9 )。
图 10-9。
批量导入申请
点击导入应用按钮。您应该会看到列出的应用(参见图 10-10 )。
图 10-10。
应用
现在您已经准备好创建和部署您的流管道了。
创建和部署自定义流
您正在使用这里的仪表板创建管道 DSL,但是也欢迎您在数据流 shell 中进行同样的操作。转到您的仪表板,单击 Stream(在左侧窗格中),然后单击 + Create stream(s) 按钮。在文本区添加 DSL(参见图 10-11 )。
图 10-11。
创建一个流
请注意,您的应用列在左侧窗格中,这意味着它们可以被拖放并连接到您的解决方案中的任何其他流式应用。您可能想知道您是否正在使用某些属性。是的,您是,但是您使用了不同的方法,以便不扰乱流管道。
接下来,单击创建流按钮。将其命名为movie-stream并点击创建流按钮。这将带您进入流列表页面(参见图 10-12 )。
图 10-12。
流
接下来,单击 Play 按钮(>),这将打开一个部署和属性页面,您可以在其中设置每个应用的功能属性。在此页面的顶部,单击“自由文本”选项卡,然后复制以下内容。
app.movie-web.server.port=8081
app.movie-web.spring.cloud.stream.bindings.output.destination=movie
app.splitter.expression=#jsonPath(payload,'$.MovieRequest.movies')
app.splitter.spring.cloud.stream.bindings.input.destination=movie
app.splitter.spring.cloud.stream.bindings.output.destination=imdb
app.movie-imdb.spring.cloud.stream.bindings.input.binder=rabbit
app.movie-imdb.spring.cloud.stream.bindings.output.binder=nats
app.movie-imdb.spring.cloud.stream.bindings.input.destination=imdb
app.movie-imdb.movie.header-key=YOUR-KEY
app.movie-imdb.spring.nats.host=nats
app.movie-imdb.spring.cloud.stream.bindings.output.destination=log
app.movie-log.spring.cloud.stream.bindings.input.destination=log
app.movie-log.spring.nats.host=nats
在继续之前,请分析属性。请注意,您正在为拆分器应用添加表达式。您正在为 NATs 服务器设置主机,并为每个应用命名目标属性。花时间在这里,直到它对你有意义。请注意,您至少需要 IMDB 服务 API 的密钥。您可以相应地进行更改(参见图 10-13 )。
图 10-13。
应用属性
接下来,您可以点击部署流按钮。如果一切顺利,您的所有应用都应该已经部署好了。你可以打开浏览器,进入你的电影网络应用。如果您使用 Kubernetes 部署它,您可以将movie-web-xxx pod 暴露为LoadBalancer并访问它(参见图 10-14 )。
图 10-14。
电影 web 应用
现在您已经准备好发送MovieRequest JSON 对象了。按下 Send 按钮,您应该会收到一条消息,说明已经处理了两部电影。如果您查看 movie-log 应用流日志,您应该会看到带有评级的增强的Movie对象。
Movie(id=tt0133093, title=The Matrix, actor=Keanu Reeves, year=1999, genre=fiction, stars=5, imdb=MovieImdb(rating=8.7, ratingCount=1620794))
Movie(id=tt0209144, title=Memento, actor=Guy Pearce, year=2000, genre=drama, stars=4, imdb=MovieImdb(rating=8.4, ratingCount=1090922))
恭喜您,您已经使用 RabbitMQ 和您的自定义 NATs 绑定器创建了自定义流式应用并部署了流管道。
Note
所有的源代码都在ch10文件夹里。大多数子文件夹都有自述文件和脚本,便于设置。
摘要
在本章中,我向您展示了如何使用自定义流式应用和自定义绑定器来创建流数据管道。我向您展示了如何使用 Maven 坐标在 Spring CloudStream 服务器中轻松访问。此外,您还了解了如何使用数据流外壳和仪表板。
一旦你熟悉了这些场景,你会发现 Spring CloudStream 很容易使用。请记住,您可以使用 Docker Compose 的本地开发,也可以使用更健壮的解决方案,如 Kubernetes 这样的云基础设施,它可以提供高可用性和其他很酷的云功能。
下一章将介绍 Spring Cloud 任务以及使用 Spring Batch 处理和转换由流触发的大量数据。