分布式--分布式文件系統(tǒng)FastDFS

大型項目中浮禾,文件服務器是很重要的角色,如果只有一臺文件服務器尺借,一旦當機绊起,會產(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后锁保,瀏覽器訪問并上傳:

后臺控制臺也打印了日志:

項目地址:

https://gitee.com/aruba/fast-dfs.git

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市半沽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吴菠,老刑警劉巖者填,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異做葵,居然都是意外死亡占哟,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來榨乎,“玉大人怎燥,你說我怎么就攤上這事∶凼睿” “怎么了铐姚?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長肛捍。 經(jīng)常有香客問我隐绵,道長,這世上最難降的妖魔是什么拙毫? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任依许,我火速辦了婚禮,結(jié)果婚禮上缀蹄,老公的妹妹穿的比我還像新娘峭跳。我一直安慰自己,他們只是感情好缺前,可當我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布蛀醉。 她就那樣靜靜地躺著,像睡著了一般诡延。 火紅的嫁衣襯著肌膚如雪滞欠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天肆良,我揣著相機與錄音筛璧,去河邊找鬼。 笑死惹恃,一個胖子當著我的面吹牛夭谤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播巫糙,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼朗儒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了参淹?” 一聲冷哼從身側(cè)響起醉锄,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎浙值,沒想到半個月后恳不,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡开呐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年烟勋,在試婚紗的時候發(fā)現(xiàn)自己被綠了规求。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡卵惦,死狀恐怖阻肿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情沮尿,我是刑警寧澤丛塌,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站蛹找,受9級特大地震影響姨伤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜庸疾,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一乍楚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧届慈,春花似錦徒溪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至揍拆,卻和暖如春渠概,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嫂拴。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工播揪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人筒狠。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓猪狈,卻偏偏與公主長得像,于是被迫代替她去往敵國和親辩恼。 傳聞我的和親對象是個殘疾皇子雇庙,可洞房花燭夜當晚...
    茶點故事閱讀 44,969評論 2 355

推薦閱讀更多精彩內(nèi)容