大量的图片和小视频在服务器上,如何存储?如何保证高并发下的下载速度?

分布式与集群

分布式是多个人干不同的事,解决的是高并发问题。
集群是多个人干一件事,解决的是高可用问题。

工作原理

image.png

Tracker (译:追踪者)
作用是负载均衡和调度,它管理着存储服务(Storage Server),可以理解为:“大管家,追踪者,调度员”;
Tracker Server可以集群,实现高可用,策略为“轮询”。

Storage (译:仓库; 贮存器)
作用是文件存储,客户端上传的文件最终存储到storage服务器上;
storage集群采用分组的方式,同组内的每台服务器是平等关系,数据同步,目的是实现数据备份,从而高可用,而不同组的服务器之间是不通信的;
同组内的每台服务器的存储量不一致的情况下,会选取容量最小的那个,所以同组内的服务器之间软硬件最好保持一致。
Storage Server会连接集群中的所有Tracker Server,定时向他们汇报自己的状态,例如:剩余空间,文件同步情况,文件上传下载次数等信息。

上传/下载

上传
FastDFS - 图2

下载
FastDFS - 图3

客户端上传文件后,storage会将文件id返回给客户端
image.png

上传示例代码

  1. <dependencies>
  2. <dependency>
  3. <groupId>net.oschina.zcx7878</groupId>
  4. <artifactId>fastdfs-client-java</artifactId>
  5. <version>1.27.0.0</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.apache.commons</groupId>
  9. <artifactId>commons-io</artifactId>
  10. <version>1.3.2</version>
  11. </dependency>
  12. </dependencies>

  1. public class TestUpload {
  2. public static void main(String[] args) throws MyException, IOException {
  3. // 加载配置文件
  4. ClientGlobal.initByProperties("config/fastdfs-client.properties");
  5. // 创建tracker客户端
  6. TrackerClient trackerClient = new TrackerClient();
  7. // 通过tracker客户端获取tracker的连接服务并返回
  8. TrackerServer trackerServer = trackerClient.getConnection();
  9. // 声明storage服务
  10. StorageServer storageServer = null;
  11. // 定义storage客户端
  12. StorageClient1 client = new StorageClient1(trackerServer,
  13. storageServer);
  14. // 定义文件元信息
  15. NameValuePair[] list = new NameValuePair[1];
  16. list[0] = new NameValuePair("fileName","g1.png");
  17. String fileID = client.upload_file1("C:\\Users\\86221\\Pictures\\grils\\g1.png", "png", list);
  18. System.out.println("fileID = " + fileID);
  19. // group1/M00/00/00/CgHc918f8l6AFYp0AAWICfQnHuk889.jpg
  20. /*
  21. group1:一台服务器,就是一个组
  22. M00: store_path0 ----> /home/fastdfs/fdfs_storage/data
  23. 00/00:两级数据目录
  24. */
  25. trackerServer.close();
  26. }
  27. }

查询示例代码

  1. public class TestQuery {
  2. public static void main(String[] args) throws MyException, IOException {
  3. // 加载配置文件
  4. ClientGlobal.initByProperties("config/fastdfs-client.properties");
  5. // 创建tracker客户端
  6. TrackerClient trackerClient = new TrackerClient();
  7. // 通过tracker客户端获取tracker的连接服务并返回
  8. TrackerServer trackerServer = trackerClient.getConnection();
  9. // 声明storage服务
  10. StorageServer storageServer = null;
  11. // 定义storage客户端
  12. StorageClient1 client = new StorageClient1(trackerServer,
  13. storageServer);
  14. FileInfo fileInfo = client.query_file_info1("group1/M00/00/00/wKgqg2GVCKOAfg_GAALKY1Ke7Rk381.png");
  15. System.out.println(fileInfo);
  16. trackerServer.close();
  17. }
  18. }

下载示例代码

  1. public class TestDownLoad {
  2. public static void main(String[] args) throws MyException, IOException {
  3. ClientGlobal.initByProperties("config/fastdfs-client.properties");
  4. // 创建tracker客户端
  5. TrackerClient trackerClient = new TrackerClient();
  6. // 通过tracker客户端获取tracker的连接服务并返回
  7. TrackerServer trackerServer = trackerClient.getConnection();
  8. // 声明storage服务
  9. StorageServer storageServer = null;
  10. // 定义storage客户端
  11. StorageClient1 client = new StorageClient1(trackerServer,
  12. storageServer);
  13. byte[] bytes = client.download_file1("group1/M00/00/00/wKgqg2GVCKOAfg_GAALKY1Ke7Rk381.png");
  14. FileOutputStream fileOutputStream = new FileOutputStream(new File("girl.png"));
  15. fileOutputStream.write(bytes);
  16. fileOutputStream.close();
  17. trackerServer.close();
  18. }
  19. }

FastDFS实现多文件上传

其实还用了redis分布式锁。

依赖

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <groupId>com.ning</groupId>
  7. <artifactId>lagou-imgServer</artifactId>
  8. <version>1.0-SNAPSHOT</version>
  9. <properties>
  10. <maven.compiler.source>8</maven.compiler.source>
  11. <maven.compiler.target>8</maven.compiler.target>
  12. </properties>
  13. <packaging>war</packaging>
  14. <dependencies>
  15. <!-- 因为有jsp页面,所以引用servlet依赖-->
  16. <dependency>
  17. <groupId>javax.servlet</groupId>
  18. <artifactId>servlet-api</artifactId>
  19. <scope>provided</scope>
  20. <version>2.5</version>
  21. </dependency>
  22. <!-- 页面提交过来的请求,使用springmvc来处理-->
  23. <dependency>
  24. <groupId>org.springframework</groupId>
  25. <artifactId>spring-webmvc</artifactId>
  26. <version>5.2.7.RELEASE</version>
  27. </dependency>
  28. <!-- java连接fastDFS的客户端工具-->
  29. <dependency>
  30. <groupId>net.oschina.zcx7878</groupId>
  31. <artifactId>fastdfs-client-java</artifactId>
  32. <version>1.27.0.0</version>
  33. </dependency>
  34. <!-- 图片上传到FastDFS需要用的到IO工具-->
  35. <dependency>
  36. <groupId>org.apache.commons</groupId>
  37. <artifactId>commons-io</artifactId>
  38. <version>1.3.2</version>
  39. </dependency>
  40. <!-- 图片保存到web服务器需要用到的IO工具-->
  41. <dependency>
  42. <groupId>commons-fileupload</groupId>
  43. <artifactId>commons-fileupload</artifactId>
  44. <version>1.3.1</version>
  45. </dependency>
  46. <!--用来转换java对象和json字符串,注意,2.7以上版本必须搭配spring5.0以上-->
  47. <dependency>
  48. <groupId>com.fasterxml.jackson.core</groupId>
  49. <artifactId>jackson-databind</artifactId>
  50. <version>2.9.8</version>
  51. </dependency>
  52. <!--实现分布式锁的工具类-->
  53. <dependency>
  54. <groupId>org.redisson</groupId>
  55. <artifactId>redisson</artifactId>
  56. <version>3.6.1</version>
  57. </dependency>
  58. <!--spring操作redis的工具类-->
  59. <dependency>
  60. <groupId>org.springframework.data</groupId>
  61. <artifactId>spring-data-redis</artifactId>
  62. <version>2.3.2.RELEASE</version>
  63. </dependency>
  64. <!--redis客户端-->
  65. <dependency>
  66. <groupId>redis.clients</groupId>
  67. <artifactId>jedis</artifactId>
  68. <version>3.1.0</version>
  69. </dependency>
  70. </dependencies>
  71. <build>
  72. <plugins>
  73. <plugin>
  74. <groupId>org.apache.tomcat.maven</groupId>
  75. <artifactId>tomcat7-maven-plugin</artifactId>
  76. <configuration>
  77. <port>8001</port>
  78. <path>/</path>
  79. </configuration>
  80. <executions>
  81. <execution>
  82. <phase>package</phase>
  83. <goals>
  84. <goal>run</goal>
  85. </goals>
  86. </execution>
  87. </executions>
  88. </plugin>
  89. </plugins>
  90. </build>
  91. </project>
  1. web.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  4. xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
  5. http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
  6. id="WebApp_ID" version="3.1">
  7. <servlet>
  8. <servlet-name>springMVC</servlet-name>
  9. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  10. <init-param>
  11. <param-name>contextConfigLocation</param-name>
  12. <param-value>classpath:spring/spring-mvc.xml</param-value>
  13. </init-param>
  14. </servlet>
  15. <servlet-mapping>
  16. <servlet-name>springMVC</servlet-name>
  17. <url-pattern>/</url-pattern>
  18. </servlet-mapping>
  19. </web-app>
  1. index.jsp
  1. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  2. <html>
  3. <head>
  4. <title>upload</title>
  5. </head>
  6. <body>
  7. <form action="upload" method="post" enctype="multipart/form-data">
  8. <input type="file" name="fname">
  9. <br>
  10. <button>提交</button>
  11. </form>
  12. <hr>
  13. <form action="upload2" method="post" enctype="multipart/form-data" >
  14. <input id="File1" name="fileUpload" multiple="multiple" type="file" value="" />
  15. <input id="" type="submit" value="上传" />
  16. </form>
  17. </body>
  18. </html>
  1. spring/spring-mvc.xml
  1. <beans xmlns="http://www.springframework.org/schema/beans"
  2. xmlns:mvc="http://www.springframework.org/schema/mvc"
  3. xmlns:context="http://www.springframework.org/schema/context"
  4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans.xsd
  7. http://www.springframework.org/schema/mvc
  8. http://www.springframework.org/schema/mvc/spring-mvc.xsd
  9. http://www.springframework.org/schema/context
  10. http://www.springframework.org/schema/context/spring-context.xsd">
  11. <!--扫描注解的包-->
  12. <context:component-scan base-package="controller"/>
  13. <!--扫描控制器中的注解:@Response-->
  14. <mvc:annotation-driven/>
  15. <!--上传文件的解析器(规定上传文件的大小限制)-->
  16. <bean id="multipartResolver"
  17. class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
  18. <!-- 上传文件最大限制:2GB-->
  19. <property name="maxUploadSize" value="2048000000"/>
  20. </bean>
  21. <bean id="stringRedisTemplate"
  22. class="org.springframework.data.redis.core.StringRedisTemplate">
  23. <property name="connectionFactory" ref="connectionFactory"></property>
  24. </bean>
  25. <bean id="connectionFactory"
  26. class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
  27. <property name="hostName" value="192.168.42.131"></property>
  28. <property name="port" value="6379"/>
  29. </bean>
  30. </beans>
  1. config/fastdfs-client.properties
  1. ##fastdfs-client.properties
  2. fastdfs.connect_timeout_in_seconds = 5
  3. fastdfs.network_timeout_in_seconds = 30
  4. fastdfs.charset = UTF-8
  5. fastdfs.http_anti_steal_token = false
  6. fastdfs.http_secret_key = FastDFS1234567890
  7. fastdfs.http_tracker_http_port = 80
  8. fastdfs.tracker_servers = 192.168.42.131:22122
  1. controller/FileController
  1. package controller;
  2. import entity.FileSystem;
  3. import org.csource.common.MyException;
  4. import org.csource.common.NameValuePair;
  5. import org.csource.fastdfs.*;
  6. import org.redisson.Redisson;
  7. import org.redisson.api.RLock;
  8. import org.redisson.config.Config;
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.context.annotation.Bean;
  11. import org.springframework.data.redis.core.StringRedisTemplate;
  12. import org.springframework.stereotype.Controller;
  13. import org.springframework.web.bind.annotation.RequestMapping;
  14. import org.springframework.web.bind.annotation.RequestParam;
  15. import org.springframework.web.bind.annotation.ResponseBody;
  16. import org.springframework.web.multipart.MultipartFile;
  17. import org.springframework.web.multipart.MultipartHttpServletRequest;
  18. import java.io.File;
  19. import java.io.IOException;
  20. import java.util.ArrayList;
  21. import java.util.UUID;
  22. import java.util.concurrent.TimeUnit;
  23. @Controller
  24. public class FileController {
  25. @Autowired
  26. private StringRedisTemplate stringRedisTemplate;
  27. @Bean
  28. public Redisson getRedisson() {
  29. Config config = new Config();
  30. config.useSingleServer().setAddress("redis://192.168.42.131:6379").setDatabase(0);
  31. // config.useClusterServers().setScanInterval(2000).addNodeAddress("redis://192.168.42.131:6379",
  32. // "redis://192.168.42.132:6379", "redis://192.168.42.133:6379");
  33. return (Redisson) Redisson.create(config);
  34. }
  35. @Autowired
  36. private Redisson redisson;
  37. @RequestMapping("/upload")
  38. @ResponseBody
  39. public synchronized FileSystem upload(MultipartHttpServletRequest request) throws IOException, MyException {
  40. FileSystem fileSystem = new FileSystem();
  41. MultipartFile file = request.getFile("fname");
  42. String originalFilename = file.getOriginalFilename();
  43. String suffixName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
  44. String newFileName = UUID.randomUUID().toString() + "." +suffixName;
  45. File locallFile = new File("D:/upload/" + newFileName);
  46. file.transferTo(locallFile);
  47. String newFilePath = locallFile.getAbsolutePath();
  48. // to FastDFS
  49. ClientGlobal.initByProperties("config/fastdfs-client.properties");
  50. TrackerClient trackerClient = new TrackerClient();
  51. TrackerServer trackerServer = trackerClient.getConnection();
  52. StorageServer storageServer = null;
  53. StorageClient1 storageClient1 = new StorageClient1(trackerServer, storageServer);
  54. NameValuePair[] list = new NameValuePair[1];
  55. list[0] = new NameValuePair("fileName", originalFilename);
  56. String fileId = storageClient1.upload_file1(newFilePath, suffixName, list);
  57. trackerServer.close();
  58. fileSystem.setFileId(fileId);
  59. fileSystem.setFileName(originalFilename);
  60. fileSystem.setFilePath(fileId);
  61. System.out.println(fileId);
  62. return fileSystem;
  63. }
  64. @RequestMapping("/upload2")
  65. @ResponseBody
  66. public Object upload2(@RequestParam("fileUpload") MultipartFile[] files) {
  67. RLock lock = redisson.getLock("videos");
  68. if (files == null || files.length < 1) {
  69. return "no file!";
  70. }
  71. lock.lock(files.length* 10L, TimeUnit.SECONDS);
  72. ArrayList<FileSystem> arrayList = null;
  73. try {
  74. arrayList = new ArrayList<>();
  75. for (MultipartFile file : files) {
  76. FileSystem fileSystem = new FileSystem();
  77. String originalFilename = file.getOriginalFilename();
  78. if (originalFilename.equals(""))
  79. continue;
  80. String suffixName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
  81. String newFileName = UUID.randomUUID().toString() + "." +suffixName;
  82. File locallFile = new File("D:/upload/" + newFileName);
  83. file.transferTo(locallFile);
  84. String newFilePath = locallFile.getAbsolutePath();
  85. // to FastDFS
  86. ClientGlobal.initByProperties("config/fastdfs-client.properties");
  87. TrackerClient trackerClient = new TrackerClient();
  88. TrackerServer trackerServer = trackerClient.getConnection();
  89. StorageServer storageServer = null;
  90. StorageClient1 storageClient1 = new StorageClient1(trackerServer, storageServer);
  91. NameValuePair[] list = new NameValuePair[1];
  92. list[0] = new NameValuePair("fileName", originalFilename);
  93. String fileId = storageClient1.upload_file1(newFilePath, suffixName, list);
  94. trackerServer.close();
  95. fileSystem.setFileId(fileId);
  96. fileSystem.setFileName(originalFilename);
  97. fileSystem.setFilePath(fileId);
  98. System.out.println(fileId);
  99. arrayList.add(fileSystem);
  100. }
  101. } catch (IOException | IllegalStateException | MyException e) {
  102. e.printStackTrace();
  103. } finally {
  104. lock.unlock();
  105. }
  106. return arrayList;
  107. }
  108. }
  1. entity/FileSystem
  1. package entity;
  2. import java.io.Serializable;
  3. public class FileSystem implements Serializable {
  4. private String fileId;
  5. private String filePath;
  6. private String fileName;
  7. @Override
  8. public String toString() {
  9. return "FileSystem{" +
  10. "fileId='" + fileId + '\'' +
  11. ", filePath='" + filePath + '\'' +
  12. ", fileName='" + fileName + '\'' +
  13. '}';
  14. }
  15. public String getFileId() {
  16. return fileId;
  17. }
  18. public void setFileId(String fileId) {
  19. this.fileId = fileId;
  20. }
  21. public String getFilePath() {
  22. return filePath;
  23. }
  24. public void setFilePath(String filePath) {
  25. this.filePath = filePath;
  26. }
  27. public String getFileName() {
  28. return fileName;
  29. }
  30. public void setFileName(String fileName) {
  31. this.fileName = fileName;
  32. }
  33. }
  1. 整体结构<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/12718322/1637215107910-7e3d871f-dbd3-46e3-b01a-8a415ea33a29.png#clientId=u689c66d6-b644-4&from=paste&height=324&id=u14905596&margin=%5Bobject%20Object%5D&name=image.png&originHeight=324&originWidth=497&originalType=binary&ratio=1&size=15701&status=done&style=none&taskId=u4e920521-72e5-4347-942d-182074f7492&width=497)