Jive
論壇的一個主要特點就是其性能速度快,因此很多巨大訪問量的網站都采用了
Jive
論壇。這些都是由于
Jive
采取了高速緩存機制。
緩存(
Cache
)機制是提高系統運行性能必不可少的技術。緩存機制從原理上講比較簡單,就是在原始數據第一次讀取后保存在內存中,下次讀取時,就直接從內存中讀取。原始數據有可能保存在持久化介質或網絡上。緩存機制也是代理模式的一種實現。
Jive
的
Cache
總體來說實現得不是非常精簡和有效。它是針對每個具體數據對象逐個實現緩沖,這種“窮盡”的辦法不是實踐所推薦的用法。通過使用動態代理模式,可以根據具體方法的不同來實現緩存是值得推薦的做法。
Jive
的緩存實現得比較簡單,可以用來學習和研究緩存機制。
Jive
中的
Cache
實現了緩存機制的大部分行為,它是將對象用惟一的關鍵字
Key
作標識保存在
HashMap
或
Hashtable
中。當然,必須知道這些對象的大小,這個前提條件的設定可以保證緩存增長時不會超過規定的最大值。
如果緩存增長得太大,一些不經常被訪問的對象將首先從緩存中刪除。如果設置了對象的最大生命周期時間,即使這個對象被反復頻繁訪問,也將從緩存中刪除。這個特性可以適用于一些周期性需要刷新的數據,如來自數據庫的數據。
在
Cach
中除了
getObject()
方法的性能依據緩存大小,其他方法的性能都是比較快的。一個
HashMap
用來實現快速尋找,兩個
LinkedList
中一個以一定的訪問順序來保存對象,叫
accessed LinkedList
;另外一個以它們加入緩存的順序保存這些對象,這種保存對象只是保存對象的引用,叫
age LinkedList
。注意,這里的
LinkedList
不是
JDK
中的
LinkedList
,而是
Jive
自己定義的
LinkedList
。
當對象被加入緩存時,首先被
CacheObject
封裝。封裝有以下信息:對象大小(以字節計算),一個指向
accessed LinkedList
的引用,一個指向
age LinkedList
的引用。
當從緩存中獲取一個對象如
ObjectA
時,首先,
HashMap
尋找到指向封裝
ObjectA
等信息的
CacheObject
對象。然后,這個對象將被移動到
accessed LinkedList
的前面,還有其他一些動作如緩存清理、刪除、過期失效等都是在這個動作中一起觸發實現的。
public class Cache implements Cacheable {
??? /**
???? *
因為
System.currentTimeMillis()
執行非常耗費性能,因此如果
get
操作都執行
*
這條語句將會形成性能瓶頸,
通過一個全局時間戳來實現每秒更新
*
當然,這意味著在緩存過期時間計算上有一到幾秒的誤差
???? */
??? protected static long currentTime = CacheTimer.currentTime;
??? //CacheObject
對象
??? protected HashMap cachedObjectsHash;
??? //accessed LinkedList
最經常訪問的排列在最前面
??? protected LinkedList lastAccessedList;
??? //
以緩存加入順序排列,最后加入排在最前面;越早加入的排在最后面
??? protected LinkedList ageList;
??? //
緩存最大限制
默認是
128k
可根據內存設定,越大性能越高
??? protected int maxSize =? 128 * 1024;
??? //
當前緩存的大小
??? protected int size = 0;
??? //
最大生命周期時間,默認是沒有
??? protected long maxLifetime = -1;
??? //
緩存的擊中率,用于評測緩存效率
??? protected long cacheHits, cacheMisses = 0L;
?
??? public Cache() {
??????? //
構造
HashMap.
默認
capacity
是
11
??????? //
如果實際大小超過
11
,
HashMap
將自動擴充,但是每次擴充都
//
是性能開銷,因此期初要設置大一點
??????? 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) {
??????? //
刪除已經存在的
key
??????? remove(key);
??????? int objectSize = object.getSize();
??????? //
如果被緩存對象的大小超過最大值,就放棄
??????? if (objectSize > maxSize * .90) {??????????? return;??????? }
??????? size += objectSize;
??????? //
創建一個
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);
??????? //
這里直接調用
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
中,關鍵字
Key
是一個對象,為了再次提高性能,可以進一步將
Key
確定為一個
long
類型的整數。
建立
LongCache
只是為了提高原來的
Cache
性能,本身無多大意義,可以將
LongCache
看成與
Cache
一樣的類。
LongCache
的關鍵字
Key
是
Forum
、
ForumThread
以及
ForumMessage
等
long
類型的
ID
,值
Value
是
Forum
、
ForumThread
以及
ForumMessage
等的對象。這些基本是通過
DatabaseCacheManager
實現完成,在主要類
DbForumFactory
的初始化構造時,同時構造了
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;???? //
用戶權限緩存
?
??? 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
為基礎構建的,以
ForumCache
為例,代碼如下:
public class ForumCache extends DatabaseCache {
??? //
以
Cache
構建
ID
緩存
??? protected Cache forumIDCache = new Cache(128*1024, 6*JiveGlobals.HOUR);
??? //
以
LongCache
構建整個對象緩存
??? 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) {??? //
如果緩存沒有從數據庫中獲取
??????????? 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) { //
如果緩存沒有
從數據庫獲得
??????????? long forumID = factory.getForumID(name);
??????????? forumIDLong = new CacheableLong(forumID); //
生成一個緩存對象
??????????? forumIDCache.add(name, forumIDLong);
??????? }
??????? return get(forumIDLong.getLong());
??? }
??? …
}
由此可以看到,
LongCache
封裝了
Cache
的核心功能,而
ForumCache
等類則是在
LongCache
核心外又包裝了與應用系統相關的操作,這有點類似裝飾(
Decorator
)模式。
從中也可以看到
Cache
和
LongCache
兩種緩存的用法。
使用
Cache
時的關鍵字
Key
是任何字段。如上面代碼中的
String name
,如果用戶大量帖子主題查詢中,
Key
是
query + blockID
,見
DbForum
中的
getThreadBlock
方法;而值
Value
則是
Long
類型的
ID
,如
ForumID
或
ThreadID
等。
LongCache
的關鍵字
Key
是
Long
類型的
ID
,如
ForumID
或
ThreadID
等;而值
Value
則是
Forum
、
ForumThread
或
ForumMessage
等主要具體對象。
在實際使用中,大多數是根據
ID
獲得對象。但有時并不是這樣,因此根據應用區分了兩種
Cache
,這其實類似數據庫的數據表,除了主關鍵字外還有其他關鍵字。
緩存中對象是原對象的映射,如何確保緩存中對象和原對象的一致性?即當原對象發生變化時,緩存中的對象也必須立即更新。這是緩存機制需要解決的另外一個基本技術問題。
Jive
中是在原對象發生變化時,立即進行清除緩存中對象,如
ForumMessage
對象的創建。在
DbForumThread
的
AddMessage
方法中有下列語句:
factory.cacheManager.threadCache.remove(this.id);
factory.cacheManager.forumCache.remove(this.forumID);
即當有新的帖子加入時,將
ForumThreadCache
和
ForumCache
相關緩沖全部清除。這樣,當有相關對象讀取時,將直接從數據庫中讀取,這是一種非常簡單的緩存更新方式。
在復雜的系統,例如有一臺以上的服務器運行著
Jive
系統。如果一個用戶登陸一臺服務器后,通過這臺服務器增加新帖。那么按照上述原理,只能更新本服務器
JVM
中的緩存數據,而其他服務器則無從得知這種改變,這就需要一種分布式的緩存機制。
圖
3-7? Jive
主要對象的訪問
|
到目前可以發現
,
整個
Jive
系統其實是圍繞
Forum
、
ForumThread
和
ForumMessage
等這些主要對象展開的讀取、修改或創建等操作。由于這些對象原先持久化保存在數據庫中,為了提高性能和加強安全性,
Jive
在這些對象外面分別實現兩層包裝,如圖
3-7
所示。
客戶端如果需要訪問這些對象,首先要經過它們的代理對象。進行訪問權限的檢查,然后再從緩存中獲取該對象。只有緩存不存在時,才會從數據庫中獲取。
這套機制是大多數應用系統都面臨的必須解決的基本功能,因此完全可以做成一個通用的可重復使用的框架。這樣在具體應用時,不必每個應用系統都架設開發這樣的機制。其實
EJB
就是這樣一套框架,實體
Bean
都由緩存機制支持,而通過設定
ejb-jar.xml
可以實現訪問權限控制,這些工作都直接由
EJB
容器實現了,不必在代碼中自己來實現。剩余的工作是調整
EJB
容器的參數,使之適合應用系統的具體要求,這些將在以后章節中討論。
在
Jive
中,圖
3-7
的機制是通過不同方式實現的。基本上是一配二模式:一個對象有一個緩沖對象和一個代理對象,這樣做的一個缺點是導致對象太多,系統變得復雜。這點在閱讀
Jive
源碼時可能已經發現。
如果建立一個對象工廠,工廠內部封裝了圖
3-7
機制實現過程,客戶端可以根據不同的工廠輸入參數獲得具體不同的對象。這樣也許代碼結構要更加抽象和緊湊,
Java
的動態代理
API
也許是實現這個工廠的主要技術基礎。有興趣者可以進一步研究提煉。