一)前言
七牛雲存儲、阿里雲OSS、騰訊雲COS、華為雲OBS等都是典型的對象存儲服務,本篇文章會依次介紹一下我們在最近幾年使用過程中遇到的問題,同時也介紹一下目前為止我們在項目中的一些實踐經驗(爬坑記錄),或許對你有一些幫助。
二)數年經驗積累
2.1 石器時代,一片混沌
由於同一個程序需要多機部署,從14年開始我們的項目就不再把文件保存到服務器磁盤上了,而是統一上傳到對象存儲服務中,然后把拼接后的HTTP地址保存到數據中。因為當時連運維都沒有,嫌麻煩所以直接使用的是七牛雲給的域名,地址類似於這樣的 "https://7xicvf.com1.z0.glb.clouddn.com/xxx.ext"。
除了域名很丑之外,很長一段時間內都沒出現問題,直到有一天早上我們的用戶都在群里面反饋APP中的資源無法下載,我們一看原來是七牛雲給的這個域名被停止解析了(后面了解到因為對應的一級域名涉及到“兒童澀情”,在全球范圍內被停止解析),當時我們就懵了,數據庫那么多URL太多了,涉及到各種表且分散在很多字段里面,看來快速在數據庫中替換URL不現實了,還好我們用的是PHP框架,有一個統一的入口和出口,我們就在出口處進行了string replace替換,暫時解決了這個問題。
經過這件事情后七牛雲也強烈建議我們綁定自己的業務域名,而且在控制台也聲明提供的域名只供測試,做了一些措施來防止誤使用,例如會限制資源下載帶寬、瀏覽器中訪問資源地址是直接當成附件下載而不在瀏覽器中顯示等手段。我們隨后也就綁定了業務域名,並且在數據中進行了資源替換,但因為涉及到很多表、很多列,所以替換起來很費力氣,這也在我心里埋下了一個種子,希望有一天能夠給出一個比較完美的方案來解決遇到的這些問題。
2.2 青銅時代,暴露問題
隨着公司業務規模的擴大,為了折扣或者進行一些資源互換所以就得接入更多的服務商,雖然內心是抵觸的,但還是得提供對應的支持才行,所以我在這個時候就接入了阿里雲OSS存儲。使用的方式還是一樣,新建一個資源桶,然后綁定公司自己的業務域名,上傳資源后就把拼接起來的URL(域名 + 路徑)放到數據庫中。
隨着團隊的不斷擴大,存儲的資源場景就越來越多,有私有讀的場景、也有公開讀的場景,甚至很多時候干脆會為某個業務分配一個全新的桶,讓大家愛怎么折騰就怎么折騰。如果大家的資源都放一個桶的話,當時又做不到在程序中對路徑前綴強制隔離,有些操作就不方便,例如想備份某種非常重要的資源那就得把整個存儲桶內容都備份才行,因為不容易區分哪些資源屬於哪個業務的(讓業務同學從數據庫導出資源更麻煩,同樣因為資源會放到很多表、不同列中)。另外由於涉及到很多桶,會綁定很多個業務域名,那前端對接的時候可能就要在后端設置很多跨域的域名配置(除非是設置為*,允許所有域名的跨域請求,但這會出現安全性的問題)。
鐵器時代,初有想法
針對上面的一些場景,總結此時遇到的問題:
-
- 存儲桶綁定的業務如果發生更換。 因為數據庫中寫入的是拼接的HTTP URL(業務域名 + 存儲桶中的路徑),倘若業務域名由於某些原因被替換了,那就得替換數據庫中所有的資源地址。
- 存儲桶倘若要經常變換綁定域名。業務發展過程中會嘗試很多種CDN服務商,每次更換CDN服務商就得在數據庫中替換一次資源,雖然可以在網關處替換,但其實不推薦。
- 存儲桶分配得太多難以管理。項目越來越多、人越來越復雜,為了避免互相干涉就只能不斷分配新的桶,需要分配非常多的域名,管理起來非常累。
針對上述的問題,我們進行嘗試通過中間件的方式來解決,因此誕生了第一個版本的存儲中間件,雖然后面也收到了很多吐槽,不過沒關系,有了吐槽才會有后面不斷完善的解決方案,存儲中間件設計詳情請看下文。
三)存儲中間件
3.1 蒸汽時代
其實中間件所有的內容都是圍繞着我們自定義的存儲協議展開的,存儲協議的資源地址是這樣的:oss://spaceKey/fileKey.ext,分為三部分內容:
-
- oss,為固定的協議名稱。
- spaceKey,表示存儲自定義存儲空間,一個存儲空間會綁定到某個服務商對象存儲桶的某個路徑下。
- fileKey.ext 表示文件名(含擴展名),當然 fileKey 中也可以包含 "/",因為在對象存儲中並沒有文件夾的概念,是模擬的。
針對上面的協議,我們的中間件需要包含以下部分內容:
-
- 管控系統接口,供下面的存儲組件SDK在內網調用。允許新增存儲空間,並且關聯到某個服務商某個存儲桶的某個路徑下,還可以為某個存儲桶配置多個CDN域名。
- 存儲組件SDK,供業務Web應用使用。服務端程序內部進行調用時都通過ossUrl(oss://spaceKey/fileKey.ext這種格式統稱為OssUrl,下同)進行傳遞,只有在程序返回時才通過某種機制攔截ResponseBody並把其中的 ossUrl替換為CDN地址供用戶訪問;當然也要提供一個靜態類把ossUrl轉成最終的CDN地址,兼容一些奇怪的場景需求。
3.1.1 管控系統設計
其中核心數據結構設計如案例如下,與我們實際業務代碼不同,這里主要是表達含義即可:
針對上面的數據結構相關的解釋:
-
- object_storage_space,表示存儲業務定義的存儲空間,目前有兩種訪問模式:1)公開讀私有寫2)私有讀寫,把 ossUrl 轉換成CDN地址時會用到,如果是私有讀地址則需要加上簽名信息(需要指定失效時間)。
- object_storage_vendor_bucket,表示雲服務商的實際存儲桶地址,會包含一些元數據信息,提供給存儲組件SDK進行初始化嗎,例如存儲桶地域、Endpoint等。
- object_storage_vendor_bucket_cdn,表示該服務商存儲桶綁定的CDN的地址,例如提供一個騰訊雲的CDN域名回源到阿里雲的存儲桶。priority表示優先級,倘若一個存儲桶綁定了多個CDN域名可以進行優先級排序,在存儲SDK解析CDN地址時可以用到。
- object_storage_space_bucket,表示業務自定義存儲空間關聯到某個存儲服務商存儲桶的某個路徑下,不同空間關聯不同路徑可以防止互相干涉。
3.1.2 存儲組件設計
針對上面的架構相關的解釋:
-
- 存儲SDK通過管控系統的元數據信息(配置的存儲空間信息)來初始化對應雲服務商的SDK執行文件上傳。
- 存儲SDK可以通過檢測ResponseBody(我們項目是SpringBoot為基礎框架的)中的OSS URL轉換為CDN地址供用戶訪問。
四)經驗總結
當然我們的業務系統會更加復雜一些,例如通過processor參數來適配不同雲存儲的數據處理功能,無論使用哪家雲存儲都可以提供一致的圖片處理方案(例如裁剪、水印、旋轉等)。而且也遇到了一些比較尷尬的坑,有各端配合上的、有知識短板導致方面的,這些坑不方便在這里寫下來,如有需要后面我會單獨脫敏后記錄下來。