优化文件下载性能,缓存MongoDB GRIDFS文件(实践讨论稿)

时间:2022-07-22 19:40:28

  当前的多机集群系统中,文件(附件)的上传、下载服务是通过网络文件系统(NFS),在NFS的应用中,本地NFS的客户端应用可以透明地读写位于远端NFS服务器上的文件,就像访问本地文件一样,以此形成集成负载均衡方式环境下的共享盘功能,如下图所示。
优化文件下载性能,缓存MongoDB GRIDFS文件(实践讨论稿)

  在系统中,文件管理使用MongoDB Gridfs,如下图所示,文件下载过程中,先把MongoDB中的文件读取到NFS中,再通过系统Web服务下载文件,实际上是通过NFS缓存文件共享方式提供下载服务。
优化文件下载性能,缓存MongoDB GRIDFS文件(实践讨论稿)

  下面示例为Web服务端下载JavaScript代码。

//下载附件
function downloadFile(fileId) {
$.cordys.utils.sendCordysAjax({
method: 'GetFileById',
namespace: 'http://unicom.com/common/attachment',
parameters: {
fileId: fileId,
gridFSName: window.isTransWkfl ? window.opener.bizRvsnNumber : window.bizRvsnNumber
}
}).done(function (response) {
var downloadFileUrl = response['tuple']['old']['C_MONGODB_FILE']['FILE_PATH'];
if (downloadFileUrl) {
window.open(downloadFileUrl);
}
}).fail(function (returnData) {
alert('附件下载失败!');
console.error('error' + returnData)
});
}

  下面示例为下载Webservice代码。

    public static com.unicom.common.attachment.C_MONGODB_FILE getFileByIdImp(String fileId, String gridFSName) {

try {
DB db = MongoDBUtil.getDB();
GridFS gridFS = new GridFS(db, gridFSName);
ObjectId objId = new ObjectId(fileId);
GridFSDBFile gridFSDBFile = (GridFSDBFile) gridFS.findOne(objId);
C_MONGODB_FILE fileDetail = new C_MONGODB_FILE();
if (gridFSDBFile != null) {

SimpleDateFormat sdfFileName = new SimpleDateFormat("yyyy-MM-dd_hh-mm-ss.SSS");
String fileSize = Float.toString(gridFSDBFile.getLength());
String fileType = gridFSDBFile.getContentType();
String fileName = sdfFileName.format(new Date()) + "." + fileType;
InputStream inputStream = gridFSDBFile.getInputStream();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String datePath = sdf.format(new Date());
String downloadPath = EIBProperties.getInstallDir() + File.separator + "webroot" + File.separator + "shared" + File.separator + "download" + File.separator + datePath + File.separator;
String fileFullName = downloadPath + fileName;
String filePath = "/cordys/download/" + datePath + "/" + fileName;
String fileUploader = (String) gridFSDBFile.get("fileUploader");
File file = new File(downloadPath);
if (!file.exists()) {
file.mkdirs();
}
file = new File(fileFullName);
if (!file.exists()) {
file.createNewFile();
} else {
file.delete();
file.createNewFile();
}
OutputStream outputStream = new FileOutputStream(file);
int byteCount = 0;
while ((byteCount = inputStream.read()) != -1) {
outputStream.write(byteCount);
}
outputStream.flush();
inputStream.close();
outputStream.close();
fileDetail.setFILE_ID(fileId);
fileDetail.setFILE_NAME(fileName);
fileDetail.setFILE_TYPE(fileType);
fileDetail.setFILE_SIZE(fileSize);
fileDetail.setFILE_CONTENT(filePath);
fileDetail.setFILE_PATH(filePath);
fileDetail.setFILE_UPLOADER(fileUploader);
return fileDetail;
} else {
return null;
}
} catch (UnknownHostException e) {
return null;
} catch (IOException e) {
return null;
} finally {
}
}

  在使用过程中出现如下图性能问题,对于大附件,容易出现下载失败的问题(实质为超时)。
优化文件下载性能,缓存MongoDB GRIDFS文件(实践讨论稿)

分析原因

  在负载均衡集群环境中,并发量大或文件体积大的时候,NFS系统可能存在瓶颈问题,特别是下载操作时,多出一步从MongoDB读取文件写入NFS的过程。如果写入时间过长,则将造成Web服务端请求超时。
  上传附件过程,一般体现不出来。主要是上传附件到缓存NFS后,再写入MongoDB,而此时已经是异步操作,上传的响应已经反馈了。

解决方案

1、临时解决方案,延长Webservice超时时长;

2、优化NFS缓存文件管理,减少“从MongoDB读取文件写入NFS”的次数。优化方案设计图如下:

优化文件下载性能,缓存MongoDB GRIDFS文件(实践讨论稿)

3、每台服务器上都生成可下载的缓存文件,抛弃文件共享盘。

设计思路

  按方案2的设计如下,首先创建个附件缓存表,记录上传、下载过程中的缓存文件,方便多次重复使用文件。

1、附件缓存表结构

字段名称 类型 说明
文件ID 字符
上传时间 时间 为了与上传附件复用
初次下载时间 时间
文件名称 字符 显示出的中文名称,方便维护
文件缓存路径 字符 文件相对路径和名称
组织DN 字符 为了区分租户

  第一次下载失败(超时),但是在异步情况下,缓存文件已经产生,下次下载时,就不必再从MongoDB中读取写到缓存中,提高了系统性能。

2、缓存使用过程。

Created with Raphaël 2.1.0 客户端(JS) 客户端(JS) WEB服务 WEB服务 缓存服务 缓存服务 数据库(MySQL) 数据库(MySQL) 下载文件 按文件ID查询缓存文件 查询缓存记录 返回缓存文件路径 从NFS中下载文件

3、生成缓存并下载过程

Created with Raphaël 2.1.0 客户端(JS) 客户端(JS) WEB服务 WEB服务 缓存服务 缓存服务 文件下载服务 文件下载服务 MongoDB服务 MongoDB服务 下载文件 未查询到缓存文件 按文件ID查询文件 读取文件 增加/修改缓存数据记录 缓存并反馈车票

  欢迎讨论分享。

参考:

集群环境下文件上传方法与运维(Uploading a File to a Service) 肖永威 2016.08