分享大厂对于缓存操作的封装

hello,伙伴们好久不见,我是shigen。发现有两周没有更新我的文章了。也是因为最近比较忙,基本是993了。

缓存大家再熟悉不过了,几乎是现在任何系统的标配,并引申出来很多的问题:缓存穿透、缓存击穿、缓存雪崩…哎,作为天天敲业务代码的人,哪有时间天天考虑这么多的破事。直接封装一个东西,我们直接拿来就用岂不是美哉。看了项目组的代码,我也忍不住 diy 了,对于增删就算了,就是 get set 的 API 调用,修改?直接删了重新添加吧,哪有先查缓存再去修改保存的。难点就在于缓存的查询,要不缓存的穿透、击穿、雪崩会诞生对吧。
我们先看下缓存的逻辑:

是的,其实就是这么简单,剩下的就是考虑一下缓存穿透问题,最常见的处理方式就是加锁。这里我采用的是信号量 Semaphore。
好的,现在展示我的代码,代码结构如下:

.
├── CacheEnum.java									-- 缓存枚举
├── CacheLoader.java								-- 缓存加载接口
├── CacheService.java								-- 缓存服务
└── CacheServiceImpl.java 					-- 缓存服务实现类1 directory, 4 files

ok,现在我们一一讲解下:

CacheEnum

主要的代码:

public enum CacheEnum {/** 用户token缓存 */USER_TOKEN("USER_TOKEN", 60, "用户token"),/** 用户信息缓存 */USER_INFO("USER_INFO", 60, "用户信息"),;/** 缓存前缀 */private final String  cacheName;/** 缓存过期时间 */private final Integer expire;/** 缓存描述 */private final String  desc;

其他的就是 get/set 方法,这里不做展示。主要解决的痛点就是缓存过期时间的统一管理、缓存名称的统一管理。

CacheService

这里边就是定义了缓存操作的接口:

public interface CacheService {/*** 获取缓存* @param cacheName 缓存名称* @param key 缓存key* @param type 缓存类型* @return 缓存值* @param <T> 缓存类型*/<T> T get(String cacheName, String key, Class<T> type);/*** 获取缓存* @param cacheName 缓存名称* @param key 缓存key* @param type 缓存类型* @param loader 缓存加载器* @return 缓存值* @param <T> 缓存类型*/<T> T get(String cacheName, String key, Class<T> type, CacheLoader<T> loader);/*** 删除缓存* @param cacheName 缓存名称* @param key 缓存key*/void delete(String cacheName, String key);/*** 设置缓存* @param cacheName 缓存名称* @param key 缓存key* @param value 缓存值*/void set(String cacheName, String key, Object value);
}    

其实就是一些增删查的方法。只不过这里我们更加关注的是:缓存的名称,缓存的 key,缓存对象,缓存对象的类型。
在 22 行这里用到了CacheLoader 接口,其实就是处理缓存对象在缓存中拿不到的问题,它的定义也很简单:

@FunctionalInterface
public interface CacheLoader<V> {/*** 加载缓存* @param key 缓存key* @return 缓存值*/V load(String key);
}

就一个方法,直接使用上 lambda 表达式,下边的测试类会讲到。

CacheServiceImpl

@Slf4j
@Service
public class CacheServiceImpl implements CacheService {/** Semaphore */private static final Semaphore        CACHE_LOCK = new Semaphore(100);/** 缓存操作 */@Autowiredprivate RedisTemplate<String, Object> redisTemplate;/*** 获取缓存key* @param cacheName 缓存名称* @param key 缓存key* @return 缓存key*/private String getCacheKey(String cacheName, String key) {Assert.isTrue(StrUtil.isNotBlank(cacheName), "cacheName不能为空");Assert.isTrue(StrUtil.isNotBlank(key), "key不能为空");Assert.isTrue(CacheEnum.getByCacheName(cacheName) != null, "需要使用CacheEnum枚举创建缓存");return cacheName + ":" + key;}@Overridepublic <T> T get(String cacheName, String key, Class<T> type) {Object value = redisTemplate.opsForValue().get(getCacheKey(cacheName, key));if (value != null) {return type.cast(value);}return null;}@Overridepublic <T> T get(String cacheName, String key, Class<T> type, CacheLoader<T> cacheLoader) {try {// 获取锁, 防止缓存击穿CACHE_LOCK.acquire();String cacheKey = getCacheKey(cacheName, key);Object value = redisTemplate.opsForValue().get(cacheKey);if (value != null) {return type.cast(value);}value = cacheLoader.load(cacheKey);if (value != null) {this.set(cacheName, key, value);return type.cast(value);}} catch (InterruptedException e) {log.warn("获取锁失败");} finally {CACHE_LOCK.release();}return null;}@Overridepublic void delete(String cacheName, String key) {redisTemplate.opsForValue().getOperations().delete(getCacheKey(cacheName, key));}@Overridepublic void set(String cacheName, String key, Object value) {String cacheKey = getCacheKey(cacheName, key);CacheEnum cacheEnum = CacheEnum.getByCacheName(cacheName);Assert.isTrue(cacheEnum != null, "需要使用CacheEnum枚举创建缓存");redisTemplate.opsForValue().set(cacheKey, value, cacheEnum.getExpire(), TimeUnit.SECONDS);}
}

这里就是接口的具体实现。需要注意:

  1. 在获得完整的缓存 key 的时候,我们其实对于缓存的 cacheName 做了验证,参见上代码块 21 行,不允许自己定义缓存的 cacheName,统一在枚举类中定义。
  2. 因为 tair 的资源有点不好申请,这里使用的 redis 作为缓存的工具,结合 spring-boot-starter-data-redis 作为操作的 API。
  3. 应对缓存穿透问题,这里使用的是Semaphore 信号量。

别的就没什么好说的,现在我们来测试一下我们的封装是否管用。

测试代码

设置缓存

测试用定义的枚举类创建缓存:

    @Testvoid set() {cacheService.set(CacheEnum.USER_INFO.getCacheName(), "10001", getFakeUser("10001"));}

是没问题的,不用枚举类创建:

    @Testvoid testSetSelfDefinedCacheName() {cacheService.set("user", "10001", getFakeUser("10001"));}

直接异常出来了:

java.lang.IllegalArgumentException: 需要使用CacheEnum枚举创建缓存
读取缓存

读取缓存,我的 API 中分为两种情况:直接读取,没有就算了;读取缓存,没有的话再从 DB 中拿。对应的单测如下:

    @Testvoid testGet() {UserEntity user = cacheService.get(CacheEnum.USER_INFO.getCacheName(), "10001",UserEntity.class);log.info("user: {}", user);}@Testvoid testGetWithCacheLoader() {UserEntity user = cacheService.get(CacheEnum.USER_INFO.getCacheName(), "10001",UserEntity.class, new CacheLoader<UserEntity>() {@Overridepublic UserEntity load(String key) {return getFakeUser("10001");}});log.info("user: {}", user);}@Testvoid testGetWithSimpledCacheLoader() {UserEntity user = cacheService.get(CacheEnum.USER_INFO.getCacheName(), "10001",UserEntity.class, key -> getFakeUser(key));log.info("user: {}", user);}

第三种就是对于 lambda 接口的简化写法。
基于以上的方式,我们操作缓存就变得更加容易了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/370797.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

UEC++ 虚幻5第三人称射击游戏(二)

UEC++ 虚幻5第三人称射击游戏(二) 派生榴弹类武器 新建一个继承自Weapon的子类作为派生榴弹类武器 将Weapon类中的Fire函数添加virtual关键字变为虚函数让榴弹类继承重写 在ProjectileWeapon中重写Fire函数,新建生成投射物的模版变量 Fire函数重写逻辑 代码//生成的投射物U…

如何计算弧线弹道的落地位置

1&#xff09;如何计算弧线弹道的落地位置 2&#xff09;Unity 2021 IL2CPP下使用Protobuf-net序列化报异常 3&#xff09;编译问题&#xff0c;用Mono可以&#xff0c;但用IL2CPP就报错 4&#xff09;Wwise的Bank在安卓上LoadBank之后&#xff0c;播放没有声音 这是第393篇UWA…

DP:背包问题----0/1背包问题

文章目录 &#x1f497;背包问题&#x1f49b;背包问题的变体&#x1f9e1;0/1 背包问题的数学定义&#x1f49a;解决背包问题的方法&#x1f499;例子 &#x1f497;解决背包问题的一般步骤&#xff1f;&#x1f497;例题&#x1f497;总结 ❤️❤️❤️❤️❤️博客主页&…

毕业季有感

本文介绍一些刚毕业、即将入职前的随想与心得。 毕业和上班无缝衔接。要离开北京了&#xff0c;来到天津。 我一向很喜欢探索新环境&#xff0c;每一次要到新的学校、新的城市、新的单位都会很激动&#xff1b;这一次也是一样&#xff0c;在一开始几乎只有对新环境的憧憬。但是…

rocketmq-console可视化界面功能说明

rocketmq-console可视化界面功能说明 登录界面OPS(运维)Dashboard(驾驶舱)Cluster(集群)Topic(主题)Consumer(消费者)Producer(生产者)Message(消息)MessageTrace(消息轨迹) rocketmq-console是rocketmq的一款可视化工具&#xff0c;提供了mq的使用详情等功能。 本章针对于rock…

【机器学习】机器学习重塑广告营销:精准触达,高效转化的未来之路

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀目录 &#x1f4d2;1. 引言&#x1f4d9;2. 机器学习基础与广告营销的结合&#x1f9e9;机器学习在广告营销中的核心应用领域&#x1f339;用…

CSRF靶场通关合集

目录 前言 CSRF漏洞总结 1.PiKachu靶场 1.1CSRF(get) 1.2 CSRF(post)请求 1.3 CSRF Token 2.DVWA靶场 难度低 难度中 难度高 前言 最近系统的将从web渗透到内网渗透的知识点做一个回顾,同时结合一些实战的案例来演示,下面是对刚开始学习时对靶场的一个总结. CSRF漏洞…

LLM - 循环神经网络(RNN)

1. RNN的关键点&#xff1a;即在处理序列数据时会有顺序的记忆。比如&#xff0c;RNN在处理一个字符串时&#xff0c;在对字母表顺序有记忆的前提下&#xff0c;处理这个字符串会更容易。就像人一样&#xff0c;读取下面第一个字符串会更容易&#xff0c;因为人对字母出现的顺序…

LabVIEW汽车转向器测试系统

绍了一种基于LabVIEW的汽车转向器测试系统。该系统集成了数据采集、控制和分析功能&#xff0c;能够对转向器进行高效、准确的测试。通过LabVIEW平台&#xff0c;实现了对转向器性能参数的实时监测和分析&#xff0c;提升了测试效率和数据精度&#xff0c;为汽车转向器的研发和…

《Windows API 每日一练》8.4 edit控件

编辑类是最简单的预定义窗口类&#xff0c;而另一方面却又是最复杂的。当你用“edit”作为类名创建子窗口时&#xff0c;可以基于CreateWindow调用的x坐标、y坐标、宽度和高度参数定义一个矩形。这个矩形包含可编辑的文本。一旦子窗口控件获得输入焦点&#xff0c;你就可以输入…

(南京观海微电子)——MOS管原理及应用区别

MOS管&#xff1a; 全称为金属氧化物半导体场效应管&#xff08;Metal Oxide Semiconductor Field Effect Transistor&#xff09;&#xff0c;也被称为MOSFET&#xff08;Metal-Oxide-Semiconductor Field-Effect Transistor&#xff09;。它是一种半导体器件&#xff0c;常用…

【PB案例学习笔记】-27制作一个控制任务栏显示与隐藏的小程序

写在前面 这是PB案例学习笔记系列文章的第27篇&#xff0c;该系列文章适合具有一定PB基础的读者。 通过一个个由浅入深的编程实战案例学习&#xff0c;提高编程技巧&#xff0c;以保证小伙伴们能应付公司的各种开发需求。 文章中设计到的源码&#xff0c;小凡都上传到了gite…

240706_昇思学习打卡-Day18-基于MindSpore的GPT2文本摘要

240706_昇思学习打卡-Day18-基于MindSpore的GPT2文本摘要 今天做一个根据一段文章提取摘要的提取器&#xff0c;基于nlpcc2017摘要数据&#xff0c;内容为新闻正文及其摘要&#xff0c;就是训练集及标签。 首先我们来预装以下MindSpore环境 %%capture captured_output # 实验…

vb.netcad二开自学笔记2:认识vs编辑器

认识一下宇宙第一编辑器的界面图标含义还是很重要的&#xff0c;否则都不知道面对的是什么还怎么继续&#xff1f; 一、VS编辑器中常见的图标的含义 变量 长方体&#xff1a;变量 局部变量 两个矩形块&#xff1a;枚举 预定义的枚举 紫色立方体&#xff1a;方法 橙色树状结构…

数据结构——二叉树相关题目

1.寻找二叉树中数值为x的节点 //寻找二叉树中数值为x的节点 BTNode* TreeFind(BTNode* root, BTDataType x)//传过来二叉树的地址和根的地址&#xff0c;以及需要查找的数据 {if (root Null){return Null;}//首先需要先判断这个树是否为空&#xff0c;如果为空直接返回空if (…

筛选Github上的一些优质项目

每个项目旁都有标签说明其特点&#xff0c;如今日热捧、多模态、收入生成、机器人、大型语言模型等。 项目涵盖了不同的编程语言和领域&#xff0c;包括人工智能、语言模型、网页数据采集、聊天机器人、语音合成、AI 代理工具集、语音转录、大型语言模型、DevOps、本地文件共享…

《Programming from the Ground Up》阅读笔记:p19-p48

《Programming from the Ground Up》学习第2天&#xff0c;p19-p48总结&#xff0c;总计30页。 一、技术总结 1.object file p20, An object file is code that is in the machine’s language, but has not been completely put together。 之前在很多地方都看到object fi…

IDEA中使用Maven打包及碰到的问题

1. 项目打包 IDEA中&#xff0c;maven打包的方式有两种&#xff0c;分别是 install 和 package &#xff0c;他们的区别如下&#xff1a; install 方式 install 打包时做了两件事&#xff0c;① 将项目打包成 jar 或者 war&#xff0c;打包结果存放在项目的 target 目录下。…

C++_STL---list

list的相关介绍 list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。 list的底层是带头双向循环链表结构&#xff0c;链表中每个元素存储在互不相关的独立节点中&#xff0c;在节点中通过指针指向其前一个元素和后一个元素。…

Google重大更新--解读Android Auto认证4.3

Google在今年五月更新了Android Auto 4.2.2版本&#xff0c;而在2024年7月他们推出了Android Auto 4.3版本&#xff0c;这是自2023年9月以来对Android Auto 4.2版本的一次重大更新。 为了确保合规性和顺利认证&#xff0c;OEM和Tire1必须确保PDK组件版本与正在认证的主机的Rece…