大型項目中浮禾,文件服務器是很重要的角色,如果只有一臺文件服務器尺借,一旦當機绊起,會產(chǎn)生很大影響,和業(yè)務服務器不同燎斩,文件服務器主要還是處理存放文件虱歪,和讀取文件的功能
專用分布式文件系統(tǒng)是基于google File System的思想,文件上傳后不能修改栅表。需要專門的api對文件進行訪問实蔽,也可稱作分布式文件存儲服務。典型代表:MogileFS谨读、FastDFS局装、TFS
FastDFS由國人余慶開發(fā),在chinaunix中擔任FastDFS版主劳殖。FastDFS的架構(gòu)如下:
三個角色:
角色 | 描述 |
---|---|
Client | 客戶端铐尚,調(diào)用方,就是我們的業(yè)務服務器的代碼 |
Tracker | 跟蹤服務器(索引服務器)哆姻,調(diào)度中心宣增,作負載均衡作用 |
Storage | 存儲服務器,文件真實的存儲服務器矛缨,同組內(nèi)使用RAID1 |
由圖可知爹脾,Tracker和Storage都支持集群,Storage內(nèi)部以組來區(qū)分
一箕昭、FastDFS安裝
1. 工具安裝
FastDFS是由c/c++編寫灵妨,使用了cmake進行編譯,所以需要安裝cmake落竹,至于make泌霍、gcc、g++編譯器述召,一般linux都自帶
yum install cmake
2. FastDFS下載
地址(墻):https://sourceforge.net/projects/fastdfs/files/
可以從我代碼倉庫中下載:https://gitee.com/aruba/fast-dfs.git
傳到服務器中:
3. 解壓編譯libfastcommon-master
解壓:
unzip libfastcommon-master.zip
cd libfastcommon-master
使用make.sh編譯:
./make.sh
編譯完后安裝:
./make.sh install
創(chuàng)建軟連接:
ln -s /user/lib64/libfastcommon.so /usr/local/lib/libfastcommon.so
ln -s /usr/local/lib64/libfdfsclient.so /usr/local/lib/libfdfsclient.so
4. 解壓編譯FastDFS
解壓:
tar zxf FastDFS_v5.08.tar.gz
cd FastDFS
編譯安裝:
./make.sh
./make.sh install
安裝后 FastDFS主程序所在的位置:
目錄 | 描述 |
---|---|
/usr/bin | 可執(zhí)行文件所在的位置朱转、主程序代碼所在位置 |
/etc/fdfs | 配置文件所在的位置 |
/usr/include/fastdfs | 包含一些插件組所在的位置 |
5. 配置和啟動Tracker
創(chuàng)建存放tracker數(shù)據(jù)的目錄:
mkdir -p /usr/local/fastdfs/tracker
進入配置目錄:
cd /etc/fdfs
復制sample配置文件:
cp tracker.conf.sample tracker.conf
修改配置文件:
vi tracker.conf
指定為上面創(chuàng)建的tracker目錄路徑:
啟動服務和查看服務啟動狀態(tài)命令:
service fdfs_trackerd start
service fdfs_trackerd status
6. 配置和啟動Storage
Tracker和Storage可以在不同服務器上
創(chuàng)建兩個目錄, 把base用于存儲基礎數(shù)據(jù)和日志积暖,store用于存儲上傳數(shù)據(jù):
mkdir -p /usr/local/fastdfs/storage/base
mkdir -p /usr/local/fastdfs/storage/store
復制sample配置文件:
cp storage.conf.sample storage.conf
修改配置文件:
vi storage.conf
指定上面創(chuàng)建的base和store目錄路徑藤为,以及Tracker服務器地址
-
base_path:
-
store_path0:
-
tracker_server:
啟動和查看服務:
service fdfs_storaged start
service fdfs_storaged status
最后記得關(guān)閉防火墻
二、FastDFS文件上傳和下載
FastDFS文件操作需要專門的API夺刑,不過都已經(jīng)封裝成工具類了
1. 依賴
<dependency>
<groupId>cn.bestwu</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.27</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
2. fdfs_client.conf
在resources目錄下新建配置文件fdfs_client.conf:
connect_timeout = 10
network_timeout = 30
charset = UTF-8
http.tracker_http_port = 8080
tracker_server = 192.168.42.4:22122
tracker服務器ip改為自己的
3. 工具類
/**
* FastDFS分布式文件系統(tǒng)操作客戶端.
*/
public class FastDFSClient {
private static final String CONF_FILENAME = Thread.currentThread().getContextClassLoader().getResource("").getPath() + "fdfs_client.conf";
private static StorageClient storageClient = null;
/**
* 只加載一次.
*/
static {
try {
ClientGlobal.init(CONF_FILENAME);
TrackerClient trackerClient = new TrackerClient(ClientGlobal.g_tracker_group);
TrackerServer trackerServer = trackerClient.getConnection();
StorageServer storageServer = trackerClient.getStoreStorage(trackerServer);
storageClient = new StorageClient(trackerServer, storageServer);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* @param inputStream 上傳的文件輸入流
* @param fileName 上傳的文件原始名
* @return
*/
public static String[] uploadFile(InputStream inputStream, String fileName) {
try {
// 文件的元數(shù)據(jù)
NameValuePair[] meta_list = new NameValuePair[2];
// 第一組元數(shù)據(jù)缅疟,文件的原始名稱
meta_list[0] = new NameValuePair("file name", fileName);
// 第二組元數(shù)據(jù)
meta_list[1] = new NameValuePair("file length", inputStream.available() + "");
// 準備字節(jié)數(shù)組
byte[] file_buff = null;
if (inputStream != null) {
// 查看文件的長度
int len = inputStream.available();
// 創(chuàng)建對應長度的字節(jié)數(shù)組
file_buff = new byte[len];
// 將輸入流中的字節(jié)內(nèi)容分别,讀到字節(jié)數(shù)組中。
inputStream.read(file_buff);
}
// 上傳文件窿吩。參數(shù)含義:要上傳的文件的內(nèi)容(使用字節(jié)數(shù)組傳遞)茎杂,上傳的文件的類型(擴展名),元數(shù)據(jù)
String[] fileids = storageClient.upload_file(file_buff, getFileExt(fileName), meta_list);
return fileids;
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
/**
* @param file 文件
* @param fileName 文件名
* @return 返回Null則為失敗
*/
public static String[] uploadFile(File file, String fileName) {
FileInputStream fis = null;
try {
NameValuePair[] meta_list = null; // new NameValuePair[0];
fis = new FileInputStream(file);
byte[] file_buff = null;
if (fis != null) {
int len = fis.available();
file_buff = new byte[len];
fis.read(file_buff);
}
String[] fileids = storageClient.upload_file(file_buff, getFileExt(fileName), meta_list);
return fileids;
} catch (Exception ex) {
return null;
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 根據(jù)組名和遠程文件名來刪除一個文件
*
* @param groupName 例如 "group1" 如果不指定該值纫雁,默認為group1
* @param remoteFileName 例如"M00/00/00/wKgxgk5HbLvfP86RAAAAChd9X1Y736.jpg"
* @return 0為成功煌往,非0為失敗,具體為錯誤代碼
*/
public static int deleteFile(String groupName, String remoteFileName) {
try {
int result = storageClient.delete_file(groupName == null ? "group1" : groupName, remoteFileName);
return result;
} catch (Exception ex) {
return 0;
}
}
/**
* 修改一個已經(jīng)存在的文件
*
* @param oldGroupName 舊的組名
* @param oldFileName 舊的文件名
* @param file 新文件
* @param fileName 新文件名
* @return 返回空則為失敗
*/
public static String[] modifyFile(String oldGroupName, String oldFileName, File file, String fileName) {
String[] fileids = null;
try {
// 先上傳
fileids = uploadFile(file, fileName);
if (fileids == null) {
return null;
}
// 再刪除
int delResult = deleteFile(oldGroupName, oldFileName);
if (delResult != 0) {
return null;
}
} catch (Exception ex) {
return null;
}
return fileids;
}
/**
* 文件下載
*
* @param groupName 卷名
* @param remoteFileName 文件名
* @return 返回一個流
*/
public static InputStream downloadFile(String groupName, String remoteFileName) {
try {
byte[] bytes = storageClient.download_file(groupName, remoteFileName);
InputStream inputStream = new ByteArrayInputStream(bytes);
return inputStream;
} catch (Exception ex) {
return null;
}
}
public static NameValuePair[] getMetaDate(String groupName, String remoteFileName) {
try {
NameValuePair[] nvp = storageClient.get_metadata(groupName, remoteFileName);
return nvp;
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
/**
* 獲取文件后綴名(不帶點).
*
* @return 如:"jpg" or "".
*/
private static String getFileExt(String fileName) {
if (StringUtils.isBlank(fileName) || !fileName.contains(".")) {
return "";
} else {
return fileName.substring(fileName.lastIndexOf(".") + 1); // 不帶最后的點
}
}
}
4. 文件上傳
public class Upload {
public static void main(String[] args) {
InputStream is = null;
try {
is = new FileInputStream(new File("C:\\Users\\tyqhc\\Pictures\\蘋果.png"));
String[] ret = FastDFSClient.uploadFile(is, "蘋果.png");
System.out.println(Arrays.toString(ret));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
執(zhí)行結(jié)果:
5. 文件下載
public class DownLoad {
public static void main(String[] args) {
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = FastDFSClient.downloadFile("group1", "M00/00/00/wKgqBGKsFF2AToPoAAAnvQxtles494.png");
outputStream = new FileOutputStream(new File("D:\\a.jpg"));
int index = 0;
while ((index = inputStream.read()) != -1) {
outputStream.write(index);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
try {
outputStream.flush();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
執(zhí)行結(jié)果:
三轧邪、Nginx反向代理
由于FastDFS不支持http刽脖,上傳后就無法通過http訪問了,所以使用Nginx反向代理忌愚,來支持使用http進行訪問
Nginx安裝前曲管,可以使用yum下載工具,就不用手動配置openssl等了:
yum install -y automake autoconf libtool pcre pcre-devel zlib zlib-devel openssl openssl-devel
Nginx下載可以參考以前的文章:Nginx流媒體服務器搭建硕糊,只需要下載nginx院水,并解壓
1. 解壓fastdfs-nginx-module_v1.16.tar.gz
tar zxf fastdfs-nginx-module_v1.16.tar.gz
2. 修改module配置
cd fastdfs-nginx-module
cd src
vi config
路徑中l(wèi)ocal去掉,修改后:
3. 編譯和安裝nginx
編譯nginx简十,指定編譯安裝到當前目錄的bin目錄下檬某,并添加FastDFS的module:
cd /root/nginx/nginx-1.12.1
./configure --prefix=`pwd`/bin --add-module=/root/nginx/fastdfs-nginx-module/src
安裝nginx:
make install
4. 配置FastDFS
4.1 將fastdfs-nginx-module模塊中的配置文件mod_fastdfs.conf,復制到FastDFS配置文件目錄/etc/fdfs下
cp /root/nginx/fastdfs-nginx-module/src/mod_fastdfs.conf /etc/fdfs
并進行編輯:
vi /etc/fdfs/mod_fastdfs.conf
修改內(nèi)容如下:
方便復制:
connect_timeout=10
tracker_server=192.168.42.4:22122
url_have_group_name=true
store_path0=/usr/local/fastdfs/storage/store
4.2 復制FastDFS解壓后的conf文件至/etc/fdfs下:
cp /root/fastdfs/FastDFS/conf/http.conf /etc/fdfs
cp /root/fastdfs/FastDFS/conf/mime.types /etc/fdfs
4.3 創(chuàng)建軟連接
M00是FastDFS保存數(shù)據(jù)時使用的虛擬目錄螟蝙,需要當它鏈接到真實目錄上:
ln -s /usr/local/fastdfs/storage/store/data/ /usr/local/fastdfs/storage/store/data/M00
5. 配置nginx
接下來就到了配置nginx的環(huán)節(jié)恢恼,配置nginx只需要修改它的nginx.conf文件
5.1 打開nginx.conf文件
定位到nginx安裝的目錄,通過vi編輯器打開:
cd /root/nginx/nginx-1.12.1/bin/conf
vi nginx.conf
5.2 權(quán)限使用root用戶
nginx需要訪問文件權(quán)限胰默,所以給它最大的用戶權(quán)限:
5.3 配置反向代理
FastDFS為http預留的是哪個端口呢场斑?查看/etc/fdfs/storage.conf 文件:
vi /etc/fdfs/storage.conf
可以看到默認使用的是8888:
所以我們需要反向代理8888端口,匹配group0到group9的/M00下的文件請求
5.4 啟動nginx
cd ../sbin
./nginx
使用瀏覽器訪問我們之前上傳的圖片:
ip:8888/group1/M00/00/00/wKgqBGKsFF2AToPoAAAnvQxtles494.png
四牵署、SpringBoot中使用
服務器配置好后漏隐,后臺開發(fā)上傳功能,無非就是在service層使用FastDFS工具類上傳碟刺,使用之前SpringBoot文件上傳的項目:SpringBoot--文件上傳
將上面文件上傳的工具類和依賴進行相同配置:
1. service層
定義上傳接口:
public interface UploadService {
String upload(MultipartFile file) throws IOException;
}
實現(xiàn)接口:
@Service
public class UploadServiceImpl implements UploadService {
@Override
public String upload(MultipartFile file) throws IOException {
String[] ret = FastDFSClient.uploadFile(file.getInputStream(), file.getName());
StringBuilder stringBuilder = new StringBuilder();
for (String item : ret) {
stringBuilder.append(File.separator).append(item);
}
return stringBuilder.toString();
}
}
2. controller層
注入UploadService并上傳:
@Controller
public class PlayerController {
// 文件存儲位置
private final static String FILESERVER = "http://192.168.42.4:8888/";
@Autowired
private UploadService uploadService;
@RequestMapping("uploadImg.do")
@ResponseBody
public Map<String, Object> uploadImg(MultipartFile img, HttpServletRequest req) throws IOException {
Map<String, Object> model = new HashMap<>();
System.out.println("OriginalFilename:" + img.getOriginalFilename());
String suffix = img.getOriginalFilename().substring(img.getOriginalFilename().lastIndexOf('.'));
System.out.println("suffix:" + suffix);
if (!suffix.equalsIgnoreCase(".jpg")) {
model.put("msg", "文件類型必須為jpg");
return model;
}
String path = uploadService.upload(img);
model.put("msg", "上傳成功");
model.put("filepath", FILESERVER + path);
model.put("filetype", img.getContentType());
System.out.println("返回結(jié)果:" + model);
return model;
}
}
啟動SpringBoot后锁保,瀏覽器訪問并上傳:
后臺控制臺也打印了日志: