銆嬸煇燄煇燄煇?/span>Caffeine[1]鏄竴涓珮鎬ц兘锛岄珮鍛戒腑鐜囷紝浣庡唴瀛樺崰鐢紝nearoptimal鐨勬湰鍦扮紦瀛橈紝绠€鍗曟潵璇村畠鏄?GuavaCache鐨勪紭鍖栧姞寮虹増锛屾湁浜涙枃绔犳妸"/>

娣卞叆婧愮爜鍒嗘瀽锛岀紦瀛樹箣鐜?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>

娣卞叆婧愮爜鍒嗘瀽锛岀紦瀛樹箣鐜?Caffeine 涓轰綍杩欎箞鐚?
缁熻棰戠巼 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 涓紝杩戜技棰戠巼鐨勭粺璁″涓嬪浘鎵€绀猴細

娣卞叆婧愮爜鍒嗘瀽锛岀紦瀛樹箣鐜?Caffeine 涓轰綍杩欎箞鐚?

瀵逛竴涓?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>onAccesss 鏂规硶锛岃€?/span>onAccess鏂规硶閲屾湁涓€鍙ワ細

frequencySketch().increment(key);

杩欏彞灏辨槸杩藉姞璁板綍鐨勯鐜囷紝涓嬮潰鎴戜滑鐪嬬湅鍏蜂綋瀹炵幇

//FrequencySketch鐨勪竴浜涘睘鎬?/span>

//绉嶅瓙鏁?/span>
static final long[] SEED = { // A mixture of seeds from FNV-1a, CityHash, and Murmur3
    0xc3a5c85c97cb3127L0xb492b66fbe98f273L0x9ae16a3b2f90404fL0xcbf29ce484222325L};
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 涓轰綍杩欎箞鐚?


淇濇柊鏈哄埗

涓轰簡璁╃紦瀛樹繚鎸佲€滄柊椴溾€濓紝鍓旈櫎鎺夎繃寰€棰戠巼寰堥珮浣嗕箣鍚庝笉缁忓父鐨勭紦瀛橈紝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 鐨勮璁″涓嬫墍绀猴紙涓ゅ浘绛変环锛夛細

娣卞叆婧愮爜鍒嗘瀽锛岀紦瀛樹箣鐜?Caffeine 涓轰綍杩欎箞鐚?
娣卞叆婧愮爜鍒嗘瀽锛岀紦瀛樹箣鐜?Caffeine 涓轰綍杩欎箞鐚?

瀹冧富瑕佸寘鎷袱涓紦瀛樻ā鍧楋紝涓荤紦瀛樻槸 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);
      }
    }
  }
鍏堣涓€涓?Caffeine 瀵逛笂闈㈣鍒扮殑 W-TinyLFU 绛栫暐鐨勫疄鐜扮敤鍒扮殑鏁版嵁缁撴瀯锛?/span>
//鏈€澶х殑涓暟闄愬埗
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>

娣卞叆婧愮爜鍒嗘瀽锛岀紦瀛樹箣鐜?Caffeine 涓轰綍杩欎箞鐚?

鎴戜滑鐪嬬湅 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<Eextends 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<Eimplements 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, 01);
}

/**
 * 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 涓轰綍杩欎箞鐚?

鍥犱负鍦ㄦ垜鐨勫鏃堕棿杞垎鏋愮殑鏂囩珷閲屽凡缁忚浜嗘椂闂磋疆鐨勫師鐞嗗拰鏈哄埗浜嗭紝鎵€浠ユ垜灏变笉灞曞紑 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>

[1]

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

娣卞叆婧愮爜鍒嗘瀽锛岀紦瀛樹箣鐜?Caffeine 涓轰綍杩欎箞鐚?

鑰佸娴欐睙涓滄捣杈癸紝闈犳捣鍚冩捣锛岀洰鍓嶇粡钀ヤ竴涓皬鍝佺墝锛岃鏅€氫汉鍚冨埌鏈€鏂伴矞鐨勬捣椴溿€傛湁鍏磋叮鍙互鐐瑰嚮浜嗚В锛氥€?span class="mq-1485">銆嬸煇燄煇燄煇?/span>

寰€鏈熸帹鑽?/p>


涓嬫柟浜岀淮鐮佸叧娉ㄦ垜

鎶€鏈崏鏍?/span>锛?span class="mq-1508">鍧氭寔鍒嗕韩 缂栫▼锛岀畻娉曪紝鏋舵瀯

鏈嬪弸鍔╁姏涓嬶紒鐐逛釜 璧?/strong> 鍜?/strong> 鍦ㄧ湅 锛?/strong>

以上是关于娣卞叆婧愮爜鍒嗘瀽锛岀紦瀛樹箣鐜?Caffeine 涓轰綍杩欎箞鐚?的主要内容,如果未能解决你的问题,请参考以下文章

AtomicInteger 婧愮爜鍒嗘瀽

ArrayList婧愮爜鍒嗘瀽

JDK婧愮爜鍒嗘瀽鍒濇鏁寸悊

Memcached婧愮爜鍒嗘瀽涔媔tems.c

log4j2婧愮爜鍒嗘瀽-1

绮惧敖 MyBatis 婧愮爜鍒嗘瀽 - SqlSession 浼氳瘽涓?SQL 鎵ц鍏ュ彛