iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0
Software Development

學校沒教的後端生存指南:30 天打造非同步 S3 微服務,部署 K8s 實現 HA 架構系列 第 13

[ Day 13 ] AWS S3 清理 : 利用生命週期規則(Lifecycle Rules)與版本控制自動清理孤兒與歷史檔案

  • 分享至 

  • xImage
  •  

昨天我們完成了 Idempotency 冪等性的實作!讓前端在發起請求前先生成唯一的 uploadId,並將此 ID 作為 S3 Key。搭配昨天的 S3 User Metadata 檔名還原與 Semaphore 控流閘,我們的應用層三層防禦大功告成!

這時候,我們再來思考一個問題:雖然在 Java 程式碼層面成功防堵了重複物件的產生,但那些在網路超時(Timeout)斷線、被使用者中途放棄的上傳垃圾,以及重試覆蓋後的歷史版本,現在到底流落到哪裡去了?

今天我們不寫任何 Java 程式碼,把視角拉高到 雲端基礎設施維運(Cloud Infrastructure & DevOps)的層次。我們將深入 AWS 雲端,實戰配置 S3 物件版本控制(Versioning)與生命週期規則(Lifecycle Rules),實現 0 開發成本、0 系統負載,由 AWS 官方在背景幫我們自動消滅儲存計費吸血鬼的防禦!

既然 Java 實作 Double-Check 與限流,為什麼還需要 版本控制與生命週期?

它們兩者在分散式架構中解決的是不同維度的問題,兩者是互補的共生關係:

  • Java API 層面(當下交易的修復):解決的是當下使用者的交易完整性與體驗
  • AWS S3 Lifecycle 層面(事後計費清運):解決的是事後防禦所產生的計費垃圾與成本控制

為什麼 S3 Lifecycle 無法取代 Java API 的防禦?

  • S3 Lifecycle 規則是非同步背景運作的(通常一天只執行一次):當 PUT 網路超時發生在 12:00:00 時,微服務如果沒有實作 Double-Check 的 headObject 探針,就無法在 12:00:01 回應給使用者,使用者會立刻看到 500 失敗,交易流程被迫中斷,體驗被摧毀。
  • S3 託管規則無法保護本機硬體資源:如果沒有 Java API 的 Semaphore 背壓限流,海量請求在微秒級湧入時,你的 JVM 執行緒與本地網卡會在瞬間卡死爆炸,S3 生命週期規則對此根本無能為力。
  • 沒有 Java 的 Idempotency(冪等性),S3 Lifecycle 根本不敢幫你清垃圾:如果前端在失敗重試時每次都生成不同的 UUID(如 file_v1.jpg、file_v2.jpg、file_v3.jpg),在 AWS 眼中,這三個檔案都是「正常、全新、不同名」的物件!S3 根本不知道它們是內容相同的重複垃圾,它絕對不敢擅自刪除。唯有 Java 做好冪等,限制重試只能寫入同一個 Key,S3 才會觸發「版本覆蓋」產生歷史版本,這時 S3 Lifecycle 才能依據規則將其清除。

為什麼 Java API 也離不開 S3 Lifecycle?

  • 當我們在 Java API 做了完美的冪等覆蓋(Key A ➜ Key A),雖然 S3 桶子在表面上看起來只有一個 Key A 物件,但因為我們的儲存桶開啟了 Versioning (版本控制),舊的 Key A 歷史版本依然堆在背景天天對你計費。
  • 當網路去程丟包,大檔案傳輸中斷,會產生「分段上傳碎片(Incomplete Multipart Uploads)」,這在應用層是完全隱形且無法用 Java API 直接探測到的。
  • 如果我們在 Java 裡寫排程(@Scheduled)去天天 LIST S3 並呼叫 DELETE 清理,會壓垮微服務的 CPU、引發 OOM,且產生高昂的 S3 LIST 呼叫費!

因此,最完美的解法是用 Java 程式在當下防衛主動自我修復,用 S3 託管規則在事後 0 成本自動清理!

認識 S3 的計費真相與隱形吸血鬼

AWS S3 核心計費項目表

雲端服務的設定中有很多眉眉角角,有時候調整了一些參數設定可以在效能不變很多的狀況下大幅降低成本,例如我們在建立service或是VM的時候其實可以把最低核心數量設為0(通常預設為1或他會建議你至少要寫1,但根據個人需求有時候其實不用),s3 的儲存服務也是一樣,我們先來看一下他的計費方式:

計費項目 計費維度 東京區域標準儲存單價 (USD) 備註
儲存空間費 (Storage) 每月儲存總 GB 數 $0.023 / GB 歷史版本與分段碎片皆算入此總量
Class A 請求費 (API) PUT, COPY, POST, LIST $0.005 / 1,000 次 費用極高!用 Java 寫 LIST 掃描是預算大忌
Class B 請求費 (API) GET, HEAD, Select $0.0004 / 10,000 次 費用極低,double check 選 HEAD 的原因
資料傳出費 (Data Transfer) 傳出到 Internet 的總 GB $0.09 / GB 傳入(Inbound)至 S3 是免費的

在這個計費結構下,如果我們放任系統累積以下兩種隱形垃圾,雲端帳單很快就會失控:

  • 吸血鬼一號:被隱藏的歷史版本(Noncurrent Versions)

    • 在企業級的生產環境中,為了防範駭客勒索或人為誤刪,資安政策通常會強制儲存桶開啟版本控制(Versioning)功能。這意味著檔案被覆蓋時,舊檔案不會消失,而是被隱藏起來
    • 如果只開啟 Versioning,然後壓力測試時為 S3 Key 實現了冪等性,讓前端重試時直接覆蓋同一個檔案。每一次覆蓋,舊的檔案並不會被真正抹除,而是被標記為 Noncurrent Version(非最新版本)留在雲端
    • 使用者在前端只能下載到最新的檔案,但在 AWS 的帳單裡,它會對所有舊版本累積計費。如果沒有生命週期規則,這些因重試產生的歷史垃圾將無止盡地消耗預算。
  • 吸血鬼二號:大檔案傳輸中斷留下的未完成分段上傳(Incomplete Multipart Uploads)

    • 實務上aws再上傳大檔案的時候會經歷三個階段,建立Initiate、上傳upload、合併complete,當使用者上傳數百 MB 的大檔案時,AWS SDK 會自動將檔案切成多個 Part 分段上傳
    • 如果傳輸到 99% 時使用者進地下室斷網了,且前端沒有呼叫 AbortMultipartUpload,這些已經上傳的幾百個 Part 碎片會永久暫存在 S3 伺服器中
    • 這些碎片在 AWS S3 Console 的預設網頁介面上是完全看不見的!你的 Bucket 表面上空空如也,但每個月卻會收到暴增的儲存帳單!

配置示範:開啟 Versioning 後,用 API 進行本地測試

Versioning 是 Amazon S3 的一項功能,用來保留、擷取和還原 S3 儲存貯體(Bucket)中每個物件(Object)的所有版本,有以下特點 :

  • 防止誤刪與覆寫:當你覆寫或刪除檔案時,S3 不會永久抹除舊資料,而是新增一個版本或加上刪除標記(Delete Marker),讓你可以輕鬆救回檔案。
  • 完整歷史紀錄:同一個檔名可以對應多個不同時間點上傳的內容,方便追溯與還原。
  • 狀態切換:版本控制有三種狀態:未啟用(Un-enabled)、已啟用(Enabled)、已暫停(Suspended)。注意版本控制一旦啟用過就無法完全刪除該狀態,只能選擇暫停。

沒有設定versioning

由測試內容可以看到我們上傳兩次id結尾是11的檔案,然後透過查詢發現s3只有一筆資料
https://ithelp.ithome.com.tw/upload/images/20260908/20183864UlQMcQv1BG.png

從aws console來看也只有一筆檔案
https://ithelp.ithome.com.tw/upload/images/20260908/20183864YrboYxAmI5.png

打開versioning 版控

  1. 進入AWS S3 Console,點擊你的 Bucket,進入 Properties (屬性) 頁籤。

  2. 找到 Bucket Versioning (儲存貯體版本控制),點擊 Edit,將其改為 Enable (啟用) 並儲存。
    https://ithelp.ithome.com.tw/upload/images/20260908/20183864kZsLlAQEUv.png

    1. 再次上傳兩個相同的內容,發現雖然在list api看起來還是只有一個成功上傳的檔案
      https://ithelp.ithome.com.tw/upload/images/20260908/20183864u9pklFtlJx.png
  3. 但是從aws console來看就可以看到有版本迭代的紀錄!
    https://ithelp.ithome.com.tw/upload/images/20260908/20183864jbRPFbcb6V.png

透過前面兩個實驗可以發現,雖然不啟用 Versioning 似乎可以直接省去歷史版本清理的麻煩,但在企業級高安全規格的生產環境中,為了防範人為手殘誤刪、或是駭客勒索軟體惡意篡改,100% 的 S3 儲存桶都會被資安政策強制要求開啟 Versioning!

配置示範:在 AWS S3 Console 架設自動清理防線

現在,讓我們點開 AWS S3 控制台,親手為我們的儲存桶拉起這條 0 運維成本的生命週期自癒防線。

步驟 1:建立生命週期規則

  1. 登入 AWS S3 Console
  2. 點擊你建立的 Bucket(例如 ithome-netty-s3-demo),切換到 Management(管理)頁籤
  3. 在 Lifecycle rules(生命週期規則) 區塊中,點擊 Create lifecycle rule(建立生命週期規則)

https://ithelp.ithome.com.tw/upload/images/20260908/20183864rkyGRJ1Zgs.png

步驟 2:配置自癒規則 A ── 自動清理未完成的分段上傳

這條規則能徹底防堵斷線遺留的隱形碎片計費黑洞:

  1. Rule name(規則名稱):填入 CleanIncompleteMultipartUploads
  2. Rule scope(規則範圍):選擇 「Apply to all objects in the bucket(套用至儲存貯體中的所有物件)」,並勾選下方的確認宣告
  3. Lifecycle rule actions(生命週期規則動作):
    • 勾選 Delete expired object delete markers or incomplete multipart uploads(刪除過期的物件刪除標記或未完成的分段上傳)
  4. 設定清理天數:
    • 勾選 Delete incomplete multipart uploads(刪除未完成的分段上傳)。
    • 在下方出現的 Deplay in days 欄位: 7 。(這行設定代表:只要有分段上傳任務在發起後 7 天內沒有被 Complete,AWS 底層會自動在背景將其所有碎片徹底刪除,1 MB 都不留!)
  5. 點擊 Create rule(建立規則)。
    https://ithelp.ithome.com.tw/upload/images/20260908/20183864bYx0vFugpv.png

步驟 3:配置自癒規則 B ── 永久刪除過期的歷史版本

如果你的 Bucket 開啟了版本控制(Versioning),我們必須確保重試產生的舊版本不會無限期霸佔空間:

  1. 在同一個管理頁面,點擊 Create lifecycle rule 建立第二條規則
  2. Rule name(規則名稱):填入 ExpireNoncurrentVersions
  3. Rule scope(規則範圍):同樣套用至所有物件,並確認套用宣告
  4. Lifecycle rule actions(生命週期規則動作):
    • 勾選 Permanently delete noncurrent versions of objects(永久刪除物件的非最新版本)
  5. 設定過期天數與保留版本限制:
    • Days after objects become noncurrent(物件變成非最新版本幾天後刪除): 3
    • Number of newer versions to retain(要保留的較新歷史版本數,選填): 1(這代表我們最多只幫使用者留 1 個最近的舊版備份,其餘更舊的在 3 天後全部清除,安全又省錢!)。
  6. 點擊 Create rule(建立規則)
    https://ithelp.ithome.com.tw/upload/images/20260908/20183864Xlh7kHe29g.png

如此一來時間到之後s3就會自動幫你清理一些孤兒檔案啦!

S3 Lifecycle 的 IaC JSON 宣告

在現代高規格的雲端原生(Cloud Native)維運中,資深架構師極少手動登入網頁點擊配置。我們通常會使用 IaC(基礎設施即代碼,如 Terraform 或 AWS CloudFormation),或在初始化專案時直接呼叫 API 來套用規則。

以下是我們在背景默默運行的這兩條生命週期防禦線的標準 JSON 配置。看懂這個底層結構,能讓你對 S3 的微觀運行邏輯有更精準的掌控力:

{
    "Rules": [
        {
            "ID": "CleanIncompleteMultipartUploads",
            "Status": "Enabled",
            "Filter": { "Prefix": "" },
            "AbortIncompleteMultipartUpload": {
                "DaysAfterInitiation": 7
            }
        },
        {
            "ID": "ExpireNoncurrentVersions",
            "Status": "Enabled",
            "Filter": { "Prefix": "" },
            "NoncurrentVersionExpiration": {
                "NoncurrentDays": 3,
                "NewerNoncurrentVersions": 1
            }
        }
    ]
}
  • AbortIncompleteMultipartUpload 刪除: DaysAfterInitiation: 7 代表 AWS S3 每天會在背景定時掃描。只要發現某個大檔案上傳任務發起超過 7 天卻依然處於 Incomplete(未完成)狀態,底層機制就會自動將其所有隱形碎片徹底抹除,連 1 KB 的快取都不留給你計費。

  • NoncurrentVersionExpiration 收斂: 搭配我們 Day 12 的冪等重試,NoncurrentDays: 3 與 NewerNoncurrentVersions: 1 的組合堪稱完美。當檔案被新版本覆蓋後,最多只幫我保留 1 個最近的舊版備份容錯。其餘更舊的歷史版本,請在 3 天後自動永久刪除。這在資料安全與儲存成本之間,取得了最完美的平衡。

總結

今天,我們跳脫純程式碼開發,以一個更宏觀的視角,完成了我們微服務背後最關鍵的基礎設施自動化清理機制。並且學習到:

  1. Double-Check 與 Idempotency 覆蓋重試 雖然救回了當下交易的狀態,但物理上依然會在 S3 產生歷史版本與斷線分段碎片的計費垃圾。
  2. 不盲目用 Java 寫排程,而是利用 AWS 內建的 S3 Lifecycle Rules,以 0 程式碼、0 微服務硬體開銷,自動在背景消滅計費黑洞。

明天我們將在本地開發環境中,引入混沌工程(Chaos Engineering),教大家如何用 Linux tc 與 macOS pfctl 等工具,親手把本機的網路卡模擬成惡劣網路環境,來對微服務進行更貼近真是現況測試!

參考資料


上一篇
[ Day 12 ] 前端重試一百次也絕不產生髒資料!實作客戶端重試與 Idempotency 設計
下一篇
[ Day 14 ] 初探混沌工程(Chaos Engineering ):本地端用 tc / Clumsy 製造網路延遲與封包遺失
系列文
學校沒教的後端生存指南:30 天打造非同步 S3 微服務,部署 K8s 實現 HA 架構14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言