分布式文件系统FastDFS如何做到高可用

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

FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。

与Hadoop有什么区别?

  Hadoop也是一个分布式文件系统,hadoop是处理大数据的,什么是大数据呢?就是海量数据。海量数据你一块磁盘估计存不下,那么就需要把数据存到多个磁盘上,还得统一管理,这时就需要一个分布式文件系统来管理。FastDFS同样也是这么一个意思,图片我们有很多,但容量有上限,所以我们要把这些所有的图片存储到多台服务器上,还要进行统一管理,那么就需要一个分布式文件系统,很显然那就是FastDFS了,FastDFS适合于存取图片(建议范围:4KB < file_size <500MB)。

  FastDFS其实很早开始使用,但这次要作内训,因此下面借鉴网友的部份内容结合实践作总结,有些出处可能忘了 请多担待。

FastDFS架构

  下面来看一张FastDFS的架构图,如下图所示。

  FastDFS架构包括Tracker server和Storage server。

  客户端请求Tracker server进行文件上传、下载,通过Tracker server调度最终由Storage server完成文件上传和下载。Tracker server的作用是负载均衡和调度,通过Tracker server在文件上传时可以根据一些策略找到Storage server提供文件上传服务,可以将tracker称为追踪服务器或调度服务器。Tracker server跟踪器和存储节点都可以由一台或多台服务器构成,跟踪器和存储节点中的服务器均可以随时增加或下线而不会影响线上服务,其中跟踪器中的所有服务器都是对等的,可以根据服务器的压力情况随时增加或减少。Tracker负责管理所有的Storage和group,每个storage在启动后会连接Tracker,告知自己所属的group等信息,并保持周期性的心跳,tracker根据storage的心跳信息,建立group==>[storage server list]的映射表,Tracker需要管理的元信息很少,会全部存储在内存中;另外tracker上的元信息都是由storage汇报的信息生成的,本身不需要持久化任何数据,这样使得tracker非常容易扩展,直接增加tracker机器即可扩展为tracker cluster来服务,cluster里每个tracker之间是完全对等的,所有的tracker都接受stroage的心跳信息,生成元数据信息来提供读写服务。

  Storage server作用是文件存储,客户端上传的文件最终存储在Storage服务器上,Storage server没有实现自己的文件系统而是利用操作系统的文件系统来管理文件,可以将storage称为存储服务器。存储系统由一个或多个组组成,组与组之间的文件是相互独立的,所有组的文件容量累加就是整个存储系统中的文件容量。一个卷[Volume](组[group])可以由一台或多台存储服务器组成,一个组中的存储服务器中的文件都是相同的,组中的多台存储服务器起到了冗余备份和负载均衡的作用,数据互为备份,存储空间以group内容量最小的storage为准,所以建议group内的多个storage尽量配置相同,以免造成存储空间的浪费。

  我们从上图还能看到,Client端可以有多个,也就是同时支持多个客户端对FastDFS集群服务进行访问,Tracker是跟踪器,负责协调Client与Storage之间的交互,为了实现高可用性,需要用多个Tracker来作为跟踪器。Storage是专门用来存储东西的,而且是分组进行存储的,每一组可以有多台设备,这几台设备存储的内容完全一致,这样做也是为了高可用性,当现有分组容量不够时,我们可以水平扩容,即增加分组来达到扩容的目的。另外需要注意的一点是,如果一组中的设备容量大小不一致,比如设备A容量是80G,设备B的容量是100G,那么这两台设备所在的组的容量会以小的容量为准,也就是说,当存储的东西大小超过80G时,我们将无法存储到该组中了。Client端在与Storage进行交互的时候也与Tracker cluster进行交互,说的通俗点就是Storage向Tracker cluster进行汇报登记,告诉Tracker现在自己哪些位置还空闲,剩余空间是多大。

文件上传的流程

  现给出一张文件上传的时序图,如下图所示:

  从中可以看到,Client想上传图片,它先向Tracker进行询问,Tracker查看一下登记信息之后,告诉Client哪个storage当前空闲,Tracker会把IP和端口号都返回给Client,Client在拿到IP和端口号之后,便不再需要通过Tracker,直接便向Storage进行上传图片,Storage在保存图片的同时,会向Tracker进行汇报,告诉Tracker它当前是否还留有剩余空间,以及剩余空间大小。汇报完之后,Storage将服务器上存储图片的地址返回给Client,Client可以拿着这个地址进行访问图片。说得更加细致一点,客户端上传文件后存储服务器将文件ID返回给客户端,此文件ID用于以后访问该文件的索引信息。文件索引信息包括:组名,虚拟磁盘路径,数据两级目录,文件名,如下所示:

组名:文件上传后所在的storage组名称,在文件上传成功后由storage服务器返回,需要客户端自行保存。

虚拟磁盘路径:storage配置的虚拟路径,与磁盘选项store_path*对应。如果配置了store_path0则是M00,如果配置了store_path1则是M01,以此类推。

数据两级目录:storage服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据文件。

文件名:与文件上传时不同。是由存储服务器根据特定信息生成,文件名包含:源存储服务器IP地址、文件创建时间戳、文件大小、随机数和文件拓展名等信息。


文件下载的流程

  现给出一张文件下载的时序图,如下图所示:

文件下载的步骤可以是:

1. client询问tracker下载文件的storage,参数为文件标识(组名和文件名)。

2. tracker返回一台可用的storage。

3. client直接和storage通讯完成文件下载。

搭建FastDFS

安装组件

nginx+FastDFS+fastdfs-nginx-module

布署结构:

tracker:storage0:192.168.80.32

storage1:192.168.80.30

storage2:192.168.80.31

nginx 放在192.168.80.32

下载下列最新的安装包:

fastdfs-master.zip:FastDFS源码

libfastcommon-master.zip:(从 FastDFS 和 FastDHT 中提取出来的公共 C 函数库)

fastdfs-nginx-module-master.zip:storage节点http服务nginx模块

nginx.tar.gz:Nginx安装包

防火墙

防火墙中打开tracker服务器端口( 默认为 22122)

shell> vi /etc/sysconfig/iptables

添加如下端口行:

-A INPUT -m state --state NEW -m tcp -p tcp --dport 22122 -j ACCEPT

storage服务器需要添加端口

-A INPUT -m state --state NEW -m tcp -p tcp --dport 23000 -j ACCEPT

重启防火墙:

shell> service iptables restart

所有服务器先安装libevent工具包

yum -y install libevent

所有服务器安装libfastcommon工具包

先将fastdfs-master.zip和libfastcommon-master.zip 上传至所有服务器的/opt文件夹下

1. 解压缩 unzip libfastcommon-master.zip

编译安装

2. cd libfastcommon-master

3. ./make.sh

4. ./make.sh install

5. 把/usr/lib64/libfastcommon.so文件向/usr/lib/下复制一份 (32位才需要)


安装Tracker服务

解压缩unzip fastdfs-master.zip

编译安装

cd fastdfs-master

./make.sh

./make.sh install

安装后在/usr/bin/目录下有以fdfs开头的文件都是编译出来的。

配置文件都放到/etc/fdfs文件夹

把/opt/fastdfs-master/conf目录下的所有的配置文件都复制到/etc/fdfs下。

cp -r /opt/fastdfs-master/conf/*  /etc/fdfs

创建文件存放路径

mkdir -p /data/fastdfs/tracker#创建tracker文件存放路径(tracker服务器才需要)

mkdir -p /data/fastdfs/storage#创建storage 文件存放路径mkdir -p /data/fastdfs/client#创建client 文件存放路径

配置tracker服务

修改/etc/fdfs/conf/tracker.conf文件。

vim /etc/fdfs/tracker.conf #编辑tracker配置文件

bind_addr=#绑定IP  绑定的IP地址 (常用于服务器有多个IP但只希望一个IP提供服务)。

如果不填则表示所有的(一般不填就OK),相信较熟练的SA都常用到类似功能,很多系统和应用都有

port=22122#tracker服务端口

base_path=/data/fastdfs/tracker#目录地址,里面会创建data(存放存储服务器信息)、logs,日志文件  (根目录必须存在,子目录会自动创建)

其它内容默认,有关配置说明参考文章后面补充内容。

配置storage服务

在2台storage服务器上配置storage服务,前题也是安装libfastcommon和fastdfs。

修改/etc/fdfs/conf/storage.conf文件。

group_name=group1#存储组名

client_bind=true#当连接其他服务器时解析该主机地址

port=23000#storage端口 23000

base_path=/data/fastdfs/storage#基础存储数据和日志文件

store_path0=/data/fastdfs/storage#group 所占用的目录或硬盘,有几个写几个

tracker_server=192.168.80.32:22122#指定tracker1服务器

其它内容默认,有关配置说明参考文章后面补充内容。

只编辑tracker服务器上的client.conf

vim /etc/fdfs/client.conf

base_path=/data/fastdfs/client#基础数据和日志文件

tracker_server=192.168.80.32:22122#tracker1服务器


以上就是fastdfs的所有配置,下面分别启动32服务上的tracker、storage和30和31上的storage。

启动tracker

/usr/bin/fdfs_trackerd  /etc/fdfs/tracker.conf

重启使用命令:

/usr/bin/fdfs_trackerd  /etc/fdfs/tracker.conf  restart

检查FastDFS Tracker Server是否启动成功:

ps -ef | grep fdfs_trackerd

停止:

/etc/init.d/fdfs_trackerd stop

设置tracker服务开机启动

chkconfig fdfs_trakcerd on

启动storage服务

/usr/bin/fdfs_storaged  /etc/fdfs/storage.conf restart

停止storage服务器 /etc/init.d/fdfs_storaged stop

设置storage服务开机启动

chkconfig fdfs_storaged on

测试服务

/usr/bin/fdfs_test  /etc/fdfs/client.conf  upload  /etc/fdfs/anti-steal.jpg

有以上返回内容说明已经上传成功,但我们将url 在浏览器上访问时会返回404 是不允许直接访问的,因此我们需要用到下面的nginx代理来作web服务访问。

搭建nginx提供http服务

fastdfs-nginx-module 作用说明

FastDFS 通过 Tracker 服务器,将文件放在 Storage 服务器存储,但是同组存储服务器之间需要进入文件复制,有同步延迟的问题。

如果Tracker 服务器将文件上传到了 192.168.80.30,上传成功后文件ID已经返回给客户端。此时 FastDFS 存储集群机制会将这个文件同步到同组存储192.168.80.31,在文件还没有复制完成的情况下,客户端如果用这个文件 ID 在 192.168.80.31 上取文件,就会出现文件无法访问的错误。

而 fastdfs-nginx-module 可以重定向文件连接到源服务器取文件,避免客户端由于复制延迟导致的文件无法访问错误

可以使用官方提供的nginx插件。要使用nginx插件需要重新编译。

更改fastdfs-nginx-module配置

上传fastdfs-nginx-module.tar.gz

1. 解压插件压缩包

2. 修改/opt/fastdfs-nginx-module/src/config文件,把其中的local去掉。(最新的没有local)

将mod_fastdfs.conf 拷贝到fdfs下进行配置

cd fastdfs-nginx-module/src

cp mod_fastdfs.conf /etc/fdfs/#将mod_fastdfs.conf 拷贝到fdfs下进行配置

vim /etc/fdfs/mod_fastdfs.conf

base_path=/tmp#日志存放路径

tracker_server=192.168.80.32:22122#配置成tracker server 地址和端口

storage_server_port=23000url_have_group_name =true#url 中是否包组名

store_path0=/data/fastdfs/storage#文件存放路径,与storage 一致

group_name=group1

编译安装Nginx

安装依赖包

yum -y install zlib zlib-devel openssl openssl--devel pcre pcre-devel

安装nginx

上传解压nginx

cd nginx

对nginx重新config

./configure \--prefix=/opt/nginx \--pid-path=/opt/nginx/nginx.pid \--lock-path=/opt/nginx.lock \--error-log-path=/opt/nginx/log/error.log \--http-log-path=/opt/nginx/log/access.log \--with-http_gzip_static_module \--http-client-body-temp-path=/opt/nginx/client \--http-proxy-temp-path=/opt/nginx/proxy \--http-fastcgi-temp-path=/opt/nginx/fastcgi \--http-uwsgi-temp-path=/opt/nginx/uwsgi \--http-scgi-temp-path=/opt/nginx/scgi \--add-module=/opt/fastdfs-nginx-module/src


 再进行

./make.sh && ./make.sh install

配置nginx

 vim /opt/nginx/conf/nginx.conf

在nginx的配置文件中添加一个Server:

server {

       listen       8888;

       server_name  192.168.80.32;

       location /group1/M00/{

       #root /home/FastDFS/fdfs_storage/data;

    ngx_fastdfs_module;       

 }}


 但上面的内容已经固定死了group1的M00,Storage 对应有多个 group 的情况下,访问路径带 group 名,如/group1/M00/00/00/xxx,在这里改用:

location ~/group([0-9])/M00 {

ngx_fastdfs_module;

}

注意:

8888 端口值是要与/etc/fdfs/storage.conf 中的http.server_port=8888 相对应,因为 http.server_port 默认为 8888,如果想改成 80,则要对应修改过来。

如查下载时如发现老报 404, 将 nginx.conf 第一行 user nobody 修改为 user root 后重新启动。

防火墙中打开 Nginx 的 8888 端口

vi /etc/sysconfig/iptables

添加:

-A INPUT -m state --state NEW -m tcp -p tcp --dport 8888 -j ACCEPT

重新启动防火墙:

shell> service iptables restart

开机自启动

即在rc.local增加启动代码就可以了。

vi /etc/rc.local

增加一行 /opt/nginx/sbin/nginx

设置执行权限:

chmod 755 rc.local

启动Nginx

shell>/opt/nginx/sbin/nginx

ngx_http_fastdfs_set pid=xxx

nginx重启命令为:

/opt/nginx/sbin/nginx -s reload

访问测试图片

通过浏览器访问测试时上传的文件,测试上传后返回的文件ID为:group1/M00/00/00/wKhQIFoKF3KAfw8wAABdrZgsqUU551_big.jpg,用浏览访问的地址为:http://192.168.80.32:8888/group1/M00/00/00/wKhQIFoKF3KAfw8wAABdrZgsqUU551_big.jpg

注意: 千万不要使用 kill -9 命令强杀 FastDFS 进程,否则可能会导致 binlog 数据丢失。

   至此已经搭起了一个简单的集群模式,其中上面的配置需要根据实际情况进行调整。

说明:

tracker配置文件说明:

disabled=false#配置是否生效

bind_addr=192.168.6.102#绑定IP

port=22122#服务端口

connect_timeout=30#连接超时时间

network_timeout=60#tracker server的网络超时,单位为秒。

base_path=/home/yangzi#目录地址,里面会创建data(存放存储服务器信息)、logs,日志文件

max_connections=256#系统提供服务最大连接数

work_threads=4#线程数,通常设置CPU数

store_lookup=2上传组(卷) 的方式 0:轮询方式 1: 指定组 2: 平衡负载(选择最大剩余空间的组(卷)上传)

这里如果在应用层指定了上传到一个固定组,那么这个参数被绕过

store_group=group1

当上一个参数设定为1 时 (store_lookup=1,即指定组名时),必须设置本参数为系统中存在的一个组名。如果选择其他的上传方式,这个参数就没有效了

store_server=0选择哪个storage server 进行上传操作(一个文件被上传后,这个storage server就相当于这个文件的storage server源,会对同组的storage server推送这个文件达到同步效果)

# 0: 轮询方式# 1: 根据ip 地址进行排序选择第一个服务器(IP地址最小者)#2: 根据优先级进行排序(上传优先级由storage server来设置,参数名为upload_priority)

store_path=0选择storage server 中的哪个目录进行上传。storage server可以有多个存放文件的base path(可以理解为多个磁盘)。# 0: 轮流方式,多个目录依次存放文件#2: 选择剩余空间最大的目录存放文件(注意:剩余磁盘空间是动态的,因此存储到的目录或磁盘可能也是变化的)

download_server=0选择哪个 storage server 作为下载服务器

#0: 轮询方式,可以下载当前文件的任一storage server

#1: 哪个为源storage server 就用哪一个 (前面说过了这个storage server源 是怎样产生的) 就是之前上传到哪个storage server服务器就是哪个了

reserved_storage_space = 4GB

storage server 上保留的空间,保证系统或其他应用需求空间(指出 如果同组的服务器的硬盘大小一样,以最小的为准,也就是只要同组中有一台服务器达到这个标准了,这个标准就生效,原因就是因为他们进行备份)

log_level=info#选择日志级别

run_by_group=#操作系统运行FastDFS的用户组

run_by_user=#操作系统运行FastDFS的用户

allow_hosts=*#可以连接到此 tracker server 的ip范围(对所有类型的连接都有影响,包括客户端,storage server)

sync_log_buff_interval = 10# 同步或刷新日志信息到硬盘的时间间隔,单位为秒#注意:tracker server 的日志不是时时写硬盘的,而是先写内存。

check_active_interval = 120# 检测 storage server 存活的时间隔,单位为秒。# storage server定期向tracker server 发心跳,如果tracker server在一个check_active_interval内还没有收到storage server的一次心跳,那边将认为该storage server已经下线。所以本参数值必须大于storage server配置的心跳时间间隔。通常配置为storage server心跳时间间隔的2倍或3倍。thread_stack_size = 64KB# 线程栈的大小。FastDFS server端采用了线程方式。更正一下,tracker server线程栈不应小于64KB,不是512KB。# 线程栈越大,一个线程占用的系统资源就越多。如果要启动更多的线程(V1.x对应的参数为max_connections,V2.0为work_threads),可以适当降低本参数值。

storage_ip_changed_auto_adjust =true# 这个参数控制当storage server IP地址改变时,集群是否自动调整。注:只有在storage server进程重启时才完成自动调整。storage_sync_file_max_delay = 86400# V2.0引入的参数。存储服务器之间同步文件的最大延迟时间,缺省为1天。根据实际情况进行调整storage_sync_file_max_time = 300# V2.0引入的参数。存储服务器同步一个文件需要消耗的最大时间,缺省为300s,即5分钟。http.disabled=true# HTTP服务是否不生效 当然编译的时候我已经把 with_httpd宏去掉了,http.server_port=80# HTTP服务端口#下列参数只有 开启http服务才有用

http.check_alive_interval=30

http.check_alive_type=tcp

http.check_alive_uri=/status.html

http.need_find_content_type=true


storage.conf配置说明:

disabled=false#配置是否生效

group_name=group1#storage所在组(卷)

bind_addr=192.168.6.100# 绑定IP,另一太 storage IP为 192.168.6.101

client_bind=true#bind_addr通常是针对server的。当指定bind_addr时,本参数才有效。

port=23000# 是storage 服务端口

connect_timeout=30# 连接超时时间,针对socket套接字函数

connectnetwork_timeout=60# storage server 网络超时时间,单位为秒。

heart_beat_interval=30# 心跳间隔时间,单位为秒

stat_report_interval=60# storage server向tracker server报告磁盘剩余空间的时间间隔,单位为秒。

base_path=/home/eric# base_path 目录地址,根目录必须存在 子目录会自动生成# 会产生data(数据存储地方)、 logs日志文件

max_connections=256# 最大连接数buff_size = 256KB# 设置队列结点的buffer大小。

work_threads=4# 工作线程数

disk_rw_separated =true# 磁盘IO读写是否分离,缺省是分离的。

disk_reader_threads = 1# 针对单个存储路径的读线程数,缺省值为1

disk_writer_threads = 1# 针对单个存储路径的写线程数,缺省值为1

sync_wait_msec=200# 同步文件时,如果从binlog中没有读到要同步的文件,休眠N毫秒后重新读取,0表示不休眠,立即再次尝试读取。

sync_interval=0# 同步上一个文件后,再同步下一个文件的时间间隔,单位为毫秒,0表示不休眠,直接同步下一个文件。

sync_start_time=00:00

sync_end_time=23:59# 允许系统同步的时间段 (默认是全天) 。一般用于避免高峰同步产生一些问题而设定,相信sa都会明白。

write_mark_file_freq=500# 把storage的mark文件定期同步到磁盘的时间间隔,单位为秒

store_path_count=1# 存放文件时storage server支持多个路径(例如磁盘)。这里配置存放文件的基路径数目,通常只配一个目录。

store_path0=/home/eric# 逐一配置store_path个路径,索引号基于0。注意配置方法后面有0,1,2 ......,需要配置0到store_path - 1。

# 如果不配置base_path0,那边它就和base_path对应的路径一样。

subdir_count_per_path=32# FastDFS存储文件时,采用了两级目录。这里配置存放文件的目录个数

tracker_server=192.168.6.188:22122# tracker_server 的列表 要写端口的哦

log_level=info# 日志级别

run_by_group=# 运行storage 用户组

run_by_user=# 运行storage 用户

allow_hosts=*# 允许连接IP列表

file_distribute_path_mode=0# 文件在data目录下分散存储策略。# 0: 轮流存放# 1: 随机存储

file_distribute_rotate_count=100# 当上面的参数

file_distribute_path_mode配置为0(轮流存放方式)时,本参数有效。#当一个目录下的文件存放的文件数达到本参数值时,后续上传的文件存储到下一个目录中

fsync_after_written_bytes=0# 当写入大文件时,每写入N个字节,调用一次系统函数fsync将内容强行同步到硬盘。0表示从不调用

fsyncsync_log_buff_interval=10# 同步或刷新日志信息到硬盘的时间间隔,单位为秒

sync_binlog_buff_interval=60# 同步binglog(更新操作日志)到硬盘的时间间隔,单位为秒

sync_stat_file_interval=300# 把storage的stat文件同步到磁盘的时间间隔,单位为秒。

thread_stack_size=512KB# 线程栈的大小。FastDFS server端采用了线程方式。

# 线程栈越大,一个线程占用的系统资源就越多。

upload_priority=10本storage server作为源服务器,上传文件的优先级,可以为负数。值越小,优先级越高。这里就和 tracker.conf 中store_server= 2时的配置相对应了

if_alias_prefix=

check_file_duplicate=0# 是否检测上传文件已经存在。如果已经存在,则不存在文件内容,建立一个符号链接以节省磁盘空间。 结合 fastdfh使用的。 1是检测,0是不检测,我们不使用fastdfh 当然 0

key_namespace=FastDFS# 当上个参数设定为1 或 yes时 (true/on也是可以的) , 在FastDHT中的命名空间

keep_alive=0# 与FastDHT servers 的连接方式 (是否为持久连接) 

# 下面是http的配置了就不多说了http.disabled=truehttp.domain_name=http.server_port=80http.trunk_size=256KB

http.need_find_content_type=true


 问题:

编译安装nginx需要pcre包,未安装会有如下提示:

./configure:error:the HTTP rewritemodulerequires the PCRE library.Youcan either disable themodulebyusing--without-http_rewrite_moduleoption,orinstall the PCRE libraryintothe system,orbuild the PCRE librarystaticallyfromthe sourcewithnginxbyusing--with-pcre=option.

需要安装pcre的devel包,pcre-devel。使用yum安装即可:(以下命令还带有ssl、zlib等依赖的安装)

yum-y install zlib zlib-devel openssl openssl--devel pcre pcre-devel

推荐阅读更多精彩内容

  • 详细配置fastdfds分布式文件服务器的方法: 跟踪服务器:192.168.4.121 (edu-dfs-tra...
    Tsinyong阅读 2,322评论 2 7
  • 1 FastDFS简介 FastDFS是一款类Google FS开源的轻量级分布式文件系统,它用纯C语言实现,支持...
    小小少年Boy阅读 6,074评论 1 7
  • 内容简介在跟随项目实践过程中,代码中需要使用上传图片的功能,其中使用了FastDFS作为图片服务器,在此记录一下搭...
    Little_Dragon_阅读 998评论 0 0
  • 我们看多了别人的悲哀,别人的欣喜,别人的欢畅,别人的泪水。我们以为自己不是那一个脆弱的人,我们假想自己可以巧妙的绕...
    会慢慢懂得阅读 475评论 5 0
  • 在这个地方荆轲告别燕太子丹,壮士悲歌壮气,怒发冲冠。那时的人已经都不在了,今天的易水还是那样的寒冷。
    薛兆锴阅读 95评论 0 0
  • 昨晚病了, 可是怎么也睡不着, 自己也觉得奇怪, 非一般人也。 也只能说是最近心情低落吧, 虽总是起起伏伏,可依旧...
    WHY归阅读 147评论 0 1