玩转Spring Cache --- 扩展缓存注解支持失效时间TTL【享学Spring】(下)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 玩转Spring Cache --- 扩展缓存注解支持失效时间TTL【享学Spring】(下)

方式二:自定义cacheNames方式


虽然我觉得方案一已经能够满足我们需求了,但是广大小伙伴还是觉得使用起来不太自由,毕竟大多数Cache都希望是通过在注解指定CacheNames让其自动生成就行(其实提前追备好有助于提升初次访问的性能)。但是为了便用性摆第一位的话,那就应广大小伙伴的要求,写出本例供以参考:


其实最终我们期望的使用方式如下:


@Cacheable(cacheNames = "demoCache#3600", key = "#id + 0"),


通过#分隔,后面部分表示此Cache的TTL(单位:秒)


为了实现这个效果,其实并不难,只需要对RedisCacheManager稍稍的改造一下即可达到目的:

public class MyRedisCacheManager extends RedisCacheManager {
    public MyRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
        super(cacheWriter, defaultCacheConfiguration);
    }
    @Override
    protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
        String[] array = StringUtils.delimitedListToStringArray(name, "#");
        name = array[0];
        if (array.length > 1) { // 解析TTL
            long ttl = Long.parseLong(array[1]);
            cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(ttl)); // 注意单位我此处用的是秒,而非毫秒
        }
        return super.createRedisCache(name, cacheConfig);
    }
}


使用我自定义的MyRedisCacheManager配置CacheConfig如下:


@EnableCaching // 使用了CacheManager,别忘了开启它  否则无效
@Configuration
public class CacheConfig extends CachingConfigurerSupport {
    @Bean
    public CacheManager cacheManager() {
        RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofDays(1))
                .computePrefixWith(cacheName -> "caching:" + cacheName);
        MyRedisCacheManager redisCacheManager = new MyRedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory()), defaultCacheConfig);
        return redisCacheManager;
    }
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
        configuration.setHostName("10.102.132.150");
        configuration.setPort(6379);
        configuration.setDatabase(0);
        LettuceConnectionFactory factory = new LettuceConnectionFactory(configuration);
        return factory;
    }
    @Bean
    public RedisTemplate<String, String> stringRedisTemplate() {
        RedisTemplate<String, String> redisTemplate = new StringRedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        return redisTemplate;
    }
}


使用示例如下:


@Service
public class CacheDemoServiceImpl implements CacheDemoService {
    @Cacheable(cacheNames = {
            "demoCache#3600", "demoCar#600", "demoFsx"
    }, key = "#id")
    @Override
    public Object getFromDB(Integer id) {
        System.out.println("模拟去db查询~~~" + id);
        return "hello cache...";
    }
}


打印结果:


模拟去db查询~~~1
----------验证缓存是否生效----------
org.springframework.data.redis.cache.RedisCache@53f4c1e6
hello cache...


缓存生效。Redis Server里查到缓存结果如图(TTL都分别生效了):



image.png


说明:demoFsx没有指定TTL,所以走了默认值ttl=1天


小细节


  1. 同样的,禁用前缀并不影响它的TTL的生效与否
  2. 若在CacheManager里已经配置了Cache对应的TTL配置,那就以CacheManager里配置的为准
  3. 若多个方法里配置了同一个CacheName,TTL以第一个执行的生成Cache的方法配置的为准

总之一个原则:TTL是和Cache绑定的,且是在Cache在首次被初始化的时候就被指定好了


关于此方案,其实还可以扩展一下,比如可以扩展成可配置的如下:

@Cacheable(cacheNames = "demoCache#${cache.timeout:1800}",key = "#id")


其实这么想的小伙伴,我觉得根本原因是不太能理解cacheName和Redis的key的关系导致的(本文以Redis为例~)


我此处不直接解答这个问题,但我对此额外抛出3个问题,相信答案就不攻自破了:


  1. 为何同一个Cache下管理的key你需要不同的TTL???(这种设计本身就不合理吧)
  2. 在不禁用前缀的情况下,cacheName默认都会反映到key上。因此即使你有这种特殊需求,你也可以通过定义特殊的CacheName来实现
  3. 若你真想控制到key这种细粒度,我只能说:实现成本太高了且会打破一定的封装性,后续扩展受限


综合来说,不管从场景上还是技术上,我都是极力不推荐这种行为的


总结


本文主要介绍了让缓存注解支持TTL失效时间,提供的两种方式都可以用在生产环境中。合理的使用、控制失效时间,能让你的应用更加的高效,缓存利用得更合理。


另外关于Spring缓存其实还有一个重要知识点:缓存即将过期时主动刷新缓存:

因为缓存失效后,就会有一些请求会打到DB上,这段时间如果是高并发的话DB压力就很大(sync=true可以有一定的缓解作用),DB就很危险,容易造成雪崩。

因此我们是期望在缓存即将过期的某一时间点,后台主动去更新缓存以确保前端请求的缓存命中率。关于这部分的实现,只有在高并发系统下才有需求,有兴趣和有需要的小伙伴可往这方面考虑一把~

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
5月前
|
存储 缓存 NoSQL
【Azure Redis 缓存】关于Azure Cache for Redis 服务在传输和存储键值对(Key/Value)的加密问题
【Azure Redis 缓存】关于Azure Cache for Redis 服务在传输和存储键值对(Key/Value)的加密问题
|
2月前
|
SQL 缓存 Java
MyBatis如何关闭一级缓存(分注解和xml两种方式)
MyBatis如何关闭一级缓存(分注解和xml两种方式)
89 5
|
2月前
|
存储 缓存 Java
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
368 2
|
3月前
|
缓存 NoSQL Java
Springboot自定义注解+aop实现redis自动清除缓存功能
通过上述步骤,我们不仅实现了一个高度灵活的缓存管理机制,还保证了代码的整洁与可维护性。自定义注解与AOP的结合,让缓存清除逻辑与业务逻辑分离,便于未来的扩展和修改。这种设计模式非常适合需要频繁更新缓存的应用场景,大大提高了开发效率和系统的响应速度。
91 2
|
5月前
|
缓存 NoSQL Redis
【Azure Redis 缓存】Azure Cache for Redis 服务的导出RDB文件无法在自建的Redis服务中导入
【Azure Redis 缓存】Azure Cache for Redis 服务的导出RDB文件无法在自建的Redis服务中导入
|
5月前
|
缓存 开发框架 NoSQL
【Azure Redis 缓存】VM 里的 Redis 能直接迁移到 Azure Cache for Redis ? 需要改动代码吗?
【Azure Redis 缓存】VM 里的 Redis 能直接迁移到 Azure Cache for Redis ? 需要改动代码吗?
|
5月前
|
缓存 NoSQL Unix
【Azure Redis 缓存】Azure Cache for Redis 中如何快速查看慢指令情况(Slowlogs)
【Azure Redis 缓存】Azure Cache for Redis 中如何快速查看慢指令情况(Slowlogs)
|
5月前
|
缓存 NoSQL Redis
【Azure Redis 缓存】Azure Cache for Redis 是否记录具体读/写(Get/Set)或删除(Del)了哪些key呢?
【Azure Redis 缓存】Azure Cache for Redis 是否记录具体读/写(Get/Set)或删除(Del)了哪些key呢?
|
5月前
|
存储 缓存 NoSQL
【Azure Redis 缓存】Azure Cache for Redis 专用终结点, 虚拟网络, 公网访问链路
【Azure Redis 缓存】Azure Cache for Redis 专用终结点, 虚拟网络, 公网访问链路
|
19天前
|
存储 缓存 NoSQL
解决Redis缓存数据类型丢失问题
解决Redis缓存数据类型丢失问题
164 85