SpringBoot与缓存原理分析

SpringBoot与缓存

一、Spring缓存抽象

1. 概述

Spring从3.1开始定义了Cache和CacheManager接口来统一不同的缓存技术,并支持使用JCache(JSR-107)注解简化开发

  • CacheManager管理多个Cache组件,对缓存真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一的名字
  • Cache接口为缓存的组件规范定义,包含缓存的各种操作集合
  • Cache接口下Spring提供了xxxCache的实现,例如RedisCache,EhCacheCache等
    在这里插入图片描述

2. 缓存注解以及概念

名称简介
Cache缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache等
CacheManager缓存管理器,管理各种缓存(Cache)组件
@Cacheable针对方法配置,根据方法的请求参数对其结果进行缓存
@CacheEvict清空缓存
@CachePut保证方法被调用,又希望结果被缓存
@EnableCaching开启基于注解的缓存
keyGenerator缓存数据时key生成策略
serialize缓存数据时value序列化策略

二、环境搭建

  1. 导入相关依赖

    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-cache</artifactId>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-web</artifactId>
    		</dependency>
    
  2. 编写Controller、Service、Dao层等相关业务接口

三、入门案例

  1. 开启缓存,使用@EnableCaching注解

    @SpringBootApplication
    @EnableCaching
    public class SpringBootCacheTestApplication {
        public static void main(String[] args) {
            SpringApplication.run(SpringBootCacheTestApplication.class,args);
        }
    }
    
  2. 以@Cacheable为例

    @Slf4j
    @Service
    public class UserService {
    
        @Autowired
        public Employee employee;
    
        @Cacheable(value = {"user"}, key = "#id")
        public User getUser(Integer id) {
            log.info("------------------查询数据库 id=" + id + "------------------");
            return employee.getEmployees().get(id);
        }
    }
    
  3. 运行结果

    在这里插入图片描述

    • 第一次调用该接口时系统会查询数据库
    • 之后调用该接口时系统会直接从缓存中取值
  4. @Cacheable注解的相关属性

    • cacheName/value:指定缓存组件的名字,可以是数组
    • key:缓存数据时使用的key(默认使用方法参数的值),可以使用SpEL表达式
    • keyGenerator:key的生成器:可以自定义。key和keyGenerator二选一
    • cacheManager:指定缓存管理器
    • condition:指定符合条件的情况下才缓存
    • unless:当unless指定的条件为true则不缓存(例如unless="#result==null",即当返回的结果为空时则不缓存)
    • sync:指定缓存是否使用异步模式

四、原理

1. 底层源码

  1. 缓存自动配置类

    @Import({CacheAutoConfiguration.CacheConfigurationImportSelector.class, CacheAutoConfiguration.CacheManagerEntityManagerFactoryDependsOnPostProcessor.class})
    public class CacheAutoConfiguration {
        ......
    }
    
  2. 缓存的配置类

    在这里插入图片描述

    • 默认SimpleCacheConfiguration该配置类生效

      在这里插入图片描述

    • 该类向容器中注入了一个CacheManager,该缓存管理器为ConcurrentMapCacheManager

    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnMissingBean({CacheManager.class})
    @Conditional({CacheCondition.class})
    class SimpleCacheConfiguration {
        SimpleCacheConfiguration() {
        }
    
        @Bean
        ConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers) {
            ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
            List<String> cacheNames = cacheProperties.getCacheNames();
            if (!cacheNames.isEmpty()) {
                cacheManager.setCacheNames(cacheNames);
            }
    
            return (ConcurrentMapCacheManager)cacheManagerCustomizers.customize(cacheManager);
        }
    }
    
  3. ConcurrentMapCacheManager类

    • 可以创建ConcurrentHashMap类型的缓存组件
    public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
        //用来保存缓存组件
        private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap(16);
        private boolean dynamic = true;
        private boolean allowNullValues = true;
        private boolean storeByValue = false;
        @Nullable
        private SerializationDelegate serialization;
    
        public ConcurrentMapCacheManager() {
        }
    
        public ConcurrentMapCacheManager(String... cacheNames) {
            this.setCacheNames(Arrays.asList(cacheNames));
        }
        
        ......
    
        //根据缓存名获取缓存
        @Nullable
        public Cache getCache(String name) {
            Cache cache = (Cache)this.cacheMap.get(name);
            if (cache == null && this.dynamic) {
                synchronized(this.cacheMap) {
                    cache = (Cache)this.cacheMap.get(name);
                    if (cache == null) {
                        cache = this.createConcurrentMapCache(name);
                        this.cacheMap.put(name, cache);
                    }
                }
            }
    
            return cache;
        }
    
        ......
        
        //创建ConcurrentHashMap类型的缓存
        protected Cache createConcurrentMapCache(String name) {
            SerializationDelegate actualSerialization = this.isStoreByValue() ? this.serialization : null;
            return new ConcurrentMapCache(name, new ConcurrentHashMap(256), this.isAllowNullValues(), actualSerialization);
        }
    }
    
    • ConcurrentMapCache类,该类中有lookup、get、put等方法,会向ConcurrentMap中取出或存入数据

    在这里插入图片描述

2. 运行流程

  1. 以Cacheable为例,方法运行之前先去查Cache缓存组件(CacheManager先获取相应缓存),按照CacheNames指定的缓存名获取。第一次如果没有缓存组件则会自动创建
  2. 去Cache中查找缓存的内容,使用key来查找。key默认使用keyGenerator生成
  3. 当缓存中没有值时则会调用目标方法
  4. 将目标方法的返回值存入缓存中

五、测试

  1. 自定义key的生成策略

    • 编写配置类
    • 实现KeyGenerator接口并返回
    //标记配置类注解
    @Configuration
    public class MyCacheConfig {
        @Bean("myKeyGenerator")//KeyGenerator的id
        public KeyGenerator profileKeyGenerator() {
            return new KeyGenerator() {
                @Override
                public Object generate(Object target, Method method, Object... params) {
                    //自定义的key生成策略
                    return method.getName()+"["+Arrays.asList(params).toString()+"]";
                }
            };
        }
    }
    
    • 在@Cacheable等注解中指定keyGenerator的id即可,例如@Cacheable(value={“缓存名”},keyGenerator=“myKeyGenerator”)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AM6njQDR-1630051212594)(C:\Users\T450\AppData\Roaming\Typora\typora-user-images\image-20210827114528344.png)]

  2. @Cacheable的condition和unless属性

    //当员工id大于1时才缓存
    @Cacheable(value = {"user"}, keyGenerator = "myKeyGenerator",condition = "#id>1")
    //当员工id为2时不缓存
    @Cacheable(value = {"user"}, keyGenerator = "myKeyGenerator",condition = "#id>1",unless = "#id==2")
    
  3. @CachePut注解:既调用方法又更新缓存

    @CachePut(value = "user", key = "#id")
    public User updateEmployee(Integer id, Integer age, String name) {
        log.info("------------------Service层更新用户,id=" + id + "------------------");
        return userDao.updateEmployee(id, age, name);
    }
    
    • 该方法运行结束后,将返回值放入缓存中
  4. @CacheEvict注解:清除缓存中的数据

    • 按指定key删除数据
    @CacheEvict(value = "user", key = "#id")
    public void delUser(Integer id) {
        log.info("------------------Service层删除用户,id=" + id + "------------------");
    }
    
    • 删除缓存中的所有数据
    @CacheEvict(value = "user", allEntries = true,beforeInvocation=true)
    public void delUser(Integer id) {
        log.info("------------------Service层删除用户,id=" + id + "------------------");
    }
    //beforeInvocation=true:在方法执行之前清空缓存,默认为false(当方法执行过程中出现异常时缓存也会被清除)
    //allEntries=true:清空缓存中的所有内容
    
  5. @Caching注解:定义复杂的缓存规则、

    //源码
    public @interface Caching {
        Cacheable[] cacheable() default {};
    
        CachePut[] put() default {};
    
        CacheEvict[] evict() default {};
    }
    //示例
    @Caching(
            evict = {
                    @CacheEvict(value = "user",allEntries = true,condition = "#id==某个值"),
                    @CacheEvict(value = "user",key = "#id")
            }
    )
    //当id==某个值时会清空缓存,当id!=某个值时只会清空key==id的缓存
    
  6. @CacheConfig注解

    //源码
    public @interface CacheConfig {
        String[] cacheNames() default {};
    
        String keyGenerator() default "";
    
        String cacheManager() default "";
    
        String cacheResolver() default "";
    }
    
    • 该注解配置在类上,抽取缓存的公共配置。可以配置缓存名、缓存管理器等属性,类中的缓存方法则默认使用类上配置的缓存属性值

六、整合Redis

1. 环境配置

  1. 引入相应的包

    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-data-redis</artifactId>
    			<version>2.5.3</version>
    		</dependency>
    
  2. 配置文件中配置Redis

2. 自定义缓存管理器

  1. 底层原理

    • 当引入Redis时,底层Redis的相关配置就会被匹配到

    在这里插入图片描述

    • 引入Redis的starter后,容器中保存的是RedisCacheManager
    • RedisCacheManager帮我们创建RedisCache来作为缓存组件
    • 默认保存数据时会对数据进行序列化
  2. 自定义CacheManager

    • 系统中的缓存管理器创建条件为当系统中没有缓存管理器时才创建,当我们自定义之后,相当于系统中已有缓存管理器,则系统就会使用我们创建的缓存管理器
    @Configuration
    public class MyRedisConfig extends CachingConfigurerSupport {
    
        @Resource
        private RedisConnectionFactory redisFactory;
        
        /**
         * 配置CacheManager
         *
         * @return
         */
        @Bean
        public CacheManager cacheManager() {
            RedisSerializer<String> redisSerializer = new StringRedisSerializer();
            Jackson2JsonRedisSerializer<User> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(User.class);
            // 配置序列化(解决乱码的问题)
            RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                    .entryTtl(Duration.ofSeconds(300))   //设置缓存失效时间
                    .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                    .disableCachingNullValues();
    
            return RedisCacheManager.builder(redisFactory)
                    .cacheDefaults(config)
                    .build();
        }
    
        /**
         * RedisTemplate配置
         */
        @Bean
        public RedisTemplate<String, User> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
            // 设置序列化
            Jackson2JsonRedisSerializer<User> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(
                    User.class);
            // 配置redisTemplate
            RedisTemplate<String, User> redisTemplate = new RedisTemplate<>();
            redisTemplate.setConnectionFactory(redisConnectionFactory);
            redisTemplate.setDefaultSerializer(jackson2JsonRedisSerializer);
            return redisTemplate;
        }
    }
    
  3. 测试

    @Slf4j
    @Service
    @CacheConfig(cacheNames = "user")
    public class UserService {
    
        @Autowired
        public Employee employee;
    
        @Autowired
        public UserDao userDao;
    
        @Cacheable(value = "user", key = "#id")
        public User getUser(Integer id) {
            log.info("------------------Service层查询用户,id=" + id + "------------------");
            return employee.getEmployees().get(id);
        }
    }
    
  4. 结果

    在这里插入图片描述

七、参考资料

<think>好的,我现在需要回答用户关于Spring Boot全局缓存的介绍。首先,我得理解用户的需求。他们可能想了解如何在Spring Boot应用中配置和管理全局缓存,确保多个组件或服务共享同一个缓存实例,以提高性能。 接下来,我需要回忆Spring Boot中缓存的基本概念。Spring Boot通过Spring Cache抽象来支持缓存,通常整合了像Ehcache、Redis、Caffeine这样的实现。全局缓存可能指的是应用范围内统一的缓存配置,所有需要缓存的地方都使用同一个缓存管理器,避免重复配置。 然后,我得考虑全局缓存的配置步骤。首先,添加相关依赖,比如Spring Boot Starter Cache和具体的缓存库,比如Redis。然后,在配置文件中设置缓存参数,比如Redis的主机和端口。接着,通过@EnableCaching注解启用缓存支持,并定义一个全局的CacheManager bean,这样整个应用都使用同一个配置。 还要注意缓存的一致性和过期策略。不同的缓存实现有不同的配置方式,比如在Redis中可以通过@Cacheable注解设置过期时间,或者在配置文件中定义。需要提醒用户根据实际需求选择合适的策略,避免数据不一致或内存溢出。 另外,可能要考虑分布式环境下的情况。如果应用是分布式的,使用Redis这样的分布式缓存是必要的,而本地缓存如Caffeine可能不适合。需要解释不同缓存实现的适用场景。 用户可能会遇到的问题包括缓存配置不生效、缓存穿透或雪崩。因此,在回答中应该提到常见问题及解决方法,比如使用@Cacheable的sync属性防止缓存击穿,或者配置合适的过期时间和最大容量。 最后,我需要组织这些信息,确保结构清晰,分步骤说明如何配置全局缓存,并给出示例代码和配置,帮助用户理解和实现。同时,提醒注意事项和最佳实践,确保回答全面有用。</think>在Spring Boot中,全局缓存是通过统一的缓存管理器(`CacheManager`)实现的,它允许在整个应用中共享缓存配置和实例。以下是关键实现步骤和原理: ### 一、核心配置步骤 1. **添加依赖** ```xml <!-- Spring Boot缓存支持 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!-- 以Redis为例 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> ``` 2. **配置文件(application.yml)** ```yaml spring: cache: type: redis # 指定缓存类型 redis: time-to-live: 300000 # 默认缓存过期时间(ms) key-prefix: "APP_CACHE:" # 键前缀 use-key-prefix: true redis: host: localhost port: 6379 ``` 3. **启用缓存** ```java @SpringBootApplication @EnableCaching // 启用缓存注解 public class Application { @Bean public CacheManager cacheManager(RedisConnectionFactory factory) { RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .serializeValuesWith(RedisSerializationContext.SerializationPair .fromSerializer(new GenericJackson2JsonRedisSerializer())); return RedisCacheManager.builder(factory) .cacheDefaults(config) .transactionAware() // 支持事务 .build(); } } ``` ### 二、缓存使用注解 1. **@Cacheable** ```java @Cacheable(value = "users", key = "#id", unless = "#result == null") public User getUserById(Long id) { return userRepository.findById(id).orElse(null); } ``` 2. **@CacheEvict** ```java @CacheEvict(value = "users", key = "#user.id") public void updateUser(User user) { userRepository.save(user); } ``` 3. **@CachePut** ```java @CachePut(value = "users", key = "#user.id") public User createUser(User user) { return userRepository.save(user); } ``` ### 三、高级配置技巧 1. **多缓存策略共存** ```java @Bean public CacheManager cacheManager(RedisConnectionFactory factory) { Map<String, RedisCacheConfiguration> configMap = new HashMap<>(); configMap.put("users", RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(2))); configMap.put("products", RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(30))); return RedisCacheManager.builder(factory) .withInitialCacheConfigurations(configMap) .build(); } ``` 2. **二级缓存组合** ```java @Bean public CacheManager layeredCacheManager() { CaffeineCacheManager localCache = new CaffeineCacheManager(); localCache.setCaffeine(Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES)); RedisCacheManager remoteCache = RedisCacheManager.create(redisConnectionFactory); return new CompositeCacheManager(localCache, remoteCache); } ``` ### 四、性能优化建议 1. **序列化优化** 使用`GenericJackson2JsonRedisSerializer`替代JDK序列化,提升效率和可读性 2. **缓存穿透防护** ```java @Cacheable(value = "users", key = "#id", unless = "#result == null", sync = true) // 添加sync同步锁 ``` 3. **批量操作支持** ```java @Cacheable(value = "users", keyGenerator = "batchKeyGenerator") public List<User> batchGetUsers(List<Long> ids) { // 批量查询逻辑 } ``` ### 五、监控诊断 1. 通过`actuator`端点监控: ```yaml management: endpoints: web: exposure: include: health,info,caches ``` 2. 使用Redis命令分析: ```bash redis-cli monitor | grep "APP_CACHE" ``` 通过上述配置,Spring Boot的全局缓存可以实现: - 统一的生命周期管理 - 跨服务的缓存共享 - 细粒度的缓存控制 - 多种缓存策略组合 实际应用中需根据数据访问模式(读多写少/读写均衡)选择合适的缓存策略,并配合适当的过期时间和淘汰策略(LRU/LFU)来平衡性能数据一致性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ysw!不将就

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值