专业建设网站专家/百度seo关键词工具

专业建设网站专家,百度seo关键词工具,在哪个网站做一件代发靠谱,深圳营销网站建设报价redis中常见的问题 前言 在本文中,我们将探讨 Redis 在缓存中的应用,并解决一些常见的缓存问题。为了简化理解,本文中的一些配置是直接写死的,实际项目中建议将这些配置写入配置文件,并通过配置文件读取。 一、为什…

redis中常见的问题

前言

在本文中,我们将探讨 Redis 在缓存中的应用,并解决一些常见的缓存问题。为了简化理解,本文中的一些配置是直接写死的,实际项目中建议将这些配置写入配置文件,并通过配置文件读取。

一、为什么需要缓存?

在Web应用开发中,频繁的数据库查询和复杂的计算操作会显著影响系统性能。为了提升系统的响应速度和整体性能,缓存机制成为了不可或缺的一部分。Spring Cache通过抽象缓存层,使开发者能够通过简单的注解实现方法级别的缓存,从而有效减少重复计算和数据库访问,显著提升系统的响应速度。

前提

本文使用Redis作为缓存管理器(CacheManager),因此你需要确保正确引入并配置Redis。

引入与基本使用(此处由AI代写,非本文重点)

Spring Cache快速配置

Java配置类示例:

@Configuration
@EnableCaching
@Slf4j
public class RedisCachingAutoConfiguration {@Resourceprivate RedisConnectionFactory redisConnectionFactory;@Beanpublic CacheManager defaultCacheManager() {RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1));return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory)).cacheDefaults(configuration).build();}
}

三、核心注解深度解析

1. @Cacheable:数据读取缓存

@Cacheable(value = "users", key = "#userId", unless = "#result == null")
public User getUserById(Long userId) {return userRepository.findById(userId).orElse(null);
}
  • value:指定缓存名称(必填)
  • key:支持SpEL表达式生成缓存键
  • condition:方法执行前判断(例如userId > 1000才缓存)
  • unless:方法执行后判断(例如空结果不缓存)
属性执行时机访问变量作用场景
condition方法执行前判断只能访问方法参数(如 #argName决定是否执行缓存逻辑(包括是否执行方法体)
unless方法执行后判断可以访问方法参数和返回值(如 #result决定是否将方法返回值存入缓存(不影响是否执行方法体)

2. @CachePut:强制更新缓存

@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {return userRepository.save(user);
}

适用场景:数据更新后同步缓存,确保后续读取的是最新数据。

3. @CacheEvict:精准清除缓存

@CacheEvict(value = "users", key = "#userId", beforeInvocation = true)
public void deleteUser(Long userId) {userRepository.deleteById(userId);
}
  • 删除指定条目:通过key精准定位
  • 清空整个缓存allEntries = true
  • beforeInvocation:方法执行前清除(避免执行失败导致脏数据)

4. @Caching:组合操作

@Caching(put = @CachePut(value = "users", key = "#user.id"),evict = @CacheEvict(value = "userList", allEntries = true)
)
public User updateUserProfile(User user) {// 业务逻辑
}

5. @CacheConfig:类级别配置

@Service
@CacheConfig(cacheNames = "products")
public class ProductService {// 类中方法默认使用products缓存
}

工程化实践解决方案

前面的示例内容由AI编写,经过测试可用。然而,在实际使用中,这些用法可能不符合某些场景需求,或者使用起来不够方便。以下是一些常见问题及解决方案:

  1. 自动生成的key格式为{cacheable.value}::{cacheable.key},为什么一定是"::"两个冒号?
    (查看源码org.springframework.data.redis.cache.CacheKeyPrefix
    如果需要为key统一加前缀,可以在RedisCacheConfiguration中设置。

  2. 批量删除时,@CacheEvict不够灵活。

    • 方案一:使用@CacheEvict并设置allEntriestrue,但这样会删除所有value相同的缓存,可能会误删不需要清除的数据。
    • 方案二:手动调用删除缓存。
    • 方案三:自定义批量删除缓存注解。
  3. 大部分场景下,使用某个固定属性值作为缓存时,增删改操作每次都要写key取某个值,非常繁琐。

    • 方案一:自定义KeyGenerator
  4. 高并发场景下如何确保数据的一致性和系统的稳定性?

    • 方案一:在单体架构中,可以在构建CacheManager时指定RedisCacheWriterlockingRedisCacheWriter,并在@CachePut@CacheEvict中指定带锁的CacheManager
    • 方案二:在集群环境中,可以在@CachePut@CacheEvict对应的方法上加分布式锁(如Redisson)。
  5. 如何防止缓存雪崩?

    • 定义多个缓存管理器,每个管理器有不同的过期时间。
    • 在方法上指定使用哪个缓存管理器。
  6. 如何防止缓存穿透?

    • 缓存框架中允许缓存null,未找到的数据可以直接缓存空值。

统一修改前缀与定义key序列化

import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;import javax.annotation.Resource;
import java.time.Duration;/***redisCache配置** @author weiwenbin* @date 2025/03/11 下午5:15*/
@Configuration
@EnableCaching
@Slf4j
public class RedisCachingAutoConfiguration {@Resourceprivate RedisConnectionFactory redisConnectionFactory;@Beanpublic CacheManager defaultNoLockingCacheManager() {String keyPre = "hatzi";String directoryName = "cache";RedisCacheConfiguration configuration = getCacheConfiguration(Duration.ofHours(1), keyPre, directoryName);return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory)).cacheDefaults(configuration).build();}/*** 缓存的异常处理*/@Beanpublic CacheErrorHandler errorHandler() {// 异常处理,当Redis发生异常时,打印日志,但是程序正常走log.info("初始化 -> [{}]", "Redis CacheErrorHandler");return new CacheErrorHandler() {@Overridepublic void handleCacheGetError(RuntimeException e, Cache cache, Object key) {log.error("Redis occur handleCacheGetError:key -> [{}]", key, e);}@Overridepublic void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {log.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e);}@Overridepublic void handleCacheEvictError(RuntimeException e, Cache cache, Object key) {log.error("Redis occur handleCacheEvictError:key -> [{}]", key, e);}@Overridepublic void handleCacheClearError(RuntimeException e, Cache cache) {log.error("Redis occur handleCacheClearError:", e);}};}public static RedisCacheConfiguration getCacheConfiguration(Duration duration, String keyPre, String directoryName) {RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig().entryTtl(duration);/*** 默认CacheKeyPrefix 中分隔符为"::" 我想改成":" 所以这样写* 20250315放弃serializeKeysWith是因为自定义批量删除注解serializeKeysWith设置的前缀未生效*/configuration = configuration.computePrefixWith(cacheName -> {String pre = "";if (StrUtil.isNotBlank(keyPre)) {pre += keyPre + ":";}if (StrUtil.isNotBlank(directoryName)) {pre += directoryName + ":";}return pre + cacheName + ":";});return configuration;}
}

自定义KeyGenerator

自定义KeyGenerator

@Component
@Slf4j
public class PkKeyGenerator implements KeyGenerator {@Override@Nonnullpublic Object generate(@Nonnull Object target, @Nonnull Method method, Object... params) {if (params.length == 0) {log.info("PkKeyGenerator key defaultKey");return "defaultKey";}for (Object param : params) {if (param == null) {continue;}if (param instanceof PkKeyGeneratorInterface) {PkKeyGeneratorInterface pkKeyGenerator = (PkKeyGeneratorInterface) param;String key = pkKeyGenerator.cachePkVal();if (StrUtil.isBlank(key)) {return "defaultKey";}log.info("PkKeyGenerator key :{}", key);return key;}}log.info("PkKeyGenerator key defaultKey");return "defaultKey";}
}

自定义接口

public interface PkKeyGeneratorInterface {String cachePkVal();
}

入参实现接口

public class SysTenantQueryDTO implements PkKeyGeneratorInterface, Serializable {private static final long serialVersionUID = 1L;@ApiModelProperty(value = "id")private Long id;@Overridepublic String cachePkVal() {return id.toString();}
}

注解中使用

@Cacheable(value = "sysTenant", keyGenerator = "pkKeyGenerator")
public SysTenantVO getVOInfoBy(SysTenantQueryDTO queryDTO) {// 业务代码
}

自定义注解批量删除

工具类

public class CacheDataUtils {/*** 批量键清除方法* 该方法用于从指定的缓存中清除一批键对应的缓存对象* 主要解决批量清除缓存的需求,提高缓存管理的灵活性和效率** @param cacheManager 缓存管理器,用于管理缓存* @param cacheName    缓存名称,用于指定需要操作的缓存* @param keys         需要清除的键集合,这些键对应的缓存对象将会被清除*/public static void batchEvict(CacheManager cacheManager, String cacheName, Collection<?> keys) {// 检查传入的键集合是否为空,如果为空则直接返回,避免不必要的操作if (CollUtil.isEmpty(keys)) {return;}// 获取指定名称的缓存对象Cache cache = cacheManager.getCache(cacheName);// 检查缓存对象是否存在,如果存在则逐个清除传入的键对应的缓存对象if (cache != null) {keys.forEach(cache::evict);}}
}

自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BatchCacheEvict {/*** 目标缓存名称** @return String[]*/String[] cacheNames() default {};/*** 缓存键(SpEL表达式)** @return String*/String key();/*** 指定CacheManager Bean名称** @return String*/String cacheManager() default "";/*** 是否在方法执行前删除* 建议后置删除** @return boolean*/boolean beforeInvocation() default false;/*** 条件表达式(SpEL)** @return String*/String condition() default "";
}

切面编程

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.hatzi.core.enums.SystemResultEnum;
import com.hatzi.core.exception.BaseException;
import com.hatzi.sys.cache.annotation.BatchCacheEvict;
import com.hatzi.sys.cache.util.CacheDataUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.cache.CacheManager;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;import java.util.Collection;/*** 批量清除缓存切面类* 用于处理带有 @BatchCacheEvict 注解的方法,进行缓存的批量清除操作** @author weiwenbin*/
@Aspect
@Component
@Slf4j
public class BatchCacheEvictAspect {// SpEL 解析器private final ExpressionParser parser = new SpelExpressionParser();// 参数名发现器(用于解析方法参数名)private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();/*** 处理批量清除缓存的操作** @param joinPoint  切入点* @param batchEvict 批量清除缓存注解* @return 方法执行结果* @throws Throwable 可能抛出的异常*/@Around("@annotation(batchEvict)")public Object handleBatchEvict(ProceedingJoinPoint joinPoint, BatchCacheEvict batchEvict) throws Throwable {// 条件判断if (StrUtil.isNotBlank(batchEvict.condition()) && !isConditionPassed(joinPoint, batchEvict.condition())) {log.info("handleBatchEvict isConditionPassed is false");return joinPoint.proceed();}// 空值检查if (ArrayUtil.isEmpty(batchEvict.cacheNames()) || StrUtil.isEmpty(batchEvict.key())) {log.info("handleBatchEvict cacheNames or key is empty");return joinPoint.proceed();}// 前置删除if (batchEvict.beforeInvocation()) {evictCaches(joinPoint, batchEvict);}try {Object result = joinPoint.proceed();// 后置删除if (!batchEvict.beforeInvocation()) {evictCaches(joinPoint, batchEvict);}return result;} catch (Exception ex) {log.error(ex.getMessage());throw ex;}}/*** 执行缓存的批量清除操作** @param joinPoint  切入点* @param batchEvict 批量清除缓存注解*/private void evictCaches(ProceedingJoinPoint joinPoint, BatchCacheEvict batchEvict) {// 创建 SpEL 上下文EvaluationContext context = createEvaluationContext(joinPoint);String cachedManagerName = batchEvict.cacheManager();String keyExpr = batchEvict.key();String[] cacheNames = batchEvict.cacheNames();//获取缓存对象CacheManager cacheManager = getCacheManager(cachedManagerName);//解析key的值Object key = parser.parseExpression(keyExpr).getValue(context);if (!(key instanceof Collection)) {log.error("keyExpr 类型错误必须是Collection的子类");throw new BaseException(SystemResultEnum.INTERNAL_SERVER_ERROR);}for (String cacheName : cacheNames) {CacheDataUtils.batchEvict(cacheManager, cacheName, (Collection<?>) key);}}/*** 创建 SpEL 上下文** @param joinPoint 切入点* @return SpEL 上下文对象*/private EvaluationContext createEvaluationContext(ProceedingJoinPoint joinPoint) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();// 构建 SpEL 上下文(支持方法参数名解析)return new MethodBasedEvaluationContext(joinPoint.getTarget(),signature.getMethod(),joinPoint.getArgs(),parameterNameDiscoverer);}/*** 获取缓存管理器对象** @param cacheManagerName 缓存管理器名称* @return 缓存管理器对象*/private CacheManager getCacheManager(String cacheManagerName) {return StrUtil.isBlank(cacheManagerName) ?SpringUtil.getBean(CacheManager.class) :SpringUtil.getBean(cacheManagerName, CacheManager.class);}/*** 判断条件是否满足** @param joinPoint 切入点* @param condition 条件表达式* @return 是否满足条件*/private boolean isConditionPassed(ProceedingJoinPoint joinPoint, String condition) {return Boolean.TRUE.equals(parser.parseExpression(condition).getValue(createEvaluationContext(joinPoint), Boolean.class));}
}

使用

@Override
@Transactional(rollbackFor = {Exception.class})
@BatchCacheEvict(cacheNames = "sysTenant", key = "#idList")
public Boolean delByIds(List<Long> idList) {// 手动删除// CacheDataUtils.batchEvict(SpringUtil.getBean("defaultCacheManager", CacheManager.class),"sysTenant", idList);// 业务代码
}

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

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

相关文章

区块链开发技术公司:引领数字经济的创新力量

在数字化浪潮席卷全球的今天&#xff0c;区块链技术作为新兴技术的代表&#xff0c;正以其独特的去中心化、不可篡改和透明性等特点&#xff0c;深刻改变着各行各业的发展格局。区块链开发技术公司&#xff0c;作为这一领域的先锋和推动者&#xff0c;正不断研发创新&#xff0…

多条件排序(C# and Lua)

C# 升序排序 OrderBy 按升序对序列的元素进行排序 ThenBy 按升序对序列中的元素执行后续排序 降序排序 OrderByDescending 按降序对序列的元素排序 ThenByDescending 按降序对序列中的元素执行后续排序 public class Fruit {public int id;public string name;publi…

React19源码系列之Hooks(useId)

useId的介绍 https://zh-hans.react.dev/reference/react/useId useId 是 React 18 引入的一个新 Hook&#xff0c;主要用于生成全局唯一的 ID。在开发中&#xff0c;我们经常需要为元素&#xff08;如表单元素、模态框等&#xff09;生成唯一 ID&#xff0c;以便在 JavaScri…

Redisson 分布式锁原理

加锁原理 # 如果锁不存在 if (redis.call(exists, KEYS[1]) 0) then# hash结构,锁名称为key,线程唯一标识为itemKey&#xff0c;itemValue为一个计数器。支持相同客户端线程可重入,每次加锁计数器1.redis.call(hincrby, KEYS[1], ARGV[2], 1);# 设置过期时间redis.call(pexpi…

单元化架构在字节跳动的落地实践

资料来源&#xff1a;火山引擎-开发者社区 什么是单元化 单元化的核心理念是将业务按照某种维度划分成一个个单元&#xff0c; 理想情况下每个单元内部都是完成所有业务操作的自包含集合&#xff0c;能独立处理业务流程&#xff0c;各个单元均有其中一部分数据&#xff0c;所有…

基于Python的垃圾短信分类

垃圾短信分类 1 垃圾短信分类问题介绍 1.1 垃圾短信 随着移动互联科技的高速发展&#xff0c;信息技术在不断改变着我们的生活&#xff0c;让我们的生活更方便&#xff0c;其中移动通信技术己经在我们生活起到至关重要的作用&#xff0c;与我们每个人人息息相关。短信作为移…

leetcode1971.寻找图中是否存在路径

初尝并查集&#xff0c;直接套用模板 class Solution { private:vector<int> father;void init() {for(int i0;i<father.size();i)father[i]i;}int find(int v) {return vfather[v]?v:father[v]find(father[v]);//路径压缩}bool isSame(int u,int v){ufind(u);vfind…

QAI AppBuilder 快速上手(7):目标检测应用实例

YOLOv8_det是YOLO 系列目标检测模型&#xff0c;专为高效、准确地检测图像中的物体而设计。该模型通过引入新的功能和改进点&#xff0c;如因式分解卷积&#xff08;factorized convolutions&#xff09;和批量归一化&#xff08;batch normalization&#xff09;&#xff0c;在…

景联文科技:以高质量数据标注推动人工智能领域创新与发展

在当今这个由数据驱动的时代&#xff0c;高质量的数据标注对于推动机器学习、自然语言处理&#xff08;NLP&#xff09;、计算机视觉等领域的发展具有不可替代的重要性。数据标注过程涉及对原始数据进行加工&#xff0c;通过标注特定对象的特征来生成能够被机器学习模型识别和使…

MySQL 索引下推

概念 索引下推&#xff08;Index Condition Pushdown&#xff0c;简称 ICP&#xff09; 是 MySQL 5.6 版本中提供的一项索引优化功能&#xff0c;它允许存储引擎在索引遍历过程中&#xff0c;执行部分 WHERE字句的判断条件&#xff0c;直接过滤掉不满足条件的记录&#xff0c;…

Unity | 游戏数据配置

目录 一、ScriptableObject 1.创建ScriptableObject 2.创建asset资源 3.asset资源的读取与保存 二、Excel转JSON 1.Excel格式 2.导表工具 (1)处理A格式Excel (2)处理B格式Excel 三、解析Json文件 1.读取test.json文件 四、相关插件 在游戏开发中,策划…

2025信创即时通讯排行:安全合规与生态适配双轮驱动

随着信息技术应用创新&#xff08;信创&#xff09;战略的深化&#xff0c;国产即时通讯工具在政企市场的渗透率显著提升。2025年作为“十四五”规划收官之年&#xff0c;信创产业迎来规模化应用关键节点。本文将从认证标准、市场表现、技术架构、行业适配四大维度&#xff0c;…

关于TVS管漏电流的问题?

问题描述&#xff1a; 在量产的带电池故事机生产中&#xff0c;工厂产线测试电流时&#xff0c;有1台机器电流比正常机器大10mA左右。 原因分析&#xff1a; 1、分析电路原理图&#xff0c;去除可能出现问题的电压或器件&#xff08;不影响系统&#xff09;&#xff0c;发现…

RAG 架构地基工程-Retrieval 模块的系统设计分享

目录 一、知识注入的关键前奏——RAG 系统中的检索综述 &#xff08;一&#xff09;模块定位&#xff1a;连接语言模型与知识世界的桥梁 &#xff08;二&#xff09;核心任务&#xff1a;四大关键问题的协调解法 &#xff08;三&#xff09;系统特征&#xff1a;性能、精度…

Java-servlet(七)详细讲解Servlet注解

Java-servlet&#xff08;七&#xff09;详细讲解Servlet注解 前言一、注解的基本概念二、Override 注解2.1 作用与优势2.2 示例代码 三、Target 注解3.1 定义与用途3.2 示例代码 四、WebServlet 注解4.1 作用4.2 示例代码 五、反射与注解5.1 反射的概念5.2 注解与反射的结合使…

机器学习——分类、回归、聚类、LASSO回归、Ridge回归(自用)

纠正自己的误区&#xff1a;机器学习是一个大范围&#xff0c;并不是一个小的方向&#xff0c;比如&#xff1a;线性回归预测、卷积神经网络和强化学都是机器学习算法在不同场景的应用。 机器学习最为关键的是要有数据&#xff0c;也就是数据集 名词解释&#xff1a;数据集中的…

本地AI大模型工具箱 Your local AI toolkit:LMStudio

LMStudio介绍 官网&#xff1a;LM Studio - Discover, download, and run local LLMs LMStudio 是一个面向机器学习和自然语言处理的&#xff0c;旨在使开发者更容易构建和部署AI语言模型的应用软件。 LMStudio的特点是&#xff1a; 完全本地离线运行AI大模型 可以从Huggi…

[OpenCV】相机标定之棋盘格角点检测与绘制

在OpenCV中&#xff0c;棋盘格角点检测与绘制是一个常见的任务&#xff0c;通常用于相机标定。 棋盘格自定义可参考: OpenCV: Create calibration pattern 目录 1. 棋盘格角点检测 findChessboardCorners()2. 棋盘格角点绘制 drawChessboardCorners()3. 代码示例C版本python版本…

redis的典型应用 --缓存

Redis最主要的用途&#xff0c;分为三个方面&#xff1a; 1.存储数据&#xff08;内存数据库&#xff09; 2.缓存&#xff08;最常用&#xff09; 3.消息队列 缓存 (cache) 是计算机中的⼀个经典的概念。核⼼思路就是把⼀些常⽤的数据放到触⼿可及(访问速度更快)的地⽅&…

本地基于Ollama部署的DeepSeek详细接口文档说明

前文&#xff0c;我们已经在本地基于Ollama部署好了DeepSeek大模型&#xff0c;并且已经告知过如何查看本地的API。为了避免网络安全问题&#xff0c;我们希望已经在本地调优的模型&#xff0c;能够嵌入到在本地的其他应用程序中&#xff0c;发挥本地DeepSeek的作用。因此需要知…