大量的图片和小视频在服务器上,如何存储?如何保证高并发下的下载速度?
分布式与集群
分布式是多个人干不同的事,解决的是高并发问题。
集群是多个人干一件事,解决的是高可用问题。
工作原理

Tracker (译:追踪者)
作用是负载均衡和调度,它管理着存储服务(Storage Server),可以理解为:“大管家,追踪者,调度员”;
Tracker Server可以集群,实现高可用,策略为“轮询”。
Storage (译:仓库; 贮存器)
作用是文件存储,客户端上传的文件最终存储到storage服务器上;
storage集群采用分组的方式,同组内的每台服务器是平等关系,数据同步,目的是实现数据备份,从而高可用,而不同组的服务器之间是不通信的;
同组内的每台服务器的存储量不一致的情况下,会选取容量最小的那个,所以同组内的服务器之间软硬件最好保持一致。
Storage Server会连接集群中的所有Tracker Server,定时向他们汇报自己的状态,例如:剩余空间,文件同步情况,文件上传下载次数等信息。
上传/下载
上传
下载
客户端上传文件后,storage会将文件id返回给客户端
上传示例代码
<dependencies><dependency><groupId>net.oschina.zcx7878</groupId><artifactId>fastdfs-client-java</artifactId><version>1.27.0.0</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-io</artifactId><version>1.3.2</version></dependency></dependencies>
public class TestUpload {public static void main(String[] args) throws MyException, IOException {// 加载配置文件ClientGlobal.initByProperties("config/fastdfs-client.properties");// 创建tracker客户端TrackerClient trackerClient = new TrackerClient();// 通过tracker客户端获取tracker的连接服务并返回TrackerServer trackerServer = trackerClient.getConnection();// 声明storage服务StorageServer storageServer = null;// 定义storage客户端StorageClient1 client = new StorageClient1(trackerServer,storageServer);// 定义文件元信息NameValuePair[] list = new NameValuePair[1];list[0] = new NameValuePair("fileName","g1.png");String fileID = client.upload_file1("C:\\Users\\86221\\Pictures\\grils\\g1.png", "png", list);System.out.println("fileID = " + fileID);// group1/M00/00/00/CgHc918f8l6AFYp0AAWICfQnHuk889.jpg/*group1:一台服务器,就是一个组M00: store_path0 ----> /home/fastdfs/fdfs_storage/data00/00:两级数据目录*/trackerServer.close();}}
查询示例代码
public class TestQuery {public static void main(String[] args) throws MyException, IOException {// 加载配置文件ClientGlobal.initByProperties("config/fastdfs-client.properties");// 创建tracker客户端TrackerClient trackerClient = new TrackerClient();// 通过tracker客户端获取tracker的连接服务并返回TrackerServer trackerServer = trackerClient.getConnection();// 声明storage服务StorageServer storageServer = null;// 定义storage客户端StorageClient1 client = new StorageClient1(trackerServer,storageServer);FileInfo fileInfo = client.query_file_info1("group1/M00/00/00/wKgqg2GVCKOAfg_GAALKY1Ke7Rk381.png");System.out.println(fileInfo);trackerServer.close();}}
下载示例代码
public class TestDownLoad {public static void main(String[] args) throws MyException, IOException {ClientGlobal.initByProperties("config/fastdfs-client.properties");// 创建tracker客户端TrackerClient trackerClient = new TrackerClient();// 通过tracker客户端获取tracker的连接服务并返回TrackerServer trackerServer = trackerClient.getConnection();// 声明storage服务StorageServer storageServer = null;// 定义storage客户端StorageClient1 client = new StorageClient1(trackerServer,storageServer);byte[] bytes = client.download_file1("group1/M00/00/00/wKgqg2GVCKOAfg_GAALKY1Ke7Rk381.png");FileOutputStream fileOutputStream = new FileOutputStream(new File("girl.png"));fileOutputStream.write(bytes);fileOutputStream.close();trackerServer.close();}}
FastDFS实现多文件上传
其实还用了redis分布式锁。
依赖
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.ning</groupId><artifactId>lagou-imgServer</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><packaging>war</packaging><dependencies><!-- 因为有jsp页面,所以引用servlet依赖--><dependency><groupId>javax.servlet</groupId><artifactId>servlet-api</artifactId><scope>provided</scope><version>2.5</version></dependency><!-- 页面提交过来的请求,使用springmvc来处理--><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.2.7.RELEASE</version></dependency><!-- java连接fastDFS的客户端工具--><dependency><groupId>net.oschina.zcx7878</groupId><artifactId>fastdfs-client-java</artifactId><version>1.27.0.0</version></dependency><!-- 图片上传到FastDFS需要用的到IO工具--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-io</artifactId><version>1.3.2</version></dependency><!-- 图片保存到web服务器需要用到的IO工具--><dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.3.1</version></dependency><!--用来转换java对象和json字符串,注意,2.7以上版本必须搭配spring5.0以上--><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.9.8</version></dependency><!--实现分布式锁的工具类--><dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.6.1</version></dependency><!--spring操作redis的工具类--><dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-redis</artifactId><version>2.3.2.RELEASE</version></dependency><!--redis客户端--><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>3.1.0</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><configuration><port>8001</port><path>/</path></configuration><executions><execution><phase>package</phase><goals><goal>run</goal></goals></execution></executions></plugin></plugins></build></project>
web.xml
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://xmlns.jcp.org/xml/ns/javaee"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaeehttp://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"id="WebApp_ID" version="3.1"><servlet><servlet-name>springMVC</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring/spring-mvc.xml</param-value></init-param></servlet><servlet-mapping><servlet-name>springMVC</servlet-name><url-pattern>/</url-pattern></servlet-mapping></web-app>
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %><html><head><title>upload</title></head><body><form action="upload" method="post" enctype="multipart/form-data"><input type="file" name="fname"><br><button>提交</button></form><hr><form action="upload2" method="post" enctype="multipart/form-data" ><input id="File1" name="fileUpload" multiple="multiple" type="file" value="" /><input id="" type="submit" value="上传" /></form></body></html>
spring/spring-mvc.xml
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:mvc="http://www.springframework.org/schema/mvc"xmlns:context="http://www.springframework.org/schema/context"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!--扫描注解的包--><context:component-scan base-package="controller"/><!--扫描控制器中的注解:@Response--><mvc:annotation-driven/><!--上传文件的解析器(规定上传文件的大小限制)--><bean id="multipartResolver"class="org.springframework.web.multipart.commons.CommonsMultipartResolver"><!-- 上传文件最大限制:2GB--><property name="maxUploadSize" value="2048000000"/></bean><bean id="stringRedisTemplate"class="org.springframework.data.redis.core.StringRedisTemplate"><property name="connectionFactory" ref="connectionFactory"></property></bean><bean id="connectionFactory"class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"><property name="hostName" value="192.168.42.131"></property><property name="port" value="6379"/></bean></beans>
config/fastdfs-client.properties
##fastdfs-client.propertiesfastdfs.connect_timeout_in_seconds = 5fastdfs.network_timeout_in_seconds = 30fastdfs.charset = UTF-8fastdfs.http_anti_steal_token = falsefastdfs.http_secret_key = FastDFS1234567890fastdfs.http_tracker_http_port = 80fastdfs.tracker_servers = 192.168.42.131:22122
controller/FileController
package controller;import entity.FileSystem;import org.csource.common.MyException;import org.csource.common.NameValuePair;import org.csource.fastdfs.*;import org.redisson.Redisson;import org.redisson.api.RLock;import org.redisson.config.Config;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.multipart.MultipartFile;import org.springframework.web.multipart.MultipartHttpServletRequest;import java.io.File;import java.io.IOException;import java.util.ArrayList;import java.util.UUID;import java.util.concurrent.TimeUnit;@Controllerpublic class FileController {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Beanpublic Redisson getRedisson() {Config config = new Config();config.useSingleServer().setAddress("redis://192.168.42.131:6379").setDatabase(0);// config.useClusterServers().setScanInterval(2000).addNodeAddress("redis://192.168.42.131:6379",// "redis://192.168.42.132:6379", "redis://192.168.42.133:6379");return (Redisson) Redisson.create(config);}@Autowiredprivate Redisson redisson;@RequestMapping("/upload")@ResponseBodypublic synchronized FileSystem upload(MultipartHttpServletRequest request) throws IOException, MyException {FileSystem fileSystem = new FileSystem();MultipartFile file = request.getFile("fname");String originalFilename = file.getOriginalFilename();String suffixName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);String newFileName = UUID.randomUUID().toString() + "." +suffixName;File locallFile = new File("D:/upload/" + newFileName);file.transferTo(locallFile);String newFilePath = locallFile.getAbsolutePath();// to FastDFSClientGlobal.initByProperties("config/fastdfs-client.properties");TrackerClient trackerClient = new TrackerClient();TrackerServer trackerServer = trackerClient.getConnection();StorageServer storageServer = null;StorageClient1 storageClient1 = new StorageClient1(trackerServer, storageServer);NameValuePair[] list = new NameValuePair[1];list[0] = new NameValuePair("fileName", originalFilename);String fileId = storageClient1.upload_file1(newFilePath, suffixName, list);trackerServer.close();fileSystem.setFileId(fileId);fileSystem.setFileName(originalFilename);fileSystem.setFilePath(fileId);System.out.println(fileId);return fileSystem;}@RequestMapping("/upload2")@ResponseBodypublic Object upload2(@RequestParam("fileUpload") MultipartFile[] files) {RLock lock = redisson.getLock("videos");if (files == null || files.length < 1) {return "no file!";}lock.lock(files.length* 10L, TimeUnit.SECONDS);ArrayList<FileSystem> arrayList = null;try {arrayList = new ArrayList<>();for (MultipartFile file : files) {FileSystem fileSystem = new FileSystem();String originalFilename = file.getOriginalFilename();if (originalFilename.equals(""))continue;String suffixName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);String newFileName = UUID.randomUUID().toString() + "." +suffixName;File locallFile = new File("D:/upload/" + newFileName);file.transferTo(locallFile);String newFilePath = locallFile.getAbsolutePath();// to FastDFSClientGlobal.initByProperties("config/fastdfs-client.properties");TrackerClient trackerClient = new TrackerClient();TrackerServer trackerServer = trackerClient.getConnection();StorageServer storageServer = null;StorageClient1 storageClient1 = new StorageClient1(trackerServer, storageServer);NameValuePair[] list = new NameValuePair[1];list[0] = new NameValuePair("fileName", originalFilename);String fileId = storageClient1.upload_file1(newFilePath, suffixName, list);trackerServer.close();fileSystem.setFileId(fileId);fileSystem.setFileName(originalFilename);fileSystem.setFilePath(fileId);System.out.println(fileId);arrayList.add(fileSystem);}} catch (IOException | IllegalStateException | MyException e) {e.printStackTrace();} finally {lock.unlock();}return arrayList;}}
entity/FileSystem
package entity;import java.io.Serializable;public class FileSystem implements Serializable {private String fileId;private String filePath;private String fileName;@Overridepublic String toString() {return "FileSystem{" +"fileId='" + fileId + '\'' +", filePath='" + filePath + '\'' +", fileName='" + fileName + '\'' +'}';}public String getFileId() {return fileId;}public void setFileId(String fileId) {this.fileId = fileId;}public String getFilePath() {return filePath;}public void setFilePath(String filePath) {this.filePath = filePath;}public String getFileName() {return fileName;}public void setFileName(String fileName) {this.fileName = fileName;}}
整体结构<br />
