跳轉到內容

WebObjects/EOF/使用 EOF/快取和新鮮度

來自華夏公益教科書,開放的書籍,為開放的世界

關於記憶體管理的內容與快取和新鮮度之間存在很大重疊。為了全面理解快取和記憶體管理如何在應用程式設計中發揮作用,應該閱讀這兩部分內容。

新鮮度

[編輯 | 編輯原始碼]

與 WebObjects 相關的最常見問題之一,也是最難解決的問題之一是:“我的應用程式一直在使用資料庫中的舊值。如何確保它獲取最新的資料?”

注意事項和警告

[編輯 | 編輯原始碼]

在有人願意撰寫關於實際情況和企業物件框架正在做什麼的章節之前,這些內容將不會在這個站點上出現。但請理解,EOF 試圖提高效率,儘可能地減少對資料庫的訪問次數,因為資料庫事務相對昂貴。你在這個領域做出的每一個選擇實際上都是應用程式效率和資料庫值“新鮮度”之間的權衡。根據應用程式的要求,可能需要不同的選擇。

這些方法中的一些也可能影響“樂觀鎖定”的行為,具體取決於需要的樂觀鎖定行為。關於 WO/EOF 中的 OptimisticLockingTechniques 的一章將受到歡迎,因為有效地使用樂觀鎖定(同時考慮到其他應用程式例項更改的值以及同一例項中的其他會話)可能比想象的要棘手。

注意:有些人建議使用 EOFetchSpecification.setRefreshesRefetchedObjects 和 EOEditingContext.setDefaultFetchTimestampLag 結合起來會導致“無法遞減快照”異常。不幸的是,目前我們對此並不瞭解。

但以下是一些確保資料庫值“新鮮度”的方法

  • 在應用程式建構函式中,呼叫
 EOEditingContext.setDefaultFetchTimestampLag(2);

這意味著,每個 EOEditingContext 在建立時,都將堅持從資料庫獲取最新的資料,這些資料不比 EOEditingContext 本身早 2 秒。如果 EOEditingContext 在 1:23:00 建立,並且有記錄在 1:22:58 之前建立的快照,則不會使用這些快照,而是會執行新的資料庫獲取操作。

上面的引數“2”表示 EOEditingContext 願意接受資料的建立時間之前的秒數。你可以使用任何想要的值。仔細想想,你會意識到,將它設定為“0”(你可能最初會想要這樣),你的應用程式可能會在負載非常大的情況下執行更多的資料庫事務,而不會在資料“新鮮度”方面獲得多少提升,因此非零值可能更可取。

[哎呀,根據你相信的哪個版本的文件,引數可能是毫秒而不是秒。注意這一點。

[在 WO5.2.3 中,預設值為 3600000,因此顯然是毫秒]

[來自 WO 5.3 文件:public static void setDefaultFetchTimestampLag(long lag)

將新例項化編輯上下文的預設時間戳滯後設定為 lag。預設滯後為 3,600,000 毫秒(一小時)。當初始化新的編輯上下文時,它會被分配一個獲取時間戳,該時間戳等於當前時間減去預設時間戳滯後。將滯後設定為較大的數字可能會導致每個新的編輯上下文都接受非常舊的快取資料。將滯後設定為太低的值可能會由於過度獲取而導致效能下降。負滯後值將被視為 0。

為了說明清楚:滯後構成固定時間,而不是滾動時間視窗。因此,它只確保新建立的 EC 中的任何資料不早於建立時間的減去滯後。

  • 對於一些(或所有!)EOFetchSpecifications,在其上呼叫 setRefreshesRefetchedObjects(true)。這意味著,當你實際執行這個獲取規範時,並且它執行與資料庫的交易,資料庫返回的值將被實際使用!與 refreshesRefetchedObjects==false 的預設值相反,WO/EOF 有時會忽略資料庫返回的新值,而選擇快取快照中的舊值。

注意,將兩種方法(setDefaultFetchTimestampLag 和 setRefreshesRefetchedObjects)組合起來會產生錯誤。不推薦這樣做。如果你這樣做,你會看到 decrementSnapshotCountForGlobalID 錯誤。

注意,如果你在 EOFetchSpecification 上使用 setPrefetchingRelationshipKeyPaths,則提到的關係將被跟蹤,並且它們的目的地將使用此 EOFetchSpecification 進行獲取,並且 setRefreshesRefetchedObjects 指令也將應用於這些目的地物件。

不確定是否推薦

[編輯 | 編輯原始碼]
  • 有些人使用各種方法來使他們的企業物件失效和/或重新獲取,以確保下次訪問這些物件時將獲得最新資料。我對這些方法有一些顧慮,因為文件令人困惑,在某些情況下實際上建議不要使用它們。儘管如此,有些人表示他們已經有效地使用了這些方法。如果你可以的話,我建議使用其他方法。也許其他人願意貢獻更多資訊?
[編輯 | 編輯原始碼]
  • 我不建議嘗試使用 databaseContextShouldUpdateCurrentSnapshot EODatabaseContext 委託方法。文件暗示你可以實現此委託方法,這樣你將始終獲得最新資料,這類似於在每個獲取規範上都呼叫 setRefreshesRefetchecObjects(true) 的效果,而無需在每個獲取規範上都這樣做。但根據我自己的經驗,嘗試這樣做會導致 EOF 出現各種問題,並且會發生各種神秘的異常。因此我不建議這樣做。

不同開發者實際的做法

[編輯 | 編輯原始碼]
Jonathan Rochkind
[編輯 | 編輯原始碼]

在我的高度互動式和協作應用程式中,我高度重視資料的最新性,而不是應用程式的效率。實施 setDefaultTimestampLag,使用非常低的值,並在大多數獲取操作中呼叫 setRefreshesRefetchedObjects(true) 會在我的應用程式中生成可接受的和相當新鮮的資料。我並不認為效率受到很大影響,但我沒有對此進行充分的調查,而且我不關心可以分派的請求數量。在每個時間段內的最大數量。

Michael Johnston
[編輯 | 編輯原始碼]

當一個 EO 絕對需要是最新的時候(例如 1,在一個每天高峰時有超過 500,000 場比賽的遊戲中選擇每小時的獲勝者;例如 2,一個非同步事件處理器,它透過網路和內部執行緒接收事件),我使用帶有鎖列的兩步獲取-儲存方法,以及一個帶有隨機、遞增休眠時間的輪詢迴圈。 這是多例項、多應用伺服器安全的,但它不會在 Oracle 上鎖定一個執行緒超過 0.8 秒。 到目前為止。 我嘗試過使用資料庫鎖定,但這會導致幾秒鐘的鎖定。

Jesse Barnum
[edit | edit source]

當我想確保我的企業物件的詳細資訊檢視是最新的時,我使用這段程式碼。 我並沒有顯式地執行獲取操作,我只是使特定的物件及其相關物件失效,當頁面顯示時,它們將自動重新設定並獲取。 如程式碼示例所示,您必須顯式地使相關物件失效。 如果您可以依靠您的使用者知道資訊何時過時,那麼一個巧妙的技術是在使用者在瀏覽器中點選重新載入時觸發這段程式碼。

 EOEditingContext ec = object.editingContext();
 NSMutableArray ids = new NSMutableArray();
 
 // Invalidate the object
 ids.addObject(ec.globalIDForObject(object));
 // Invalidate a to-one related item
 ids.addObject(ec.globalIDForObject((EOEnterpriseObject)object.valueForKey("adreq"))); 
 // Invalidate a to-many relationship
 Enumeration en = ((NSArray)object.valueForKey("correspondences")).objectEnumerator();
 while(en.hasMoreElements()) 
   ids.addObject(ec.globalIDForObject((EOEnterpriseObject)en.nextElement()));
 ec.invalidateObjectsWithGlobalIDs(ids);

重新整理多對多關係

[edit | edit source]

Chuck Hill

[edit | edit source]

上面描述的重新整理方法是有限的,因為它們只重新整理屬性和一對一關係(換句話說,就是行中的直接資料)。 它們不會重新整理多對多關係,以顯示哪些物件在關係中的變化。 這意味著,如果物件被另一個程序(或在另一個物件儲存中)新增到關係中,您的程式碼將不會看到它們是相關的。 相反,如果物件從另一個物件儲存中的關係中刪除,它們仍然會出現在關係中。 我不知道為什麼是這樣,也許這僅僅是 EOF 的一個缺點。 重新整理多對多關係不幸的是代價昂貴且費力。

一個解決方案是使您需要重新整理多對多關係的物件失效。 雖然這有效,但在處理方面可能很昂貴,並且如果失效的物件有未儲存的更改,則會產生意想不到的副作用。

我使用過另一種選擇,但我仍然不確定當編輯正在進行時是否沒有任何不良副作用。 這種重新整理多對多關係的方法透過將多對多關係的快照設定為 null 來工作。 它一次只對一個物件和一個關係起作用,因此重新整理多個物件可能有點煩人。 實現有點複雜,因為我們需要深入到 EODatabase 級別。

以下是重新整理 sourceObject 上的 relationshipName 的程式碼的要點

 sourceEditingContext = sourceObject.editingContext();
 EOEntity sourceObjectEntity = EOUtilities.entityForObject(sourceEditingContext, sourceObject);
 EOModel sourceObjectModel = sourceObjectEntity.model();
 EOGlobalID sourceGlobalID = sourceEditingContext.globalIDForObject(sourceObject);
 EODatabaseContext dbContext = EODatabaseContext.registeredDatabaseContextForModel(sourceObjectModel, sourceEditingContext);
 EODatabase database = dbContext.database();
 database.recordSnapshotForSourceGlobalID(null, sourceGlobalID, relationshipName);

Anjo Krank

[edit | edit source]

雖然上面的方法將在下次建立 EO 時獲取最新資料,但您需要執行一個額外的步驟才能在現有物件中看到新資料

 Object o = eo.storedValueForKey(relationshipName);
 if(o instanceof EOFaulting) {
   EOFaulting toManyArray = (EOFaulting)o;
   if (!toManyArray.isFault()) {
       EOFaulting tmpToManyArray = (EOFaulting)((EOObjectStoreCoordinator)ec.rootObjectStore()).arrayFaultWithSourceGlobalID(gid, relationshipName, ec);
       toManyArray.turnIntoFault(tmpToManyArray.faultHandler());
   }
 } else {
       // we should check if the existing object is an array, too
       EOFaulting tmpToManyArray = (EOFaulting)((EOObjectStoreCoordinator)ec.rootObjectStore())
             .arrayFaultWithSourceGlobalID(gid, relationshipName, ec);
       eo.takeStoredValueForKey(tmpToManyArray, relationshipName);
 }

Pierre Bernard

[edit | edit source]

據我瞭解,當使用設定為重新整理物件的獲取規範預取關係時,該關係應該被重新整理。 不幸的是,這不是 WebObjects 5.1 和 5.2 所做的事情。 我還沒有測試過更高版本。 為了解決這個問題,您需要在 EODatabaseContext 的子類中新增以下內容

 /**
  * Internal method that handles prefetching of to-many relationships.
* // TBD This is a workaround to what looks like a bug in WO 5.1 & WO 5.2. * Remove as soon as it's no longer needed * * The problem is that even refreshing fetches don't refresh the to-many * relationships they prefetch. */ public void _followToManyRelationshipWithFetchSpecification(EORelationship relationship, EOFetchSpecification fetchspecification, NSArray objects, EOEditingContext editingcontext) { int count = objects.count(); for (int i = 0; i < count; i++) { EOEnterpriseObject object = (EOEnterpriseObject) objects.objectAtIndex(i); EOGlobalID sourceGlobalID = editingcontext.globalIDForObject(object); String relationshipName = relationship.name(); if (!object.isFault()) { EOFaulting toManyArray = (EOFaulting) object.storedValueForKey(relationshipName); if (!toManyArray.isFault()) { EOFaulting tmpToManyArray = (EOFaulting) arrayFaultWithSourceGlobalID( sourceGlobalID, relationshipName, editingcontext); // Turn the existing array back into a fault by assigning it // the fault handler of the newly created fault toManyArray.turnIntoFault(tmpToManyArray.faultHandler()); } } } super._followToManyRelationshipWithFetchSpecification(relationship, fetchspecification, objects, editingcontext); }

EOEntity 的記憶體中快取設定

[edit | edit source]

當您為 EOEntity 設定記憶體中快取為 true 時,它告訴 EOF 您希望它始終嘗試使用記憶體中快取來儲存此實體的所有例項。 當您第一次獲取標記為記憶體中快取的實體的物件時,該實體的所有例項都將被獲取到記憶體中。 這對於相對靜態的資料(如列舉型別 EO 或其他類似型別的在您的應用程式中很少修改的資料)非常有用。 請注意,這完全獨立於 EOF 的正常快照快取,無論此設定的值如何,該快取都會被使用。 此設定僅用於確定您的實體的整個資料集是否應該始終被快取。 在使用此設定時,有一些非常重要的實現細節您應該注意。

下面描述的所有行為可能會在 WO 版本中發生變化,並且不是記憶體中快取功能的固有要求,但對於使用此功能的任何人都應謹慎考慮。 所有這些都被驗證為截至 WebObjects 5.3 為止是正確的

第一個是記憶體中快取繞過了快照引用計數。 記憶體中快取的物件不會被 EOF 釋放。 最後,這應該沒什麼大不了的,因為您不應該在具有非常大量物件的實體上使用此標誌。

在效能方面,請注意,記憶體中快取的物件僅按 EOGlobalID(主鍵)索引。 如果您使用 EOQualifier 來查詢您的物件(而不是遍歷包含該物件的到一或到多關係,該關係使用主鍵查詢),您將在記憶體中對您的物件進行“全表掃描”。 這是僅在小基數實體上使用記憶體中快取的另一個原因。 如果您快取了 200 萬行的實體,那麼使用 EOQualifier 獲取 EO 可能比讓資料庫首先處理它的業務要慢。

記憶體中快取的另一個主要效能細節是,如果您對快取實體型別的任何 EO 進行更改,該型別的所有 EO 將從快取中清除,並在下次訪問時重新載入。 這進一步支援了不將記憶體中快取用於可變 EO 的最佳實踐。 如果您有一個設定為記憶體中快取的人員 EO,並且您更改了一個人員的姓名,您的整個快取將被清空。 下次您獲取任何人員時,整個人員表將被重新載入。 這在一系列大型更改中可能是災難性的(您最終可能會清空快取 - 您儲存一個 EO,快取被清空,然後它重新載入整個快取,您儲存下一個,快取被清空,等等等等)。

最後,記憶體中快取 EO 的一個怪癖是,除非您的 EOFetchSpecification 在其上設定了 setDeep(true),否則快取將不會被使用。 如果 isDeep 在您的獲取規範上為 false,則將發生正常的資料庫獲取(否定您快取的用處)。 這也適用於內部使用 EOFetchSpecifications 的 EOUtilities 方法。 Project Wonder 提供了幾個常用 EOUtilities 獲取方法的替代實現,這些方法設定了 setDeep(true)。

invalidateAllObjects 很糟糕!

[edit | edit source]

首先,也是最重要的是,EOF 中失效的概念是關於與資料庫的快取一致性,而不是記憶體管理。

現在,失效確實有一些副作用,這些副作用可以對 Java 記憶體使用產生積極的影響,但將其用於此目的類似於使用 50 磅重的錘子將那些微型圖片懸掛釘釘到牆上。

我強烈建議您只在您作為應用程式程式設計師擁有外部資訊(EOF 無法訪問)時使用失效,這些資訊涉及資料庫狀態的變化。 例如,您剛剛執行了一個任意儲存過程,該過程對您的表進行了各種副作用。 或者您從另一個程序收到了一個 RMI 訊息,說它更新了一行。 或者今天是星期一凌晨 3 點,在星期一凌晨 2 點,您的 DBA 的 cron 作業總是在“temp”表中刪除所有內容。 或者您在 EOF 中發現了一個錯誤(唉,它發生了),這是唯一的解決方法。

正如其他人所指出的,失效強制清空 EODatabase 快取中的快照。 這會提高快取未命中率,從而降低應用程式的效能。 根據您的故障觸發模式,最初只需要 1 次獲取即可檢索的 10,000 行可能需要 10,000 次獲取才能恢復到快取中。 很糟糕。 預取和批次故障可以稍微改善這一點。 它們實際上比單純的故障要好得多,但沒有什麼能取代第一次執行正確的獲取。 沒有任何東西能比它高几個數量級。

清空快照會產生一些其他有害的影響。 此快取是應用程式中所有 EC 共享的資源。 當一個 EC 消滅了所有其他 EC 依賴的資源時...... 因此,EOF 釋出有關失效的通知。 每當失效發生時,應用程式中的所有 EC 都會受到影響,並且必須處理通知。 在一個具有許多併發會話的大型 Web 應用程式中,這可能是一堆不必要的閒聊。 失效及其隨之而來的通知會給併發執行的執行緒帶來很大的壓力。 失效會傳播到整個應用程式,即使它們是來自巢狀 EC 的。

[更技術性的解釋:整個以 EOObjectStoreCoordinator 為中心的 EOF 堆疊。 在 5.1 中,理論上可以擁有幾個這樣的堆疊,每個堆疊都有自己的快取和 EOObjectStoreCoordinator。 大多數 EOF 通知不會在使用不同 OSC 的 EC 之間傳遞。 在實踐中,對於 5.1 來說,這整個應用程式。]

重新設定要溫和得多。 它隻影響它被呼叫的 EC,因此它不會干擾其他使用者的 EC。 它會減少快照上的引用計數(這可能會也可能不會釋放快照)。 它會破壞該 EC 中的出站引用。 而且,如果快取中的當前快照“足夠新鮮”(根據該 EC 的 fetchTimestamp 定義),觸發故障將使用快取而不是生成另一個獲取。 重新設定不會發布通知(儘管如果快照不新鮮,觸發故障將導致獲取,這將導致釋出)。

好吧,這一切都與資料庫有關。 這條執行緒實際上是關於 Java 中 EOF 的記憶體管理,所以回到主題。

ec.dispose() 做了什麼?

[編輯 | 編輯原始碼]

EOEditingContext 做了很多工作。它與許多其他物件有關聯(包括註冊自身以接收通知),並且主要負責維護一個非常大的物件迴圈圖(EO)。dispose() 告訴 EC 它將不再被使用,並且它應該執行儘可能多的清理工作。這會遞減快照引用計數,取消註冊各種內容,並斷開引用。根據您的 JVM,這可能會或可能不會幫助 GC 更快地回收記憶體。如果您不呼叫 dispose(),EC 的終結器將會這樣做。大多數應用程式不需要直接呼叫 dispose(),但您的情況可能有所不同。

dispose() 不會呼叫任何 invalidate 方法。

對於批處理樣式的 EOF 操作,撤消功能通常被忽視為問題(強引用)的來源。預設情況下,EOF 使用無限撤消/重做堆疊。如果您不打算使用撤消/重做,那麼您應該認真考慮將 EC 的撤消管理器設定為 null,或者在邏輯檢查點(例如,儲存更改後)對 EC 的撤消管理器執行 removeAllActions。將 EC 的撤消管理器設定為 null 的唯一缺點是您將無法從驗證異常中恢復。根據您的批處理操作,這可能不是問題。對於健壯的應用程式,我建議保留撤消管理器,但使用 setLevelsOfUndo() 來保持堆疊較小,並定期呼叫 removeAllActions()。

對於在 WebObjects 應用程式之外使用 EOF 的應用程式,一些處理會延遲到當前事件結束時完成。您可能需要手動呼叫 NSDelayedCallbackCenter 上的 eventEnded()。在 WebObjects 應用程式中,這將在請求結束時為您完成。

在 WO 5.1 中,EOEditingContexts 對其所有已註冊 EO 具有強引用,而 EO 對其編輯上下文沒有引用。對 GC 的影響應該相當明顯。

invalidate 不會清除 EO 物件本身。它會強制資料庫快取的記憶體消失,但 EO 仍將保留在記憶體中並由其 EC 保留...

避免不必要的資料庫訪問 - 一些技巧

[編輯 | 編輯原始碼]

在最佳化效能時,我們通常會開啟 EOAdaptorDebugEnabled(或其 NSLog 等效項)。經常出現的一個問題是,如何減少/消除冗餘的資料庫提取?

EOF 有一個快照,它儲存表示您從資料庫請求的物件的字典。這基本上是一個應用程式範圍的快取。當您第一次將物件提取到 EOEditingContext 中時,字典會儲存在快照中(使用用於過期字典的時間戳滯後)。editingcontext 會增加該快照條目上的引用計數。當您處置該 editingcontext 時,該條目在快照中的引用計數會遞減。當該字典的快照計數降至 0 時,它會在某個時刻從快照中刪除。因此,下次您再次請求該物件時,即使它相當新鮮,它也必須訪問資料庫來提取它...

如果您使用的是會話的 defaultEditingContext(不推薦 - 但有時在舊版應用程式中可能),請注意,當會話終止時,它會處置其 editingcontext。這意味著,如果您只將物件提取到該 editingcontext 中,它很可能從快照中釋放。如果您從另一個 editingcontext 對同一個物件進行重複提取,它將不得不返回資料庫。我曾經遇到過一個應用程式,該應用程式透過直接操作使用會話預設的 editingcontext。當直接操作完成建立其頁面後,它會終止會話。對相同內容的重複呼叫會導致對相同資料的重複資料庫訪問。

解決此問題的一種方法是將 EO 提取到具有更長生命週期的 EOEditingcontext 中(例如,一個跨會話生存的 EOEditingcontext),並使用 EOUtilities 為任何臨時 editingcontext 使用建立該物件的本地例項。

開發人員面臨的第二個常見問題是按鍵值查詢。例如,考慮以下提取

EOQualifier qualifier = EOQualifier.qualifierWithQualifierFormat ("lastName = 'Smith'", null); EOFetchSpecification fetchspec = new EOFetchSpecification("Person", qualifier, null); editingContext.objectsWithFetchSpecification(fetchSpec,editingContext);

這會導致每次都向資料庫發出請求,即使始終返回相同物件也是如此。原因是 EOF 無法知道匹配查詢的物件數量是否發生了變化。

如果您處於查詢主鍵或知道返回的物件不會經常更改的情況,請考慮實現快取。您的快取應該只儲存返回物件的 EOGlobalID。這樣,當您將來進行此提取時,您可以將鍵與您的快取進行比較,並且只需返回與該鍵匹配的 EOGlobalID *無需* 提取資料庫。

原始行

[編輯 | 編輯原始碼]

原始行結果從不快取。如果您的應用程式正在使用原始行進行其批處理操作,並且仍然遇到記憶體問題,那麼您需要評估您的程式碼。OptimizeIt 或 JProbe 絕對值得擁有。即使您沒有進行原始行工作,也是如此。

華夏公益教科書