Chinaunix首页 | 论坛 | 博客
  • 博客访问: 84213
  • 博文数量: 165
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1655
  • 用 户 组: 普通用户
  • 注册时间: 2022-09-26 14:37
文章分类

全部博文(165)

文章存档

2024年(2)

2023年(95)

2022年(68)

我的朋友

分类: Java

2023-01-31 10:30:51

作者: 京东零售 王震

背景

在早期参与涅槃氛围标签中台项目中,前台要求接口性能999要求50ms以下,通过设计Caffeine、ehcache堆外缓存、jimDB三级缓存,利用内存、堆外、jimDB缓存不同的特性提升接口性能, 内存缓存采用Caffeine缓存,利用W-TinyLFU算法获得更高的内存命中率;同时利用堆外缓存降低内存缓存大小,减少GC频率,同时也减少了网络IO带来的性能消耗;利用JimDB提升接口高可用、高并发;后期通过压测及性能调优999性能<20ms
image.png

当时由于项目工期紧张,三级缓存实现较为臃肿、业务侵入性强、可读性差,在近期场景化推荐项目中,为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 @EnableCaching public 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%

压测数据

未开启缓存

image.png

开启多级缓存

image.png

监控数据

未开启缓存

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

接口UMP

image.png

服务引擎系统

image.png

订购履约系统

image.png

开启缓存

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

接口UMP

image.png

服务引擎系统

image.png

订购履约系统:

image.png


阅读(301) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~