内容


Java 开发 2.0

通过 Heroku 的 PaaS 用 Git 提交 Java 应用程序

Heroku 扩展其 Ruby 功能以实现 Java 应用程序可扩展性

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: Java 开发 2.0

敬请期待该系列的后续内容。

此内容是该系列的一部分:Java 开发 2.0

敬请期待该系列的后续内容。

Java 开发 2.0 系列的近期文章已将重点转移到适用于 Java 开发的 PaaS (Platform as a Service) 平台上。此次我将邀请您了解 Heroku,这是最近推出的另一种用来支持 Java 应用程序的流行 PaaS 系统。

Heroku 植根于 Ruby,在它上进行 Java 应用程序开发和部署的方法与之前使用的其他 Java PaaS 选项截然不同,尤其是 Amazon 的 Elastic Beanstalk 和 Google App Engine (GAE)。在开始介绍 Heroku 之前,我认为先了解一下它与这两种平台的共同点和不同之处对您会有所帮助。

GAE 和 Beanstalk:两种重量级类型

我们从 Java 开发 2.0:攀登 Elastic Beanstalk 了解到,Google App Engine 和 Amazon 的 Elastic Beanstalk 在灵活性方面是截然不同的。Java 开发 2.0:使用 Google App Engine 是一个非常紧凑的沙箱,要求您遵守规则;而 Elastic Beanstalk 是完全自定义的:如果在 JVM 上运行它,Amazon 的 PaaS 会支持您使用它。GAE 限制了您的 Java 库选项,而且还控制大多数应用程序的扩展方式。实际上,将应用程序部署到 GAE 是一个放松式的练习:您不用知道 Web 应用程序的位置或者是有多少应用程序实例正在运行。从好的方面讲,Google 承担所有扩展工作,毫无疑问,GAE 应用程序扩展非常好。释放控制权意味着您无需担心太多,当然,除了编写可靠的代码。

Java 开发 2.0:攀登 Elastic Beanstalk 是另一个极端。除了为您提供工具选择外,它还提供了很多控制,控制应用程序如何扩展。这样做的缺点是显而易见的:没有动手参与,Amazon 所能做的工作很少。但是,如果您想要优化应用程序部署,并且想要一个高度可扩展的基础架构,那么 Amazon 的 Elastic Beanstalk 无疑是一个好的选择。

考虑到编码和部署的易用扩展,可将扩展范围设置为 10 至 0(10 表示很痛苦 而 0 表示很轻松)之间的数字,我将 GAE 设置为 4,将 Elastic Beanstalk 设置为 6。GAE 支持我无缝地运行脚本和上传文件,但是有的时候,有限的兼容开发工具并不是我想要的。相反,Elastic Beanstalk 支持我使用想要的任意库,但是部署过程可能更长且有时更枯燥;我会增加复杂性以获得更多控制权。

挑战者 Heroku

与 GAE 和 Elastic Beanstalk 一样,Heroku 旨在进行水平扩展。将您的代码部署到 Heroku 称为 dynos 的地方,从本质上讲,它们是 Web 容器。如果您想要扩展系统,只需添加更多 dynos,便可让 Heroku 处理更多的同步 Web 请求。这是一个简单概念,比 GAE 提供的控制权更多,并且无需满足 Elastic Beanstalk 的配置要求。

对于 Heroku,Git(不是 Ant 或 Maven)是您的部署管道。当您准备好部署应用程序时,可通过 Git push 命令来完成此操作,本文章后面 将详细介绍这一命令。

在易用扩展方面,与 GAE 和 Elastic Beanstalk 相比,Heroku 的扩展范围为 2,这意味着接近很轻松。Heroku 在工具方面提供了极大的灵活性,因此我可以为任意指定的工作选择最高效的工具。在配置方面,Heroku 提供了比 GAE 更多的控制权,但它提供的控制权少于 Elastic Beanstalk,这样的配置有时恰到好处。您还可以轻松地将在 Heroku 上构建的应用程序迁移到 Elastic Beanstalk:如果需要更细粒度的控制,而不是只想了解应用程序的可扩展性,那么可以继续阅读后面的内容!

Heroku 入门

要开始了解 Heroku,您需要安装和设置下列内容:

注意,Maven 不是 Heroku 所必需的,在这里我只将它用作构建工具。Heroku 的 Java 文档也基于 Maven。

构建和运行应用程序

安装所有内容之后,请选择将要工作的目录并运行下列 Maven 命令(为了适应页面宽度,对代码进行了分行):

mvn archetype:generate -DarchetypeGroupId=org.mortbay.jetty.archetype 
  -DarchetypeArtifactId=jetty-archetype-assembler -DarchetypeVersion=7.5.0.RC0

Maven 会提示您提供 groupIdartifactId。我通常为 groupId 使用我的包名,为 artifactId 使用我的项目名。然后,Maven 将生成一个项目结构,能够在 Jetty 的基础上构建并运行 Web 应用程序。因此,只需一个步骤,即可搭建可部署到 Heroku 的云基础架构中的 Web 应用程序框架。尽管 Maven 原型使用的是 Jetty,但 Heroku 并不仅在 Web 服务器端支持 Jetty。实际上,Heroku 甚至不知道 Jetty,或者说它没有注意到 Jetty。

之前的 Maven 命令生成的默认应用程序是一个普通的、单调的 "hello world" 应用程序。实际上,如果您浏览到应用程序的 src 目录,然后选择 main—>webapp,就会看到一个 index.html 文件。运行应用程序和该文件将(您可能已经猜到)输出文本 "hello world"。

运行应用程序也非常简单。如果您在新生成的项目目录(具有您为 artifactId 命名的名称)中输入命令 mvn install,将为您所需的操作系统(我的是 OSX)生成一个 shell 脚本。只需输入 $>sh target/bin/webapp,然后在您喜欢的浏览器中输入 http://localhost:8080 即可。

现实世界中的 Heroku

使用 Heroku,您可以部署想要的任何 Java 库。为了举例进行说明 Heroku 并让人们熟悉它(至少为了正在阅读本文的人们),我将构建我的位置收集移动 Web 服务 Magnus 的另一个典型应用程序,已在 Amazon 的 Elastic Beanstalk 简介中介绍了它(参见 参考资料)。对于我的 RESTful 库,我计划使用 Apache Wink,这是 JAX-RS 规范的实现。我的 Web 服务实现将提供一个 PUT 端点,接收 JSON 并将从文档获得的相关数据插入到 MongoDB 实例中。因此,这将通过 Morphia 在 MongoHQ 上进行托管(参见 参考资料)。

我必须做的第一件事是使用新 Wink 和 Morphia 依赖项更新 Magnus 的 Maven pom.xml 文件,如清单 1 所示:

清单 1. 将 Wink 和 Morphia 添加到 Maven POM
<dependency>
 <groupId>org.apache.wink</groupId>
 <artifactId>wink-server</artifactId>
 <version>1.1.3-incubating</version>
</dependency>

<dependency>
 <groupId>org.apache.wink</groupId>
 <artifactId>wink-json-provider</artifactId>
 <version>1.1.3-incubating</version>
</dependency>

<dependency>
 <groupId>com.google.code.morphia</groupId>
 <artifactId>morphia</artifactId>
 <version>0.99</version>
</dependency>

请注意,我还更新了 POM 文件,以查看内部的 Morphia Maven 存储库并获得版本 0.99:

清单 2. 将新的存储库添加到 Maven POM
<repositories>
 <repository>
  <id>morphia repository</id>
  <url>http://morphia.googlecode.com/svn/mavenrepo/</url>
 </repository>
</repositories>

接下来,我将创建位置资源,即一个将表示用户位置的 Wink 端点:

清单 3. 创建一个 Wink LocationResource
@Path("/location")
public class LocationResource {

 @PUT
 @Consumes(MediaType.APPLICATION_JSON)
 @Path("{id}")
 public String updateAccountLocation(@PathParam("id") int accountId, JSONObject 
  requestJSON) {
  try{

   double latitude = Double.parseDouble(requestJSON.get("latitude").toString());
   double longitude = Double.parseDouble(requestJSON.get("longitude").toString());

   SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy HH:mm");
   Date dt = formatter.parse(requestJSON.get("timestamp").toString());

   new Location(accountId, dt, latitude, longitude).save();
   return new JSONObject().put("status", "success").toString();
  }catch(Exception e){
   return "failure with " + requestJSON;
  }
 } 
}

Wink 让我可以通过三个注释来描述我的 RESTful 服务:一个注释描述我的 HTTP 方法 (PUT),一个描述我的预期请求内容类型 (JSON),最后一个表明端点接受参数(在本例中是 location/:accountId)。

Location 类是我在 Elastic Beanstalk 简介 中介绍的 Morphia-backed 对象。它仅在 MongoHQ 中创建一个文档,表示指定帐户的位置。位置(从理论上讲,是通过移动设备接收的)由 RESTful 端点的参数表示。

Wink 和 Jetty

接下来,我想将 Wink 与 Jetty 相连接,因此我需要进行两个操作:创建一个 Application 类并在我的 web.xml 文件中进行配置。

Wink Application 类(如清单 4 所示)的用途是加载相应的资源类:

清单 4. 一个 Wink Application 类
public class MarmarisApplication extends Application {
 @Override
 public Set<Class<?>> getClasses() {
  Set<Class<?>> classes = new HashSet<Class<?>>();
  classes.add(LocationResource.class);
  return classes;
 }
}

在清单 5 中,我将更新应用程序的 web.xml 文件,添加特定于 Wink 的属性,比如指向我的 Application 类的一个指针和所需的 URL 模式,在本例中是 /service/resource

清单 5. 通过 web.xml 连接 Wink
<servlet>
 <servlet-name>MarmarisApp</servlet-name>
 <servlet-class>org.apache.wink.server.internal.servlet.RestServlet</servlet-class>
 <init-param>
  <param-name>javax.ws.rs.Application</param-name>
  <param-value>com.b50.marmaris.MarmarisApplication</param-value>
 </init-param>
 <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
 <servlet-name>MarmarisApp</servlet-name>
 <url-pattern>/service/*</url-pattern>
</servlet-mapping>

作为测试,可以尝试重新运行 mvn install 命令,并在本地启用应用程序(在本例中是 Magnus)。您应该能够将 JSON 文档(如清单 6 所示) 提交到端点 http://localhost:8080/service/location/{account_id},其中 account_id 是一个数字。

清单 6. 表示位置的一个 JSON 文档
{
 "name":"location payload",
 "latitude":"46.49",
 "longitude":"71.11",
 "timestamp":"09-02-2011 14:43"
}

编写此应用程序并不难,现在我们将开始一个更简单的部分:在 Heroku 的云中部署它!

Heroku 和 Git

Heroku 的开发管道是 Git,如果您不熟悉分布式版本控制,可进行一些调整。(通过 Git)部署到 Heroku 类似于将 Subversion 提交发送到主管道外的一个分支。但是,在本例中,Heroku 不是主代码存储库;它只是一个备用远程存储库。要部署应用程序,将通过 Git 对其源代码执行 push 操作。

注意,不使用 war 文件,Heroku 将一原生的形式来部署您的项目。正如当我们开始构建应用程序的 Procfile 时所看到的那样,Heroku 只是寻找要执行的代码的一个 JVM。(如果您现在想了解我谈论的内容,请参阅在项目的包结构中生成的 Main.java 文件,然后将其中的内容与您 POM 中的各项相关联。)

尽管此处的描述不是非常直观,但当您开始使用 Heroku 的部署模型时就会发现它很有用。此外,Heroku 与 Git 的紧密集成使得将各种分支推送到不同环境中变得快速而又简捷。

两阶段部署

部署设置分两个步骤进行:首先,您需要创建代码并将它提交到一个本地 Git 存储库。可以在项目的根目录中完成此操作,方法是在终端中输入清单 7 中的命令:

清单 7. Git 初始化和提交
$> git init
$> git add .
$> git commit -m "initial commit before Heroko deployment"

现在,您的本地 Git 存储库包含了代码的快照(即已对代码进行了版本控制)。

接下来,要在 Heroku 中创建一个应用程序。我已在清单 8 中通过 heroku 命令行客户端(这里是有必要安装 Ruby 和 RubyGems 的位置)完成了这一操作:

清单 8. 创建 Heroku 应用程序
$> heroku create marmaris --stack cedar

清单 8 中的 heroku create 命令在 cedar stack 上创建了一个名为 "marmaris" 的应用程序。注意,您必须选择一个不同的应用程序名称,比如此时采用的名称。此外,可以将名称保留为 Heroku,它将为您生成一个独一无二的名称。

Heroku 有许多堆栈。Cedar 支持 Java 和 Node.js,而其他平台(比如 Bamboo)支持较新的 Ruby 版本。当您执行 heroku create 命令时,将更新您的 Git 配置并添加一个名为 heroku 的远程存储库。

Heroku 的 Procfile

在将代码基部署到 Heroku 之前,您需要告诉它如何运行应用程序。可使用 Procfile 轻松地完成这项操作,它只是一个描述命令的文本文件。如下所示,我的 Procfile 指向 target 目录中的 webapp shell 脚本。

清单 9. 创建 Procfile
$> echo 'web: sh target/bin/webapp' > Procfile

创建了新文件后,通知 Git 也很重要,否则在您通过 Git 对部署应用程序执行 push 操作时,Heroku 有可能不知道该程序。

清单 10. 通知 Git
$> git add Profile
$> git commit -m "updated to include my Profile"

最后,要部署应用程序,只需将 Git pushheroku 远程存储库重即可,如清单 11 所示:

清单 11. 部署到 Heroku
$> git push heroku

您应该看到 heroku 返回的一系列信息,请寻找类似下列内容的一则信息:

http://your_app_name.herokuapp.com deployed to Heroku

在您喜欢的浏览器中输入 URL,(假设您在项目目录中保留了默认的 index.html 文件)您将看到 "hello, world!" 输出。

尽管 "hello, world!" 总是十分有趣,但此应用程序的用途是通过 HTTP PUT 接收位置信息。因此,通过使用 WizTools.org 的 RESTClient,我可以根据 RESTful 端点发出 HTTP PUT 命令,提供一个 JSON 文档。我使用词 success 发送了一个很好的 JSON 响应。

扩展和维护 Heroku

默认情况下,Heroku 在一个 dyno 上运行您的应用程序。此 dyno 是免费的,实际上,它会在没有活动时自行关闭,并在出现请求时开启。如果您需要扩展应用程序,可添加更多 dyno,这可通过 heroku scale 命令来完成:

清单 12. 扩展应用程序
$> heroku scale web=2

如果您需要缩小应用程序的规模,还可以通过减少请求 dyno 的数量来缩减应用程序。例如,在本例中,我将该数量缩减为 web=1。也可通过 Heroku 的 Web 界面来完成这两项操作。

您还可以通过清单 13 中的 heroku logs 命令在 Heroku 上跟踪日志:

清单 13. 实时查看 Heroku 上的日志
$> heroku logs -t

heroku 命令行客户端支持许多特性来扩展、监控并管理您的应用程序;参见 参考资料 了解文档。Heroku 还支持许多第三方加载项,除了默认的数据存储。我建议检查 Heroku 对 MongoHQ 和 PostgreSQL 的支持。

结束语

Heroku 与 Git 的紧密集成提供了一种通过云部署和扩展 Java 应用程序的新模式,但是这也使过程变得非常简单且强大。总之,自由使用无限的 Java 库,然后轻松地部署生成的应用程序,这的确很适合我。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology, Cloud computing
ArticleID=781606
ArticleTitle=Java 开发 2.0: 通过 Heroku 的 PaaS 用 Git 提交 Java 应用程序
publish-date=12192011