玩轉(zhuǎn)EhCache之最簡(jiǎn)單的緩存框架

一谷誓、簡(jiǎn)介

Ehcache是一個(gè)用Java實(shí)現(xiàn)的使用簡(jiǎn)單谓谦,高速,實(shí)現(xiàn)線程安全的緩存管理類庫(kù),ehcache提供了用內(nèi)存畅姊,磁盤文件存儲(chǔ)咒钟,以及分布式存儲(chǔ)方式等多種靈活的cache管理方案。同時(shí)ehcache作為開放源代碼項(xiàng)目若未,采用限制比較寬松的Apache License V2.0作為授權(quán)方式朱嘴,被廣泛地用于Hibernate, Spring,Cocoon等其他開源系統(tǒng)粗合。Ehcache 從 Hibernate 發(fā)展而來(lái)萍嬉,逐漸涵蓋了 Cahce 界的全部功能,是目前發(fā)展勢(shì)頭最好的一個(gè)項(xiàng)目。具有快速,簡(jiǎn)單,低消耗隙疚,依賴性小帚湘,擴(kuò)展性強(qiáng),支持對(duì)象或序列化緩存,支持緩存或元素的失效甚淡,提供 LRU大诸、LFU 和 FIFO 緩存策略,支持內(nèi)存緩存和磁盤緩存贯卦,分布式緩存機(jī)制等等特點(diǎn)资柔。

備注:為了方便大家了最新版本的Ehcache,本文中1-6節(jié)采用的最新的Ehcache3.0的特性和使用介紹撵割,從第7節(jié)開始采用的是Ehcache2.10.2版本來(lái)與Spring相結(jié)合來(lái)做案例介紹贿堰,包括后面的源碼分析也將采用這個(gè)版本

二、主要特性

快速啡彬;
簡(jiǎn)單羹与;
多種緩存策略;
緩存數(shù)據(jù)有兩級(jí):內(nèi)存和磁盤庶灿,因此無(wú)需擔(dān)心容量問題纵搁;
緩存數(shù)據(jù)會(huì)在虛擬機(jī)重啟的過程中寫入磁盤;
可以通過 RMI往踢、可插入 API 等方式進(jìn)行分布式緩存腾誉;
具有緩存和緩存管理器的偵聽接口;
支持多緩存管理器實(shí)例峻呕,以及一個(gè)實(shí)例的多個(gè)緩存區(qū)域利职;
提供 Hibernate 的緩存實(shí)現(xiàn);

三瘦癌、Ehcache的架構(gòu)設(shè)計(jì)圖


說(shuō)明
CacheManager:是緩存管理器猪贪,可以通過單例或者多例的方式創(chuàng)建,也是Ehcache的入口類讯私。
Cache:每個(gè)CacheManager可以管理多個(gè)Cache热押,每個(gè)Cache可以采用hash的方式管理多個(gè)Element西傀。
Element:用于存放真正緩存內(nèi)容的。

結(jié)構(gòu)圖如下所示:

四楞黄、Ehcache的緩存數(shù)據(jù)淘汰策略

FIFO:先進(jìn)先出
LFU:最少被使用,緩存的元素有一個(gè)hit屬性抡驼,hit值最小的將會(huì)被清出緩存鬼廓。
LRU:最近最少使用,緩存的元素有一個(gè)時(shí)間戳致盟,當(dāng)緩存容量滿了碎税,而又需要騰出地方來(lái)緩存新的元素的時(shí)候,那么現(xiàn)有緩存元素中時(shí)間戳離當(dāng)前時(shí)間最遠(yuǎn)的元素將被清出緩存馏锡。

五雷蹂、Ehcache的緩存數(shù)據(jù)過期策略

Ehcache采用的是懶淘汰機(jī)制,每次往緩存放入數(shù)據(jù)的時(shí)候杯道,都會(huì)存一個(gè)時(shí)間匪煌,在讀取的時(shí)候要和設(shè)置的時(shí)間做TTL比較來(lái)判斷是否過期。

六党巾、Ehcache緩存的使用介紹

6.1萎庭、目前最新的Ehcache是3.0版本,我們也就使用3.0版本來(lái)介紹它的使用介紹齿拂,看如下代碼:

Paste_Image.png

注:這段代碼介紹了Ehcache3.0的緩存使用生命周期的一個(gè)過程驳规。
1、靜態(tài)方法CacheManagerBuilder.newCacheManagerBuilder將返回一個(gè)新的org.ehcache.config.builders.CacheManagerBuilder的實(shí)例署海。
2吗购、當(dāng)我們要構(gòu)建一個(gè)緩存管理器的時(shí)候,使用CacheManagerBuilder來(lái)創(chuàng)建一個(gè)預(yù)配置(pre-configured)緩存砸狞。

  • 第一個(gè)參數(shù)是一個(gè)別名捻勉,用于Cache和Cachemanager進(jìn)行配合。
  • 第二個(gè)參數(shù)是org.ehcache.config.CacheConfiguration主要用來(lái)配置Cache刀森。我們使用org.ehcache.config.builders.CacheConfigurationBuilder的靜態(tài)方法newCacheConfigurationBuilder來(lái)創(chuàng)建一個(gè)默認(rèn)配置實(shí)例贯底。

3、最后調(diào)用.build方法返回一個(gè)完整的實(shí)例撒强,當(dāng)然我們也能使用CacheManager來(lái)初始化禽捆。
4、在你開始使用CacheManager的時(shí)候飘哨,需要使用init()方法進(jìn)行初始化胚想。
5、我們能取回在第二步中設(shè)定的pre-configured別名芽隆,我們對(duì)于key和要傳遞的值類型浊服,要求是類型安全的统屈,否則將拋出ClassCastException異常。
6牙躺、可以根據(jù)需求愁憔,通過CacheManager創(chuàng)建出新的Cache。實(shí)例化和完整實(shí)例化的Cache將通過CacheManager.getCache API返回孽拷。
7吨掌、使用put方法存儲(chǔ)數(shù)據(jù)。
8脓恕、使用get方法獲取數(shù)據(jù)膜宋。
9、我們可以通過CacheManager.removeCache方法來(lái)獲取Cache炼幔,但是Cache取出來(lái)以后CacheManager將會(huì)刪除自身保存的Cache實(shí)例秋茫。
10、close方法將釋放CacheManager所管理的緩存資源乃秀。

6.2肛著、關(guān)于磁盤持久化

Paste_Image.png

注:如果您想使用持久化機(jī)制,就需要提供一個(gè)磁盤存儲(chǔ)的位置給CacheManagerBuilder.persistence這個(gè)方法跺讯,另外在使用的過程中策泣,你還需要定義一個(gè)磁盤使用的資源池。

上面的例子其實(shí)是分配了非常少的磁盤存儲(chǔ)量抬吟,不過我們需要注意的是由于存儲(chǔ)在磁盤上我們需要做序列化和反序列化萨咕,以及讀和寫的操作。它的速度肯定比內(nèi)存要慢的多火本。

6.3危队、通過xml配置文件創(chuàng)建CacheManager

Paste_Image.png

注:
1、描述緩存的別名钙畔。
2茫陆、foo的key的類型指定為String類型,而value并沒有指定類型擎析,默認(rèn)就是Object類型簿盅。
3、可以在堆中為foo創(chuàng)建2000個(gè)實(shí)體揍魂。
4桨醋、在開始淘汰過期緩存項(xiàng)之前,可以分配多達(dá)500M的堆內(nèi)存现斋。
5喜最、cache-template可以實(shí)現(xiàn)一個(gè)配置抽象,以便在未來(lái)可以進(jìn)行擴(kuò)展庄蹋。
6瞬内、bar使用了cache-template模板myDefaults迷雪,并且覆蓋了key-type類型,myDefaults的key-type是Long類型虫蝶,覆蓋后成了Number類型章咧。

使用以下代碼創(chuàng)建CacheManager:


Paste_Image.png

七、UserManagerCache介紹

** 7.1 什么是UserManagerCache能真,它能做什么赁严?**
UserManagerCache這是在Ehcache3.0中引入的新的概念,它將直接創(chuàng)建緩存而不需要使用CacheManager來(lái)進(jìn)行管理舟陆。所以這也就是UserManagerCache名稱的由來(lái)误澳。
由于沒有CacheManager的管理耻矮,用戶就必須要手動(dòng)配置所需要的服務(wù)秦躯,當(dāng)然如果你發(fā)現(xiàn)要使用大量的服務(wù),那么CacheManager則是更好的選擇裆装。

** 7.2 使用示例**
1踱承、基本示例

UserManagedCache<Long, String> userManagedCache =
    UserManagedCacheBuilder.newUserManagedCacheBuilder(Long.class, String.class)
        .build(false); 
userManagedCache.init(); 

userManagedCache.put(1L, "da one!"); 

userManagedCache.close(); 

2、持久化示例

LocalPersistenceService persistenceService = new DefaultLocalPersistenceService(new DefaultPersistenceConfiguration(new File(getStoragePath(), "myUserData"))); 

PersistentUserManagedCache<Long, String> cache = UserManagedCacheBuilder.newUserManagedCacheBuilder(Long.class, String.class)
    .with(new UserManagedPersistenceContext<Long, String>("cache-name", persistenceService)) 
    .withResourcePools(ResourcePoolsBuilder.newResourcePoolsBuilder()
        .heap(10L, EntryUnit.ENTRIES)
        .disk(10L, MemoryUnit.MB, true)) 
    .build(true);

// Work with the cache
cache.put(42L, "The Answer!");
assertThat(cache.get(42L), is("The Answer!"));

cache.close(); 
cache.destroy(); 

3哨免、讀寫緩存示例

UserManagedCache<Long, String> cache = UserManagedCacheBuilder.newUserManagedCacheBuilder(Long.class, String.class)
    .withLoaderWriter(new SampleLoaderWriter<Long, String>()) 
    .build(true);

// Work with the cache
cache.put(42L, "The Answer!");
assertThat(cache.get(42L), is("The Answer!"));

cache.close();

注:
如果你希望頻繁的讀和寫緩存茎活,則可以使用CacheLoaderWriter。

4琢唾、緩存淘汰策略示例

UserManagedCache<Long, String> cache = UserManagedCacheBuilder.newUserManagedCacheBuilder(Long.class, String.class)
    .withEvictionAdvisor(new OddKeysEvictionAdvisor<Long, String>()) 
    .withResourcePools(ResourcePoolsBuilder.newResourcePoolsBuilder()
        .heap(2L, EntryUnit.ENTRIES)) 
    .build(true);

// Work with the cache
cache.put(42L, "The Answer!");
cache.put(41L, "The wrong Answer!");
cache.put(39L, "The other wrong Answer!");

cache.close(); 

注:
如果你想使用緩存淘汰算法來(lái)淘汰數(shù)據(jù)载荔,則要使用EvictionAdvisor這個(gè)類。

5采桃、按字節(jié)設(shè)定的緩存示例

UserManagedCache<Long, String> cache = UserManagedCacheBuilder.newUserManagedCacheBuilder(Long.class, String.class)
    .withSizeOfMaxObjectSize(500, MemoryUnit.B)
    .withSizeOfMaxObjectGraph(1000) 
    .withResourcePools(ResourcePoolsBuilder.newResourcePoolsBuilder()
        .heap(3, MemoryUnit.MB)) 
    .build(true);

cache.put(1L, "Put");
cache.put(1L, "Update");

assertThat(cache.get(1L), is("Update"));

cache.close();

注:
withSizeOfMaxObjectGraph這個(gè)主要是調(diào)整可以設(shè)置多少字節(jié)對(duì)象懒熙。
.heap方法主要是設(shè)置每個(gè)對(duì)象最大可以設(shè)置多大。

八普办、緩存的使用模式

使用緩存時(shí)有幾種常見的訪問模式:
1工扎、預(yù)留緩存(Cache-Aside)
應(yīng)用程序在訪問數(shù)據(jù)庫(kù)之前必須要先訪問緩存,如果緩存中包含了該數(shù)據(jù)則直接返回衔蹲,不用再經(jīng)過數(shù)據(jù)庫(kù)肢娘,否則應(yīng)用程序必須要從先數(shù)據(jù)庫(kù)中取回?cái)?shù)據(jù),存儲(chǔ)在緩存中并且將數(shù)據(jù)返回舆驶,當(dāng)有數(shù)據(jù)要寫入的時(shí)候橱健,緩存內(nèi)容必須要和數(shù)據(jù)庫(kù)內(nèi)容保持一致。

示例如下代碼分別對(duì)應(yīng)讀和寫:

v = cache.get(k)
if(v == null) {
    v = sor.get(k)
    cache.put(k, v)
}

v = newV
sor.put(k, v)
cache.put(k, v)

這種方式是將數(shù)據(jù)庫(kù)與緩存通過客戶端應(yīng)用程序主動(dòng)管理來(lái)進(jìn)行同步沙廉,這不是很好的使用方式畴博。

2、Read-Through模式
相比上面的由客戶端應(yīng)用程序來(lái)管理數(shù)據(jù)庫(kù)和緩存同步的方式蓝仲,這種模式緩存會(huì)配有一個(gè)緩存中間件俱病,該中間件來(lái)負(fù)責(zé)數(shù)據(jù)庫(kù)數(shù)據(jù)和緩存之間的同步問題官疲。當(dāng)我們應(yīng)用要獲取緩存數(shù)據(jù)時(shí),這個(gè)緩存中間件要確認(rèn)緩存中是否有該數(shù)據(jù)亮隙,如果沒有途凫,從數(shù)據(jù)庫(kù)加載,然后放入緩存溢吻,下次以后再訪問就可以直接從緩存中獲得维费。

3、Write-Through模式
這種模式就是緩存能夠感知數(shù)據(jù)的變化促王。
也就是說(shuō)在更新數(shù)據(jù)庫(kù)的同時(shí)也要更新緩存犀盟,該模式其實(shí)也是弱一致性,當(dāng)數(shù)據(jù)庫(kù)更新數(shù)據(jù)失敗的時(shí)候蝇狼,緩存不能繼續(xù)更新數(shù)據(jù)阅畴,要保持?jǐn)?shù)據(jù)庫(kù)和緩存的最終一致性。

4迅耘、Write-behind模式
該模式是以緩存為優(yōu)先贱枣,將緩存更新的數(shù)據(jù)存放隊(duì)列中,然后數(shù)據(jù)庫(kù)定時(shí)批量從隊(duì)列中取出數(shù)據(jù)更新數(shù)據(jù)庫(kù)颤专。

九纽哥、Spring3.2+Ehcache2.10.2的使用

為了使例子更加簡(jiǎn)單易懂,我沒有直接去連接數(shù)據(jù)庫(kù)而模擬了一些操作目的主要是演示Spring結(jié)合Ehcache的使用栖秕。
JavaBean代碼如下:

public class User {  
    public Integer id;  
    public String name;  
    public String password;  
      
    // 這個(gè)需要春塌,不然在實(shí)體綁定的時(shí)候出錯(cuò)  
    public User(){}  
      
    public User(Integer id, String name, String password) {  
        super();  
        this.id = id;  
        this.name = name;  
        this.password = password;  
    }  
      
    public Integer getId() {  
        return id;  
    }  
    public void setId(Integer id) {  
        this.id = id;  
    }  
    public String getName() {  
        return name;  
    }  
    public void setName(String name) {  
        this.name = name;  
    }  
    public String getPassword() {  
        return password;  
    }  
    public void setPassword(String password) {  
        this.password = password;  
    }  
  
    @Override  
    public String toString() {  
        return "User [id=" + id + ", name=" + name + ", password=" + password  
                + "]";  
    }  
}  

UserDAO代碼如下:

@Repository("userDao")  
public class UserDao {  
    List<User> userList = initUsers();  
      
    public User findById(Integer id) { 
        for(User user : userList){  
            if(user.getId().equals(id)){  
                 return user;
            }  
        }  
        return null;  
    }  
      
    public void removeById(Integer id) { 
        User delUser = null;
        for(User user : userList){  
            if(user.getId().equals(id)){  
                  delUser = user;
            }  
        }  
        users.remove(delUser);  
    }  
      
    public void addUser(User user){  
        users.add(user);  
    }  
      
    public void updateUser(User user){  
        addUser(user);  
    }  
 
    private List<User> initUsers(){  
        List<User> users = new ArrayList<User>();  
        User u1 = new User(1,"張三","123");  
        User u2 = new User(2,"李四","124");  
        User u3 = new User(3,"王五","125");  
        users.add(u1);  
        users.add(u2);  
        users.add(u3);  
        return users;  
    }  
}  

UserService代碼如下:

@Service("userService")  
public class UserService {  
      
    @Autowired  
    private UserDao userDao;  
      
    // 查詢所有數(shù)據(jù),不需要key
    @Cacheable(value = "serviceCache")  
    public List<User> getAll(){  
        printInfo("getAll");  
        return userDao.users;  
    }  
    //根據(jù)ID來(lái)獲取數(shù)據(jù)簇捍,ID可能是主鍵也可能是唯一鍵
    @Cacheable(value = "serviceCache", key="#id")  
    public User findById(Integer id){  
        printInfo(id);  
        return userDao.findById(id);  
    }  
    //通過ID進(jìn)行刪除 
    @CacheEvict(value = "serviceCache", key="#id")  
    public void removeById(Integer id){  
        userDao.removeById(id);  
    }  
    
   //添加數(shù)據(jù)
    public void addUser(User user){  
        if(user != null && user.getId() != null){  
            userDao.addUser(user);  
        }  
    }  
    // key 支持條件只壳,包括 屬性condition ,可以 id < 20 等等
    @CacheEvict(value="serviceCache", key="#u.id")  
    public void updateUser(User u){  
        removeById(u.getId());  
        userDao.updateUser(u);  
    }  

   // allEntries 表示調(diào)用之后垦写,清空緩存吕世,默認(rèn)false,  
    // 還有個(gè)beforeInvocation 屬性,表示先清空緩存梯投,再進(jìn)行查詢  
    @CacheEvict(value="serviceCache",allEntries=true)  
    public void removeAll(){  
        System.out.println("清除所有緩存");  
    } 

    private void printInfo(Object str){  
        System.out.println("非緩存查詢----------findById"+str);  
    } 
}  

ehcache配置文件內(nèi)容如下

<cache name="serviceCache"
    eternal="false"  
    maxElementsInMemory="100" 
    overflowToDisk="false" 
    diskPersistent="false"  
    timeToIdleSeconds="0" 
    timeToLiveSeconds="300"  
    memoryStoreEvictionPolicy="LRU" /> 
</ehcache> 

Spring配置文件內(nèi)容如下:

    <bean id="cacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">  
        <property name="configLocation"  value="classpath:com/config/ehcache.xml"/> 
    </bean> 
    
    <!-- 支持緩存注解 -->
    <cache:annotation-driven cache-manager="cacheManager" />
    
    <!-- 默認(rèn)是cacheManager -->
    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">  
        <property name="cacheManager"  ref="cacheManagerFactory"/>  
    </bean>  

十命辖、Spring3.2+Ehcache2.10.2分布式緩存的使用

10.1 Ehcache集群簡(jiǎn)介
從Ehcache1.2版本開始,Ehcache就可以使用分布式的緩存了分蓖,從 1.7版本開始尔艇,開始支持共五種集群方案,分別是:

  • Terracotta
  • RMI
  • JMS
  • JGroups
  • EhCache Server

其中有三種上最為常用集群方式么鹤,分別是 RMI终娃、JGroups 以及 EhCache Server 。
其實(shí)我們?cè)谑褂肊hcache分布式緩存的過程中蒸甜,主要是以緩存插件的方式使用棠耕,如果我們想根據(jù)自己的需要使用分布式緩存那就需要自己開發(fā)來(lái)定制化余佛,在后面我們會(huì)發(fā)現(xiàn)其實(shí)Ehcache提供的分布式緩存并不是非常好用,有不少問題存在窍荧,所以對(duì)緩存數(shù)據(jù)一致性比較高的情況下辉巡,使用集中式緩存更合適,比如Redis蕊退、Memcached等郊楣。

10.2 Ehcache集群的基本概念
1、成員發(fā)現(xiàn)(Peer Discovery)
Ehcache集群概念中有一個(gè)cache組瓤荔,每個(gè)cache都是另一個(gè)cache的peer净蚤,并不像Redis或者其他分布式組件一樣有一個(gè)主的存在,Ehcache并沒有主Cache输硝,可是那如何知道集群中的其他緩存都有誰(shuí)呢今瀑?這個(gè)就是成員發(fā)現(xiàn)。

Ehcache提供了二種機(jī)制來(lái)實(shí)現(xiàn)成員發(fā)現(xiàn)功能腔丧,分別是手動(dòng)發(fā)現(xiàn)和自動(dòng)發(fā)現(xiàn)放椰。

  • 手動(dòng)發(fā)現(xiàn)
在Ehcache的配置文件中指定cacheManagerPeerProviderFactory元素的class屬性為
net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory作烟。這就需要自己去配置IP地址和端口號(hào)愉粤。
  • 自動(dòng)發(fā)現(xiàn)

自動(dòng)的發(fā)現(xiàn)方式用TCP廣播機(jī)制來(lái)確定和維持一個(gè)廣播組。它只需要一個(gè)簡(jiǎn)單的配置可以自動(dòng)的在組中添加和移除成員拿撩。在集群中也不需要什么優(yōu)化服務(wù)器的知識(shí)衣厘,這是默認(rèn)推薦的。

成員每秒向群組發(fā)送一個(gè)“心跳”压恒。如果一個(gè)成員 5秒種都沒有發(fā)出信號(hào)它將被群組移除影暴。如果一個(gè)新的成員發(fā)送了一個(gè)“心跳”它將被添加進(jìn)群組。

任何一個(gè)用這個(gè)配置安裝了復(fù)制功能的cache都將被其他的成員發(fā)現(xiàn)并標(biāo)識(shí)為可用狀態(tài)探赫。

要設(shè)置自動(dòng)的成員發(fā)現(xiàn)型宙,需要指定ehcache配置文件中

cacheManagerPeerProviderFactory元素的properties屬性,就像下面這樣:
peerDiscovery=automatic
multicastGroupAddress=multicast address | multicast host name
multicastGroupPort=port
timeToLive=0-255 (timeToLive屬性詳見常見問題部分的描述)

10.3 結(jié)合Spring看示例
先看Spring配置文件:

<!-- spring cache 配置 -->  
<!-- 啟用緩存注解功能 -->  
<cache:annotation-driven cache-manager="cacheManager"/>  
  
<!-- cacheManager工廠類伦吠,指定ehcache.xml的位置 -->  
<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"  
      p:configLocation="classpath:ehcache/ehcache.xml"/>  
  
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"  
      p:cacheManager-ref="ehcache"/>  
  
<cache:annotation-driven />  

Ehcache配置文件內(nèi)容如下:

<?xml version="1.0" encoding="UTF-8"?>  
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">  
    
    <!--EHCache分布式緩存集群環(huán)境配置-->  
    <!--rmi手動(dòng)配置-->  
    <cacheManagerPeerProviderFactory class= "net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"  
              properties="peerDiscovery=manual,rmiUrls=//localhost:40000/user"/>  
  
    <cacheManagerPeerListenerFactory  
            class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"  
            properties="hostName=localhost,port=40001, socketTimeoutMillis=120000"/>  
    <defaultCache  
            maxElementsInMemory="10000"  
            eternal="false"  
            timeToIdleSeconds="120"  
            timeToLiveSeconds="120"  
            overflowToDisk="true"  
            diskSpoolBufferSizeMB="30"  
            maxElementsOnDisk="10000000"  
            diskPersistent="false"  
            diskExpiryThreadIntervalSeconds="120"  
            memoryStoreEvictionPolicy="LRU">  
        <cacheEventListenerFactory  
                class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"/>  
    </defaultCache>  
    <cache name="user"  
           maxElementsInMemory="1000"  
           eternal="false"  
           timeToIdleSeconds="100000"  
           timeToLiveSeconds="100000"  
           overflowToDisk="false">  
        <cacheEventListenerFactory  
                class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"/>  
    </cache>  
</ehcache>  

以上配置其實(shí)就是使用RMI方式在集群的環(huán)境進(jìn)行緩存數(shù)據(jù)的復(fù)制妆兑。

十一、Ehcache的使用場(chǎng)景

11.1毛仪、Ehcache使用的注意點(diǎn)

1搁嗓、比較少的更新數(shù)據(jù)表的情況
2、對(duì)并發(fā)要求不是很嚴(yán)格的情況
多臺(tái)應(yīng)用服務(wù)器中的緩存是不能進(jìn)行實(shí)時(shí)同步的箱靴。
3腺逛、對(duì)一致性要求不高的情況下
因?yàn)镋hcache本地緩存的特性,目前無(wú)法很好的解決不同服務(wù)器間緩存同步的問題衡怀,所以我們?cè)谝恢滦砸蠓浅8叩膱?chǎng)合下棍矛,盡量使用Redis安疗、Memcached等集中式緩存。

11.2够委、Ehcache在集群茂契、分布式的情況下表現(xiàn)如何

在分布式情況下有二種同步方式:
1、RMI組播方式

Paste_Image.png

示例:

<cacheManagerPeerProviderFactory
        class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
        properties="peerDiscovery=automatic, multicastGroupAddress=localhost,
        multicastGroupPort=4446,timeToLive=255"/>

原理:當(dāng)緩存改變時(shí),ehcache會(huì)向組播IP地址和端口號(hào)發(fā)送RMI UDP組播包慨绳。
缺陷:Ehcache的組播做得比較初級(jí),功能只是基本實(shí)現(xiàn)(比如簡(jiǎn)單的一個(gè)HUB,接兩臺(tái)單網(wǎng)卡的服務(wù)器,互相之間組播同步就沒問題)掉冶,對(duì)一些復(fù)雜的環(huán)境(比如多臺(tái)服務(wù)器,每臺(tái)服務(wù)器上多地址,尤其是集群脐雪,存在一個(gè)集群地址帶多個(gè)物理機(jī)厌小,每臺(tái)物理機(jī)又帶多個(gè)虛擬站的子地址),就容易出現(xiàn)問題战秋。

2璧亚、P2P方式
原理:P2P要求每個(gè)節(jié)點(diǎn)的Ehcache都要指向其他的N-1個(gè)節(jié)點(diǎn)。

3脂信、JMS消息模式

Paste_Image.png

原理:這種模式的核心就是一個(gè)消息隊(duì)列癣蟋,每個(gè)應(yīng)用節(jié)點(diǎn)都訂閱預(yù)先定義好的主題,同時(shí)狰闪,節(jié)點(diǎn)有元素更新時(shí)疯搅,也會(huì)發(fā)布更新元素到主題中去。各個(gè)應(yīng)用服務(wù)器節(jié)點(diǎn)通過偵聽MQ獲取到最新的數(shù)據(jù)埋泵,然后分別更新自己的Ehcache緩存幔欧,Ehcache默認(rèn)支持ActiveMQ,我們也可以通過自定義組件的方式實(shí)現(xiàn)類似Kafka丽声,RabbitMQ礁蔗。

4、Cache Server模式
原理:這種模式會(huì)存在主從節(jié)點(diǎn)雁社。

Paste_Image.png

缺陷:緩存容易出現(xiàn)數(shù)據(jù)不一致的問題浴井,

11.3、使用Ehcache的瓶頸是什么

1霉撵、緩存漂移(Cache Drift):每個(gè)應(yīng)用節(jié)點(diǎn)只管理自己的緩存磺浙,在更新某個(gè)節(jié)點(diǎn)的時(shí)候,不會(huì)影響到其他的節(jié)點(diǎn)喊巍,這樣數(shù)據(jù)之間可能就不同步了屠缭。

2、數(shù)據(jù)庫(kù)瓶頸(Database Bottlenecks ):對(duì)于單實(shí)例的應(yīng)用來(lái)說(shuō)崭参,緩存可以保護(hù)數(shù)據(jù)庫(kù)的讀風(fēng)暴呵曹;但是,在集群的環(huán)境下,每一個(gè)應(yīng)用節(jié)點(diǎn)都要定期保持?jǐn)?shù)據(jù)最新奄喂,節(jié)點(diǎn)越多铐殃,要維持這樣的情況對(duì)數(shù)據(jù)庫(kù)的開銷也越大。

11.4跨新、實(shí)際工作中如何使用Ehcache

在實(shí)際工作中富腊,我更多是將Ehcache作為與Redis配合的二級(jí)緩存。
第一種方式:

Paste_Image.png

注:
這種方式通過應(yīng)用服務(wù)器的Ehcache定時(shí)輪詢Redis緩存服務(wù)器更同步更新本地緩存域帐,缺點(diǎn)是因?yàn)槊颗_(tái)服務(wù)器定時(shí)Ehcache的時(shí)間不一樣赘被,那么不同服務(wù)器刷新最新緩存的時(shí)間也不一樣,會(huì)產(chǎn)生數(shù)據(jù)不一致問題肖揣,對(duì)一致性要求不高可以使用民假。

第二種方式:

Paste_Image.png

注:
通過引入了MQ隊(duì)列,使每臺(tái)應(yīng)用服務(wù)器的Ehcache同步偵聽MQ消息龙优,這樣在一定程度上可以達(dá)到準(zhǔn)同步更新數(shù)據(jù)羊异,通過MQ推送或者拉取的方式,但是因?yàn)椴煌?wù)器之間的網(wǎng)絡(luò)速度的原因彤断,所以也不能完全達(dá)到強(qiáng)一致性野舶。基于此原理使用Zookeeper等分布式協(xié)調(diào)通知組件也是如此宰衙。

總結(jié):
1平道、使用二級(jí)緩存的好處是減少緩存數(shù)據(jù)的網(wǎng)絡(luò)傳輸開銷,當(dāng)集中式緩存出現(xiàn)故障的時(shí)候菩浙,Ehcache等本地緩存依然能夠支撐應(yīng)用程序正常使用巢掺,增加了程序的健壯性句伶。另外使用二級(jí)緩存策略可以在一定程度上阻止緩存穿透問題劲蜻。

2、根據(jù)CAP原理我們可以知道考余,如果要使用強(qiáng)一致性緩存(根據(jù)自身業(yè)務(wù)決定)先嬉,集中式緩存是最佳選擇,如(Redis楚堤,Memcached等)疫蔓。

**十二、Ehcache2.10.2源碼分析 **

12.1 源碼淘汰策略解析
首先看一下類結(jié)構(gòu)圖:

Paste_Image.png

從類結(jié)構(gòu)圖上看一共有三種緩存淘汰策略分別是:LFU身冬,LRU衅胀,F(xiàn)IFO。關(guān)于這三個(gè)概念在前面都已經(jīng)有過解釋酥筝,我們直接這三個(gè)的源碼:
1滚躯、LRUPolicy代碼如下:

public class LruPolicy extends AbstractPolicy {

    /**
     * The name of this policy as a string literal
     */
     public static final String NAME = "LRU";

    /**
     * @return the name of the Policy. Inbuilt examples are LRU, LFU and FIFO.
     */
    public String getName() {
        return NAME;
    }

    /**
     * Compares the desirableness for eviction of two elements
     *
     * Compares hit counts. If both zero,
     *
     * @param element1 the element to compare against
     * @param element2 the element to compare
     * @return true if the second element is preferable to the first element for ths policy
     */
    public boolean compare(Element element1, Element element2) {
        return element2.getLastAccessTime() < element1.getLastAccessTime();

    }

注:
accessTime小的緩存淘汰时迫。

2、LFUPolicy代碼如下:

public class LfuPolicy extends AbstractPolicy {

    /**
     * The name of this policy as a string literal
     */
    public static final String NAME = "LFU";

    /**
     * @return the name of the Policy. Inbuilt examples are LRU, LFU and FIFO.
     */
    public String getName() {
        return NAME;
    }

    /**
     * Compares the desirableness for eviction of two elements
     *
     * Compares hit counts. If both zero, 
     *
     * @param element1 the element to compare against
     * @param element2 the element to compare
     * @return true if the second element is preferable to the first element for ths policy
     */
    public boolean compare(Element element1, Element element2) {
        return element2.getHitCount() < element1.getHitCount();
        
    }
}

注:
hit值小的緩存淘汰钾麸。

3薪韩、FIFOPolicy代碼如下:

public class FifoPolicy extends AbstractPolicy {

    /**
     * The name of this policy as a string literal
     */
     public static final String NAME = "FIFO";

    /**
     * @return the name of the Policy. Inbuilt examples are LRU, LFU and FIFO.
     */
    public String getName() {
        return NAME;
    }

    /**
     * Compares the desirableness for eviction of two elements
     *
     * Compares hit counts. If both zero,
     *
     * @param element1 the element to compare against
     * @param element2 the element to compare
     * @return true if the second element is preferable to the first element for ths policy
     */
    public boolean compare(Element element1, Element element2) {
        return element2.getLatestOfCreationAndUpdateTime() < element1.getLatestOfCreationAndUpdateTime();

    }
}

注:
以creationAndUpdateTime最新或者最近的緩存淘汰。

4丧凤、這三個(gè)策略類統(tǒng)一繼承AbstractPolicy抽類
最關(guān)鍵的就是下面這個(gè)方法:

public Element selectedBasedOnPolicy(Element[] sampledElements, Element justAdded) {
        //edge condition when Memory Store configured to size 0
        if (sampledElements.length == 1) {
            return sampledElements[0];
        }
        Element lowestElement = null;
        for (Element element : sampledElements) {
            if (element == null) {
                continue;
            }
            if (lowestElement == null) {
                if (!element.equals(justAdded)) {
                    lowestElement = element;
                }
            } else if (compare(lowestElement, element) && !element.equals(justAdded)) {
                lowestElement = element;
            }

        }
        return lowestElement;
    }

注:
1募闲、這個(gè)方法主要是從取樣節(jié)點(diǎn)中查找需要淘汰的緩存。
2愿待、最關(guān)鍵的就是調(diào)用compare這個(gè)方法其實(shí)就是調(diào)用的上面那三個(gè)策略實(shí)現(xiàn)的方法來(lái)找個(gè)可以淘汰的緩存節(jié)點(diǎn)浩螺。

那么接下來(lái)我們看一下淘汰緩存的生命周期流程是怎么樣的。

Paste_Image.png

12.2 EhcacheManager類解析
這個(gè)類是2.10.2版本的最核心類仍侥,初始化年扩、創(chuàng)建緩存、獲取緩存都會(huì)用到這個(gè)類访圃,這個(gè)類里面有幾十個(gè)方法非常多厨幻,我們會(huì)按照類別分別進(jìn)行介紹,先看其構(gòu)造方法吧腿时。

Paste_Image.png

先看方法CacheManager()默認(rèn)的情況代碼如下:

public CacheManager() throws CacheException {
        // default config will be done
        status = Status.STATUS_UNINITIALISED;
        init(null, null, null, null);
}

Status.STATUS_UNINITIALISED這句的意思是緩存未被初始化况脆,在構(gòu)造方法里面要設(shè)定一個(gè)初始狀態(tài)。

我們接著看init方法批糟,這個(gè)方法是有別于其他構(gòu)造方法的格了,因?yàn)槟J(rèn)的情況下這個(gè)方法的參數(shù)全傳null值,這就意味著使用ehcache自己默認(rèn)的配置了徽鼎。
代碼如下:

protected synchronized void init(Configuration initialConfiguration, String configurationFileName, URL configurationURL,
            InputStream configurationInputStream) {
        Configuration configuration;

        if (initialConfiguration == null) {
            configuration = parseConfiguration(configurationFileName, configurationURL, configurationInputStream);
        } else {
            configuration = initialConfiguration;
        }

        assertManagementRESTServiceConfigurationIsCorrect(configuration);
        assertNoCacheManagerExistsWithSameName(configuration);

        try {
            doInit(configuration);
        } catch (Throwable t) {
            if (terracottaClient != null) {
                terracottaClient.shutdown();
            }

            if (statisticsExecutor != null) {
                statisticsExecutor.shutdown();
            }

            if (featuresManager != null) {
                featuresManager.dispose();
            }

            if (diskStorePathManager != null) {
                diskStorePathManager.releaseLock();
            }

            if (cacheManagerTimer != null) {
                cacheManagerTimer.cancel();
                cacheManagerTimer.purge();
            }

            synchronized (CacheManager.class) {
                final String name = CACHE_MANAGERS_REVERSE_MAP.remove(this);
                CACHE_MANAGERS_MAP.remove(name);
            }
            ALL_CACHE_MANAGERS.remove(this);
            if (t instanceof CacheException) {
                throw (CacheException) t;
            } else {
                throw new CacheException(t);
            }
        }
    }

說(shuō)明
1盛末、首先要判斷initialConfiguration這個(gè)參數(shù)是不是為空,判斷的情況下肯定是為就直接調(diào)用了parseConfiguration這個(gè)方法否淤,這個(gè)方法調(diào)用classpath默認(rèn)路徑來(lái)查找配置文件內(nèi)容悄但,初始化完configuration以后調(diào)用doInit方法。
2石抡、doInit方法主要用來(lái)初始化最大的本地堆大小檐嚣,初始化最大的本地持久化磁盤設(shè)置大小,集群模式啰扛,事務(wù)設(shè)置等等嚎京。

12.3 Cache類解析

cache的類繼承結(jié)構(gòu)如下所示:


Paste_Image.png

說(shuō)明:
Ehcache接口是整個(gè)緩存的核心接口,該接口提供的方法可以直接操作緩存隐解,比如put,get等方法鞍帝。由于方法太多我們只單拿出來(lái)put和get方法做一個(gè)深入分析。

先看put方法源碼:

 private void putInternal(Element element, boolean doNotNotifyCacheReplicators, boolean useCacheWriter) {
        putObserver.begin();
        if (useCacheWriter) {
            initialiseCacheWriterManager(true);
        }

        checkStatus();

        if (disabled) {
            putObserver.end(PutOutcome.IGNORED);
            return;
        }

        if (element == null) {
            if (doNotNotifyCacheReplicators) {

                LOG.debug("Element from replicated put is null. This happens because the element is a SoftReference" +
                        " and it has been collected. Increase heap memory on the JVM or set -Xms to be the same as " +
                        "-Xmx to avoid this problem.");

            }
            putObserver.end(PutOutcome.IGNORED);
            return;
        }


        if (element.getObjectKey() == null) {
            putObserver.end(PutOutcome.IGNORED);
            return;
        }

        element.resetAccessStatistics();

        applyDefaultsToElementWithoutLifespanSet(element);

        backOffIfDiskSpoolFull();
        element.updateUpdateStatistics();
        boolean elementExists = false;
        if (useCacheWriter) {
            boolean notifyListeners = true;
            try {
                elementExists = !compoundStore.putWithWriter(element, cacheWriterManager);
            } catch (StoreUpdateException e) {
                elementExists = e.isUpdate();
                notifyListeners = configuration.getCacheWriterConfiguration().getNotifyListenersOnException();
                RuntimeException cause = e.getCause();
                if (cause instanceof CacheWriterManagerException) {
                    throw ((CacheWriterManagerException)cause).getCause();
                }
                throw cause;
            } finally {
                if (notifyListeners) {
                    notifyPutInternalListeners(element, doNotNotifyCacheReplicators, elementExists);
                }
            }
        } else {
            elementExists = !compoundStore.put(element);
            notifyPutInternalListeners(element, doNotNotifyCacheReplicators, elementExists);
        }
        putObserver.end(elementExists ? PutOutcome.UPDATED : PutOutcome.ADDED);

    }

說(shuō)明:
1煞茫、代碼的邏輯其實(shí)很簡(jiǎn)單帕涌,我們看一下compoundStore這個(gè)類岩臣,實(shí)際上我們緩存的數(shù)據(jù)最終都是要到這個(gè)類里面進(jìn)行存儲(chǔ)的。
2宵膨、代碼里面使用了putObserver觀察者對(duì)象主要是用來(lái)做計(jì)數(shù)統(tǒng)計(jì)任務(wù)用的架谎。

看一下compoundStore類的繼承結(jié)構(gòu)圖如下:

Paste_Image.png

通過圖中可以看到所有的存儲(chǔ)類都實(shí)現(xiàn)Store接口類,大概有以下幾種存儲(chǔ)方式:
1辟躏、集群方式:ClusteredStore
2谷扣、緩存方式:CacheStore
3、內(nèi)存方式:MemoryStore
4捎琐、磁盤方式:DiskStore

我們以DiskStore為例深入講解磁盤的部分源碼分析会涎。

writeLock().lock();
        try {
            // ensure capacity
            if (count + 1 > threshold) {
                rehash();
            }
            HashEntry[] tab = table;
            int index = hash & (tab.length - 1);
            HashEntry first = tab[index];
            HashEntry e = first;
            while (e != null && (e.hash != hash || !key.equals(e.key))) {
                e = e.next;
            }

            Element oldElement;
            if (e != null) {
                DiskSubstitute onDiskSubstitute = e.element;
                if (!onlyIfAbsent) {
                    e.element = encoded;
                    installed = true;
                    oldElement = decode(onDiskSubstitute);

                    free(onDiskSubstitute);
                    final long existingHeapSize = onHeapPoolAccessor.delete(onDiskSubstitute.onHeapSize);
                    LOG.debug("put updated, deleted {} on heap", existingHeapSize);

                    if (onDiskSubstitute instanceof DiskStorageFactory.DiskMarker) {
                        final long existingDiskSize = onDiskPoolAccessor.delete(((DiskStorageFactory.DiskMarker) onDiskSubstitute).getSize());
                        LOG.debug("put updated, deleted {} on disk", existingDiskSize);
                    }
                    e.faulted.set(faulted);
                    cacheEventNotificationService.notifyElementUpdatedOrdered(oldElement, element);
                } else {
                    oldElement = decode(onDiskSubstitute);

                    free(encoded);
                    final long outgoingHeapSize = onHeapPoolAccessor.delete(encoded.onHeapSize);
                    LOG.debug("put if absent failed, deleted {} on heap", outgoingHeapSize);
                }
            } else {
                oldElement = null;
                ++modCount;
                tab[index] = new HashEntry(key, hash, first, encoded, new AtomicBoolean(faulted));
                installed = true;
                // write-volatile
                count = count + 1;
                cacheEventNotificationService.notifyElementPutOrdered(element);
            }
            return oldElement;

        } finally {
            writeLock().unlock();

            if (installed) {
                encoded.installed();
            }
        }

說(shuō)明:
1、流程采用寫鎖瑞凑,先將這段代碼鎖定末秃。
2、程序中有HashEntry[] tab這樣一個(gè)桶籽御,每個(gè)桶中存儲(chǔ)一個(gè)鏈表练慕,首先通過hash & (tab -1) 也就是key的hash值與桶的長(zhǎng)度減1取余得出一個(gè)桶的index。然后取出鏈表實(shí)體技掏,得到當(dāng)前鏈表實(shí)體的下一個(gè)元素铃将,如果元素為null則直接將元素賦值,否則取出舊的元素用新元素替換哑梳,釋放舊元素空間劲阎,返回舊元素。

十三鸠真、Guava Cache的使用與實(shí)現(xiàn)

Guava Cache與ConcurrentMap很相似悯仙,但也不完全一樣。最基本的區(qū)別是ConcurrentMap會(huì)一直保存所有添加的元素吠卷,直到顯式地移除锡垄。相對(duì)地,Guava Cache為了限制內(nèi)存占用撤嫩,通常都設(shè)定為自動(dòng)回收元素偎捎。在某些場(chǎng)景下,盡管LoadingCache 不回收元素序攘,它也是很有用的,因?yàn)樗鼤?huì)自動(dòng)加載緩存寻拂。

通常來(lái)說(shuō)程奠,Guava Cache
適用于:
你愿意消耗一些內(nèi)存空間來(lái)提升速度。
你預(yù)料到某些鍵會(huì)被查詢一次以上祭钉。
緩存中存放的數(shù)據(jù)總量不會(huì)超出內(nèi)存容量瞄沙。(Guava Cache是單個(gè)應(yīng)用運(yùn)行時(shí)的本地緩存。它不把數(shù)據(jù)存放到文件或外部服務(wù)器。如果這不符合你的需求距境,請(qǐng)嘗試Memcached或者Redis等集中式緩存申尼。

Guava Cache是一個(gè)全內(nèi)存的本地緩存實(shí)現(xiàn),它提供了線程安全的實(shí)現(xiàn)機(jī)制垫桂。

Guava Cache有兩種創(chuàng)建方式:

  1. CacheLoader
  2. Callable callback

13.1 CacheLoader方式
先看一段示例代碼如下:

 public static void main(String[] args) throws ExecutionException, InterruptedException {
        //緩存接口這里是LoadingCache师幕,LoadingCache在緩存項(xiàng)不存在時(shí)可以自動(dòng)加載緩存
        LoadingCache<Integer, String> strCache
                //CacheBuilder的構(gòu)造函數(shù)是私有的,只能通過其靜態(tài)方法newBuilder()來(lái)獲得CacheBuilder的實(shí)例
                = CacheBuilder.newBuilder()
                //設(shè)置并發(fā)級(jí)別為8诬滩,并發(fā)級(jí)別是指可以同時(shí)寫緩存的線程數(shù)
                .concurrencyLevel(8)
                //設(shè)置寫緩存后8秒鐘過期
                .expireAfterWrite(8, TimeUnit.SECONDS)
                //設(shè)置緩存容器的初始容量為10
                .initialCapacity(10)
                //設(shè)置緩存最大容量為100霹粥,超過100之后就會(huì)按照LRU最近雖少使用算法來(lái)移除緩存項(xiàng)
                .maximumSize(100)
                //設(shè)置要統(tǒng)計(jì)緩存的命中率
                .recordStats()
                //設(shè)置緩存的移除通知
                .removalListener(new RemovalListener<Object, Object>() {
                    public void onRemoval(RemovalNotification<Object, Object> notification) {
                        System.out.println(notification.getKey() + " was removed, cause is " + notification.getCause());
                    }
                })
                //build方法中可以指定CacheLoader,在緩存不存在時(shí)通過CacheLoader的實(shí)現(xiàn)自動(dòng)加載緩存
                .build(
                        new CacheLoader<Integer, String>() {
                            @Override
                            public String load(Integer key) throws Exception {
                                System.out.println("load data: " + key);
                                String str = key + ":cache-value";
                                return str;
                            }
                        }
                );

        for (int i = 0; i < 20; i++) {
            //從緩存中得到數(shù)據(jù)疼鸟,由于我們沒有設(shè)置過緩存后控,所以需要通過CacheLoader加載緩存數(shù)據(jù)
            String str = strCache.get(1);
            System.out.println(str);
            //休眠1秒
            TimeUnit.SECONDS.sleep(1);
        }

        System.out.println("cache stats:");
        //最后打印緩存的命中率等 情況
        System.out.println(strCache.stats().toString());
    }

運(yùn)行結(jié)果如下:

Paste_Image.png

說(shuō)明:
guava中使用緩存需要先聲明一個(gè)CacheBuilder對(duì)象,并設(shè)置緩存的相關(guān)參數(shù)空镜,然后調(diào)用其build方法獲得一個(gè)Cache接口的實(shí)例浩淘。

13.2 Callable方式
方法原型如下:get(K, Callable<V>)
這個(gè)方法返回緩存中相應(yīng)的值,如果未獲取到緩存值則調(diào)用Callable方法吴攒。這個(gè)方法簡(jiǎn)便地實(shí)現(xiàn)了模式"如果有緩存則返回馋袜;否則運(yùn)算、緩存舶斧、然后返回"欣鳖。
看示例代碼如下:

 Cache<String, String> cache = CacheBuilder.newBuilder().maximumSize(1000).build();  
        String resultVal = cache.get("test", new Callable<String>() {  
            public String call() {  
                //未根據(jù)key查到對(duì)應(yīng)緩存,設(shè)置緩存
                String strProValue="test-value"             
                return strProValue;
            }  
        });  
      
      System.out.println("return value : " + resultVal);  
    }

13.3 緩存過期刪除
guava的cache數(shù)據(jù)過期刪除的方式有二種茴厉,分別是主動(dòng)刪除和被動(dòng)刪除二種泽台。

被動(dòng)刪除三種方式

  • 基于條數(shù)限制的刪除
    使用CacheBuilder.maximumSize(long)方法進(jìn)行設(shè)置。
    注意點(diǎn):
    1矾缓、這個(gè)size不是容量大小怀酷,而是記錄條數(shù)。
    2嗜闻、使用CacheLoader方式加載緩存的時(shí)候蜕依,在并發(fā)情況下如果一個(gè)key過期刪除,正好同時(shí)有一個(gè)請(qǐng)求獲取緩存琉雳,有可能會(huì)報(bào)錯(cuò)样眠。

  • 基于過期時(shí)間刪除
    在Guava Cache中提供了二個(gè)方法可以基于過期時(shí)間刪除
    1、expireAfterAccess(long, TimeUnit):某個(gè)key最后一次訪問后翠肘,再隔多長(zhǎng)時(shí)間后刪除檐束。
    2、expireAfterWrite(long, TimeUnit):某個(gè)key被創(chuàng)建后束倍,再隔多長(zhǎng)時(shí)間后刪除被丧。

  • 基于引用的刪除
    通過使用弱引用的鍵盟戏、或弱引用的值、或軟引用的值甥桂,Guava Cache可以把緩存設(shè)置為允許垃圾回收柿究。

主動(dòng)刪除三種方式

  • 個(gè)別清除:Cache.invalidate(key)
  • 批量清除:Cache.invalidateAll(keys)
  • 清除所有緩存項(xiàng):Cache.invalidateAll()
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市黄选,隨后出現(xiàn)的幾起案子蝇摸,更是在濱河造成了極大的恐慌,老刑警劉巖糕簿,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件探入,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡懂诗,警方通過查閱死者的電腦和手機(jī)蜂嗽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)殃恒,“玉大人植旧,你說(shuō)我怎么就攤上這事±胩疲” “怎么了病附?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)亥鬓。 經(jīng)常有香客問我完沪,道長(zhǎng),這世上最難降的妖魔是什么嵌戈? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任覆积,我火速辦了婚禮,結(jié)果婚禮上熟呛,老公的妹妹穿的比我還像新娘宽档。我一直安慰自己,他們只是感情好庵朝,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布吗冤。 她就那樣靜靜地躺著,像睡著了一般九府。 火紅的嫁衣襯著肌膚如雪椎瘟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天昔逗,我揣著相機(jī)與錄音降传,去河邊找鬼。 笑死勾怒,一個(gè)胖子當(dāng)著我的面吹牛婆排,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播笔链,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼段只,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了鉴扫?” 一聲冷哼從身側(cè)響起赞枕,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎坪创,沒想到半個(gè)月后炕婶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡莱预,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年柠掂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片依沮。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涯贞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出危喉,到底是詐尸還是另有隱情宋渔,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布辜限,位于F島的核電站皇拣,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏薄嫡。R本人自食惡果不足惜氧急,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望岂座。 院中可真熱鬧态蒂,春花似錦、人聲如沸费什。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鸳址。三九已至瘩蚪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間稿黍,已是汗流浹背疹瘦。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留巡球,地道東北人言沐。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓邓嘹,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親险胰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子汹押,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 理論總結(jié) 它要解決什么樣的問題? 數(shù)據(jù)的訪問起便、存取棚贾、計(jì)算太慢、太不穩(wěn)定榆综、太消耗資源妙痹,同時(shí),這樣的操作存在重復(fù)性鼻疮。因...
    jiangmo閱讀 2,838評(píng)論 0 11
  • Ehcache是現(xiàn)在最流行的純Java開源緩存框架怯伊,配置簡(jiǎn)單、結(jié)構(gòu)清晰陋守、功能強(qiáng)大震贵,最初知道它,是從Hibernat...
    安易學(xué)車閱讀 2,026評(píng)論 0 11
  • 原文連接:https://my.oschina.net/coolfire368/blog/123377 ehcac...
    晴天哥_王志閱讀 1,334評(píng)論 0 1
  • 第一章(接) 看到王源盯著小土豆一眨不眨的眼神水评,龔阿姨立刻笑瞇瞇地把小土豆拉到身前猩系,介紹道:“源源,這是王俊凱...
    王先生的oo兔閱讀 282評(píng)論 0 0
  • 1中燥,黑屏是啥寇甸?先看看黑屏的樣子吧。 2疗涉,為啥黑屏拿霉?想想黑屏常常發(fā)生在啥時(shí)候?場(chǎng)景1:開機(jī)360提醒你軟件更新咱扣,漏洞...
    龍少俠linux閱讀 735評(píng)論 0 3