分布式文件系统之-FastDFS

介绍各种分布式文件系统:http://elf8848.iteye.com/blog/1724382

架构研究参考:http://elf8848.iteye.com/blog/1739596

安装配置地址:http://www.open-open.com/lib/view/open1435468300700.html

FastDFS 工作原理参考网址:http://blog.chinaunix.net/uid-20196318-id-4058561.html

FastDFS分布文件系统介绍:

 1.FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等,不适合做数据处理.

 2.该分布式文件系统组成:
     跟踪服务器(tracker server)、存储服务器(storage server)和客户端(client)
     存储服务器:文件存储,文件同步,提供文件访问接口,同时以key value的方式管理文件的元数据。其中      文件访问采用了nginx,用于访问文件。
     跟踪服务器:调度文件以负载均衡的方式访问,它起到了一个协调的作用,负责管理storage。所以上传文件的监听端口是归跟踪服务器管的,不直接和存储服务器挂钩。
     它们俩的关系好比是,存储服务器是搬运的员工,而跟踪服务器是协调搬运的主管.
3.简单工作原理:
     1. 客户端向文件系统发送请求
     2.文件系统的tracker 查询可用的storage,返回给客户端可用的storge ip和端口号
     3.客户端上传文件(文件内容和文件名)
     4.文件系统端存储文件,生成文件id,返回给客户端(文件名和文件路径)
     5.客户端做相应处理,结束.

客户端上传至文件系统spring集成实现:
1.配置文件准备:fdfs_client.conf,参数如下:
connect_timeout = 2
network_timeout = 30
charset = utf-8
http.tracker_http_port = 追踪器访问文件端口号
http.anti_steal_token = 是否启用tokem(默认no)
http.secret_key = (上传文件密码,和服务器端的http.conf文件内设的密码保持一致)
tracker_server=追踪器ip:上传文件端口号
minPoolSize = (连接池最小数)
maxPoolSize = (连接池最大数)
waitTimes = 等待时间
2.web.xml新建bean

  <!-- 初始化配置文件服务器配置 -->
    <bean id="propertyConfigurer"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:/fdfs_client.conf</value>
            </list>
        </property>
    </bean>
    <bean name="fileFastDFSUtil" class="com.csx.util.aliFile.FastDFSUtil"
        init-method="init">
        <property name="minPoolSize" value="${minPoolSize}" />
        <property name="maxPoolSize" value="${maxPoolSize}" />
        <property name="waitTimes" value="${waitTimes}" />
    </bean>

3.重要工具类,可直接复制用
1.FastDFSUtil工具类


import java.net.SocketTimeoutException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.StorageClient1;
import org.csource.fastdfs.StorageServer;
import org.csource.fastdfs.TrackerServer;

import com.csx.util.ReadProperties;

public class FastDFSUtil {

    private final Log log = LogFactory.getLog("FastDfsUtil.class");
    /** 连接池 */
    private FastDFSFastDFSConnectionPool connectionPool = null;
    /** 连接池默认最小连接数 */
    private long minPoolSize = 10;
    /** 连接池默认最大连接数 */
    private long maxPoolSize = 30;
    /** 当前创建的连接数 */
    private volatile long nowPoolSize = 0;
    /** 默认等待时间(单位:秒) */
    private long waitTimes = 20;

    /**
     * 初始化线程池
     * 
     * @Description:
     * 
     */
    public void init() {

        log.info("[初始化线程池(Init)[默认参数:minPoolSize=" + minPoolSize + ",maxPoolSize=" + maxPoolSize + ",waitTimes="
                + waitTimes + "]");
        connectionPool = new FastDFSFastDFSConnectionPool(minPoolSize, maxPoolSize, waitTimes);
    }

    /**
     * 
     * @Description:
     * @param groupName
     *            组名如group0
     * @param fileBytes
     *            文件字节数组
     * @param extName
     *            文件扩展名:如png
     * @param linkUrl
     *            访问地址:http://image.xxx.com
     * @return 图片上传成功后地址
     * @throws FastException
     * 
     */
    public String upload(FastDFSFile file, NameValuePair[] valuePairs) throws FastException {
        /** 封装文件信息参数 */
        TrackerServer trackerServer = null;
        try {

            /** 获取fastdfs服务器连接 */
            trackerServer = connectionPool.checkout();
            StorageServer storageServer = null;
            StorageClient1 client = new StorageClient1(trackerServer, storageServer);

            String[] results = client.upload_file(file.getContent(), file.getExt(), valuePairs);

            /** 上传完毕及时释放连接 */
            connectionPool.checkin(trackerServer);

            log.info("[上传文件(upload)-fastdfs服务器相应结果][result:results=" + results + "]");

            /** results[0]:组名,results[1]:远程文件名 */
            if (results != null && results.length == 2) {
                return ReadProperties.getFile_ip() + "/" + results[0] + "/" + results[1];
            } else {
                /** 文件系统上传返回结果错误 */
                throw FastDFSERROR.UPLOAD_RESULT_ERROR.ERROR();
            }
        } catch (FastException e) {

            log.error("[上传文件(upload)][异常:" + e + "]");
            throw e;

        } catch (SocketTimeoutException e) {
            log.error("[上传文件(upload)][异常:" + e + "]");

            //将连接数减一
            connectionPool.drop(trackerServer);

            throw FastDFSERROR.WAIT_IDLECONNECTION_TIMEOUT.ERROR();
        } catch (Exception e) {

            log.error("[上传文件(upload)][异常:" + e + "]");
            connectionPool.drop(trackerServer);
            throw FastDFSERROR.SYS_ERROR.ERROR();

        }

    }

    /**
     * 
     * @Description: 删除fastdfs服务器中文件
     * @param group_name
     *            组名
     * @param remote_filename
     *            远程文件名称
     * @throws FastException
     * 
     */
    public void deleteFile(String group_name, String remote_filename) throws FastException {
        log.info("[ 删除文件(deleteFile)][parms:group_name=" + group_name + ",remote_filename=" + remote_filename + "]");
        TrackerServer trackerServer = null;

        try {
            /** 获取可用的tracker,并创建存储server */
            trackerServer = connectionPool.checkout();
            StorageServer storageServer = null;
            StorageClient1 client1 = new StorageClient1(trackerServer, storageServer);
            /** 删除文件,并释放 trackerServer */
            int result = client1.delete_file(group_name, remote_filename);

            /** 上传完毕及时释放连接 */
            connectionPool.checkin(trackerServer);

            log.info("[ 删除文件(deleteFile)--调用fastdfs客户端返回结果][results:result=" + result + "]");

            /** 0:文件删除成功,2:文件不存在 ,其它:文件删除出错 */
            if (result == 2) {

                throw FastDFSERROR.NOT_EXIST_FILE.ERROR();

            } else if (result != 0) {

                throw FastDFSERROR.DELETE_RESULT_ERROR.ERROR();

            }

        } catch (FastException e) {

            log.error("[ 删除文件(deleteFile)][异常:" + e + "]");
            throw e;

        } catch (SocketTimeoutException e) {
            log.error("[ 删除文件(deleteFile)][异常:" + e + "]");
            throw FastDFSERROR.WAIT_IDLECONNECTION_TIMEOUT.ERROR();
        } catch (Exception e) {

            log.error("[ 删除文件(deleteFile)][异常:" + e + "]");
            connectionPool.drop(trackerServer);
            throw FastDFSERROR.SYS_ERROR.ERROR();

        }
    }

    public FastDFSFastDFSConnectionPool getConnectionPool() {
        return connectionPool;
    }

    public void setConnectionPool(FastDFSFastDFSConnectionPool connectionPool) {
        this.connectionPool = connectionPool;
    }

    public long getMinPoolSize() {
        return minPoolSize;
    }

    public void setMinPoolSize(long minPoolSize) {
        this.minPoolSize = minPoolSize;
    }

    public long getMaxPoolSize() {
        return maxPoolSize;
    }

    public void setMaxPoolSize(long maxPoolSize) {
        this.maxPoolSize = maxPoolSize;
    }

    public long getNowPoolSize() {
        return nowPoolSize;
    }

    public void setNowPoolSize(long nowPoolSize) {
        this.nowPoolSize = nowPoolSize;
    }

    public long getWaitTimes() {
        return waitTimes;
    }

    public void setWaitTimes(long waitTimes) {
        this.waitTimes = waitTimes;
    }
}

2.自定义异常类:

public class FastException extends Exception {
    private static final long serialVersionUID = -1848618491499044704L;
    private String code;
    private String description;

    public FastException(String code, String message) {
        super(message);
        this.code = code;
    }

    public FastException(String code, String message, String description) {
        super(message);
        this.code = code;
        this.description = description;
    }

    /**
     * 错误码
     * 
     * @return
     */
    public String getCode() {
        return code;
    }

    /**
     * 用户可读描述信息
     * 
     * @return
     */
    public String getDescription() {
        return description;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getName());
        sb.append(": [");
        sb.append("] - ");
        sb.append(code);
        sb.append(" - ");
        sb.append(getMessage());
        if (getDescription() != null) {
            sb.append(" - ");
            sb.append(getDescription());
        }
        return sb.toString();
    }
}

3.报错信息枚举类FastDFSERROR

public enum FastDFSERROR {

    PARAMETER_IS_NULL("21001", "必填参数为空", "必填参数为空"),

    FASTDFS_CONNECTION_FAIL("21002", "连接fastdfs服务器失败", "文件上传异常,请重试"),

    WAIT_IDLECONNECTION_TIMEOUT("21003", "等待空闲连接超时", "连接超时,请重试"),

    NOT_EXIST_GROUP("21004", "文件组不存在", "文件组不存在"),

    UPLOAD_RESULT_ERROR("21005", "fastdfs文件系统上传返回结果错误", "文件上传异常,请重试"),

    NOT_EXIST_PORTURL("21006", "未找到对应的端口和访问地址", "文件上传异常,请重试"),

    SYS_ERROR("21007", "系统错误", "系统错误"),

    FILE_PATH_ERROR("21008", "文件访问地址格式不对", "文件访问地址格式不对"),

    DELETE_RESULT_ERROR("21009", "fastdfs文件系统删除文件返回结果错误", "文件删除异常,请重试"),

    NOT_EXIST_FILE("21010", "文件不存在", "文件不存在");

    /** 错误码 */
    String code;

    /** 错误信息,用于日志输出,便于问题定位 */
    String message;

    /** 错误提示,用于客户端提示 */
    String descreption;

    FastDFSERROR(String code, String message) {
        this.message = message;
        this.code = code;
    }

    FastDFSERROR(String code, String message, String descreption) {
        this.message = message;
        this.code = code;
        this.descreption = descreption;
    }

    public FastException ERROR() {
        return new FastException(this.code, this.message, this.descreption);
    }

    public FastException ERROR(String descreption) {
        return new FastException(this.code, this.message, descreption);
    }

}

4.文件上传类FastDFSFile

/**
 * 文件上传属性,调用fastdfs上传方法必需
 *
 */
public class FastDFSFile {
    private byte[] content;// 文件内容
    private String name;// 文件名
    private String ext;// 后缀
    private String fileId;// 文件id(唯一id)

    public FastDFSFile(byte[] content, String ext,String fileId) {
        this.content = content;
        this.ext = ext;
        this.fileId = fileId;
    }

    public FastDFSFile(byte[] content, String name, String ext,String fileId) {
        this.content = content;
        this.name = name;
        this.ext = ext;
        this.fileId = fileId;
    }

    public byte[] getContent() {
        return content;
    }

    public void setContent(byte[] content) {
        this.content = content;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getExt() {
        return ext;
    }

    public void setExt(String ext) {
        this.ext = ext;
    }

    public String getFileId() {
        return fileId;
    }

    public void setFileId(String fileId) {
        this.fileId = fileId;
    }

}

5.连接池实现类(由于每次上传文件都需要新建连接去上传文件,当并发数较多的情况下,会出现连接数过多的情况,为此我实现了连接池去实现,并每隔五秒去定时扫描看连接是否失效)
1. FastDFSFastDFSConnectionPool 连接池类

import java.io.IOException;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.csource.fastdfs.ClientGlobal;
import org.csource.fastdfs.TrackerClient;
import org.csource.fastdfs.TrackerServer;

/**
 * 
 * @ClassName: FastDFSConnectionPool
 * @Description: fastdfs连接池
 * @author william_zhong
 * @date 2017-4-13
 * 
 */
public class FastDFSFastDFSConnectionPool {
    private final Log log = LogFactory.getLog("FastDFSFastDFSConnectionPool.class");
    /** 空闲的连接池 */
    private LinkedBlockingQueue<TrackerServer> idleFastDFSConnectionPool = null;
    /** 连接池默认最小连接数 */
    private long minPoolSize = 10;
    /** 连接池默认最大连接数 */
    private long maxPoolSize = 30;
    /** 当前创建的连接数 */
    private volatile long nowPoolSize = 0;
    /** 默认等待时间(单位:秒) */
    private long waitTimes = 20;
    /** fastdfs客户端创建连接默认1次 */
    private static final int COUNT = 3;

    public static final String CLIENT_CONFIG_FILE = "fdfs_client.conf";

    /**
     * 默认构造方法
     */
    public FastDFSFastDFSConnectionPool(long minPoolSize, long maxPoolSize, long waitTimes) {

        log.info("[线程池构造方法(FastDFSConnectionPool)][" + "][默认参数:minPoolSize=" + minPoolSize + ",maxPoolSize="
                + maxPoolSize + ",waitTimes=" + waitTimes + "]");
        this.minPoolSize = minPoolSize;
        this.maxPoolSize = maxPoolSize;
        this.waitTimes = waitTimes;
        /** 初始化连接池 */
        poolInit();
        /** 注册心跳 */
        FastDFSHeartBeat beat = new FastDFSHeartBeat(this);
        beat.beat();
    }

    /**
     * 
     * @Description: 连接池初始化 (在加载当前FastDFSConnectionPool时执行) 1).加载配置文件
     *               2).空闲连接池初始化; 3).创建最小连接数的连接,并放入到空闲连接池;
     * 
     */
    private void poolInit() {
        try {
            /** 加载配置文件 */
            initClientGlobal();
            /** 初始化空闲连接池 */
            idleFastDFSConnectionPool = new LinkedBlockingQueue<TrackerServer>();
            /** 往线程池中添加默认大小的线程 */
            for (int i = 0; i < minPoolSize; i++) {
                createTrackerServer(COUNT);
            }
        } catch (Exception e) {

        }
    }

    /**
     * 
     * @Description: 创建TrackerServer,并放入空闲连接池
     * 
     */
    public void createTrackerServer(int flag) {

        TrackerServer trackerServer = null;

        try {

            TrackerClient trackerClient = new TrackerClient();
            trackerServer = trackerClient.getConnection();
            while (trackerServer == null && flag < 5) {
                log.info("[创建TrackerServer(createTrackerServer)][第" + flag + "次重建]");
                flag++;
                initClientGlobal();
                trackerServer = trackerClient.getConnection();
            }
            org.csource.fastdfs.ProtoCommon.activeTest(trackerServer.getSocket());
            idleFastDFSConnectionPool.add(trackerServer);
            /** 同一时间只允许一个线程对nowPoolSize操作 **/
            synchronized (this) {
                nowPoolSize++;
            }

        } catch (Exception e) {

            log.error("[创建TrackerServer(createTrackerServer)][异常:{}]", e);

        } finally {

            if (trackerServer != null) {
                try {
                    trackerServer.close();
                } catch (Exception e) {
                    log.error("[创建TrackerServer(createTrackerServer)--关闭trackerServer异常][异常:{}]", e);
                }
            }

        }
    }

    /**
     * 
     * @Description: 获取空闲连接 1).在空闲池(idleFastDFSConnectionPool)中弹出一个连接;
     *               2).把该连接放入忙碌池(busyFastDFSConnectionPool)中; 3).返回 connection
     *               4).如果没有idle connection, 等待 wait_time秒, and check again
     * 
     * @throws AppException
     * 
     */
    public TrackerServer checkout() throws FastException {

        log.info("[获取空闲连接(checkout)]");
        TrackerServer trackerServer = idleFastDFSConnectionPool.poll();

        if (trackerServer == null) {

            if (nowPoolSize < maxPoolSize) {
                createTrackerServer(COUNT);
                try {
                    trackerServer = idleFastDFSConnectionPool.poll(waitTimes, TimeUnit.SECONDS);
                } catch (Exception e) {
                    log.error("[获取空闲连接(checkout)-error][error:获取连接超时:{}]", e);
                    throw FastDFSERROR.WAIT_IDLECONNECTION_TIMEOUT.ERROR();
                }
            }
            if (trackerServer == null) {
                log.error("[获取空闲连接(checkout)-error][error:获取连接超时(" + waitTimes + "s)]");
                throw FastDFSERROR.WAIT_IDLECONNECTION_TIMEOUT.ERROR();
            }

        }
        log.info("[获取空闲连接(checkout)][获取空闲连接成功]");
        return trackerServer;

    }

    /**
     * 
     * @Description: 释放繁忙连接 1.如果空闲池的连接小于最小连接值,就把当前连接放入idleFastDFSConnectionPool;
     *               2.如果空闲池的连接等于或大于最小连接值,就把当前释放连接丢弃;
     * 
     * @param client1
     *            需释放的连接对象
     * 
     */

    public void checkin(TrackerServer trackerServer) {

        log.info("[释放当前连接(checkin)][prams:" + trackerServer + "] ");
        if (trackerServer != null) {
            if (idleFastDFSConnectionPool.size() < minPoolSize) {
                idleFastDFSConnectionPool.add(trackerServer);
            } else {
                synchronized (this) {
                    if (nowPoolSize != 0) {
                        nowPoolSize--;
                    }
                }
            }
        }

    }

    /**
     * 
     * @Description: 删除不可用的连接,并把当前连接数减一(调用过程中trackerServer报异常,调用一般在finally中)
     * @param trackerServer
     * 
     */
    public void drop(TrackerServer trackerServer) {
        log.info("[删除不可用连接方法(drop)][parms:" + trackerServer + "] ");
        if (trackerServer != null) {
            try {
                synchronized (this) {
                    if (nowPoolSize != 0) {
                        nowPoolSize--;
                    }
                }
                trackerServer.close();
            } catch (IOException e) {
                log.info("[删除不可用连接方法(drop)--关闭trackerServer异常][异常:{}]", e);
            }
        }
    }

    private void initClientGlobal() throws Exception {
        String classPath = FastDFSFastDFSConnectionPool.class.getResource("/").getPath().replaceAll("%20"," ");// 项目真实路径
        String fdfsClientConfigFilePath = classPath + CLIENT_CONFIG_FILE;// FastDFS客户端配置文件
        ClientGlobal.init(fdfsClientConfigFilePath);
    }

    public LinkedBlockingQueue<TrackerServer> getIdleFastDFSConnectionPool() {
        return idleFastDFSConnectionPool;
    }

    public long getMinPoolSize() {
        return minPoolSize;
    }

    public void setMinPoolSize(long minPoolSize) {
        if (minPoolSize != 0) {
            this.minPoolSize = minPoolSize;
        }
    }

    public long getMaxPoolSize() {
        return maxPoolSize;
    }

    public void setMaxPoolSize(long maxPoolSize) {
        if (maxPoolSize != 0) {
            this.maxPoolSize = maxPoolSize;
        }
    }

    public long getWaitTimes() {
        return waitTimes;
    }

    public void setWaitTimes(int waitTimes) {
        if (waitTimes != 0) {
            this.waitTimes = waitTimes;
        }
    }

}
 2. FastDFSHeartBeat 定时检测连接类
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.csource.fastdfs.TrackerServer;

public class FastDFSHeartBeat {

    private final Log log = LogFactory.getLog("FastDFSHeartBeat.class");
    /** fastdfs连接池 */
    private FastDFSFastDFSConnectionPool pool = null;
    /** 小时毫秒数 */
    public static int ahour = 1000 * 60 * 5 * 1;
    /** 等待时间 */
    public static int waitTimes = 200;

    public FastDFSHeartBeat(FastDFSFastDFSConnectionPool pool) {
        this.pool = pool;
    }

    /**
     * 
     * @Description: 定时执行任务,检测当前的空闲连接是否可用,如果不可用将从连接池中移除
     * 
     */
    public void beat() {
        log.info("执行心跳方法");
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                log.info("执行检测连接方法");
                LinkedBlockingQueue<TrackerServer> idleConnectionPool = pool.getIdleFastDFSConnectionPool();
                TrackerServer ts = null;
                for (int i = 0; i < idleConnectionPool.size(); i++) {
                    try {
                        ts = idleConnectionPool.poll(waitTimes, TimeUnit.SECONDS);
                        if (ts != null) {
                            org.csource.fastdfs.ProtoCommon.activeTest(ts.getSocket());
                            idleConnectionPool.add(ts);
                        } else {
                            /** 代表已经没有空闲长连接 */
                            break;
                        }
                    } catch (Exception e) {
                        /** 发生异常,要删除,进行重建 */
                        log.error("重新设置文件连接");
                        pool.drop(ts);
                    }
                }
            }
        };
        Timer timer = new Timer();
        timer.schedule(task, ahour, ahour);
    }

}

备注:常用的命令

文件存储目录
/opt/fastdfs_storage_data

启动服务
service fdfs_trackerd start
service fdfs_storaged start
/nginx/sbin/nginx

查看服务
netstat -unltp|grep fdfs

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值