Я устанавливаю кэш с областью запросов http для фьючерсов scala.В рамках одного запроса есть несколько дорогостоящих операций (вызовы REST API), результаты которых возвращаются в ответе.Для одного и того же ресурса могут быть повторные вызовы REST.Чтобы избежать дублирования, я настраиваю кэш области запросов для хранения фьючерсов, возвращаемых этими вызовами REST.Итак, в следующий раз, когда в этом же запросе будет предпринят тот же вызов REST, результат будет загружен из кеша.
Вариант 1. Я использую инфраструктуру игры 2.6 с Scala 2.11.Я попробовал метод AsyncCacheApi :: getOrElseUpdate.Но согласно документации: https://www.playframework.com/documentation/2.6.x/ScalaCache#Accessing-the-Cache-API Примечание: getOrElseUpdate не является атомарной операцией в Ehcache и реализуется как get, за которым следует вычисление значения, а затем набора.Это означает, что значение может быть вычислено несколько раз, если несколько потоков одновременно вызывают getOrElse.
Вариант 2: Я попытался использовать akka-http-cache из следующего потока: https://groups.google.com/forum/#!topic/play-framework/qVDMvqZidpI Воспроизведение будеткэшируйте свое будущее, даже если это сбой, поэтому, если ваш вызов API потерпит неудачу, будущее в кэше будет исключением.Spray-Cache только кеширует удачные фьючерсы и разрешает гремящие стадаЕсли вам нужен только локальный кеш, а не распределенный кеш, он работает хорошо и прост в настройке.Не пытайтесь подключить его к инфраструктуре кеша воспроизведения, используйте, как указано в документах Spray-Cache.
https://doc.akka.io/docs/akka-http/current/common/caching.html Основная идея API кеша состоит в том, чтобы не хранить фактические значенияТип T сами в кеше, а скорее соответствующие фьючерсы, т.е. экземпляры типа Future [T].Преимущество этого подхода состоит в том, чтобы позаботиться о проблеме громоздких стад, когда многие запросы к определенному ключу кэша (например, к URI ресурса) поступают до того, как первый из них может быть завершен.Обычно (без специальных методов защиты, таких как так называемые «ковбойские» записи) это может привести к тому, что многие запросы будут конкурировать за системные ресурсы, пытаясь вычислить тот же результат, тем самым значительно снижая общую производительность системы.Когда вы используете кеш Akka HTTP, самый первый запрос, который поступает для определенного ключа кеша, заставляет в кеш помещаться будущее, которое затем «подключаются» к последующим запросам.Как только первый запрос завершится, все остальные также будут выполнены.Это минимизирует время обработки и нагрузку на сервер для всех запросов.
Пример кода на основе упомянутого выше варианта 2:
import akka.http.caching.LfuCache
import akka.http.caching.scaladsl.Cache
case class ChildResource(...)
case class MyResource1(id: String, attrs: JsValue, childResource: Future[ChildResource])
case class MyResource2(id: String, attrs: JsValue, childResource: Future[ChildResource])
...
case class MyResourceN(id: String, attrs: JsValue, childResource: Future[ChildResource])
object MyCache {
val childResourceCache = : Cache[String, ChildResource] = LfuCache[String, ChildResource]
val resource1Cache = : Cache[String, MyResource1] = LfuCache[String, MyResource1]
val resource1Cache = : Cache[String, MyResource1] = LfuCache[String, MyResource1]
...
val resourceNCache = : Cache[String, MyResourceN] = LfuCache[String, MyResourceN]
}
@Singleton
class RestClient(wsClient: WSClient) {
def getChildResource(requestId: String, id: String): Future[ChildResource] = MyCache.childResourceCache.getOrLoad(requestId + id, _ => wsClient.url(...))
def getResource1(requestId: String, id: String): Future[MyResource1] = {
MyCache.resource1Cache.getOrLoad(requestId + id, _ => {
val responseFuture = wsClient.url(...)
...
val childResourceFuture = getChildResource(...)
...
})
}
def getResource2(requestId: String, id: String): Future[MyResource2] = {
MyCache.resource2Cache.getOrLoad(requestId + id, _ => {
val responseFuture = wsClient.url(...)
...
val childResourceFuture = getChildResource(...)
...
})
}
...
def getResourceN(requestId: String, id: String): Future[MyResourceN] = {
MyCache.resourceNCache.getOrLoad(requestId + id, _ => {
val responseFuture = wsClient.url(...)
...
val childResourceFuture = getChildResource(...)
...
})
}
}
case class MyResponse(
myResource1: Future[MyResource1],
myResource2: Future[MyResource2],
...
myResourceN: Future[MyResourceN]
)
object MyResponse {
def apply(...): MyResponse = {
// multiple parallel threads make "getResource" REST calls to fetch all resources
...
}
}
@Singleton
@Api(value = "/myresource")
class MyController @Inject()
(implicit val executionContext: ExecutionContext, implicit val materializer: Materializer) {
def myMethod(...) = {
val requestId = UUIDs.timeBased().toString
val myResponse = MyResponse(...)
// lets remove the items cached for this request as we're ready to return the response
val childResourceCacheKeys = MyCache.childResourceCache.keys.filter(_.startsWith(requestId))
childResourceCacheKeys.foreach(MyCache.childResourceCache.remove(_))
val resource1CacheKeys = MyCache.resource1Cache.keys.filter(_.startsWith(requestId))
resource1CacheKeys.foreach(MyCache.resource1Cache.remove(_))
val resource2CacheKeys = MyCache.resource2Cache.keys.filter(_.startsWith(requestId))
resource2CacheKeys.foreach(MyCache.resource2Cache.remove(_))
...
val resourceNCacheKeys = MyCache.resourceNCache.keys.filter(_.startsWith(requestId))
resourceNCacheKeys.foreach(MyCache.resourceNCache.remove(_))
// return response
myResponse
}
}
Единственная неэффективная часть - это ручная очистка кэшированных элементов, относящихся к заданномузапрос в конце жизненного цикла этого запроса.Существует ли какая-либо библиотека / альтернатива, которая предоставляет такого рода кэш с областью http-запросов для scala-фьючерсов, из коробки?поэтому кэшированные элементы будут автоматически доступны для GC после каждого запроса ...