FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
FastDFS由跟踪服务器(Tracker Server)、存储服务器(Storage Server)和客户端(Client)构成。
Tracker
Storage
Client
Tracker不负责文件的存储,它主要负责文件管理负载均衡操作,就像是一个中介,为Client提供Storage的信息。而Storage才是负责文件上传,修改,删除等工作。
Client:“嗨,Tracker!你那里还有没有正在闲着的Storage呀?我现在有些文件需要存储。”
Tracker:“我来看看,嗯… 找到了,Storage5现在正好闲着,他的地址是192.168.200:8888/group5,你直接去找他吧。”
Client:“OK!”
Client:“嗨,Storage5!我这里现在有一堆文件,就放你这里吧。”
Storage5:“好的,老板,帮你存好了,放在我这儿你就放心吧,我还让我家二弟帮你做了备份,不会丢的。”
Client:“好的,谢谢啦!”
…
通过上面的对话,就应该能明白这三者之间的关系了吧。 从图中可以看出,Storage是一个集群,集群里面分为一个一个的组,每个组里面有不止一台Storage(也可以是一台),这么设计有什么好处呢?首先并不是只有一台Storage为客户端提供服务,而是由Tracker动态地分配相对空闲的Storage给客户端提供服务,这样就做到了负载均衡,使得不会有个别Storage压力过大。还有就是每个组里面不止一个Storage,一个组里面的所有Storage都是同步备份数据的,为的就是实现容灾,一台Storage挂了数据也不会丢失,除非全挂了。还有一个好处就是可以很方便地实现线性扩容,如果哪天Storage的空间不够用了,就可以直接添加一组Storage,然后注册到Tracker中。同样的,Tracker也是集群。
负载均衡
容灾
线性扩容
首先 Storage会定时向Tracker上报自己的状态信息 ,这样Tracker就会知道Storage还是不是活着;当 Client给Tracker发送请求 的时候, Tracker会查询是否有可用的Storage ;有的话就 将Storage的信息给Client ,Client在得到Storage的信息后,就 将文件上传到Storage ; Storage先是生成一个file_id ,再 将文件写入磁盘 中保存,最后 将file_id返回给Client 。
group1是Storage所在的 组名 ,M00是Storage的 虚拟路径 ,02/04是 两级目录 ,后面的就是 文件名 了,这个文件名是自动生成的。
我们要把FastDFS安装到docker中,首先要有docker的环境,资料提供的虚拟机里面已经安装好docker了。
接下来 将FastDFS的镜像下载到本地 :
docker pull morunchang/fastdfs
下载完成之后看一下有没有:
docker images
已经下载好了,接下来就开始安装吧。
安装Tracker
docker run -d --name tracker --net=host morunchang/fastdfs sh tracker.sh
现在Tracker就安装好了。
安装Storage
docker run -d --name storage --net=host -e TRACKER_IP=192.168.31.200:22122 -e GROUP_NAME=group1 morunchang/fastdfs sh storage.sh
Tracker和Storage开机自启动
现在我们的Tracker和Storage已经安装好了,但是如果每次开机都要去手动启动的话还是太麻烦了,所以设置一下开机自启动:
docker update --restart=always tracker docker update --restart=always storage
需要注意的是这里的“tracker”和“storage”是前面–name设置的名字,前面设置的是什么,这里就写什么。到此为止,FastDFS就已经安装好了。
配置ngx_fastdfs_module
当我们访问Storage中的文件资源的时候,中间是经过了Nginx,然后通过ngx_fastdfs_module去访问Storage的,ngx_fastdfs_module在安装Tracker和Storage的时候已经自动帮我们安装好了,我们来看一下。
docker exec -it storage /bin/bash //进入到storage容器中 vim /etc/nginx/conf/nginx.conf //修改/etc/nginx/conf/目录下的nginx。conf文件
里面会有这么一段内容
location ~ /M00 { root /data/fast_data/data; ngx_fastdfs_module; }
上面这段内容就说明ngx_fastdfs_module已经配置好了。
当浏览器访问过Storage中的资源后,即便将Storage中的资源已经被删除了,浏览器还是会访问缓存中数据,那如果我们不想这样,就可以配置一下禁止缓存。还是上面的步骤,在里面添加一行内容:
location ~ /M00 { add_header Cache-Control no-store; #告诉浏览器不要缓存数据 root /data/fast_data/data; ngx_fastdfs_module; }
nginx配置文件
前面我们说过,访问Storage先是通过了Nginx,那么Nginx的配置文件在哪呢?
docker exec -it storage /bin/bash //进入到storage容器中 cd etc/nginx/conf
可以看到,这里有个nginx.conf文件,这个就是nginx的配置文件。里面配置了Nginx的端口等信息。
Tracker和Storage配置文件
cd etc/fsds //切换到etc/fsds/conf目录下
storage.conf和tracker.conf分别是Storage和Tracker配置文件。
上一节中提到了file_id的格式,其中有个虚拟目录M00,看配置文件中的M00,其实就是/data/fast_data/data目录。切换进来看看:
cd data/fast_data/data
这些都是目录,前面不是说了file_id的虚拟目录后面跟着二级目录么,再进入00目录看一下: 还是一堆目录,再进入00目录: 这个jpg文件是我刚才传上去的。
在changgou-service下 新建一个Module叫做changgou-service-file ,因为用到了fastdfs,自然需要 添加依赖 。
<dependencies> <!-- FastDFS客户端程序包--> <dependency> <groupId>net.oschina.zcx7878</groupId> <artifactId>fastdfs-client-java</artifactId> <version>1.27.0.0</version> </dependency> </dependencies>
接下来就需要 在resource目录下创建FastDFS的配置文件fdfs_client.conf :
#连接超时,单位是秒 connect_timeout=60 #通信超时时间,发送或接收数据时。假设在超时时间后还不能发送或接收数据,则本次网络通信失败 network_timeout=60 #字符集 charset=UTF-8 #Tracker的http端口 http.tracker_http_port=8080 #Tracker服务器IP和端口设置 tracker_server=192.168.31.200:22122
注释的部分请删掉 ,我在使用的过程中总是连接失败,然后把注释删了就好了。
微服务工程自然也需要配置, 在resource目录下创建application.yml :
spring: servlet: multipart: max-file-size: 10MB #上传文件最大大小 max-request-size: 10MB #请求数据最大大小 application: name: file #该微服务的名字 server: port: 18082 #该微服务的端口 eureka: client: service-url: defaultZone: http://127.0.0.1:7001/eureka instance: prefer-ip-address: true feign: hystrix: enabled: true
最后再创建一个启动类即可, 在com.robod包下新建一个类FileApplication.class :
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) @EnableEurekaClient public class FileApplication { public static void main(String[] args) { SpringApplication.run(FileApplication.class,args); } }
这里有个地方需要强调一下,就是(exclude = {DataSourceAutoConfiguration.class}),它的作用是 取消数据源自动导入 。SpringBoot会自动从配置文件中查找spring.datasource.相关属性并自动配置单数据源。因为在这个微服务工程并没有配置数据库的相关属性,所以不加exclude的话就会报错。
(exclude = {DataSourceAutoConfiguration.class})
*************************** APPLICATION FAILED TO START *************************** Description: Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured. Reason: Failed to determine a suitable driver class Action: Consider the following: If you want an embedded database (H2, HSQL or Derby), please put it on the classpath. If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).
因此我们需要添加这行内容来取消数据源自动导入。
接下来我们先启动Eureka工程后再来启动一下该项目试试,启动后访问http://127.0.0.1:7001 OK! 项目成功启动,接下来就实现一下文件上传,删除等功能。
为了方便管理,我们将文件信息封装成一个JavaBean, 在com.robod.file包下新建一个类FastDFSFile :
@Data //不要忘了导入Lombok的依赖 public class FastDFSFile { //文件名 private String name; //文件内容 private byte[] content; //文件扩展名 private String ext; //文件MD5摘要 private String md5; //文件作者 private String author; public FastDFSFile(String name, byte[] content, String ext) { this.name = name; this.content = content; this.ext = ext; } public FastDFSFile (String name, byte[] content, String ext, String md5, String author) { this.name = name; this.content = content; this.ext = ext; this.md5 = md5; this.author = author; } }
现在再创建一个工具类来实现对文件的一些操作, 在com.robod.utils包下新建一个类FastDFSUtils :
public class FastDFSUtils { private static StorageClient storageClient; private static TrackerClient trackerClient; private static TrackerServer trackerServer; static { try { String path = new ClassPathResource("fdfs_client.conf").getPath(); //加载Tracker连接信息 ClientGlobal.init(path); //创建一个Tracker的客户端对象 trackerClient = new TrackerClient(); //通过TrackerClient访问TrackerServer服务,获取连接对象 trackerServer = trackerClient.getConnection(); //通过TrackerServer的连接信息去获取Storage的连接信息,存储进StorageClient对象中 storageClient = new StorageClient(trackerServer, null); } catch (Exception e) { e.printStackTrace(); } } /** * 上传文件 * * @param file */ public static String[] upload(FastDFSFile file) throws Exception { //上传文件 return storageClient.upload_file(file.getContent(), file.getExt(), null); } /** * 下载文件 * @param groupName 文件所在的组名 group1 * @param remoteFileName 文件的路径及名字 M00/00/00/wKgfyF8AjpSADeBLABXmlvc7iOY701.jpg */ public static InputStream downloadFile(String groupName, String remoteFileName) throws Exception { byte[] bytes = storageClient.download_file(groupName, remoteFileName); return new ByteArrayInputStream(bytes); } /** * 删除文件 * @param groupName 文件所在的组名 group1 * @param remoteFileName 文件的路径及名字 M00/00/00/wKgfyF8AjpSADeBLABXmlvc7iOY701.jpg */ public static int deleteFile(String groupName, String remoteFileName) throws Exception { return storageClient.delete_file(groupName, remoteFileName); } /** * 获取文件信息 * @param groupName 文件所在的组名 group1 * @param remoteFileName 文件的路径及名字 M00/00/00/wKgfyF8AjpSADeBLABXmlvc7iOY701.jpg */ public static FileInfo getFileInfo(String groupName, String remoteFileName) throws Exception { return storageClient.get_file_info(groupName,remoteFileName); } /** * 获取storage信息 * @return store_path_index */ public static StorageServer getStorage() throws IOException { return trackerClient.getStoreStorage(trackerServer); } /** * 获取Storage的IP和端口信息 * @param groupName * @param fileName * @return ServerInfo:ip_addr,port * @throws IOException */ public static ServerInfo[] getStorageInfo(String groupName, String fileName) throws IOException { return trackerClient.getFetchStorages(trackerServer,groupName,fileName); } /** * 获取Tracker信息 * @return */ public static String getTrackerInfo() { String ip = trackerServer.getInetSocketAddress().getHostString(); int port = ClientGlobal.getG_tracker_http_port(); return new StringBuilder(ip).append(":").append(port).toString(); } }
在这个类中我们封装了对文件的一些列操作,现在就来写个入口, 在com.robod.controller包下新建一个类FileController
@RestController @CrossOrigin @RequestMapping("/file") public class FileController { @PostMapping("/upload") public Result<String> upload(@RequestParam("file") MultipartFile multipartFile) throws Exception{ FastDFSFile fastDFSFile = new FastDFSFile( multipartFile.getOriginalFilename(), multipartFile.getBytes(), StringUtils.getFilenameExtension(multipartFile.getOriginalFilename())); System.out.println(fastDFSFile.toString() ); String[] upload = FastDFSUtils.upload(fastDFSFile); String groupName = upload[0]; String fileName = upload[1]; System.out.println(groupName); System.out.println(fileName); System.out.println("------------------"); System.out.println("获取文件信息"); FileInfo fileInfo = FastDFSUtils.getFileInfo(groupName, fileName); System.out.println(fileInfo.getSourceIpAddr()); System.out.println(fileInfo.getFileSize()); System.out.println(fileInfo.getCreateTimestamp()); System.out.println(fileInfo.getCrc32()); System.out.println("----------------------"); System.out.println("获取Storage信息"); StorageServer storage = FastDFSUtils.getStorage(); System.out.println(storage.getStorePathIndex()); System.out.println("-----------------"); System.out.println("获取Storage的IP和端口信息"); ServerInfo[] storageInfo = FastDFSUtils.getStorageInfo(groupName, fileName); for (ServerInfo serverInfo : storageInfo) { System.out.println(serverInfo.getIpAddr()); System.out.println(serverInfo.getPort()); } System.out.println("--------------------"); System.out.println("获取Tracker信息"); String trackerInfo = FastDFSUtils.getTrackerInfo(); System.out.println(trackerInfo); return new Result<>(true, StatusCode.OK,"文件上传成功","---"); } }
将项目启动起来,用postman发送请求。 从控制台的打印情况中可以看出,文件成功上传到了服务器中,而且前面写的一些获取信息的方法也正常运行了。
原文链接:https://blog.csdn.net/weixin_43461520/article/details/107137843