IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 基于scala+zio的分布式缓存工具 -> 正文阅读

[Java知识库]基于scala+zio的分布式缓存工具

cacheable

基于scala+zio的分布式缓存工具,功能类似Spring缓存注解@Cacheable@CacheEvict。支持使用caffeine和zio-redis作为存储介质。

我的目标是为scala+zio应用提供一层无侵入性、可替换、可配置、类型安全的缓存工具,不受限于存储介质,容易使用。适用于读多写少的场景,减少数据库的压力(其中Redis缓存更新中暂没有使用锁,有覆盖可能),由于应用本身是基于zio/zstream的,如果不考虑数据库压力(或JDBC与cacheable性能无区别),则没有必要使用cacheable

其中,缓存均使用hash存储,key为类名+方法名,field为方法的所有参数 value为方法的返回值。

缓存的语义

  • cacheable — 查缓存,有则返回,无则查库并设置缓存
    • local 指定是用内存作为缓存存储,默认为true
  • cacheEvict — 删除缓存中的某个方法的所有记录
    • local 指定是用内存作为缓存存储,默认为true
    • values 指定需要清除的查询方法名 编译期检查,必须与注解所在类是同一个密闭类中成员;未指定values或者为Nil,则清除该密闭类中的所有方法的缓存

如何设计

根据我们的目标:

  1. 拓展性:存储和API隔离,不使用则不需要导入
  2. 安全性:尽可能编译期检查,尽快发现错误
  3. 便捷性:提供默认实现,且用户能使用自定义实现替换默认实现
  4. 合理性:尽可能使用与spring相同的语义
  5. 存储解构:使用hash,能一键清理key的所有缓存值
  6. 内部实现原理:implicit域内搜索,编译期代码合成,元/宏编程

如果从cache中查数据比ZIO.effect中的操作快很多,则缓存才是有意义的,否则没必要使用。

@cacheableAPI设计

import zio.ZIO
import zio.stream.ZStream
// 顶级API 
trait Cache[Z] {

  def getIfPresent(business: => Z)(identities: List[String], args: List[_]): Z
  // identities用于构建缓存key,作为参数传递给cacheKey方法
  def cacheKey(keys: List[String]): String = keys.mkString("-")

  def cacheField(args: List[_]): String = args.map(_.toString).mkString("-")

}

// 省略子API trait ZStreamCache, trait ZIOCache等。

object Cache {

  // 核心,根据不同implicit实现,统一处理。
  def apply[R, E, T](business: => ZStream[R, E, T])(identities: List[String], args: List[_])(implicit streamCache: ZStreamCache[R, E, T]): ZStream[R, E, T] = {
    streamCache.getIfPresent(business)(identities, args)
  }

  def apply[R, E, T](business: => ZIO[R, E, T])(identities: List[String], args: List[_])(implicit cache: ZIOCache[R, E, T]): ZIO[R, E, T] = {
    cache.getIfPresent(business)(identities, args)
  }
}

使用API

以使用def apply[R, E, T](business: => ZIO[R, E, T])(identities: List[String], args: List[_])(implicit cache: ZIOCache[R, E, T]): ZIO[R, E, T]为例,我们需要在域内提供隐式参数implicit cache: ZIOCache[R, E, T]

def readIOFunction(id: Int, key: String): ZIO[Any, Throwable, String] = {
    // 如果从cache中查数据比ZIO.effect中的操作快很多,则缓存才是有意义的,否则没必要使用。
    val $result = ZIO.effect("hello world" + Random.nextInt())
    Cache($result)(List("UseCaseExample", "readIOFunction"), List(id, key)) // 隐式参数被使用
}

工具自带实现,只需要导入即可,如使用Redis导入import org.bitlap.cacheable.redis.Implicits._,使用caffeine导入import org.bitlap.cacheable.redis.Implicits._。如下想使用自己定义的就需要实现一个ZIOCacheZStreamCache实例作为隐式参数。同时由于作用域的原因,当前域内定义的会覆盖import导入的。

使用@cacheable注解自动生成

@cacheable(local = true) // 默认true,使用`import _root_.org.bitlap.cacheable.caffeine.Implicits._`
def readStreamFunction(id: Int, key: String): ZStream[Any, Throwable, String] = {
  ZStream.fromEffect(ZIO.effect(s"hello world--$id-$key-${Random.nextInt()}"))
}

完整例子:smt-cacheable-examples

这个注解宏逻辑比较简单,核心逻辑只有二十行左右的代码:

def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
    val resTree = annottees.map(_.tree) match {
    case (defDef @ DefDef(mods, name, tparams, vparamss, tpt, _)) :: Nil =>
        if (tpt.isEmpty) {
        c.abort(c.enclosingPosition, "The return type of the method is not specified!")
        }
        val tp = c.typecheck(tq"$tpt", c.TYPEmode).tpe
        if (!(tp <:< typeOf[zio.ZIO[_, _, _]]) && !(tp <:< typeOf[zio.stream.ZStream[_, _, _]])) {
        c.abort(c.enclosingPosition, s"The return type of the method not support type: `${tp.typeSymbol.name.toString}`!")
        }
        val importExpr = if (local) q"import _root_.org.bitlap.cacheable.caffeine.Implicits._" else q"import _root_.org.bitlap.cacheable.redis.Implicits._"
        val newBody =
        q"""
            val $resultValName = ${defDef.rhs}
            val $keyValName = List($getEnclosingClassName, ${name.decodedName.toString})
            $importExpr
            org.bitlap.cacheable.core.Cache($resultValName)($keyValName, ..${getParamsName(vparamss)})
        """
        DefDef(mods, name, tparams, vparamss, tpt, newBody)

    }
    printTree(force = true, resTree)
    c.Expr[Any](resTree)
}

总结

本身对于一个纯异步应用而已,使用cache的场景是很少的,这里更多的是使用macro和implicit设计出灵活的工具。就这个cacheable本身而言,个人认为用处不大。不过思想可以复用。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-03-24 00:21:20  更:2022-03-24 00:23:45 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/31 23:58:14-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码