用无感知的方式为你的数据加上一层缓存

前言

用无感知的方式为你的数据加上一层缓存

本篇文章会介绍一个我自己写的库,库地址在这里,主要作用是提供一个注解,在你方法上使用这个注解,库提供的功能会帮你把数据自动缓存起来,下次再调用这个方法只要入参是一致的则直接会从缓存里面拿数据,不会再执行方法了(方法里面的内容可能是走DB或者PRC)。

写这个库的原因是公司内部有一个类似工具,刚开始并没有Get到它有多大的用处,随着在公司接触更多的业务需求,发现这真的是太香了。并且我对它有一些自己的更多想法,所以打算自己写一个开源出来一方面是可以给自己其他开源项目使用,另外一方面我觉得有相同需求的人还蛮多的,而Github上也没有一个尽善尽美的类似库。

不同量级用户的代码真的是不一样,我在原来公司也做的也是C端的业务,对于缓存运用虽然非常多,但是主要是利用Redis数据结构特点来做一些业务上的实现会更多一些,例如用string来做互斥锁/配置开关/状态记录,用zset来做榜单/实现dmq需求,用list/set/hash来做高性能短时数据库。 对于很多DB的数据都是直接查,主要当时的产品用户量非常平稳,拿了融资的那段时间公司有钱,数据库用的非常好,从库也多, 上线不需要压测,随便点点RTT自己觉得过得去就行,所以我觉得偶尔写一下读写redis的代码也没啥用工作量。

但是现在的公司,C端接口都做限流,除了分页数据几乎所有数据都加一层缓存,甚至分页数据的第一页都会做。这就很麻烦,每次都要写一大堆重复代码,从Redis取值,空走就DB/RPC,然后回写Redis。最可怕的是批量取值,从Redis去拿完以后还要把没有命中的筛选出来然后去DB/RPC取,然后回写这部分值到Redis。因为用户量很大,还有羊毛党会刷接口,未命中的值可能还需要做空缓存防止穿透到DB。

本仓库包含以下内容:

  1. @Cache注解一个
  2. 对指定方法进行自动缓存(Redis或者caffeine本地缓存)
  3. 可对不存在的数据进行自动空缓存,并发下防止缓存穿透
  4. 可开启获取缓存时自动进行互斥锁,防止缓存击穿保护DB(下个版本更新)

安装导入

本库已经上架maven中央仓库,已经引入到自己项目pom文件中就行, 请注意直接在mvnrepository会出现很多2.0.0以下的版本,请不要使用,那…那…是我上架的是做测试不小心发到release上的debug版本。

所有版本查询请点击这里 这里; 这里

Maven


    cn.someget
    cache-anno
    2.0.0

Gradle

// https://mvnrepository.com/artifact/cn.someget/cache-anno
implementation group: 'cn.someget', name: 'cache-anno', version: '2.0.0'

导入jar包就好了,除此之外无需为本库做任何配置,所有bean也已经spring.factories暴露出来,可以直接被启动类扫描到。

使用说明

一. 注意事项

  1. 导入本库后配置中必须要有redis相关的配置,支持jedis和lettuce,只要spring自动装配或者你手动装配一个redisTemplate就行(注意如果你有多个RedisTemplate
  2. 使用注解存入的数据,序列化方式均为String,当然注解自动读数据反序列化也是String,所以如果你有使用注解存入数据,但是不适用注解读取数据的需求,请使用String反序列化读取。
  3. 所有redis的io异常都已经捕获,有异常只会打日志,不会影响你的业务,不会影响你的数据读取,兜底会走db查询。

二. 使用方法

  1. 查询一个数据,例如下面,通过uid查询用户的详细数据,加上@Cache(注意不要导错包哈)注解,prefix是你缓存数据Redis的Key前缀,注意需要加一个占位符,我自动写入的时候会把入参拼接到这个prefix上。 查询之前会根据注解中的prefix拼接上入参uid从Redis中尝试获取数据,如果没有数据则执行方法,执行完方法再自动写入缓存。那么下次在执行这个方法的时候,又会执行上面步骤prefix拼接入参尝试从Redis获取数据,但是因为上次自动写入了,所以拿到数据就直接返回了,不会再执行方法走DB查询了。
// 建议prefix定义成常量,便于复用, one to one可以不用传clazz参数
@Cache(prefix = "user:info:%d")
public UserInfoBO getIpUserInfo(Long uid) {
    UserInfo userInfo = userInfoMapper.selectByUid(uid);
    if (userInfo == null) {
         return null;
    }
    UserInfoBO bo = new UserInfoBO();
    BeanUtils.copyProperties(userInfo, bo);
    return bo;
}
  1. 查询多个数据,例如下面,通过一堆itemId获取多个item的详细数据,和上面一样你需要指定prefix,查询之前会通过把入参所有的itemId进行和注解里面的prefix拼接,然后 批量一次性尝试从redis里面获取,如果所有itemId都获取到则直接返回,但是如果有未命中的itemId则会把这未命中的itemId再统一走方法进行远程获取最后和Redis里面的汇总(远程获取完 会自动写入Redis,方便你下次命中)
// 返回值是集合类型,clazz必须要传
@Cache(prefix = "mall:item:%d", clazz = MallItemsBO.class)
public Map listItems(List itemsIds) {
    BaseResult> result = itemsRemoteClient.listItems(new ItemsReqDTO());
    if (result == null || CollectionUtils.isEmpty(result.getData())) {
         return Collections.emptyMap();
    }
    return result.getData().stream()
      .map(mallItemsDTO -> {
         MallItemsBO mallItemsBO = new MallItemsBO();
         BeanUtils.copyProperties(mallItemsDTO, mallItemsBO);
         return mallItemsBO;
      }).collect(Collectors.toMap(MallItemsBO::getItemId, Function.identity()));
}

三. 实现原理

核心原理是利用AOP对你为注解设置的参数和入参进行拼接成一个key,每次在方法执行前走缓存去查询一遍,如果缓存有数据则直接返回不再执行方法,如果缓存没有查数据,则执行方法拿到数据取写入缓存。

用无感知的方式为你的数据加上一层缓存

如果是数据批量获取,还会看Redis命中的数量来判断是全部返回,还是把未命中的去执行方法,最后把缓存和方法查询结果一起汇总返回给方法调用方

用无感知的方式为你的数据加上一层缓存

四. 所有支持的方法类型

类型 prefix 入参 出参 备注 one to one 自定义:占位符 包装类型或者String ? extends Object 有几个入参就要有几个占位符,不然无法使用 ont to list 自定义:占位符 包装类型或者String List 同上,理论上来说List和object对于本库是一个东西,因为我是用的是String的序列化,相同理解就好。 list to map_one 自定义:占位符 List

因为java的泛型擦除的限制我无法判断Map的value泛型具体是什么, 请@Cache中的参数hasMoreValue需要设置成true,请切记

其中占位符要注意,如果是String占位符要%s,整型占位符%d,浮点型占位符%f 详情请参考这里

其实总的来说就是分为两类,入参是对象或者List,也就是单个获取和批量获取,如果是批量获取的话切记List要在一号位,并且方法入参不能超过两个,否则会报错提示不支持。

五. 注解里面的参数含义

名称 含义 备注 prefix Redis中key的前缀, 注意要使用占位符,如入参是long, 占位符就是prefixKey:%d expire 过期时间(单位秒) 如果使用注解的时候不设置则默认10分钟,注意本库写入缓存都有过期时间,因为我想不到你为啥要不设置TTL missExpire 空缓存过期时间(单位秒) 如果是0则表示不开启空缓存(默认是0),空缓存过期时间表示如果从db也没查到生成空缓存到Redis,这个空缓存的过期时间(肯定正常缓存短,推荐3-10秒) hasMoreValue 是否list to map_map类型 因为java的泛型擦除的限制我无法判断Map的value泛型具体是什么, 请@Cache中的参数hasMoreValue需要设置成true,请切记 clazz 集合类返回值对应类型
如果返回值是List或者Map 这个必传

,因为java泛型擦除我不知道你集合泛型,反序列化需要使用。如果是one to one类型的话,这个可以省略。 usingLocalCache 是否使用本地缓存 设置true以后从Redis读取之前会查询一遍本地缓存(使用caffeine),同理拿完数据也会回写到caffeine

六. 其他功能详细说明

启用空缓存写入

@Cache含有missExpire的属性,属性含义是DB中不存在的值的过期时间(单位是秒),默认值是0,如果是0则表示如果查询的值DB中不存在的话,不进行空缓存处理。如果是非0,那么从Redis中查询值未命中会走方法查询,方法查询返回结果也是不存在那么会储存一个空值(如果是对象的是 "{}",如果是集合则是 []),过期时间是missExpire这个值(推荐3-10秒左右),支持上述表格中的所有类型。

用无感知的方式为你的数据加上一层缓存

注意:

  • 开启空缓存以后插入记录后也要进行删除缓存处理,因为可能对应值DB中已经有了,但是Redis还存在空值正处于TTL中。
  • 空缓存如果是对象是会缓存id为-1的对象,如果是集合会缓存一个空集合,id为-1的对象不会返回给方法调用方,会直接被过滤掉,符合大家编码习惯。 要注意的是缓存对象必须要有id字段(Integer和Long都可以),否则无法过滤会返回的一个所有属性都是null的空对象。2.0.1版已经支持设置对象空缓存为 "{}"所以不用必须含有id字段了,如果命中空缓存方法调用方拿到的是null,符合大家编码习惯, *但是所有空缓存对象一定要有无参构造,否则反序列化无法生成空对象。

启用本地缓存

@Cache含有usingLocalCache的属性,属性含义是是否启用本地缓存,在一些场景下电商营销域要频繁通过RPC获取商品域的商品数据,因为营销场景QPS非常高,即便在RPC之前有一层Redis,但是频繁获取商品导致Redis的QPS非常高。而商品数据是不怎么变化的,所以在Redis之前再加一层本地缓存就显得非常有必要,Redis的QPS能降低不少。

有不少场景进行本地缓存提升都非常大,本库也支持进行本地缓存,只需使用注解是把usingLocalCache的属性设置为true(默认是false),本库使用的本地缓存是最近风头压过guava的caffeine,这样获取数据之前先从本地缓存进行查询,如果本地缓存没有命中则再去查询Redis。

注意:多层缓存会增加Cache-DB不一致可能,一定程度抗流可以用,但是不要过分依赖,这里本地缓存默认TTL是3秒,暂时不支持修改。

后言

下一步计划是

  1. 写好单元测试,这个库我还会写一篇文章,基于单测的code更加直观来介绍它是怎么的好用。
  2. 防止缓存击穿已经有好的方案,下一个版本会迭代进去,对于高风险接口可以开启。
  3. 目前没有配套的缓存逐出注解,参考Spring-Cache我想一下怎么做到最好

如果你感兴趣或者有相同的需求欢迎使用本库,更加提一个 Issue 或者提交一个 Pull Request。

Original: https://www.cnblogs.com/oreoft/p/16274272.html
Author: 没有气的汽水
Title: 用无感知的方式为你的数据加上一层缓存

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/613103/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 【FTK Imager篇】FTK Imager磁盘镜像的哈希报告翻译

    FTK Imager制作完镜像后,会生成镜像文件和哈希报告,来验证镜像文件的哈希值和驱动器哈希值在创建镜像后是否匹配,以用作基准来证明案例证据的完整性。—【suy】 磁…

    Linux 2023年6月13日
    082
  • Java基础系列–07_String、StringBuffer和StringBuilder

    String类(1)字符串:字符串是 常量;它们的值在 创建之后不能更改,存储在堆中。如果字符串多次赋值,其实是每次重新赋值的时候程序都先在内存中寻找已开辟的空间是否存在该值;如果…

    Linux 2023年6月7日
    076
  • 一名研究生的自我修养

    一、如何学习 研究生阶段是学习效率最高的阶段。第一是因为动机纯粹,以前上学这么多年大部分的学习动机只是为了成绩,这个学习动机其实会很大限制同学的主动学习意愿,往往是被动学习,为了成…

    Linux 2023年6月14日
    082
  • 常见框架漏洞

    ThinkPHP 框架漏洞 thinkphp是一个国内轻量级的开发框架,采用php+apache,在更新迭代中,thinkphp也经常爆出各种漏洞,thinkphp一般有think…

    Linux 2023年6月14日
    095
  • Spring中毒太深,离开了Spring,我居然连最基本的接口都不会写了¯_(ツ)_/¯

    前言 众所周知,Java必学的框架其中就是SSM,Spring已经融入了每个开发人员的生活,成为了不可或缺的一份子。 随着 Spring 的崛起以及其功能的完善,现在可能绝大部分项…

    Linux 2023年6月13日
    079
  • 关于.NET CORE 编译时错误:Microsoft.AspNetCore.Razor.Design.CodeGeneration.targets(79, 5): The project XXXXX must provide a value for Configuration.

    此笔记记载了本人在编译.Net Core项目时遇到的 Microsoft.AspNetCore.Razor.Design.CodeGeneration.targets(79, 5)…

    Linux 2023年6月14日
    0109
  • 自动化集成:Kubernetes容器引擎详解

    前言:该系列文章,围绕持续集成:Jenkins+Docker+K8S相关组件,实现自动化管理源码编译、打包、镜像构建、部署等操作; 本篇文章主要描述Kubernetes引擎用法。 …

    Linux 2023年5月27日
    0111
  • 双绞线

    双绞线简介 双绞线(twisted pair,TP)是一种综合布线工程中最常用的传输介质,双绞线一般由两根22~26号绝缘铜导线相互缠绕而成,在一个电缆套管里的,不同线对具有不同的…

    Linux 2023年6月7日
    082
  • 【转载】技术研究和个人成长方法

    TK 教主 16 年在腾讯内部的一个分享,讲述安全研究者的个人成长。虽然分享的内容是关于安全研究领域,但我相信对各个领域的学习成长是相同的。这里记录如下: 个人成长 确立个人方向,…

    Linux 2023年6月13日
    090
  • FusionCompute制作Linux虚拟机模板

    创建虚拟机 创建虚拟机下一步这里实验就创建红帽7.4选择存储磁盘精简创建先在存储上传红帽镜像挂载镜像VNC登录安装安装完成配置yum源安装bzip*(因为最小化安装没有bzip程序…

    Linux 2023年6月8日
    0132
  • zabbix钉钉报警

    1、要在linux中安装python3 yum install -y python3(如果直接安装不上参考别的文章) 这个安装好后默认有pip3(安装好模块否则发送不到) pip3…

    Linux 2023年6月6日
    091
  • 双系统设置默认启动系统

    在原有windows系统下,我们装完Ubuntu系统后,会出现Ubuntu的grub引导界面(倒计时后自动进入Ubuntu),如下图所示。 假设我们需要将倒计时后默认启动的系统改为…

    Linux 2023年6月14日
    0133
  • 在VS code使用Remote-SSH远程连接Linux 开发C++ 配置详细介绍

    VS code 远程连接服务器,编译C++ 一、前期准备 1、VS code安装 Remote-SSH插件 2、Windows安装SSH。 3、Linux服务器连接测试。 a.接通…

    Linux 2023年5月27日
    0142
  • Redis的穿透、击穿、雪崩之间的区别与联系

    [本文出自天外归云的博客园] 缓存穿透 redis查询后有数据库查询的情况,查的数据在数据库里本来就没有,所以缓存里也没有,所以查询穿透了缓存,直接落到了数据库上,这就是缓存穿透 …

    Linux 2023年5月28日
    085
  • 前端以BASE64码的形式上传图片

    一直有一个很苦恼的问题困扰着铁柱兄,每次上传图片的时候前端要写一大堆js,然后后台也要写一堆java代码做处理。于是就在想,有没有简单又方便的方法把图片上传。今天算是搞定了。现在发…

    Linux 2023年6月13日
    078
  • Java基础系列–04_数组

    一维数组:(1)数组:存储同一种数据类型的多个元素的容器。(2)特点: 每一个元素都有编号,从0开始,最大编号是数组的长度-1。编号的专业叫法: 索引(3)定义格式A:数据类型[]…

    Linux 2023年6月7日
    091
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球