缓存神器-JetCache

序言

今天和大家聊聊阿里的一款缓存神器 JetCache。

一、缓存在开发实践中的问题

1.1 缓存方案的可扩展性问题

谈及缓存,其实有许多方案可供选择。例如:Guava Cache、Caffine、Encache、Redis 等。

这些缓存技术都能满足我们的需求,但现在有一个问题是:技术是在不断发展的,若今后有更好的缓存技术出现,如果我们需要替换之前的缓存方案,该怎么办呢?

1.2 缓存的使用问题

之前,我们已经了解到缓存方案有许多,而这些缓存在项目中的使用方式又不尽相同。这无疑将增加我们的学习成本以及开发成本(即:开发人员需要了解并掌握所使用缓存技术的使用方式以及原理)。

现如今我们的项目大多都是分布式环境,在分布式环境中有三大经典的分布式缓存问题:

  1. 缓存穿透
  2. 缓存击穿
  3. 缓存雪崩

针对这些问题,具体的缓存技术似乎并不能解决。那么,这就意味着需要开发人员每次在使用缓存时,都需要手动解决这些问题。针对这些固定的问题,是否可以采用成熟、统一的方案解决呢?

二、JSR-107 规范

在开发过程中,我们经常会用到缓存来提高系统的性能和效率。然而,不同的缓存实现可能会有各自的接口和行为,这就导致了在切换缓存实现或者在不同的缓存实现之间共享数据时会遇到困难。

JSR-107,也被称为 JCache,是由 Java 定义的一项规范。JSR-107 规范定义了一套标准的 Java 缓存 API,使得开发者可以用一致的方式来使用和切换不同的缓存实现。这样,无论你使用哪种缓存技术,只要它们遵循 JSR-107 规范,你就可以用同样的方式来操作缓存。

三、JetCache 方案

JetCache 是阿里推出的一个基于 Java 的缓存系统封装,提供统一的 API 和注解来简化缓存的使用。 JetCache提供了比 SpringCache 更加强大的注解,可以原生的支持 TTL、两级缓存、分布式自动刷新,还提供了 Cache接口用于手工缓存操作。 当前有四个实现,RedisCache、TairCache(此部分未在 github 开源)、CaffeineCache(in memory) 和一个简易的 LinkedHashMapCache(in memory),要添加新的实现也是非常简单的。

支持项 SpringCache JetCache
JSR-107 支持 支持
本地缓存 支持 支持
远程缓存 支持 支持
注解缓存 支持 支持
对象缓存 —— 支持
分布式锁 —— 支持
缓存穿透 简单 灵活方案支持
缓存击穿 简单 灵活方案支持
缓存雪崩 —— 灵活方案支持
多级缓存 简单 灵活方案支持
扩展性 支持 支持
监控 —— 支持
高级API(异步,原始特性) —— 支持

上表是由阿里技术提供的与 SpringCache 功能性相关的对比。可以看出 JetCache 功能更加强大,并在许多方面都有着显著的优势。

四、JetCache 快速入门

我们基于 jetcache-starter-redis 包,以注解缓存为例快速带大家体验 JetCache 在处理缓存时有多方便。

4.1 引入 maven 依赖包

<dependency>
  <groupId>com.alicp.jetcache</groupId>
  <artifactId>jetcache-starter-redis</artifactId>
  <version>2.7.5</version>
</dependency>

4.2 编写配置

application.yml 配置文件中添加如下配置:

jetcache:
  statIntervalMinutes: 15
  areaInCacheName: false
  local:
    default:
      type: linkedhashmap
      keyConvertor: fastjson
  remote:
    default:
      type: redis
      keyConvertor: fastjson2
      broadcastChannel: projectA
      valueEncoder: java
      valueDecoder: java
      poolConfig:
        minIdle: 5
        maxIdle: 20
        maxTotal: 50
      host: 127.0.0.1
      port: 6379

4.3 使用 JetCache

  1. 启用注解缓存

    package com.company.mypackage;
    
    import com.alicp.jetcache.anno.config.EnableCreateCacheAnnotation;
    import com.alicp.jetcache.anno.config.EnableMethodCache;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    // 激活 @Cached 注解
    @EnableMethodCache(basePackages = "com.company.mypackage")
    // 激活 @CreateCache 注解
    @EnableCreateCacheAnnotation
    public class MySpringBootApp {
        public static void main(String[] args) {
            SpringApplication.run(MySpringBootApp.class);
        }
    }
    
    
  2. 使用注解缓存

    @Service
    public class UserServiceImpl implements UserService {
    
        @Resource
        private UserMapper userMapper;
    
        @Override
        @Cached(name = "cache:user:", key = "#id", expire = 3600, cacheType = CacheType.LOCAL)
        public User getUserById(Integer id) {
            return userMapper.selectUserById(id);
        }
    }
    
  3. 使用效果

    image.png

五、JetCache 功能

5.1 解决缓存穿透

针对缓存穿透,我们通常有两种解决方案:

  1. 缓存空对象
  2. 布隆过滤器

JetCache 采用了第一种方式(即:缓存空对象)来解决缓存穿透问题。我们只需在注解中加上一个属性即可:

@Cached(cacheNullValue = true)

5.2 解决缓存击穿

针对缓存击穿问题,JetCache 提供了两种方式解决:

  1. @CachePenetrationProtect:该注解提供的是 JVM 内存锁级别的保护,旨在将并发重建的请求限制在可控范围。这种方式的核心思想是缓存重建任务可控。
  2. @CacheRefresh:该注解是基于分布式锁的缓存重建,如果对系统的要求较高可采用此种方式。

5.3 解决缓存雪崩

针对缓存雪崩问题,一般在缓存层通过两种方式解决:

  1. 建立多级缓存,多级缓存设置不同过期时间从而形成重叠的数据滑动窗口
  2. 异步维护一块固定缓存,防止缓存失效(兜底方案)

JetCache 针对上面的两种解决方式也给出了对应的方案:

  1. JetCache 可以实现多级缓存
  2. 使用 @CacheRefresh + CacheLoader 维护固定缓存

5.4 实现多级缓存

多级缓存时 JetCache 一大特色,JetCache 多级缓存有以下特点:

  1. JetCache 多级缓存本身可以拥有不同的过期时间,从而构建出多级滑动窗口
  2. JetCache 多级缓存可以简单、直接的避免缓存击穿、缓存雪崩问题
  3. JetCache 默认的注解缓存只支持两级缓存(Local + Remote),但是可以自定义扩展至 N 级
  4. JetCache 缓存可以添加自定义实现的缓存
Cache multiLevelCache = MultiLevelCacheBuilder
	.createMultiLevelCacheBuilder()
	.addCache(caffeineCache, memCache, redisCache)
	.expireAfterWrite(100, TimeUnit.SECONDS)
	.buildCache();

5.5 缓存失效/更新

有时缓存也是需要管理的。例如:当更新数据库时,使缓存失效或者同步更新缓存。JetCache 可以通过以下两个注解实现缓存失效/更新:

  1. @CacheInvalidate 缓存失效
  2. @CacheUpdate 缓存更新

5.6 编程式创建缓存

之前,我们一直采用注解声明式的创建缓存。但是在某些情况下,我们需要使用编程式创建缓存的方式。JetCache 中可以使用 CacheManager 手动创建缓存。例如:

@Autowired
private CacheManager cacheManager;
private Cache<String, UserDO> userCache;

@PostConstruct
public void init() {
    QuickConfig qc = QuickConfig.newBuilder("userCache")
    .expire(Duration.ofSeconds(100))
    .cacheType(CacheType.BOTH) // two level cache
    .syncLocal(true) // invalidate local cache in all jvm process after update
    .build();
    userCache = cacheManager.getOrCreateCache(qc);
}

5.7 缓存监控统计

当配置参数 jetcache.statIntervalMinutes 大于 0 时,使用 @CreateCache@Cached 生成的缓存将自带监控。JetCache 会按指定的时间定期通过 logger 输出统计信息。默认输出信息类似如下:
image.png

六、FAQ

本文只简单的介绍了 JetCache 的相关概念,若有特殊开发需求的小伙伴可以自行前去官方文档探索哦。

推荐阅读

  1. 为什么 MySQL 单表数据量最好别超过 2000w
  2. ConcurrentHashMap 源码分析(一)
  3. IoC 思想简单而深邃
  4. ThreadLocal
  5. JDK 动态代理
上一篇:FlyFlow:全新开源版问世,支持SpringBoot3+Flowable7


下一篇:【OceanBase诊断调优】—— 如何查看 Root Service 切换完成的时间点