Caffeine介绍
redis和caffeine的区别
相同点就不用说,广义上都是缓存的方式。咱们就说说不同。
- redis是将数据存储到内存里;caffeine是将数据存储在本地应用里
- caffeine和redis相比,没有了网络IO上的消耗
那么在高并发场景中,一般我们都是结合使用,形成一二级缓存。caffeine作为一级缓存,redis作为二级缓存。
使用流程大致如下:去一级缓存中查找数据(caffeine-本地应用内)如果没有的话,去二级缓存中查找数据(redis-内存)再没有,再去数据库中查找数据(数据库-磁盘)。
caffeine项目地址:ben-manes/caffeine: A high performance caching library for Java (github.com)
caffeine的应用
Caffeine 相当于一个缓存工厂,可以创建出多个缓存实例 Cache。这些缓存实例都继承了 Caffeine 的参数配置,Caffeine 是如何配置的,这些缓存实例就具有什么样的特性和功能。
Caffeine 是目前性能最好的本地缓存,因此,在考虑使用本地缓存时,直接选择 Caffeine 即可。
将caffeine作为一级缓存使用
1.配置相关
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>2.9.3</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.5.7</version> </dependency>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| import com.github.benmanes.caffeine.cache.Caffeine; import org.springframework.cache.caffeine.CaffeineCache; import org.springframework.cache.interceptor.SimpleKeyGenerator; import org.springframework.cache.support.AbstractCacheManager; import org.springframework.cache.support.SimpleCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit;
@Configuration public class CacheConfig {
@Bean public AbstractCacheManager cacheManager() { SimpleCacheManager cacheManager = new SimpleCacheManager(); List<CaffeineCache> caches = new ArrayList<>(); Arrays.asList(CacheInstance.values()).forEach(cacheInstance -> { CaffeineCache caffeineCache = new CaffeineCache(cacheInstance.name(), Caffeine.newBuilder() .recordStats() .expireAfterWrite(cacheInstance.getTtl(), TimeUnit.SECONDS) .build()); caches.add(caffeineCache); }); cacheManager.setCaches(caches); return cacheManager;
}
@Bean public SimpleKeyGenerator simpleKeyGenerator() { return new SimpleKeyGenerator(); }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| import cn.hutool.core.util.ReflectUtil; import com.github.benmanes.caffeine.cache.Caffeine; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.Cache; import org.springframework.cache.caffeine.CaffeineCache; import org.springframework.cache.support.AbstractCacheManager; import org.springframework.stereotype.Component;
import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit;
@Component public class CacheCreator { @Autowired private AbstractCacheManager cacheManager;
public Cache getCache(CacheInstance cacheInstance, List<String> values) {
String cacheNameSuffix = String.join("&", values); String cacheName = cacheInstance.name() + "&" + cacheNameSuffix; Cache cache = cacheManager.getCache(cacheName); if (null == cache) { synchronized (cacheName.intern()) { cache = new CaffeineCache(cacheName, Caffeine.newBuilder() .recordStats() .expireAfterWrite(cacheInstance.getTtl(), TimeUnit.SECONDS) .build()); Map<String, Cache> caches = (ConcurrentHashMap<String, Cache>) ReflectUtil.getFieldValue(cacheManager, "cacheMap"); caches.put(cacheName, cache); } } return cache; }
}
|
- 我们还需要一个对业务数据进行区分的缓存枚举类,这些缓存配置将在缓存管理器初始化时加载:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| import cn.hutool.core.util.RandomUtil;
public enum CacheInstance { STUDENT_INFO, CLASS_INFO(600, 1024), ;
private int ttl = RandomUtil.randomInt(300, 360); private int maxSize = 1024;
CacheInstance() { }
CacheInstance(int ttl) { this.ttl = ttl; }
CacheInstance(int ttl, int maxSize) { this.ttl = ttl; this.maxSize = maxSize; }
public int getMaxSize() { return maxSize; }
public void setMaxSize(int maxSize) { this.maxSize = maxSize; }
public int getTtl() { return ttl; }
public void setTtl(int ttl) { this.ttl = ttl; } }
|
2.注解实现和工具类
配置完了缓存,我们需要在业务上使用,我们可以通过切面注解的方式来实现缓存,这样可以大大减少业务代码和缓存代码的耦合性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Cacheable {
CacheInstance cacheName();
String[] cacheNameSuffix() default {};
String [] keys() default {};
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface CacheEvict {
CacheInstance[] cacheName() ;
String[] cacheNameSuffix() default {};
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
| import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.CodeSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.Cache; import org.springframework.stereotype.Component;
import java.util.Arrays; import java.util.HashMap; import java.util.Map;
@Slf4j @Aspect @Component public class CacheAspectHandler { @Autowired private CacheCreator cacheCreator;
private Boolean enableCache = Boolean.TRUE;
@Around("@annotation(cacheable)") public Object cacheResponse(ProceedingJoinPoint pjp, Cacheable cacheable) throws Throwable { Object result;
if (enableCache) { String[] argNames = ((CodeSignature) pjp.getSignature()).getParameterNames(); Object[] args = pjp.getArgs(); Map<String, Object> argMap = new HashMap<>(); for (int i = 0; i < argNames.length; i++) { argMap.put(argNames[i], args[i]); }
String key; if(cacheable.keys().length != 0){ key = CacheUtil.generateCacheKeyByMapAndSpecifiedKeys(argMap, cacheable.keys()); }else { key = CacheUtil.generateCacheKeyByMap(argMap); }
Cache cache = cacheCreator.getCache(cacheable.cacheName(), Arrays.asList(cacheable.cacheNameSuffix())); result = cache.get(key, Object.class); if (result != null) { log.debug(String.format("命中缓存,实例:%s, 键:%s ", cache.getName(), key));
} else { result = pjp.proceed(); cache.put(key, result); log.debug(String.format("缓存成功,实例:%s, 键:%s ", cache.getName(), key)); } } else { result = pjp.proceed(); }
return result;
}
@Around("@annotation(cacheEvict)") public Object evictCacheResponse(ProceedingJoinPoint pjp, CacheEvict cacheEvict) throws Throwable {
CacheInstance[] cacheInstances = cacheEvict.cacheName(); Arrays.stream(cacheInstances).forEach(cacheInstance -> { Cache cache = cacheCreator.getCache(cacheInstance, Arrays.asList(cacheEvict.cacheNameSuffix())); if (null != cache) { cache.clear(); log.debug(String.format("清除缓存成功,实例:%s ", cache.getName())); } }); return pjp.proceed();
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| import cn.hutool.core.util.ArrayUtil; import cn.hutool.json.JSONUtil; import org.springframework.cache.Cache; import org.springframework.cache.interceptor.SimpleKey;
import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors;
public class CacheUtil {
public static <T> T getValue(Cache cache, Object key, Class<T> returnClass) { if (cache == null || key == null) { return null; } return cache.get(key, returnClass); }
public static String generateCacheKey(Object... keys) { new SimpleKey(keys); List<Object> objects = Arrays.asList(keys); return objects.stream().map(o -> o == null ? "null" : String.valueOf(o)).collect(Collectors.joining("&"));
}
public static void cacheValue(Cache cache, Object value, Object... keys) { if (null == cache) { throw new IllegalArgumentException("内部错误:缓存器为空"); } cache.put(generateCacheKey(keys), value); }
public static String generateCacheKeyByMap(Map<String, Object> argMap) { return JSONUtil.toJsonStr(argMap);
}
public static String generateCacheKeyByMapAndSpecifiedKeys(Map<String, Object> argMap, String... keys) { if (ArrayUtil.isEmpty(keys)) { throw new IllegalArgumentException("请指定缓存的key"); } Map<String, Object> keysMap = new HashMap<>(); Arrays.stream(keys).forEach(key -> keysMap.put(key, argMap.get(key))); return generateCacheKeyByMap(keysMap); }
}
|
3.缓存使用
- 添加缓存-情况1:将所有参数作为缓存key,无需配置keys
1 2 3 4 5
| @Cacheable(cacheName = CacheInstance.STUDENT_INFO, //枚举类存放的缓存名 cacheNameSuffix = "selectStudentList") public Map selectStudentList(Student conditon, Clazz cls){ }
|
- 添加缓存-情况2:部分参数作为缓存key,配置keys
1 2 3 4 5 6
| @Cacheable(cacheName = CacheInstance.STUDENT_INFO, //枚举类存放的缓存名 cacheNameSuffix = "selectStudentList", //缓存前缀, 对这部分缓存的唯一标识, 这里可以使用方法名, 方便查找和删除 keys= {"conditon"}) public Map selectStudentList(Student conditon, Clazz cls){ }
|
缓存成功后,会打印缓存成功的日志,重复调用接口会打印命中缓存的日志,这时可以看到实例以及key
1 2
| 14:51:38.016 [http-nio-8097-exec-1] DEBUG c.k.c.c.CacheAspectHandler - [cacheResponse,68] - 缓存成功,实例:STUDENT_INFO&selectStudentList, 键:{"conditon":{"name":"张"}} 14:51:44.243 [http-nio-8097-exec-2] DEBUG c.k.c.c.CacheAspectHandler - [cacheResponse,63] - 命中缓存,实例:STUDENT_INFO&selectStudentList, 键:{"conditon":{"name":"张"}}
|
1 2 3 4 5
| @CacheEvict(cacheName = CacheInstance.STUDENT_INFO, //枚举类存放的缓存名 cacheNameSuffix = "selectStudentList") public void delCache(){ }
|
清除缓存成功,则会打印清除成功日志
1
| 14:54:43.911 [http-nio-8097-exec-5] DEBUG c.k.c.c.CacheAspectHandler - [lambda$evictCacheResponse$0,94] - 清除缓存成功,实例:STUDENT_INFO&selectStudentList
|
总结
以上只展示了Caffeine缓存的基础应用,基本的缓存需求可以满足,当然也可以在切面中加入redis作为二级缓存使用。
Caffeine缓存具有很好的性能和很强的扩展性,更多扩展用法可以参考Caffeine缓存的官方文档(Population zh CN · ben-manes/caffeine Wiki (github.com)),若代码有错误或不足的地方可以评论回复。
参考文章:
caffeine本地缓存的使用和详解_小曲同学呀的博客-CSDN博客_caffeine本地缓存