未验证 提交 c1d76a8c 编写于 作者: 梦境迷离's avatar 梦境迷离 提交者: GitHub

optimize cache (#273)

Signed-off-by: 梦境迷离's avatar梦境迷离 <dreamylost@outlook.com>
上级 79189947
......@@ -31,59 +31,34 @@ import scala.concurrent.{ ExecutionContext, Future }
*/
object Cache {
def getAsyncCache[K, T <: Product](implicit
def apply[K, T <: Product](implicit
cache: Aux[K, T, Future],
executionContext: ExecutionContext,
keyBuilder: CacheKeyBuilder[K]
keySerde: CacheKeySerde[K]
): CacheRef[K, T, Future] =
new CacheRef[K, cache.Out, Future] {
override def batchPutT(data: => Map[K, cache.Out]): Future[Unit] =
override def batchPutF(data: => Map[K, cache.Out]): Future[Unit] =
cache.putAll(data)
override def getT(key: K): Future[Option[cache.Out]] =
override def getF(key: K): Future[Option[cache.Out]] =
cache.get(key)
override def putT(key: K, value: cache.Out): Future[Unit] =
override def putF(key: K, value: cache.Out): Future[Unit] =
cache.put(key, value)
override def clear(): Future[Unit] = cache.clear()
override def clearF(): Future[Unit] = cache.clear()
override def remove(key: K): Future[Unit] = cache.remove(key)
override def removeF(key: K): Future[Unit] = cache.remove(key)
override def getAllT: Future[Map[K, cache.Out]] = cache.getAll
override def getAllF: Future[Map[K, cache.Out]] = cache.getAll
override def safeRefreshT(allNewData: Map[K, cache.Out]): Future[Unit] =
this.getAllT.map { t =>
val invalidData = t.keySet.filterNot(allNewData.keySet)
this.batchPutT(allNewData).map(_ => invalidData.foreach(this.remove))
override def refreshF(allNewData: Map[K, cache.Out]): Future[Unit] =
this.synchronized {
this.getAllF.map { t =>
val invalidData = t.keySet.filterNot(allNewData.keySet)
this.batchPutF(allNewData).map(_ => invalidData.foreach(this.removeF))
}
}
}
def getSyncCache[K, T <: Product](implicit
cache: Aux[K, T, Identity],
keyBuilder: CacheKeyBuilder[K]
): CacheRef[K, T, Identity] =
new CacheRef[K, cache.Out, Identity] {
override def batchPutT(data: => Map[K, cache.Out]): Identity[Unit] =
data.foreach(kv => cache.put(kv._1, kv._2))
override def getT(key: K): Identity[Option[cache.Out]] =
cache.get(key)
override def putT(key: K, value: cache.Out): Identity[Unit] =
cache.put(key, value)
override def clear(): Identity[Unit] = cache.clear()
override def getAllT: Identity[Map[K, cache.Out]] = cache.getAll
override def remove(key: K): Identity[Unit] = cache.remove(key)
override def safeRefreshT(allNewData: Map[K, cache.Out]): Identity[Unit] = {
val invalidData = this.getAllT.keySet.filterNot(allNewData.keySet)
this.batchPutT(allNewData)
invalidData.foreach(this.remove)
}
}
}
......@@ -31,7 +31,7 @@ import java.util.Collections
*/
trait CacheAdapter[V] {
def getAllKeys: Set[String]
def keys: Set[String]
def batchPut(data: Map[String, V]): Unit
......@@ -61,7 +61,7 @@ object CacheAdapter {
class LruHashMapCacheAdapter[V](underlyingCache: java.util.Map[String, V]) extends CacheAdapter[V] {
override def getAllKeys: Set[String] = underlyingCache.keySet().asScala.toSet
override def keys: Set[String] = underlyingCache.keySet().asScala.toSet
override def batchPut(data: Map[String, V]): Unit = underlyingCache.putAll(data.asJava)
......@@ -77,7 +77,7 @@ object CacheAdapter {
class ConcurrentMapCacheAdapter[V](underlyingCache: java.util.concurrent.ConcurrentMap[String, V])
extends CacheAdapter[V] {
override def getAllKeys: Set[String] = underlyingCache.keySet().asScala.toSet
override def keys: Set[String] = underlyingCache.keySet().asScala.toSet
override def batchPut(data: Map[String, V]): Unit = underlyingCache.putAll(data.asJava)
......
......@@ -27,39 +27,39 @@ import java.util.UUID
* 梦境迷离
* @version 1.0,6/8/22
*/
trait CacheKeyBuilder[T] {
trait CacheKeySerde[T] {
def serialize(key: T): String
def deserialize(key: String): T
}
object CacheKeyBuilder {
object CacheKeySerde {
implicit val intKey: CacheKeyBuilder[Int] = new CacheKeyBuilder[Int] {
implicit val intKey: CacheKeySerde[Int] = new CacheKeySerde[Int] {
override def serialize(key: Int): String = key.toString
override def deserialize(key: String): Int = key.toInt
}
implicit val stringKey: CacheKeyBuilder[String] = new CacheKeyBuilder[String] {
implicit val stringKey: CacheKeySerde[String] = new CacheKeySerde[String] {
override def serialize(key: String): String = key
override def deserialize(key: String): String = key
}
implicit val longKey: CacheKeyBuilder[Long] = new CacheKeyBuilder[Long] {
implicit val longKey: CacheKeySerde[Long] = new CacheKeySerde[Long] {
override def serialize(key: Long): String = key.toString
override def deserialize(key: String): Long = key.toLong
}
implicit val doubleKey: CacheKeyBuilder[Double] = new CacheKeyBuilder[Double] {
implicit val doubleKey: CacheKeySerde[Double] = new CacheKeySerde[Double] {
override def serialize(key: Double): String = String.valueOf(key)
override def deserialize(key: String): Double = key.toDouble
}
implicit val uuidKey: CacheKeyBuilder[UUID] = new CacheKeyBuilder[UUID] {
implicit val uuidKey: CacheKeySerde[UUID] = new CacheKeySerde[UUID] {
override def serialize(key: UUID): String = key.toString
override def deserialize(key: String): UUID = UUID.fromString(key)
......
......@@ -27,17 +27,17 @@ package org.bitlap.cache
*/
trait CacheRef[In, T <: Product, F[_]] {
def batchPutT(data: => Map[In, T]): F[Unit]
def batchPutF(data: => Map[In, T]): F[Unit]
def getT(key: In): F[Option[T]]
def getF(key: In): F[Option[T]]
def putT(key: In, value: T): F[Unit]
def putF(key: In, value: T): F[Unit]
def getAllT: F[Map[In, T]]
def getAllF: F[Map[In, T]]
def clear(): F[Unit]
def clearF(): F[Unit]
def remove(key: In): F[Unit]
def removeF(key: In): F[Unit]
def safeRefreshT(allNewData: Map[In, T]): F[Unit]
def refreshF(allNewData: Map[In, T]): F[Unit]
}
......@@ -25,7 +25,7 @@ package org.bitlap.cache
* 梦境迷离
* @version 1.0,6/8/22
*/
trait CacheStrategy
sealed trait CacheStrategy
object CacheStrategy {
......
......@@ -31,17 +31,17 @@ sealed trait GenericCache[K, F[_]] {
type Out <: Product
def get(key: K)(implicit keyBuilder: CacheKeyBuilder[K]): F[Option[Out]]
def get(key: K)(implicit keyBuilder: CacheKeySerde[K]): F[Option[Out]]
def put(key: K, value: Out)(implicit keyBuilder: CacheKeyBuilder[K]): F[Unit]
def put(key: K, value: Out)(implicit keyBuilder: CacheKeySerde[K]): F[Unit]
def putAll(map: Map[K, Out])(implicit keyBuilder: CacheKeyBuilder[K]): F[Unit]
def putAll(map: Map[K, Out])(implicit keyBuilder: CacheKeySerde[K]): F[Unit]
def getAll(implicit keyBuilder: CacheKeyBuilder[K]): F[Map[K, Out]]
def getAll(implicit keyBuilder: CacheKeySerde[K]): F[Map[K, Out]]
def clear(): F[Unit]
def remove(key: K)(implicit keyBuilder: CacheKeyBuilder[K]): F[Unit]
def remove(key: K)(implicit keyBuilder: CacheKeySerde[K]): F[Unit]
}
......@@ -49,71 +49,33 @@ object GenericCache {
type Aux[K, Out0, F[_]] = GenericCache[K, F] { type Out = Out0 }
def apply[K, Out0 <: Product](cacheStrategy: CacheStrategy): Aux[K, Out0, Identity] = new GenericCache[K, Identity] {
private val cacheAdapter: CacheAdapter[Out0] = CacheAdapter.adapted[Out0](cacheStrategy)
override type Out = Out0
override def get(
key: K
)(implicit
keyBuilder: CacheKeyBuilder[K]
): Identity[Option[Out]] = {
val v = cacheAdapter.get(keyBuilder.serialize(key))
if (v == null) None else Option(v)
}
override def put(key: K, value: Out)(implicit
keyBuilder: CacheKeyBuilder[K]
): Identity[Unit] =
cacheAdapter.put(keyBuilder.serialize(key), value)
override def putAll(map: Map[K, Out0])(implicit keyBuilder: CacheKeyBuilder[K]): Identity[Unit] =
cacheAdapter.batchPut(map.map(kv => keyBuilder.serialize(kv._1) -> kv._2))
override def clear(): Identity[Unit] = cacheAdapter.clear()
override def getAll(implicit keyBuilder: CacheKeyBuilder[K]): Identity[Map[K, Out0]] =
cacheAdapter.getAllKeys
.map(key => keyBuilder.deserialize(key) -> cacheAdapter.get(key))
.collect {
case (k, out) if out != null => k -> out
}
.toMap
override def remove(key: K)(implicit keyBuilder: CacheKeyBuilder[K]): Identity[Unit] =
cacheAdapter.remove(keyBuilder.serialize(key))
}
def apply[K, Out0 <: Product](
cacheStrategy: CacheStrategy,
executionContext: ExecutionContext
): Aux[K, Out0, Future] =
cacheStrategy: CacheStrategy
)(implicit executionContext: ExecutionContext): Aux[K, Out0, Future] =
new GenericCache[K, Future] {
implicit val ec = executionContext
private val cacheAdapter: CacheAdapter[Out0] = CacheAdapter.adapted[Out0](cacheStrategy)
override type Out = Out0
override def get(key: K)(implicit keyBuilder: CacheKeyBuilder[K]): Future[Option[Out]] =
override def get(key: K)(implicit keyBuilder: CacheKeySerde[K]): Future[Option[Out]] =
Future {
val v = cacheAdapter.get(keyBuilder.serialize(key))
if (v == null) None else Option(v)
}
def put(key: K, value: Out)(implicit keyBuilder: CacheKeyBuilder[K]): Future[Unit] =
def put(key: K, value: Out)(implicit keyBuilder: CacheKeySerde[K]): Future[Unit] =
Future {
cacheAdapter.put(keyBuilder.serialize(key), value)
}.map(_ => ())
override def putAll(map: Map[K, Out0])(implicit keyBuilder: CacheKeyBuilder[K]): Future[Unit] =
override def putAll(map: Map[K, Out0])(implicit keyBuilder: CacheKeySerde[K]): Future[Unit] =
Future {
cacheAdapter.batchPut(map.map(kv => keyBuilder.serialize(kv._1) -> kv._2))
}
override def getAll(implicit keyBuilder: CacheKeyBuilder[K]): Future[Map[K, Out0]] =
override def getAll(implicit keyBuilder: CacheKeySerde[K]): Future[Map[K, Out0]] =
Future {
cacheAdapter.getAllKeys
cacheAdapter.keys
.map(key => keyBuilder.deserialize(key) -> cacheAdapter.get(key))
.collect {
case (k, out) if out != null => k -> out
......@@ -123,7 +85,7 @@ object GenericCache {
override def clear(): Future[Unit] = Future.successful(cacheAdapter.clear())
override def remove(key: K)(implicit keyBuilder: CacheKeyBuilder[K]): Future[Unit] = Future {
override def remove(key: K)(implicit keyBuilder: CacheKeySerde[K]): Future[Unit] = Future {
cacheAdapter.remove(keyBuilder.serialize(key))
}
}
......
/*
* Copyright (c) 2022 bitlap
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package org.bitlap
/** @author
* 梦境迷离
* @version 1.0,6/10/22
*/
package object cache {
type Identity[O] = O
}
......@@ -39,36 +39,15 @@ class CacheCustomCaseSpec extends AnyFlatSpec with Matchers {
"etc2" -> TestEntity("eth2", "hello2", "world2")
)
"cache1" should "ok while uses lru cache" in {
implicit val lruCache = CacheImplicits.testEntitySyncLruCache
val cache = Cache.getSyncCache[String, TestEntity]
cache.batchPutT(data)
cache.putT("etc3", TestEntity("eth3", "hello3", "world2"))
val result: Option[TestEntity] = cache.getT("btc")
result shouldBe None
cache.clear()
val result2: Option[TestEntity] = cache.getT("etc1")
result2 shouldBe None
cache.batchPutT(data)
val result3: Option[TestEntity] = cache.getT("etc1")
result3 shouldBe Some(TestEntity("eth1", "hello1", "world2"))
val result4 = cache.getAllT
result4 shouldBe data
}
"cache2" should "ok while defines a custom cache" in {
import scala.concurrent.Await
import scala.concurrent.duration.Duration
implicit val ec = scala.concurrent.ExecutionContext.Implicits.global
implicit val customCache =
GenericCache[String, TestEntity](CacheStrategy.CustomCacheStrategy(new CacheAdapter[TestEntity] {
lazy val underlyingCache: util.HashMap[String, TestEntity] = new util.HashMap[String, TestEntity]()
override def getAllKeys: Set[String] = underlyingCache.keySet().asScala.toSet
override def keys: Set[String] = underlyingCache.keySet().asScala.toSet
override def batchPut(data: Map[String, TestEntity]): Unit = underlyingCache.putAll(data.asJava)
......@@ -80,12 +59,12 @@ class CacheCustomCaseSpec extends AnyFlatSpec with Matchers {
override def remove(k: String): Unit = underlyingCache.remove(k)
}))
val cache = Cache.getSyncCache[String, TestEntity]
cache.batchPutT(data)
val result: Option[TestEntity] = cache.getT("btc")
val cache = Cache[String, TestEntity]
Await.result(cache.batchPutF(data), Duration.Inf)
val result: Option[TestEntity] = Await.result(cache.getF("btc"), Duration.Inf)
result shouldBe Some(TestEntity("btc", "hello1", "world1"))
val result2 = cache.getAllT
val result2 = Await.result(cache.getAllF, Duration.Inf)
result2 shouldBe data
}
}
/*
* Copyright (c) 2022 bitlap
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package org.bitlap.cache
import scala.concurrent.ExecutionContext
import org.bitlap.common.TestEntity
object CacheImplicits {
// NOTE:These are two completely different caches and the data is not shared
// If you don't use global, you should also make sure this place the same as getAsyncCache's
implicit lazy val testEntitySyncCache =
GenericCache[String, TestEntity](CacheStrategy.Normal)
implicit lazy val testEntityAsyncCache =
GenericCache[String, TestEntity](CacheStrategy.Normal, ExecutionContext.Implicits.global)
implicit lazy val testEntitySyncLruCache =
GenericCache[String, TestEntity](CacheStrategy.Lru(3))
}
......@@ -34,11 +34,11 @@ class CacheKeyBuilderSpec extends AnyFlatSpec with Matchers {
"CacheKeyBuilder1" should "ok while uses uuid type" in {
val now = UUID.randomUUID()
val str = CacheKeyBuilder.uuidKey.serialize(now)
val str = CacheKeySerde.uuidKey.serialize(now)
println(str)
val v = CacheKeyBuilder.uuidKey.deserialize(str)
val v = CacheKeySerde.uuidKey.deserialize(str)
v shouldBe now
}
......
......@@ -27,6 +27,7 @@ import scala.concurrent.Await
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.DurationInt
import org.bitlap.common.TestEntity
import scala.concurrent.ExecutionContext
/** @author
* 梦境迷离
......@@ -34,38 +35,8 @@ import org.bitlap.common.TestEntity
*/
class CacheSpec extends AnyFlatSpec with Matchers {
private implicit val syncCache = CacheImplicits.testEntitySyncCache
private implicit val asyncCache = CacheImplicits.testEntityAsyncCache
private val data = Map(
"btc" -> TestEntity("btc", "hello1", "world1"),
"etc" -> TestEntity("eth", "hello2", "world2")
)
"cache1" should "get entity from cache successfully" in {
val cache = Cache.getSyncCache[String, TestEntity]
cache.batchPutT(data)
val result: Option[TestEntity] = cache.getT("etc")
result shouldBe data.get("etc")
val result2 = cache.getAllT
result2 shouldBe data
}
"cache3" should "get entity's field after refresh" in {
val cache = Cache.getSyncCache[String, TestEntity]
cache.batchPutT(data)
val newData = Map(
"btc" -> TestEntity("btc", "hello1", "world1"),
"btc-zh-cn" -> TestEntity("btc", "你好啊", "你好哦"),
"etc" -> TestEntity("eth", "hello2", "world2")
)
cache.clear()
cache.batchPutT(newData)
val result: Option[TestEntity] = cache.getT("btc-zh-cn")
result shouldBe newData.get("btc-zh-cn")
}
implicit lazy val testEntityAsyncCache =
GenericCache[String, TestEntity](CacheStrategy.Normal)(ExecutionContext.Implicits.global)
"cache4" should "async cache" in {
val newData = Map(
......@@ -77,13 +48,13 @@ class CacheSpec extends AnyFlatSpec with Matchers {
"btc-zh-cn" -> TestEntity("btc", "id456", "btc_zh_key456"),
"eth" -> TestEntity("btc", "id456", "eth_key456")
)
val cache = Cache.getAsyncCache[String, TestEntity]
val cache = Cache[String, TestEntity]
val ret = for {
_ <- cache.batchPutT(newData)
btcKey <- cache.getT("btc").map(_.map(_.key))
_ <- cache.batchPutT(newData2)
ethKey <- cache.getT("eth").map(_.map(_.key))
_ <- cache.batchPutF(newData)
btcKey <- cache.getF("btc").map(_.map(_.key))
_ <- cache.batchPutF(newData2)
ethKey <- cache.getF("eth").map(_.map(_.key))
} yield btcKey -> ethKey
Await.result(ret, 3.seconds) shouldBe Option("btc_key123") -> Option("eth_key456")
......@@ -95,34 +66,17 @@ class CacheSpec extends AnyFlatSpec with Matchers {
"btc" -> TestEntity("btc", "id123", "btc_key123"),
"btc-zh-cn" -> TestEntity("btc", "id123", "btc_zh_key123")
)
val cache = Cache.getAsyncCache[String, TestEntity]
val cache = Cache[String, TestEntity]
val ret = for {
_ <- cache.batchPutT(newData)
btcKey <- cache.getT("btc").map(_.map(_.key))
_ <- cache.clear()
ethKey <- cache.getT("eth").map(_.map(_.key))
_ <- cache.batchPutF(newData)
btcKey <- cache.getF("btc").map(_.map(_.key))
_ <- cache.clearF()
ethKey <- cache.getF("eth").map(_.map(_.key))
} yield btcKey -> ethKey
Await.result(ret, 3.seconds) shouldBe Option("btc_key123") -> None
}
"cache7" should "refresh successfully" in {
val cache = Cache.getSyncCache[String, TestEntity]
cache.batchPutT(data)
val result: Option[TestEntity] = cache.getT("etc")
result shouldBe data.get("etc")
val newData = Map(
"btc" -> TestEntity("btc", "id123", "btc_key123"),
"btc-zh-cn" -> TestEntity("btc", "id123", "btc_zh_key123")
)
cache.safeRefreshT(newData)
val result2 = cache.getT("eth")
result2 shouldBe None
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册