OSS(Object Storage Service)是阿里云提供的一款云存储服务,具备海量、安全、低成本、高可靠的特色。html
因为客户选择了OSS,咱们做为开发方也开始接触它。在实际开发过程当中遇到了各类各样的坑,经本身屡次实践及阿里技术人员的协助,终得以完成任务。安全
阿里方面为OSS提供了多种语言的开发接口,咱们用到了其中两种:Java和C/C++。本文为Java篇,C/C++的将在另外一篇给出。多线程
如今开始总结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(); }
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
经测试,死循环不只仅出如今如上的一段代码中既遍历又修改的状况下,一个进程循环写的同时另外一个进程分页遍历也会出现。猜想遍历的依据条件主要是修改时间,但无法区分已经遍历过的对象。
貌似没有特别好的解决办法,咱们使用的是一种土办法:选定一个特殊对象,再次遍历到它即强行退出循环。
有一个操做须要批量读取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()后,再也不发生程序挂起的现象。
若是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; }
注意这不是坑,而是一条有益经验:不管是读仍是写,使用多线程可显著提高批量操做的速度。
如下是写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%,效果至关明显。