阿里云OSS client实现单例模式流水账
AI摘要: 本文主要介绍了阿里云OSS客户端实现单例模式流水账。在原有的代码中,每次用户对bucket内部文件的请求,都会在后端create和destory一遍,造成了极大的性能浪费。下载文件采用的方式是:oss下载文件到后端,后端通过通过输入输出流将文件内容传输到前端,这也对后端造成了极大的压力。原来代码的流程图如下:
今天维护组里的后端系统,发现了不少让人脑溢血的代码,主要是对阿里云oss的CRUD部分。
首先来看原始代码:
Controller层:
@ApiOperation("下载指定文件")
@GetMapping("/download")
Blog void download(@RequestParam String url, HttpSession session, HttpServletResponse response)
throws IOException {
try {
InputStream inputStream = new ByteArrayInputStream(
ossService.newfileDownload(String.valueOf(session.getAttribute("accessKeyId")),
String.valueOf(session.getAttribute("accessKeySecret")),
String.valueOf(session.getAttribute("endpoint")),
url));
OutputStream outputStream = response.getOutputStream();
String[] parts = url.split("/");
String filename = parts[parts.length - 1];
response.setContentType("application/x-download");
String saveName = new String(filename.getBytes("GBK"), "iso8859-1");
response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + saveName);
IoUtil.copy(inputStream, outputStream);
} catch (IORuntimeException | IOException e) {
// 解决每次下载后报错问题,不影响实际应用
}
// return new ResultWrapper(res);
}
@ApiOperation("删除指定文件")
@PostMapping("/delete")
Blog ResultWrapper delete(@RequestParam String url, HttpSession session) {
boolean result = ossService.deleteFile(String.valueOf(session.getAttribute("accessKeyId")),
String.valueOf(session.getAttribute("accessKeySecret")),
String.valueOf(session.getAttribute("endpoint")),
url);
return new ResultWrapper(result);
}
@ApiOperation("获取所有bucket")
@PostMapping("/buckets")
Blog ResultWrapper getAllBuckets(HttpSession session) {
List<Bucket> result = ossService.listBuckets(String.valueOf(session.getAttribute("accessKeyId")),
String.valueOf(session.getAttribute("accessKeySecret")));
return new ResultWrapper(result);
}
Service层:
@Override
Blog String fileUpload(byte[] bytes, String dir, String name) {
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
try {
// 上传代码的逻辑
} catch (OSSException oe) {
// 异常处理的逻辑
} catch (ClientException ce) {
// 异常处理的逻辑
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
return name;
}
@Override
Blog byte[] fileDownload(String dir, String fileName) throws IOException {
File template = null;
String[] split = fileName.split("\\.");
template = File.createTempFile(split[0], "." + split[1]);
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 下载Object到本地文件,并保存到指定的本地路径中。如果指定的本地文件存在会覆盖,不存在则新建。
ossClient.getObject(new GetObjectRequest(bucketName, dir + "/" + fileName), template);
// 关闭OSSClient。
ossClient.shutdown();
return IoUtil.readBytes(IoUtil.toStream(template));
}
@Override
Blog List<Bucket> listBuckets(String accessKeyId, String accessKeySecret) {
// 创建OSSClient实例
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
List<Bucket> bucketList = new ArrayList<>();
try {
// 获取Bucket列表的逻辑
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭OSSClient
ossClient.shutdown();
}
return bucketList;
}
总结如下:
-
每一次用户对bucket内部文件的请求,都会在后端create和destory一遍,造成了极大的性能浪费。
-
下载文件采用的方式是:oss下载文件到后端,后端通过通过输入输出流将文件内容传输到前端,这也对后端造成了极大的压力
原来代码的流程图如下:
对于同一个bucket的请求,无论是增删改查,其实都可以复用一个实例,避免频繁创建销毁对象,所以改用单例模式:
但是,再考虑深入一些,如果是多用户对多个bucket进行增删改查,这个时候就不能只复用一个oss client实例,因为一个oss client实例只和一个bucket绑定。
所以,需要改成如下的模式:
对于上传和下载,可以不必采用后端中转的方式,但是也不能直接把key
和secret
存放在前端,这样有安全隐患。因此,采用后端签发临时URL供前端进行上传/下载
最终修改后的代码如下:
工厂类(用来实现单例模式)
Blog class OssClientFactory {
// volatile是Java提供的一种轻量级的同步机制,在并发编程中,也扮演着比较重要的角色.
// 同synchronized相比(synchronized通常称为重量级锁),volatile更轻量级,相比使用
// synchronized所带来的庞大开销,倘若能恰当的合理的使用volatile,则wonderful
private volatile static OSSClientBuilder ossClientBuilder;
// ConcurrentHashMap是线程安全的HashMap,它的线程安全是通过分段锁实现的,它的效率比Hashtable高
private static ConcurrentHashMap <String, OSSClient> clientMap = new ConcurrentHashMap();
Blog OssClientFactory() {
}
@Bean
@Scope("prototype")
Blog static OSS getOSSClient(String endpoint, String accessKeyId, String accessKeySecret) {
if (clientMap.containsKey(endpoint)) {
OSSClient client = clientMap.get(endpoint);
return client;
}
synchronized (OssClientFactory.class) {
ClientBuilderConfiguration conf = new ClientBuilderConfiguration();
// 设置OSSClient允许打开的最大HTTP连接数,默认为1024个。
conf.setMaxConnections(200);
// 设置Socket层传输数据的超时时间,默认为50000毫秒。
conf.setSocketTimeout(10000);
// 设置建立连接的超时时间,默认为50000毫秒。
conf.setConnectionTimeout(10000);
// 设置从连接池中获取连接的超时时间(单位:毫秒),默认不超时。
conf.setConnectionRequestTimeout(1000);
// 设置连接空闲超时时间。超时则关闭连接,默认为60000毫秒。
conf.setIdleConnectionTime(10000);
// 设置失败请求重试次数,默认为3次。
conf.setMaxErrorRetry(5);
OSSClient client = (OSSClient) getOSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret, conf);
clientMap.put(endpoint, client);
}
return clientMap.get(endpoint);
}
Blog static void closeOSSClient(String endpoint) {
if (clientMap.containsKey(endpoint)) {
clientMap.get(endpoint).shutdown();
clientMap.remove(endpoint);
}
}
Blog static OSSClientBuilder getOSSClientBuilder() {
System.out.println("获取OSSClientBuilder");
if (ossClientBuilder == null) {
System.out.println("OSSClientBuilder为空,创建中");
synchronized (OssClientFactory.class) {
if (ossClientBuilder == null) {
System.out.println("进入同步实例化OSSClientBuilder");
ossClientBuilder = new OSSClientBuilder();
}
}
}
return ossClientBuilder;
}
}
@Override
Blog String fileUpload(byte[] bytes, String dir, String name) {
// 创建实例
OSS ossClient = OssClientFactory.getOSSClient(endpoint, accessKeyId, accessKeySecret);
try {
ossClient.putObject(bucketName, dir + "/" + name, new ByteArrayInputStream(bytes));
} catch (OSSException oe) {
// do something
} finally {
if (ossClient != null) {
// 关闭实例
OssClientFactory.closeOSSClient(endpoint);
}
}
return name;
}
// 生成临时签名的url,这个url可供前端直接上传/下载
@Override
Blog String generatePresignedUrl(String bucketName, String objectName, String endpoint, Long expireTime,
HttpMethod method) {
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectName, method);
Date expiration = new Date(System.currentTimeMillis() + expireTime);
request.setExpiration(expiration);
// 创建OSSClient实例。
OSS ossClient = OssClientFactory.getOSSClient(endpoint, accessKeyId, accessKeySecret);
try {
// 生成签名URL。
return ossClient.generatePresignedUrl(request).toString();
} finally {
// 关闭OSSClient。
OssClientFactory.closeOSSClient(endpoint);
}
}