§Play 快取 API
快取資料是現代應用程式中常見的最佳化手法,因此 Play 提供了全域快取。
注意:快取的重要一點是,它的行為就像快取一樣:您剛剛儲存的資料可能會消失。
對於儲存在快取中的任何資料,如果資料消失,就需要制定一個重新產生策略。這種理念是 Play 背後的其中一個基本原則,與 Java EE 不同,Java EE 預期會話會在整個生命週期中保留值。
Play 提供了一個基於 Caffeine 的 CacheApi 實作,以及一個基於 Ehcache 2.x 的舊版實作。對於處理中的快取,Caffeine 通常是最佳選擇。如果您需要分散式快取,有第三方外掛程式可供 memcached 和 redis 使用。
§匯入快取 API
Play 提供 Cache API 和 Caffeine 和 Ehcache 實作的獨立相依性。
§Caffeine
若要取得 Caffeine 實作,請將 caffeine
加入您的相依性清單
libraryDependencies ++= Seq(
caffeine
)
這也會自動設定執行時期 DI 的繫結,讓元件可注入。
§EhCache
若要取得 EhCache 實作,請將 ehcache
加入您的相依性清單
libraryDependencies ++= Seq(
ehcache
)
這也會自動設定執行時期 DI 的繫結,讓元件可注入。
§自訂快取實作
若要只加入 API,請將 cacheApi
加入您的相依性清單。
libraryDependencies ++= Seq(
cacheApi
)
如果您想為 Cached
輔助程式和 AsyncCacheApi
等定義自己的繫結,而不依賴任何特定快取實作,API 相依性會很有用。如果您正在撰寫自訂快取模組,您應該使用這個。
§存取快取 API
快取 API 由 AsyncCacheApi 和 SyncCacheApi 特質定義,視您是要非同步或同步實作而定,而且可以像任何其他相依性一樣注入到您的元件中。例如
import javax.inject.Inject
import play.api.cache._
import play.api.mvc._
class Application @Inject() (cache: AsyncCacheApi, cc: ControllerComponents) extends AbstractController(cc) {}
注意:API 故意簡化,以允許插入多個實作。如果您需要更具體的 API,請使用快取外掛程式提供的 API。
使用這個簡單的 API,您可以在快取中儲存資料
val result: Future[Done] = cache.set("item.key", connectedUser)
然後稍後擷取資料
val futureMaybeUser: Future[Option[User]] = cache.get[User]("item.key")
還有一個方便的輔助程式,用於從快取中擷取資料或在資料不存在時設定快取中的值
val futureUser: Future[User] = cache.getOrElseUpdate[User]("item.key") {
User.findById(connectedUser)
}
注意:getOrElseUpdate
在 EhCache 中並非原子操作,而是實作為先 get
,再計算值,然後 set
。這表示如果有多個執行緒同時呼叫 getOrElse
,則可能會計算值多次。
你可以傳遞持續時間來指定到期時間,預設持續時間為無限長
import scala.concurrent.duration._
val result: Future[Done] = cache.set("item.key", connectedUser, 5.minutes)
若要從快取中移除項目,請使用 remove
方法
val removeResult: Future[Done] = cache.remove("item.key")
若要從快取中移除所有項目,請使用 removeAll
方法
val removeAllResult: Future[Done] = cache.removeAll()
removeAll()
僅在 AsyncCacheApi
中可用,因為很少需要同步移除快取中的所有元素。預期只有在特殊情況下才需要以管理員操作移除快取中的所有項目,而不是應用程式的正常操作的一部分。
請注意,SyncCacheApi 具有相同的 API,只不過它直接傳回值,而不使用未來。
§存取不同的快取
可以依據名稱定義和使用具有不同組態的不同快取。若要存取不同的快取,請在注入快取時,對你的相依關係使用 NamedCache 限定詞,例如
import javax.inject.Inject
import play.api.cache._
import play.api.mvc._
class Application @Inject() (
@NamedCache("session-cache") sessionCache: AsyncCacheApi,
cc: ControllerComponents
) extends AbstractController(cc) {}
如果你要存取多個不同的快取,則需要指示 Play 在 application.conf
中繫結這些快取,如下所示
play.cache.bindCaches = ["db-cache", "user-cache", "session-cache"]
定義和組態命名快取取決於你使用的快取實作,以下是使用 Caffeine 組態命名快取的範例。
§使用 Caffeine 組態命名快取
如果您想傳遞將用作所有快取的備份的預設自訂組態,您可以透過指定來執行
play.cache.caffeine.defaults = {
initial-capacity = 200
...
}
您也可以透過執行來傳遞特定快取的自訂組態資料
play.cache.caffeine.user-cache = {
initial-capacity = 200
...
}
§使用 EhCache 組態命名快取
使用 EhCache 實作時,預設快取稱為 play,且可以透過建立名為 ehcache.xml 的檔案來組態。其他快取可以使用不同的組態,甚至實作來組態。
預設情況下,Play 會嘗試為您建立具有來自 play.cache.bindCaches
名稱的快取。如果您想在 ehcache.xml
中自行定義,您可以設定
play.cache.createBoundCaches = false
§設定執行內容
預設情況下,Caffeine 和 EhCache 會將元素儲存在記憶體中。因此,讀取和寫入快取的速度應該非常快,因為幾乎沒有任何封鎖 I/O。
然而,視快取的組態方式而定(例如,使用 EhCache 的 DiskStore
),可能會出現封鎖 I/O,這可能會變得太昂貴,因為即使非同步實作也會封鎖預設執行內容中的執行緒。
對於這種情況,您可以組態不同的 Pekko 分派器,並透過 play.cache.dispatcher
設定,讓快取外掛程式使用它
play.cache.dispatcher = "contexts.blockingCacheDispatcher"
contexts {
blockingCacheDispatcher {
fork-join-executor {
parallelism-factor = 3.0
}
}
}
§Caffeine
使用 Caffeine 時,這將設定 Caffeine 的 內部執行器。實際上,設定 play.cache.dispatcher
會設定 play.cache.caffeine.defaults.executor
。如同 上述所述,因此您可以為不同的快取設定不同的執行器
play.cache.caffeine.user-cache = {
executor = "contexts.anotherBlockingCacheDispatcher"
...
}
§EhCache
對於 EhCache,Play 會在給定分派器的執行緒上以 Future 執行任何 EhCache 作業。
§快取 HTTP 回應
您可以輕鬆使用標準動作組合來建立智慧快取動作。
注意:Play HTTP
Result
實例可以安全快取並在稍後重複使用。
Cached 類別可協助您建立快取動作。
import javax.inject.Inject
import play.api.cache.Cached
class Application @Inject() (cached: Cached, cc: ControllerComponents) extends AbstractController(cc) {}
您可以使用固定金鑰(例如 "homePage"
)快取動作的結果。
def index = cached("homePage") {
Action {
Ok("Hello world")
}
}
如果結果有所不同,您可以使用不同的金鑰快取每個結果。在此範例中,每個使用者都有不同的快取結果。
def userProfile = WithAuthentication(_.session.get("username")) { userId =>
cached(req => "profile." + userId) {
Action.async {
User.find(userId).map { user => Ok(views.html.profile(user)) }
}
}
}
§控制快取
您可以輕鬆控制要快取的內容或要從快取中排除的內容。
您可能只想快取 200 Ok 結果。
def get(index: Int) = cached.status(_ => "/resource/" + index, 200) {
Action {
if (index > 0) {
Ok(Json.obj("id" -> index))
} else {
NotFound
}
}
}
或僅快取 404 Not Found 幾分鐘
def get(index: Int) = {
val caching = cached
.status(_ => "/resource/" + index, 200)
.includeStatus(404, 600)
caching {
Action {
if (index % 2 == 1) {
Ok(Json.obj("id" -> index))
} else {
NotFound
}
}
}
}
§自訂實作
可以提供快取 API 的自訂實作。請確定您有 cacheApi
相依性。
接著,您可以實作 AsyncCacheApi 並將其繫結在 DI 容器中。您也可以將 SyncCacheApi 繫結到 DefaultSyncCacheApi,後者僅封裝非同步實作。
請注意,removeAll
方法可能不受您的快取實作支援,原因可能是不可行或會造成不必要的低效率。如果是這種情況,您可以在 removeAll
方法中擲回 UnsupportedOperationException
。
若要提供快取 API 的實作,除了預設實作之外,您可以建立自訂限定詞,或重複使用 NamedCache
限定詞來繫結實作。
§與自訂實作一起使用 Caffeine
若要使用 Caffeine 的預設實作,您需要 caffeine
相依性,而且必須在 application.conf
中停用 Caffeine 模組自動繫結。
play.modules.disabled += "play.api.cache.caffeine.CaffeineCacheModule"
§將 EhCache 與自訂實作搭配使用
若要使用 EhCache 的預設實作,您將需要 ehcache
相依性,而且必須停用 EhCache 模組在 application.conf
中自動繫結。
play.modules.disabled += "play.api.cache.ehcache.EhCacheModule"
在這個文件裡發現錯誤嗎?此頁面的原始程式碼可以在 這裡 找到。在閱讀 文件指南 後,請隨時貢獻一個 pull request。有問題或建議要分享嗎?請前往 我們的社群論壇 與社群展開對話。