天天看點

dubbo2.5-spring4-mybastis3.2-springmvc4-mongodb3.4-redis3.2整合(八)SpringMVC上傳檔案到FastDFS

版權聲明:本文為部落客原創文章,未經部落客允許不得轉載。 https://blog.csdn.net/u010046908/article/details/54906751

目前項目中需要存儲一些檔案、視訊等。于是乎,查找了一些關于檔案伺服器資料。其中有Lustre、HDFS、Gluster、Alluxio、Ceph 、FastDFS。下面簡單介紹一下:

  1. Lustre 是一個大規模的、安全可靠的、具備高可用性的叢集檔案系統,它是由SUN公司開發和維護的。該項目主要的目的就是開發下一代的叢集檔案系統,目前可以支援超過10000個節點,數以PB的資料存儲量。
  2. HDFS Hadoop Distributed File System,簡稱HDFS,是一個分布式檔案系統。HDFS是一個高度容錯性的系統,适合部署在廉價的機器上。HDFS能提供高吞吐量的資料通路,非常适合大規模資料集上的應用。
  3. GlusterFS 是一個叢集的檔案系統,支援PB級的資料量。GlusterFS 通過RDMA和TCP/IP方式将分布到不同伺服器上的存儲空間彙內建一個大的網絡化并行檔案系統。
  4. Alluxio 前身是Tachyon,是以記憶體為中心的分布式檔案系統,擁有高性能和容錯能力,能夠為叢集架構(如Spark、MapReduce)提供可靠的記憶體級速度的檔案共享服務。
  5. Ceph 是新一代開源分布式檔案系統,主要目标是設計成基于POSIX的沒有單點故障的分布式檔案系統,提高資料的容錯性并實作無縫的複制。
  6. FastDFS是一個開源的輕量級分布式檔案系統,它對檔案進行管理,功能包括:檔案存儲、檔案同步、檔案通路(檔案上傳、檔案下載下傳)等,解決了大容量存儲和負載均衡的問題。特别适合以檔案為載體的線上服務,如相冊網站、視訊網站等等。FastDFS為網際網路量身定制,充分考慮了備援備份、負載均衡、線性擴容等機制,并注重高可用、高性能等名額,使用FastDFS很容易搭建一套高性能的檔案伺服器叢集提供檔案上傳、下載下傳等服務。

    通過以上6中檔案的伺服器的介紹,我們業務非常适合選擇用FastDFS,是以就了解學習了一番,感覺确實頗為強大,在此再次感謝淘寶資深架構師餘慶大神開源了如此優秀的輕量級分布式檔案系統,本篇文章就記錄一下FastDFS的最新版本5.0.9在CentOS7中的安裝與配置。

1.Fastdfs的簡介

了解一下基礎概念,FastDFS是一個開源的輕量級分布式檔案系統,由跟蹤伺服器(tracker server)、存儲伺服器(storage server)和用戶端(client)三個部分組成,主要解決了海量資料存儲問題,特别适合以中小檔案(建議範圍:4KB < file_size <500MB)為載體的線上服務。

FastDFS系統結構如下圖所示:

跟蹤器和存儲節點都可以由一台多台伺服器構成。跟蹤器和存儲節點中的伺服器均可以随時增加或下線而不會影響線上服務。其中跟蹤器中的所有伺服器都是對等的,可以根據伺服器的壓力情況随時增加或減少。

為了支援大容量,存儲節點(伺服器)采用了分卷(或分組)的組織方式。存儲系統由一個或多個卷組成,卷與卷之間的檔案是互相獨立的,所有卷 的檔案容量累加就是整個存儲系統中的檔案容量。一個卷可以由一台或多台存儲伺服器組成,一個卷下的存儲伺服器中的檔案都是相同的,卷中的多台存儲伺服器起 到了備援備份和負載均衡的作用。

在卷中增加伺服器時,同步已有的檔案由系統自動完成,同步完成後,系統自動将新增伺服器切換到線上提供服務。

當存儲空間不足或即将耗盡時,可以動态添加卷。隻需要增加一台或多台伺服器,并将它們配置為一個新的卷,這樣就擴大了存儲系統的容量。

2.FastDFS的下載下傳

Fastdfs的穩定版下載下傳位址

3.FastDFS的 安裝

詳細見

CentOS 7 安裝配置分布式檔案系統 FastDFS 5.0.5

4.SpringMVC上傳檔案到FastDFS

4.1 fast_client.cnf配置

connect_timeout = 2
#網絡逾時時間
network_timeout = 30
#字元集
charset = UTF-8
#跟蹤伺服器的端口
http.tracker_http_port = 9099
http.anti_steal_token = no
http.secret_key = FastDFS1234567890
#跟蹤伺服器位址 。跟蹤伺服器主要是起到負載均衡的作用
tracker_server = 192.168.0.116:22122           

4.2 fastdfs檔案上傳的流程

上傳檔案互動過程:

  1. client詢問tracker上傳到的storage,不需要附加參數;
  2. tracker傳回一台可用的storage;
  3. client直接和storage通訊完成檔案上傳。

4.3 FastDFS檔案下載下傳的流程

下載下傳檔案互動過程:

  1. client詢問tracker下載下傳檔案的storage,參數為檔案辨別(卷名和檔案名);
  2. client直接和storage通訊完成檔案下載下傳。

需要說明的是,client為使用FastDFS服務的調用方,client也應該是一台伺服器,它對tracker和storage的調用均為伺服器間的調用。

4.4 FastDFSUtil 的封裝

package com.lidong.dubbo.util;

import org.csource.common.MyException;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.HashMap;
import java.util.Map;

/**
 * @項目名稱:lidong-dubbo
 * @類名:FastDFSUtil
 * @類的描述: FastDFS 上傳檔案到檔案伺服器
 * @作者:lidong
 * @建立時間:2017/2/6 下午5:23
 * @公司:chni
 * @QQ:1561281670
 * @郵箱:[email protected]
 */
public class FastDFSUtil {

    private final static

    Logger logger = LoggerFactory.getLogger(FastDFSUtil.class);


    /**
     *上傳伺服器本地檔案-通過Linux用戶端,調用用戶端指令上傳
     * @param filePath 檔案絕對路徑
     * @return Map<String,Object> code-傳回代碼, group-檔案組, msg-檔案路徑/錯誤資訊
     */
    public static Map<String, Object> uploadLocalFile(String filePath) {
        Map<String, Object> retMap = new HashMap<String, Object>();
        /**
         * 1.上傳檔案的指令
         */
        String command = "fdfs_upload_file /etc/fdfs/client.conf  " + filePath;
        /**
         * 2.定義檔案的傳回資訊
         */
        String fileId = "";
        InputStreamReader inputStreamReader = null;
        BufferedReader bufferedReader = null;
        try {
            /**
             * 3.通過調用api, 執行linux指令上傳檔案
             */
            Process process = Runtime.getRuntime().exec(command);
            /**
             * 4.讀取上傳後傳回的資訊
             */
             inputStreamReader = new InputStreamReader(process.getInputStream());
             bufferedReader = new BufferedReader(inputStreamReader);
            String line;
            if ((line = bufferedReader.readLine()) != null) {
                fileId = line;
            }
            /**
             * 5.如果fileId包含M00,說明檔案已經上傳成功。否則檔案上傳失敗
             */
            if (fileId.contains("M00")) {
                retMap.put("code", "0000");
                retMap.put("group", fileId.substring(0, 6));
                retMap.put("msg", fileId.substring(7, fileId.length()));
            } else {
                retMap.put("code", "0001");  //上傳錯誤
                retMap.put("msg", fileId);   //傳回資訊
            }

        } catch (Exception e) {
            logger.error("IOException:" + e.getMessage());
            retMap.put("code", "0002");
            retMap.put("msg", e.getMessage());
        }finally {
            if (inputStreamReader!=null){
                try {
                    inputStreamReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return retMap;
    }


    /**
     * Description: 直接通過fdfs java用戶端上傳到伺服器-讀取本地檔案上傳
     *
     * @param filePath 本地檔案絕對路徑
     * @return Map<String,Object> code-傳回代碼, group-檔案組, msg-檔案路徑/錯誤資訊
     */
    public static Map<String, Object> upload(String filePath) {
        Map<String, Object> retMap = new HashMap<String, Object>();
        File file = new File(filePath);
        TrackerServer trackerServer = null;
        StorageServer storageServer = null;
        if (file.isFile()) {
            try {
                String tempFileName = file.getName();
                byte[] fileBuff = FileUtil.getBytesFromFile(file);
                String fileId = "";
                //截取字尾
                String fileExtName = tempFileName.substring(tempFileName.lastIndexOf(".") + 1);
                ConfigAndConnectionServer configAndConnectionServer = new ConfigAndConnectionServer().invoke(1);
                StorageClient1 storageClient1 = configAndConnectionServer.getStorageClient1();
                storageServer = configAndConnectionServer.getStorageServer();
                trackerServer = configAndConnectionServer.getTrackerServer();

                /**
              * 4.設定檔案的相關屬性。調用用戶端的upload_file1的方法上傳檔案
              */
                NameValuePair[] metaList = new NameValuePair[3];
                //原始檔案名稱
                metaList[0] = new NameValuePair("fileName", tempFileName);
                //檔案字尾
                metaList[1] = new NameValuePair("fileExtName", fileExtName);
                //檔案大小
                metaList[2] = new NameValuePair("fileLength", String.valueOf(file.length()));
                //開始上傳檔案
                fileId = storageClient1.upload_file1(fileBuff, fileExtName, metaList);
                retMap = handleResult(retMap, fileId);
            } catch (Exception e) {
                e.printStackTrace();
                retMap.put("code", "0002");
                retMap.put("msg", e.getMessage());
            }finally {
                /**
                 * 5.關閉跟蹤伺服器的連接配接
                 */
                colse(storageServer, trackerServer);
            }
        } else {
            retMap.put("code", "0001");
            retMap.put("msg", "error:本地檔案不存在!");
        }
        return retMap;
    }


    /**
     * Description:遠端選擇上傳檔案-通過MultipartFile
     *
     * @param file 檔案流
     * @return Map<String,Object> code-傳回代碼, group-檔案組, msg-檔案路徑/錯誤資訊
     */
    public static Map<String, Object> upload(MultipartFile file) {
        Map<String, Object> retMap = new HashMap<String, Object>();
        TrackerServer trackerServer = null;
        StorageServer storageServer = null;
        try {
            if (file.isEmpty()) {
                retMap.put("code", "0001");
                retMap.put("msg", "error:檔案為空!");
            } else {
                ConfigAndConnectionServer configAndConnectionServer = new ConfigAndConnectionServer().invoke(1);
                StorageClient1 storageClient1 = configAndConnectionServer.getStorageClient1();
                storageServer = configAndConnectionServer.getStorageServer();
                trackerServer = configAndConnectionServer.getTrackerServer();
                String tempFileName = file.getOriginalFilename();
                //設定元資訊
                NameValuePair[] metaList = new NameValuePair[3];
                //原始檔案名稱
                metaList[0] = new NameValuePair("fileName", tempFileName);
                //檔案字尾
                byte[] fileBuff = file.getBytes();
                String fileId = "";
                //截取字尾
                String fileExtName = tempFileName.substring(tempFileName.lastIndexOf(".") + 1);

                metaList[1] = new NameValuePair("fileExtName", fileExtName);
                //檔案大小
                metaList[2] = new NameValuePair("fileLength", String.valueOf(file.getSize()));
                /**
                 * 4.調用用戶端呢的upload_file1的方法開始上傳檔案
                 */
                fileId = storageClient1.upload_file1(fileBuff, fileExtName, metaList);
                retMap = handleResult(retMap, fileId);
            }
        } catch (Exception e) {
            retMap.put("code", "0002");
            retMap.put("msg", "error:檔案上傳失敗!");
        }finally {
            /**
             * 5.關閉跟蹤伺服器的連接配接
             */
            colse(storageServer, trackerServer);
        }
        return retMap;
    }


    /**
     * 下載下傳檔案
     *
     * @param response
     * @param filepath 資料庫存的檔案路徑
     * @param downname 下載下傳後的名稱
     *                 filepath M00/開頭的檔案路徑
     *                 group 檔案所在的組 如:group0
     * @throws IOException
     */
    public static void download(HttpServletResponse response, String group, String filepath, String downname) {
        StorageServer storageServer = null;
        TrackerServer trackerServer = null;
        try {
            ConfigAndConnectionServer configAndConnectionServer = new ConfigAndConnectionServer().invoke(0);
            StorageClient storageClient = configAndConnectionServer.getStorageClient();
            storageServer = configAndConnectionServer.getStorageServer();
            trackerServer = configAndConnectionServer.getTrackerServer();

            /**
             *4.調用用戶端的下載下傳download_file的方法
             */
            byte[] b = storageClient.download_file(group, filepath);
            if (b == null) {
                logger.error("Error1 : file not Found!");
                response.getWriter().write("Error1 : file not Found!");
            } else {
                logger.info("下載下傳檔案..");
                downname = new String(downname.getBytes("utf-8"), "ISO8859-1");
                response.setHeader("Content-Disposition", "attachment;fileName=" + downname);
                OutputStream out = response.getOutputStream();
                out.write(b);
                out.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
            try {
                response.getWriter().write("Error1 : file not Found!");
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }finally {
            /**
             * 5.關閉跟蹤伺服器的連接配接
             */
            colse(storageServer, trackerServer);
        }
    }

    /**
     * 删除檔案
     *
     * @param group 檔案分組,  filepath 已M00/ 開頭的檔案路徑
     * @return Map<String,Object> code-傳回代碼,  msg-錯誤資訊
     */
    public static Map<String, Object> delete(String group, String filepath) {
        Map<String, Object> retMap = new HashMap<String, Object>();
        StorageServer storageServer = null;
        TrackerServer trackerServer = null;
        try {
            ConfigAndConnectionServer configAndConnectionServer = new ConfigAndConnectionServer().invoke(0);
            StorageClient storageClient = configAndConnectionServer.getStorageClient();
            storageServer = configAndConnectionServer.getStorageServer();
            trackerServer = configAndConnectionServer.getTrackerServer();
            /**
             * 4.調用用戶端的delete_file方法删除檔案
             */
            int i = storageClient.delete_file(group, filepath);
            if (i == 0) {
                retMap.put("code", "0000");
                retMap.put("msg", "删除成功!");
            } else {
                retMap.put("code", "0001");
                retMap.put("msg", "檔案不存在!");
            }
        } catch (Exception e) {
            e.printStackTrace();
            retMap.put("code", "0002");
            retMap.put("msg", "删除失敗!");
        } finally {
            /**
             * 5.關閉跟蹤伺服器的連接配接
             */
            colse(storageServer, trackerServer);
        }

        return retMap;

    }

    /**
     * 關閉伺服器
     *
     * @param storageServer
     * @param trackerServer
     */
    private static void colse(StorageServer storageServer, TrackerServer trackerServer) {
        if (storageServer != null && trackerServer != null) {
            try {
                storageServer.close();
                trackerServer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

    /**
     * 處理上傳到檔案伺服器之後,傳回來的結果
     *
     * @param retMap
     * @param fileId
     * @return
     */
    private static Map<String, Object> handleResult(Map<String, Object> retMap, String fileId) {
        if (!fileId.equals("") && fileId != null) {
            retMap.put("code", "0000");
            retMap.put("group", fileId.substring(0, 6));
            retMap.put("msg", fileId.substring(7, fileId.length()));
        } else {
            retMap.put("code", "0003");
            retMap.put("msg", "error:上傳失敗!");
        }

        return retMap;
    }

    /**
     * @項目名稱:lidong-dubbo
     * @類名:FastDFSUtil
     * @類的描述: ConfigAndConnectionServer
     * @作者:lidong
     * @建立時間:2017/2/7 上午8:47
     * @公司:chni
     * @QQ:1561281670
     * @郵箱:[email protected]
     */
    private static class ConfigAndConnectionServer {
        private TrackerServer trackerServer;
        private StorageServer storageServer;
        private StorageClient storageClient;
        private StorageClient1 storageClient1;


        public TrackerServer getTrackerServer() {
            return trackerServer;
        }

        public StorageServer getStorageServer() {
            return storageServer;
        }

        public StorageClient getStorageClient() {
            return storageClient;
        }

        public StorageClient1 getStorageClient1() {
            return storageClient1;
        }

        public ConfigAndConnectionServer invoke(int flag) throws IOException, MyException {
            /**
             * 1.讀取fastDFS用戶端配置檔案
             */
            ClassPathResource cpr = new ClassPathResource("fdfs_client.conf");
            /**
             * 2.配置檔案的初始化資訊
             */
            ClientGlobal.init(cpr.getClassLoader().getResource("fdfs_client.conf").getPath());
            TrackerClient tracker = new TrackerClient();
            /**
             * 3.建立連接配接
             */
            trackerServer = tracker.getConnection();
            storageServer = null;
            /**
             * 如果flag=0時候,構造StorageClient對象否則構造StorageClient1
             */
            if (flag == 0) {
                storageClient = new StorageClient(trackerServer, storageServer);
            } else {
                storageClient1 = new StorageClient1(trackerServer, storageServer);
            }
            return this;
        }
    }
}

           

4.5 SpringMVC 上傳檔案到Fastdfs檔案伺服器

@RequestMapping("/upload")
    public String addUser(@RequestParam("file") CommonsMultipartFile[] files,
                          HttpServletRequest request){

        for(int i = 0;i<files.length;i++){
            logger.info("fileName-->" + files[i].getOriginalFilename()+"     file-size--->"+files[i].getSize());
            Map<String, Object> retMap = FastDFSUtil.upload(files[i]);
            String code = (String) retMap.get("code");
            String group = (String) retMap.get("group");
            String msg = (String) retMap.get("msg");

            if ("0000".equals(code)){
                logger.info("檔案上傳成功");
                //TODO:将上傳檔案的路徑儲存到mysql資料庫
            }else {
                logger.info("檔案上傳失敗");
            }


        }
        return "/success";
    }
           

基本上就這麼多。大家在學習的過程中如果遇到問題。可以直接在下面評論、吐槽。

代碼位址