FastDFS从入门到精通

一、概述

​ 各类博客已经对FastDFS进行了介绍,笔者偷懒,就不再进行介绍了。只列出重要的官方资料,如下。

序号内容网址
1github源码、wikihttps://github.com/happyfish100/
2官方客户端https://github.com/happyfish100/fastdfs-client-java
3余庆大佬微信公众号fastdfs100(重要)

二、集群架构

1、单机

​ 如果为单机,那么直接参考余庆github上wiki即可,地址:https://github.com/happyfish100/fastdfs/wiki/

2、集群

​ 集群搭建可以根据自己情况来选择,笔者自己推荐使用如图机构来搭建。搭建过程及其具体命令请读者参考其余博客。

6台机器集群架构
在这里插入图片描述

说明:
1)在没有小文件合并存储需求(当台storage文件数未超过千万均可不用)下,整体架构没有主从概念。比如,每台tracker不会互相通信,组内每台storage都会有相同文件备份,且都可对外提供文件读写操作。
2)nginx和fastdfs_nginx_modlue插件可以不搭建,因只为http访问和下载文件提供支持,对api操作文件没有任何帮助(api操作使用二进制tcp协议)。
3)虚拟IP(vip)可用keepalived实现,只为提供给两台tracker上的ng防止产生单点。tracker上ng只为负载4台storage上ng(注意在tracker上不需要配置fastdfs_nginx_modlue插件)。在storage上的四台ng是去映射fastdfs_nginx_modlue插件。由此来为http提供访问支持。
4)fastdfs_nginx_moudle模块配置有tracker机器ip和port,会从tracker获取文件已经同步完成的strorage,从而解决延迟同步访问不到文件的问题。
5)api操作fastdfs不会产生延迟同步问题,读者可以往下看源码解析部分。此处只是提出问题,为了引导读者自己进行思考。

3、安全配置

1)白名单

该白名单配置在tracker服务器上,找到tracker.conf,修改allow_hosts=192.168.1.[8-88]。然后重启即可。当然,也是可以不这么使用,直接在虚拟机上配置白名单。

2)防盗链

为了图片不被盗用,造成机器IO过高等问题。此处很简单,请查看其他博客。当然,笔记建议直接使用nginx去做防盗链即可,不需要使用内置的。

三、Java API

​ github上有多种客户端源码,都是根据特定cmd来操作服务器。其中有使用netty4的版本,官方提供的api直接通过bio来实现。读者可以根据自己需要来选择使用,不过其余版本性能问题均多,在不考虑大流量情况下,笔者建议使用官方提供的api。下载略,直接开始解析api(主要以上传和下载为例,其余类似)。

1、Api 详解

1)上传
序号api备注
1/方法1/upload_file1(byte[] file_buff, String file_ext_name, NameValuePair[] meta_list);
/方法2/upload_file1(String group_name, byte[] file_buff, String file_ext_name, NameValuePair[] meta_list);
/方法3/upload_file1(String master_file_id, String prefix_name, byte[] file_buff, int offset, int length, String file_ext_name, NameValuePair[] meta_list);
/方法4/upload_file1(String master_file_id, String prefix_name, byte[] file_buff, String file_ext_name, NameValuePair[] meta_list);
上传File Buffer(字节数组);经常被使用在springmvc上传方法中。
2/方法5/upload_file1(String group_name, long file_size, UploadCallback callback, String file_ext_name, NameValuePair[] meta_list);
/方法6/upload_file1(String master_file_id, String prefix_name, long file_size, UploadCallback callback, String file_ext_name, NameValuePair[] meta_list);
通过回调的方式上传文件流
3/方法7/upload_file1(String local_file_name, String file_ext_name, NameValuePair[] meta_list);
/方法8/upload_file1(String group_name, String local_file_name, String file_ext_name, NameValuePair[] meta_list);
/方法9/upload_file1(String master_file_id, String prefix_name, String local_file_name, String file_ext_name, NameValuePair[] meta_list);
上传本地文件

说明: 以上api根据自己使用场景来选择。比如springmvc上传,没有必要非要使用通过回调来上传,当然使用起来也是不麻烦。

2)下载

​ 直接使用storageClient.download_file(groupName, filePath)就可下载。笔者上文说道下载没有延迟同步问题,为何呢?下面将详细讲解。

​ 在下载时,聪明的我们不会在new StorageClient的时候传入storageServer,会让api重新去获取stroageServer,代码如下。在重新调用tracker.getFetchStorage的时候,tracker会有自己的寻址算法。从来保证了延迟同步问题不会发生(敲黑板,注意fastdfs中文件同步配置,非要乱搞,此处省略一万字)。

    /**
     * check storage socket, if null create a new connection
     *
     * @param group_name      the group name of storage server
     * @param remote_filename filename on storage server
     * @return true if create a new connection
     */
    protected boolean newReadableStorageConnection(String group_name, String remote_filename) throws IOException, MyException {
        // 传入storageServer则不再从trackerServer获取
        if (this.storageServer != null) {
            return false;
        } else {
            // 防止并发问题从而新new
            TrackerClient tracker = new TrackerClient();
            this.storageServer = tracker.getFetchStorage(this.trackerServer, group_name, remote_filename);
            if (this.storageServer == null) {
                throw new MyException("getStoreStorage fail, errno code: " + tracker.getErrorCode());
            }
            return true;
        }
    }

2、api负载的使用

1)api使用说明

​ 集群api应该如何使用呢?上文架构说了,如果没有浏览器访问图片这种需求,那么可以不搭建ng及其fastdfs_nginx_moudle插件。那么没有ng做负载,api如何做到负载的呢?

  • 在通过TrackerClient获取TrackerServer的时候,java代码有简单轮询。

  • 通过tracker服务器获取storageServer就不用我们操心啦(哈哈)。

    TrackerClient负载源码如下:

      /**
       * return connected tracker server
       *
       * @return connected tracker server, null for fail
       */
      public TrackerServer getTrackerServer() throws IOException {
        int current_index;
    
        synchronized (this.lock) {
          this.tracker_server_index++;
          if (this.tracker_server_index >= this.tracker_servers.length) {
            this.tracker_server_index = 0;
          }
    
          current_index = this.tracker_server_index;
        }
    
        try {
          return this.getTrackerServer(current_index);
        } catch (IOException ex) {
          System.err.println("connect to server " + this.tracker_servers[current_index].getAddress().getHostAddress() + ":" + this.tracker_servers[current_index].getPort() + " fail");
          ex.printStackTrace(System.err);
        }
    
        for (int i = 0; i < this.tracker_servers.length; i++) {
          if (i == current_index) {
            continue;
          }
    
          try {
            TrackerServer trackerServer = this.getTrackerServer(i);
    
            synchronized (this.lock) {
              if (this.tracker_server_index == current_index) {
                this.tracker_server_index = i;
              }
            }
    
            return trackerServer;
          } catch (IOException ex) {
            System.err.println("connect to server " + this.tracker_servers[i].getAddress().getHostAddress() + ":" + this.tracker_servers[i].getPort() + " fail");
            ex.printStackTrace(System.err);
          }
        }
    
        return null;
      }
    

    所以说呢(敲黑板,划重点),有些同学的博客Utils类就有问题,希望别误导了读博客的同学,比如:

    public class FastDfsUtil {
    	
    	private static TrackerClient trackerClient = null;
    	private static TrackerServer trackerServer = null;
        private static StorageClient storageClient = null;
        private static StorageServer storageServer = null;
    
    	static {
    		try {
    			ClientGlobal.init("fdfs_client.conf");
    			trackerClient = new TrackerClient();
    			trackerServer = trackerClient.getTrackerServer();
    			storageClient = new StorageClient(trackerServer, storageServer);
    		} catch (IOException | MyException e) {
    			throw new RuntimeException("FastDfs工具类初始化失败!");
    		}
    	}
      
      	// ...........................................
    }
    

    直接使用trackerClient.getTrackerServer()获取怎么可以呢?(单机的同学请忽略)请读者自己脑补。

2)tracker负载算法

​ 该算法不仅仅做负载,而且解决了延迟同步访问文件不到的情况。那么该算法是如何的呢?原来,当client询问tracker有哪个(或哪些)storage可以下载指定文件时,tracker返回满足如下四个条件之一的storage:

  • 该文件上传到的源头storage(通过文件名反解出的storage ID/IP来判别);
  • (当前时间 -文件创建时间戳) > 文件同步延迟阀值(如一天);
  • 文件创建时间戳 < storage被同步到的时间戳;
  • 文件创建时间戳 == storage被同步到的时间戳,且(当前时间 -文件创建时间戳) > 同步一个文件的最大时长(如5分钟)。

补充:storage 生成的文件名中,包含源头storage ID/IP地址和文件创建时间戳。storage 定时向tracker报告文件同步情况,包括向同组其他storage同步到的文件时间戳。tracker收到storage的文件同步报告后,找出该组内每台storage被同步到的最小时间戳,作为storage属性保存到内存中。上述文件同步延迟阀值和同步一个文件的最大时长这两个参数,在tracker.conf中配置,配置项分别是 storage_sync_file_max_delay 和 storage_sync_file_max_time。

3、官方api线程非安全

​ 因为官方api线程非安全(具体为什么不安全,比如全局变量定义之内的),那么我们就不能全局使用同一个变量,最好每次使用都new新对象。当然,ClientGlobal.init(“fdfs_client.conf”)这句代码只需要加载一次。

​ 最正确的操作fastdfs的方式,只需要每次使用都new一个StorageClient即可(划重点,使用不带参数的函数就行,当然,读者有需求需要如何如何,也是可以使用带传入参数的,不过传入的值必须每次都new个新对象)。

4、api内置连接池

​ 在v1.28之前,还需要我们自己来实现来连接池。现在已经不需要了。该连接池是按照ip:port来创建。意思是说每个tracker、storage服务都有自己的连接池。通过ConnectionPool来管理多个ConnectionManager实现。

/**
 * 该Pool管理了所有ip:port及其对应的连接池,包括tracker以及storage
 */
public class ConnectionPool {
    /**
     * key is ip:port, value is ConnectionManager
     */
    private final static ConcurrentHashMap<String, ConnectionManager> CP = new ConcurrentHashMap<String, ConnectionManager>();
  
  // .....................
}
    
public class ConnectionManager {

    private InetSocketAddress inetSocketAddress;

    /**
     * total create connection pool
     * 连接池中连接总数
     */
    private AtomicInteger totalCount = new AtomicInteger();

    /**
     * free connection count
     * 连接池中空闲连接数
     */
    private AtomicInteger freeCount = new AtomicInteger();

    /**
     * lock 公平锁
     */
    private ReentrantLock lock = new ReentrantLock(true);

    private Condition condition = lock.newCondition();

    /**
     * 空闲连接数
     * free connections
     */
    private LinkedList<Connection> freeConnections = new LinkedList<Connection>();
  
    // .....................
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值