總結阿里雲OSS的開發坑(Java篇)

1、序言

OSS(Object Storage Service)是阿里雲提供的一款雲存儲服務,具備海量、安全、低成本、高可靠的特色。html

因爲客戶選擇了OSS,咱們做爲開發方也開始接觸它。在實際開發過程當中遇到了各類各樣的坑,經本身屢次實踐及阿里技術人員的協助,終得以完成任務。安全

阿里方面爲OSS提供了多種語言的開發接口,咱們用到了其中兩種:Java和C/C++。本文爲Java篇,C/C++的將在另外一篇給出。多線程

2、OSS的一些概念

  • EndPoint, accessKeyID, accessKeySecret:欲使用OSS,先要在阿里雲上申請相應的空間資源,而EndPoint, accessKeyID, accessKeySecret則至關於域名、帳號和密碼,是所申請資源的使用憑證,須要妥善保管。
  • Bucket:是用於存儲對象的容器,全部對象都必須屬於且只屬於一個Bucket,Bucket的屬性(控制地域、訪問權限、生命週期等)對全部對象都同等有效,同一空間資源下Bucket名必須惟一,且建立後不能再更名。
  • 對象/文件:對象/文件是 OSS 存儲數據的基本單元。對象/文件由元信息(Object Meta),用戶數據(Data)和文件名(Key)組成。文件名是惟一的,重複上傳同名的對象意味着覆蓋之前內容,但OSS支持在已有對象後部追加數據。
  • 目錄:實際上是一種特殊的對象(無Data),僅僅是爲了管理方便,除此之外並沒有多大意義。
  • 其它概念,如分片、回調、追加、權限等,因開發中未涉及,再也不展開,有興趣者請參考:https://help.aliyun.com/document_detail/31827.html?spm=a2c4g.11186623.4.1.TamX1d

3、1號坑:找不到對象拋出OSSException

如今開始總結Java開發時的幾個坑,這些都是SDK文檔和示例代碼中沒有的。先從簡單的提及。ide

Java SDK提供了兩種搜素OSS對象的方法:按文件名精確匹配和按文件前綴批量查找,不支持其它模糊查詢、正規表達式查詢,也不支持按元信息查詢。這些都好理解,很少說。工具

在按文件名Key精確查找OSS對象時,若是不存在該Key對應的文件,會拋出OSSException(對應的錯誤信息是ErrorCode爲「NoSuchKey」),而不是返回null。測試

所以,須要在Java代碼里加上對該例外的捕獲處理:ui

    try
    {
        OSSObject obj = null;
        obj = client.getObject(bucketName, ossKey);
        if (obj != null)
          obj.close();
    }
    catch (OSSException e)
    {
       // OSS在查找不到某對象時,會拋出ErrorCode爲「NoSuchKey」的OSSException,而不是返回null
       if (e.getErrorCode().contains("NoSuchKey"))
       {
          System.out.println("找不到OSS文件:" + ossKey);
          continue;
       }
       else
       e.printStackTrace();
    }
    catch (ClientException | IOException e)
    {
       e.printStackTrace();
    } 

4、2號坑:分頁遍歷時的死循環

OSS Java SDK提供了分頁遍歷的功能,一頁最多能夠遍歷1000個文件。但若是一邊遍歷一邊更新對象,則很容易造成死循環。this

項目中的一個小工具的示例代碼以下:阿里雲

        String nextMarker = null;
        ObjectListing objListing;
        do
        {
            if (nextMarker == null) // 第一次的分頁
                objListing = client.listObjects(new ListObjectsRequest(bucketName).withMaxKeys(1000));
            else // 之後的分頁,附帶nextMarker
                objListing = client.listObjects(
                        new ListObjectsRequest(bucketName).withMarker(nextMarker).withMaxKeys(1000));

            List<OSSObjectSummary> sums = objListing.getObjectSummaries();
            for (OSSObjectSummary s : sums)
            {
                String ossKey = s.getKey();
                ...
                ossClient.putObject(bucketName, s, new ByteArrayInputStream(mnt.getData()));
                ...
            }
            // 下一次分頁的nextMarker
            nextMarker = objListing.getNextMarker();
         } while (objListing.isTruncated());

由於有了putObject操做(帶顏色處),運行時成了死循環,objListing.isTruncated()永遠爲false。spa

經測試,死循環不只僅出如今如上的一段代碼中既遍歷又修改的狀況下,一個進程循環寫的同時另外一個進程分頁遍歷也會出現。猜想遍歷的依據條件主要是修改時間,但無法區分已經遍歷過的對象。

貌似沒有特別好的解決辦法,咱們使用的是一種土辦法:選定一個特殊對象,再次遍歷到它即強行退出循環。

5、3號坑:循環getObject超過1024次的掛起

有一個操做須要批量讀取OSS對象,按示例代碼編寫後測試,發現一旦循環調用getObject()程序就會掛起,不繼續運行也不退出,只能強行關閉。

後來經一步步跟蹤,發現此問題是因爲getObject()後沒有及時close對象而引發,臨界值是1024(也多是1023)。

        List<String> pks; //存放的是ossKey
        for (String pk : pks)
        {
            HSBJMntDataPK pk = pks.get(i);

            OSSObject obj = null;
            try
            {
                obj = ossClient.getObject(bucketName, pk);
            }
            catch (OSSException e)
            {
                if (e.getErrorCode().contains("NoSuchKey"))
                    continue;
                e.printStackTrace();
            }
            catch (ClientException e)
            {
                e.printStackTrace();
            }

            // 處理obj的代碼,略過

            try // 及時釋放OSSObject,不然循環達到1024次會suspend
            {
                obj.close();
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
        }

增長了顏色標出的obj.close()後,再也不發生程序掛起的現象。

6、4號坑:關於UserMetaData中的大小寫

 若是OSS對象帶有一些簡單的自定義屬性,好比本項目中用到的建立者、版本、類型、備註等,能夠做爲UserMetaData存放到元信息(Object Meta)中。與把它們合併到Data中的方式相比,這樣作不但簡化了Data的構造和解析過程,還能夠縮短讀寫時間,在本項目中能提升速度30%左右。

可是,一開始把屬性值寫入元信息後,讀取時卻讀不到值。後來發現,UserMetaData的key值按照項目習慣是首字母大寫的,但在寫入時OSS都自動轉換爲全小寫處理,讀取時再按首字母大寫就讀取不到。在將UserMetaData的key值改成全小寫後,問題解決。

    public static ObjectMetadata buildMeta(MntData mnt)
    {
        ObjectMetadata meta = new ObjectMetadata();
        // UserMetadata中,key會被轉換爲所有小寫,因此爲統一賦值時也用小寫
        meta.addUserMetadata("author", author);
        meta.addUserMetadata("version", version);
        meta.addUserMetadata("type", type);
        meta.addUserMetadata("purpose", purpose);
        return meta;
    }

    public static MntData parseObject(OSSObject ossObject)
    {
        ObjectMetadata meta = ossObject.getObjectMetadata();
        Map<String, String> metadata = meta.getUserMetadata();
        MntData mnt = new MntData();
        // 從UserMeta獲取value時,key必須爲全小寫
        mnt.setAuthor(metadata.get("author") == null ? "-" : metadata.get("author"));
        mnt.setVersion(metadata.get("version") == null ? "1.0" : metadata.get("version"));
        mnt.setType(metadata.get("type") == null ? "-" : metadata.get("type"));
        mnt.setPurpose(metadata.get("purpose") == null ? "" : metadata.get("purpose"));
        // UpdateTime爲OSS自帶的元數據
        mnt.setUpdateTime(new Timestamp(meta.getLastModified().getTime()));

         mnt.setData(getBytesFromObj(ossObject));

        return mnt;
    }

7、經驗:使用多進程可提高速度

注意這不是坑,而是一條有益經驗:不管是讀仍是寫,使用多線程可顯著提高批量操做的速度。

如下是寫OSS進程的示例代碼:

public class OssPutThread extends Thread
{
    List<MntData> mnts;
    int index;

    public OssPutThread(List<MntData> mnts, int index)
    {
        this.mnts = mnts;
        this.index = index;
    }

    @Override
    public void run()
    {
        // 線程內新生成一個OSSClient
        OSSClient ossClient = new OSSClient(endPoint, accessKeyId, accessKeySecret);
        for (int i = 0; i < mnts.size(); i ++)
        {
            HSBJMntData mnt = mnts.get(i);
            String ossKey = OssUtil.buildOssKey(mnt); 
            // data轉換爲InputStream,其它屬性值放入ObjectMetaData
            ossClient.putObject(bucketName, ossKey, OssUtil.buildOssObject(mnt), OssUtil.buildMeta(mnt));
        }

        //線程結束前釋放OSSClient
        ossClient.shutdown();
    }
}

在本項目中,線程數每多一倍,批量讀寫的速度可提高90%,效果至關明顯。

相關文章
相關標籤/搜索