大量的图片和小视频在服务器上,如何存储?如何保证高并发下的下载速度?
分布式与集群
分布式是多个人干不同的事,解决的是高并发问题。
集群是多个人干一件事,解决的是高可用问题。
工作原理
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/data
00/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/javaee
http://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/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://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.properties
fastdfs.connect_timeout_in_seconds = 5
fastdfs.network_timeout_in_seconds = 30
fastdfs.charset = UTF-8
fastdfs.http_anti_steal_token = false
fastdfs.http_secret_key = FastDFS1234567890
fastdfs.http_tracker_http_port = 80
fastdfs.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;
@Controller
public class FileController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Bean
public 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);
}
@Autowired
private Redisson redisson;
@RequestMapping("/upload")
@ResponseBody
public 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 FastDFS
ClientGlobal.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")
@ResponseBody
public 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 FastDFS
ClientGlobal.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;
@Override
public 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 />![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)