上善若水
          In general the OO style is to use a lot of little objects with a lot of little methods that give us a lot of plug points for overriding and variation. To do is to be -Nietzsche, To bei is to do -Kant, Do be do be do -Sinatra
          posts - 146,comments - 147,trackbacks - 0
          前記:最近公司在做的項目完全基于Cache(Gemfire)構建了一個類數據庫的系統,自己做的一個小項目里用過Guava的Cache,以前做過的項目中使用過EHCache,既然和Cache那么有緣,那就趁這個機會好好研究一下Java中的Cache庫。在Java社區中已經提供了很多Cache庫實現,具體可以參考http://www.open-open.com/13.htm,這里只關注自己用到的幾個Cache庫而且這幾個庫都比較具有代表性:Guava中提供的Cache是基于單JVM的簡單實現;EHCache出自Hibernate,也是基于單JVM的實現,是對單JVM Cache比較完善的實現;而Gemfire則提供了對分布式Cache的完善實現。這一系列的文章主要關注在這幾個Cache系統的實現上,因而步探討關于Cache的好處、何時用Cache等問題,由于他們都是基于內存的Cache,因而也僅局限于這種類型的Cache(說實話,我不知道有沒有其他的Cache系統,比如基于文件?囧)。

          記得我最早接觸Cache是在大學學計算機組成原理的時候,由于CPU的速度要遠大于內存的讀取速度,為了提高CPU的效率,CPU會在內部提供緩存區,該緩存區的讀取速度和CPU的處理速度類似,CPU可以直接從緩存區中讀取數據,從而解決CPU的處理速度和內存讀取速度不匹配的問題。緩存之所以能解決這個問題是基于程序的局部性原理,即”程序在執行時呈現出局部性規律,即在一段時間內,整個程序的執行僅限于程序中的某一部分。相應地,執行所訪問的存儲空間也局限于某個內存區域。局部性原理又表現為:時間局部性和空間局部性。時間局部性是指如果程序中的某條指令一旦執行,則不久之后該指令可能再次被執行;如果某數據被訪問,則不久之后該數據可能再次被訪問。空間局部性是指一旦程序訪問了某個存儲單元,則不久之后。其附近的存儲單元也將被訪問。”在實際工作中,CPU先向緩存區讀取數據,如果緩存區已存在,則讀取緩存中的數據(命中),否則(失效),將內存中相應數據塊載入緩存中,以提高接下來的訪問速度。由于成本和CPU大小的限制,CPU只能提供有限的緩存區,因而緩存區的大小是衡量CPU性能的重要指標之一。

          使用緩存,在CPU向內存更新數據時需要處理一個問題(寫回策略問題),即CPU在更新數據時只更新緩存的數據(write back,寫回,當緩存需要被替換時才將緩存中更新的值寫回內存),還是CPU在更新數據時同時更新緩存中和內存中的數據(write through,寫通)。在寫回策略中,為了減少內存寫操作,緩存塊通常還設有一個臟位(dirty bit),用以標識該塊在被載入之后是否發生過更新。如果一個緩存塊在被置換回內存之前從未被寫入過,則可以免去回寫操作;寫回的優點是節省了大量的寫操作。這主要是因為,對一個數據塊內不同單元的更新僅需一次寫操作即可完成。這種內存帶寬上的節省進一步降低了能耗,因此頗適用于嵌入式系統。寫通策略由于要經常和內存交互(有些CPU設計會在中間提供寫緩沖器以緩解性能),因而性能較差,但是它實現簡單,而且能簡單的維持數據一致性。

          在軟件的緩存系統中,一般是為了解決內存的訪問速率和磁盤、網絡、數據庫(屬于磁盤或網絡訪問,單獨列出來因為它的應用比較廣泛)等訪問速率不匹配的問題(對于內存緩存系統來說)。但是由于內存大小和成本的限制,我們又不能把所有的數據先加載進內存來。因而如CPU中的緩存,我們只能先將一部分數據保存在緩存中。此時,對于緩存,我們一般要解決如下需求:
          1. 使用給定Key從Cache中讀取Value值。CPU是通過內存地址來定位內存已獲取相應內存中的值,類似的在軟件Cache中,需要通過某個Key值來標識相關的值。因而可以簡單的認為軟件中的Cache是一個存儲鍵值對的Map,比如Gemfire中的Region就繼承自Map,只是Cache的實現更加復雜。
          2. 當給定的Key在當前Cache不存在時,程序員可以通過指定相應的邏輯從其他源(如數據庫、網絡等源)中加載該Key對應的Value值,同時將該值返回。在CPU中,基于程序局部性原理,一般是默認的加載接下來的一段內存塊,然而在軟件中,不同的需求有不同的加載邏輯,因而需要用戶自己指定對應的加載邏輯,而且一般來說也很難預知接下來要讀取的數據,所以只能一次只加載一條紀錄(對可預知的場景下當然可以批量加載數據,只是此時需要權衡當前操作的響應時間問題)。
          3. 可以向Cache中寫入Key-Value鍵值對(新增的紀錄或對原有的鍵值對的更新)。就像CPU的寫回策略中有寫回和寫通策略,有些Cache系統提供了寫通接口。如果沒有提供寫通接口,程序員需要額外的邏輯處理寫通策略。也可以如CPU中的Cache一樣,只當相應的鍵值對移出Cache以后,再將值寫回到數據源,可以提供一個標記位以決定要不要寫回(不過感覺這種實現比較復雜,代碼的的耦合度也比較高,如果為提升寫的速度,采用異步寫回即可,為防止數據丟失,可以使用Queue來存儲)。
          4. 將給定Key的鍵值對移出Cache(或給定多個Key以批量移除,甚至清除整個Cache)。
          5. 配置Cache的最大使用率,當Cache超過該使用率時,可配置溢出策略
            1. 直接移除溢出的鍵值對。在移除時決定是否要寫回已更新的數據到數據源。
            2. 將溢出的溢出的鍵值對寫到磁盤中。在寫磁盤時需要解決如何序列化鍵值對,如何存儲序列化后的數據到磁盤中,如何布局磁盤存儲,如何解決磁盤碎片問題,如何從磁盤中找回相應的鍵值對,如何讀取磁盤中的數據并方序列化,如何處理磁盤溢出等問題。
            3. 在溢出策略中,除了如何處理溢出的鍵值對問題,還需要處理如何選擇溢出的鍵值對問題。這有點類似內存的頁面置換算法(其實內存也可以看作是對磁盤的Cache),一般使用的算法有:先進先出(FIFO)、最近最少使用(LRU)、最少使用(LFU)、Clock置換(類LRU)、工作集等算法。
          6. 對Cache中的鍵值對,可以配置其生存時間,以處理某些鍵值對在長時間不被使用,但是又沒能溢出的問題(因為溢出策略的選擇或者Cache沒有到溢出階段),以提前釋放內存。
          7. 對某些特定的鍵值對,我們希望它能一直留在內存中不被溢出,有些Cache系統提供PIN配置(動態或靜態),以確保該鍵值對不會被溢出。
          8. 提供Cache狀態、命中率等統計信息,如磁盤大小、Cache大小、平均查詢時間、每秒查詢次數、內存命中次數、磁盤命中次數等信息。
          9. 提供注冊Cache相關的事件處理器,如Cache的創建、Cache的銷毀、一條鍵值對的添加、一條鍵值對的更新、鍵值對溢出等事件。
          10. 由于引入Cache的目的就是為了提升程序的讀寫性能,而且一般Cache都需要在多線程環境下工作,因而在實現時一般需要保證線程安全,以及提供高效的讀寫性能。
          在Java中,Map是最簡單的Cache,為了高效的在多線程環境中使用,可以使用ConcurrentHashMap,這也正是我之前參與的一個項目中最開始的實現(后來引入EHCache)。為了語意更加清晰、保持接口的簡單,下面我實現了一個基于Map的最簡單的Cache系統,用以演示Cache的基本使用方式。用戶可以向它提供數據、查詢數據、判斷給定Key的存在性、移除給定的Key(s)、清除整個Cache等操作。以下是Cache的接口定義。
          public interface Cache<K, V> {
              public String getName();
              public V get(K key);
              public Map<? extends K, ? extends V> getAll(Iterator<? extends K> keys);
              public boolean isPresent(K key);
              public void put(K key, V value);
              public void putAll(Map<? extends K, ? extends V> entries);
              public void invalidate(K key);
              public void invalidateAll(Iterator<? extends K> keys);
              public void invalidateAll();
              public boolean isEmpty();
              public int size();
              public void clear();
              public Map<? extends K, ? extends V> asMap();
          }
          這個簡單的Cache實現只是對HashMap的封裝,之所以選擇HashMap而不是ConcurrentHashMap是因為在ConcurrentHashMap無法實現getAll()方法;并且這里所有的操作我都加鎖了,因而也不需要ConcurrentHashMap來保證線程安全問題;為了提升性能,我使用了讀寫鎖,以提升并發查詢性能。因為代碼比較簡單,所以把所有代碼都貼上了(懶得整理了。。。。)。
          public class CacheImpl<K, V> implements Cache<K, V> {
              private final String name;
              private final HashMap<K, V> cache;
              private final ReadWriteLock lock = new ReentrantReadWriteLock();
              private final Lock readLock = lock.readLock();
              private final Lock writeLock = lock.writeLock();
              
              public CacheImpl(String name) {
                  this.name = name;
                  cache = new HashMap<K, V>();
              }
              
              public CacheImpl(String name, int initialCapacity) {
                  this.name = name;
                  cache = new HashMap<K, V>(initialCapacity);
              }
              
              public String getName() {
                  return name;
              }

              public V get(K key) {
                  readLock.lock();
                  try {
                      return cache.get(key);
                  } finally {
                      readLock.unlock();
                  }
              }

              public Map<? extends K, ? extends V> getAll(Iterator<? extends K> keys) {
                  readLock.lock();
                  try {
                      Map<K, V> map = new HashMap<K, V>();
                      List<K> noEntryKeys = new ArrayList<K>();
                      while(keys.hasNext()) {
                          K key = keys.next();
                          if(isPresent(key)) {
                              map.put(key, cache.get(key));
                          } else {
                              noEntryKeys.add(key);
                          }
                      }
                      
                      if(!noEntryKeys.isEmpty()) {
                          throw new CacheEntriesNotExistException(this, noEntryKeys);
                      }
                      
                      return map;
                  } finally {
                      readLock.unlock();
                  }
              }

              public boolean isPresent(K key) {
                  readLock.lock();
                  try {
                      return cache.containsKey(key);
                  } finally {
                      readLock.unlock();
                  }
              }

              public void put(K key, V value) {
                  writeLock.lock();
                  try {
                      cache.put(key, value);
                  } finally {
                      writeLock.unlock();
                  }
              }

              public void putAll(Map<? extends K, ? extends V> entries) {
                  writeLock.lock();
                  try {
                      cache.putAll(entries);
                  } finally {
                      writeLock.unlock();
                  }
              }

              public void invalidate(K key) {
                  writeLock.lock();
                  try {
                      if(!isPresent(key)) {
                          throw new CacheEntryNotExistsException(this, key);
                      }
                      cache.remove(key);
                  } finally {
                      writeLock.unlock();
                  }
              }

              public void invalidateAll(Iterator<? extends K> keys) {
                  writeLock.lock();
                  try {
                      List<K> noEntryKeys = new ArrayList<K>();
                      while(keys.hasNext()) {
                          K key = keys.next();
                          if(!isPresent(key)) {
                              noEntryKeys.add(key);
                          }
                      }
                      if(!noEntryKeys.isEmpty()) {
                          throw new CacheEntriesNotExistException(this, noEntryKeys);
                      }
                      
                      while(keys.hasNext()) {
                          K key = keys.next();
                          invalidate(key);
                      }
                  } finally {
                      writeLock.unlock();
                  }
              }

              public void invalidateAll() {
                  writeLock.lock();
                  try {
                      cache.clear();
                  } finally {
                      writeLock.unlock();
                  }
              }

              public int size() {
                  readLock.lock();
                  try {
                      return cache.size();
                  } finally {
                      readLock.unlock();
                  }
              }

              public void clear() {
                  writeLock.lock();
                  try {
                      cache.clear();
                  } finally {
                      writeLock.unlock();
                  }
              }

              public Map<? extends K, ? extends V> asMap() {
                  readLock.lock();
                  try {
                      return new ConcurrentHashMap<K, V>(cache);
                  } finally {
                      readLock.unlock();
                  }
              }

              public boolean isEmpty() {
                  readLock.lock();
                  try {
                      return cache.isEmpty();
                  } finally {
                      readLock.unlock();
                  }
              }

          }
          其簡單的使用用例如下:
              @Test
              public void testCacheSimpleUsage() {
                  Book uml = bookFactory.createUMLDistilled();
                  Book derivatives = bookFactory.createDerivatives();
                  
                  String umlBookISBN = uml.getIsbn();
                  String derivativesBookISBN = derivatives.getIsbn();
                  
                  Cache<String, Book> cache = cacheFactory.create("book-cache");
                  cache.put(umlBookISBN, uml);
                  cache.put(derivativesBookISBN, derivatives);
                  
                  Book fetchedBackUml = cache.get(umlBookISBN);
                  System.out.println(fetchedBackUml);
                  
                  Book fetchedBackDerivatives = cache.get(derivativesBookISBN);
                  System.out.println(fetchedBackDerivatives);
              }
          posted on 2013-10-15 23:46 DLevin 閱讀(15760) 評論(1)  編輯  收藏 所屬分類: CodeToolsEHCache

          FeedBack:
          # re: Java Cache系列之Cache概述和Simple Cache
          2013-10-17 19:16 | ayesd
          不知道common-jcs 是否和你研究的cache相關  回復  更多評論
            
          主站蜘蛛池模板: 潞城市| 花莲市| 和田县| 达拉特旗| 赤峰市| 昭苏县| 铜山县| 民丰县| 盘山县| 金寨县| 大渡口区| 阜新市| 台安县| 泰州市| 固镇县| 梅河口市| 辉南县| 缙云县| 呼伦贝尔市| 英山县| 德安县| 迁安市| 天全县| 芜湖市| 宁安市| 施甸县| 玉环县| 贡觉县| 五原县| 石棉县| 元江| 林州市| 沙田区| 平度市| 北海市| 仁布县| 吉木乃县| 兰西县| 衢州市| 洛阳市| 内江市|