一、引言
当系统存在大量的图片、视频、文档等文件需要存储和管理时,对于分布式系统而言,如何高效、可靠地存储这些文件是一个关键问题。mongodb 的 gridfs 作为一种分布式文件存储机制,为我们提供了一个优秀的解决方案。它基于 mongodb 的分布式架构,能够轻松应对海量文件存储的挑战,同时提供了便捷的文件操作接口。
二、gridfs 原理剖析
gridfs 是 mongodb 中用于存储大文件的一种规范。它将文件分割成多个较小的 chunks(默认大小为 256kb),并将这些 chunks 存储在 fs.chunks 集合中,而文件的元数据(如文件名、大小、创建时间、mime 类型等)则存储在 fs.files 集合中。这样的设计不仅能够突破 mongodb 单个文档大小的限制(默认 16mb),还能利用 mongodb 的分布式特性,实现文件的分布式存储和高效读取。
例如,当我们上传一个 1gb 的视频文件时,gridfs 会将其切分为约 4096 个 256kb 的 chunks,然后将这些 chunks 分散存储在不同的 mongodb 节点上,同时在 fs.files 集合中记录文件的相关信息。
三、spring boot 集成 gridfs
在实际项目中,我们通常使用 spring boot 与 mongodb 结合,下面是具体的集成步骤与代码示例。
3.1 添加依赖
在 pom.xml 文件中添加 spring boot 与 mongodb 相关依赖:
<dependencies>
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-data-mongodb</artifactid>
</dependency>
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-web</artifactid>
</dependency>
</dependencies>3.2 配置 mongodb 连接
在 application.properties 中配置 mongodb 的连接信息:
spring.data.mongodb.uri=mongodb://localhost:27017/fs spring.data.mongodb.database=fs
3.3 编写服务类
使用 gridfstemplate 和 gridfsbucket 来实现文件的上传、下载、删除等操作:
@service
publicclass mongofsstoreservice implements fsstoreservice {
privatefinal gridfstemplate gridfstemplate;
private gridfsbucket gridfsbucket;
public mongofsstoreservice(gridfstemplate gridfstemplate) {
this.gridfstemplate = gridfstemplate;
}
@autowired(required = false)
public void setgridfsbucket(gridfsbucket gridfsbucket) {
this.gridfsbucket = gridfsbucket;
}
/**
* 上传文件
* @param in
* @param fileinfo
* @return
*/
@override
public fileinfo uploadfile(inputstream in, fileinfo fileinfo){
objectid objectid = gridfstemplate.store(in, fileinfo.getfileid(), fileinfo.getcontenttype(), fileinfo);
fileinfo.setdataid(objectid.tostring());
return fileinfo;
}
/**
*
* @param in
* @param filename
* @return
*/
@override
public fileinfo uploadfile(inputstream in, string filename) {
fileinfo fileinfo = fileinfo.fromstream(in, filename);
return uploadfile(in, fileinfo);
}
/**
*
* @param fileid
* @return
*/
@override
public file downloadfile(string fileid){
gridfsresource gridfsresource = download(fileid);
if( gridfsresource != null ){
gridfsfile gridfsfile = gridfsresource.getgridfsfile();
fileinfo fileinfo = jsonhelper.convert(gridfsfile.getmetadata(), fileinfo.class);
try(inputstream in = gridfsresource.getinputstream()) {
return filehelper.newfile( in, fileinfo.getfileid() ); //
} catch (ioexception e) {
thrownew runtimeexception(e);
}
}
returnnull;
}
/**
* 查找文件
* @param fileid
* @return
*/
public gridfsresource download(string fileid) {
gridfsfile gridfsfile = gridfstemplate.findone(query.query(gridfscriteria.wherefilename().is(fileid)));
if (gridfsfile == null) {
returnnull;
}
if( gridfsbucket == null ){
return gridfstemplate.getresource(gridfsfile.getfilename());
}
gridfsdownloadstream downloadstream = gridfsbucket.opendownloadstream(gridfsfile.getobjectid());
returnnew gridfsresource(gridfsfile, downloadstream);
}
/**
* 删除文件
* @param fileid
*/
@override
public void deletefile(string fileid) {
gridfstemplate.delete(query.query(gridfscriteria.wherefilename().is(fileid)));
}
}
3.4 创建控制器
提供 rest api 接口,方便外部调用:
@restcontroller
@requestmapping("/mongo")
publicclass mongofsstorecontroller {
privatefinal mongofsstoreservice mongofsstoreservice;
public mongofsstorecontroller(mongofsstoreservice mongofsstoreservice) {
this.mongofsstoreservice = mongofsstoreservice;
}
/**
*
* @param file
* @return
*/
@requestmapping("/upload")
public responseentity<result> uploadfile(@requestparam("file") multipartfile file){
try(inputstream in = file.getinputstream()){
fileinfo fileinfo = convertmultipartfile(file);
return responseentity.ok( result.ok(mongofsstoreservice.uploadfile(in, fileinfo)) );
}catch (exception e){
return responseentity.ok( result.fail(httpstatus.internal_server_error.value(), e.getmessage()) );
}
}
private fileinfo convertmultipartfile(multipartfile file){
fileinfo fileinfo = new fileinfo();
fileinfo.settype(filenameutils.getextension(file.getoriginalfilename()));
fileinfo.setfileid(uuid.randomuuid().tostring() + "." + fileinfo.gettype()); //
fileinfo.setfilename(file.getoriginalfilename());
fileinfo.setsize(file.getsize());
fileinfo.setcontenttype(file.getcontenttype());
fileinfo.setcreatetime(new date());
return fileinfo;
}
/**
*
* @param fileid
* @param response
*/
@requestmapping("/download")
public void downloadfile(@requestparam("fileid") string fileid, httpservletresponse response){
file file = mongofsstoreservice.downloadfile(fileid);
if( file != null ){
response.setcontenttype("application/octet-stream");
response.setheader("content-disposition", "attachment; filename=\"" + file.getname() + "\"");
try {
fileutils.copyfile(file, response.getoutputstream());
} catch (ioexception e) {
thrownew runtimeexception(e);
}
}
}
@requestmapping("/download/{fileid}")
public responseentity<inputstreamresource> download(@pathvariable("fileid") string fileid) throws ioexception {
gridfsresource resource = mongofsstoreservice.download(fileid);
if( resource != null ){
gridfsfile gridfsfile = resource.getgridfsfile();
fileinfo fileinfo = jsonhelper.convert(gridfsfile.getmetadata(), fileinfo.class);
return responseentity.ok()
.header(httpheaders.content_disposition, "attachment; filename=\"" + fileinfo.getfilename() + "\"")
.contentlength(fileinfo.getsize())
// .contenttype(mediatype.parsemediatype(fileinfo.getcontenttype()))
.body(new inputstreamresource(resource.getinputstream()));
}
// return responseentity.nocontent().build();
return responseentity.internalservererror().build();
}
/**
*
* @param fileid
* @return
*/
@requestmapping("/delete")
public responseentity<string> deletefile(@requestparam("fileid") string fileid){
mongofsstoreservice.deletefile(fileid);
return responseentity.ok("删除成功");
}四、实战中的常见问题与解决方案
4.1 文件下载时的内存管理
在下载文件时,gridfsdownloadstream 提供了流式处理的能力,避免一次性将整个文件加载到内存中。我们可以通过 gridfsresource 将流包装后直接返回给客户端,实现边读边传,从而节省内存。例如:
// 正确:直接返回 inputstreamresource,边读边传
return responseentity.ok()
.body(new inputstreamresource(resource.getinputstream()));而应避免将整个文件读取到字节数组中再返回,如以下错误示例:
// 错误:将整个文件加载到内存再返回
byte[] content = resource.getinputstream().readallbytes();
return responseentity.ok()
.body(content);五、总结
基于 mongodb gridfs 的分布式文件存储方案,凭借其独特的文件分块存储原理和与 mongodb 分布式架构的紧密结合,为我们提供了一种高效、可靠的文件存储方式。通过 spring boot 的集成,我们能够快速在项目中实现文件的上传、下载、查询和删除等功能。在实际应用过程中,我们需要关注内存管理、数据类型转换、时间类型处理等常见问题,并采用合适的解决方案。随着技术的不断发展,gridfs 也在持续优化和完善,将为更多的分布式文件存储场景提供强大的支持。
对于中小文件存储,gridfs 是一个简单高效的选择;对于超大规模文件或需要极致性能的场景,可以考虑结合对象存储(如 minio、s3)使用。
以上就是基于mongodb实现文件的分布式存储的详细内容,更多关于mongodb文件分布式存储的资料请关注代码网其它相关文章!
发表评论