真正的緩存之王,Google Guava 只是弟弟
點(diǎn)擊關(guān)注公眾號(hào),Java干貨及時(shí)送達(dá)
作者:rickiyang
來(lái)源:www.cnblogs.com/rickiyang/p/11074158.html
FIFO:先進(jìn)先出,在這種淘汰算法中,先進(jìn)入緩存的會(huì)先被淘汰,會(huì)導(dǎo)致命中率很低。
LRU:最近最少使用算法,每次訪問(wèn)數(shù)據(jù)都會(huì)將其放在我們的隊(duì)尾,如果需要淘汰數(shù)據(jù),就只需要淘汰隊(duì)首即可。仍然有個(gè)問(wèn)題,如果有個(gè)數(shù)據(jù)在 1 分鐘訪問(wèn)了 1000次,再后 1 分鐘沒(méi)有訪問(wèn)這個(gè)數(shù)據(jù),但是有其他的數(shù)據(jù)訪問(wèn),就導(dǎo)致了我們這個(gè)熱點(diǎn)數(shù)據(jù)被淘汰。
LFU:最近最少頻率使用,利用額外的空間記錄每個(gè)數(shù)據(jù)的使用頻率,然后選出頻率最低進(jìn)行淘汰。這樣就避免了 LRU 不能處理時(shí)間段的問(wèn)題。
當(dāng)數(shù)據(jù)的訪問(wèn)模式不隨時(shí)間變化的時(shí)候,LFU的策略能夠帶來(lái)最佳的緩存命中率。然而LFU有兩個(gè)缺點(diǎn):
首先,它需要給每個(gè)記錄項(xiàng)維護(hù)頻率信息,每次訪問(wèn)都需要更新,這是個(gè)巨大的開(kāi)銷;
其次,如果數(shù)據(jù)訪問(wèn)模式隨時(shí)間有變,LFU的頻率信息無(wú)法隨之變化,因此早先頻繁訪問(wèn)的記錄可能會(huì)占據(jù)緩存,而后期訪問(wèn)較多的記錄則無(wú)法被命中。
因此,大多數(shù)的緩存設(shè)計(jì)都是基于LRU或者其變種來(lái)進(jìn)行的。相比之下,LRU并不需要維護(hù)昂貴的緩存記錄元信息,同時(shí)也能夠反應(yīng)隨時(shí)間變化的數(shù)據(jù)訪問(wèn)模式。然而,在許多負(fù)載之下,LRU依然需要更多的空間才能做到跟LFU一致的緩存命中率。因此,一個(gè)“現(xiàn)代”的緩存,應(yīng)當(dāng)能夠綜合兩者的長(zhǎng)處。

<dependency><groupId>com.github.ben-manes.caffeinegroupId><artifactId>caffeineartifactId><version>2.6.2version>dependency>
/** * 手動(dòng)加載 * @param key * @return */public Object manulOperator(String key) { Cache<String, Object> cache = Caffeine.newBuilder() .expireAfterWrite(1, TimeUnit.SECONDS) .expireAfterAccess(1, TimeUnit.SECONDS) .maximumSize(10) .build(); //如果一個(gè)key不存在,那么會(huì)進(jìn)入指定的函數(shù)生成value Object value = cache.get(key, t -> setValue(key).apply(key));????cache.put("hello",value); //判斷是否存在如果不存返回null Object ifPresent = cache.getIfPresent(key); //移除一個(gè)key cache.invalidate(key); return value;}public Function setValue(String key){ return t -> key + "value";} /** * 同步加載 * @param key * @return */public Object syncOperator(String key){ LoadingCache<String, Object> cache = Caffeine.newBuilder() .maximumSize(100) .expireAfterWrite(1, TimeUnit.MINUTES) .build(k -> setValue(key).apply(key)); return cache.get(key);}public Function<String, Object> setValue(String key){ return t -> key + "value";}/** * 異步加載 * * @param key * @return */public Object asyncOperator(String key){ AsyncLoadingCache<String, Object> cache = Caffeine.newBuilder() .maximumSize(100) .expireAfterWrite(1, TimeUnit.MINUTES) .buildAsync(k -> setAsyncValue(key).get());????return?cache.get(key);}public CompletableFuture<Object> setAsyncValue(String key){ return CompletableFuture.supplyAsync(() -> { return key + "value"; });}// 根據(jù)緩存的計(jì)數(shù)進(jìn)行驅(qū)逐LoadingCache cache = Caffeine.newBuilder() .maximumSize(10000) .build(key -> function(key));// 根據(jù)緩存的權(quán)重來(lái)進(jìn)行驅(qū)逐(權(quán)重只是用于確定緩存大小,不會(huì)用于決定該緩存是否被驅(qū)逐)LoadingCache cache1 = Caffeine.newBuilder() .maximumWeight(10000) .weigher(key -> function1(key)) .build(key -> function(key)); // 基于固定的到期策略進(jìn)行退出LoadingCache cache = Caffeine.newBuilder() .expireAfterAccess(5, TimeUnit.MINUTES) .build(key -> function(key));LoadingCache cache1 = Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .build(key -> function(key));// 基于不同的到期策略進(jìn)行退出LoadingCache cache2 = Caffeine.newBuilder() .expireAfter(new Expiry() { @Override public long expireAfterCreate(String key, Object value, long currentTime) { return TimeUnit.SECONDS.toNanos(seconds); }????????@Override public long expireAfterUpdate(@Nonnull String s, @Nonnull Object o, long l, long l1) { return 0; }????????@Override public long expireAfterRead(@Nonnull String s, @Nonnull Object o, long l, long l1) { return 0; } }).build(key -> function(key)); 
// 當(dāng)key和value都沒(méi)有引用時(shí)驅(qū)逐緩存LoadingCache cache = Caffeine.newBuilder() .weakKeys() .weakValues() .build(key -> function(key));//?當(dāng)垃圾收集器需要釋放內(nèi)存時(shí)驅(qū)逐LoadingCache cache1 = Caffeine.newBuilder() .softValues() .build(key -> function(key)); Cache<String, Object> cache = Caffeine.newBuilder().removalListener((String key, Object value, RemovalCause cause) ->System.out.printf("Key %s was removed (%s)%n", key, cause)).build();
LoadingCache<String, Object> cache2 = Caffeine.newBuilder().writer(new CacheWriter<String, Object>() {public void write(String key, Object value) {// 寫(xiě)入到外部存儲(chǔ)}public void delete(String key, Object value, RemovalCause cause) {// 刪除外部存儲(chǔ)}}).build(key -> function(key));
Cache<String, Object> cache = Caffeine.newBuilder().maximumSize(10_000).recordStats().build();
hitRate(): 返回緩存命中率evictionCount():?緩存回收數(shù)量averageLoadPenalty():?加載新值的平均時(shí)間<dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-cacheartifactId>dependency><dependency><groupId>com.github.ben-manes.caffeinegroupId><artifactId>caffeineartifactId><version>2.6.2version>dependency>
?class?SingleDatabaseApplication?{ public static void main(String[] args) { SpringApplication.run(SingleDatabaseApplication.class, args); }}spring.cache.cache-names=cache1spring.cache.caffeine.spec=initialCapacity=50,maximumSize=500,expireAfterWrite=10s
spring:cache:type: caffeine:userCachecaffeine:spec: maximumSize=1024,refreshAfterWrite=60s
import com.github.benmanes.caffeine.cache.CacheLoader;import org.springframework.context.annotation.Bean;import?org.springframework.context.annotation.Configuration;/** * @author: rickiyang * @date: 2019/6/15 * @description: */?class?CacheConfig?{ /** * 相當(dāng)于在構(gòu)建LoadingCache對(duì)象的時(shí)候 build()方法中指定過(guò)期之后的加載策略方法 * 必須要指定這個(gè)Bean,refreshAfterWrite=60s屬性才生效 * @return */ public CacheLoader<String, Object> cacheLoader() { CacheLoader<String, Object> cacheLoader = new CacheLoader<String, Object>() { public Object load(String key) throws Exception { return null; } // 重寫(xiě)這個(gè)方法將oldValue值返回回去,進(jìn)而刷新緩存 @Override public Object reload(String key, Object oldValue) throws Exception { return oldValue; } }; return cacheLoader; }}initialCapacity=[integer]:?初始的緩存空間大小maximumSize=[long]:?緩存的最大條數(shù)maximumWeight=[long]:?緩存的最大權(quán)重expireAfterAccess=[duration]:?最后一次寫(xiě)入或訪問(wèn)后經(jīng)過(guò)固定時(shí)間過(guò)期expireAfterWrite=[duration]:?最后一次寫(xiě)入后經(jīng)過(guò)固定時(shí)間過(guò)期refreshAfterWrite=[duration]:?創(chuàng)建緩存或者最近一次更新緩存后經(jīng)過(guò)固定的時(shí)間間隔,刷新緩存weakKeys:?打開(kāi)key的弱引用weakValues:打開(kāi)value的弱引用softValues:打開(kāi)value的軟引用recordStats:開(kāi)發(fā)統(tǒng)計(jì)功能注意:expireAfterWrite和expireAfterAccess同時(shí)存在時(shí),以expireAfterWrite為準(zhǔn)。maximumSize和maximumWeight不可以同時(shí)使用weakValues和softValues不可以同時(shí)使用package?com.rickiyang.learn.cache;import com.github.benmanes.caffeine.cache.CacheLoader;import com.github.benmanes.caffeine.cache.Caffeine;import org.apache.commons.compress.utils.Lists;import org.springframework.cache.CacheManager;import org.springframework.cache.caffeine.CaffeineCache;import org.springframework.cache.support.SimpleCacheManager;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import?org.springframework.context.annotation.Primary;import java.util.ArrayList;import java.util.List;import?java.util.concurrent.TimeUnit;/** * @author: rickiyang * @date: 2019/6/15 * @description: */ class CacheConfig { /** * 創(chuàng)建基于Caffeine的Cache Manager * 初始化一些key存入 * @return */ public CacheManager caffeineCacheManager() { SimpleCacheManager cacheManager = new SimpleCacheManager(); ArrayList caches = Lists.newArrayList(); List list = setCacheBean(); for(CacheBean cacheBean : list){ caches.add(new CaffeineCache(cacheBean.getKey(), Caffeine.newBuilder().recordStats() .expireAfterWrite(cacheBean.getTtl(), TimeUnit.SECONDS) .maximumSize(cacheBean.getMaximumSize()) .build())); } cacheManager.setCaches(caches); return cacheManager;????} /** * 初始化一些緩存的 key * @return */ private List setCacheBean() { List list = Lists.newArrayList(); CacheBean userCache = new CacheBean(); userCache.setKey("userCache"); userCache.setTtl(60);????????userCache.setMaximumSize(10000); CacheBean deptCache = new CacheBean(); deptCache.setKey("userCache"); deptCache.setTtl(60);????????deptCache.setMaximumSize(10000); list.add(userCache);????????list.add(deptCache); return list;????} class CacheBean { private String key; private long ttl;????????private?long?maximumSize; public String getKey() { return key;????????} public void setKey(String key) { this.key = key;????????} public long getTtl() { return ttl;????????} public void setTtl(long ttl) { this.ttl = ttl;????????} public long getMaximumSize() { return maximumSize;????????} public void setMaximumSize(long maximumSize) { this.maximumSize = maximumSize; }????}} @Cacheable 觸發(fā)緩存入口(這里一般放在創(chuàng)建和獲取的方法上,@Cacheable注解會(huì)先查詢是否已經(jīng)有緩存,有會(huì)使用緩存,沒(méi)有則會(huì)執(zhí)行方法并緩存)
@CacheEvict 觸發(fā)緩存的eviction(用于刪除的方法上)
@CachePut 更新緩存且不影響方法執(zhí)行(用于修改的方法上,該注解下的方法始終會(huì)被執(zhí)行)
@Caching 將多個(gè)緩存組合在一個(gè)方法上(該注解可以允許一個(gè)方法同時(shí)設(shè)置多個(gè)注解)
@CacheConfig 在類級(jí)別設(shè)置一些緩存相關(guān)的共同配置(與其它緩存配合使用)
@Cacheable:它的注解的方法是否被執(zhí)行取決于Cacheable中的條件,方法很多時(shí)候都可能不被執(zhí)行。
@CachePut:這個(gè)注解不會(huì)影響方法的執(zhí)行,也就是說(shuō)無(wú)論它配置的條件是什么,方法都會(huì)被執(zhí)行,更多的時(shí)候是被用到修改上。
public??Cacheable?{ /** * 要使用的cache的名字 */ ("cacheNames") String[] value() default {}; /** * 同value(),決定要使用那個(gè)/些緩存 */ ("value") String[] cacheNames() default {}; /** * 使用SpEL表達(dá)式來(lái)設(shè)定緩存的key,如果不設(shè)置默認(rèn)方法上所有參數(shù)都會(huì)作為key的一部分 */ String key() default ""; /** * 用來(lái)生成key,與key()不可以共用 */????String?keyGenerator()?default?""; /** * 設(shè)定要使用的cacheManager,必須先設(shè)置好cacheManager的bean,這是使用該bean的名字 */????String?cacheManager()?default?""; /** * 使用cacheResolver來(lái)設(shè)定使用的緩存,用法同cacheManager,但是與cacheManager不可以同時(shí)使用 */ String cacheResolver() default ""; /** * 使用SpEL表達(dá)式設(shè)定出發(fā)緩存的條件,在方法執(zhí)行前生效 */ String condition() default ""; /** * 使用SpEL設(shè)置出發(fā)緩存的條件,這里是方法執(zhí)行完生效,所以條件中可以有方法執(zhí)行后的value */ String unless() default ""; /** * 用于同步的,在緩存失效(過(guò)期不存在等各種原因)的時(shí)候,如果多個(gè)線程同時(shí)訪問(wèn)被標(biāo)注的方法 * 則只允許一個(gè)線程通過(guò)去執(zhí)行方法 */????boolean?sync()?default?false;}package?com.rickiyang.learn.cache;import com.rickiyang.learn.entity.User;import org.springframework.cache.annotation.CacheEvict;import org.springframework.cache.annotation.CachePut;import org.springframework.cache.annotation.Cacheable;import org.springframework.stereotype.Service;/** * @author: rickiyang * @date: 2019/6/15 * @description: 本地cache */?class?UserCacheService?{ /** * 查找 * 先查緩存,如果查不到,會(huì)查數(shù)據(jù)庫(kù)并存入緩存 * @param id */ (value = "userCache", key = "#id", sync = true) public void getUser(long id){ //查找數(shù)據(jù)庫(kù) } /** * 更新/保存 * @param user */ @CachePut(value = "userCache", key = "#user.id") public void saveUser(User user){ //todo 保存數(shù)據(jù)庫(kù)????} /** * 刪除 * @param user */ @CacheEvict(value = "userCache",key = "#user.id") public void delUser(User user){ //todo 保存數(shù)據(jù)庫(kù) }}
@Cacheable(key = "targetClass + methodName +#p0")

往 期 推 薦
1、Log4j2維護(hù)者吐槽沒(méi)工資還要挨罵,GO安全負(fù)責(zé)人建議開(kāi)源作者向公司收費(fèi) 2、太難了!讓程序員崩潰的8個(gè)瞬間 3、2021年程序員們都在用的神級(jí)數(shù)據(jù)庫(kù) 4、Windows重要功能被閹割,全球用戶怒噴數(shù)月后微軟終于悔改 5、牛逼!國(guó)產(chǎn)開(kāi)源的遠(yuǎn)程桌面火了,只有9MB 支持自建中繼器! 6、摔到老三的 Java,未來(lái)在哪? 7、真香!用 IDEA 神器看源碼,效率真高! 點(diǎn)分享
點(diǎn)收藏
點(diǎn)點(diǎn)贊
點(diǎn)在看





