总结阿⾥云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),仅仅是为了管理⽅便,除此以外并⽆多⼤意义。
其它概念,如分⽚、回调、追加、权限等,因开发中未涉及,不再展开,有兴趣者请参考:
三、1号坑:不到对象抛出OSSException
现在开始总结Java开发时的⼏个坑,这些都是SDK⽂档和⽰例代码中没有的。先从简单的说起。
Java SDK提供了两种搜素OSS对象的⽅法:按⽂件名精确匹配和按⽂件前缀批量查,不⽀持其它模糊查询、正规表达式查询,也不⽀持按元信息查询。这些都好理解,不多说。
在按⽂件名Key精确查OSS对象时,如果不存在该Key对应的⽂件,会抛出OSSException(对应的错误信息是ErrorCode
为“NoSuchKey”),⽽不是返回null。
因此,需要在Java代码⾥加上对该例外的捕获处理:
try
{
OSSObject obj = null;
obj = Object(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();
}
四、2号坑:分页遍历时的死循环
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 = ObjectSummaries();
for (OSSObjectSummary s : sums)
{
String ossKey = s.getKey();
...
ossClient.putObject(bucketName, s, new Data()));
...
}
// 下⼀次分页的nextMarker
nextMarker = NextMarker();
} while (objListing.isTruncated());
因为有了putObject操作(带颜⾊处),运⾏时成了死循环,objListing.isTruncated()永远为false。
经测试,死循环不仅仅出现在如上的⼀段代码中既遍历⼜修改的情况下,⼀个进程循环写的同时另⼀个进程分页遍历也会出现。猜测遍历的依据条件主要是修改时间,但没法区分已经遍历过的对象。
貌似没有特别好的解决办法,我们使⽤的是⼀种⼟办法:选定⼀个特殊对象,再次遍历到它即强⾏退出循环。
五、3号坑:循环getObject超过1024次的挂起
有⼀个操作需要批量读取OSS对象,按⽰例代码编写后测试,发现⼀旦循环调⽤getObject()程序就会挂起,不继续运⾏也不退出,只能强⾏关闭。
后来经⼀步步跟踪,发现此问题是由于getObject()后没有及时close对象⽽引起,临界值是1024(也可能是1023)。
List<String> pks; //存放的是ossKey
for (String pk : pks)
{
HSBJMntDataPK pk = (i);
OSSObject obj = null;
try
{
obj = Object(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()后,不再发⽣程序挂起的现象。
六、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);
truncated在存储过程中怎么使用meta.addUserMetadata("purpose", purpose);
return meta;
}
public static MntData parseObject(OSSObject ossObject)
{
ObjectMetadata meta = ObjectMetadata();
Map<String, String> metadata = UserMetadata();
MntData mnt = new MntData();
// 从UserMeta获取value时,key必须为全⼩写
mnt.("author") == null ? "-" : ("author"));
mnt.("version") == null ? "1.0" : ("version"));
mnt.("type") == null ? "-" : ("type"));
mnt.("purpose") == null ? "" : ("purpose"));
// UpdateTime为OSS⾃带的元数据
mnt.setUpdateTime(new LastModified().getTime()));
mnt.setData(getBytesFromObj(ossObject));
return mnt;
}
七、经验:使⽤多进程可提升速度
注意这不是坑,⽽是⼀条有益经验:⽆论是读还是写,使⽤多线程可显著提升批量操作的速度。以下是写OSS进程的⽰例代码:
public class OssPutThread extends Thread
{
List<MntData> mnts;
int index;
public OssPutThread(List<MntData> mnts, int index)
{
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 = (i);
String ossKey = OssUtil.buildOssKey(mnt);
// data转换为InputStream,其它属性值放⼊ObjectMetaData
ossClient.putObject(bucketName, ossKey, OssUtil.buildOssObject(mnt), OssUtil.buildMeta(mnt));
}
//线程结束前释放OSSClient
ossClient.shutdown();
}
}
在本项⽬中,线程数每多⼀倍,批量读写的速度可提升90%,效果相当明显。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论