搶先一步
VMware 提供培訓和認證,以加速您的進展。
了解更多在我的第二篇關於 Grails 和 Cloud Foundry 的部落格文章中,我介紹了 Grails Twitter 範例 的一個變體,它可以託管在 CloudFoundry.com 上。當時我提到,使用 Searchable 外掛程式進行全文檢索會將您限制為單一應用程式實例,因為搜尋索引對於每個實例都是唯一的。換句話說,您很可能根據瀏覽器路由到的應用程式實例而獲得不同的搜尋結果。
我也說過,解決此問題的一個選項是在實例之間同步搜尋索引。但這聽起來並不容易,不是嗎?碰巧的是,Cloud Foundry 中引入 RabbitMQ 服務意味著所需的程式碼變更遠小於您可能預期的。因此,讓我們看看我是如何為 Grails Twitter 狀態訊息新增全文檢索功能的。
Searchable 外掛程式強烈假設您想要為標準 GORM 網域類別建立索引。這意味著 Hibernate/SQL。但 Grails Twitter 狀態訊息儲存在 MongoDB 中,而不是 MySQL 中。我們可以讓它們可搜尋嗎?是的,我們可以,但會犧牲一些功能。
與一般網域類別一樣,搜尋Status實例的第一步是新增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 Broker,然後 Broker 將其轉發到所有應用程式實例。然後每個實例都會為訊息識別的Status實例建立索引。
在我們可以實作此功能之前,我們需要安裝 RabbitMQ 外掛程式
grails install-plugin rabbitmq
下一步是使用適當的交換器和佇列設定 Broker。我之前已經寫過關於 AMQP 協定 和 RabbitMQ 外掛程式 的部落格文章,因此我不會在此處詳細介紹交換器和佇列。只要說我們只需要一個 fanout 交換器(其中所有訊息都會路由到所有監聽器)和一個訂閱該交換器的 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()方法用於傳送包含Status實例 ID 和類別名稱的簡單字串。在這種情況下,我們只處理Status實例,但使服務通用於所有潛在的可搜尋網域類別很有用。此外,使用 Groovy 表示我們不必進行任何令人討厭的反射:我們只需取得類別並直接在其上呼叫我們想要的方法!
的重點部分SyncService是rabbitSubscribe屬性和handleMessage()方法。前者宣告服務應訂閱交換器 "search.sync",這是我將訊息傳送到的交換器。handleMessage()方法在每次從該交換器接收到訊息時都會被調用,訊息內容作為其引數。因此,該方法會擷取類別名稱和實例 ID,並使用 GrailsDomainClass.get()方法從資料儲存區(我們的Status訊息的 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 是您雲端工具箱中的必備項目。