領先一步
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 是您雲工具箱中必不可少的一項。