如何将异步缓存与Kotlin协同路由一起使用?
我有一个使用协同路由的Kotlin JVM服务器应用程序,我需要在非阻塞网络调用之前放置一个缓存。我想我可以用咖啡因来获得我需要的非阻塞缓存行为。我需要实现的接口使用如何将异步缓存与Kotlin协同路由一起使用?,kotlin,caching,kotlin-coroutines,caffeine,Kotlin,Caching,Kotlin Coroutines,Caffeine,我有一个使用协同路由的Kotlin JVM服务器应用程序,我需要在非阻塞网络调用之前放置一个缓存。我想我可以用咖啡因来获得我需要的非阻塞缓存行为。我需要实现的接口使用CompletableFuture。同时,我想调用的加载缓存项的方法是suspend函数 我可以这样弥合差距: abstract class SuspendingCacheLoader<K, V>: AsyncCacheLoader<K, V> { abstract suspend fun load(
CompletableFuture
。同时,我想调用的加载缓存项的方法是suspend
函数
我可以这样弥合差距:
abstract class SuspendingCacheLoader<K, V>: AsyncCacheLoader<K, V> {
abstract suspend fun load(key: K): V
final override fun asyncLoad(key: K, executor: Executor): CompletableFuture<V> {
return GlobalScope.async(executor.asCoroutineDispatcher()) {
load(key)
}.asCompletableFuture()
}
}
抽象类SuspendingCacheLoader:AsyncCacheLoader{
摘要暂停娱乐负载(键:K):V
最终重载(键:K,执行者:执行者):CompletableFuture{
返回GlobalScope.async(executor.ascoroutinedpatcher()){
加载(键)
}.asCompletableFuture()
}
}
这将在提供的执行器上运行load
功能(默认情况下,ForkJoinPool
),从咖啡因的角度来看,这是正确的行为
然而,我知道我应该努力
我考虑让我的SuspendingCacheLoader
实现并管理自己的协同程序上下文。但是CoroutineScope
旨在由具有托管生命周期的对象实现。缓存和AsyncCacheLoader
都没有任何生命周期挂钩。缓存拥有Executor
和CompletableFuture
实例,因此它已经通过这种方式控制加载任务的生命周期。我看不出让任务归协同路由上下文所有会增加什么,我担心在缓存停止使用后无法正确关闭协同路由上下文
编写我自己的异步缓存机制会非常困难,所以如果可以的话,我想与咖啡因实现集成
使用GlobalScope
是实现AsyncCacheLoader
的正确方法,还是有更好的解决方案
缓存拥有Executor和CompletableFuture实例,因此它已经通过这种方式控制加载任务的生命周期
但事实并非如此,caffine
上的文档指定它使用用户提供的Executor
或ForkJoinPool.commonPool()
(如果未提供)。这意味着没有默认的生命周期
不管怎样,直接调用GlobalScope
似乎是错误的解决方案,因为没有理由硬编码选择。只需通过构造函数提供一个CoroutineScope
,并使用GlobalScope
作为参数,而缓存没有显式的生命周期可绑定。经过一番思考,我想出了一个更简单的解决方案,我认为它更习惯于使用coroutines
该方法通过使用而不是实现AsyncCacheLoader
工作。但是,它会忽略缓存配置为使用的执行器
,遵循此处其他一些答案的建议
类SuspendingCache(私有val asyncCache:asyncCache){
暂停乐趣获取(键:K):V=主管范围{
getAsync(key).await()
}
private-fun-CoroutineScope.getAsync(key:K)=asyncCache.get(key){K,->
未来{
荷载值(k)
}
}
private-suspend-fun-loadValue(键:K):V=TODO(“加载值”)
}
请注意,这取决于future
coroutine生成器和await()
函数的kotlinx-coroutines-jdk8
我认为忽略执行者可能是正确的选择。正如@Kiskae指出的,缓存在默认情况下将使用ForkJoinPool
。选择使用它而不是默认的协程调度器可能没有什么用处。但是,如果我们愿意,可以通过更改getAsync
函数轻松使用它:
private-fun-CoroutineScope.getAsync(key:K)=asyncCache.get(key){K,executor->
future(executor.ascoroutinedpatcher()){
荷载值(k)
}
}
以下是我的解决方案:
定义coroutinerticle
fun <K, V> CoroutineVerticle.buildCache(configurator: Caffeine<Any, Any>.() -> Unit = {}, loader: suspend CoroutineScope.(K) -> V) = Caffeine.newBuilder().apply(configurator).buildAsync { key: K, _ ->
// do not use cache's executor
future {
loader(key)
}
}
val cache : AsyncLoadingCache<String, String> = buildCache({
maximumSize(10_000)
expireAfterWrite(10, TimeUnit.MINUTES)
}) { key ->
// load data and return it
delay(1000)
"data for key: $key"
}
使用缓存
suspend fun doSomething() {
val data = cache.get('key').await()
val future = cache.get('key2')
val data2 = future.await()
}
这里有一个简单的解决方案。用您的类型替换K,V符号
val cache = Caffeine.newBuilder().buildAsync<K, V> { key: K, _ ->
val future = CompletableFuture<V>()
launch {
val result = someAwaitOperation(key)
future.complete(result)
}
future
}
val cache=caffee.newBuilder().buildAsync{key:K,->
val future=CompletableFuture()
发射{
val结果=操作(键)
未来。完成(结果)
}
未来
}
建议这样的扩展方法
suspend inline.suspendingLoadingCache(
交叉线悬挂装载机:悬挂(键:K)->V
):AsyncLoadingCache=
buildAsync{key,executor:executor->
CoroutineScope(executor.asCoroutineDispatcher()).future{
吊杆装载机(钥匙)
}
}
不建议使用GlobalScope
,请使用CoroutineScope(executor.ascoroutinedipatcher())
future
方法在kotlinx-coroutines-jdk8
模块中定义感谢您的回复和对咖啡因执行者的澄清。我喜欢在构造函数中接受CoroutineScope
。如果我最终通过了来自更广泛应用程序的CoroutineScope
,这会给我带来什么好处?我设想在异步缓存加载器
中,我希望覆盖调度程序
(使用提供的执行器
)和作业
(因为缓存加载失败传播到缓存之外没有意义)。一旦这样做了,似乎就不会有太多的原始上下文了。我不知道传入的协同路由作用域会给您带来什么好处,因为您显然是在协同路由世界之外工作,而您的调用方手头没有它。