先行一步
VMware 提供培训和认证,助您加速发展。
了解更多在我的上一篇文章中,我展示了使用相应的插件将 Grails 应用程序部署到 Cloud Foundry 是多么容易。希望这激发了您的兴趣,您已准备好查看一个更复杂的 Grails 应用程序,它展示了 GORM 插件的强大功能并扩展了 Cloud Foundry 服务。如果您还没有 Cloud Foundry 账户,请耐心等待。公告发布后的反响非常热烈,因此处理积压的请求需要一些时间。
简单的 Twitter 克隆已几乎成为 Grails 样本应用程序的标准,因此毫不奇怪又为 Cloud Foundry 开发了一个新版本。您可以在 GitHub 上找到代码以及其他 Cloud Foundry 样本,您还可以试用已经在云上运行的 应用程序实例。
在这种情况下,我们混合使用了数据存储,其中 MongoDB 存储状态消息和标签,SQL 数据库用于存储用户信息(因为我们使用了 Spring Security),而 Redis 用于缓存整体标签信息。
“Person”和“Authority”领域类是标准的 Spring Security 类,所以除了它们通过 Hibernate 映射,因此在应用程序部署时需要 Cloud Foundry MySQL 服务之外,我不会再说更多关于它们的内容。
更值得关注的是“Status”领域类。通过添加一个静态“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,并添加一个瞬态的、只读的“author”属性,以便轻松访问关联的“PersonPerson
”实例。知道方法后就简单了!您还可以看到,标签不是作为单独的领域实例存储的(标签是状态消息中以 '#' 开头的任何字符串)。相反,每个“StatusStatus
”将其标签列表存储为普通字符串——这是 MongoDB 网站上建议的一种方法。这意味着保存和检索状态消息非常廉价,但这确实提出了一个重要问题:您如何找出系统中存在哪些标签以及每个标签有多少个出现次数?毕竟没有可以查询的“tag”表,而这在关系型数据库中通常是会有的。这就是 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 插件会自动将搜索索引的标准位置替换为基于“HOMEHOME
”变量的位置,所以您无需做任何事情!这一切都很棒,但是云上的文件系统访问存在一个严重问题,我之前在谈论 Ehcache 时曾暗示过:如果您有多个应用程序实例,那么每个实例都会有自己(不同)的文件副本。这通常不是您想要的。例如,所有应用程序实例应该共享相同的搜索索引,否则访问一个实例的用户会获得与由不同实例服务的用户不同的搜索结果。
Cloud Foundry 仍处于早期阶段,因此预计今年会有更多服务上线。搜索服务(以及其他服务)的请求已经在处理中。在此期间,我们可以提供一个权宜之计,它说明了云中协调挑战的常见解决方案:使用 Redis 对发布/订阅消息的支持来保持文件同步。例如,在 Grails Twitter 中,我们可以在每次添加用户时触发一个事件,然后所有实例都会侦听该事件并索引新用户。修改或删除用户也是如此。在撰写本文时,Grails 的 Redis 插件还不直接支持发布/订阅(即将支持!),但在该支持可用之前,您可以使用 Spring Data。
如您所见,通过合理利用可用的服务,特别是 Redis,您已经可以将中等复杂度的应用程序部署到 Cloud Foundry。而且,将应用程序部署到 Cloud Foundry 的简便性使其成为一个引人注目的部署目标。
尽管如此,重要的是要理解未来会有更多服务添加到 Cloud Foundry,这将使您能够部署具有更多功能的应用程序。随着这些服务的到来,我们致力于确保相应的 Grails 支持像现有支持一样流畅易用。所以请实验,享受乐趣,并期待 Cloud Foundry 服务的一些令人兴奋的补充!