分类: java
2023-01-31 10:30:51
在早期参与涅槃氛围标签中台项目中,前台要求接口性能999要求50ms以下,通过设计caffeine、ehcache堆外缓存、jimdb三级缓存,利用内存、堆外、jimdb缓存不同的特性提升接口性能, 内存缓存采用caffeine缓存,利用w-tinylfu算法获得更高的内存命中率;同时利用堆外缓存降低内存缓存大小,减少gc频率,同时也减少了网络io带来的性能消耗;利用jimdb提升接口高可用、高并发;后期通过压测及性能调优999性能<20ms
当时由于项目工期紧张,三级缓存实现较为臃肿、业务侵入性强、可读性差,在近期场景化推荐项目中,为b端商家场景化资源投放推荐,考虑到b端流量相对c端流量较小,但需保证接口性能稳定。采用springcache实现caffeine、jimdb多级缓存方案,实现了低侵入性、可扩展、高可用的缓存方案,极大提升了系统稳定性,保证接口性能小于100ms;
/**
* 分级缓存
* 基于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;
}
}
/**
* 二级缓存实现
* 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%
下游应用由于4分钟后磁盘打满,性能到达瓶颈
接口ump 服务引擎系统 订购履约系统上游系统cpu利用率90%左右,下游系统调用量明显减少,cpu利用率仅10%左右
接口ump 服务引擎系统 订购履约系统: