使用 Cloud Foundry Workers 与 Spring

工程 | Josh Long | 2012年5月9日 | ...

您可能已经阅读了Jennifer Hickey 的精彩博文,例如介绍 Cloud Foundry 工作进程、如何在设置Ruby Resque 后台作业中使用它们,以及今天发布的介绍 Spring 支持的文章

Spring 开发人员的关键要点

  1. 您需要使用 gem update vmc 更新您的 vmc 版本。
  2. Cloud Foundry 工作进程允许您运行 public static void main 作业。也就是说,Cloud Foundry 工作进程基本上是一个进程,比 Web 应用程序更低级,这自然映射到许多所谓的后台作业。
  3. 您需要提供 Cloud Foundry 将运行的命令。您可以提供您希望它使用的 java 命令,但更简单的方法是提供一个 shell 脚本,让 Cloud Foundry 为您运行该 shell 脚本。您提供的命令应使用 $JAVA_OPTS,Cloud Foundry 已提供该命令以确保一致的内存使用和 JVM 设置。
  4. 有多种方法可以自动化 Cloud Foundry 可部署应用程序的创建。如果您使用 Maven,则org.codehaus.mojo:appassembler-maven-plugin 插件将帮助您创建一个启动脚本并打包您的 .jar 文件以方便部署,以及指定一个入口点类。
  5. 其他所有内容基本上都相同。当您对 Java .jar 项目执行 vmc push 时,Cloud Foundry 会询问您应用程序是否为独立应用程序。确认后,它将引导您完成后续设置。

因此,让我们看一下几个常见的架构和安排,它们使用 Cloud Foundry 工作进程更容易且更自然。我们将根据 Spring 框架和两个相关项目(Spring IntegrationSpring Batch)来查看这些模式,这两个框架在 Web 应用程序内和外部都非常出色。正如我们将看到的,这两个框架都支持解耦和改进的组合性。我们将发生的事情发生的时间分离,并将发生的事情发生的地点分离,这一切都是为了释放前端的容量。

我有一个需要遵守的时间表!

我经常被问到的一个问题是:如何在 Cloud Foundry 上进行作业调度?Cloud Foundry 支持 Spring 应用程序,而 Spring 当然一直以来都支持企业级调度抽象,例如Quartz 和 Spring 3.0 的 @Scheduled 注解。@Scheduled 非常棒,因为它非常易于添加到现有应用程序中。在最简单的情况下,您将 @EnableScheduling 添加到您的 Java 配置或 <task:annotation-driven/> 添加到您的 XML 中,然后在代码中使用 @Scheduled 注解。这在企业应用程序中是非常自然的事情 - 也许您有一个需要运行的分析或报告流程?一些长时间运行的批处理流程?我提供了一个示例,演示了如何使用 @Scheduled 运行 Spring Batch Job。Spring Batch 作业本身是一个工作线程,它与一个 Web 服务一起工作,该服务的糟糕 SLA 使其不适合实时使用。在 Spring Batch 中处理工作更安全、更简洁,其恢复和重试功能可以弥补任何网络中断、网络延迟等带来的不足。我将您引导至代码示例以了解大部分详细信息,我们将只查看入口点,然后查看如何将应用程序部署到 Cloud Foundry。

					    
// set to every 10s for testing.
@Scheduled(fixedRate = 10 * 1000)
public void runNightlyStockPriceRecorder() throws Throwable {
	JobParameters params = new JobParametersBuilder()
		.addDate("date", new Date())
		.toJobParameters();
	JobExecution jobExecution = jobLauncher.run(job, params);
	BatchStatus batchStatus = jobExecution.getStatus();
	while (batchStatus.isRunning()) {
		logger.info("Still running...");
		Thread.sleep(1000);
	}
	logger.info(String.format("Exit status: %s", jobExecution.getExitStatus().getExitCode()));
	JobInstance jobInstance = jobExecution.getJobInstance();
	logger.info(String.format("job instance Id: %d", jobInstance.getId()));
}

此方法将按照 @Scheduled 注解规定的频率调用:在本例中,每 10 秒调用一次。使用 JobLauncher 启动 Job 后,应用程序将阻塞(可以避免这种情况,但在本例中,由于我们预期它会阻塞,因此是可以的)。根据数据和批处理流程的类型,它可能会阻塞几分钟……或者一周!Spring Batch 是一个强大的批处理引擎,它旨在安全地处理大型数据集。批处理流程通常不属于与 HTTP 请求相同的管道 - 它具有确定性和长时间运行的特点,因此能够在 Cloud Foundry 中使用长时间运行的工作进程在这里是一个真正的优势。

让我们将应用程序部署到 Cloud Foundry。示例应用程序是 Maven 项目。要构建它们,请转到代码库的根目录,并使用Maven 构建工具运行 mvn clean install

从这里开始,应用程序的部署很容易,假设您已经设置了 vmc 命令行工具并更新到最新版本,正如 Jennifer 在第一篇文章中解释的那样。该应用程序在代码的根目录中附带一个 manifest.yml 文件。Cloud Foundry manifest.yml 文件完整地描述了应用程序期望平台即服务为其提供的全部内容,包括要使用的数据库、分配多少 RAM 等。Cloud Foundry 读取此清单并在部署应用程序时满足其要求。因此,仅仅通过部署此应用程序,您还将获得此应用程序需要的 PostgreSQL 数据库实例以存储示例数据,以及 Spring Batch 为其运行时状态维护的表。


jlongmbp17:cf-workers-batch jlong$ vmc --path target/appassembler/ push 
Pushing application 'batch'...
Creating Application: OK
Creating Service [stock_batch]: OK
Binding Service [stock_batch]: OK
Uploading Application:
  Checking for available resources: OK
  Processing resources: OK
  Packing application: OK
  Uploading (55K): OK   
Push Status: OK
Staging Application 'batch': OK                                                 
Starting Application 'batch': OK      
                                          
jlongmbp17:cf-workers-batch jlong$ vmc apps
+---------------------+----+---------+----------------------------------+----------------+
| Application         | #  | Health  | URLS                             | Services       |
+---------------------+----+---------+----------------------------------+----------------+
| batch               | 1  | RUNNING |                                  | stock_batch    |
...

计划方法每十秒运行一次,因此请等待十秒钟,然后再执行后续步骤。接下来,我们将登录到数据库以查看我们的流程做了什么。Spring Batch 作业获取 STOCKS 表中的所有股票代码,然后查找其当前价格信息并将该快照数据插入 STOCKS_DATA 表中。vmc tunnel 命令创建一条直接进入云管理的数据库的隧道。我使用它进入我们的 PostgreSQL 实例并在 STOCKS_DATA 表上运行查询(SELECT * FROM STOCKS_DATA)。


jlongmbp17:cf-workers-batch jlong$ vmc tunnel stock_batch
Binding Service [stock_batch]: OK
Stopping Application 'caldecott': OK
Staging Application 'caldecott': OK                                             
Starting Application 'caldecott': OK                                            
Getting tunnel connection info: OK

Service connection info: 
  username : u59993cf15bc0461a1d2648f7eab27f2da15f
  password : p1be9035f3c764817809ac81f3267
  name     : d5c5a80838d7bcd79dc7eefa6c1b04d22

Starting tunnel to stock_batch on port 10002.
1: none
2: psql
Which client would you like to start?: 2
Launching 'psql -h localhost -p 10002 -d d5c5aefa6c1b04d2280838d7bcd79dc7e -U u59993cf15bc04817809ac861a1d2648f -w'

psql (9.0.5, server 9.0.4)
Type "help" for help.

d5c5aefa6c1b04d2280838d7bcd79dc7e=> select * from stocks_data;
 id | date_analysed | high_price | low_price | closing_price | symbol 
----+---------------+------------+-----------+---------------+--------
  1 | 2012-05-08    |        613 |     602.3 |         602.3 | GOOG
  2 | 2012-05-08    |      30.78 |     30.25 |         30.25 | MSFT
  3 | 2012-05-08    |      27.87 |     27.56 |         27.56 | ORCL
  4 | 2012-05-08    |      32.41 |     32.06 |         32.06 | ADBE
  5 | 2012-05-08    |     107.38 |       103 |           103 | VMW
...

成功了!在我们继续之前,现在是关闭应用程序的最佳时机。每十秒钟查找一次这些股票并将新记录插入其中绝对没有任何好处!在几个小时内,该数据集很可能变得非常庞大。我建议您更改频率(请参阅注释以了解可以在 @Scheduled 注解中使用的 cron 表达式,该表达式在每个工作日的晚上 11 点运行),然后执行 vmc --path target/appassembler/ update,或者直接将其停止。

jlongmbp17:cf-workers-batch jlong$ vmc stop batch

因此,批处理和作业调度是 Spring 和 Spring Batch 以及 Cloud Foundry 轻松处理的两个非常常见的用例。另一个(不同但同样常见)的用例是为易于扩展而构建架构。毕竟,当您的下一个大型应用程序被奥普拉效应/./ 影响时,扩展到底意味着什么?现在让我们来看看这一点……

嘿!去队列后面排队!

这有点违反直觉,但一个包含许多小型、专注于单一功能的组件的系统比一个单体应用程序更容易扩展,因为各个工作负载可以独立于应用程序的其他部分进行扩展和调整。假设您有一个资源匮乏的前端 Web 应用程序,不应将其与速度较慢、可能存在 I/O 负担的功能捆绑在一起。解耦主线程与速度较慢的后端流程的一种简单方法是引入消息队列,例如 RabbitMQ。在消息传递中,我们讨论的是积极的消费者模式,其中工作被排队,而工作进程会尽快地将这些工作出列并完成。如果工作进程无法跟上新工作的可用性,则可以轻松添加新的工作进程来弥补不足:只需 vmc instances my-backend +10!每个单独的请求可能仍然需要固定时间,但系统的整体吞吐量会增加。

对于我们的下一个示例 - 除了它是另一种在 Cloud Foundry 上使用独立工作进程的方法之外,与我们的上一个示例几乎没有关系 - 我们将研究构建面向消息的服务。我们将使用RabbitMQ,这是一个世界级的消息队列,可在 Cloud Foundry 上用作服务。消息传递系统只是众所周知的邮箱。它们接受消息并分发消息。通过将您的API简化为像 RabbitMQ 这样的消息代理(它本身基于 AMQP 协议),您为应用程序提供了尽可能友好的接口。消息传递系统本质上也是异步的,因此它们不会强加请求/回复交换的概念,尽管也支持这种交换。如果您想利用 RabbitMQ 与其他工作进程集成,您仍然可以为您的服务消费者公开一个外观网关,这样他们就不需要知道服务是如何实现的(如果您不希望他们知道)。我们将使用 Spring Integration,它支持企业集成模式,从 Java 接口类型构建一个消息网关

```java

public interface StockSymbolLookupClient { StockSymbolLookup lookupSymbol (String symbol) throws Throwable; }

<P>With Spring Integration's help, calls to this method will result in a message being sent to RabbitMQ where, on the other side, our service will dequeue the message, process it, and then, eventually send a reply back to RabbitMQ, which the caller of this method will receive  as the reply value.  </P>  <P> To configure the client, I used a little bit of Spring Integration to setup the gateway, which then forwards the request to the outbound AMQP gateway adapter.</P>

```xml

<?xml version="1.0" encoding="UTF-8"?>
	<beans:beans ...>
	    ...
	    <gateway
	            service-interface="org.cloudfoundry.workers.stocks.integration.client.StockClientGateway"
	            default-request-channel="outboundSymbolsRequests"
	            default-reply-channel="outboundSymbolsReplies"
	    />

	    <amqp:outbound-gateway
	            request-channel="outboundSymbolsRequests"
	            reply-channel="outboundSymbolsReplies"
	            routing-key="tickers"
	            amqp-template="amqpTemplate"
	    />
	</beans:beans>

我省略了 RabbitMQ 特定连接机制的详细信息(当然,所有这些都使用 cloudfoundry-runtime API 完成) - 请参阅客户端示例。此代码片段显示了重要部分:Spring 将基于 StockClientGateway interface 在运行时合成一个实现,我们可以注入该实现,然后从客户端代码中使用它来调用服务。

在服务端,我们需要一些代码从 RabbitMQ 中取出消息,然后转发到工作节点,最后将结果作为回复通过 RabbitMQ 传回。当然,服务端可以独立于客户端进行扩展。您可能每个 Web 应用程序对应一个客户端,但运行十个服务实现,来分担压力并满足额外的需求。以下是我们的服务实现代码,再次省略 RabbitMQ 特定的连接 Bean(查看示例


<?xml version="1.0" encoding="UTF-8"?>
	<beans:beans ...>
	    ...
	    <amqp:inbound-gateway request-channel="inboundSymbolsRequests"
		    queue-names="tickers"
		    message-converter="mc"
		    connection-factory="connectionFactory"/>

	    <service-activator ref="client" input-channel="inboundSymbolsRequests" requires-reply="true"/>

	</beans:beans>

这种方法非常强大。在我的示例中,我只是简单地推迟了 RESTful Web 服务调用的微不足道的成本。但您可以想象用这种方式做更多更复杂的事情 - 图像处理、批处理作业、大型分析等。

此示例的部署稍微复杂一些,因为它包含两个部分。网关实现是一个简单的 Bean,Spring Integration 将其作为消息系统的客户端代理提供。我们可以从另一个后台作业、Web 应用程序或任何其他地方使用网关。在我们的例子中,我们的客户端 - 一个 Spring MVC 应用程序 - 使用网关来调用服务。服务提供了有趣的功能,接收来自预配的 RabbitMQ 实例的请求,并调用股票行情查询 Bean,然后通过 RabbitMQ 将结果返回给请求者。我们需要先部署服务。和之前一样,如果您还没有,需要在根目录下运行 mvn clean install 来构建整个代码库。然后,从 cf-workers-integration-service 文件夹的根目录下运行以下命令


jlongmbp17:cf-workers-integration-service jlong$ vmc --path target/appassembler/ push 
Pushing application 'integration-service'...
Creating Application: OK
Creating Service [stock_rabbitmq]: OK
Binding Service [stock_rabbitmq]: OK
Uploading Application:
  Checking for available resources: OK
  Processing resources: OK
  Packing application: OK
  Uploading (44K): OK   
Push Status: OK
Staging Application 'integration-service': OK                                   
Starting Application 'integration-service': OK                                  

服务就位后,让我们从 Web 客户端调用它一次来测试它。返回到 cf-workers-integration-webclient 目录。然后,运行与服务相同的命令。

jlongmbp17:cf-workers-integration-webclient jlong$ vmc --path target/cf-workers-integration-webclient-1.0-SNAPSHOT push
Pushing application 'integration-webclient'...
Creating Application: OK
Binding Service [stock_rabbitmq]: OK
Uploading Application:
  Checking for available resources: OK
  Processing resources: OK
  Packing application: OK
  Uploading (3K): OK   
Push Status: OK
Staging Application 'integration-webclient': OK                                 
Starting Application 'integration-webclient': OK                                                         

应用程序部署完成后(您可以通过运行 vmc apps 确认两者都运行正常)。现在,让我们打开客户端并试用一下我们的服务:打开 Web 客户端应用程序的 URL。在我的例子中,URL 是 integration-webclient.cloudfoundry.com。它应该会提供一个简单的表单。输入股票代码(我用 VMW 测试...),然后按回车键。您应该会看到页面上显示数据。

然后,如果您查看 integration-service 的日志,您应该会看到控制台输出与您在客户端看到的相同信息。如果是这样,恭喜您!

下一步

此时,您拥有了强大的功能。Cloud Foundry 工作节点是处理不适合 HTTP 请求处理管道的理想场所。根据我的经验,这包括很多事情:批处理、集成和消息传递代码、分析、报告、大数据和补偿事务,以及确实通过 HTTP 以外的其他协议公开的系统。

获取 Spring 新闻通讯

与 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,以加速您的进步。

了解更多

获取支持

Tanzu Spring 在一个简单的订阅中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部