WebObjects/Web 應用程式/開發/資料庫與檔案系統
關於是否將媒體儲存在資料庫中,還是將媒體儲存在檔案系統中並在資料庫中僅儲存引用,存在著持續的爭論,通常與媒體檔案相關。
本文試圖追蹤一些關於這些爭論的著名文章。
請記住,資料庫的目的是儲存要搜尋和檢索的資料。
您實際向資料庫傳送包含影像 blob 的查詢的情況很少見(例如,搜尋與特定二進位制資料匹配的影像)。更可能的是,您會根據影像的元資料(如日期、時間、影像名稱或檔案系統路徑)執行搜尋。一個好的解決方案是儲存指向媒體的路徑,然後只需構建客戶端瀏覽器引用的引用 URL,或者讓應用程式從檔案系統中檢索媒體並透過 WebObjects 介面卡提供它。在前一種情況下,您可以將媒體儲存在 Web 伺服器下(例如影像縮圖),而在後一種情況下,您可以將全尺寸影像儲存在伺服器檔案系統的任何其他位置,並根據使用者的個人資料提供它們(例如,他們是否成功檢出?等等)。
在我看來,將影像儲存在資料庫中通常是一個壞主意。從資料庫中檢索影像資料比讓 Apache 提供影像的開銷要大得多。Apache 針對此目的進行了高度最佳化。資料庫通常沒有。
我的建議是隻在資料庫中儲存影像的 URL,並將該 URL 寫入動態頁面。或者,如果您知道路徑始終保持不變,您也可以只儲存檔名。
似乎不同的人對此主題有不同的看法。我已經關注了關於此主題的幾個主題,但我仍然沒有就最佳設計模式得出結論。
我不想將影像放在網路上的共享點上,並連結到影像目錄。這將建立一個單點故障。如果您因任何原因丟失了與儲存影像的盒子的連線,那麼您將無法訪問這些影像。如果將影像儲存在資料庫中,您可以使用資料庫叢集來提供一些冗餘和故障轉移支援。
無論哪種情況,您都將透過共享直接傳輸影像的二進位制資料,或透過資料庫連線傳輸。我個人採取簡單的方法,將影像儲存在資料庫中。
示例:假設我有一個 Product 實體,並且想要上傳和儲存產品照片:我會建立兩個實體 Product 和 ProductPhoto。然後,我會使用 toOne 或 toMany 關係將它們關聯起來,具體取決於每個 Product 物件是否需要一個或多個 ProductPhoto 物件。
使用此設計模式,獲取 Product 資料不會直接載入影像。相反,EOF 將建立代表影像的錯誤。只有在訪問 ProductPhoto 錯誤物件時觸發錯誤,才會獲取影像資料。因此,如果您獲取 500 個產品並將它們批處理到 WODisplayGroup 中的 10 個組,那麼您的第一頁將只獲取前 10 個影像,而不是 500 個(並且只有在存在訪問影像資料的 WOElement? 或方法的情況下)。
此模式還極大地簡化了上傳和儲存影像,因為您可以將用於上傳影像的 NSData 繫結到 ProductPhoto 的 imageData BLOB。
許多人可能不同意我的觀點,但對於我的目的,此設計模式取得了很好的成功。
您可以在 /Developer/Examples/JavaWebObjects/Frameworks 中的 JavaRealEstate 框架示例中找到針對 toOne 和 toMany 照片的此設計模式的實現。
將影像儲存在我們的資料庫 (OpenBase) 中,我沒有任何問題。我們開發了許多基於“社群”的網站,其中包括照片集和線上約會服務,它們都使用羅伯特在他的訊息中談到的相同方法。
我必須說,我們沒有遇到任何效能問題,對我來說,這是最優雅、最可擴充套件的解決方案,原因如下:
- 您可以使用所有可用的資料庫叢集選項來實現資料冗餘和資料備份。
- 如果您的網站規模大幅增長,並且您需要多個 HTTP 伺服器(以及多個應用程式伺服器),如果您的影像由本地檔案系統中的 Apache 提供,那麼您將遇到必須以某種方式將影像目錄複製到多個 Apache 伺服器的問題。我並不是說這不可能做到……只是需要進行適當的規劃。使用資料庫作為影像儲存將解決所有這些問題。
- 使用資料庫方法,WebObjects 將快取您的影像,因此您實際上不必每次都訪問資料庫。
- 如果您需要將影像與其他物件“關聯”,我討厭我的影像被儲存在檔案系統中。最終您很可能會遇到損壞的連結等問題……這會變得相當混亂。此外,如果您需要控制對影像的訪問(例如,只有登入使用者才能檢視產品影像),您將需要依賴檔案系統/Apache 安全性,這為您的應用程式增加了另一層複雜性。
再說一次,我知道很多人可能不同意這種方法。但是,它對我們非常有效,對於動態影像(或使用者可以更改/上傳的影像),我認為這是最有效的方法。也就是說,我們確實使用 Apache 提供靜態影像。
我很想聽聽其他人和他們在將影像儲存在資料庫中的經歷。您會聽到很多人說“不要這樣做,它不會執行良好”……但這些人真的嘗試過嗎?或者他們只是被告知不要這樣做。我已經對這個主題很感興趣一段時間了,並且我已經進行了廣泛的搜尋,但從未找到任何“正確”的答案。我認為這也取決於您使用的資料庫以及資料庫本身儲存影像的方式。我知道有些資料庫比其他資料庫好得多,而我個人認為,這就是您最有可能遇到效能下降(如果有的話)的地方。
在 Fortnum & Mason 線上商店 http://www.fortnumandmason.com 上,產品目錄包含很多影像。此外,他們 (F&M) 至少每年更改目錄和相關影像兩次。因此,我編寫了一個工具,允許他們將產品影像上傳到資料庫中,僅僅是為了將所有內容放在一個地方以進行備份。當準備好部署新的目錄時,將從資料庫中提取影像並放置在 Web 伺服器下(因為正如每個人都指出的那樣,Web 伺服器在分發影像方面特別擅長)。然後,主要的 F&M Web 應用程式將從 Web 伺服器獲取所有影像,而不是在從資料庫獲取後在 WebObjects 應用程式中進行快取。
但是,在開發過程中,我們直接使用來自資料庫的影像。命令列開關切換影像是否從 Web 伺服器或資料庫讀取。
執行所有這些操作意味著記憶體佔用更小,因為應用程式不會快取影像,並且它還意味著我們可以對 Web 伺服器執行一些巧妙的操作來稍微分散負載。
Chuck Hill 昨天在關於使用 Web 伺服器的優缺點方面寫了一些內容 - http://lists.apple.com/mhonarc/webobjects-dev/msg05564.html(使用“archives”、“archives”作為使用者名稱/密碼)。
我個人的意見和經驗僅供參考,我兩種方法都試過。我一直都在討論這個問題,所以我決定把它寫下來,放在一個地方。
資料庫的優點
- 非常容易。
- 效果很好。
- 與其他所有東西保持一致;也就是說,所有資料都來自資料庫。
資料庫的缺點
- 如果你使用 BLOB(或等效物),則在 BLOB 周圍會有一些調整問題。
- 我不理解人們所說的“它解決了單點故障”。難道你只是將單點故障從檔案系統轉移到了資料庫?當然,資料庫提供了複製功能,但你也可以使用可叢集的檔案系統(如 Transarc 或 Andrew File System (AFS))來實現相同的目的。
- 這“毫無意義”。它只是將一堆非關係資料儲存在關係資料庫中。感覺很髒 :-)
- 如果你需要操作資料,這將變得非常困難。例如,如果它是文字,並且你想修復一個拼寫錯誤,那將很麻煩。如果是影像,更新 EXIF 標題將是一項苦差事。使用 Perl 指令碼處理目錄中的檔案要容易得多。
檔案系統的優點
- 它更具可擴充套件性。
- 如果需要,操作影像更容易。特別是離線操作。
- 它更便宜。(磁碟在成本方面比資料庫便宜,尤其是在你需要 DBA 的情況下)。
檔案系統的缺點
- 它稍微難以管理。你可能仍然需要一個數據庫來跟蹤影像。
- 構建可以上傳影像的內容管理系統更難。WebDAV 可能會解決這個問題。
好吧,這是我的兩分錢。
將它們儲存在資料庫中的一大問題是,EOF 會快取資料,至少會快取一段時間。對於負載很高的網站或內容很大的網站,這會很快消耗掉記憶體。
另一種方法是將它們儲存在檔案系統中,但不能直接提供給 Web 伺服器。在資料庫中保留一個物件,該物件引用檔案系統中的資料。使用 Java 流將資料從請求移動到檔案系統,然後從檔案系統移動到響應。這避免了 EOF 開銷,但允許應用程式控制訪問。讓 Web 伺服器直接訪問和分發影像等效率更高,但如果你有訪問限制,這將不可行。這種混合資料庫/檔案系統方法在這種情況下很有用。
PetiteAbeille 寫了一篇關於 EOF 檔案系統介面卡的文章,這篇文章可能與這個問題相關:http://www.wodeveloper.com/omniLists/eof/2002/June/msg00053.html
我們在 WebObjects 應用程式(電子航海日誌)中從資料庫獲取影像。這是一個訪問頻率非常高的網站,允許使用者建立包含文字、影像和其他附件的條目。我們發現了從資料庫載入影像的其他“優點”。
資料庫的其他優點
- 航海日誌不是唯一使用我們資料庫的應用程式。我們只使用資料庫的一部分。其他應用程式寫入資料庫,並向我們的航海日誌新增條目,並使用我們的航海日誌中的資料。將完整的航海日誌條目(影像、文字、附件)儲存在資料庫中意味著其他應用程式可以輕鬆訪問航海日誌條目。因此,我們很好地集成了。
- 資料庫是所有資料的唯一官方來源
- 備份一致且容易。備份資料庫時,會備份所有航海日誌資料,並且備份是自一致的。
- 將資料從一臺機器轉移到另一臺機器更容易,因為你只需要轉移一項內容。
- 由於影像由使用者提供,因此資料庫提供了一種避免檔名衝突的方法。如果你使用檔案系統,你需要小心儲存檔名以避免重複。
- 我們幾年前開發的第一個航海日誌(沒有 WO)將包括影像在內的資料儲存在檔案系統中。事實證明這非常不靈活。如果你想重新組織資料夾佈局,許多連結將斷開。對於資料庫儲存來說,這根本不是問題。
- 資料庫 simply 更靈活。檔案系統儲存非常嚴格。你可以將檔案儲存在當前應用程式中,但需求會隨著時間的推移而發生變化,資料庫儲存在支援未來需求方面更加靈活,而這些需求你今天可能還沒有考慮到。
選擇資料庫儲存還是檔案系統儲存實際上取決於你的應用程式。對於我們的應用程式來說,電子航海日誌正越來越多地與其他系統整合,而資料庫在這項整合中變得至關重要。
我很樂意將影像儲存在資料庫中,但是...我的客戶使用 Oracle 或 FrontBase。但是,我現在不幸的是,我正在參與一個必須使用 MS-SQL 的應用程式:似乎它真的不支援 BLOB(實際上,資料庫管理員只是告訴我“不要在你的表中使用 BLOB,永遠不要——我們對它們有最糟糕的體驗”)。
我自己當然也嘗試過 :)(使用測試資料庫),發現確實存在一些問題,例如 BLOB 永遠不會被 WHERE 子句找到(即使證明給出了正確的值)。我沒有測試很長時間 :)
因此,雖然我堅信將影像儲存在資料庫中,但我可以理解其他不幸沒有使用 FrontBase 的使用者可能會有不同的意見 :)
我寫這篇文章的原因是:在決定將影像儲存在何處之前,請檢查要使用的具體資料庫。如果是 FrontBase 或 Oracle,你可能希望將它們儲存在資料庫中,如果是 MS-SQL,你可能希望將它們儲存在檔案系統中 :)
我現在有點生疏了,所以請原諒我在這方面有任何失誤。WO 5.3 重新燃起了我對 WebObjects 的興趣。
我已經對使用資料庫中的影像進行了一些實驗,並且通常發現,如果你做對了,與檔案系統方法相比,這樣做幾乎沒有效能影響——資料庫在處理 blob 資料方面總體上有了很大改進,許多不儲存影像的理由 simply 不再適用。此外,將影像儲存在資料庫中使編寫、維護和備份應用程式變得容易得多。
通常,我會為分發影像建立直接操作方法。在你的 DirectAction 中,這將類似於以下內容(除了你可能希望新增驗證,如果你不想讓任何人透過篡改 URL 來獲取你的影像):
public WOActionResults imageAction()
{
// PictureTest is an EOEntity with a BLOB containing the image data
PictureTest pt = getPictureTestEO();
return jpegResponseWithData(pt.image());
}
private PictureTest getPictureTestEO()
{
// Yes - you can get the session in a direct action
// you just need to be prepared to deal with one not existing
// whether you return an image if no session exists depends on
// on your own application needs.
WOSession theSession = existingSession();
EOEditingContext ec = (theSession == null) ? new EOEditingContext() : theSession.defaultEditingContext();
String picid = (String)request().formValueForKey("picid");
return (PictureTest)EOUtilities.objectMatchingKeyAndValue(ec, "PictureTest","id", new Integer(picid));
}
private WOResponse jpegResponseWithData(NSData theData)
{
// This method returns the data so that the browser
// recognizes the image type. In this particular application
// I've just hardcoded a mime type of JPEG because I only
// use JPEG images, but a better way would be to store the mime-type
// that corresponds to the image data in the BLOB as a separate
// field. I might revise this sample later on to show that.
WOResponse response = WOApplication.application().createResponseInContext(context());
response.appendHeader("image/jpeg", "Content-Type");
response.appendContentData(theData);
return response;
}
然後,在你的 WOComponent 中,你可以建立一個虛擬訪問器,如下所示
public String imageURL()
{
return context().directActionURLForActionNamed("icon", null) + "?picid=" + pictureItem.id();
}
在本例中,pictureItem 是我在 WORepetition 中使用的 PictureItem EOEntity 例項——我只是從當前選擇的圖片中提取 ID 號。
然後,在 WOBuilder 中,將 WOImage 或 WOActiveImage 的 src 繫結繫結到 imageURL
這種方法消除了透過直接繫結到 EOEntity 的屬性而獲得的許多開銷,並且實際上沒有做太多額外的工作。
將影像儲存在資料庫中通常不是一個好主意,原因很多。首先,它比直接從 Web 伺服器提供影像要慢得多,並且它完全繞過了從檔案系統提供影像時存在的許多自動“最佳化”。如果無法避免,那就無法避免……但是,如果你希望將你的解決方案擴充套件到一個龐大的使用者群或高訪問率,那麼預計將花費大量的工程和硬體成本才能使資料庫中的影像快速執行。
特別地
影像通常從檔案系統中靜態提供...因為你現在將它們作為動態內容提供,所以會產生以下效能影響
- 沒有客戶端快取 [糟糕] 頁面上的單個影像的五個副本會在 WO 上產生五個單獨的命中。
- 影像請求必須序列化——不僅 IMAGE 命中必須序列化,而且 WOF 應用程式上的所有其他命中都必須等待任何掛起的影像命中處理完畢。就 Netscape 的 REALLY SLOW 表佈局演算法而言,該演算法需要知道所有影像的大小,這意味著使用者在 ALL 影像命中至少返回影像大小之前,將看不到表格的內容……由於命中是序列化的,這意味著除了最後一個影像之外的所有影像都必須被完全處理。
- 靜態命中與完全動態命中之間的效能差異非常大 [有利於靜態命中]。想想看...靜態命中基本上意味著 Web 伺服器開啟一個檔案,將內容讀/寫到套接字,關閉...動態命中需要 IPC、資料庫往返 [可能]、一堆記憶體操作、請求/響應傳遞等等等等...
- 沒有伺服器端快取;你的應用程式的每個例項最終都會在其記憶體中儲存一個每個影像的副本。同樣,資料庫和 WO 應用程式伺服器之間的 IPC 也必須來回傳遞所有這些資料。
- 大多數資料庫並不擅長處理 BLOB……無論如何
替代方法?
在你的資料庫中放一個檔案系統中的路徑,而不是一個 blob;抽象地進行任意設定以方便管理等等...如果你真的需要影像來自資料庫,那麼構建一個影像管理器,該管理器維護檔案系統中影像的層次結構,並仲裁資料庫與影像之間的更新。
一個想法:如果需要重新整理影像並且擔心客戶端或代理防火牆快取,請在檔案系統中重新命名影像(或移動它)並生成一個新的 URL - 這應該是影像管理器的責任。
我同意上面所說的大部分內容,但讓我經常將影像和檔案移入資料庫的一個考慮因素是安全性。如果沒有安全考慮,那麼將它們放在檔案系統上並直接從 Web 伺服器提供它們是相當合理的(您最大的挑戰是保持一切同步...確保粗心的管理員或病毒掃描器不會來刪除/重新命名/更改檔案從您的應用程式中)。但是,您經常會處理諸如“這組使用者可以閱讀此 PDF”或“只有特定級別的註冊使用者才能看到此影像”之類的用例。在這些情況下,您透過動態提供 PDF 或影像而遭受的打擊是為嘗試實現其他訪問控制(例如,物理分離檔案並使用 .htaccess 或 Windows 檔案許可權)而付出的管理頭痛的小代價。以我的經驗,大多數針對此問題的解決方案最終都會採用代理安排 - 應用程式讀取資源然後將其寫回客戶端 - 在這種情況下,影像可能已被很好地儲存在資料庫中。