介绍各种分布式文件系统: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