深入了解 Grails 和 Cloud Foundry

工程 | Peter Ledbrook | 2011年4月21日 | ...

我之前的文章中,我向您展示了使用相应的插件将 Grails 应用程序部署到Cloud Foundry是多么容易。希望这能激起您的兴趣,您已准备好查看一个更复杂的 Grails 应用程序,它展示了 GORM 插件的强大功能并扩展了 Cloud Foundry 服务。如果您还没有 Cloud Foundry 帐户,请耐心等待。公告发布后的反响非常热烈,因此处理积压的请求需要一些时间。

GrailsTwitter

简单的 Twitter 克隆已经成为 Grails 示例应用程序的几乎标准配置,因此为 Cloud Foundry 开发另一个版本也就不足为奇了。你可以在 GitHub 上找到该代码以及其他 Cloud Foundry 示例,你还可以测试一个已经运行在云上的 应用程序实例

在这种情况下,我们使用了混合数据存储:MongoDB 存储状态消息和标签,SQL 数据库存储用户信息(因为我们使用了 Spring Security),Redis 用于缓存整体标签信息。


GrailsTwitter 的数据模型

人物还是权限领域类是标准的 Spring Security,所以我除了它们是由 Hibernate 映射的之外,不会再多说什么,因此当应用程序部署时需要 Cloud Foundry MySQL 服务。

更有趣的是状态领域类。通过向该类添加一个静态的mapWith属性,我们可以确保它的实例存储在 MongoDB 而不是 SQL 存储中。

package org.grails.twitter

import org.grails.twitter.auth.Person

class Status {
    static mapWith = "mongo"
    static transients = ["author"]
	
    String message
    Long authorId
    List<String> tags = []
    Date dateCreated
	
    Person getAuthor() {
        return Person.get(authorId)
    }
}

除此之外,它看起来像一个相当标准的 GORM 领域类。那么我们如何将存储在 MongoDB 中的状态消息与存储在 SQL 数据库中的用户链接起来呢?在这种情况下,我们不能使用标准的 GORM 一对一关系,所以我们显式地将用户的 ID 存储在状态消息中,并添加一个瞬态的、只读的作者属性,以便轻松访问关联的人物实例。知道怎么做就简单了!

你还可以看到标签不是作为单独的领域实例存储的(标签是状态消息中以“#”开头的任何字符串)。相反,每个状态将其标签列表作为纯字符串存储——这是 MongoDB 网站上建议的方法。这意味着保存和检索状态消息非常廉价,但这确实提出了一个重要问题:你如何找出系统中存在哪些标签以及每个标签出现的次数?毕竟没有“标签”表可供查询,这在关系数据库中通常会有。

这就是 MongoDB 对 MapReduce 函数的支持派上用场的地方。Map 函数将所有状态消息中的所有标签分解出来,然后 Reduce 函数将它们聚合起来,并为每个标签计算总数。你可以在TagService.cacheTags()方法中看到相关的代码。MapReduce 可能需要一些时间才能理解,但它非常适合并行计算。然而,对于单个 MongoDB 实例,此操作并非快得惊人。因此,缓存结果是有意义的。

缓存

每当我想到缓存时,我立刻想到 Ehcache。它就像 Java 平台上开源缓存的鼻祖。甚至 Grails 的 Spring Cache 插件目前也被硬编码为使用它。那么当我们的应用程序部署到云上时,我们可以使用它吗?如果我们只有一个应用程序实例,那么当然可以。你只需要配置磁盘存储的位置,尽管这并非易事,稍后我会讨论文件系统访问。但是云部署的一个关键优势是能够快速创建多个实例来处理负载,在这种情况下 Ehcache 就不是一个选项了。

缓存通常是直接的键值存储,因此目前 Cloud Foundry 上提供了一种替代方案,即 Redis 服务,并且其他缓存服务(如 vFabric Gemfire)正在开发中。在 GrailsTwitter 应用程序中,我们枚举了标签及其总计数,并将结果存储在 Redis 排序集中。这是在事务中完成的(通过redis.multi()/.exec()),确保集合原子更新——这很重要,因为可能有许多并发请求涌入标签列表。作为一个额外的奖励,排序集确保我们始终按“总计数”的顺序检索标签。

当然,由于 Redis 是一个独立的实例,你可以拥有任意数量的应用程序实例:所有这些实例都将使用相同的 Redis 服务。该应用程序还演示了如何直接使用 Redis,而不是通过 GORM API——我们没有在 GrailsTwitter 中将任何领域类映射到 Redis。

这个示例应用程序还演示了 Searchable 插件的使用,该插件基于 Compass,在这种情况下处理用户搜索。现在,该插件必须将其搜索索引存储在某个地方,那么当我们把应用程序部署到 Cloud Foundry 时,它怎么知道把它们放在哪里呢?

文件系统访问

在许多生产环境中,你提前知道可以将搜索索引等文件存储在哪里,因此你将路径放入运行时配置中。或者,你可能通过grails.config.locations设置包含外部配置文件,而运维人员会将适当的路径放入该文件中。但对于 Cloud Foundry,你无法提前知道任何可用的文件位置,也无法部署外部配置文件。

幸运的是,Cloud Foundry 在运行时通过HOME环境变量提供了一个合适的文件路径。你的应用程序所要做的就是读取它,然后使用该路径作为基础创建所需的任何目录和文件。对于 Searchable,Cloud Foundry 插件会自动将搜索索引的标准位置替换为基于HOME变量的位置,所以你无需做任何事情!

这都是很棒的东西,但云中的文件系统访问存在一个严重问题,我之前在谈论 Ehcache 时已经暗示过:如果你有多个应用程序实例,那么每个实例将拥有其自己的(不同的)文件副本。这很少是你想要的。例如,所有应用程序实例都应该共享相同的搜索索引,否则一个用户访问一个实例将获得与由不同实例提供服务的用户不同的搜索结果。

Cloud Foundry 仍处于早期阶段,因此您可以期待全年会有更多服务上线。对搜索服务(及其他服务)的请求已经提交。在此期间,我们可以提供一个变通方案,它说明了解决云中协调挑战的常见解决方案:使用 Redis 对发布/订阅消息的支持来保持文件同步。例如,在 Grails Twitter 中,我们可以在每次添加用户时触发一个事件,然后所有实例都将监听该事件并索引新用户。修改或删除用户也是如此。编写本文时,Grails 的 Redis 插件尚不支持开箱即用的发布/订阅(它即将推出!),但在该支持可用之前,您可以使用 Spring Data。

未来

正如你所见,通过明智地利用可用服务,特别是 Redis,你已经可以将中等复杂度的应用程序部署到 Cloud Foundry。而将应用程序部署到 Cloud Foundry 的简便性使其成为一个引人注目的部署目标。

尽管如此,重要的是要明白未来 Cloud Foundry 将添加更多服务,这将允许您部署具有更多功能的应用程序。随着这些服务的到来,我们致力于确保相应的 Grails 支持像现有服务一样流畅易用。所以,尽情试验,玩得开心,期待 Cloud Foundry 服务带来一些令人兴奋的补充!

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

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

了解更多

获得支持

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件,只需一份简单的订阅。

了解更多

即将举行的活动

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

查看所有