最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

热推荐:基于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 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 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
服务引擎系统
订购履约系统:

关键词: 二级缓存 一级缓存 引擎系统