Java中怎么設(shè)計(jì)本地緩存-創(chuàng)新互聯(lián)

這篇文章給大家介紹Java中怎么設(shè)計(jì)本地緩存,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。

10年積累的成都網(wǎng)站建設(shè)、網(wǎng)站制作經(jīng)驗(yàn),可以快速應(yīng)對(duì)客戶對(duì)網(wǎng)站的新想法和需求。提供各種問題對(duì)應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識(shí)你,你也不認(rèn)識(shí)我。但先網(wǎng)站制作后付款的網(wǎng)站建設(shè)流程,更有彭州免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。

  1.數(shù)據(jù)結(jié)構(gòu)

  首要考慮的就是數(shù)據(jù)該如何存儲(chǔ),用什么數(shù)據(jù)結(jié)構(gòu)存儲(chǔ),最簡(jiǎn)單的就直接用Map來存儲(chǔ)數(shù)據(jù);或者復(fù)雜的如redis一樣提供了多種數(shù)據(jù)類型哈希,列表,集合,有序集合等,底層使用了雙端鏈表,壓縮列表,集合,跳躍表等數(shù)據(jù)結(jié)構(gòu);

  2.對(duì)象上限

  因?yàn)槭潜镜鼐彺妫瑑?nèi)存有上限,所以一般都會(huì)指定緩存對(duì)象的數(shù)量比如1024,當(dāng)達(dá)到某個(gè)上限后需要有某種策略去刪除多余的數(shù)據(jù);

  3.清除策略

  上面說到當(dāng)達(dá)到對(duì)象上限之后需要有清除策略,常見的比如有LRU(最近最少使用)、FIFO(先進(jìn)先出)、LFU(最近最不常用)、SOFT(軟引用)、WEAK(弱引用)等策略;

  4.過期時(shí)間

  除了使用清除策略,一般本地緩存也會(huì)有一個(gè)過期時(shí)間設(shè)置,比如redis可以給每個(gè)key設(shè)置一個(gè)過期時(shí)間,這樣當(dāng)達(dá)到過期時(shí)間之后直接刪除,采用清除策略+過期時(shí)間雙重保證;

  5.線程安全

  像redis是直接使用單線程處理,所以就不存在線程安全問題;而我們現(xiàn)在提供的本地緩存往往是可以多個(gè)線程同時(shí)訪問的,所以線程安全是不容忽視的問題;并且線程安全問題是不應(yīng)該拋給使用者去保證;

  6.簡(jiǎn)明的接口

  提供一個(gè)傻瓜式的對(duì)外接口是很有必要的,對(duì)使用者來說使用此緩存不是一種負(fù)擔(dān)而是一種享受;提供常用的get,put,remove,clear,getSize方法即可;

  7.是否持久化

  這個(gè)其實(shí)不是必須的,是否需要將緩存數(shù)據(jù)持久化看需求;本地緩存如ehcache是支持持久化的,而guava是沒有持久化功能的;分布式緩存如redis是有持久化功能的,memcached是沒有持久化功能的;

  8.阻塞機(jī)制

  在看Mybatis源碼的時(shí)候,二級(jí)緩存提供了一個(gè)blocking標(biāo)識(shí),表示當(dāng)在緩存中找不到元素時(shí),它設(shè)置對(duì)緩存鍵的鎖定;這樣其他線程將等待此元素被填充,而不是命中數(shù)據(jù)庫;其實(shí)我們使用緩存的目的就是因?yàn)楸痪彺娴臄?shù)據(jù)生成比較費(fèi)時(shí),比如調(diào)用對(duì)外的接口,查詢數(shù)據(jù)庫,計(jì)算量很大的結(jié)果等等;這時(shí)候如果多個(gè)線程同時(shí)調(diào)用get方法獲取的結(jié)果都為null,每個(gè)線程都去執(zhí)行一遍費(fèi)時(shí)的計(jì)算,其實(shí)也是對(duì)資源的浪費(fèi);比較好的辦法是只有一個(gè)線程去執(zhí)行,其他線程等待,計(jì)算一次就夠了;但是此功能基本上都交給使用者來處理,很少有本地緩存有這種功能;

  如何實(shí)現(xiàn)

  以上大致介紹了實(shí)現(xiàn)一個(gè)本地緩存我們都有哪些需要考慮的地方,當(dāng)然可能還有其他沒有考慮到的點(diǎn);下面繼續(xù)看看關(guān)于每個(gè)點(diǎn)都應(yīng)該如何去實(shí)現(xiàn),重點(diǎn)介紹一下思路;

  1.數(shù)據(jù)結(jié)構(gòu)

  本地緩存最常見的是直接使用Map來存儲(chǔ),比如guava使用ConcurrentHashMap,ehcache也是用了ConcurrentHashMap,Mybatis二級(jí)緩存使用HashMap來存儲(chǔ):

  Map<Object, Object> cache = new ConcurrentHashMap<Object, Object>()

  Mybatis使用HashMap本身是非線程安全的,所以可以看到起內(nèi)部使用了一個(gè)SynchronizedCache用來包裝,保證線程的安全性;

  當(dāng)然除了使用Map來存儲(chǔ),可能還使用其他數(shù)據(jù)結(jié)構(gòu)來存儲(chǔ),比如redis使用了雙端鏈表,壓縮列表,整數(shù)集合,跳躍表和字典;當(dāng)然這主要是因?yàn)閞edis對(duì)外提供的接口很豐富除了哈希還有列表,集合,有序集合等功能;

  2.對(duì)象上限

  本地緩存常見的一個(gè)屬性,一般緩存都會(huì)有一個(gè)默認(rèn)值比如1024,在用戶沒有指定的情況下默認(rèn)指定;當(dāng)緩存的數(shù)據(jù)達(dá)到指定大值時(shí),需要有相關(guān)策略從緩存中清除多余的數(shù)據(jù)這就涉及到下面要介紹的清除策略;

  3.清除策略

  配合對(duì)象上限之后使用,場(chǎng)景的清除策略如:LRU(最近最少使用)、FIFO(先進(jìn)先出)、LFU(最近最不常用)、SOFT(軟引用)、WEAK(弱引用);

  LRU:Least Recently Used的縮寫最近最少使用,移除最長(zhǎng)時(shí)間不被使用的對(duì)象;常見的使用LinkedHashMap來實(shí)現(xiàn),也是很多本地緩存默認(rèn)使用的策略;

  FIFO:先進(jìn)先出,按對(duì)象進(jìn)入緩存的順序來移除它們;常見使用隊(duì)列Queue來實(shí)現(xiàn);

  LFU:Least Frequently Used的縮寫大概也是最近最少使用的意思,和LRU有點(diǎn)像;區(qū)別點(diǎn)在LRU的淘汰規(guī)則是基于訪問時(shí)間,而LFU是基于訪問次數(shù)的;可以通過HashMap并且記錄訪問次數(shù)來實(shí)現(xiàn);

  SOFT:軟引用基于垃圾回收器狀態(tài)和軟引用規(guī)則移除對(duì)象;常見使用SoftReference來實(shí)現(xiàn);

  WEAK:弱引用更積極地基于垃圾收集器狀態(tài)和弱引用規(guī)則移除對(duì)象;常見使用WeakReference來實(shí)現(xiàn);

  4.過期時(shí)間

  設(shè)置過期時(shí)間,讓緩存數(shù)據(jù)在指定時(shí)間過后自動(dòng)刪除;常見的過期數(shù)據(jù)刪除策略有兩種方式:被動(dòng)刪除和主動(dòng)刪除;

  被動(dòng)刪除:每次進(jìn)行g(shù)et/put操作的時(shí)候都會(huì)檢查一下當(dāng)前key是否已經(jīng)過期,如果過期則刪除,類似如下代碼:

  if (System.currentTimeMillis() - lastClear > clearInterval) {

  clear();

  }

  主動(dòng)刪除:專門有一個(gè)job在后臺(tái)定期去檢查數(shù)據(jù)是否過期,如果過期則刪除,這其實(shí)可以有效的處理冷數(shù)據(jù);

  5.線程安全

  盡量用線程安全的類去存儲(chǔ)數(shù)據(jù),比如使用ConcurrentHashMap代替HashMap;或者提供相應(yīng)的同步處理類,比如Mybatis提供了SynchronizedCache:

  public synchronized void putObject(Object key, Object object) {

  ...省略...

  }

  @Override

  public synchronized Object getObject(Object key) {

  ...省略...

  }

  6.簡(jiǎn)明的接口

  提供常用的get,put,remove,clear,getSize方法即可,比如Mybatis的Cache接口:

  public interface Cache {

  String getId();

  void putObject(Object key, Object value);

  Object getObject(Object key);

  Object removeObject(Object key);

  void clear();

  int getSize();

  ReadWriteLock getReadWriteLock();

  }

  再來看看guava提供的Cache接口,相對(duì)來說也是比較簡(jiǎn)潔的:

  public interface Cache<K, V> {

  V getIfPresent(@CompatibleWith("K") Object key);

  V get(K key, Callable<? extends V> loader) throws ExecutionException;

  ImmutableMap<K, V> getAllPresent(Iterable<?> keys);

  void put(K key, V value);

  void putAll(Map<? extends K, ? extends V> m);

  void invalidate(@CompatibleWith("K") Object key);

  void invalidateAll(Iterable<?> keys);

  void invalidateAll();

  long size();

  CacheStats stats();

  ConcurrentMap<K, V> asMap();

  void cleanUp();

  }

  7.是否持久化

  持久化的好處是重啟之后可以再次加載文件中的數(shù)據(jù),這樣就起到類似熱加載的功效;比如ehcache提供了是否持久化磁盤緩存的功能,將緩存數(shù)據(jù)存放在一個(gè).data文件中;

  diskPersistent="false" //是否持久化磁盤緩存

  redis更是將持久化功能發(fā)揮到極致,慢慢的有點(diǎn)像數(shù)據(jù)庫了;提供了AOF和RDB兩種持久化方式;當(dāng)然很多情況下可以配合使用兩種方式;

  8.阻塞機(jī)制

  除了在Mybatis中看到了BlockingCache來實(shí)現(xiàn)此功能,之前在看<<java并發(fā)編程實(shí)戰(zhàn)>>的時(shí)候其中有實(shí)現(xiàn)一個(gè)很完美的緩存,大致代碼如下:

  public class Memoizerl<A, V> implements Computable<A, V> {

  private final Map<A, Future<V>> cache = new ConcurrentHashMap<A, Future<V>>();

  private final Computable<A, V> c;

  public Memoizerl(Computable<A, V> c) {

  this.c = c;

  }

  @Override

  public V compute(A arg) throws InterruptedException, ExecutionException {

  while (true) {

  Future<V> f = cache.get(arg);

  if (f == null) {

  Callable<V> eval = new Callable<V>() {

  @Override

  public V call() throws Exception {

  return c.compute(arg);

  }

  };

  FutureTask<V> ft = new FutureTask<V>(eval);

  f = cache.putIfAbsent(arg, ft);

  if (f == null) {

  f = ft;

  ft.run();

  }

  try {

  return f.get();

  } catch (CancellationException e) {

  cache.remove(arg, f);

  }

  }

  }

  }

  }

  compute是一個(gè)計(jì)算很費(fèi)時(shí)的方法,所以這里把計(jì)算的結(jié)果緩存起來,但是有個(gè)問題就是如果兩個(gè)線程同時(shí)進(jìn)入此方法中怎么保證只計(jì)算一次,這里最核心的地方在于使用了ConcurrentHashMap的putIfAbsent方法,同時(shí)只會(huì)寫入一個(gè)FutureTask;

關(guān)于Java中怎么設(shè)計(jì)本地緩存就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。

標(biāo)題名稱:Java中怎么設(shè)計(jì)本地緩存-創(chuàng)新互聯(lián)
當(dāng)前URL:http://bm7419.com/article2/dgdeoc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站營(yíng)銷、自適應(yīng)網(wǎng)站、域名注冊(cè)、手機(jī)網(wǎng)站建設(shè)品牌網(wǎng)站制作、外貿(mào)網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

成都定制網(wǎng)站建設(shè)