娣卞叆婧愮爜鍒嗘瀽锛岀紦瀛樹箣鐜?Caffeine 涓轰綍杩欎箞鐚?
Posted 绋嬪簭鍛樻偿鐡﹀尃
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了娣卞叆婧愮爜鍒嗘瀽锛岀紦瀛樹箣鐜?Caffeine 涓轰綍杩欎箞鐚?相关的知识,希望对你有一定的参考价值。
鑰佸娴欐睙涓滄捣杈癸紝闈犳捣鍚冩捣锛岀洰鍓嶇粡钀ヤ竴涓皬鍝佺墝锛岃鏅€氫汉鍚冨埌鏈€鏂伴矞鐨勬捣椴溿€傛湁鍏磋叮鍙互鐐瑰嚮浜嗚В锛氥€?span class="mq-9">銆嬸煇燄煇燄煇?/span>
Caffeine[1]鏄竴涓珮鎬ц兘锛岄珮鍛戒腑鐜囷紝浣庡唴瀛樺崰鐢紝near optimal 鐨勬湰鍦扮紦瀛橈紝绠€鍗曟潵璇村畠鏄?Guava Cache 鐨勪紭鍖栧姞寮虹増锛屾湁浜涙枃绔犳妸 Caffeine 绉颁负鈥滄柊涓€浠g殑缂撳瓨鈥濄€佲€滅幇浠g紦瀛樹箣鐜嬧€濄€?/span>
鏈枃灏嗛噸鐐硅瑙?Caffeine 鐨勯珮鎬ц兘璁捐锛屼互鍙婂搴旈儴鍒嗙殑婧愮爜鍒嗘瀽銆?/span>
涓?Guava Cache 姣旇緝
濡傛灉浣犲 Guava Cache 杩樹笉鐞嗚В鐨勮瘽锛屽彲浠ョ偣鍑?span class="mq-20">杩欓噷[2]鏉ョ湅涓€涓嬫垜涔嬪墠鍐欒繃鍏充簬 Guava Cache 鐨勬枃绔犮€?/span>
澶у閮界煡閬擄紝Spring5 鍗冲皢鏀惧純鎺?Guava Cache 浣滀负缂撳瓨鏈哄埗锛岃€屾敼鐢?Caffeine 浣滀负鏂扮殑鏈湴 Cache 鐨勭粍浠讹紝杩欏浜?Caffeine 鏉ヨ鏄竴涓緢澶х殑鑲畾銆備负浠€涔?Spring 浼氳繖鏍峰仛鍛紵鍏跺疄鍦?Caffeine 鐨?span class="mq-24">Benchmarks[3]閲岀粰鍑轰簡濂介潛浠旂殑鏁版嵁锛屽璇诲拰鍐欑殑鍦烘櫙锛岃繕鏈夎窡鍏朵粬鍑犱釜缂撳瓨宸ュ叿杩涜浜嗘瘮杈冿紝Caffeine 鐨勬€ц兘閮借〃鐜板緢绐佸嚭銆?/span>
浣跨敤 Caffeine
Caffeine 涓轰簡鏂逛究澶у浣跨敤浠ュ強浠?Guava Cache 鍒囨崲杩囨潵锛堝緢鏈夐拡瀵规€у晩锝烇級锛屽€熼壌浜?Guava Cache 澶ч儴鍒嗙殑姒傚康锛堣濡傛牳蹇冩蹇?/span>Cache
銆?/span>LoadingCache
銆?/span>CacheLoader
銆?/span>CacheBuilder
绛夌瓑锛夛紝瀵逛簬 Caffeine 鐨勭悊瑙e彧瑕佹妸瀹冨綋浣?Guava Cache 灏卞彲浠ヤ簡銆?/span>
浣跨敤涓婏紝澶у鍙鎶?Caffeine 鐨勫寘寮曡繘鏉ワ紝鐒跺悗鎹竴涓?cache 鐨勫疄鐜扮被锛屽熀鏈簲璇ュ氨娌¢棶棰樹簡銆傝繖瀵逛笌宸茬粡浣跨敤杩?Guava Cache 鐨勫悓瀛︽潵璇存病鏈変换浣曢毦搴︼紝鐢氳嚦杩樻湁涓€鐐圭啛鎮夌殑鍛抽亾锛屽鏋滀綘涔嬪墠娌℃湁浣跨敤杩?Guava Cache锛屽彲浠ユ煡鐪?Caffeine 鐨?span class="mq-41">瀹樻柟 API 璇存槑鏂囨。[4]锛屽叾涓?/span>Population
锛?/span>Eviction
锛?/span>Removal
锛?/span>Refresh
锛?/span>Statistics
锛?/span>Cleanup
锛?/span>Policy
绛夌瓑杩欎簺鐗规€ч兘鏄窡 Guava Cache 鍩烘湰涓€鏍风殑銆?/span>
涓嬮潰缁欏嚭涓€涓緥瀛愯鏄庢€庢牱鍒涘缓涓€涓?Cache锛?/span>
private static LoadingCache<String, String> cache = Caffeine.newBuilder()
//鏈€澶т釜鏁伴檺鍒?/span>
.maximumSize(256L)
//鍒濆鍖栧閲?/span>
.initialCapacity(1)
//璁块棶鍚庤繃鏈燂紙鍖呮嫭璇诲拰鍐欙級
.expireAfterAccess(2, TimeUnit.DAYS)
//鍐欏悗杩囨湡
.expireAfterWrite(2, TimeUnit.HOURS)
//鍐欏悗鑷姩寮傛鍒锋柊
.refreshAfterWrite(1, TimeUnit.HOURS)
//璁板綍涓嬬紦瀛樼殑涓€浜涚粺璁℃暟鎹紝渚嬪鍛戒腑鐜囩瓑
.recordStats()
//cache瀵圭紦瀛樺啓鐨勯€氱煡鍥炶皟
.writer(new CacheWriter<Object, Object>() {
@Override
public void write(@NonNull Object key, @NonNull Object value) {
log.info("key={}, CacheWriter write", key);
}
@Override
public void delete(@NonNull Object key, @Nullable Object value, @NonNull RemovalCause cause) {
log.info("key={}, cause={}, CacheWriter delete", key, cause);
}
})
//浣跨敤CacheLoader鍒涘缓涓€涓狶oadingCache
.build(new CacheLoader<String, String>() {
//鍚屾鍔犺浇鏁版嵁
@Nullable
@Override
public String load(@NonNull String key) throws Exception {
return "value_" + key;
}
//寮傛鍔犺浇鏁版嵁
@Nullable
@Override
public String reload(@NonNull String key, @NonNull String oldValue) throws Exception {
return "value_" + key;
}
});
鏇村浠?Guava Cache 杩佺Щ杩囨潵鐨勪娇鐢ㄨ鏄庯紝璇风湅杩欓噷[5]
Caffeine 鐨勯珮鎬ц兘璁捐
鍒ゆ柇涓€涓紦瀛樼殑濂藉潖鏈€鏍稿績鐨勬寚鏍囧氨鏄懡涓巼锛屽奖鍝嶇紦瀛樺懡涓巼鏈夊緢澶氬洜绱狅紝鍖呮嫭涓氬姟鍦烘櫙銆佹窐姹扮瓥鐣ャ€佹竻鐞嗙瓥鐣ャ€佺紦瀛樺閲忕瓑绛夈€傚鏋滀綔涓烘湰鍦扮紦瀛橈紝 瀹冪殑鎬ц兘鐨勬儏鍐碉紝璧勬簮鐨勫崰鐢ㄤ篃閮芥槸涓€涓緢閲嶈鐨勬寚鏍囥€備笅闈?/span>
鎴戜滑鏉ョ湅鐪?Caffeine 鍦ㄨ繖鍑犱釜鏂归潰鏄€庝箞鐫€鎵嬬殑锛屽浣曞仛浼樺寲鐨勩€?/span>
锛堟敞锛氭湰鏂囦笉浼氬垎鏋?Caffeine 鍏ㄩ儴婧愮爜锛屽彧浼氬鏍稿績璁捐鐨勫疄鐜拌繘琛屽垎鏋愶紝浣嗘垜寤鸿璇昏€呮妸 Caffeine 鐨勬簮鐮侀兘娑夌寧涓€涓嬶紝鏈変釜 overview 鎵嶈兘鏇村ソ鐞嗚В鏈枃銆傚鏋滀綘鐪嬭繃 Guava Cache 鐨勬簮鐮佷篃琛岋紝浠g爜鐨勬暟鎹粨鏋勫拰澶勭悊閫昏緫寰堢被浼肩殑銆?/span>
婧愮爜鍩轰簬锛歝affeine-2.8.0.jar锛?/span>
W-TinyLFU 鏁翠綋璁捐
涓婇潰璇村埌娣樻卑绛栫暐鏄奖鍝嶇紦瀛樺懡涓巼鐨勫洜绱犱箣涓€锛屼竴鑸瘮杈冪畝鍗曠殑缂撳瓨灏变細鐩存帴鐢ㄥ埌 LFU(Least Frequently Used锛屽嵆鏈€涓嶇粡甯镐娇鐢? 鎴栬€?span class="mq-131">LRU(Least Recently Used锛屽嵆鏈€杩戞渶灏戜娇鐢? 锛岃€?Caffeine 灏辨槸浣跨敤浜?nbsp;W-TinyLFU 绠楁硶銆?/span>
W-TinyLFU 鐪嬪悕瀛楀氨鑳藉ぇ姒傜寽鍑烘潵锛屽畠鏄?LFU 鐨勫彉绉嶏紝涔熸槸涓€绉嶇紦瀛樻窐姹扮畻娉曘€傞偅涓轰粈涔堣浣跨敤 W-TinyLFU 鍛紵
LRU 鍜?LFU 鐨勭己鐐?/span>
-
LRU 瀹炵幇绠€鍗曪紝鍦ㄤ竴鑸儏鍐典笅鑳藉琛ㄧ幇鍑哄緢濂界殑鍛戒腑鐜囷紝鏄竴涓€滄€т环姣斺€濆緢楂樼殑绠楁硶锛屽钩鏃朵篃寰堝父鐢ㄣ€傝櫧鐒?LRU 瀵圭獊鍙戞€х殑绋€鐤忔祦閲忥紙sparse bursts锛夎〃鐜板緢濂斤紝浣嗗悓鏃朵篃浼氫骇鐢熺紦瀛樻薄鏌擄紝涓句緥鏉ヨ锛屽鏋滃伓鐒舵€х殑瑕佸鍏ㄩ噺鏁版嵁杩涜閬嶅巻锛岄偅涔堚€滃巻鍙茶闂褰曗€濆氨浼氳鍒疯蛋锛岄€犳垚姹℃煋銆? -
濡傛灉鏁版嵁鐨勫垎甯冨湪涓€娈垫椂闂村唴鏄浐瀹氱殑璇濓紝閭d箞 LFU 鍙互杈惧埌鏈€楂樼殑鍛戒腑鐜囥€備絾鏄?LFU 鏈変袱涓己鐐癸紝绗竴锛屽畠闇€瑕佺粰姣忎釜璁板綍椤圭淮鎶ら鐜囦俊鎭紝姣忔璁块棶閮介渶瑕佹洿鏂帮紝杩欐槸涓法澶х殑寮€閿€锛涚浜岋紝瀵圭獊鍙戞€х殑绋€鐤忔祦閲忔棤鍔涳紝鍥犱负鍓嶆湡缁忓父璁块棶鐨勮褰曞凡缁忓崰鐢ㄤ簡缂撳瓨锛屽伓鐒剁殑娴侀噺涓嶅お鍙兘浼氳淇濈暀涓嬫潵锛岃€屼笖杩囧幓鐨勪竴浜涘ぇ閲忚璁块棶鐨勮褰曞湪灏嗘潵涔熶笉涓€瀹氫細浣跨敤涓婏紝杩欐牱灏变竴鐩存妸鈥滃潙鈥濆崰鐫€浜嗐€?
鏃犺 LRU 杩樻槸 LFU 閮芥湁鍏跺悇鑷殑缂虹偣锛屼笉杩囷紝鐜板湪宸茬粡鏈夊緢澶氶拡瀵瑰叾缂虹偣鑰屾敼鑹€佷紭鍖栧嚭鏉ョ殑鍙樼绠楁硶銆?/span>
TinyLFU
TinyLFU 灏辨槸鍏朵腑涓€涓紭鍖栫畻娉曪紝瀹冩槸涓撻棬涓轰簡瑙e喅 LFU 涓婅堪鎻愬埌鐨勪袱涓棶棰樿€岃璁捐鍑烘潵鐨勩€?/span>
瑙e喅绗竴涓棶棰樻槸閲囩敤浜?Count鈥揗in Sketch 绠楁硶銆?/span>
瑙e喅绗簩涓棶棰樻槸璁╄褰曞敖閲忎繚鎸佺浉瀵圭殑鈥滄柊椴溾€濓紙Freshness Mechanism锛夛紝骞朵笖褰撴湁鏂扮殑璁板綍鎻掑叆鏃讹紝鍙互璁╁畠璺熻€佺殑璁板綍杩涜鈥淧K鈥濓紝杈撹€呭氨浼氳娣樻卑锛岃繖鏍蜂竴浜涜€佺殑銆佷笉鍐嶉渶瑕佺殑璁板綍灏变細琚墧闄ゃ€?/span>
涓嬪浘鏄?TinyLFU 璁捐鍥撅紙鏉ヨ嚜瀹樻柟锛?/span>
缁熻棰戠巼 Count鈥揗in Sketch 绠楁硶
濡備綍瀵逛竴涓?key 杩涜缁熻锛屼絾鍙堝彲浠ヨ妭鐪佺┖闂村憿锛燂紙涓嶆槸绠€鍗曠殑浣跨敤HashMap
锛岃繖澶秷鑰楀唴瀛樹簡锛夛紝娉ㄦ剰鍝︼紝涓嶉渶瑕佺簿纭殑缁熻锛屽彧闇€瑕佷竴涓繎浼煎€煎氨鍙互浜嗭紝鎬庝箞鏍凤紝杩欐牱鍦烘櫙鏄笉鏄緢鐔熸倝锛屽鏋滀綘鏄€佸徃鏈猴紝鎴栬宸茬粡鑱旀兂鍒板竷闅嗚繃婊ゅ櫒锛圔loom Filter锛夌殑搴旂敤浜嗐€?/span>
娌¢敊锛屽皢瑕佷粙缁嶇殑 Count鈥揗in Sketch 鐨勫師鐞嗚窡 Bloom Filter 涓€鏍凤紝鍙笉杩?Bloom Filter 鍙湁 0 鍜?1 鐨勫€硷紝閭d箞浣犲彲浠ユ妸 Count鈥揗in Sketch 鐪嬩綔鏄€滄暟鍊尖€濈増鐨?Bloom Filter銆?/span>
鏇村鍏充簬 Count鈥揗in Sketch 鐨勪粙缁嶈鑷鎼滅储銆?/span>
鍦?TinyLFU 涓紝杩戜技棰戠巼鐨勭粺璁″涓嬪浘鎵€绀猴細
瀵逛竴涓?key 杩涜澶氭 hash 鍑芥暟鍚庯紝index 鍒板涓暟缁勪綅缃悗杩涜绱姞锛屾煡璇㈡椂鍙栧涓€间腑鐨勬渶灏忓€煎嵆鍙€?/span>
Caffeine 瀵硅繖涓畻娉曠殑瀹炵幇鍦?/span>FrequencySketch
绫汇€備絾 Caffeine 瀵规鏈夎繘涓€姝ョ殑浼樺寲锛屼緥濡?Count鈥揗in Sketch 浣跨敤浜嗕簩缁存暟缁勶紝Caffeine 鍙槸鐢ㄤ簡涓€涓竴缁寸殑鏁扮粍锛涘啀鑰咃紝濡傛灉鏄暟鍊肩被鍨嬬殑璇濓紝杩欎釜鏁伴渶瑕佺敤 int 鎴?long 鏉ュ瓨鍌紝浣嗘槸 Caffeine 璁や负缂撳瓨鐨勮闂鐜囦笉闇€瑕佺敤鍒伴偅涔堝ぇ锛屽彧闇€瑕?15 灏辫冻澶燂紝涓€鑸涓鸿揪鍒?15 娆$殑棰戠巼绠楁槸寰堥珮鐨勪簡锛岃€屼笖 Caffeine 杩樻湁鍙﹀涓€涓満鍒舵潵浣垮緱杩欎釜棰戠巼杩涜琛伴€€鍑忓崐锛堜笅闈㈠氨浼氳鍒帮級銆傚鏋滄渶澶ф槸 15 鐨勮瘽锛岄偅涔堝彧闇€瑕?4 涓?bit 灏卞彲浠ユ弧瓒充簡锛屼竴涓?long 鏈?64bit锛屽彲浠ュ瓨鍌?16 涓繖鏍风殑缁熻鏁帮紝Caffeine 灏辨槸杩欐牱鐨勮璁★紝浣垮緱瀛樺偍鏁堢巼鎻愰珮浜?16 鍊嶃€?/span>
Caffeine 瀵圭紦瀛樼殑璇诲啓锛?/span>afterRead
鍜?/span>afterWrite
鏂规硶锛夐兘浼氳皟鐢?/span>onAccess
s 鏂规硶锛岃€?/span>onAccess
鏂规硶閲屾湁涓€鍙ワ細
frequencySketch().increment(key);
杩欏彞灏辨槸杩藉姞璁板綍鐨勯鐜囷紝涓嬮潰鎴戜滑鐪嬬湅鍏蜂綋瀹炵幇
//FrequencySketch鐨勪竴浜涘睘鎬?/span>
//绉嶅瓙鏁?/span>
static final long[] SEED = { // A mixture of seeds from FNV-1a, CityHash, and Murmur3
0xc3a5c85c97cb3127L, 0xb492b66fbe98f273L, 0x9ae16a3b2f90404fL, 0xcbf29ce484222325L};
static final long RESET_MASK = 0x7777777777777777L;
static final long ONE_MASK = 0x1111111111111111L;
int sampleSize;
//涓轰簡蹇€熸牴鎹甴ash鍊煎緱鍒皌able鐨刬ndex鍊肩殑鎺╃爜
//table鐨勯暱搴ize涓€鑸负2鐨刵娆℃柟锛岃€宼ableMask涓簊ize-1锛岃繖鏍峰氨鍙互閫氳繃&鎿嶄綔鏉ユā鎷熷彇浣欐搷浣滐紝閫熷害蹇緢澶氾紝鑰佸徃鏈洪兘鐭ラ亾
int tableMask;
//瀛樺偍鏁版嵁鐨勪竴缁磍ong鏁扮粍
long[] table;
int size;
/**
* Increments the popularity of the element if it does not exceed the maximum (15). The popularity
* of all elements will be periodically down sampled when the observed events exceeds a threshold.
* This process provides a frequency aging to allow expired long term entries to fade away.
*
* @param e the element to add
*/
public void increment(@NonNull E e) {
if (isNotInitialized()) {
return;
}
//鏍规嵁key鐨刪ashCode閫氳繃涓€涓搱甯屽嚱鏁板緱鍒颁竴涓猦ash鍊?/span>
//鏈潵灏辨槸hashCode浜嗭紝涓轰粈涔堣繕瑕佸啀鍋氫竴娆ash锛熸€曞師鏉ョ殑hashCode涓嶅鍧囧寑鍒嗘暎锛屽啀鎵撴暎涓€涓嬨€?/span>
int hash = spread(e.hashCode());
//杩欏彞鍏夌湅鏈夌偣闅剧悊瑙?/span>
//灏卞鎴戝垰鎵嶈鐨勶紝Caffeine鎶婁竴涓猯ong鐨?4bit鍒掑垎鎴?6涓瓑鍒嗭紝姣忎竴绛夊垎4涓猙it銆?/span>
//杩欎釜start灏辨槸鐢ㄦ潵瀹氫綅鍒版槸鍝竴涓瓑鍒嗙殑锛岀敤hash鍊间綆涓や綅浣滀负闅忔満鏁帮紝鍐嶅乏绉?浣嶏紝寰楀埌涓€涓皬浜?6鐨勫€?/span>
int start = (hash & 3) << 2;
//indexOf鏂规硶鐨勬剰鎬濆氨鏄紝鏍规嵁hash鍊煎拰涓嶅悓绉嶅瓙寰楀埌table鐨勪笅鏍噄ndex
//杩欓噷閫氳繃鍥涗釜涓嶅悓鐨勭瀛愶紝寰楀埌鍥涗釜涓嶅悓鐨勪笅鏍噄ndex
int index0 = indexOf(hash, 0);
int index1 = indexOf(hash, 1);
int index2 = indexOf(hash, 2);
int index3 = indexOf(hash, 3);
//鏍规嵁index鍜宻tart(+1, +2, +3)鐨勫€硷紝鎶妕able[index]瀵瑰簲鐨勭瓑鍒嗚拷鍔?
//杩欎釜incrementAt鏂规硶鏈夌偣闅剧悊瑙o紝鐪嬫垜涓嬮潰鐨勮В閲?/span>
boolean added = incrementAt(index0, start);
added |= incrementAt(index1, start + 1);
added |= incrementAt(index2, start + 2);
added |= incrementAt(index3, start + 3);
//杩欎釜reset绛変笅璇?/span>
if (added && (++size == sampleSize)) {
reset();
}
}
/**
* Increments the specified counter by 1 if it is not already at the maximum value (15).
*
* @param i the table index (16 counters)
* @param j the counter to increment
* @return if incremented
*/
boolean incrementAt(int i, int j) {
//杩欎釜j琛ㄧず16涓瓑鍒嗙殑涓嬫爣锛岄偅涔坥ffset灏辨槸鐩稿綋浜庡湪64浣嶄腑鐨勪笅鏍囷紙杩欎釜鑷繁鎯虫兂锛?/span>
int offset = j << 2;
//涓婇潰鎻愬埌Caffeine鎶婇鐜囩粺璁℃渶澶у畾涓?5锛屽嵆0xfL
//mask灏辨槸鍦?4浣嶄腑鐨勬帺鐮侊紝鍗?111鍚庨潰璺熷緢澶氫釜0
long mask = (0xfL << offset);
//濡傛灉&鐨勭粨鏋滀笉绛変簬15锛岄偅涔堝氨杩藉姞1銆傜瓑浜?5灏变笉浼氬啀鍔犱簡
if ((table[i] & mask) != mask) {
table[i] += (1L << offset);
return true;
}
return false;
}
/**
* Returns the table index for the counter at the specified depth.
*
* @param item the element's hash
* @param i the counter depth
* @return the table index
*/
int indexOf(int item, int i) {
long hash = SEED[i] * item;
hash += hash >>> 32;
return ((int) hash) & tableMask;
}
/**
* Applies a supplemental hash function to a given hashCode, which defends against poor quality
* hash functions.
*/
int spread(int x) {
x = ((x >>> 16) ^ x) * 0x45d9f3b;
x = ((x >>> 16) ^ x) * 0x45d9f3b;
return (x >>> 16) ^ x;
}
鐭ラ亾浜嗚拷鍔犳柟娉曪紝閭d箞璇诲彇鏂规硶frequency
灏卞緢瀹规槗鐞嗚В浜嗐€?/p>
/**
* Returns the estimated number of occurrences of an element, up to the maximum (15).
*
* @param e the element to count occurrences of
* @return the estimated number of occurrences of the element; possibly zero but never negative
*/
@NonNegative
public int frequency(@NonNull E e) {
if (isNotInitialized()) {
return 0;
}
//寰楀埌hash鍊硷紝璺熶笂闈竴鏍?/span>
int hash = spread(e.hashCode());
//寰楀埌绛夊垎鐨勪笅鏍囷紝璺熶笂闈竴鏍?/span>
int start = (hash & 3) << 2;
int frequency = Integer.MAX_VALUE;
//寰幆鍥涙锛屽垎鍒幏鍙栧湪table鏁扮粍涓笉鍚岀殑涓嬫爣浣嶇疆
for (int i = 0; i < 4; i++) {
int index = indexOf(hash, i);
//杩欎釜鎿嶄綔灏变笉澶氳浜嗭紝鍏跺疄璺熶笂闈ncrementAt鏄竴鏍风殑锛屽畾浣嶅埌table[index] + 绛夊垎鐨勪綅缃紝鍐嶆牴鎹甿ask鍙栧嚭璁℃暟鍊?/span>
int count = (int) ((table[index] >>> ((start + i) << 2)) & 0xfL);
//鍙栧洓涓腑鐨勮緝灏忓€?/span>
frequency = Math.min(frequency, count);
}
return frequency;
}
閫氳繃浠g爜鍜屾敞閲婃垨鑰呰鑰呭彲鑳介毦浠ョ悊瑙o紝涓嬪浘鏄垜鐢诲嚭鏉ュ府鍔╁ぇ瀹剁悊瑙g殑缁撴瀯鍥俱€?/span>
娉ㄦ剰绱壊铏氱嚎妗嗭紝鍏朵腑钃濊壊灏忔牸灏辨槸闇€瑕佽绠楃殑浣嶇疆锛?/span>
淇濇柊鏈哄埗
涓轰簡璁╃紦瀛樹繚鎸佲€滄柊椴溾€濓紝鍓旈櫎鎺夎繃寰€棰戠巼寰堥珮浣嗕箣鍚庝笉缁忓父鐨勭紦瀛橈紝Caffeine 鏈変竴涓?Freshness Mechanism銆傚仛娉曞緢绠€绛旓紝灏辨槸褰撴暣浣撶殑缁熻璁℃暟锛堝綋鍓嶆墍鏈夎褰曠殑棰戠巼缁熻涔嬪拰锛岃繖涓暟鍊煎唴閮ㄧ淮鎶わ級杈惧埌鏌愪竴涓€兼椂锛岄偅涔堟墍鏈夎褰曠殑棰戠巼缁熻闄や互 2銆?/span>
浠庝笂闈㈢殑浠g爜
//size鍙橀噺灏辨槸鎵€鏈夎褰曠殑棰戠巼缁熻涔嬶紝鍗虫瘡涓褰曞姞1锛岃繖涓猻ize閮戒細鍔?
//sampleSize涓€涓槇鍊硷紝浠嶧requencySketch鍒濆鍖栧彲浠ョ湅鍒板畠鐨勫€间负maximumSize鐨?0鍊?/span>
if (added && (++size == sampleSize)) {
reset();
}
鐪嬪埌reset
鏂规硶灏辨槸鍋氳繖涓簨鎯?/span>
/** Reduces every counter by half of its original value. */
void reset() {
int count = 0;
for (int i = 0; i < table.length; i++) {
count += Long.bitCount(table[i] & ONE_MASK);
table[i] = (table[i] >>> 1) & RESET_MASK;
}
size = (size >>> 1) - (count >>> 2);
}
鍏充簬杩欎釜 reset 鏂规硶锛屼负浠€涔堟槸闄や互 2锛岃€屼笉鏄叾浠栵紝鍙婂叾姝g‘鎬э紝鍦ㄦ渶涓嬮潰鐨勫弬鑰冭祫鏂欑殑 TinyLFU 璁烘枃涓?3.3 绔犺妭缁欏嚭浜嗘暟瀛﹁瘉鏄庯紝澶у鏈夊叴瓒e彲浠ョ湅鐪嬨€?/span>
澧炲姞涓€涓?Window锛?/span>
Caffeine 閫氳繃娴嬭瘯鍙戠幇 TinyLFU 鍦ㄩ潰瀵圭獊鍙戞€х殑绋€鐤忔祦閲忥紙sparse bursts锛夋椂琛ㄧ幇寰堝樊锛屽洜涓烘柊鐨勮褰曪紙new items锛夎繕娌℃潵寰楀強寤虹珛瓒冲鐨勯鐜囧氨琚墧闄ゅ嚭鍘讳簡锛岃繖灏变娇寰楀懡涓巼涓嬮檷銆?/span>
浜庢槸 Caffeine 璁捐鍑轰竴绉嶆柊鐨?policy锛屽嵆 Window Tiny LFU锛圵-TinyLFU锛?/strong>锛屽苟閫氳繃瀹為獙鍜屽疄璺靛彂鐜?W-TinyLFU 姣?TinyLFU 琛ㄧ幇鐨勬洿濂姐€?/span>
W-TinyLFU 鐨勮璁″涓嬫墍绀猴紙涓ゅ浘绛変环锛夛細
瀹冧富瑕佸寘鎷袱涓紦瀛樻ā鍧楋紝涓荤紦瀛樻槸 SLRU锛圫egmented LRU锛屽嵆鍒嗘 LRU锛夛紝SLRU 鍖呮嫭涓€涓悕涓?protected 鍜屼竴涓悕涓?probation 鐨勭紦瀛樺尯銆傞€氳繃澧炲姞涓€涓紦瀛樺尯锛堝嵆 Window Cache锛夛紝褰撴湁鏂扮殑璁板綍鎻掑叆鏃讹紝浼氬厛鍦?window 鍖哄憜涓€涓嬶紝灏卞彲浠ラ伩鍏嶄笂杩拌鐨?sparse bursts 闂銆?/span>
娣樻卑绛栫暐锛坋viction policy锛?/span>
褰?window 鍖烘弧浜嗭紝灏变細鏍规嵁 LRU 鎶?candidate锛堝嵆娣樻卑鍑烘潵鐨勫厓绱狅級鏀惧埌 probation 鍖猴紝濡傛灉 probation 鍖轰篃婊′簡锛屽氨鎶?candidate 鍜?probation 灏嗚娣樻卑鐨勫厓绱?victim锛屼袱涓繘琛屸€淧K鈥濓紝鑳滆€呯暀鍦?probation锛岃緭鑰呭氨瑕佽娣樻卑浜嗐€?/span>
鑰屼笖缁忚繃瀹為獙鍙戠幇褰?window 鍖洪厤缃负鎬诲閲忕殑 1%锛屽墿浣欑殑 99%褰撲腑鐨?80%鍒嗙粰 protected 鍖猴紝20%鍒嗙粰 probation 鍖烘椂锛岃繖鏃舵暣浣撴€ц兘鍜屽懡涓巼琛ㄧ幇寰楁渶濂斤紝鎵€浠?Caffeine 榛樿鐨勬瘮渚嬭缃氨鏄繖涓€?/span>
涓嶈繃杩欎釜姣斾緥 Caffeine 浼氬湪杩愯鏃舵牴鎹粺璁℃暟鎹紙statistics锛夊幓鍔ㄦ€佽皟鏁达紝濡傛灉浣犵殑搴旂敤绋嬪簭鐨勭紦瀛橀殢鐫€鏃堕棿鍙樺寲姣旇緝蹇殑璇濓紝閭d箞澧炲姞 window 鍖虹殑姣斾緥鍙互鎻愰珮鍛戒腑鐜囷紝鐩稿弽缂撳瓨閮芥槸姣旇緝鍥哄畾涓嶅彉鐨勮瘽锛屽鍔?Main Cache 鍖猴紙protected 鍖?+probation 鍖猴級鐨勬瘮渚嬩細鏈夎緝濂界殑鏁堟灉銆?/span>
涓嬮潰鎴戜滑鐪嬬湅涓婇潰璇村埌鐨勬窐姹扮瓥鐣ユ槸鎬庝箞瀹炵幇鐨勶細
涓€鑸紦瀛樺璇诲啓鎿嶄綔鍚庨兘鏈夊悗缁殑涓€绯诲垪鈥滅淮鎶も€濇搷浣滐紝Caffeine 涔熶笉渚嬪锛岃繖浜涙搷浣滈兘鍦?/span>maintenance
鏂规硶锛屾垜浠皢瑕佽鍒扮殑娣樻卑绛栫暐涔熷湪閲岄潰銆?/span>
杩欐柟娉曟瘮杈冮噸瑕侊紝涓嬮潰涔熶細鎻愬埌锛屾墍浠ヨ繖閲屽彧鍏堣璺熲€滄窐姹扮瓥鐣モ€濇湁鍏崇殑evictEntries
鍜?/span>climb
銆?/span>
/**
* Performs the pending maintenance work and sets the state flags during processing to avoid
* excess scheduling attempts. The read buffer, write buffer, and reference queues are
* drained, followed by expiration, and size-based eviction.
*
* @param task an additional pending task to run, or {@code null} if not present
*/
@GuardedBy("evictionLock")
void maintenance(@Nullable Runnable task) {
lazySetDrainStatus(PROCESSING_TO_IDLE);
try {
drainReadBuffer();
drainWriteBuffer();
if (task != null) {
task.run();
}
drainKeyReferences();
drainValueReferences();
expireEntries();
//鎶婄鍚堟潯浠剁殑璁板綍娣樻卑鎺?/span>
evictEntries();
//鍔ㄦ€佽皟鏁磜indow鍖哄拰protected鍖虹殑澶у皬
climb();
} finally {
if ((drainStatus() != PROCESSING_TO_IDLE) || !casDrainStatus(PROCESSING_TO_IDLE, IDLE)) {
lazySetDrainStatus(REQUIRED);
}
}
}
//鏈€澶х殑涓暟闄愬埗
long maximum;
//褰撳墠鐨勪釜鏁?/span>
long weightedSize;
//window鍖虹殑鏈€澶ч檺鍒?/span>
long windowMaximum;
//window鍖哄綋鍓嶇殑涓暟
long windowWeightedSize;
//protected鍖虹殑鏈€澶ч檺鍒?/span>
long mainProtectedMaximum;
//protected鍖哄綋鍓嶇殑涓暟
long mainProtectedWeightedSize;
//涓嬩竴娆¢渶瑕佽皟鏁寸殑澶у皬锛堣繕闇€瑕佽繘涓€姝ヨ绠楋級
double stepSize;
//window鍖洪渶瑕佽皟鏁寸殑澶у皬
long adjustment;
//鍛戒腑璁℃暟
int hitsInSample;
//涓嶅懡涓殑璁℃暟
int missesInSample;
//涓婁竴娆$殑缂撳瓨鍛戒腑鐜?/span>
double previousSampleHitRate;
final FrequencySketch<K> sketch;
//window鍖虹殑LRU queue锛團IFO锛?/span>
final AccessOrderDeque<Node<K, V>> accessOrderWindowDeque;
//probation鍖虹殑LRU queue锛團IFO锛?/span>
final AccessOrderDeque<Node<K, V>> accessOrderProbationDeque;
//protected鍖虹殑LRU queue锛團IFO锛?/span>
final AccessOrderDeque<Node<K, V>> accessOrderProtectedDeque;
浠ュ強榛樿姣斾緥璁剧疆锛堟剰鎬濈湅娉ㄩ噴锛?/span>
/** The initial percent of the maximum weighted capacity dedicated to the main space. */
static final double PERCENT_MAIN = 0.99d;
/** The percent of the maximum weighted capacity dedicated to the main's protected space. */
static final double PERCENT_MAIN_PROTECTED = 0.80d;
/** The difference in hit rates that restarts the climber. */
static final double HILL_CLIMBER_RESTART_THRESHOLD = 0.05d;
/** The percent of the total size to adapt the window by. */
static final double HILL_CLIMBER_STEP_PERCENT = 0.0625d;
/** The rate to decrease the step size to adapt by. */
static final double HILL_CLIMBER_STEP_DECAY_RATE = 0.98d;
/** The maximum number of entries that can be transfered between queues. */
閲嶇偣鏉ヤ簡锛?/span>evictEntries
鍜?/span>climb
鏂规硶锛?/span>
/** Evicts entries if the cache exceeds the maximum. */
@GuardedBy("evictionLock")
void evictEntries() {
if (!evicts()) {
return;
}
//娣樻卑window鍖虹殑璁板綍
int candidates = evictFromWindow();
//娣樻卑Main鍖虹殑璁板綍
evictFromMain(candidates);
}
/**
* Evicts entries from the window space into the main space while the window size exceeds a
* maximum.
*
* @return the number of candidate entries evicted from the window space
*/
//鏍规嵁W-TinyLFU锛屾柊鐨勬暟鎹兘浼氭棤鏉′欢鐨勫姞鍒癮dmission window
//浣嗘槸window鏄湁澶у皬闄愬埗锛屾墍浠ヨ鈥滃畾鏈熲€濆仛涓€涓嬧€滅淮鎶も€?/span>
@GuardedBy("evictionLock")
int evictFromWindow() {
int candidates = 0;
//鏌ョ湅window queue鐨勫ご閮ㄨ妭鐐?/span>
Node<K, V> node = accessOrderWindowDeque().peek();
//濡傛灉window鍖鸿秴杩囦簡鏈€澶х殑闄愬埗锛岄偅涔堝氨瑕佹妸鈥滃鍑烘潵鈥濈殑璁板綍鍋氬鐞?/span>
while (windowWeightedSize() > windowMaximum()) {
// The pending operations will adjust the size to reflect the correct weight
if (node == null) {
break;
}
//涓嬩竴涓妭鐐?/span>
Node<K, V> next = node.getNextInAccessOrder();
if (node.getWeight() != 0) {
//鎶妌ode瀹氫綅鍦╬robation鍖?/span>
node.makeMainProbation();
//浠巜indow鍖哄幓鎺?/span>
accessOrderWindowDeque().remove(node);
//鍔犲叆鍒皃robation queue锛岀浉褰撲簬鎶婅妭鐐圭Щ鍔ㄥ埌probation鍖猴紙鏅嬪崌浜嗭級
accessOrderProbationDeque().add(node);
candidates++;
//鍥犱负绉婚櫎浜嗕竴涓妭鐐癸紝鎵€浠ラ渶瑕佽皟鏁磜indow鐨剆ize
setWindowWeightedSize(windowWeightedSize() - node.getPolicyWeight());
}
//澶勭悊涓嬩竴涓妭鐐?/span>
node = next;
}
return candidates;
}
evictFromMain
鏂规硶锛?/span>
/**
* Evicts entries from the main space if the cache exceeds the maximum capacity. The main space
* determines whether admitting an entry (coming from the window space) is preferable to retaining
* the eviction policy's victim. This is decision is made using a frequency filter so that the
* least frequently used entry is removed.
*
* The window space candidates were previously placed in the MRU position and the eviction
* policy's victim is at the LRU position. The two ends of the queue are evaluated while an
* eviction is required. The number of remaining candidates is provided and decremented on
* eviction, so that when there are no more candidates the victim is evicted.
*
* @param candidates the number of candidate entries evicted from the window space
*/
//鏍规嵁W-TinyLFU锛屼粠window鏅嬪崌杩囨潵鐨勮璺焢robation鍖虹殑杩涜鈥淧K鈥濓紝鑳滆€呮墠鑳界暀涓?/span>
@GuardedBy("evictionLock")
void evictFromMain(int candidates) {
int victimQueue = PROBATION;
//victim鏄痯robation queue鐨勫ご閮?/span>
Node<K, V> victim = accessOrderProbationDeque().peekFirst();
//candidate鏄痯robation queue鐨勫熬閮紝涔熷氨鏄垰浠巜indow鏅嬪崌鏉ョ殑
Node<K, V> candidate = accessOrderProbationDeque().peekLast();
//褰揷ache涓嶅瀹归噺鏃舵墠鍋氬鐞?/span>
while (weightedSize() > maximum()) {
// Stop trying to evict candidates and always prefer the victim
if (candidates == 0) {
candidate = null;
}
//瀵筩andidate涓簄ull涓攙ictim涓篵ull鐨勫鐞?/span>
if ((candidate == null) && (victim == null)) {
if (victimQueue == PROBATION) {
victim = accessOrderProtectedDeque().peekFirst();
victimQueue = PROTECTED;
continue;
} else if (victimQueue == PROTECTED) {
victim = accessOrderWindowDeque().peekFirst();
victimQueue = WINDOW;
continue;
}
// The pending operations will adjust the size to reflect the correct weight
break;
}
//瀵硅妭鐐圭殑weight涓?鐨勫鐞?/span>
if ((victim != null) && (victim.getPolicyWeight() == 0)) {
victim = victim.getNextInAccessOrder();
continue;
} else if ((candidate != null) && (candidate.getPolicyWeight() == 0)) {
candidate = candidate.getPreviousInAccessOrder();
candidates--;
continue;
}
// Evict immediately if only one of the entries is present
if (victim == null) {
@SuppressWarnings("NullAway")
Node<K, V> previous = candidate.getPreviousInAccessOrder();
Node<K, V> evict = candidate;
candidate = previous;
candidates--;
evictEntry(evict, RemovalCause.SIZE, 0L);
continue;
} else if (candidate == null) {
Node<K, V> evict = victim;
victim = victim.getNextInAccessOrder();
evictEntry(evict, RemovalCause.SIZE, 0L);
continue;
}
// Evict immediately if an entry was collected
K victimKey = victim.getKey();
K candidateKey = candidate.getKey();
if (victimKey == null) {
@NonNull Node<K, V> evict = victim;
victim = victim.getNextInAccessOrder();
evictEntry(evict, RemovalCause.COLLECTED, 0L);
continue;
} else if (candidateKey == null) {
candidates--;
@NonNull Node<K, V> evict = candidate;
candidate = candidate.getPreviousInAccessOrder();
evictEntry(evict, RemovalCause.COLLECTED, 0L);
continue;
}
//鏀句笉涓嬬殑鑺傜偣鐩存帴澶勭悊鎺?/span>
if (candidate.getPolicyWeight() > maximum()) {
candidates--;
Node<K, V> evict = candidate;
candidate = candidate.getPreviousInAccessOrder();
evictEntry(evict, RemovalCause.SIZE, 0L);
continue;
}
//鏍规嵁鑺傜偣鐨勭粺璁¢鐜噁requency鏉ュ仛姣旇緝锛岀湅鐪嬭澶勭悊鎺塿ictim杩樻槸candidate
//admit鏄叿浣撶殑姣旇緝瑙勫垯锛岀湅涓嬮潰
candidates--;
//濡傛灉candidate鑳滃嚭鍒欐窐姹皏ictim
if (admit(candidateKey, victimKey)) {
Node<K, V> evict = victim;
victim = victim.getNextInAccessOrder();
evictEntry(evict, RemovalCause.SIZE, 0L);
candidate = candidate.getPreviousInAccessOrder();
} else {
//濡傛灉鏄痸ictim鑳滃嚭锛屽垯娣樻卑candidate
Node<K, V> evict = candidate;
candidate = candidate.getPreviousInAccessOrder();
evictEntry(evict, RemovalCause.SIZE, 0L);
}
}
}
/**
* Determines if the candidate should be accepted into the main space, as determined by its
* frequency relative to the victim. A small amount of randomness is used to protect against hash
* collision attacks, where the victim's frequency is artificially raised so that no new entries
* are admitted.
*
* @param candidateKey the key for the entry being proposed for long term retention
* @param victimKey the key for the entry chosen by the eviction policy for replacement
* @return if the candidate should be admitted and the victim ejected
*/
@GuardedBy("evictionLock")
boolean admit(K candidateKey, K victimKey) {
//鍒嗗埆鑾峰彇victim鍜宑andidate鐨勭粺璁¢鐜?/span>
//frequency杩欎釜鏂规硶鐨勫師鐞嗗拰瀹炵幇涓婇潰宸茬粡瑙i噴浜?/span>
int victimFreq = frequencySketch().frequency(victimKey);
int candidateFreq = frequencySketch().frequency(candidateKey);
//璋佸ぇ璋佽耽
if (candidateFreq > victimFreq) {
return true;
//濡傛灉鐩哥瓑锛宑andidate灏忎簬5閮藉綋杈撲簡
} else if (candidateFreq <= 5) {
// The maximum frequency is 15 and halved to 7 after a reset to age the history. An attack
// exploits that a hot candidate is rejected in favor of a hot victim. The threshold of a warm
// candidate reduces the number of random acceptances to minimize the impact on the hit rate.
return false;
}
//濡傛灉鐩哥瓑涓攃andidate澶т簬5锛屽垯闅忔満娣樻卑涓€涓?/span>
int random = ThreadLocalRandom.current().nextInt();
return ((random & 127) == 0);
}
climb
鏂规硶涓昏鏄敤鏉ヨ皟鏁?window size 鐨勶紝浣垮緱 Caffeine 鍙互閫傚簲浣犵殑搴旂敤绫诲瀷锛堝 OLAP 鎴?OLTP锛夎〃鐜板嚭鏈€浣崇殑鍛戒腑鐜囥€?/span>
涓嬪浘鏄畼鏂规祴璇曠殑鏁版嵁锛?/span>
鎴戜滑鐪嬬湅 window size 鐨勮皟鏁存槸鎬庝箞瀹炵幇鐨勩€?/span>
璋冩暣鏃剁敤鍒扮殑榛樿姣斾緥鏁版嵁锛?/span>
//涓庝笂娆″懡涓巼涔嬪樊鐨勯槇鍊?/span>
static final double HILL_CLIMBER_RESTART_THRESHOLD = 0.05d;
//姝ラ暱锛堣皟鏁达級鐨勫ぇ灏忥紙璺熸渶澶у€糾aximum鐨勬瘮渚嬶級
static final double HILL_CLIMBER_STEP_PERCENT = 0.0625d;
//姝ラ暱鐨勮“鍑忔瘮渚?/span>
static final double HILL_CLIMBER_STEP_DECAY_RATE = 0.98d;
/** Adapts the eviction policy to towards the optimal recency / frequency configuration. */
//climb鏂规硶鐨勪富瑕佷綔鐢ㄥ氨鏄姩鎬佽皟鏁磜indow鍖虹殑澶у皬锛堢浉搴旂殑锛宮ain鍖虹殑澶у皬涔熶細鍙戠敓鍙樺寲锛屼袱涓箣鍜屼负100%锛夈€?/span>
//鍥犱负鍖哄煙鐨勫ぇ灏忓彂鐢熶簡鍙樺寲锛岄偅涔堝尯鍩熷唴鐨勬暟鎹篃鍙兘闇€瑕佸彂鐢熺浉搴旂殑绉诲姩銆?/span>
@GuardedBy("evictionLock")
void climb() {
if (!evicts()) {
return;
}
//纭畾window闇€瑕佽皟鏁寸殑澶у皬
determineAdjustment();
//濡傛灉protected鍖烘湁婧㈠嚭锛屾妸婧㈠嚭閮ㄥ垎绉诲姩鍒皃robation鍖恒€傚洜涓轰笅闈㈢殑鎿嶄綔鏈夊彲鑳介渶瑕佽皟鏁村埌protected鍖恒€?/span>
demoteFromMainProtected();
long amount = adjustment();
if (amount == 0) {
return;
} else if (amount > 0) {
//澧炲姞window鐨勫ぇ灏?/span>
increaseWindow();
} else {
//鍑忓皯window鐨勫ぇ灏?/span>
decreaseWindow();
}
}
涓嬮潰鍒嗗埆灞曞紑姣忎釜鏂规硶鏉ヨВ閲婏細
/** Calculates the amount to adapt the window by and sets {@link #adjustment()} accordingly. */
@GuardedBy("evictionLock")
void determineAdjustment() {
//濡傛灉frequencySketch杩樻病鍒濆鍖栵紝鍒欒繑鍥?/span>
if (frequencySketch().isNotInitialized()) {
setPreviousSampleHitRate(0.0);
setMissesInSample(0);
setHitsInSample(0);
return;
}
//鎬昏姹傞噺 = 鍛戒腑 + miss
int requestCount = hitsInSample() + missesInSample();
//娌¤揪鍒皊ampleSize鍒欒繑鍥?/span>
//榛樿涓媠ampleSize = 10 * maximum銆傜敤sampleSize鏉ュ垽鏂紦瀛樻槸鍚﹁冻澶熲€濈儹鈥溿€?/span>
if (requestCount < frequencySketch().sampleSize) {
return;
}
//鍛戒腑鐜囩殑鍏紡 = 鍛戒腑 / 鎬昏姹?/span>
double hitRate = (double) hitsInSample() / requestCount;
//鍛戒腑鐜囩殑宸€?/span>
double hitRateChange = hitRate - previousSampleHitRate();
//鏈璋冩暣鐨勫ぇ灏忥紝鏄敱鍛戒腑鐜囩殑宸€煎拰涓婃鐨剆tepSize鍐冲畾鐨?/span>
double amount = (hitRateChange >= 0) ? stepSize() : -stepSize();
//涓嬫鐨勮皟鏁村ぇ灏忥細濡傛灉鍛戒腑鐜囩殑涔嬪樊澶т簬0.05锛屽垯閲嶇疆涓?.065 * maximum锛屽惁鍒欐寜鐓?.98鏉ヨ繘琛岃“鍑?/span>
double nextStepSize = (Math.abs(hitRateChange) >= HILL_CLIMBER_RESTART_THRESHOLD)
? HILL_CLIMBER_STEP_PERCENT * maximum() * (amount >= 0 ? 1 : -1)
: HILL_CLIMBER_STEP_DECAY_RATE * amount;
setPreviousSampleHitRate(hitRate);
setAdjustment((long) amount);
setStepSize(nextStepSize);
setMissesInSample(0);
setHitsInSample(0);
}
/** Transfers the nodes from the protected to the probation region if it exceeds the maximum. */
//杩欎釜鏂规硶姣旇緝绠€鍗曪紝鍑忓皯protected鍖烘孩鍑虹殑閮ㄥ垎
@GuardedBy("evictionLock")
void demoteFromMainProtected() {
long mainProtectedMaximum = mainProtectedMaximum();
long mainProtectedWeightedSize = mainProtectedWeightedSize();
if (mainProtectedWeightedSize <= mainProtectedMaximum) {
return;
}
for (int i = 0; i < QUEUE_TRANSFER_THRESHOLD; i++) {
if (mainProtectedWeightedSize <= mainProtectedMaximum) {
break;
}
Node<K, V> demoted = accessOrderProtectedDeque().poll();
if (demoted == null) {
break;
}
demoted.makeMainProbation();
accessOrderProbationDeque().add(demoted);
mainProtectedWeightedSize -= demoted.getPolicyWeight();
}
setMainProtectedWeightedSize(mainProtectedWeightedSize);
}
/**
* Increases the size of the admission window by shrinking the portion allocated to the main
* space. As the main space is partitioned into probation and protected regions (80% / 20%), for
* simplicity only the protected is reduced. If the regions exceed their maximums, this may cause
* protected items to be demoted to the probation region and probation items to be demoted to the
* admission window.
*/
//澧炲姞window鍖虹殑澶у皬锛岃繖涓柟娉曟瘮杈冪畝鍗曪紝鎬濊矾灏卞儚鎴戜笂闈㈣鐨?/span>
@GuardedBy("evictionLock")
void increaseWindow() {
if (mainProtectedMaximum() == 0) {
return;
}
long quota = Math.min(adjustment(), mainProtectedMaximum());
setMainProtectedMaximum(mainProtectedMaximum() - quota);
setWindowMaximum(windowMaximum() + quota);
demoteFromMainProtected();
for (int i = 0; i < QUEUE_TRANSFER_THRESHOLD; i++) {
Node<K, V> candidate = accessOrderProbationDeque().peek();
boolean probation = true;
if ((candidate == null) || (quota < candidate.getPolicyWeight())) {
candidate = accessOrderProtectedDeque().peek();
probation = false;
}
if (candidate == null) {
break;
}
int weight = candidate.getPolicyWeight();
if (quota < weight) {
break;
}
quota -= weight;
if (probation) {
accessOrderProbationDeque().remove(candidate);
} else {
setMainProtectedWeightedSize(mainProtectedWeightedSize() - weight);
accessOrderProtectedDeque().remove(candidate);
}
setWindowWeightedSize(windowWeightedSize() + weight);
accessOrderWindowDeque().add(candidate);
candidate.makeWindow();
}
setMainProtectedMaximum(mainProtectedMaximum() + quota);
setWindowMaximum(windowMaximum() - quota);
setAdjustment(quota);
}
/** Decreases the size of the admission window and increases the main's protected region. */
//鍚屼笂increaseWindow宸笉澶氾紝鍙嶆搷浣?/span>
@GuardedBy("evictionLock")
void decreaseWindow() {
if (windowMaximum() <= 1) {
return;
}
long quota = Math.min(-adjustment(), Math.max(0, windowMaximum() - 1));
setMainProtectedMaximum(mainProtectedMaximum() + quota);
setWindowMaximum(windowMaximum() - quota);
for (int i = 0; i < QUEUE_TRANSFER_THRESHOLD; i++) {
Node<K, V> candidate = accessOrderWindowDeque().peek();
if (candidate == null) {
break;
}
int weight = candidate.getPolicyWeight();
if (quota < weight) {
break;
}
quota -= weight;
setMainProtectedWeightedSize(mainProtectedWeightedSize() + weight);
setWindowWeightedSize(windowWeightedSize() - weight);
accessOrderWindowDeque().remove(candidate);
accessOrderProbationDeque().add(candidate);
candidate.makeMainProbation();
}
setMainProtectedMaximum(mainProtectedMaximum() - quota);
setWindowMaximum(windowMaximum() + quota);
setAdjustment(-quota);
}
浠ヤ笂锛屾槸 Caffeine 鐨?W-TinyLFU 绛栫暐鐨勮璁″師鐞嗗強浠g爜瀹炵幇瑙f瀽銆?/span>
寮傛鐨勯珮鎬ц兘璇诲啓
涓€鑸殑缂撳瓨姣忔瀵规暟鎹鐞嗗畬涔嬪悗锛堣鐨勮瘽锛屽凡缁忓瓨鍦ㄥ垯鐩存帴杩斿洖锛屼笉瀛樺湪鍒?load 鏁版嵁锛屼繚瀛橈紝鍐嶈繑鍥烇紱鍐欑殑璇濓紝鍒欑洿鎺ユ彃鍏ユ垨鏇存柊锛夛紝浣嗘槸鍥犱负瑕佺淮鎶や竴浜涙窐姹扮瓥鐣ワ紝鍒欓渶瑕佷竴浜涢澶栫殑鎿嶄綔锛岃濡傦細
-
璁$畻鍜屾瘮杈冩暟鎹殑鏄惁杩囨湡 -
缁熻棰戠巼锛堝儚 LFU 鎴栧叾鍙樼锛? -
缁存姢 read queue 鍜?write queue -
娣樻卑绗﹀悎鏉′欢鐨勬暟鎹? -
绛夌瓑銆傘€傘€?
杩欑鏁版嵁鐨勮鍐欎即闅忕潃缂撳瓨鐘舵€佺殑鍙樻洿锛孏uava Cache 鐨勫仛娉曟槸鎶婅繖浜涙搷浣滃拰璇诲啓鎿嶄綔鏀惧湪涓€璧凤紝鍦ㄤ竴涓悓姝ュ姞閿佺殑鎿嶄綔涓畬鎴愶紝铏界劧 Guava Cache 宸у鍦板埄鐢ㄤ簡 JDK 鐨?ConcurrentHashMap锛堝垎娈甸攣鎴栬€呮棤閿?CAS锛夋潵闄嶄綆閿佺殑瀵嗗害锛岃揪鍒版彁楂樺苟鍙戝害鐨勭洰鐨勩€備絾鏄紝瀵逛簬涓€浜涚儹鐐规暟鎹紝杩欑鍋氭硶杩樻槸閬垮厤涓嶄簡棰戠箒鐨勯攣绔炰簤銆侰affeine 鍊熼壌浜嗘暟鎹簱绯荤粺鐨?WAL锛圵rite-Ahead Logging锛夋€濇兂锛屽嵆鍏堝啓鏃ュ織鍐嶆墽琛屾搷浣滐紝杩欑鎬濇兂鍚屾牱閫傚悎缂撳瓨鐨勶紝鎵ц璇诲啓鎿嶄綔鏃讹紝鍏堟妸鎿嶄綔璁板綍鍦ㄧ紦鍐插尯锛岀劧鍚庡湪鍚堥€傜殑鏃舵満寮傛銆佹壒閲忓湴鎵ц缂撳啿鍖轰腑鐨勫唴瀹广€備絾鍦ㄦ墽琛岀紦鍐插尯鐨勫唴瀹规椂锛屼篃鏄渶瑕佸湪缂撳啿鍖哄姞涓婂悓姝ラ攣鐨勶紝涓嶇劧瀛樺湪骞跺彂闂锛屽彧涓嶈繃杩欐牱灏卞彲浠ユ妸瀵归攣鐨勭珵浜変粠缂撳瓨鏁版嵁杞Щ鍒板缂撳啿鍖轰笂銆?/span>
ReadBuffer
鍦?Caffeine 鐨勫唴閮ㄥ疄鐜颁腑锛屼负浜嗗緢濂界殑鏀寔涓嶅悓鐨?Features锛堝 Eviction锛孯emoval锛孯efresh锛孲tatistics锛孋leanup锛孭olicy 绛夌瓑锛夛紝鎵╁睍浜嗗緢澶氬瓙绫伙紝瀹冧滑鍏卞悓鐨勭埗绫绘槸BoundedLocalCache
锛岃€?/span>readBuffer
灏辨槸浣滀负瀹冧滑鍏辨湁鐨勫睘鎬э紝鍗抽兘鏄敤涓€鏍风殑 readBuffer锛岀湅瀹氫箟锛?/span>
final Buffer<Node<K, V>> readBuffer;
readBuffer = evicts() || collectKeys() || collectValues() || expiresAfterAccess()
? new BoundedBuffer<>()
: Buffer.disabled();
涓婇潰鎻愬埌 Caffeine 瀵规瘡娆$紦瀛樼殑璇绘搷浣滈兘浼氳Е鍙?/span>afterRead
/**
* Performs the post-processing work required after a read.
*
* @param node the entry in the page replacement policy
* @param now the current time, in nanoseconds
* @param recordHit if the hit count should be incremented
*/
void afterRead(Node<K, V> node, long now, boolean recordHit) {
if (recordHit) {
statsCounter().recordHits(1);
}
//鎶婅褰曞姞鍏ュ埌readBuffer
//鍒ゆ柇鏄惁闇€瑕佺珛鍗冲鐞唕eadBuffer
//娉ㄦ剰杩欓噷鏃犺offer鏄惁鎴愬姛閮藉彲浠ヨ蛋涓嬪幓鐨勶紝鍗冲厑璁稿啓鍏eadBuffer涓㈠け锛屽洜涓鸿繖涓?/span>
boolean delayable = skipReadBuffer() || (readBuffer.offer(node) != Buffer.FULL);
if (shouldDrainBuffers(delayable)) {
scheduleDrainBuffers();
}
refreshIfNeeded(node, now);
}
/**
* Returns whether maintenance work is needed.
*
* @param delayable if draining the read buffer can be delayed
*/
//caffeine鐢ㄤ簡涓€缁勭姸鎬佹潵瀹氫箟鍜岀鐞嗏€滅淮鎶も€濈殑杩囩▼
boolean shouldDrainBuffers(boolean delayable) {
switch (drainStatus()) {
case IDLE:
return !delayable;
case REQUIRED:
return true;
case PROCESSING_TO_IDLE:
case PROCESSING_TO_REQUIRED:
return false;
default:
throw new IllegalStateException();
}
}
閲嶇偣鐪?/span>BoundedBuffer
/**
* A striped, non-blocking, bounded buffer.
*
* @author ben.manes@gmail.com (Ben Manes)
* @param <E> the type of elements maintained by this buffer
*/
final class BoundedBuffer<E> extends StripedBuffer<E>
瀹冩槸涓€涓?striped銆侀潪闃诲銆佹湁鐣岄檺鐨?buffer锛岀户鎵夸簬StripedBuffer
绫汇€備笅闈㈢湅鐪?/span>StripedBuffer
鐨勫疄鐜帮細
/**
* A base class providing the mechanics for supporting dynamic striping of bounded buffers. This
* implementation is an adaption of the numeric 64-bit {@link java.util.concurrent.atomic.Striped64}
* class, which is used by atomic counters. The approach was modified to lazily grow an array of
* buffers in order to minimize memory usage for caches that are not heavily contended on.
*
* @author dl@cs.oswego.edu (Doug Lea)
* @author ben.manes@gmail.com (Ben Manes)
*/
abstract class StripedBuffer<E> implements Buffer<E>
杩欎釜StripedBuffer
璁捐鐨勬€濇兂鏄窡Striped64
绫讳技鐨勶紝閫氳繃鎵╁睍缁撴瀯鎶婄珵浜夌儹鐐瑰垎绂汇€?/span>
鍏蜂綋瀹炵幇鏄繖鏍风殑锛?/span>StripedBuffer
缁存姢涓€涓?/span>Buffer[]
鏁扮粍锛屾瘡涓厓绱犲氨鏄竴涓?/span>RingBuffer
锛屾瘡涓嚎绋嬬敤鑷繁threadLocalRandomProbe
灞炴€т綔涓?hash 鍊硷紝杩欐牱灏辩浉褰撲簬姣忎釜绾跨▼閮芥湁鑷繁鈥滀笓灞炩€濈殑RingBuffer
锛屽氨涓嶄細浜х敓绔炰簤鍟︼紝鑰屼笉鏄敤 key 鐨?/span>hashCode
浣滀负 hash 鍊硷紝鍥犱负浼氫骇鐢熺儹鐐规暟鎹棶棰樸€?/span>
鐪嬬湅StripedBuffer
鐨勫睘鎬?/span>
/** Table of buffers. When non-null, size is a power of 2. */
//RingBuffer鏁扮粍
transient volatile Buffer<E> @Nullable[] table;
//褰撹繘琛宺esize鏃讹紝闇€瑕佹暣涓猼able閿佷綇銆倀ableBusy浣滀负CAS鐨勬爣璁般€?/span>
static final long TABLE_BUSY = UnsafeAccess.objectFieldOffset(StripedBuffer.class, "tableBusy");
static final long PROBE = UnsafeAccess.objectFieldOffset(Thread.class, "threadLocalRandomProbe");
/** Number of CPUS. */
static final int NCPU = Runtime.getRuntime().availableProcessors();
/** The bound on the table size. */
//table鏈€澶ize
static final int MAXIMUM_TABLE_SIZE = 4 * ceilingNextPowerOfTwo(NCPU);
/** The maximum number of attempts when trying to expand the table. */
//濡傛灉鍙戠敓绔炰簤鏃讹紙CAS澶辫触锛夌殑灏濊瘯娆℃暟
static final int ATTEMPTS = 3;
/** Table of buffers. When non-null, size is a power of 2. */
//鏍稿績鏁版嵁缁撴瀯
transient volatile Buffer<E> @Nullable[] table;
/** Spinlock (locked via CAS) used when resizing and/or creating Buffers. */
transient volatile int tableBusy;
/** CASes the tableBusy field from 0 to 1 to acquire lock. */
final boolean casTableBusy() {
return UnsafeAccess.UNSAFE.compareAndSwapInt(this, TABLE_BUSY, 0, 1);
}
/**
* Returns the probe value for the current thread. Duplicated from ThreadLocalRandom because of
* packaging restrictions.
*/
static final int getProbe() {
return UnsafeAccess.UNSAFE.getInt(Thread.currentThread(), PROBE);
}
offer
鏂规硶锛屽綋娌″垵濮嬪寲鎴栧瓨鍦ㄧ珵浜夋椂锛屽垯鎵╁涓?2 鍊嶃€?/span>
瀹為檯鏄皟鐢?/span>RingBuffer
鐨?offer 鏂规硶锛屾妸鏁版嵁杩藉姞鍒?/span>RingBuffer
鍚庨潰銆?/span>
@Override
public int offer(E e) {
int mask;
int result = 0;
Buffer<E> buffer;
//鏄惁涓嶅瓨鍦ㄧ珵浜?/span>
boolean uncontended = true;
Buffer<E>[] buffers = table
//鏄惁宸茬粡鍒濆鍖?/span>
if ((buffers == null)
|| (mask = buffers.length - 1) < 0
//鐢╰hread鐨勯殢鏈哄€间綔涓篽ash鍊硷紝寰楀埌瀵瑰簲浣嶇疆鐨凴ingBuffer
|| (buffer = buffers[getProbe() & mask]) == null
//妫€鏌ヨ拷鍔犲埌RingBuffer鏄惁鎴愬姛
|| !(uncontended = ((result = buffer.offer(e)) != Buffer.FAILED))) {
//鍏朵腑涓€涓鍚堟潯浠跺垯杩涜鎵╁
expandOrRetry(e, uncontended);
}
return result;
}
/**
* Handles cases of updates involving initialization, resizing, creating new Buffers, and/or
* contention. See above for explanation. This method suffers the usual non-modularity problems of
* optimistic retry code, relying on rechecked sets of reads.
*
* @param e the element to add
* @param wasUncontended false if CAS failed before call
*/
//杩欎釜鏂规硶姣旇緝闀匡紝浣嗘€濊矾杩樻槸鐩稿娓呮櫚鐨勩€?/span>
@SuppressWarnings("PMD.ConfusingTernary")
final void expandOrRetry(E e, boolean wasUncontended) {
int h;
if ((h = getProbe()) == 0) {
ThreadLocalRandom.current(); // force initialization
h = getProbe();
wasUncontended = true;
}
boolean collide = false; // True if last slot nonempty
for (int attempt = 0; attempt < ATTEMPTS; attempt++) {
Buffer<E>[] buffers;
Buffer<E> buffer;
int n;
if (((buffers = table) != null) && ((n = buffers.length) > 0)) {
if ((buffer = buffers[(n - 1) & h]) == null) {
if ((tableBusy == 0) && casTableBusy()) { // Try to attach new Buffer
boolean created = false;
try { // Recheck under lock
Buffer<E>[] rs;
int mask, j;
if (((rs = table) != null) && ((mask = rs.length) > 0)
&& (rs[j = (mask - 1) & h] == null)) {
rs[j] = create(e);
created = true;
}
} finally {
tableBusy = 0;
}
if (created) {
break;
}
continue; // Slot is now non-empty
}
collide = false;
} else if (!wasUncontended) { // CAS already known to fail
wasUncontended = true; // Continue after rehash
} else if (buffer.offer(e) != Buffer.FAILED) {
break;
} else if (n >= MAXIMUM_TABLE_SIZE || table != buffers) {
collide = false; // At max size or stale
} else if (!collide) {
collide = true;
} else if (tableBusy == 0 && casTableBusy()) {
try {
if (table == buffers) { // Expand table unless stale
table = Arrays.copyOf(buffers, n << 1);
}
} finally {
tableBusy = 0;
}
collide = false;
continue; // Retry with expanded table
}
h = advanceProbe(h);
} else if ((tableBusy == 0) && (table == buffers) && casTableBusy()) {
boolean init = false;
try { // Initialize table
if (table == buffers) {
@SuppressWarnings({"unchecked", "rawtypes"})
Buffer<E>[] rs = new Buffer[1];
rs[0] = create(e);
table = rs;
init = true;
}
} finally {
tableBusy = 0;
}
if (init) {
break;
}
}
}
}
鏈€鍚庣湅鐪?/span>RingBuffer
锛屾敞鎰?/span>RingBuffer
鏄?/span>BoundedBuffer
鐨勫唴閮ㄧ被銆?/span>
/** The maximum number of elements per buffer. */
static final int BUFFER_SIZE = 16;
// Assume 4-byte references and 64-byte cache line (16 elements per line)
//256闀垮害锛屼絾鏄槸浠?6涓哄崟浣嶏紝鎵€浠ユ渶澶氬瓨鏀?6涓厓绱?/span>
static final int SPACED_SIZE = BUFFER_SIZE << 4;
static final int SPACED_MASK = SPACED_SIZE - 1;
static final int OFFSET = 16;
//RingBuffer鏁扮粍
final AtomicReferenceArray<E> buffer;
//鎻掑叆鏂规硶
@Override
public int offer(E e) {
long head = readCounter;
long tail = relaxedWriteCounter();
//鐢╤ead鍜宼ail鏉ラ檺鍒朵釜鏁?/span>
long size = (tail - head);
if (size >= SPACED_SIZE) {
return Buffer.FULL;
}
//tail杩藉姞16
if (casWriteCounter(tail, tail + OFFSET)) {
//鐢╰ail鈥滃彇浣欌€濆緱鍒颁笅鏍?/span>
int index = (int) (tail & SPACED_MASK);
//鐢╱nsafe.putOrderedObject璁惧€?/span>
buffer.lazySet(index, e);
return Buffer.SUCCESS;
}
//濡傛灉CAS澶辫触鍒欒繑鍥炲け璐?/span>
return Buffer.FAILED;
}
//鐢╟onsumer鏉ュ鐞哹uffer鐨勬暟鎹?/span>
@Override
public void drainTo(Consumer<E> consumer) {
long head = readCounter;
long tail = relaxedWriteCounter();
//鍒ゆ柇鏁版嵁澶氬皯
long size = (tail - head);
if (size == 0) {
return;
}
do {
int index = (int) (head & SPACED_MASK);
E e = buffer.get(index);
if (e == null) {
// not published yet
break;
}
buffer.lazySet(index, null);
consumer.accept(e);
//head涔熻窡tail涓€鏍凤紝姣忔閫掑16
head += OFFSET;
} while (head != tail);
lazySetReadCounter(head);
}
娉ㄦ剰锛宺ing buffer 鐨?size锛堝浐瀹氭槸 16 涓級鏄笉鍙樼殑锛屽彉鐨勬槸 head 鍜?tail 鑰屽凡銆?/span>
鎬荤殑鏉ヨReadBuffer
鏈夊涓嬬壒鐐癸細
-
浣跨敤 Striped-RingBuffer
鏉ユ彁鍗囧 buffer 鐨勮鍐?/span> -
鐢?thread 鐨?hash 鏉ラ伩寮€鐑偣 key 鐨勭珵浜? -
鍏佽鍐欏叆鐨勪涪澶?
WriteBuffer
writeBuffer
璺?/span>readBuffer
涓嶄竴鏍凤紝涓昏浣撶幇鍦ㄤ娇鐢ㄥ満鏅殑涓嶄竴鏍枫€傛湰鏉ョ紦瀛樼殑涓€鑸満鏅槸璇诲鍐欏皯鐨勶紝璇荤殑骞跺彂浼氭洿楂橈紝涓?afterRead 鏄惧緱娌¢偅涔堥噸瑕侊紝鍏佽寤惰繜鐢氳嚦涓㈠け銆傚啓涓嶄竴鏍凤紝鍐?/span>afterWrite
涓嶅厑璁镐涪澶憋紝涓旇姹傚敖閲忛┈涓婃墽琛屻€侰affeine 浣跨敤MPSC锛圡ultiple Producer / Single Consumer锛変綔涓?buffer 鏁扮粍锛屽疄鐜板湪MpscGrowableArrayQueue
绫伙紝瀹冩槸浠跨収JCTools
鐨?/span>MpscGrowableArrayQueue
鏉ュ啓鐨勩€?/span>
MPSC 鍏佽鏃犻攣鐨勯珮骞跺彂鍐欏叆锛屼絾鍙厑璁镐竴涓秷璐硅€咃紝鍚屾椂涔熺壓鐗蹭簡閮ㄥ垎鎿嶄綔銆?/span>
MPSC 鎴戞墦绠楀彟澶栧垎鏋愶紝杩欓噷涓嶅睍寮€浜嗐€?/span>
TimerWheel
闄や簡鏀寔expireAfterAccess
鍜?/span>expireAfterWrite
涔嬪锛圙uava Cache 涔熸敮鎸佽繖涓や釜鐗规€э級锛孋affeine 杩樻敮鎸?/span>expireAfter
銆傚洜涓?/span>expireAfterAccess
鍜?/span>expireAfterWrite
閮藉彧鑳芥槸鍥哄畾鐨勮繃鏈熸椂闂达紝杩欏彲鑳芥弧瓒充笉浜嗘煇浜涘満鏅紝璀璁板綍鐨勮繃鏈熸椂闂存槸闇€瑕佹牴鎹煇浜涙潯浠惰€屼笉涓€鏍风殑锛岃繖灏遍渶瑕佺敤鎴疯嚜瀹氫箟杩囨湡鏃堕棿銆?/span>
鍏堢湅鐪?/span>expireAfter
鐨勭敤娉?/span>
private static LoadingCache<String, String> cache = Caffeine.newBuilder()
.maximumSize(256L)
.initialCapacity(1)
//.expireAfterAccess(2, TimeUnit.DAYS)
//.expireAfterWrite(2, TimeUnit.HOURS)
.refreshAfterWrite(1, TimeUnit.HOURS)
//鑷畾涔夎繃鏈熸椂闂?/span>
.expireAfter(new Expiry<String, String>() {
//杩斿洖鍒涘缓鍚庣殑杩囨湡鏃堕棿
@Override
public long expireAfterCreate(@NonNull String key, @NonNull String value, long currentTime) {
return 0;
}
//杩斿洖鏇存柊鍚庣殑杩囨湡鏃堕棿
@Override
public long expireAfterUpdate(@NonNull String key, @NonNull String value, long currentTime, @NonNegative long currentDuration) {
return 0;
}
//杩斿洖璇诲彇鍚庣殑杩囨湡鏃堕棿
@Override
public long expireAfterRead(@NonNull String key, @NonNull String value, long currentTime, @NonNegative long currentDuration) {
return 0;
}
})
.recordStats()
.build(new CacheLoader<String, String>() {
@Nullable
@Override
public String load(@NonNull String key) throws Exception {
return "value_" + key;
}
});
閫氳繃鑷畾涔夎繃鏈熸椂闂达紝浣垮緱涓嶅悓鐨?key 鍙互鍔ㄦ€佺殑寰楀埌涓嶅悓鐨勮繃鏈熸椂闂淬€?/span>
娉ㄦ剰锛屾垜鎶?/span>expireAfterAccess
鍜?/span>expireAfterWrite
娉ㄩ噴浜嗭紝鍥犱负杩欎袱涓壒鎬т笉鑳借窡expireAfter
涓€璧蜂娇鐢ㄣ€?/span>
鑰屽綋浣跨敤浜?/span>expireAfter
鐗规€у悗锛孋affeine 浼氬惎鐢ㄤ竴绉嶅彨鈥滄椂闂磋疆鈥濈殑绠楁硶鏉ュ疄鐜拌繖涓姛鑳姐€傛洿澶氬叧浜庢椂闂磋疆鐨勪粙缁嶏紝鍙互鐪嬫垜鐨勬枃绔?span class="mq-1339">HashedWheelTimer 鏃堕棿杞師鐞嗗垎鏋?/span>[6]銆?/span>
濂斤紝閲嶇偣鏉ヤ簡锛屼负浠€涔堣鐢ㄦ椂闂磋疆锛?/span>
瀵?/span>expireAfterAccess
鍜?/span>expireAfterWrite
鐨勫疄鐜版槸鐢ㄤ竴涓?/span>AccessOrderDeque
鍙岀闃熷垪锛屽畠鏄?FIFO 鐨勶紝鍥犱负瀹冧滑鐨勮繃鏈熸椂闂存槸鍥哄畾鐨勶紝鎵€浠ュ湪闃熷垪澶寸殑鏁版嵁鑲畾鏄渶鏃╄繃鏈熺殑锛岃澶勭悊杩囨湡鏁版嵁鏃讹紝鍙渶瑕侀鍏堢湅鐪嬪ご閮ㄦ槸鍚﹁繃鏈燂紝鐒跺悗鍐嶆尐涓鏌ュ氨鍙互浜嗐€備絾鏄紝濡傛灉杩囨湡鏃堕棿涓嶄竴鏍风殑璇濓紝杩欓渶瑕佸accessOrderQueue
杩涜鎺掑簭&鎻掑叆锛岃繖涓唬浠峰お澶т簡銆備簬鏄紝Caffeine 鐢ㄤ簡涓€绉嶆洿鍔犻珮鏁堛€佷紭闆呯殑绠楁硶-鏃堕棿杞€?/span>
鏃堕棿杞殑缁撴瀯锛?/span>
鍥犱负鍦ㄦ垜鐨勫鏃堕棿杞垎鏋愮殑鏂囩珷閲屽凡缁忚浜嗘椂闂磋疆鐨勫師鐞嗗拰鏈哄埗浜嗭紝鎵€浠ユ垜灏变笉灞曞紑 Caffeine 瀵规椂闂磋疆鐨勫疄鐜颁簡銆?/span>
Caffeine 瀵规椂闂磋疆鐨勫疄鐜板湪TimerWheel
锛屽畠鏄竴绉嶅灞傛椂闂磋疆锛坔ierarchical timing wheels 锛夈€?/span>
鐪嬬湅鍏冪礌鍔犲叆鍒版椂闂磋疆鐨?/span>schedule
鏂规硶锛?/span>
/**
* Schedules a timer event for the node.
*
* @param node the entry in the cache
*/
public void schedule(@NonNull Node<K, V> node) {
Node<K, V> sentinel = findBucket(node.getVariableTime());
link(sentinel, node);
}
/**
* Determines the bucket that the timer event should be added to.
*
* @param time the time when the event fires
* @return the sentinel at the head of the bucket
*/
Node<K, V> findBucket(long time) {
long duration = time - nanos;
int length = wheel.length - 1;
for (int i = 0; i < length; i++) {
if (duration < SPANS[i + 1]) {
long ticks = (time >>> SHIFT[i]);
int index = (int) (ticks & (wheel[i].length - 1));
return wheel[i][index];
}
}
return wheel[length][0];
}
/** Adds the entry at the tail of the bucket's list. */
void link(Node<K, V> sentinel, Node<K, V> node) {
node.setPreviousInVariableOrder(sentinel.getPreviousInVariableOrder());
node.setNextInVariableOrder(sentinel);
sentinel.getPreviousInVariableOrder().setNextInVariableOrder(node);
sentinel.setPreviousInVariableOrder(node);
}
鍏朵粬
Caffeine 杩樻湁鍏朵粬鐨勪紭鍖栨€ц兘鐨勬墜娈碉紝濡備娇鐢ㄨ蒋寮曠敤鍜屽急寮曠敤銆佹秷闄や吉鍏变韩銆?/span>CompletableFuture
寮傛绛夌瓑銆?/span>
鎬荤粨
Caffeien 鏄竴涓紭绉€鐨勬湰鍦扮紦瀛橈紝閫氳繃浣跨敤 W-TinyLFU 绠楁硶锛?楂樻€ц兘鐨?readBuffer 鍜?WriteBuffer锛屾椂闂磋疆绠楁硶绛夛紝浣垮緱瀹冩嫢鏈夐珮鎬ц兘锛岄珮鍛戒腑鐜囷紙near optimal锛夛紝浣庡唴瀛樺崰鐢ㄧ瓑鐗圭偣銆?/strong>
鍙傝€冭祫鏂?/span>
TinyLFU 璁烘枃[7]
Design Of A Modern Cache[8]
Design Of A Modern Cache鈥擯art Deux[9]
Caffeine 鐨?github[10]
鍙傝€冭祫鏂?/span>
Caffeine: https://github.com/ben-manes/caffeine
[2]杩欓噷: https://albenw.github.io/posts/df42dc84/
[3]Benchmarks: https://github.com/ben-manes/caffeine/wiki/Benchmarks
[4]瀹樻柟API璇存槑鏂囨。: https://github.com/ben-manes/caffeine/wiki
[5]杩欓噷: https://github.com/ben-manes/caffeine/wiki/Guava
[6]HashedWheelTimer鏃堕棿杞師鐞嗗垎鏋? https://albenw.github.io/posts/ec8df8c/
[7]TinyLFU璁烘枃: https://arxiv.org/abs/1512.00727
[8]Design Of A Modern Cache: http://highscalability.com/blog/2016/1/25/design-of-a-modern-cache.html
[9]Design Of A Modern Cache鈥擯art Deux: http://highscalability.com/blog/2019/2/25/design-of-a-modern-cachepart-deux.html
[10]Caffeine鐨刧ithub: https://github.com/ben-manes/caffeine
鑰佸娴欐睙涓滄捣杈癸紝闈犳捣鍚冩捣锛岀洰鍓嶇粡钀ヤ竴涓皬鍝佺墝锛岃鏅€氫汉鍚冨埌鏈€鏂伴矞鐨勬捣椴溿€傛湁鍏磋叮鍙互鐐瑰嚮浜嗚В锛氥€?span class="mq-1485">銆嬸煇燄煇燄煇?/span>
寰€鏈熸帹鑽?/p>
涓嬫柟浜岀淮鐮佸叧娉ㄦ垜
鎶€鏈崏鏍?/span>锛?span class="mq-1508">鍧氭寔鍒嗕韩 缂栫▼锛岀畻娉曪紝鏋舵瀯
以上是关于娣卞叆婧愮爜鍒嗘瀽锛岀紦瀛樹箣鐜?Caffeine 涓轰綍杩欎箞鐚?的主要内容,如果未能解决你的问题,请参考以下文章