领先一步
VMware 提供培训和认证,助您加速进步。
了解更多在我第二篇关于 Grails 和 Cloud Foundry 的博客中,我介绍了一个Grails Twitter 示例的变体,它可以在CloudFoundry.com上托管。当时我提到,使用 Searchable 插件进行全文搜索会将您限制在一个应用程序实例中,因为搜索索引对于每个实例都是唯一的。换句话说,您可能会根据浏览器路由到的应用程序实例,非常容易地获得不同的搜索结果。
我还说过,解决这个问题的办法之一是同步各个实例的搜索索引。但这听起来并不容易,是吗?恰好,RabbitMQ 服务被引入 Cloud Foundry 意味着所需的代码更改比您预期的要小得多。那么,让我们看看我是如何为 Grails Twitter 状态消息添加全文搜索的。
Searchable 插件有一个很强的假设,即您想要索引标准的 GORM 域类。这意味着 Hibernate/SQL。但 Grails Twitter 状态消息存储在 MongoDB 中,而不是 MySQL。我们能让它们可搜索吗?是的,我们可以,但会牺牲一些功能。
与普通域类一样,搜索的第一个步骤是为状态实例添加一个searchable属性
package org.grails.twitter
import org.grails.twitter.auth.Person
class Status {
static mapWith = "mongo"
static transients = ["author"]
static searchable = {
only = ["message", "dateCreated"]
authorId index: "no", store: "yes"
}
String message
Long authorId
List<String> tags = []
Date dateCreated
Person getAuthor() {
return Person.get(authorId)
}
static constraints = {
message maxSize: 160
}
}
在这种情况下,我想能够搜索创建日期和消息内容,但仅此而已。我还想在搜索结果中链接到消息的作者。但是,如果authorId未被索引,那么搜索结果将不包含发布者的 ID。因此,我将其存储在authorId索引中,但不对其进行搜索(index: "no")。很简单,不是吗?当显示搜索结果时,它们现在可以包含每条消息作者的姓名。
索引非 Hibernate 域类的一个重要限制是镜像将不起作用。这意味着新消息在保存时不会自动索引。幸运的是,我们实际上不想要这种行为,所以我禁用了镜像和“启动时批量索引”,在Config.groovy:
searchable {
...
mirrorChanges = false
bulkIndexOnStartup = false
}
当然,我们确实希望在启动时对状态消息进行索引,因为 Cloud Foundry 上的文件系统是暂时的,因此搜索索引在每次启动时都需要重新构建。但是自动索引也不能与非 Hibernate 域类一起工作,因此我在BootStrap.groovy:
...
class BootStrap {
def searchableService
def springSecurityService
def init = { servletContext ->
...
// Index all Hibernate mapped domain classes.
searchableService.reindex()
// Index all status messages.
def statusMessages = Status.list()
log.info "Indexing ${statusMessages.size()} status messages"
Status.reindex(statusMessages)
log.info "Finished indexing"
}
...
}
的末尾进行了手动索引。这并不是很多代码,但足以使状态消息可搜索。剩下要做的就是确保新消息被索引并且应用程序实例之间的搜索索引得到同步。
保持搜索索引同步的基本模型非常简单:
每次保存状态消息时,都会向 RabbitMQ 代理发送一条消息,然后代理将其转发给所有应用程序实例。然后每个实例都会索引状态消息标识的实例。
在实现此功能之前,我们需要安装 RabbitMQ 插件。
grails install-plugin rabbitmq
下一个任务是配置代理,包括适当的交换机和队列。我之前已经写过关于AMQP 协议和RabbitMQ 插件的文章,所以我在这里不会详细介绍交换机和队列。只需说我们只需要一个扇出交换机(所有消息都路由给所有侦听器)和一个订阅该交换机的 Grails 服务。所以,在Config.groovy我添加了
rabbitmq {
connectionfactory {
username = 'guest'
password = 'guest'
hostname = 'localhost'
}
queues = {
exchange name: 'search.sync', type: fanout, durable: false
}
}
重要部分是交换机声明:当应用程序部署到 Cloud Foundry 时,连接工厂设置会被忽略,因为 RabbitMQ 服务在运行时会绑定到应用程序。
发送消息只需一行代码。
...
class StatusService {
def springSecurityService
def tagService
void updateStatus(long userId, String message) {
def status = new Status(message: message, authorId: userId).save(flush: true, failOnError: true)
rabbitSend 'search.sync', '', "${status.id}:${status.class.name}"
runAsync {
tagService.extractTagsFromMessage(status)
}
}
...
}
而索引状态消息的服务也差不多复杂。
package org.grails.twitter
class SyncService {
static rabbitSubscribe = "search.sync"
static transactional = false
def grailsApplication
def searchableService
void handleMessage(String message) {
def parts = message.split(/:/)
if (parts.size() != 2) {
log.error "Invalid message: $message"
return
}
def domainClass = grailsApplication.getDomainClass(parts[1])
log.debug "Reindexing instance ${parts[0]} of ${parts[1]}"
try {
searchableService.reindex(domainClass.clazz.get(parts[0]))
}
catch (Exception ex) {
log.error "Failed to index instance ${parts[0]} of ${parts[1]}", ex
}
}
}
所以rabbitSend()方法用于发送一个简单的字符串,其中包含状态实例 ID 和类名。在这种情况下,我们只处理状态实例,但最好使服务能够处理所有潜在的可搜索域类。此外,使用 Groovy 意味着我们不必进行任何讨厌的反射:我们只需获取类并直接在其上调用我们想要的方法!
中的重要部分SyncService是rabbitSubscribe属性和handleMessage()方法。前者声明服务应订阅交换机“search.sync”,我正向其发送消息。该handleMessage()方法在每次从该交换机接收到消息时被调用,消息内容作为其参数。因此,该方法提取类名和实例 ID,并使用 GrailsDomainClass.get()方法从数据存储(对于我们的状态消息是 MongoDB)中检索相关实例。最后,searchableService.reindex()方法将状态消息添加到本地搜索索引。当然,这发生在每个应用程序实例上。
现在应用程序已准备好部署到 Cloud Foundry 并扩展到允许的最大实例数!您可以在 CloudFoundry.com 上看到结果。请注意,在 GitHub 项目中,我进行了一些 UI 工作来支持全文搜索,但这些更改与当前主题关系不大。
我必须说,我对自己只需要这么少的代码就能让搜索索引同步感到惊讶。不仅如此,我还能专注于如何解决问题而不是如何编码,因为编码非常简单。最重要的是,使用 Cloud Foundry 意味着部署只需创建和绑定一个 RabbitMQ 服务,然后运行grails prod cf-update命令将更改推送到服务器。很简单。
如您所见,RabbitMQ 可以为与云相关的问题提供创新的解决方案,而 Grails 插件通过其约定机制使其非常易于使用。您可以与同一个应用程序的不同实例、不同的 Grails 应用程序,甚至使用不同语言和框架编写的应用程序进行通信。例如,我们可以部署一个简单的 Node.js 或 Sinatra 应用程序来记录和显示“search.sync”消息,以便您跟踪它们。基本上,RabbitMQ 是您云工具箱中必不可少的一项。