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