天天看點

高可用高性能分布式檔案系統FastDFS實踐Java程式 下載下傳源碼并加入本地倉庫 示例源碼

  在前篇 高可用高性能分布式檔案系統FastDFS進階keepalived+nginx對多tracker進行高可用熱備 中已介紹搭建高可用的分布式檔案系統架構。

  那怎麼在程式中調用,其實網上有很多栗子,這裡在他們的基礎上作個簡單的介紹。

下載下傳源碼并加入本地倉庫

官網Java用戶端源代碼:https://github.com/happyfish100/fastdfs-client-java  

打開源碼後 執行maven install 将代碼打成jar到本地maven倉庫(這步可自行 google)

示例源碼

然後建立一個Demo工程,這裡采用spring mvc模式建立。

在pom中加入引用 maven中依賴jar包

<!-- fastdfs上傳下載下傳圖檔 路徑和上面的pom中對應 -->

<dependency>
    <groupId>org.csource</groupId>
    <artifactId>fastdfs-client-java</artifactId>
    <version>1.27-SNAPSHOT</version>
</dependency>      

fastdfs-client.properties

在resources的

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 = 10.0.11.201:22122,10.0.11.202:22122,10.0.11.203:22122      

 建立FastDFSClient工具類

package com.james.utils;

import org.csource.common.MyException;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.URLDecoder;

/**
 * Created by James on 2015/11/14.
 * FastDFS檔案上傳
 */
public class FastDFSClientUtils {
    private TrackerClient trackerClient = null;
    private TrackerServer trackerServer = null;
    private StorageServer storageServer = null;
    private StorageClient1 storageClient = null;

    public FastDFSClientUtils(String conf) throws Exception {
        if (conf.contains("classpath:")) {
            String path = this.getClass().getResource("/").getPath();
            conf = conf.replace("classpath:", URLDecoder.decode(path, "UTF-8"));
        }
        ClientGlobal.init(conf);
        trackerClient = new TrackerClient();
        trackerServer = trackerClient.getConnection();
        storageServer = null;
        storageClient = new StorageClient1(trackerServer, storageServer);
    }

    /**
     * 上傳檔案方法
     * <p>Title: uploadFile</p>
     * <p>Description: </p>
     *
     * @param fileName 檔案全路徑
     * @param extName  檔案擴充名,不包含(.)
     * @param metas    檔案擴充資訊
     * @return
     * @throws Exception
     */
    public String uploadFile(String fileName, String extName, NameValuePair[] metas) {
        String result = null;
        try {
            result = storageClient.upload_file1(fileName, extName, metas);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (MyException e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 上傳檔案,傳fileName
     *
     * @param fileName 檔案的磁盤路徑名稱 如:D:/image/aaa.jpg
     * @return null為失敗
     */
    public String uploadFile(String fileName) {
        return uploadFile(fileName, null, null);
    }

    /**
     * @param fileName 檔案的磁盤路徑名稱 如:D:/image/aaa.jpg
     * @param extName  檔案的擴充名 如 txt jpg等
     * @return null為失敗
     */
    public String uploadFile(String fileName, String extName) {
        return uploadFile(fileName, extName, null);
    }

    /**
     * 上傳檔案方法
     * <p>Title: uploadFile</p>
     * <p>Description: </p>
     *
     * @param fileContent 檔案的内容,位元組數組
     * @param extName     檔案擴充名
     * @param metas       檔案擴充資訊
     * @return
     * @throws Exception
     */
    public String uploadFile(byte[] fileContent, String extName, NameValuePair[] metas) {
        String result = null;
        try {
            result = storageClient.upload_file1(fileContent, extName, metas);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (MyException e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 上傳檔案
     *
     * @param fileContent 檔案的位元組數組
     * @return null為失敗
     * @throws Exception
     */
    public String uploadFile(byte[] fileContent) throws Exception {
        return uploadFile(fileContent, null, null);
    }

    /**
     * 上傳檔案
     *
     * @param fileContent 檔案的位元組數組
     * @param extName     檔案的擴充名 如 txt  jpg png 等
     * @return null為失敗
     */
    public String uploadFile(byte[] fileContent, String extName) {
        return uploadFile(fileContent, extName, null);
    }

    /**
     * 檔案下載下傳到磁盤
     *
     * @param path   圖檔路徑
     * @param output 輸出流 中包含要輸出到磁盤的路徑
     * @return -1失敗,0成功
     */
    public int download_file(String path, BufferedOutputStream output) {
        //byte[] b = storageClient.download_file(group, path);
        int result = -1;
        try {
            byte[] b = storageClient.download_file1(path);
            try {
                if (b != null) {
                    output.write(b);
                    result = 0;
                }
            } catch (Exception e) {
            } //使用者可能取消了下載下傳
            finally {
                if (output != null)
                    try {
                        output.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 擷取檔案數組
     *
     * @param path 檔案的路徑 如group1/M00/00/00/wKgRsVjtwpSAXGwkAAAweEAzRjw471.jpg
     * @return
     */
    public byte[] download_bytes(String path) {
        byte[] b = null;
        try {
            b = storageClient.download_file1(path);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (MyException e) {
            e.printStackTrace();
        }
        return b;
    }

    /**
     * 删除檔案
     *
     * @param group       組名 如:group1
     * @param storagePath 不帶組名的路徑名稱 如:M00/00/00/wKgRsVjtwpSAXGwkAAAweEAzRjw471.jpg
     * @return -1失敗,0成功
     */
    public Integer delete_file(String group, String storagePath) {
        int result = -1;
        try {
            result = storageClient.delete_file(group, storagePath);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (MyException e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * @param storagePath 檔案的全部路徑 如:group1/M00/00/00/wKgRsVjtwpSAXGwkAAAweEAzRjw471.jpg
     * @return -1失敗,0成功
     * @throws IOException
     * @throws Exception
     */
    public Integer delete_file(String storagePath) {
        int result = -1;
        try {
            result = storageClient.delete_file1(storagePath);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (MyException e) {
            e.printStackTrace();
        }
        return result;
    }
}      

Java用戶端檔案上傳、下載下傳、删除和中繼資料擷取測試:

package com.james.fdfs;
import org.junit.Test;
import java.io.File;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import  com.james.utils.FastDFSClientUtils;

public class FastDFSClientUtilsTest {

    /**
     * 檔案上傳測試
     */
    @Test
    public void testUpload() {
        File file = new File("C:\\Users\\James\\Pic\\share.jpg");
        Map<String,String> metaList = new HashMap<String, String>();
        metaList.put("width","1024");
        metaList.put("height","768");
        String fid = FastDFSClientUtils.uploadFile(file,file.getName(),metaList);
        System.out.println("upload local file " + file.getPath() + " ok, fileid=" + fid);
        //上傳成功傳回的檔案ID: group1/M00/00/00/wKgAyVgFk9aAB8hwAA-8Q6_7tHw351.jpg
    }

    /**
     * 檔案下載下傳測試
     */
    @Test
    public void testDownload() {
        int r = FastDFSClientUtils.download_file("group1/M00/00/00/wKgAyVgFk9aAB8hwAA-8Q6_7tHw351.jpg", new File("DownloadFile_fid.jpg"));
        System.out.println(r == 0 ? "下載下傳成功" : "下載下傳失敗");
    }


    /**
     * 檔案删除測試
     */
    @Test
    public void testDelete() {
        int r = FastDFSClientUtils.delete_file("group1/M00/00/00/wKgAyVgFk9aAB8hwAA-8Q6_7tHw351.jpg");
        System.out.println(r == 0 ? "删除成功" : "删除失敗");
    }
}      

如果沒有什麼問題将會看到列印的日志。

Net版本

net版本可參考另外一位網友代碼:

https://github.com/huanzui/fastdfs.client.net

問題

現在分布式檔案平台已經完成了搭建和代碼測試,但實踐過程中還是有幾個問題:

1、上傳到平台的檔案名都是無規律的64base編碼過的字元串,是以如果隻作為如圖檔等檔案存儲是沒有問題的,因為我們不關心其檔案名,但如果作為要下載下傳的内容,如附件,或安裝包,下載下傳時如果還是編碼那無法直覺的知道此檔案是做什麼的,是要轉換為正确的檔案名。

解決:關于這個問題,網上有方法是通過nginx,利用域名和FID拼出url,然後在url後面增加一個參數,指定原始檔案名。

例如:http://121.14.161.48:9030/group2/M00/00/89/eQ6h3FKJf_PRl8p4AUz4wO8tqaA688.apk?attname=filename.apk

在Nginx上進行如下配置,這樣Nginx就會截獲url中的參數attname,在Http響應頭裡面加上字段 Content-Disposition “attachment;filename=$arg_attname”。

這裡隻提供了一個方案,具體内容其實還需要一個篇幅來介紹,有時間再寫吧。

2、實際用的時候我們其實是想按業務還将不同檔案放在不同的檔案夾中的,比如聊天檔案,文檔檔案,還有臨時檔案 有時需要定時清理的,但分布式檔案平台是沒法指定檔案夾的。

解決:最常用的做法是自己實作一個檔案對應庫,将上傳的檔案名,時間,對應的業務等資訊與最終的檔案路徑對應起來,這樣就可以作任何邏輯了,但缺點是非常麻煩。

FastDFS沒有看到儲存到指定的2級目錄的API,但可以儲存到指定的group,可以指定某個group為哪個業務用。但這樣會破壞整個FastDFS的分布式結構,造成某個group非常巨大,而且不容易擴容。實際使用時還會有其它業務的内容進入到此group。

3、大檔案如何斷點續傳?

如果檔案大于100M,則需要斷點續傳的功能了,FastDFS對于大檔案來說是有點吃力的,但還是可以實作,根據網友提供的方案來看就是需要客戶進行切片上傳,并且切片位元組大小小于等于storage配置的buff_size,預設是256k,這一塊需要自己實作。