总结一下阿里云OSS的开发坑(Java篇)

一、序言

OSS(Object Storage Service)是阿里云提供的一款云存储服务,具有海量、安全、低成本、高可靠的特点。

由于客户选择了OSS,我们作为开发方也开始接触它。在实际开发过程中遇到了各种各样的坑,经自己多次实践及阿里技术人员的协助,终得以完成任务。

阿里方面为OSS提供了多种语言的开发接口,我们用到了其中两种:Java和C/C++。本文为Java篇,C/C++的将在另一篇给出。

二、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

三、坑一:找不到对象抛出OSSException

现在开始总结Java开发时的几个坑,这些都是SDK文档和示例代码中没有的。先从简单的说起。

Java SDK提供了两种搜素OSS对象的方法:按文件名精确匹配和按文件前缀批量查找,不支持其它模糊查询、正规表达式查询,也不支持按元信息查询。这些都好理解,不多说。

在按文件名Key精确查找OSS对象时,如果不存在该Key对应的文件,会抛出OSSException(对应的错误信息是ErrorCode为“NoSuchKey”),而不是返回null。

因此,需要在Java代码里加上对该例外的捕获处理:

    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();
    } 

四、坑二、分页遍历的死循环

OSS Java SDK提供了分页遍历的功能,一页最多可以遍历1000个文件。但如果一边遍历一边更新对象,则很容易形成死循环。

项目中的一个小工具的示例代码如下:

        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。

经测试,死循环不仅仅出现在如上的一段代码中既遍历又修改的情况下,一个进程循环写的同时另一个进程分页遍历也会出现。猜测遍历的依据条件主要是修改时间,但没法区分已经遍历过的对象。

貌似没有特别好的解决办法,我们使用的是一种土办法:选定一个特殊对象,再次遍历到它即强行退出循环。

五、坑三:循环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()后,不再发生程序挂起的现象。

六、坑四:关于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;
    }

七、经验:使用多进程可提升速度

注意这不是坑,而是一条有益经验:无论是读写,使用多线程可显著提升批量操作的速度。

以下是写进程的示例代码:

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%,效果相当明显。

原文地址:https://www.cnblogs.com/wggj/p/9172202.html

时间: 2024-10-01 05:28:16

总结一下阿里云OSS的开发坑(Java篇)的相关文章

阿里云OSS存储开发(一)

Step 1. 初始化一个OSSClient OSSClient是与OSS服务交互的客户端,SDK的OSS操作都是通过OSSClient完成的. 下面代码新建了一个OSSClient: using Aliyun.OpenServices.OpenStorageService; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Ta

阿里云OSS存储开发

Step 1. 初始化一个OSSClient        OSSClient是与OSS服务交互的客户端,SDK的OSS操作都是通过OSSClient完成的. 下面代码新建了一个OSSClient: using Aliyun.OpenServices.OpenStorageService; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threa

【初码干货】使用阿里云对Web开发中的资源文件进行CDN加速的深入研究和实践

提示:阅读本文需提前了解的相关知识 1.阿里云(https://www.aliyun.com) 2.阿里云CDN(https://www.aliyun.com/product/cdn) 3.阿里云OSS(https://www.aliyun.com/product/oss) 4.HTTPS(http://baike.baidu.com/view/14121.htm) 阅读目录结构 引: 一.准备工作 二.整体功能结构 三.具体实现步骤 四.关键点和问题处理 五.延伸与扩展 六.总结与思考 引:

Windows环境下用C#编程将文件上传至阿里云OSS笔记

本系列文章由ex_net(张建波)编写,转载请注明出处. http://blog.csdn.net/ex_net/article/details/24962567 作者:张建波 邮箱: [email protected]  欢迎来信交流! 第1步: 下载阿里云OSS的SDK包,由于笔者的环境是PHP服务,所以下载的是PHP的SDK包 http://help.aliyun.com/view/13438816.html 第2步:将代码整合进你的网站或服务中. 第3步:配置OSS访问接口 (1)找到c

jeesz分布式架构集成阿里云oss存储

1. 服务接口定义 /** * 文件上传  1:头像 2:显示图片 3:个人封面  :4:基础图片 * @param request * @param response * @param uid 用户id * @param userType 文件上传  1:头像 2:显示图片 3:个人封面  :4:基础图片 0:视频 * @param files 上传的文件对象 * @return * @throws Exception */ @RequestMapping(value = "/upload/b

阿里云 OSS

原文:https://help.aliyun.com/ 阿里云 OSS 阿里云 OSS(Object Storage Service)为您提供基于网络的数据存取服务.使用 OSS,您可以通过网络随时存储和调用包括文本.图片.音频和视频等在内的各种结构化或非结构化数据文件. 在使用阿里云 OSS 之前,您需要了解 OSS 的几个基本概念.阿里云 OSS 将数据文件以对象(object)的形式上传到存储空间(bucket)中. 您可以创建一个或者多个存储空间,然后向每个存储空间中添加一个或多个文件.

在OneThink(ThinkPHP3.2.3)中整合阿里云OSS的PHP-SDK2.0.4,实现Web端直传,服务端签名直传并设置上传回调的实现流程

在OneThink(ThinkPHP3.2.3)中整合阿里云OSS的PHP-SDK2.0.4,实现本地文件上传流程 by shuijingwan · 2016/01/13 1.SDK安装 github地址:https://github.com/aliyun/aliyun-oss-php-sdk 2.复制aliyun-oss-php-sdk-master\src\OSS至passport.hmwis.com\ThinkPHP\Library\Vendor\OSS,如图1.2 复制aliyun-os

阿里云OSS获Docker官方支持 近百万开发者因此受益

  近年来,开源的容器应用引擎Docker在企业IT市场中风生水起,短短两年内,建立在Docker容器软件内的应用被下载5.35亿次.全球约有400万人在使用Docker,约有150,000个在线应用靠Docker容器运行,大型企业正在空前快速地测试或计划采用Docker,胜过其他任何开源的云端产品. 8月12日,Docker1.8和Docker Registry 2.1发布,新版本中加入了Docker Registry对阿里云开放存储服务的正式支持,阿里云成为被Docker官方支持的存储服务的

thinkphp集成系列之阿里云oss

web2.0时代:除了纯信息展示类的网站:基本都是有文件上传功能的: 最不济你得让用户换个头像吧:但是随着业务的发展: 如果上传的文件都和网站程序源代码放在一起:那是有相当多的弊端的: 1:静态文件会占用大量带宽: 2:服务器的成本略高: 常规的做法是把php源代码放到一台服务器上:图片等静态文件放在另一台服务器上: 当一个神奇的“云”时代的到来后:一切就变的更加简单了: 在业务还比较小的时候:我们无需大费周折的去搞一台静态文件服务器:直接使用第三方的即可: 好了:洛里啰嗦了半天:下面请出本篇博