[TOC]

0x00 进阶使用

1.FastDFS 重复文件处理

描述: 由于FastDFS本身不能对重复上传的文件进行去重, 所以使用FastDFS时如果多次上传同一张照片,默认都会将其上传到storage服务器中,这样造成磁盘空间的浪费,所以我们可以采用 FastDHT 进行重复文件处理,以达到同一份图片只留存一份。

FastDHT是一个高性能的分布式哈希系统,它是基于键值对存储的,它可以存储大量键值对,例如文件名映射,会话数据和用户相关数据。但它依赖于Berkeley DB作为数据存储的媒介,使用libevent做网络IO处理, 同时需要依赖于libfastcommon

FastDHT 项目地址: https://github.com/happyfish100/fastdht
Berkeley DB 下载地址: https://www.oracle.com/database/technologies/related/berkeleydb-downloads.html

FastDHT是一个高性能的分布式哈希系统,它是基于键值对存储的,而且它需要依赖于Berkeley DB作为数据存储的媒介,使用libevent做网络IO处理。同时需要依赖于libfastcommon。

在 FastDHT 中,key 包含三个字段:namespace、object ID 和 key name。这三个概念类似于数据库系统的概念:命名空间与数据库名称、对象 ID 与表名称以及键名称与字段名称,由于导入了命名空间和对象 ID,系统更加灵活。

  • 命名空间的目的是解决之间可能的数据冲突多个用户,例如不同的应用程序或产品。
  • 对象ID的目的是方便组织和管理 对象相关数据,如用户数据,提高整体性能。


FastDHT 流程:
描述: FastDFS的storage server每次上传均计算文件的hash值,然后从FastDHT服务器上进行查找比对,如果没有返回,则写入hash,并将文件保存;如果有返回,则建立一个新的文件链接(软链),不保存文件。

FastDHT 由客户端决定应该选择哪台服务器,为例避免查表根据key(文件)的hash code来选择服务器,算法描述如下:

  1. 计算出key(文件)的hash值hash_code
  2. group_index = hash_code % group_count
  3. new_hash_code = hash_code 高16位和低16位互换
  4. server_index = new_hash_code % 组内 server_count

为啥计算 server_indexgroup_index 时使用了不同的hash code?

因为如果group_count和组内server_count相等,例如都等于2,那么对于一个组来说,任何key值都将选中其中一台固定的服务器server_index == group_index,所以上述高16位和低16位互换就是为了解决此问题。


部署流程:

  • Step 1.Berkeley DB 与 fastdht 下载编译安装 (此处安装环境还是Ubuntu并已经安装了FastDFS)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    # (1) Berkeley DB 需要认证后下载并解压:
    # https://download.oracle.com/otn/berkeley-db/db-18.1.40.tar.gz
    # https://download.oracle.com/otn/berkeley-db/db-18.1.32.tar.gz
    tar -zxf db-18.1.40.tar.gz -C /usr/local/src/
    # 防止编译错误,这是db-18.1.40版本的Bug。
    mkdir -vp /usr/local/src/db-18.1.40/docs/{bdb-sql,gsg_db_server}
    # 进入build_unix目录,必须是这个目录
    cd /usr/local/src/db-18.1.40/build_unix
    # 执行 configure 命令(一定要是进入上面的目录后,使用相对路径执行命令):
    ../dist/configure --prefix=/usr/local/berkeley-db
    # 编译并安装
    make && make install
    # 查看安装下目录结构
    ls /usr/local/berkeley-db
    # 词语删除db-18.1.40.tar.gz解压后的文件夹
    rm -rf /usr/local/src/db-18.1.40

    # (2) 拉取 fastdht 项目并进行编译安装
    cd /usr/local/src/
    git clone https://github.com/happyfish100/fastdht.git
    cd /usr/local/src/fastdht
    # 编译安装 (安装前必须解决依赖问题) 一定要确认是否已经安装了libevent、libevent-devel 和libfastcommon依赖安装包。
    apt-get install libevent-dev
    # 解决db.h文件错误问题
    vim /usr/local/src/fastdht/make.sh
    27 行: CFLAGS='-Wall -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE -I/usr/local/berkeley-db/include/ -L/usr/local/berkeley-db/lib/'
    ./make.sh && ./make.sh install
    # 查看安装了可执行文件以及配置文件
    ls /usr/local/bin/fdht*
    # /usr/local/bin/fdht_batch_test /usr/local/bin/fdht_get /usr/local/bin/fdht_test_set
    # /usr/local/bin/fdht_compress /usr/local/bin/fdht_set /usr/local/bin/fdht_test_thread
    # /usr/local/bin/fdhtd /usr/local/bin/fdht_test
    # /usr/local/bin/fdht_delete /usr/local/bin/fdht_test_get
    # 安装成功后fastdht被安装在/etc/fdht目录下,生成3个配置文件
    ls /etc/fdht/
    # fdht_client.conf fdhtd.conf fdht_servers.conf


    # (3) 拷贝库文件并重新加载库文件 否则将会报错
    cp /usr/local/berkeley-db/lib/libdb-18.so /usr/lib/ && cp /usr/local/berkeley-db/lib/libdb-18.so /usr/lib64/
    ldconfig
    # ldd /usr/local/bin/fdhtd


  • Step 2.fastdht 相关配置文件设置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    # (1) 创建fastdht数据日志存放目录
    mkdir -vp /home/fdfs/fdht
    chown -R fdfs:fdfs /home/fdfs/fdht


    # (2) 修改 /etc/fdht/fdht_client.conf 配置文件
    vi /etc/fdht/fdht_client.conf
    # 保持持久连接
    keep_alive=1
    base_path=/home/fdfs/fdht
    ## 本行前有#表示打开,如果想关闭(注释)此选项,则应该为##开头
    #include /etc/fdht/fdht_servers.conf


    # (3) 修改 /etc/fdht/fdht_servers.conf 配置文件 (单节点)
    group_count = 1
    group0 = 10.10.107.225:11411
    # group0 = 10.10.107.226:11411


    # (4) 修改 /etc/fdht/fdhtd.conf 配置文件
    vi /etc/fdht/fdhtd.conf
    # 启用fdhtd.conf配置
    disabled=false
    # 绑定监听地址
    bind_addr=10.10.107.225
    port=11411
    # FastDHT数据和日志存放目录
    base_path=/home/fdfs/fdht
    # 该目录必须是已经存在的,前面已经创建过了
    bash_path= /fastdfs/fastdht
    # 文件HASH值存放方式BDB for Berkeley DB
    store_type = BDB
    # 缓存大小默认64MB(需要根据实际情况进行配置)
    cache_size = 128MB
    # 与前面一样,用指定用户和组来执行程序
    run_by_group=fdfs
    run_by_user=fdfs


  • Step 3.在 FastDFS Storage 配置文件中设置接入FastDHT (重点)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 配置/etc/fdfs/目录下的storage.conf
    vi /etc/fdfs/storage.conf

    # 检查文件副本并使用FastDHT存储文件索引
    check_file_duplicate = 1
    # 检查文件副本算法方式
    ## hash: four 32 bits hash code
    ## md5: MD5 signature
    file_signature_method = hash
    # 用于存储文件索引(键值对)的命名空间
    key_namespace = FastDFS
    # 将keep_alive设置为1以启用与FastDHT服务器的持久连接
    keep_alive = 1
    # 您可以使用“#include filename”(不包括双引号)指令加载FastDHT服务器列表 (此处非常注意、注意、#与inclue之间没有空格、没有空格以及其它字符)
    #include /etc/fdht/fdht_servers.conf

Tips : 非常注意启用include时#include之间不能包含空格(巨坑、巨坑)


  • Step 4.使用systemd管理fdhtd进程和重启fdhtd、storage服务
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    # 服务清单单元
    tee /lib/systemd/system/fdhtd.service <<'EOF'
    [Unit]
    Description=FastDHT daemon service
    After=network-online.target

    [Service]
    Type=forking
    WorkingDirectory=/home/fdfs/fdht
    PIDFile=/home/fdfs/fdht/data/fdhtd.pid
    ExecStart=/usr/local/bin/fdhtd /etc/fdht/fdhtd.conf start
    ExecStop=/usr/local/bin/fdhtd /etc/fdht/fdhtd.conf stop
    ExecReload=/usr/local/bin/fdhtd /etc/fdht/fdhtd.conf restart

    # No artificial start/stop timeout
    TimeoutSec=3

    # Disable OOM kill by Linux kernel
    OOMScoreAdjust=-1000

    [Install]
    WantedBy=multi-user.target
    EOF
    systemctl daemon-reload

    # 防火墙放行设置
    ufw allow 11411/tcp

    # 重新启动服务
    /usr/local/bin/fdhtd /etc/fdht/fdhtd.conf stop
    systemctl restart fdhtd.service && systemctl status fdhtd.service
    systemctl restart fdfs_storaged.service && systemctl status fdfs_storaged.service

    # 执行结果:
    # ● fdhtd.service - FastDHT daemon service
    # Loaded: loaded (/lib/systemd/system/fdhtd.service; disabled; vendor preset: enabled)
    # Active: active (running) since Tue 2021-11-02 22:22:17 CST; 6s ago
    # Process: 940357 ExecStart=/usr/local/bin/fdhtd /etc/fdht/fdhtd.conf start (code=exited, status=0/SUCCESS)
    # Main PID: 940359 (fdhtd)
    # Tasks: 12 (limit: 9414)
    # Memory: 2.5M
    # CGroup: /system.slice/fdhtd.service
    # └─940359 /usr/local/bin/fdhtd /etc/fdht/fdhtd.conf start

    # Nov 02 22:22:17 elk1 systemd[1]: Starting FastDHT daemon service...
    # Nov 02 22:22:17 elk1 systemd[1]: Started FastDHT daemon service.


    # fast* 相关进程查看
    $ ps aux | grep "/etc/fd"
    # fdfs 837401 0.0 0.0 211196 6132 ? Sl Oct30 0:38 /usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf start
    # fdfs 940039 0.0 0.0 534916 4136 ? Sl 22:12 0:00 /usr/bin/fdfs_storaged /etc/fdfs/storage.conf start
    # fdfs 940359 0.0 0.0 253952 3436 ? Sl 22:22 0:00 /usr/local/bin/fdhtd /etc/fdht/fdhtd.conf start


  • Step 5.验证FastDHT文件去重功能。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # (1) 我们三次上传同一个文件,首次上传时同一文件分两次先后上传(串行)
    [email protected]:/tmp/test$fdfs_upload_file /etc/fdfs/client.conf bibi.png
    group1/M00/00/00/Cgpr4mGBS9yABcwKAAFcGln-TU4047.png # 文件首次上传,即生成软链接指向上传文件
    [email protected]:/tmp/test$fdfs_upload_file /etc/fdfs/client.conf bibi.png
    group1/M00/00/00/Cgpr4WGBS92ASA_jAAFcGkEHz3I407.png
    [email protected]:/tmp/test$fdfs_upload_file /etc/fdfs/client.conf bibi.png
    group1/M00/00/00/Cgpr4mGBS92APWWCAAFcGh4OjGI465.png

    # (2) 可以看到上传的三个文件指向同一个Cgpr4WGBSxaABHlXAAFcGo7c2Jk187.png副本。Nice
    [email protected]:/tmp/test# find /home/fdfs/storage/data/ -name "Cgpr4*.png" -ctime -0.5 -exec ls -lh {} \;
    -rw-r--r-- 1 fdfs fdfs 88K Nov 2 22:28 /home/fdfs/storage/data/00/00/Cgpr4WGBSxaABHlXAAFcGo7c2Jk187.png
    lrwxrwxrwx 1 fdfs fdfs 64 Nov 2 22:31 /home/fdfs/storage/data/00/00/Cgpr4WGBS92ASA_jAAFcGkEHz3I407.png -> /home/fdfs/storage/data/00/00/Cgpr4WGBSxaABHlXAAFcGo7c2Jk187.png
    lrwxrwxrwx 1 fdfs fdfs 64 Nov 2 22:31 /home/fdfs/storage/data/00/00/Cgpr4mGBS9yABcwKAAFcGln-TU4047.png -> /home/fdfs/storage/data/00/00/Cgpr4mGBSxWAV58oAAFcGo7c2Jk916.png
    lrwxrwxrwx 1 fdfs fdfs 64 Nov 2 22:31 /home/fdfs/storage/data/00/00/Cgpr4mGBS92APWWCAAFcGh4OjGI465.png -> /home/fdfs/storage/data/00/00/Cgpr4mGBSxWAV58oAAFcGo7c2Jk916.png
WeiyiGeek.FastDHT重复处理

WeiyiGeek.FastDHT重复处理

至此完毕!


上面是单实例的FastDHT环境搭建,在实际企业环境中我们可以根据需求搭建 FastDHT 集群。

FastDHT 集群由一个或多个组(group)组成,每个组由一台或多台服务器组成,同组服务器上存储的数据是相同的,数据同步只在同组的服务器之间进行。组内各个服务器是对等的,对数据进行存取时,可以根据key的hash值来决定使用哪台服务器。数据同步采用推(Push)的方式,由源服务器主动将数据同步到本组的其他服务器。

  • Step 1.FastDHT 集群的安装非常的简单,按照上面单实例流程进行安装宁外一台FastDHT服务,然后分别修改两台主机的fdht_servers.conf,重启后即可使用。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # (1) 配置两台机器的fdht_servers.conf文件
    vim /etc/fdhtd/fdht_servers.conf
    group_count = 2
    # FastDHT 集群中划分一个组,改组由两台服务器进行存储索引数据(完全相同,它会实时同步)。
    group0 = 10.10.107.225:11411
    group0 = 10.10.107.226:11411
    # group1 = 10.10.107.227:11411

    # (2) 重新两台机器的相关服务
    /usr/local/bin/fdhtd /etc/fdht/fdhtd.conf stop
    systemctl restart fdhtd.service && systemctl status fdhtd.service
    systemctl restart fdfs_storaged.service && systemctl status fdfs_storaged.service


  • Step 2.验证FastDHT 集群的去重功能是否正常。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    # (1) 上传多份hello.logs
    [email protected]:/tmp# ls
    hello.logs
    [email protected]:/tmp# fdfs_upload_file /etc/fdfs/client.conf hello.logs
    group1/M00/00/00/Cgpr4WGBUTmAKPrOAAAAHW1scFk00.logs
    group1/M00/00/00/Cgpr4WGBWruAVSl2AAAAHUTG_1091.logs

    # (2) 同样只有一个源文件,其它都是链接指向 `Cgpr4WGBUTmAH106AAAAHdiEv7475.logs`
    [email protected]:/tmp# find /home/fdfs/storage/data/ -name "*.logs" -ctime -0.5 -exec ls -lh {} \;
    lrwxrwxrwx 1 fdfs fdfs 64 Nov 2 22:54 /home/fdfs/storage/data/00/00/Cgpr4WGBUTmAKPrOAAAAHW1scFk00.logs -> /home/fdfs/storage/data/00/00/Cgpr4WGBUTmAH106AAAAHdiEv7475.logs
    lrwxrwxrwx 1 fdfs fdfs 64 Nov 2 22:54 /home/fdfs/storage/data/00/00/Cgpr4WGBWruAVSl2AAAAHUTG_1091.logs -> /home/fdfs/storage/data/00/00/Cgpr4WGBUTmAH106AAAAHdiEv7475.logs
    -rw-r--r-- 1 fdfs fdfs 29 Nov 2 22:54 /home/fdfs/storage/data/00/00/Cgpr4WGBUTmAH106AAAAHdiEv7475.logs

    # (3) 链接拷贝以及文件打开
    [email protected]:/tmp# cp /home/fdfs/storage/data/00/00/Cgpr4WGBUTmAKPrOAAAAHW1scFk00.logs .
    [email protected]:/tmp# ls -alh
    -rw-r--r-- 1 root root 29 Nov 2 22:55 Cgpr4WGBUTmAKPrOAAAAHW1scFk00.logs # 此时不再是链接文件。
    -rw-r--r-- 1 root root 29 Nov 2 22:48 hello.logs
    [email protected]:/tmp# cat Cgpr4WGBUTmAKPrOAAAAHW1scFk00.logs
    Hello World,FastDFS!
    By Test

Tips : FastDFS不会返回原始文件的索引,返回的全部都是软链接,当所有的软链接都被删除的时候,原始文件也会从FastDFS中被删除。


扩展说明: 使用fdht提供的相关工具操作FastDHT服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 设置值
fdht_set /etc/fdht/fdht_client.conf database:user01 name='WeiyiGeek',age=18;
# success set key count: 2, fail count: 0

# 读取值
fdht_get <config_file> [namespace:][object id] <key list split by comma>
fdht_get /etc/fdht/fdht_client.conf database:user01 name,age;
# name=WeiyiGeek
# age=18

# 删除值
fdht_delete /etc/fdht/fdht_client.conf database:user01 name;
# success delete keys: name
# success delete key count: 1, fail count: 0

至此安装搭建配置、验证测试完成!


2.FastDFS 原始文件名恢复

描述: 当其它文档被上传到FastDFS后,Storage服务端将返回的文件索引(FID),其中文件名是根据FastDFS自定义规则重新生成的而不是原始文件名。

例如: 在使用浏览器访问 http://file.weiyigeek.top/group2/M00/00/89/eQ6h3FKJf_PRl8p4AUz4wO8tqaA688.docx, 显示给用户的文件名会是这样的eQ6h3FKJf_PRl8p4AUz4wO8tqaA688.docx 那FastDFS在进行文件下载时如何恢复原始文件名称。


操作流程:

  • Step 1.应用系统在上传文件到FastDFS成功时将原始文件名以及”文件索引(FID)”都保存下来(例如:保存到数据库)。


  • Step 2.用户点击下载的时用Nginx的域名和FID拼出url,然后在url后面增加一个参数,指定原始文件名。
    例如:http://file.weiyigeek.top/group2/M00/00/89/eQ6h3FKJf_PRl8p4AUz4wO8tqaA688.docx?attname=filename.docx


  • Step 3.然后在Nginx上进行如下配置此时Nginx就会截获url中的参数attname,并且在Http响应头中进行展示Content-Disposition "attachment;filename=$arg_attname"
    1
    2
    3
    4
    5
    6
    location /group1/M00 {
    if ($arg_attname ~* \.(doc|docx|txt|pdf|zip|rar|xls|xlsx|png|jpeg|jpg)$) {
    add_header Content-Disposition "attachment;filename=$arg_attname";
    }
    ngx_fastdfs_module;
    }

Tips : 上面脚本主要功能是通过正则匹配文件后缀,然后将下载文件名改为路径参数attname的值,并返回给客户端。


  • Step 4.现在要将下载后的文件名称由Cgpr4WGCMMOAamL7AAApDWjxgXI44.xlsx改为设备清单.xlsx, 访问如下下载路径格式即可http://file.weiyigeek.top/group1/M00/00/00/Cgpr4WGCMMOAamL7AAApDWjxgXI44.xlsx?attname=设备清单.xlsx
1
2
3
4
5
6
7
8
9
10
# 浏览器返回的响应头
HTTP/1.1 200 OK
Server: nginx/1.20.1
Date: Wed, 03 Nov 2021 06:53:42 GMT
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
Content-Length: 10509
Last-Modified: Wed, 03 Nov 2021 06:48:35 GMT
Connection: keep-alive
Accept-Ranges: bytes
Content-Disposition: attachment;filename=%E8%AE%BE%E5%A4%87%E6%B8%85%E5%8D%95.xlsx
WeiyiGeek.FastDFS下载文件重命名

WeiyiGeek.FastDFS下载文件重命名


3.FastDFS 资源防盗链功能

描述: 前面使用nginx支持http方式访问文件,但所有人都能直接访问这个文件服务器,所以有些场景我们需要做一下权限控制。

FastDFS 的权限控制是在服务端开启token验证, 客户端根据文件名、当前unix时间戳、秘钥获取token,然后在地址中带上token参数即可通过http方式访问文件。

操作流程:

  • (1) 服务端开启token验证, 我们修改/etc/fdfs/http.conf配置文件。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    vim /etc/fdfs/http.conf
    # HTTP默认内容类型
    http.default_content_type = application/octet-stream
    # MIME类型映射文件名
    http.mime_types_filename = mime.types
    # 设置为true表示开启token验证
    http.anti_steal.check_token=true
    # 密钥,它需要与客户端配置文件的 fastdfs.http_secret_key 保持一致。
    http.anti_steal.secret_key=WeiyiGeek.top
    # 设置token失效的时间单位为秒(s)
    http.anti_steal.token_ttl=1800
    # 当检查令牌失败时返回文件的内容
    http.anti_steal.token_check_fail = /home/yuqing/fastdfs/conf/anti-steal.jpg
    # 支持HTTP范围的多区域
    http.multi_range.enabed = true


  • (2) 拷贝Token检查失败时显示的图片以及重启Nginx服务。
    1
    2
    cp /usr/local/src/fastdfs/conf/anti-steal.jpg /usr/local/nginx/html/anti-steal.jpg
    systemctl restart nginx


  • (3) 客户端生成Token需要文件名称、当前时间以及httpSecretKey参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    /**
    * 获取访问服务器的token,拼接到地址后面
    *
    * @param filepath 文件路径 group1/M00/00/00/wKgzgFnkTPyAIAUGAAEoRmXZPp876.jpeg
    * @param httpSecretKey 密钥
    * @param ts 当前时间戳
    * @return 返回token,如: token=078d370098b03e9020b82c829c205e1f&ts=1508141521
    */
    public static String getToken(String filepath, String httpSecretKey){
    // unix seconds
    int ts = (int) Instant.now().getEpochSecond();
    // token
    String token = "null";
    try {
    token = ProtoCommon.getToken(getFilename(filepath), ts, httpSecretKey);
    } catch (UnsupportedEncodingException e) {
    e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
    e.printStackTrace();
    } catch (MyException e) {
    e.printStackTrace();
    }
    StringBuilder sb = new StringBuilder();
    sb.append("token=").append(token);
    sb.append("&ts=").append(ts);
    return sb.toString(); //返回的token是token和时间戳的拼接
    }


  • (4) 此时访问文件需要带上生成的token以及unix时间戳,例如http://file.weiyigeek.top/group1/M00/00/00/wKgzgFnkaXqAIfXyAAEoRmXZPp878.jpeg?token=078d370098b03e9020b82c829c205e1f&ts=1508141521


注意事项:

  • Tips: 如果生成的token验证无法通过,请进行如下两项检查:

    (1) 确认调用token生成函数(ProtoCommon.getToken),传递的文件ID中没有包含group name。传递的文件ID格式形如:M00/00/00/wKgzgFnkTPyAIAUGAAEoRmXZPp876.jpeg
    (2) 确认服务器时间基本是一致的,注意服务器时间不能相差太多,不要相差到分钟级别。

  • Tips: 如果系统文件隐私性较高,可以直接通过fastdfs-client提供的API去访问即可,不用再配置Nginx走http访问。配置Nginx的主要目的是为了快速访问服务器的文件(如图片),如果还要加权限验证,则需要客户端生成token,其实已经没有多大意义, 但是防止爬虫还是非常不错的。


4.FastDFS 从文件的使用技巧

描述: 当时使用FastDFS存储一个图片的多个分辨率的备份时,希望只记录源图的FID,并能将其它分辨率的图片与源图关联,此时可以使用从文件方法。

主从文件是指文件ID有关联的文件,一个主文件可以对应多个从文件。

主文件ID = 主文件名 + 主文件扩展名
从文件ID = 主文件名 + 从文件后缀名 + 从文件扩展名


以本场景为例:主文件为原始图片,从文件为该图片的一张或多张缩略图。

流程说明:

  • 先上传主文件(即:原文件),得到主文件FID。
  • 然后上传从文件(即:缩略图),指定主文件FID和从文件后缀名,上传后得到从文件FID。

java伪代码,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
private static Logger logger = Logger.getLogger(FastDFSUtils.class);

static{
try {
ClientGlobal.init("D:/WorkSpace/app-filesystem/conf/fdfs_client.conf");
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public static String uploadFile(String filePath) throws Exception{
String fileId = "";
String fileExtName = "";
if (filePath.contains(".")) {
fileExtName = filePath.substring(filePath.lastIndexOf(".") + 1);
} else {
logger.warn("Fail to upload file, because the format of filename is illegal.");
return fileId;
}

//建立连接
/*.......*/

//上传文件 (主文件点)
try {
fileId = client.upload_file1(filePath, fileExtName, null);
} catch (Exception e) {
logger.warn("Upload file \"" + filePath + "\"fails");
}finally{
trackerServer.close();
}
return fileId;
}


public static String uploadSlaveFile(String masterFileId, String prefixName, String slaveFilePath) throws Exception{
String slaveFileId = "";
String slaveFileExtName = "";
if (slaveFilePath.contains(".")) {
slaveFileExtName = slaveFilePath.substring(slaveFilePath.lastIndexOf(".") + 1);
} else {
logger.warn("Fail to upload file, because the format of filename is illegal.");
return slaveFileId;
}

//建立连接
/*.......*/

//上传文件 (从文件点)
try {
slaveFileId = client.upload_file1(masterFileId, prefixName, slaveFilePath, slaveFileExtName, null);
} catch (Exception e) {
logger.warn("Upload file \"" + slaveFilePath + "\"fails");
}finally{
trackerServer.close();
}

return slaveFileId;
}

public static int download(String fileId, String localFile) throws Exception{
int result = 0;
//建立连接
TrackerClient tracker = new TrackerClient();
TrackerServer trackerServer = tracker.getConnection();
StorageServer storageServer = null;
StorageClient1 client = new StorageClient1(trackerServer, storageServer);

//上传文件
try {
result = client.download_file1(fileId, localFile);
} catch (Exception e) {
logger.warn("Download file \"" + localFile + "\"fails");
}finally{
trackerServer.close();
}

return result;
}

public static void main(String[] args) {
try {
String masterFileId = uploadFile("D:/Tmp/apk/t01134ede0e696735e7.png");
System.out.println(masterFileId);
download(masterFileId, "D:/Tmp/apk/master.png");

String slaveFileId = uploadSlaveFile(masterFileId, "_120x120", "D:/Tmp/apk/PC.png");
System.out.println(slaveFileId);

download(slaveFileId, "D:/Tmp/apk/slave.png");
} catch (Exception e) {
logger.error("upload file to FastDFS failed.", e);
}
}
}

上面代码运行后打印的文件Id为:
1
2
主文件:group1/M00/00/00/wKhbylJx1zkIAAAAAAApPcQL87AAAAAAQCmDxUAAClV522.png
从文件:group1/M00/00/00/wKhbylJx1zkIAAAAAAApPcQL87AAAAAAQCmDxUAAClV522_120x120.png

Tips : FastDFS中的主从文件只是在文件ID上有联系。FastDFS server端没有记录主从文件对应关系,因此删除主文件,FastDFS不会自动删除从文件。删除主文件后,从文件的级联删除,需要由应用端来实现(程序后端)。

参考地址: http://www.ttlsa.com/fastdfs/fastdfs-experience-sharing


0x01 容灾恢复

1.主从同步异常恢复测试

描述: 假如此时 10.10.107.225tracker、storage 由于异常退出, 此时10.10.107.226 正常运行,此时当我们想把文件上传到fastdfs时,它将自动通过/etc/fdfs/client.txt中配置的tracker server地址,选择一个可用的tracker server地址进行文件上传,并在主 storage 节点服务恢复后自动同步到其storage数据目录中。

演示流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# (1) 故障演示将主 master 10.10.107.225 的 fdfs_storaged 和 fdfs_trackerd.service 进行停止
systemctl stop fdfs_storaged.service fdfs_trackerd.service

# (2) 从客户端查看fdfs storage相关信息。
fdfs_monitor /etc/fdfs/client.conf
# tracker server is 10.10.107.226:22122 # 此时发现 tracker server 变成slave的地址。
# Storage 1:
# id = 10.10.107.225
# ip_addr = 10.10.107.225 OFFLINE # 存储离线状态
# http domain = file1.weiyigeek.top

# Storage 2:
# id = 10.10.107.226
# ip_addr = 10.10.107.226 ACTIVE
# http domain = file2.weiyigeek.top

# (3) 客户端上传文件到fastdfs中。
fdfs_upload_file /etc/fdfs/client.conf ip.txt
# group1/M00/00/00/Cgpr4WF_PduAOZWzAAACU_pTy9w129.txt

# (4) slave 节点 storage 已正常存储, Master 肯定没有存储到。
Slave$ ls /home/fdfs/storage/data/00/00/Cgpr4WF_PduAOZWzAAACU_pTy9w129.txt
/home/fdfs/storage/data/00/00/Cgpr4WF_PduAOZWzAAACU_pTy9w129.txt

Master$ ls /home/fdfs/storage/data/00/00/Cgpr4WF_PduAOZWzAAACU_pTy9w129.txt
ls: cannot access '/home/fdfs/storage/data/00/00/Cgpr4WF_PduAOZWzAAACU_pTy9w129.txt': No such file or directory

# (5) 此时启动master节点的fdfs_storaged.service 和 fdfs_trackerd.service
systemctl start fdfs_storaged.service fdfs_trackerd.service

# (6) 验证是否slave节点的文件是否同步到master过来。
Master$ cat /home/fdfs/storage/data/sync/binlog.000
1635728859 c M00/00/00/Cgpr4WF_PduAOZWzAAACU_pTy9w129.txt

Master$ find /home/fdfs/storage/data/ -name *.png -ctime -0.5 -exec ls -lh {} \;
-rw-r--r-- 1 fdfs fdfs 295K Oct 31 16:08 /home/fdfs/storage/data/00/00/Cgpr4WF_PduAOZWzAAACU_pTy9w129.txt


2.集群数据整体迁移(思路)

描述: 当我们系统需要从一个地方迁移到另一个地方并且需要数据保持不变,假如此时ip地址和网络情况不一样了,通常我会将FastDFS存储的目录整体拷贝过去,该方法简单粗暴且有效,这样文件在文件系统中的位置也不会发生变化,访问文件时文件地址只需要修改为迁移后的ip即可。

解决方案流程:

Step 1.在需要迁移的那边用同样的docker镜像构建FastDFS文件系统,容器映射目录、http访问端口尽量保持不变。

Step 2.首先将tracker目录下的data文件夹直接拷贝过去,覆盖新的文件系统中的tracker中的data目录,进行如下的修改: 

1
2
3
4
# 将下面文件中的旧ip改为新地址的ip即可。
storage_groups_new.dat
storage_servers_new.dat
storage_sync_timestamp.dat

Step 3.其次将storage目录下的data文件夹之际拷贝过去,覆盖新的文件系统中的storage中的data目录,进行如下的修改(可使用ll -a命令查看隐藏文件):

1
2
3
ls data/
.data_init_flag
sync/${IP_addr}_${port}.mark

将上述文件中的旧ip改为新地址的ip即可。

Step 4.将上面都修改完成之后,启动集群,关闭防火墙,在新的服务器环境下通过url访问文件系统中的文件,即可获取到文件。

Step 5.假如迁移前后ip地址和端口不变只是系统进行更换时,我们配置好服务后将可直接将data目录拷贝过去,不需要修改任何信息。


0x02 测试优化

3.1 测试工具

描述: 我们采用 fastdfs 客户端命令 fdfs_upload_file 和 xargs 并发执行工具,上传测试样本 10W 张表情包图片 ,大小在 8KB–128KB 之间,我们分别进行上传和下载测试。

1
2
3
4
5
6
# 上传测试命令:
pwd # /opt/photo
time ls | xargs -n 1 -I {} -P 256 sh -c "/usr/bin/fdfs_upload_file /etc/fdfs/client.conf {}"

# 下载测试命令:
time cat url.log | xargs -n 1 -I {} -P 256 sh -c "wget {}"


测试结果
使用 FastDFS 自带的上传测试工具和 xargs 并发执行工具,通过 xargs -P 参数指定的并发请求数,测得结果为单机性能在网络环境稳定的情况下可以达到 5000 并发上传请求。10W 张图片上传时间耗时 2 分 11 秒左右。使用定时脚本持续测试,总测试上传 100W 张图片。分析 tracker 服务器和 storage 服务器的日志,无论上传还是下载均未发现错误和异常,性能和稳定性较好。


3.2 优化参数

描述: 根据业务需求和线上环境调整一下参数,可充分发挥 FastDFS 文件系统的性能.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 接收请求的线程数数量
accept_threads=1

# work thread count, should <= max_connections
# default value is 4
# since V2.00
# 工作线程数量,应当小于等于最大连接数
work_threads=4

# min buff size
# default value 8KB
# 最小缓冲大小,默认值为 8KB
min_buff_size = 8KB

# max buff size
# default value 128KB
# 最大缓冲大小,默认值为 128KB
max_buff_size = 128KB

# thread stack size, should >= 64KB
# default value is 256KB
# 线程栈的大小,应当大于 64KB,默认为 256KB
thread_stack_size = 256KB

# if use connection pool
# default value is false
# since V4.05
# 是否使用连接池
use_connection_pool = false

# connections whose the idle time exceeds this time will be closed
# unit: second
# default value is 3600
# since V4.05
# 连接池的最大空闲时间
connection_pool_max_idle_time = 3600



0x0n 入坑&出坑

问题1.在启动Fastdfs时Storage启动报one MUST be an inner IP and another is a outer IP, or two different types of inner IP addresses错误!

  • 错误信息:
    1
    2
    3
    ERROR - file: ../client/client_func.c, line: 168, conf file: /etc/fdfs/storage.conf, tracker_s
    erver is invalid, error info: invalid ip addresses 10.10.107.225 and 10.10.107.226, one MUST be an inner IP and another is a outer IP, or two different types of inner IP addresses
    [2021-10-30 15:27:42] CRIT - exit abnormally!
  • 错误原因: 由于/etc/fdfs/storage.conf中的 stracker_server = 10.10.107.225,10.10.107.226:22122 配置了同一个网段的地址,而非一个是内部地址,另外一个是外部地址。
  • 解决办法:
    1
    2
    3
    # tracker server 地址以及端口(如果有多个则填写多个tracker_server)
    tracker_server = 10.10.107.225:22122
    tracker_server = 10.10.107.226:22122


问题2.tracker端工具链接storage时报无法连接到 storage 服务器

  • 错误信息:
    1
    2
    3
    [2019-07-25 10:40:54] ERROR - file: ../client/storage_client.c, line: 996, fdfs_recv_response fail, result: 107
    upload file fail, error no: 107, error info: Transport endpoint is not connected
    [2019-07-25 10:40:54] ERROR - file: tracker_proto.c, line: 37, server: 10.20.172.192:23000, recv data fail, errno: 107, error info: Transport endpoint is not connected
  • 解决办法: 首先是在 tracker.conf 配置文件中添加允许访问的 IP ,其次是系统添加防火墙规则,例如: ufw allow 23000/tcp


问题3. 当 storage 服务器磁盘存储空间用尽时报tracker_query_storage fail, error no: 28, error info: No space left on device错误。

  • 错误信息:
    1
    2
    3
    [2019-07-26 16:16:45] ERROR - file: tracker_proto.c, line: 48, server: 10.10.107.232:22122, response status 28 != 0
    [2019-07-26 16:16:45] ERROR - file: ../client/tracker_client.c, line: 907, fdfs_recv_response fail, result: 28
    tracker_query_storage fail, error no: 28, error info: No space left on device
  • 错误原因: 当 storage 服务器设定的上传存储目录所在的分区磁盘用尽将会出现无剩余空间的错误日志
  • 解决办法: 增加 storage 存储磁盘空间或者临时解决办法设置tracker配置文件中的reserved_storage_space = 10%参数。


问题4.当 storage 服务器磁盘存储空间用尽时报bind port 22122 failed, errno: 98, error info: Address already in use.错误

  • 错误信息: 使用service fdfs_trackerd restart重启 tracker 或者 storage 服务时会报错,会提示端口已经占用。
    1
    2
    3
    [2019-07-25 10:36:55] INFO - FastDFS v5.12, base_path=/home/dfs, run_by_group=, run_by_user=, connect_timeout=10s, network_timeout=60s, port=22122, bind_addr=, max_connections=1024, accept_threads=1, work_threads=4, min_buff_size=8,192, max_buff_size=131,072, store_lookup=2, store_group=, store_server=0, store_path=0, reserved_storage_space=10.00%, download_server=0, allow_ip_count=-1, sync_log_buff_interval=10s, check_active_interval=120s, thread_stack_size=256 KB, storage_ip_changed_auto_adjust=1, storage_sync_file_max_delay=86400s, storage_sync_file_max_time=300s, use_trunk_file=0, slot_min_size=256, slot_max_size=16 MB, trunk_file_size=64 MB, trunk_create_file_advance=0, trunk_create_file_time_base=02:00, trunk_create_file_interval=86400, trunk_create_file_space_threshold=20 GB, trunk_init_check_occupying=0, trunk_init_reload_from_binlog=0, trunk_compress_binlog_min_interval=0, use_storage_id=0, id_type_in_filename=ip, storage_id_count=0, rotate_error_log=0, error_log_rotate_time=00:00, rotate_error_log_size=0, log_file_keep_days=0, store_slave_file_use_link=0, use_connection_pool=0, g_connection_pool_max_idle_time=3600s
    [2019-07-25 10:36:55] ERROR - file: sockopt.c, line: 868, bind port 22122 failed, errno: 98, error info: Address already in use.
    [2019-07-25 10:36:55] CRIT - exit abnormally!
  • 解决办法:
    1
    2
    3
    4
    5
    # 分别暂停 fdfs_trackerd 和 fdfs_storaged 服务即可。
    /usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf stop
    /usr/bin/fdfs_storaged /etc/fdfs/storage.conf stop

    # 最终解决的方案就是使用 kill -9 命令杀死 tracker 服务或者 storage 服务,然后再重新启动相应服务即可。 (不到万不得已千万不要用)


问题5.当启动storage服务器报fdht_client/fdht_func.c, line: 361, invalid group count: 0 <= 0!错误

  • 错误信息:
    1
    2
    2021-10-30 23:06:15] ERROR - file: fdht_client/fdht_func.c, line: 361, invalid group count: 0 <= 0!
    [2021-10-30 23:06:15] CRIT - exit abnormally!
  • 错误原因: 由于 fast_dht 未正确配置或者启动导致。
  • 解决办法: 在 storage.conf 中进行配置禁用重复文件校验。
    1
    2
    3
    check_file_duplicate = 0
    file_signature_method = hash
    keep_alive = 0


问题6.启动 Nginx 时报 load conf file "/etc/fdfs/mod_fastdfs.conf" fail, ret code: 2错误

  • 错误信息:
    1
    2
    3
    4
    [2021-10-30 21:51:31] ERROR - file: shared_func.c, line: 1214, file /etc/fdfs/mod_fastdfs.conf not exist
    [2021-10-30 21:51:31] ERROR - file: /usr/local/src/fastdfs-nginx-module/src/common.c, line: 163, load conf file "/et
    c/fdfs/mod_fastdfs.conf" fail, ret code: 2
    2021/10/30 21:51:31 [alert] 1302417#0: worker process 1302418 exited with fatal code 2 and cannot be respawned
  • 解决办法: 将拉取的fastdfs-nginx-module项目中的mod_fastdfs.conf复制到/etc/fdfs中。
    1
    cp /usr/local/src/fastdfs-nginx-module/src/mod_fastdfs.conf /etc/fdfs/mod_fastdfs.conf


问题7.启动 Nginx 时报 the "user" directive makes sense only if the master process runs with super-user privileges警告

  • 错误信息:
    1
    2
    3
    4
    $ more /usr/local/nginx/logs/error.log
    2021/10/30 21:51:31 [warn] 1302406#0: the "user" directive makes sense only if the master process runs with super-us
    er privileges, ignored in /usr/local/nginx/conf/nginx.conf:2
    ngx_http_fastdfs_process_init pid=1302418
  • 错误原因: 由于我是采用systemd管理的nginx服务而我在其unit service文件中指定一个与nginx.conf配置文件中的相同的低权限用户。
  • 解决办法: 在 /usr/lib/systemd/system/nginx.service 权限指定的 User 和 Group 字段或者将其设置root。


问题7.在进行FastDHT安装配置时安装berkeley-dbb报错cp: cannot stat 'bdb-sql': No such file or directory

  • 问题信息:
    1
    2
    3
    4
    cp: cannot stat 'bdb-sql': No such file or directory
    cp: cannot stat 'gsg_db_server': No such file or directory
    Makefile:1307: recipe for target 'install_docs' failed
    make: *** [install_docs] Error 1
  • 问题原因: 18.1.40 版本的BUG
  • 解决办法:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 方法1,此问题为18.1.40的问题,回退版本至18.1.38即可成功安装。
    https://download.oracle.com/otn/berkeley-db/db-18.1.32.tar.gz

    # 方法2,将18.1.38中的docs文件夹拷到当前目录下之后重新make install
    tar -xzvf db-18.1.32.tar.gz
    tar -xzvf db-18.1.40.tar.gz
    cp -rp db-18.1.32/docs/bdb-sql db-18.1.40/docs/
    cp -rp db-18.1.32/docs/gsg_db_server db-18.1.40/docs/

    # 方法3,就是本文上面的方法
    # 防止编译错误,这是db-18.1.40版本的Bug。
    mkdir -vp /usr/local/src/db-18.1.40/docs/{bdb-sql,gsg_db_server}

错误参考: https://stackoverflow.com/questions/64707079/berkeley-db-make-install-fails-on-linux


问题8.在进行FastDHT安装配置时报fatal error: db.h: No such file or directory错误

  • 问题信息:
    1
    2
    3
    4
    5
    6
    cc -Wall -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE -g -O -DDEBUG_FLAG -c -o db_op.o db_op.c  -I../common -I/usr/include/fastcommon -I/usr/local/include
    db_op.c:9:10: fatal error: db.h: No such file or directory
    9 | #include <db.h>
    | ^~~~~~
    compilation terminated.
    make: *** [Makefile:25: db_op.o] Error 1
  • 解决办法:
    1
    2
    3
    4
    5
    6
    7
    8
    # (1) 解决db.h文件不存在的错误问题
    vim /usr/local/src/fastdht/make.sh
    27 行: CFLAGS='-Wall -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE -I/usr/local/berkeley-db/include/ -L/usr/local/berkeley-db/lib/'

    # (2) 拷贝库文件并重新加载库文件否则启动时将会报错
    cp /usr/local/berkeley-db/lib/libdb-18.so /usr/lib/ && cp /usr/local/berkeley-db/lib/libdb-18.so /usr/lib64/
    ldconfig
    # ldd /usr/local/bin/fdhtd


问题9.在启动FastDHT后又重启FastDFS的storage服务时报dht_client/fdht_func.c, line: 361, invalid group count: 0 <= 0错误

  • 错误信息:
    1
    2
    3
    more /home/fdfs/storage/logs/storaged.log
    [2021-11-02 21:17:23] ERROR - file: fdht_client/fdht_func.c, line: 361, invalid group count: 0 <= 0!
    [2021-11-02 21:17:23] CRIT - exit abnormally!
  • 错误原因: 通常两个原因,FastDHT 未配置成功(需要进一步验证是否正常运行),到成功运行工作时,验证storage接入FastDHT的参数是否有误。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    $ cat /etc/fdht/fdht_servers.conf
    group_count = 1
    group0 = 10.10.107.225:11411

    # group_count = 3
    # group0 = CentOSA:11411
    # group1 = CentOSB:11411
    # group2 = CentOSC:11411

    $ cat /etc/fdht/fdht_servers.conf
    ...
    check_file_duplicate = 1
    file_signature_method = hash
    key_namespace = FastDFS
    keep_alive = 1
    #include /etc/fdht/fdht_servers.conf #我是此处出错导致。
    ...