写点什么

基于 Spring Cache 实现 Caffeine、jimDB 多级缓存实战

  • 2023-01-31
    北京
  • 本文字数:5168 字

    阅读完需:约 17 分钟

基于Spring Cache实现Caffeine、jimDB多级缓存实战
作者: 京东零售 王震

背景

在早期参与涅槃氛围标签中台项目中,前台要求接口性能 999 要求 50ms 以下,通过设计 Caffeine、ehcache 堆外缓存、jimDB 三级缓存,利用内存、堆外、jimDB 缓存不同的特性提升接口性能,


内存缓存采用 Caffeine 缓存,利用 W-TinyLFU 算法获得更高的内存命中率;同时利用堆外缓存降低内存缓存大小,减少 GC 频率,同时也减少了网络 IO 带来的性能消耗;利用 JimDB 提升接口高可用、高并发;后期通过压测及性能调优 999 性能<20ms



当时由于项目工期紧张,三级缓存实现较为臃肿、业务侵入性强、可读性差,在近期场景化推荐项目中,为 B 端商家场景化资源投放推荐,考虑到 B 端流量相对 C 端流量较小,但需保证接口性能稳定。采用 SpringCache 实现 caffeine、jimDB 多级缓存方案,实现了低侵入性、可扩展、高可用的缓存方案,极大提升了系统稳定性,保证接口性能小于 100ms;

Spring Cache 实现多级缓存

多级缓存实例 MultilevelCache

/** * 分级缓存 * 基于Caffeine + jimDB 实现二级缓存 * @author wangzhen520 * @date 2022/12/9 */public class MultilevelCache extends AbstractValueAdaptingCache {
/** * 缓存名称 */ private String name;
/** * 是否开启一级缓存 */ private boolean enableFirstCache = true;
/** * 一级缓存 */ private Cache firstCache;
/** * 二级缓存 */ private Cache secondCache;
@Override protected Object lookup(Object key) { Object value; recordCount(getUmpKey(this.getName(), UMP_GET_CACHE, UMP_ALL)); if(enableFirstCache){ //查询一级缓存 value = getWrapperValue(getForFirstCache(key)); log.info("{}#lookup getForFirstCache key={} value={}", this.getClass().getSimpleName(), key, value); if(value != null){ return value; } } value = getWrapperValue(getForSecondCache(key)); log.info("{}#lookup getForSecondCache key={} value={}", this.getClass().getSimpleName(), key, value); //二级缓存不为空,则更新一级缓存 boolean putFirstCache = (Objects.nonNull(value) || isAllowNullValues()) && enableFirstCache; if(putFirstCache){ recordCount(getUmpKey(this.getName(), UMP_FIRST_CACHE, UMP_NO_HIT)); log.info("{}#lookup put firstCache key={} value={}", this.getClass().getSimpleName(), key, value); firstCache.put(key, value); } return value; }
@Override public void put(Object key, Object value) { if(enableFirstCache){ checkFirstCache(); firstCache.put(key, value); } secondCache.put(key, value); }
/** * 查询一级缓存 * @param key * @return */ private ValueWrapper getForFirstCache(Object key){ checkFirstCache(); ValueWrapper valueWrapper = firstCache.get(key); if(valueWrapper == null || Objects.isNull(valueWrapper.get())){ recordCount(getUmpKey(this.getName(), UMP_FIRST_CACHE, UMP_NO_HIT)); } return valueWrapper; }
/** * 查询二级缓存 * @param key * @return */ private ValueWrapper getForSecondCache(Object key){ ValueWrapper valueWrapper = secondCache.get(key); if(valueWrapper == null || Objects.isNull(valueWrapper.get())){ recordCount(getUmpKey(this.getName(), UMP_SECOND_CACHE, UMP_NO_HIT)); } return valueWrapper; }
private Object getWrapperValue(ValueWrapper valueWrapper){ return Optional.ofNullable(valueWrapper).map(ValueWrapper::get).orElse(null); }
}
复制代码

多级缓存管理器抽象

/** * 多级缓存实现抽象类 * 一级缓存 * @see AbstractMultilevelCacheManager#getFirstCache(String) * 二级缓存 * @see AbstractMultilevelCacheManager#getSecondCache(String) * @author wangzhen520 * @date 2022/12/9 */public abstract class AbstractMultilevelCacheManager implements CacheManager {
private final ConcurrentMap<String, MultilevelCache> cacheMap = new ConcurrentHashMap<>(16);
/** * 是否动态生成 * @see MultilevelCache */ protected boolean dynamic = true; /** * 默认开启一级缓存 */ protected boolean enableFirstCache = true; /** * 是否允许空值 */ protected boolean allowNullValues = true;
/** * ump监控前缀 不设置不开启监控 */ private String umpKeyPrefix;

protected MultilevelCache createMultilevelCache(String name) { Assert.hasLength(name, "createMultilevelCache name is not null"); MultilevelCache multilevelCache = new MultilevelCache(allowNullValues); multilevelCache.setName(name); multilevelCache.setUmpKeyPrefix(this.umpKeyPrefix); multilevelCache.setEnableFirstCache(this.enableFirstCache); multilevelCache.setFirstCache(getFirstCache(name)); multilevelCache.setSecondCache(getSecondCache(name)); return multilevelCache; }

@Override public Cache getCache(String name) { MultilevelCache cache = this.cacheMap.get(name); if (cache == null && dynamic) { synchronized (this.cacheMap) { cache = this.cacheMap.get(name); if (cache == null) { cache = createMultilevelCache(name); this.cacheMap.put(name, cache); } return cache; } } return cache; }
@Override public Collection<String> getCacheNames() { return Collections.unmodifiableSet(this.cacheMap.keySet()); }
/** * 一级缓存 * @param name * @return */ protected abstract Cache getFirstCache(String name);
/** * 二级缓存 * @param name * @return */ protected abstract Cache getSecondCache(String name);
public boolean isDynamic() { return dynamic; }
public void setDynamic(boolean dynamic) { this.dynamic = dynamic; }
public boolean isEnableFirstCache() { return enableFirstCache; }
public void setEnableFirstCache(boolean enableFirstCache) { this.enableFirstCache = enableFirstCache; }
public String getUmpKeyPrefix() { return umpKeyPrefix; }
public void setUmpKeyPrefix(String umpKeyPrefix) { this.umpKeyPrefix = umpKeyPrefix; }}
复制代码

基于 jimDB Caffiene 缓存实现多级缓存管理器


/** * 二级缓存实现 * caffeine + jimDB 二级缓存 * @author wangzhen520 * @date 2022/12/9 */public class CaffeineJimMultilevelCacheManager extends AbstractMultilevelCacheManager {
private CaffeineCacheManager caffeineCacheManager;
private JimCacheManager jimCacheManager;
public CaffeineJimMultilevelCacheManager(CaffeineCacheManager caffeineCacheManager, JimCacheManager jimCacheManager) { this.caffeineCacheManager = caffeineCacheManager; this.jimCacheManager = jimCacheManager; caffeineCacheManager.setAllowNullValues(this.allowNullValues); }
/** * 一级缓存实现 * 基于caffeine实现 * @see org.springframework.cache.caffeine.CaffeineCache * @param name * @return */ @Override protected Cache getFirstCache(String name) { if(!isEnableFirstCache()){ return null; } return caffeineCacheManager.getCache(name); }
/** * 二级缓存基于jimDB实现 * @see com.jd.jim.cli.springcache.JimStringCache * @param name * @return */ @Override protected Cache getSecondCache(String name) { return jimCacheManager.getCache(name); }}
复制代码

缓存配置

/** * @author wangzhen520 * @date 2022/12/9 */@Configuration@EnableCachingpublic class CacheConfiguration {
/** * 基于caffeine + JimDB 多级缓存Manager * @param firstCacheManager * @param secondCacheManager * @return */ @Primary @Bean(name = "caffeineJimCacheManager") public CacheManager multilevelCacheManager(@Param("firstCacheManager") CaffeineCacheManager firstCacheManager, @Param("secondCacheManager") JimCacheManager secondCacheManager){ CaffeineJimMultilevelCacheManager cacheManager = new CaffeineJimMultilevelCacheManager(firstCacheManager, secondCacheManager); cacheManager.setUmpKeyPrefix(String.format("%s.%s", UmpConstants.Key.PREFIX, UmpConstants.SYSTEM_NAME)); cacheManager.setEnableFirstCache(true); cacheManager.setDynamic(true); return cacheManager; }
/** * 一级缓存Manager * @return */ @Bean(name = "firstCacheManager") public CaffeineCacheManager firstCacheManager(){ CaffeineCacheManager firstCacheManager = new CaffeineCacheManager(); firstCacheManager.setCaffeine(Caffeine.newBuilder() .initialCapacity(firstCacheInitialCapacity) .maximumSize(firstCacheMaximumSize) .expireAfterWrite(Duration.ofSeconds(firstCacheDurationSeconds))); firstCacheManager.setAllowNullValues(true); return firstCacheManager; }
/** * 初始化二级缓存Manager * @param jimClientLF * @return */ @Bean(name = "secondCacheManager") public JimCacheManager secondCacheManager(@Param("jimClientLF") Cluster jimClientLF){ JimDbCache jimDbCache = new JimDbCache<>(); jimDbCache.setJimClient(jimClientLF); jimDbCache.setKeyPrefix(MultilevelCacheConstants.SERVICE_RULE_MATCH_CACHE); jimDbCache.setEntryTimeout(secondCacheExpireSeconds); jimDbCache.setValueSerializer(new JsonStringSerializer(ServiceRuleMatchResult.class)); JimCacheManager secondCacheManager = new JimCacheManager(); secondCacheManager.setCaches(Arrays.asList(jimDbCache)); return secondCacheManager; }
复制代码

接口性能压测

压测环境

廊坊4C8G * 3
复制代码

压测结果

1、50 并发时,未开启缓存,压测 5min,TP99: 67ms,TP999: 223ms,TPS:2072.39 笔/秒,此时服务引擎 cpu 利用率 40%左右;订购履约 cpu 利用率 70%左右,磁盘使用率 4min 后被打满;


2、50 并发时,开启二级缓存,压测 10min,TP99: 33ms,TP999: 38ms,TPS:28521.18.笔/秒,此时服务引擎 cpu 利用率 90%左右,订购履约 cpu 利用率 10%左右,磁盘使用率 3%左右;

缓存命中分析

总调用次数:1840486/min 一级缓存命中:1822820 /min 二级缓存命中:14454/min


一级缓存命中率:99.04%


二级缓存命中率:81.81%

压测数据

未开启缓存

开启多级缓存

监控数据

未开启缓存

下游应用由于 4 分钟后磁盘打满,性能到达瓶颈

接口 UMP
服务引擎系统
订购履约系统

开启缓存

上游系统 CPU 利用率 90%左右,下游系统调用量明显减少,CPU 利用率仅 10%左右

接口 UMP
服务引擎系统
订购履约系统:


发布于: 刚刚阅读数: 2
用户头像

拥抱技术,与开发者携手创造未来! 2018-11-20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
基于Spring Cache实现Caffeine、jimDB多级缓存实战_spring_京东科技开发者_InfoQ写作社区