前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >本地缓存Caffeine的坑,我是如何一步步爬出来的

本地缓存Caffeine的坑,我是如何一步步爬出来的

作者头像
程序员小义
发布2024-07-04 16:09:13
1380
发布2024-07-04 16:09:13
举报
文章被收录于专栏:小义思小义思

在当前的软件开发中,缓存技术被广泛应用于提高数据访问速度和降低数据库压力,如本地缓存和redis分布式缓存。小义本想实现一个caffeine+redis的多级缓存组件,但没想到又双叒叕踩坑了,今天主要聊聊本地缓存和caffeine。

本地缓存作为单机服务最先触及的缓存层,选择合适的方案对于提升应用性能和响应速度至关重要,其主要的实现方式有以下几种:

  • Ehcache

Ehcache是一个纯Java的缓存库,易于使用,提供了丰富的缓存策略,如LRU(最近最少使用)、FIFO(先进先出)等。

  • Guava Cache

Guava是Google开发的Java核心库,其中的缓存模块简单易用,支持自定义缓存驱逐策略,如大小限制、时间限制等。

  • Caffeine

Caffeine相比Guava,它提供了更好的内存管理机制和更快的读写性能,可以看做是guava的增强版。

之所以选择caffeine,是因为Caffeine是为Java 8及以上版本设计的,它利用了Java 8的并发特性,提供了高性能的缓存实现,而且Caffeine采用了一种结合LRU、LFU优点的算法:W-TinyLFU,在性能上有明显的优越性,同时过期时间设置,很符合自己要实现的组件的功能要求。本以为可以高效实现技术方案了,一写代码哪哪都是bug。

  1. 过期时间设置不灵活

caffeine默认只支持统一设置缓存时间,如下面代码:

代码语言:javascript
复制
    public static final Cache<String, Object> caffeine = Caffeine.newBuilder()
            .expireAfter(new CaffeineExpiry())
            .initialCapacity(100)
                .maximumSize(1000)
                .build();
  1. 无法获取缓存项的过期时间

不能针对不同的key设置个性化的过期时间,那自然就无法查看缓存项的剩余过期时间。如果想实现像redis那样有ttl命令可以查看key剩余的过期时间,需要自行扩展。

针对上述问题,还好caffeine支持自定义缓存对象,同时利用expireAfter方法可以实现对缓存项的过期时间设置,代码实现方式大致如下:

缓存对象类,设置过期时间和创建时间属性。

代码语言:javascript
复制
@Data
public class CacheObject<T> {
    T data;
    long expire;
    long createDate;//创建时间
    public CacheObject(T data, long second, long createDate) {
        this.data = data;
        this.expire = TimeUnit.SECONDS.toNanos(second);
        this.createDate = createDate;
    }
}

自定义caffeine本地缓存,可实现不同key的过期时间设置。

代码语言:javascript
复制
@Component
public class MyCaffeineCache {

    private Cache<String, CacheObject> CAFFEINE;

    public static final long NOT_EXPIRED = Long.MAX_VALUE;

    @PostConstruct
    public void init() {
        CAFFEINE = Caffeine.newBuilder()
                .expireAfter(new Expiry<String, CacheObject<?>>() {
                    @Override
                    public long expireAfterCreate(@NonNull String s, @NonNull CacheObject<?> cacheObject, long l) {
                        return cacheObject.getExpire();
                    }

                    @Override
                    public long expireAfterUpdate(@NonNull String s, @NonNull CacheObject<?> cacheObject, long l, @NonNegative long l1) {
                        return l1;
                    }

                    @Override
                    public long expireAfterRead(@NonNull String s, @NonNull CacheObject<?> cacheObject, long l, @NonNegative long l1) {
                        return l1;
                    }
                })
                .initialCapacity(100)
                .maximumSize(1024)
                .build();
    }

    public <T> void set(String key, T value, long expire) {
        CacheObject<T> tCacheObject = new CacheObject<>(value, expire, System.currentTimeMillis());
        CAFFEINE.put(key, tCacheObject);
    }

    public <T> T get(String key) {
        CacheObject<?> ifPresent = CAFFEINE.getIfPresent(key);
        if (Objects.isNull(ifPresent)) {
            return null;
        }
        return (T) ifPresent.getData();
    }

    public void delete(String key) {
        CAFFEINE.invalidate(key);
    }

    /**
     * 获取key的剩余过期时间,单位秒
     * @param key
     * @return
     */
    public Long getTtl(String key) {
        CacheObject o = (CacheObject)CAFFEINE.getIfPresent(key);
        if (Objects.isNull(o)) {
            return null;
        }
        Long flat = ((o.getCreateDate() + o.getExpire()/1000000) - System.currentTimeMillis())/1000;
        return flat;
    }

}

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-06-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员小义 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档