每日一得

          不求多得,只求一得 about java,hibernate,spring,design,database,Ror,ruby,快速開發(fā)
          最近關(guān)心的內(nèi)容:SSH,seam,flex,敏捷,TDD
          本站的官方站點是:顛覆軟件

            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            220 隨筆 :: 9 文章 :: 421 評論 :: 0 Trackbacks

          4? Jive 的緩存機制

          Jive 論壇的一個主要特點就是其性能速度快,因此很多巨大訪問量的網(wǎng)站都采用了 Jive 論壇。這些都是由于 Jive 采取了高速緩存機制。

          緩存( Cache )機制是提高系統(tǒng)運行性能必不可少的技術(shù)。緩存機制從原理上講比較簡單,就是在原始數(shù)據(jù)第一次讀取后保存在內(nèi)存中,下次讀取時,就直接從內(nèi)存中讀取。原始數(shù)據(jù)有可能保存在持久化介質(zhì)或網(wǎng)絡(luò)上。緩存機制也是代理模式的一種實現(xiàn)。

          4.1? 緩存原理和實現(xiàn)

          Jive Cache 總體來說實現(xiàn)得不是非常精簡和有效。它是針對每個具體數(shù)據(jù)對象逐個實現(xiàn)緩沖,這種“窮盡”的辦法不是實踐所推薦的用法。通過使用動態(tài)代理模式,可以根據(jù)具體方法的不同來實現(xiàn)緩存是值得推薦的做法。 Jive 的緩存實現(xiàn)得比較簡單,可以用來學習和研究緩存機制。

          Jive 中的 Cache 實現(xiàn)了緩存機制的大部分行為,它是將對象用惟一的關(guān)鍵字 Key 作標識保存在 HashMap Hashtable 中。當然,必須知道這些對象的大小,這個前提條件的設(shè)定可以保證緩存增長時不會超過規(guī)定的最大值。

          如果緩存增長得太大,一些不經(jīng)常被訪問的對象將首先從緩存中刪除。如果設(shè)置了對象的最大生命周期時間,即使這個對象被反復(fù)頻繁訪問,也將從緩存中刪除。這個特性可以適用于一些周期性需要刷新的數(shù)據(jù),如來自數(shù)據(jù)庫的數(shù)據(jù)。

          Cach 中除了 getObject() 方法的性能依據(jù)緩存大小,其他方法的性能都是比較快的。一個 HashMap 用來實現(xiàn)快速尋找,兩個 LinkedList 中一個以一定的訪問順序來保存對象,叫 accessed LinkedList ;另外一個以它們加入緩存的順序保存這些對象,這種保存對象只是保存對象的引用,叫 age LinkedList 。注意,這里的 LinkedList 不是 JDK 中的 LinkedList ,而是 Jive 自己定義的 LinkedList

          當對象被加入緩存時,首先被 CacheObject 封裝。封裝有以下信息:對象大小(以字節(jié)計算),一個指向 accessed LinkedList 的引用,一個指向 age LinkedList 的引用。

          當從緩存中獲取一個對象如 ObjectA 時,首先, HashMap 尋找到指向封裝 ObjectA 等信息的 CacheObject 對象。然后,這個對象將被移動到 accessed LinkedList 的前面,還有其他一些動作如緩存清理、刪除、過期失效等都是在這個動作中一起觸發(fā)實現(xiàn)的。

          public class Cache implements Cacheable {

          ??? /**

          ???? * 因為 System.currentTimeMillis() 執(zhí)行非常耗費性能,因此如果 get 操作都執(zhí)行

          * 這條語句將會形成性能瓶頸, 通過一個全局時間戳來實現(xiàn)每秒更新

          * 當然,這意味著在緩存過期時間計算上有一到幾秒的誤差

          ???? */

          ??? protected static long currentTime = CacheTimer.currentTime;

          ??? //CacheObject 對象

          ??? protected HashMap cachedObjectsHash;

          ??? //accessed LinkedList 最經(jīng)常訪問的排列在最前面

          ??? protected LinkedList lastAccessedList;

          ??? // 以緩存加入順序排列,最后加入排在最前面;越早加入的排在最后面

          ??? protected LinkedList ageList;

          ??? // 緩存最大限制 默認是 128k 可根據(jù)內(nèi)存設(shè)定,越大性能越高

          ??? protected int maxSize =? 128 * 1024;

          ??? // 當前緩存的大小

          ??? protected int size = 0;

          ??? // 最大生命周期時間,默認是沒有

          ??? protected long maxLifetime = -1;

          ??? // 緩存的擊中率,用于評測緩存效率

          ??? protected long cacheHits, cacheMisses = 0L;

          ?

          ??? public Cache() {

          ??????? // 構(gòu)造 HashMap. 默認 capacity 11

          ??????? // 如果實際大小超過 11 HashMap 將自動擴充,但是每次擴充都

          // 是性能開銷,因此期初要設(shè)置大一點

          ??????? cachedObjectsHash = new HashMap(103);

          ??????? lastAccessedList = new LinkedList();

          ??????? ageList = new LinkedList();

          ??? }

          ??? public Cache(int maxSize) {

          ??????? this();

          ??????? this.maxSize = maxSize;

          ??? }

          ? ??public Cache(long maxLifetime) {

          ??????? this();

          ??????? this.maxLifetime = maxLifetime;

          ??? }

          ??? public Cache(int maxSize, long maxLifetime) {

          ??????? this();

          ??????? this.maxSize = maxSize;

          ??????? this.maxLifetime = maxLifetime;

          ??? }

          ??? public int getSize() {??????? return size;??? }

          ??? public int getMaxSize() {??????? return maxSize;??? }

          ?

          ??? public void setMaxSize(int maxSize) {

          ??????? this.maxSize = maxSize;

          ??????? // 有可能緩存大小超過最大值,需要激活刪除清理動作

          ??????? cullCache();

          ??? }

          ??? public synchronized int getNumElements() {

          ??????? return cachedObjectsHash.size();

          ??? }

          ?

          ??? /**

          ???? * 增加一個 Cacheable 對象

          * 因為 HashMap 不是線程安全的,所以操作方法要使用同步

          * 如果使用 Hashtable 就不必同步

          ???? */

          ??? public synchronized void add(Object key, Cacheable object) {

          ??????? // 刪除已經(jīng)存在的 key

          ??????? remove(key);

          ??????? int objectSize = object.getSize();

          ??????? // 如果被緩存對象的大小超過最大值,就放棄

          ??????? if (objectSize > maxSize * .90) {??????????? return;??????? }

          ??????? size += objectSize;

          ??????? // 創(chuàng)建一個 CacheObject 對象

          ??????? CacheObject cacheObject = new CacheObject(object, objectSize);

          ??????? cachedObjectsHash.put(key, cacheObject);? // 保存這個 CacheObject

          ??????? // 加入 accessed LinkedList Jive 自己的 LinkedList 在加入時可以返回值

          ??????? LinkedListNode lastAccessedNode = lastAccessedList.addFirst(key);

          ??????? // 保存引用

          ??????? cacheObject.lastAccessedListNode = lastAccessedNode;

          ??????? // 加入到 age LinkedList

          ??????? LinkedListNode ageNode = ageList.addFirst(key);

          ??????? // 這里直接調(diào)用 System.currentTimeMillis(); 用法值得討論

          ??????? ageNode.timestamp = System.currentTimeMillis();

          ??????? // 保存引用

          ??????? cacheObject.ageListNode = ageNode;

          ??????? // 做一些清理工作

          ??????? cullCache();

          ??? }

          ??? /**

          ???? * 從緩存中獲得一個被緩存的對象,這個方法在下面兩種情況返回空

          ???? *??? <li> 該對象引用從來沒有被加入緩存中

          ???? *??? <li> 對象引用因為過期被清除 </ul>

          ???? */

          ??? public synchronized Cacheable get(Object key) {

          ??????? // 清除過期緩存

          ??????? deleteExpiredEntries();

          ??????? // Key 從緩存中獲取一個對象引用

          ??????? CacheObject cacheObject = (CacheObject)cachedObjectsHash.get(key);

          ??????? if (cacheObject == null) {

          ??????????? // 不存在,增加未命中率

          ??????????? cacheMisses++;

          ??????????? return null;

          ??????? }

          ??????? // 存在,增加命中率

          ??????? cacheHits++;

          ??????? // accessed LinkedList 中將對象從當前位置刪除

          ??????? // 重新插入在第一個

          ??????? cacheObject.lastAccessedListNode.remove();

          ??? ????lastAccessedList.addFirst(cacheObject.lastAccessedListNode);

          ??????? return cacheObject.object;

          ??? }

          ??? …

          }

          Cache 中,關(guān)鍵字 Key 是一個對象,為了再次提高性能,可以進一步將 Key 確定為一個 long 類型的整數(shù)。

          4.2? 緩存使用

          建立 LongCache 只是為了提高原來的 Cache 性能,本身無多大意義,可以將 LongCache 看成與 Cache 一樣的類。

          LongCache 的關(guān)鍵字 Key Forum ForumThread 以及 ForumMessage long 類型的 ID ,值 Value Forum ForumThread 以及 ForumMessage 等的對象。這些基本是通過 DatabaseCacheManager 實現(xiàn)完成,在主要類 DbForumFactory 的初始化構(gòu)造時,同時構(gòu)造了 DatabaseCacheManager 的實例 cacheManager

          前面過濾器功能分析中, Message 對象獲得方法的第一句如下:

          protected ForumMessage getMessage(long messageID, long threadID, long forumID) throws

          ????? ForumMessageNotFoundException {

          ??? DbForumMessage message = cacheManager.messageCache.get(messageID);

          ??? …

          }

          其中, cacheManager DatabaseCacheManager 的實例, DatabaseCacheManager 是一個緩存 Facade 類。在其中包含了 5 種類型的緩存,都是針對 Jive 5 個主要對象, DatabaseCacheManager 主要代碼如下:

          public class DatabaseCacheManager {

          ?? ?public UserCache userCache;??????? ????????????????? // 用戶資料緩存

          ??? public GroupCache groupCache;?????? ??????????????? // 組資料緩存

          ??? public ForumCache forumCache;????? ???????????????? //Forum 論壇緩存

          ??? public ForumThreadCache threadCache; ?????????????? //Thread 主題緩存

          ??? public ForumMessageCache messageCache;????????? //Message 緩存

          ??? public UserPermissionsCache userPermsCache;???? // 用戶權(quán)限緩存

          ?

          ??? public DatabaseCacheManager(DbForumFactory factory) {

          ??????? …

          ??????? forumCache =

          ??????????? new ForumCache(new LongCache(forumCacheSize, 6*HOUR), factory);

          ??????? threadCache =

          ??????????? new ForumThreadCache(

          ????????????????? new LongCache(threadCacheSize, 6*HOUR), factory);

          ??????? messageCache = new ForumMessageCache(

          ????????????????? new LongCache(messageCacheSize, 6*HOUR), factory);

          ??????? userCache = new UserCache(

          ????????????????? new LongCache(userCacheSize, 6*HOUR), factory);

          ??????? groupCache = new GroupCache(

          ????????????????? new LongCache(groupCacheSize, 6*HOUR), factory);

          ??????? userPermsCache = new UserPermissionsCache(

          ??????????????? new UserPermsCache(userPermCacheSize, 24*HOUR), factory

          ??????? );

          ??? }

          ??? …

          }

          從以上代碼看出, ForumCache 等對象生成都是以 LongCache 為基礎(chǔ)構(gòu)建的,以 ForumCache 為例,代碼如下:

          public class ForumCache extends DatabaseCache {

          ??? // Cache 構(gòu)建 ID 緩存

          ??? protected Cache forumIDCache = new Cache(128*1024, 6*JiveGlobals.HOUR);

          ??? // LongCache 構(gòu)建整個對象緩存

          ??? public ForumCache(LongCache cache, DbForumFactory forumFactory) {

          ??????? super(cache, forumFactory);

          ??? }

          ?

          ??? public DbForum get(long forumID) throws ForumNotFoundException {

          ??????? …

          ??????? DbForum forum = (DbForum)cache.get(forumID);

          ??????? if (forum == null) {??? // 如果緩存沒有從數(shù)據(jù)庫中獲取

          ??????????? forum = new DbForum(forumID, factory);

          ??????????? cache.add(forumID, forum);

          ??????? }

          ??????? return forum;

          ??? }

          ?

          public Forum get(String name) throws ForumNotFoundException {

          ?????? ??// name key ,從 forumIDCache 中獲取 ID

          ?CacheableLong forumIDLong = (CacheableLong)forumIDCache.get(name);

          ??????? if (forumIDLong == null) { // 如果緩存沒有 從數(shù)據(jù)庫獲得

          ??????????? long forumID = factory.getForumID(name);

          ??????????? forumIDLong = new CacheableLong(forumID); // 生成一個緩存對象

          ??????????? forumIDCache.add(name, forumIDLong);

          ??????? }

          ??????? return get(forumIDLong.getLong());

          ??? }

          ??? …

          }

          由此可以看到, LongCache 封裝了 Cache 的核心功能,而 ForumCache 等類則是在 LongCache 核心外又包裝了與應(yīng)用系統(tǒng)相關(guān)的操作,這有點類似裝飾( Decorator )模式。

          從中也可以看到 Cache LongCache 兩種緩存的用法。

          使用 Cache 時的關(guān)鍵字 Key 是任何字段。如上面代碼中的 String name ,如果用戶大量帖子主題查詢中, Key query + blockID ,見 DbForum 中的 getThreadBlock 方法;而值 Value 則是 Long 類型的 ID ,如 ForumID ThreadID 等。

          LongCache 的關(guān)鍵字 Key Long 類型的 ID ,如 ForumID ThreadID 等;而值 Value 則是 Forum ForumThread ForumMessage 等主要具體對象。

          在實際使用中,大多數(shù)是根據(jù) ID 獲得對象。但有時并不是這樣,因此根據(jù)應(yīng)用區(qū)分了兩種 Cache ,這其實類似數(shù)據(jù)庫的數(shù)據(jù)表,除了主關(guān)鍵字外還有其他關(guān)鍵字。

          4.3? 小結(jié)

          緩存中對象是原對象的映射,如何確保緩存中對象和原對象的一致性?即當原對象發(fā)生變化時,緩存中的對象也必須立即更新。這是緩存機制需要解決的另外一個基本技術(shù)問題。

          Jive 中是在原對象發(fā)生變化時,立即進行清除緩存中對象,如 ForumMessage 對象的創(chuàng)建。在 DbForumThread AddMessage 方法中有下列語句:

          factory.cacheManager.threadCache.remove(this.id);

          factory.cacheManager.forumCache.remove(this.forumID);

          即當有新的帖子加入時,將 ForumThreadCache ForumCache 相關(guān)緩沖全部清除。這樣,當有相關(guān)對象讀取時,將直接從數(shù)據(jù)庫中讀取,這是一種非常簡單的緩存更新方式。

          在復(fù)雜的系統(tǒng),例如有一臺以上的服務(wù)器運行著 Jive 系統(tǒng)。如果一個用戶登陸一臺服務(wù)器后,通過這臺服務(wù)器增加新帖。那么按照上述原理,只能更新本服務(wù)器 JVM 中的緩存數(shù)據(jù),而其他服務(wù)器則無從得知這種改變,這就需要一種分布式的緩存機制。

          3-7? Jive 主要對象的訪問

          到目前可以發(fā)現(xiàn) 整個 Jive 系統(tǒng)其實是圍繞 Forum ForumThread ForumMessage 等這些主要對象展開的讀取、修改或創(chuàng)建等操作。由于這些對象原先持久化保存在數(shù)據(jù)庫中,為了提高性能和加強安全性, Jive 在這些對象外面分別實現(xiàn)兩層包裝,如圖 3-7 所示。

          客戶端如果需要訪問這些對象,首先要經(jīng)過它們的代理對象。進行訪問權(quán)限的檢查,然后再從緩存中獲取該對象。只有緩存不存在時,才會從數(shù)據(jù)庫中獲取。

          這套機制是大多數(shù)應(yīng)用系統(tǒng)都面臨的必須解決的基本功能,因此完全可以做成一個通用的可重復(fù)使用的框架。這樣在具體應(yīng)用時,不必每個應(yīng)用系統(tǒng)都架設(shè)開發(fā)這樣的機制。其實 EJB 就是這樣一套框架,實體 Bean 都由緩存機制支持,而通過設(shè)定 ejb-jar.xml 可以實現(xiàn)訪問權(quán)限控制,這些工作都直接由 EJB 容器實現(xiàn)了,不必在代碼中自己來實現(xiàn)。剩余的工作是調(diào)整 EJB 容器的參數(shù),使之適合應(yīng)用系統(tǒng)的具體要求,這些將在以后章節(jié)中討論。

          Jive 中,圖 3-7 的機制是通過不同方式實現(xiàn)的。基本上是一配二模式:一個對象有一個緩沖對象和一個代理對象,這樣做的一個缺點是導(dǎo)致對象太多,系統(tǒng)變得復(fù)雜。這點在閱讀 Jive 源碼時可能已經(jīng)發(fā)現(xiàn)。

          如果建立一個對象工廠,工廠內(nèi)部封裝了圖 3-7 機制實現(xiàn)過程,客戶端可以根據(jù)不同的工廠輸入?yún)?shù)獲得具體不同的對象。這樣也許代碼結(jié)構(gòu)要更加抽象和緊湊, Java 的動態(tài)代理 API 也許是實現(xiàn)這個工廠的主要技術(shù)基礎(chǔ)。有興趣者可以進一步研究提煉。

          posted on 2006-08-31 12:27 Alex 閱讀(444) 評論(0)  編輯  收藏 所屬分類: java
          主站蜘蛛池模板: 内丘县| 桦南县| 大关县| 茂名市| 同心县| 视频| 桃园县| 城固县| 化州市| 阿图什市| 苗栗市| 古交市| 开江县| 平遥县| 同江市| 磐石市| 桂东县| 广东省| 施甸县| 裕民县| 青州市| 黎川县| 长乐市| 多伦县| 射阳县| 湟源县| 渭南市| 北海市| 济宁市| 广平县| 德昌县| 恩平市| 张家川| 静乐县| 霍城县| 桑植县| 贺兰县| 涞源县| 南丰县| 合江县| 微博|