如何避免产生存储不足规定时长容量费用?

低频访问、归档类型、冷归档或者深度冷归档有最低存储时长的要求。在存储不足规定时长的情况下,转换Object存储类型或提前删除Object会产生不足规定时长容量费用。为避免产生不足规定时长容量费用,您需要了解不同存储类型Object的最低存储时长计算方法,确保满足其最低存储时长后再进行转储或者删除。

存储类型与最低存储时长

存储类型

最低存储时长

计算方法

标准

不涉及

低频访问

30

以Object的LastModified时间开始计算

归档

60

以Object的LastModified时间开始计算

冷归档

180

以Object转储为冷归档类型的时间开始计算

深度冷归档

180

以Object转储为深度冷归档类型的时间开始计算

示例代码

以常用SDK示例代码为例获取对象元数据,并根据对象的LastModified、TransitionTime与当前时间进行比较,以判断是否满足最低存储时长的要求。

Java

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
import com.aliyun.oss.common.utils.DateUtil;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.HeadObjectRequest;
import com.aliyuncs.exceptions.ClientException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class Demo {

    public static void main(String[] args) throws ClientException {
        // yourEndpoint填写Bucket所在地域对应的Endpoint。
        String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
        // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        // 填写Bucket名称。
        String bucketName = "examplebucket";

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);

        // 指定要检查的单个对象的完整路径。
        String[] objectNames = {"example.txt"};

        // 存储类型与最低存储时长映射(单位:天)。
        Map<String, Integer> minimumRetentionPeriod = new HashMap<>();
        minimumRetentionPeriod.put("Standard", 0);
        minimumRetentionPeriod.put("IA", 30);
        minimumRetentionPeriod.put("Archive", 60);
        minimumRetentionPeriod.put("ColdArchive", 180);
        minimumRetentionPeriod.put("DeepColdArchive", 180);

        for (String objectName : objectNames) {
            objectName = objectName.trim();
            try {
                // 获取对象元数据。
                HeadObjectRequest headObjectRequest = new HeadObjectRequest(bucketName, objectName);
                ObjectMetadata objectMetadata = ossClient.headObject(headObjectRequest);

                // 获取存储类型和最后修改时间。
                String storageClass = String.valueOf(objectMetadata.getObjectStorageClass());
                String lastModifiedStr = objectMetadata.getLastModified().toString();
                Date lastModified = objectMetadata.getLastModified();

                if ("ColdArchive".equals(storageClass) || "DeepColdArchive".equals(storageClass)) {
                    Object transitionTimeObj = objectMetadata.getRawMetadata().get("x-oss-transition-time");
                    String transitionTimeStr = String.valueOf(transitionTimeObj);
                    Date transitionTime = DateUtil.parseRfc822Date(transitionTimeStr);

                    if (transitionTime != null) {
                        lastModified = transitionTime;
                    } else {
                        throw new Exception("对象 '" + objectName + "' 的存储类型为:" + storageClass
                                + ", x-oss-transition-time时间为" + transitionTimeStr + "。");
                    }
                }

                // 获取当前时间。
                Date currentTime = new Date();

                // 计算已存储时长(天)。
                long storageDuration = (currentTime.getTime() - lastModified.getTime()) / (1000 * 60 * 60 * 24);

                // 打印信息。
                System.out.println("对象名称: " + objectName);
                System.out.println("存储类型: " + storageClass);
                System.out.println("创建时间: " + lastModifiedStr);
                System.out.println("已存储时长: " + storageDuration + " 天");

                // 判断是否达到最低存储时长。
                if (minimumRetentionPeriod.containsKey(storageClass)) {
                    int minRetention = minimumRetentionPeriod.get(storageClass);
                    if (storageDuration < minRetention) {
                        int daysRemaining = minRetention - (int) storageDuration;
                        System.out.println(objectName + " 尚未达到最低存储时长,应再存储 " + daysRemaining + " 天,提前删除会产生"
                                + daysRemaining + "天的不足规定时长的容量费用。");
                    } else {
                        int daysExceeded = (int) storageDuration - minRetention;
                        System.out.println(objectName + " 已达到最低存储时长,超出部分为 " + daysExceeded + " 天,删除不会产生不足规定时长的容量费用。");
                    }
                } else {
                    System.out.println(objectName + " 的存储类型未被识别。");
                }
                System.out.println("----------------------------------------");  // 分隔符
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println("获取 " + objectName + " 元数据时出现错误: " + e.getMessage());
                System.out.println("----------------------------------------");  // 分隔符
            }
        }

        // 关闭OSSClient。
        ossClient.shutdown();
    }
}

Python

import datetime
import oss2
from oss2.credentials import EnvironmentVariableCredentialsProvider

# 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
auth = oss2.ProviderAuth(EnvironmentVariableCredentialsProvider())
# 填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
# yourBucketName填写存储空间名称。
bucket = oss2.Bucket(auth, 'https://oss-cn-hangzhou.aliyuncs.com', 'examplebucket')
# 指定要检查的单个对象的完整路径。
object_names = ['example.txt']

# 存储类型与最低存储时长映射(单位:天)。
minimum_retention_period = {
    'Standard': 0,
    'IA': 30,
    'Archive': 60,
    'ColdArchive': 180,
    'DeepColdArchive': 180
}

for object_name in object_names:
    object_name = object_name.strip()

    try:
        # 通过head_object方法获取对象的全部元数据。
        object_info = bucket.head_object(object_name)

        # 获取存储类型和最后修改时间。
        storage_class = object_info.headers['x-oss-storage-class']
        last_modified = object_info.headers['Last-Modified']

        # 将最后修改时间转换为datetime对象。
        last_modified_time = datetime.datetime.strptime(last_modified, '%a, %d %b %Y %H:%M:%S GMT')

        transition_time_str = object_info.headers.get('x-oss-transition-time')
        transition_time = None
        if storage_class == 'ColdArchive' or storage_class == 'DeepColdArchive':
            if transition_time_str:
                last_modified_time = datetime.datetime.strptime(transition_time_str, "%a, %d %b %Y %H:%M:%S %Z")
            else:
                raise Exception(f"对象 '{object_name}' 的存储类型为:{storage_class}, x-oss-transition-time时间为{transition_time_str}。")


        # 获取当前时间。
        current_time = datetime.datetime.now()

        # 计算已存储时长(天)。
        storage_duration = (current_time - last_modified_time).days

        # 打印信息。
        print(f"对象名称: {object_name}")
        print(f"存储类型: {storage_class}")
        print(f"创建时间: {last_modified}")
        print(f"已存储时长: {storage_duration} 天")

        # 判断是否达到最低存储时长。
        if storage_class in minimum_retention_period:
            min_retention = minimum_retention_period[storage_class]
            if storage_duration < min_retention:
                days_remaining = min_retention - storage_duration
                print(f"{object_name} 尚未达到最低存储时长,应再存储 {days_remaining} 天,提前删除会产生{days_remaining}天的不足规定时长的容量费用。")
            else:
                days_exceeded = storage_duration - min_retention
                print(f"{object_name} 已达到最低存储时长,超出部分为 {days_exceeded} 天,删除不会产生不足规定时长的容量费用。")
        else:
            print(f"{object_name} 的存储类型未被识别。")

        print("-" * 40)  # 分隔符

    except Exception as e:
        print(f"获取 {object_name} 元数据时出现错误: {str(e)}")
        print("-" * 40)  # 分隔符

Node.js

const OSS = require("ali-oss");

// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
const config = {
  accessKeyId: process.env.OSS_ACCESS_KEY_ID,
  accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET,
  // region填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
  region: "oss-cn-hangzhou",
  // 填写Bucket名称。
  bucket: "examplebucket",
};
const client = new OSS(config);

// 存储类型与最低存储时长映射(单位:天)。
const minimum_retention_period = {
  Standard: 0,
  IA: 30,
  Archive: 60,
  ColdArchive: 180,
  DeepColdArchive: 180,
};

// 计算时间相差天数。
function getDays(date1, date2) {
  if (!(date1 instanceof Date) || !(date2 instanceof Date)) {
    throw new Error("需要传递有效的Date对象");
  }
  const timestamp1 = date1.getTime();
  const timestamp2 = date2.getTime();
  const diffInMilliseconds = Math.abs(timestamp2 - timestamp1);
  const diffInDays = diffInMilliseconds / (1000 * 60 * 60 * 24);

  return Math.round(diffInDays);
}

// 指定要检查的单个对象的完整路径。
const object_names = ["example.jpg"];

(async () => {
  for (const object_name of object_names) {
    try {
      // 通过head方法获取对象的全部元数据。
      const object_info = await client.head(object_name);
      const { headers } = object_info.res;

      // 获取存储类型和最后修改时间。
      const storage_class = headers["x-oss-storage-class"];
      const last_modified = headers["last-modified"];

      let last_modified_time = new Date(last_modified);
      const transition_time_str = headers["x-oss-transition-time"];
      if (["ColdArchive", "DeepColdArchive"].includes(storage_class)) {
        if (transition_time_str)
          last_modified_time = new Date(transition_time_str);
        else {
          const errorStr = `对象 '${object_name}' 的存储类型为:${storage_class}, x-oss-transition-time时间为${transition_time_str}。通过生命周期转到冷归档和深度冷归档的文件,才会有x-oss-transition-time`;
          throw new Error(errorStr);
        }
      }

      const current_time = new Date(); // 获取当前时间。
      const storage_duration = getDays(current_time, last_modified_time); // 计算已存储时长(天)。
      // 打印信息。
      console.log(`对象名称: ${object_name}`);
      console.log(`存储类型: ${storage_class}`);
      console.log(`创建时间: ${last_modified}`);
      console.log(`已存储时长: ${storage_duration} 天`);

      // 判断是否达到最低存储时长。
      if (Object.keys(minimum_retention_period).includes(storage_class)) {
        min_retention = minimum_retention_period[storage_class];
        if (storage_duration < min_retention) {
          const days_remaining = min_retention - storage_duration;
          console.log(
            `${object_name} 尚未达到最低存储时长,应再存储 ${days_remaining} 天,提前删除会产生${days_remaining}天的不足规定时长的容量费用。`
          );
        } else {
          const days_exceeded = storage_duration - min_retention;
          console.log(
            `${object_name} 已达到最低存储时长,超出部分为 ${days_exceeded} 天,删除不会产生不足规定时长的容量费用。`
          );
        }
      } else console.log(`${object_name} 的存储类型未被识别。`);
    } catch (e) {
      console.log(`获取 ${object_name} 元数据时出现错误:`, e);
    }
  }
})();

PHP

<?php
if (is_file(__DIR__ . '/../autoload.php')) {
    require_once __DIR__ . '/../autoload.php';
}
if (is_file(__DIR__ . '/../vendor/autoload.php')) {
    require_once __DIR__ . '/../vendor/autoload.php';
}

use OSS\Credentials\EnvironmentVariableCredentialsProvider;
use OSS\OssClient;
use OSS\Core\OssException;

// 填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
$endpoint = 'http://oss-cn-hangzhou.aliyuncs.com';
// 填写存储空间名称。
$bucketName = 'examplebucket';

// 创建OssClient实例。
try {
    // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
    $provider = new EnvironmentVariableCredentialsProvider();
    $config = array(
        "provider" => $provider,
        "endpoint" => $endpoint,
    );
    $ossClient = new OssClient($config);
	// 指定要检查的单个对象的完整路径。
    $objectNames = ['example.txt'];
    $minimumRetentionPeriod = [
        'Standard' => 0,
        'IA' => 30,
        'Archive' => 60,
        'ColdArchive' => 180,
        'DeepColdArchive' => 180,
    ];

    foreach ($objectNames as $objectName) {
        $objectName = trim($objectName);
        try {
            // 通过headObject方法获取对象的全部元数据。
            $objectInfo = $ossClient->getObjectMeta($bucketName, $objectName);

            // 获取存储类型和最后修改时间。
            $storageClass = $objectInfo['x-oss-storage-class'];
            $lastModified = $objectInfo['last-modified'];

            // 将最后修改时间转换为时间戳。
            $lastModifiedTime = strtotime($lastModified);

            if (in_array($storageClass, array("ColdArchive", "DeepColdArchive")) && isset($objectInfo["x-oss-transition-time"])) {
                $lastModifiedTime = strtotime($objectInfo["x-oss-transition-time"]);
            }

            // 获取当前时间。
            $currentTime = time();

            // 计算已存储时长(天)。
            $storageDuration = floor(($currentTime - $lastModifiedTime) / (60 * 60 * 24));

            // 打印信息。
            echo "对象名称: $objectName\n";
            echo "存储类型: $storageClass\n";
            echo "创建时间: $lastModified\n";
            echo "已存储时长: $storageDuration 天\n";

            // 判断是否达到最低存储时长。
            if (isset($minimumRetentionPeriod[$storageClass])) {
                $minRetention = $minimumRetentionPeriod[$storageClass];
                if ($storageDuration < $minRetention) {
                    $daysRemaining = $minRetention - $storageDuration;
                    echo "$objectName 尚未达到最低存储时长,应再存储 $daysRemaining 天,提前删除会产生 $daysRemaining 天的不足规定时长的容量费用。\n";
                } else {
                    $daysExceeded = $storageDuration - $minRetention;
                    echo "$objectName 已达到最低存储时长,超出部分为 $daysExceeded 天,删除不会产生不足规定时长的容量费用。\n";
                }
            } else {
                echo "$objectName 的存储类型未被识别。\n";
            }
            echo str_repeat("-", 40) . "\n"; // 分隔符
        } catch (OssException $e) {
            echo "获取 $objectName 元数据时出现错误: " . $e->getMessage() . "\n";
            echo str_repeat("-", 40) . "\n"; // 分隔符
        }
    }
} catch (OssException $e) {
    printf(__FUNCTION__ . ": FAILED\n");
    printf($e->getMessage() . "\n");
    return;
}

Go

package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"strings"
	"time"

	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
)

func main() {
	cfg := oss.LoadDefaultConfig().
		WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
		WithRegion("cn-hangzhou").
		WithEndpoint("https://oss-cn-hangzhou.aliyuncs.com")
	client := oss.NewClient(cfg)
	// 指定要检查的单个对象的完整路径。
	objectNames := []string{"example.txt"}
	minimumRetentionPeriod := map[string]int{
		"Standard":        0,
		"IA":              30,
		"Archive":         60,
		"ColdArchive":     180,
		"DeepColdArchive": 180,
	}
        // 填写Bucket名称。
	bucketName := "examplebucket"
	for _, objectName := range objectNames {
		objectName = strings.Trim(objectName, " ")
		objectInfo, err := client.HeadObject(context.TODO(), &oss.HeadObjectRequest{
			Bucket: oss.Ptr(bucketName),
			Key:    oss.Ptr(objectName),
		})
		if err != nil {
			log.Printf("获取 %s 元数据时出现错误: %v\n", objectName, err)
			continue
		}

		storageClass := objectInfo.StorageClass
		currentTime := time.Now().Unix()
		storageDuration := (currentTime - objectInfo.LastModified.Unix()) / 60 / 60 / 24
		if *storageClass == "ColdArchive" || *storageClass == "DeepColdArchive" {
			if objectInfo.Headers.Get("x-oss-transition-time") != "" {
				transitionTime, err := time.Parse(http.TimeFormat, objectInfo.Headers.Get("x-oss-transition-time"))
				if err != nil {
					fmt.Printf("Failed to parse %s x-oss-transition-time: %v\n", objectName, err)
					continue
				}
				storageDuration = (currentTime - transitionTime.Unix()) / 60 / 60 / 24
			}
		}

		fmt.Printf("对象名称: %s\n", objectName)
		fmt.Printf("存储类型: %s\n", *storageClass)
		fmt.Printf("创建时间: %s\n", *objectInfo.LastModified)
		fmt.Printf("已存储时长: %d 天\n", storageDuration)

		if minimumRetentionPeriod[*storageClass] != 0 {
			minRetention := minimumRetentionPeriod[*storageClass]
			daysRemaining := minRetention - int(storageDuration)
			fmt.Printf("%s 尚未达到最低存储时长,应再存储 %d 天,提前删除会产生 %d 天的不足规定时长的容量费用。\n", objectName, daysRemaining, daysRemaining)
		} else {
			fmt.Printf("%s 的存储类型未被识别。\n", objectName)
		}
	}
}

更多参考

以上示例代码演示通过head_object方法获取单个对象的存储类型,并根据对象的LastModified、TransitionTime与当前时间进行比较,以判断是否满足最低存储时长的要求。如果涉及获取多个对象,可以使用GetBucket (ListObjects)ListObjectVersions(GetBucketVersions)ListBucketInventory接口。