Spring Boot集成高效缓存(Caffeine)

614 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第16天,点击查看活动详情

背景

项目中我们经常会用缓存来提升系统的性能,但是一旦提到缓存,大多数人都会想到的是Redis和MemCache。其实缓存可以分为本地缓存和分布式缓存。

图片.png

通过上图我们知道分布式缓存和本地缓存的优缺点,然而本地缓存也经过了几代的优化,由第一代的Encache、经过一段时间到第二代本地缓存Guava Cache,在到最新的缓存Caffeine,每一代的缓存都经过相关优化和性能的提升。

简介

Caffeine是基于java8实现的新一代缓存工具,缓存性能接近理论最优。不同的是Caffeine采用了一种结合LRU、LFU优点的算法:W-TinyLFU,在性能上有明显的提升。

原理

常见的缓存策略

FIFO(First In First Out) 先进先出

它是优先淘汰掉最先缓存的数据、是最简单的淘汰算法。缺点是如果先缓存的数据使用频率比较高的话,那么该数据就不停地进进出出,因此它的缓存命中率比较低。这也就是caffenie所描述的近乎最佳的命中率

LRU(Least Recently Used):最近最久未使用。

它是优先淘汰掉最久未访问到的数据。缺点是不能很好地应对偶然的突发流量。有一批数据在59秒都没有被访问,然后到1分钟的时候过期了,但在1分01秒来了大批量的访问,导致穿透缓存,打垮数据库。

LFU(Least Frequently Used):最少使用

最近最少频率使用。它是优先淘汰掉最不经常使用的数据,需要维护一个表示使用频率的字段。 就凭这个字段对于缓存来说就是很不友好的,因为我们的内存资源是真的很可贵的。

而Caffeine使用的是W-TinyLFU 算法。

W-TinyLFU 算法简介

传统的LFU受时间周期的影响比较大。所以各种LFU的变种出现了,基于时间周期进行衰减,或者在最近某个时间段内的频率。同样的LFU也会使用额外空间记录每一个数据访问的频率,即使数据没有在缓存中也需要记录,所以需要维护的额外空间很大。

W-TinyLFU结合了LRU和LFU,以及其他的算法的一些特点,优化了其他算法的相关缺陷。

系统集成

引入jar包

 <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.4.RELEASE</version>
</parent>
        
  <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <dependency>
       <groupId>com.github.ben-manes.caffeine</groupId>
       <artifactId>caffeine</artifactId>
  </dependency>

说明:SpringBoot默认已经集成了Caffeine,只需要添加相关依赖即可,无需指定版本号。

缓存相关配置

@Configuration
public class CacheConfig
{
    @Bean
    public Cache<String, Object> creatCaffeineCache() {

        return  Caffeine.newBuilder()
                //设置最后一次写入或访问后经过固定时间过期
                .expireAfterWrite(60, TimeUnit.SECONDS)
                //初始化缓存空间大小
                .initialCapacity(100)
                //最大缓存数
                .maximumSize(1000)
                .build();
    }
}

说明:

  • initialCapacity:初始的缓存空间大小
  • maximumSize:缓存的最大条数
  • expireAfterWrite:最后一次写入后经过固定时间过期
  • refreshAfterWrite:写入后经过固定的时间刷新缓存
  • expireAfterAccess :最后一次写入或访问后经过固定时间过期

expireAfterWrite 和 expireAfterAccess同时设置时,以expireAfterWrite为标准。

缓存的顶级接口

public interface CacheService<T>
{
    void put(String key ,T value);
    
    T get(String key);
}

具体实现

@Service("CaffeineCacheService")
public class CaffeineCacheServiceImpl<T> implements CacheService<T>
{
    @Resource
    private Cache<String, T> caffeineCache;
    
    /**
     * 获取缓存对象
     * @param key
     * @return
     */
    public T get(String key)
    {
       return caffeineCache.asMap().get(key);
    }
    
    public void put(String key ,T value)
    {
       caffeineCache.asMap().put(key, value);
    }
}

说明:设计顶级接口,其他系统只需要调用Api接口方法,无需关注具体的实现。

具体业务调用缓存接口


@Service
@Transactional(rollbackFor=Exception.class)
public class UserServiceImpl implements UserService
{
    private Logger logger=LoggerFactory.getLogger(UserServiceImpl.class);
    
    @Qualifier("CaffeineCacheService")
    @Autowired
    private CacheService<User> cacheService;
    
    @Autowired
    private UserMapper userMapper;
    
    @Override
    public User getUserById(String userId)
    {
        User cacheUser =cacheService.get(userId);
        if(cacheUser!=null)
        {
            logger.info("get date in cache");
            return cacheUser;
        }
        
        User dbUser=userMapper.getUserById(userId);
        if(dbUser!=null)
        {
            logger.info("put data into cache");
            cacheService.put(userId, dbUser);
        }
        return dbUser;
    }
}

说明:先通过缓存查询,如果缓存中不存在,则在查询数据库,再将数据库的信息添加到缓存中。

测试示例代码

@RestController
@RequestMapping("/user")
public class UserController
{
    private Logger logger=LoggerFactory.getLogger(UserController.class);
    
    @Autowired
    private UserService userService;
    
    @GetMapping("getUserById")
    public User getUserById(@RequestParam String userId)
    {
       logger.info("getUserByName paramter userId:"+userId);
       return userService.getUserById(userId); 
    }
}

测试结果

第一次访问接口日志的信息如下:

图片.png

说明:第一次查询缓存中未存储数据,所以先查询数据库,在将数据添加到缓存中。

第二次访问接口日志的信息如下:

图片.png

说明:缓存中已经存在数据,查询结果直接从缓存中获取。

总结

本文讲解了Spring Boot集成高效缓存(Caffeine)的使用,如有疑问请随时反馈。