当前的多机集群系统中,文件(附件)的上传、下载服务是通过网络文件系统(NFS),在NFS的应用中,本地NFS的客户端应用可以透明地读写位于远端NFS服务器上的文件,就像访问本地文件一样,以此形成集成负载均衡方式环境下的共享盘功能,如下图所示。
在系统中,文件管理使用MongoDB Gridfs,如下图所示,文件下载过程中,先把MongoDB中的文件读取到NFS中,再通过系统Web服务下载文件,实际上是通过NFS缓存文件共享方式提供下载服务。
下面示例为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 {
}
}
在使用过程中出现如下图性能问题,对于大附件,容易出现下载失败的问题(实质为超时)。
分析原因
在负载均衡集群环境中,并发量大或文件体积大的时候,NFS系统可能存在瓶颈问题,特别是下载操作时,多出一步从MongoDB读取文件写入NFS的过程。如果写入时间过长,则将造成Web服务端请求超时。
上传附件过程,一般体现不出来。主要是上传附件到缓存NFS后,再写入MongoDB,而此时已经是异步操作,上传的响应已经反馈了。
解决方案
1、临时解决方案,延长Webservice超时时长;
2、优化NFS缓存文件管理,减少“从MongoDB读取文件写入NFS”的次数。优化方案设计图如下:
3、每台服务器上都生成可下载的缓存文件,抛弃文件共享盘。
设计思路
按方案2的设计如下,首先创建个附件缓存表,记录上传、下载过程中的缓存文件,方便多次重复使用文件。
1、附件缓存表结构
字段名称 | 类型 | 说明 |
---|---|---|
文件ID | 字符 | |
上传时间 | 时间 | 为了与上传附件复用 |
初次下载时间 | 时间 | |
文件名称 | 字符 | 显示出的中文名称,方便维护 |
文件缓存路径 | 字符 | 文件相对路径和名称 |
组织DN | 字符 | 为了区分租户 |
第一次下载失败(超时),但是在异步情况下,缓存文件已经产生,下次下载时,就不必再从MongoDB中读取写到缓存中,提高了系统性能。
2、缓存使用过程。
3、生成缓存并下载过程
欢迎讨论分享。
参考:
集群环境下文件上传方法与运维(Uploading a File to a Service) 肖永威 2016.08