java中的本地緩存,工作后陸續用到,一直想寫,一直無從下手,最近又涉及到這方面的問題了,梳理了一下。自己構造單例、guava、ehcache基本上涵蓋了目前的大多數行為了。
為什么要有本地緩存?
在系統中,有些數據,數據量小,但是訪問十分頻繁(例如國家標准行政區域數據),針對這種場景,需要將數據搞到應用的本地緩存中,以提升系統的訪問效率,減少無謂的數據庫訪問(數據庫訪問占用數據庫連接,同時網絡消耗比較大),但是有一點需要注意,就是緩存的占用空間以及緩存的失效策略。
為什么是本地緩存,而不是分布式的集群緩存?
目前的數據,大多是業務無關的小數據緩存,沒有必要搞分布式的集群緩存,目前涉及到訂單和商品的數據,會直接走DB進行請求,再加上分布式緩存的構建,集群維護成本比較高,不太適合緊急的業務項目。
這里介紹一下緩存使用的三個階段(摘自info架構師文檔)
本地緩存在那個區域?
目前考慮的是占用了JVM的heap區域,再細化一點的就是heap中的old區,目前的數據量來看,都是一些小數據,加起來沒有幾百兆,放在heap區域最快最方便。后期如果需要放置在本地緩存的數據大的時候,可以考慮在off-heap區域(direct-memory 或者 big-memory),但是off-heap區域的話,需要考慮對象的序列化(因為off-heap區域存儲的是二進制的數據),另外一個的話就是off-heap的GC問題。其實,如果真的數據量比較大,那其實就可以考慮搞一個集中式的緩存系統,可以是單機,也可以是集群,來承擔緩存的作用。
搞一個單例模式,里面有個Map的變量來放置數據
關於單例模式,一個既簡單又復雜的模式(http://iamzhongyong.iteye.com/blog/1539642)
非常典型的代碼如下:
public class SingletonMap {
//一個本地的緩存Map
private Map localCacheStore = new HashMap();
//一個私有的對象,非懶漢模式
private static SingletonMap singletonMap = new SingletonMap();
//私有構造方法,外部不可以new一個對象
private SingletonMap(){
}
//靜態方法,外部獲得實例對象
public static SingletonMap getInstance(){
return singletonMap;
}
//獲得緩存中的數據
public Object getValueByKey(String key){
return localCacheStore.get(key);
}
//向緩存中添加數據
public void putValue(String key , Object value){
localCacheStore.put(key, value);
}
}
這種能不能用?可以用,但是非常局限
但是這種的就是本地緩存了嗎?答案顯然不是,為啥呢?
1、 沒有緩存大小的設置,無法限定緩存體的大小以及存儲數據的限制(max size limit);
2、 沒有緩存的失效策略(eviction policies);
3、 沒有弱鍵引用,在內存占用吃緊的情況下,JVM是無法回收的(weak rererences keys);
4、 沒有監控統計(statistics);
5、 持久性存儲(persistent store);
所以,這種就直接廢掉了。。。
引入EhCache來構建緩存(詳細介紹: http://raychase.iteye.com/blog/1545906)
EhCahce的核心類:
A、CacheManager:Cache的管理類;
B、Cache:具體的cache類信息,負責緩存的get和put等操作
C、CacheConfiguration :cache的配置信息,包含策略、最大值等信息
D、Element:cache中單條緩存數據的單位
典型的代碼如下:
public static void main(String[] args) {
//EhCache的緩存,是通過CacheManager來進行管理的
CacheManager cacheManager = CacheManager.getInstance();
//緩存的配置,也可以通過xml文件進行
CacheConfiguration conf = new CacheConfiguration();
conf.name("cache_name_default");//設置名字
conf.maxEntriesLocalHeap(1000);//最大的緩存數量
conf.memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LRU);//設置失效策略
//創建一個緩存對象,並把設置的信息傳入進去
Cache localCache = new Cache(conf);
//將緩存對象添加到管理器中
cacheManager.addCache(localCache);
localCache.put(new Element("iamzhongyong", new Date()));
System.out.println(localCache.getSize());
System.out.println(localCache.getStatistics().toString());
System.out.println(localCache.getName());
System.out.println(localCache.get("iamzhongyong").toString());
System.out.println(localCache.get("iamzhongyong").getObjectValue());
}
當然,Cache的配置信息,可以通過配置文件制定了。。。
優點:功能強大,有失效策略、最大數量設置等,緩存的持久化只有企業版才有,組件的緩存同步,可以通過jgroup來實現
缺點:功能強大的同時,也使其更加復雜
引入guava的cacheBuilder來構建緩存
這個非常強大、簡單,通過一個CacheBuilder類就可以滿足需求。
缺點就是如果要組件同步的話,需要自己實現這個功能。
典型的代碼如下:
public class GuavaCacheBuilderTest {
public static void main(String[] args) throws Exception{
GuavaCacheBuilderTest cache = new GuavaCacheBuilderTest();
cache.getNameLoadingCache("bixiao");
}
public void getNameLoadingCache(String name) throws Exception{
LoadingCache cache = CacheBuilder.newBuilder()
.maximumSize(20)//設置大小,條目數
.expireAfterWrite(20, TimeUnit.SECONDS)//設置失效時間,創建時間
.expireAfterAccess(20, TimeUnit.HOURS) //設置時效時間,最后一次被訪問
.removalListener(new RemovalListener() { //移除緩存的監聽器
public void onRemoval(RemovalNotification notification) {
System.out.println("有緩存數據被移除了");
}})
.build(new CacheLoader(){ //通過回調加載緩存
@Override
public String load(String name) throws Exception {
return name + "-" + "iamzhongyong";
}
});
System.out.println(cache.get(name));
//cache.invalidateAll();
}
}
緩存預熱怎么搞?
A、全量預熱,固定的時間段移除所有,然后再全量預熱
適用場景:
1、數據更新不頻繁,例如每天晚上3點更新即可的需求;
2、數據基本沒有變化,例如全國區域性數據;
B、增量預熱(緩存查詢,沒有,則查詢數據庫,有則放入緩存)
適用場景:
1、 數據更新要求緩存中同步更新的場景
集群內部,緩存的一致性如何保證?
如果采用ehcache的話,可以使用框架本身的JGroup來實現組內機器之間的緩存同步。
如果是采用google的cacheBuilder的話,需要自己實現緩存的同步。
A、非實時生效數據:數據的更新不會時時發生,應用啟動的時候更新即可,然后定時程序定時去清理緩存;
B、需要實時生效數據:啟動時可預熱也可不預熱,但是緩存數據變更后,集群之間需要同步