最新要闻
- 陕西招聘会现3万月薪岗位学生排长龙 招聘人员:半天收简历150份
- 当前动态:供不应求!真我GT Neo5 1TB版真香:二手用户也抢着要
- “山药成了精”?男子买到奇葩山药外形酷似人脚掌
- 美少女三消游戏 《Mirror 2: Project X》开发组宣布破产解散
- 酒店回应到211大学招服务员:符合流程 面向所有高校毕业生
- 快看点丨京东CEO徐雷:百亿补贴效果超过预期、要做天天低价
- 【新要闻】比亚迪加入降价大军!宋Pro DM-i限时优惠:88元折扣6888元
- 焦点简讯:耳机煲机一般要煲多久_耳机煲机方法是什么?买回来新耳机要怎样煲?耳机要煲多久?
- 全球速看:浙四医院官网招聘2021_浙四医院官网
- 环球观点:京东2022年收入超1万亿:“百亿补贴”会一直有!
- 简约时尚 健身备一件:361°新款轻薄速干衣39元冲量
- 越来越卖不动了!最畅销十款数码相机一览:索尼成赢家 第一性价比绝
- 全球通讯!Intel的大小核CPU架构:最终还是把一些老游戏坑了
- 往返近1000元!景区回应坐滑竿上山按斤收费:网友力挺明码标价
- 前沿资讯!海洋风筝爆火 一周暴涨498%超过秋裤!网友:春天的信号
- 天天消息!甜香丝滑 旺旺邦德低脂轻乳咖啡官方狂促:合2元一瓶
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
爆肝两万字,详解fastdfs分布式文件系统
1.学习目标
2.简介
技术论坛:http://bbs.chinaunix.net/forum-240-1.html资源地址:https://sourceforge.net/projects/fastdfs/源码地址:https://github.com/happyfish100
【资料图】
- FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。
- FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
- FastDFS服务端有两个角色:跟踪器(tracker)和存储节点(storage)。跟踪器主要做调度工作,在访问上起负载均衡的作用。
- 存储节点存储文件,完成文件管理的所有功能:就是这样的存储、同步和提供存取接口,FastDFS同时对文件metadata进行管理。所谓文件的metadata就是文件的相关属性,以键值对(key value)方式表示,如:width=1024,其中的key为width,value为1024。文件metadata是文件属性列表,可以包含多个键值对。
- 跟踪器和存储节点都可以由一台或多台服务器构成。跟踪器和存储节点中的服务器均可以随时增加或下线而不会影响线上服务。其中跟踪器中的所有服务器都是对等的,可以根据服务器的压力情况随时增加或减少。
- 为了支持大容量,存储节点(服务器)采用了分卷(或分组)的组织方式。存储系统由一个或多个卷组成,卷与卷之间的文件是相互独立的,所有卷的文件容量累加就是整个存储系统中的文件容量。一个卷可以由一台或多台存储服务器组成,一个卷下的存储服务器中的文件都是相同的,卷中的多台存储服务器起到了冗余备份和负载均衡的作用
- 在卷中增加服务器时,同步已有的文件由系统自动完成,同步完成后,系统自动将新增服务器切换到线上提供服务。当存储空间不足或即将耗尽时,可以动态添加卷。只需要增加一台或多台服务器,并将它们配置为一个新的卷,这样就扩大了存储系统的容量。
FastDFS中的文件标识分为两个部分:卷名和文件名,二者缺一不可。
2.1.架构图
解释:写入假设我现在client要上传文件,我要找到跟踪器tracker,tracker找到client,然后tracker找到存储节点,看看存储节点那个卷下面的节点比较空闲,能放得下这个文件,然后写入进去,生成一个文件名读取如果我们client要下载文件,不需要与tracker再做交互,直接与storage打交道,根据我们当时上传文件tracker给我们提供的文件名,我们加上服务器名字和端口号再加上完整的文件名即可读取下载成功主备切换我们storage存储节点是由多个卷构成的一个大的集群,比方说c d e盘构成一个硬盘,每个卷又分成主和子,他们两者文件类型一致,如果主服务器崩了,子服务器马上可以顶上
2.2.上传流程
- client询问tracker上传到的storage,不需要附加参数
- tracker返回一台可用的storage;
- client直接和storage通讯完成文件上传
2.3.下载流程
- client询问tracker下载文件的storage,参数为文件标识(组名和文件名);
- tracker返回一台可用的storage;
- client直接和storage通讯完成文件下载。
client可以直接去到Storage进行在线的读取和下载,这个在线读取和下载前提是我们知道它的一个ip地址和端口号,后面跟上卷名,再跟上文件名,我们如果知道这个完整路径的话,可以直接去到我们存储节点进行在线预览,或者是在线下载;如果我们现在只知道一个卷名和文件名,我们并不知道ip和端口,我们也可以去找我们的tracker,拿着我们的卷名和文件名去找我们的跟踪器,跟踪器就会找到对应的卷名和文件名所在的节点,会把这个存储节点的ip地址和端口号返回给我们的客户端,然后我们client再次通过我们的ip端口卷名文件名,然后直接通过我们的存储节点进行我们的读取和下载操作,一般我们如果考虑效率问题的话,肯定是我们直接拿着ip端口卷名文件名直接去存储节点,读取我们的一个文件,如果实在是不知道ip和端口情况下,可能就需要通过我们tracker,但一般情况下,我们tracker上传的时候,我们tracker会返回一个完整的卷名和文件名加ip和端口,我们一般呢会把返回的相对路径存放到数据库里面去,那我们需要进行文件预览和下载的时候呢,我们一般从数据库拿到我们带有ip和端口的卷名和文件名这一整串信息的数据直接去storage进行一个下载 ,效率更高一点!!!
2.4.术语介绍
- TrackerServer:跟踪服务器,主要做调度工作,在访问上起负载均衡的作用,记录storage server的状态,是连接Client和Storage server的枢纽。
- Storage Server:存储服务器,文件和meta data都保存到存储服务器上
- group:组,也称为卷,同组内服务器上的文件是完全相同的【主崩子接】
- 文件标识:包括两部分:组名和文件名(包含路径)
- meta data:文件相关属性,键值对(Key Value Pair)方式,如:width1024,height=768
2.5.同步机制
- 同一组内的storage server之间是对等的,文件上传、删除等操作可以在任意一台storage server上进行;【比方我们client去进行写操作,会根据tracker自己去调度,假设三台服务器,会根据tracker调度结果,返回里面任意一台的IP地址和端口,主要是看storage那一台服务器符合调度的一个规则,比方说那一台storage它现在是空闲的,那一台storage它的一个剩余磁盘容量能放下这个文件,它返回的并不一定是某一台的ip地址,可能是三台里面随机选一个ip地址或者端口进行返回】
- 文件同步只在组内的storage server之间进行,采用push方式,即源服务器同步给目标服务器;【采用广播方式,比如说我们现在是一个高并发,每一台都正好在进行写操作,传统的我们一个服务器新增了数据量,它们之间要进行一个相互通信,一直通信到最后一个服务器,再进行相应的数据同步,表示我这里现在新增了数据,你和我同步一下,这是传统的,我们发现它们相互通信的次数非常频繁;所以我们一般采用广播模式,比方说我们某个服务器写了一条数据,它就会广播告诉其它服务器,表示我这里新增了数据,你们也新增一下;】
- 源头数据才需要同步,备份数据不需要再次同步,否则就构成环路了;【被写入数据称为源服务器;我们源头数据才需要进行一个同步,如果是备份数据不需要广播同步,一直不停广播就形成环路了!!!】
- 上述第二条规则有个例外,就是新增加一台storage server时,由已有的一台storage server将已有的所有数据(包括源头数据和备份数据)同步给新增服务器【假设我们有三台服务器,增加了一台,加的这一台就会从我们原有的三台里面,随机选一台作为一个源头数据,然后就会把这个源头数据和我们的备份数据同步到新增的服务器里面】
2.6.FastDFS运行时目录结构
2.6.1.Tracker Server目录
2.7.FastDFS和其它文件存储的简单对比
2.7.1.FastDFS和集中存储方式对比
指标 | FastDFS | NFS | 集中存储设备加 NetApp、NAS |
---|---|---|---|
线性扩容性 | 高【扩容好】 | 差 | 差 |
文件高并发访问性能 | 高【速度快】 | 差 | 一般 |
文件访问方式 | 专用API【有自己的AIP】 | POSIX【可移植操作系统接口】 | POSIX |
硬件成本 | 较低【成本低】 | 中等【硬盘成本高】 | 高【硬盘成本高】 |
相同内容文件只保存一份 | 支持【相同文件只有只保留一份】 | 不支持 | 不支持 |
2.7.2.FastDFS和mogileFS对比
指标 | FastDFS | mogileFS |
---|---|---|
系统简洁性 | 简洁 只有两个角色:tracker和storage | 一般有三个角色:trocker、storage和存储文件信息的mysql db |
系统性能 | 很高(没有使用数据库,文件同步直接点对点,不经过tracker中转) | 高(使用mysql来存储文件索引信息,文件同步通过tracker调度和中转) |
系统稳定性 | 高(C语言开发,可以支持高并发和高负载) | 一般(Perl语言开发,高并发和高负载支持一般)【没有C语言高并发高负载好】 |
RAID方式 | 分组(组内冗余),灵活性较大 | 动态冗余,灵活性一般 |
通信协议 | 专用协议,下载文件支持http【专用协议最大的好处就是写的操作效率跟高,在tcp/ip之上】 | http |
技术文档 | 较详细 | 较少 |
文件附加属性(meta data) | 支持【文件相关属性存储在storage】 | 不支持 |
相同内容只保存一份 | 支持【根据文件相关属性判断,如果属性一致则覆盖】 | 不支持 |
下载文件时支持文件偏移量【断点续传, 从指定位置向前向后移动的字节数,比如我们下载一个文件一半点暂停,然后再点开始会从你已经下载好的进度开始,而有的文件你点了暂停可能就让你从头开始下载】 | 支持 | 不支持 |
有没有比FastDFS更好的呢?当然有,那就是我们的hdfs,hdfs更多的是大数据方面去用,它的性能会比FastDFS更好一点
3.安装
3.1.安装简介
FastDFS主要是两个角色,一个tracker和storage,它们本质上都是一个FastDFS一个包,它们通过对应不同配置来确认它们不同的角色,所以它们通用的安装都是FastDFS的安装包我们这边也准备了两台服务器,一台是安装我们tracker,一台安装storage,它们只是对应角色配置不一样如果电脑配置比较差的话,也可以直接安装在一台服务器上,只需修改对应角色配置即可,安装包都是一样,也可以实现!!!
3.2.FastDFS安装包
上传所选安装包
- 第一个就是fastdfs安装包
- 第二个对应client安装包
- 第三个是nginx模块包
- 第四个就是fastdfs公用的库
- 第五个fastdfs和nginx整合
3.3.安装依赖
3.3.1.安装c++相关依赖
我们fastdfs是根据C语言进行开发的,所以我们需要安装C++相关依赖
yum -y install cmake gcc-c++
3.3.2.安装fastdfs核心库
安装完成之后,我们还需要安装我们fastdfs核心库这个核心库呢其实是从fastdfs和fastdht中提取出来的公用的C函数的库,fastdfs和fastdht是同样一个作者去写的两个产品,都是C语言编写,然后里面会有一些公用函数库,作者把它提取出来当成一个专门核心库如果我们后缀名是.zip,需要安装一个zip解压插件
yum -y install unzip
我们把需要安装的fastdfs所以文件放在一个文件夹方便管理
mkdir -p /usr/local/fastdfs
然后我们就可以去解压了zip后缀
unzip libfastcommon-1.0.43.zip
tar后缀
tar -zxvf libfastcommon-1.0.43.zip
我们进行之后看见有一个make.sh执行脚本,我们就通过这个脚本去进行一个编译和安装
.make.sh
这样就会进行编译编译好后我们就可以进行安装了
.make.sh install
还有一个问题就是我们fastdfs主程序的lib目录是在/usr/local/lib下面的,所以我们需要去创建一些软链接,这些软链接就相当于快捷方式,把它的一个快捷方式从原本指向的目录改成我们想要的指向的目录这个意思相当把前面原本指向的路径指定到我们后面需要我们指定路径
ln -s /usr/1ib64/1ibfastcommon.so /usr/local/lib/libfastcommon.soln -s /usr/local/lib64/libfdfsclient.so /usr/local/lib/libfdfsclient.soln -s /usr/local/lib64/libfdfsclient.so /usr/lib/libfdfsclient.so
3.3.3.安装fastdfs
tar -zxvf fastdfs-6.06.tar.gz
我们发现这里也有make.sh我们可以做一个可选的操作,就是说我们可以把它的一个路径改成指定的路径,因为它默认安装路径是在/usr下面,我们可以把它改成/usr/local下面去,当然在集群下面就不要去改,那我们现在是安装的一个单节点,可以去尝试的改一下我们进入fastdfs
vim make.sh
我们搜索/TAGE_PREFIX我们把它改到/usr/local然后我们再次去安装
./make.sh 先编译
再安装
./make.sh install
安装后,FastDFS主程序所在的位置是:
- /usr/local/bin可执行文件所在位置。默认安装在/usr/bin中。
- /etc/fdfs配置文件所在位置。就是默认位置。
- /usr/1oca1/1ib64主程序代码所在位置。默认在usr/bin中。
- /usr/local/irclude/fastdfs包含的一些插件组所在位置。默认在/usr/include/fastdfs中。
安装好了之后,我们可以去看一下服务脚本所在位置
cd /etc/init.d/
然后我们可以看一下我们配置文件的模板所在位置
cd /etc/fdfs/
如果我们这台服务器当做tracker来用的话,只需拷贝tracker配置拷贝过去进行修改,把后面的.sample删掉就可以用了,建议不要拿源文件直接用,最好是拷贝过去再进行修改,因为如果改错了还有备份!!!我们还可查看内置命令所在目录
cd /usr/local/bin/
这个就是fastdfs内置命令,包括重启,启动,停止,还有测试,跟踪等等。。。到这里,我们整个tracker服务器的fastdfs安装好了!!!**同理,我们还要去安装storage,拿storage的安装和tracker是一模一样的,只是配置文件不同,其它安装包都一样,所以我们如果在同一台服务器部署只需修改配置文件即可!!! **
4.配置tracker
4.1.拷贝tracker.conf.sample
我们首先进入配置文件模板
cd /etc/fdfs
我们看到里有个tracker.conf.sample然后我们拷贝一下,到conf就行
cp tacker..sample tracker.conf
4.2.配置tracker.conf
然后我们就可以进行配置了
vim tracker.conf
我们简单了解一下里面的属性
- bind_addr = 绑定的ip地址
- port = 22122 端口号
- connect_timeout = 5 连接超时
- network_timeout = 60 网络超时
- base_path = /home/yuqing/fastdfs【这个是fastdfs我们一个tracker启动之后使用的一个根目录,它呢也要去存储一些信息,它存储的就是我们卷里面的一些storage的存储节点,包括我们卷1 卷2 卷3 每个卷下面有哪些storage,每个storage它的ip和它的端口都要去存储一下,因为它是做一个中转调度操作,client发起一个写操作的时候,我们的一个tracker就会去调度找到哪一个节点可以给我们去写操作,然后会返回ip和端口,tracker需要把我们卷下面的所有的一个存储节点ip和端口存储一下,这边我们可以把这个目录修改一下,base_path = /fastdfs/tracker,这个目录是我们自定义的,待会我们要去创建这个目录】
- max_connections = 1024 最大连接
- accept_threads = 1 接收的线程
- work_threads = 4 工作的线程
- min_buff_size = 8KB 最小缓冲8KB
- max_buff_size = 128KB 最大的缓冲
- store_server = 0 负载均衡策略,0表示轮询机制,如果是1的话,就是第一个服务器通过我们ip地址查找到的第一个服务器,如果是2的话查找出来的也是第一个服务器,不是通过ip地址
- store_path = 0 默认轮询
- download_server = 0 下载服务默认轮询
这里我们就配置完成了,我们主要配置base_path 根目录即可!!!然后我们别忘了创建刚才自定义的目录
mkdir -p /fastdfs/tracker
4.3.启动tracker
我们创建完目录呢,就可以启动tracker了,来到启动目录
cd /etc/init.d/
我们看到有两个文件 fdfs_trackerd和fdfs_storaged这两个就是我们启动的文件我们之前安装的时候,我们修改过它的一个目录,所以呢我们这边启动的时候呢,也去修改它的一个目录,如果我们没有去修改它的目录,这边可以直接启动!!!我们进入配置文件修改目录即可!!!
vim fdfs_trackerd
我们修改之后应该是/usr/local/bin/fdfs_trackerd,保存并退出, 启动即可
./fdfs_trackerd start
怎么去看有没有启动成功呢?两种方法:查看状态
./fdfs_trackerd status
查看进程
ps -ef|grep fdfs
停止
.fdfs_trackerd stop
重启
/etc/init.d/fdfs_trackerd restart
开机启动我们进入文件
vim /etc/rc.d/rc.local
添加启动文件
/etc/init.d/fdfs_tracked start
4.4.小结
我们现在已经启动了tracker包括我们相应的一些配置,其实真正要做的配置没有,默认的端口都没改,只是把它的base_path 根目录进行了一个修改,当然这个修改也是可有可无的,但是记住一定要创建,有一个负载均衡,大部分默认都是一个轮询的方式,其它的也没什么!!!
5.配置Storage
5.1.拷贝storage.conf.sample
跟tracker一样
cd /etc/fdfs
同样,拷贝一份
cp storage.conf.sample storage.conf
5.2.配置storage.conf
修改storage
vim storage.conf
也是一样,我们观察一下里面的属性
- group_name = group1 默认组名,也是卷名
- bind_addr = 绑定的ip地址
- client_bind = true 是否允许客户端访问
- port = 23000 端口号
- connect_timeout = 5 连接超时
- network_timeout = 60 网络超时
- base_path = /home/yuqing/fastdfs【同样这里也是存放storage_server它里面基础数据的内容,以及日志内容的目录,比如说启动的进程号、同步的相应信息,我们也可以进行修改,/fastdfs/storage/base】
- max_connections =1024 最大连接数
- buff_size = 256KB 缓冲大小
- accept_threads = 1接收的线程
- work_threads = 4 工作的线程
- store_path0 = /home/yuqing/fastdfs【这个目录是我们真正存放文件的目录,我们也更改一下 /fastdfs/storage/store,这个目录会在我们storage启动的时候,它会去生成256x256目录,当然我们base_path和store_path0可以用同一个目录也是没有问题的,一般建议分开好区分】
- tracker_server = 192.168.209.121:22122【这里也是我们需要修改的地方,有两个我们只需要一个即可,为什么有两个,因为我们tracker也是可以搞集群的,可以配置多个,这里我们直接写我们tracker追踪服务器ip地址,如果是同一台服务器写本机ip即可,端口不变!!!】
保存并退出即可!!!创建我们刚才自定义的目录
mkdir -p /fastdfs/storage/basemkdir -p /fastdfs/storage/store
我们安装的时候也修改了目录,所以我们这里也要修改一下
vim /etc/init.d/fdfs_storaged
修改PRG=/usr/local/bin/fdfs_storaged
5.3.启动storage
保存并退出,启动storage
/etc/init.d/fdfs_storaged start
查看状态
/etc/init.d/fdfs_storaged status
停止
/etc/init.d/fdfs_storaged stop
重启
/etc/init.d/fdfs_storaged restart
开启启动我们进入文件
vim /etc/rc.d/rc.local
添加启动文件启动之后我们可以去看看我们刚才创建的两个目录
cd /fastdfs storage/
base目录【基础数据目录】可以看到有一个data和logs 基础数据和日志可以看到有对应的一个日志进入data目录里面有对应我们storage的一个进程号,然后启动的一个数据和**同步相应的相关信息 **我们回去看我们store
cd ../../store
这里也有一个data,这个data存放我们上传文件的目录我们发现这边使用16进制,从00一直到FF,一共是256个目录,然后我们每一个目录下面还有256个子目录我们cd 00 再 ls我们发现还是有 00 -FF 256个目录再00就没了!!!再往下这边就会存放我们的文件了,至于我们文件上传上来之后会存放在那个目录下面,这个不需要我们去关系,storage会自己去存放,并且我们的tracker会去把我们的一个完整的卷名加文件名,这个文件名就是从data到00再到00再到下面的具体文件名一整个完整路径返回给我们,我们直接能拿到,它具体存放在着256个目录那个目录这个不需要我们操心,这是storage自定义去完成的,我们这里storage开机启动前提是必须先启动tracker再启动storage,不然会报错,因为我们storage配置文件里面配置了tracker_server,所以我们这边如果想要stroage开机自启,必须设置tracker也开机自启,不然不建议storage自启!!!
6.Client配置【可选】
客户端配置不是必需的,因为客户端配置完相当于用命令行去测试我们的fastdfs,所以我们如果不准备用命令行测试,而是准备用代码测试的话,我们完全可以跳过!!!我们可以把Client放在tracker和storage任意服务器下,并不影响,把它配置改一下即可!!!
6.1.拷贝client.conf.sample
首先进入我们配置模板
cd /etc/fdfs
我们可以看到这边有一个client.conf.sample同样,我们先拷贝
cp client.conf.sample client.conf
6.2.配置client.conf
进入配置文件
vim client.conf
可以看下它的属性,更改的地方加粗
- connect_timeout =5 连接超时
- network_timeout = 60 网络超时
- base_path = /home/yuqing/fastdfs 基础目录 【我们更改一些,/fastdfs/client,这里也是放客户端运行所产生的一些相应的数据】
- tracker_server = 192.168.0.196:22122【我们之前说过,我们如果是客户端进行一个上传和下载的话,中间都要经过一个tracker,我们上传的时候呢,根据tracker找到storage的ip和端口进行上传,如果我们是下载的话,客户端也需要通过我们的tracker找到我们的一个ip和端口进行一个下载,所以我们这边要配置一个tracker,改成本机地址 192.168.248.101】
到这里,client配置就完成了,我们保存并退出,创建刚刚自定义的目录
mkdir -p /fastdfs/client
6.3.上传文件
我们看到root目录下面有个图片,我们可以把这个上传上去怎么去上传呢?上传的话因为我们安装的时候改过对应的一个目录,我们的命令目录呢在/usr/local/bin我们上传就是fdfs_upload_file
./fdfs_upload_file /etc/fdfs/client.conf ~/cat-114782_640 (1).jpg
上传命令+客户端配置+上传的文件返回上传的完整卷名加文件名,它的文件名是重新命名的
- group1是我们storage里的配置,配的一个卷名的名称
- M00 这是一个虚拟目录
- 00和00表示它放在data-00-00目录下,当然一般情况下是按照顺序保存的,但我们也不能完全保证,storage有自己的一个规则,上传到哪一个目录下面去
- wKj4ZWQJdC-AOV6HAAKSU_3XkA0484.jpg 文件名 重新命名了,防止文件名重复!!!
我们可以进入storage存放文件目录去查看我们上传的文件
小结
我们要记住M00是一个虚拟目录,有点相当于我们windows的快捷方式,它的引用主要是引用到我们的data目录下面,data就对应M00,快捷方式的一个意思
6.4.删除文件
./fdfs_delete_file /etc/fdfs/client.conf group1/M00/00/00/wKj4ZWQJdC-AOV6HAAKSU_3XkA0484.jpg
删除命令+client配置文件+完整的卷名和文件名这里要注意,因为我们现在操作没有ip和端口,所以我们需要根据追踪器,根据这一整串完整的文件名去获取stroage的ip和端口,才会去进行一个删除这也就是我们client为什么要去配置tracker服务器这样则表示删除成功我们可以进入data-00-00查看是否删除成功我们发现后最484的文件名没有了
7.安装nginx和fastdfs_nginx_module
为什么要安装nginx呢?因为我们fastdfs是一个文件系统,那可以存放很多类型的文件,比如说存放图片或者其它一个类型,图片的话我们可以通过网页直接去预览,而不需要通过我们现在这个操作,通过client拿到一个完整的卷名加文件名,通过tracker拿到对应的ip地址和端口再去下载预览,太麻烦,我们可以通过http协议直接在我们的url里面输入我们的一个ip端口,卷名文件名直接在浏览器里面可以访问这张图片或预览,那这个时候呢我们fastdfs没法实现,就要安装fastdfs_module_nginx和nginx进行代理
7.1.安装组件fastdfs-nginx-module
解压
tar zxvf fastdfs-nginx-module-1.22
cd 进入目录
- HISTOPY 历史文件
- INSTALL 安装
- src 源码
我们来到src源码目录源码目录有相应的一个配置,我们需要修改相应的一个配置
vim config
为什么要改配置呢?因为我们去安装我们的组件之后呢,会去安装我们的nginx,安装nginx的时候需要把我们的一个module模块加进去,加进去之后会寻找我们的一个fastdfs对应的一个安装的目录,如果目录不正确可能就安装失败了!!!所以我们需要去更改我们的一个目录路径**修改的时候也有区别,因为我们装fastdfs的时候修改了我们安装目录,所以我们这串目录是改过目录之后的,如果我们没有修改过fastdfs的安装目录的,我们改的是另一个目录 **
/usr/local/include/fastdfs /usr/include/fastcommon/
fastdfs位置+核心库,这两个改呢,是因为我们安装改过,如果没有的话呢,我们改的不是这两个,就应该是把local删掉就行了保存并退出,我们的模块就改好了,改好了之后并不代表它已经安装了,怎么去安装呢,就是我们去安装nginx的时候,把模块添加上去即可,所以我们还要去安装我们的nginx
7.2.安装nginx
依赖安装之前我们还需要安装对应依赖
yum install -y gcc gcc-c++ make automake autoconf libtool pcre pcre-develzlib zlib-developenss1 openss1-devel
解压nginx
tar -zxvf nginx-1.16.1.tar.gz
查看目录我们需要去更改一下它的目录更改目录先准备一个目录
mkdir -p /var/temp/nginx
配置nginx安装信息
./configure \--prefix=/usr/local/nginx \--pid-path=/var/run/nginx/nginx.pid \--lock-path=/var/lock/nginx.lock \--error-log-path=/var/log/nginx/error.log \--http-log-path=/var/log/nginx/access.log \--with-http_gzip_static_module \--http-client-body-temp-path=/var/temp/nginx/client \--http-proxy-temp-path=/var/temp/nginx/proxy \--http-fastcgi-temp-path=/var/temp/nginx/fastcgi\--http-uwsgi-temp-path=/var/temp/nginx/uwsgi \--http-scgi-temp-path=/var/temp/nginx/scgi \--add-module=/usr/local/fastdfs/fastdfs-nginx-module-1.22/src
- --prefix=/usr/local/nginx \ 安装路径
下面这些信息放入我们刚才创建的文件
- --http-client-body-temp-path=/var/temp/nginx/client \
- --http-proxy-temp-path=/var/temp/nginx/proxy \
- --http-fastcgi-temp-path=/var/temp/nginx/fastcgi\
- --http-uwsgi-temp-path=/var/temp/nginx/uwsgi \
- --http-scgi-temp-path=/var/temp/nginx/scgi \
- --add-module=/usr/local/fastdfs/fastdfs-nginx-module-1.22/src【这个是必须定义的,这个是我们安装nginx的时候需要加载的一个模块,如果没有指定的话呢,nginx安装过程中呢,就不会去加载fastdfs_nginx_module模块,如果nginx配置文件添加了这个模块,nginx启动会报错,后续功能无法实现!!!】
这样我们nginx安装信息就配置完成接下来我们就可以进行安装了预编译
make
编译加安装
make install
8.配置nginx模块
8.1.拷贝mod_fastdfs.conf
同理跟上面操作一样我们先把fastdfs_nginx_module模块配置文件修改
cd /usr/local/fastdfs/fastdfs-nginx-module-1.22/src/
这里面有一个我们对应的一个配置文件mod_fastdfs.conf
cd mod_fastdfs.conf /etc/fdfs
然后我们进行一个相应的修改
8.2.修改mod_fastdfs.conf
- connect_timeout = 2 连接超时 ,可改可不改,改成10
- network_timeout = 30 网络超时
- base_path 基础目录
- tracker_server = 192.168.248.101:22122【这里也需要我们tracker服务器,改成本机跟踪服务器ip即可】
- storage_server_port = 23000 端口号
- url_have_group_name = false 【表示我的url中是否要包含group名字,这里我们改成true
- 包含即可 】
- store_path0 = /home/yuqing/fastdfs【我们这里要改成我们之前存放文件的目录 /fastdfs/store】
保存并退出
8.3.拷贝http配置
这两个文件是因为我们nginx在网页上直接看图片的时候呢,是用的一个http协议,所以我们需要去拷贝一下http
cp /usr/local/fastdfs/fastdfs-6.06/conf/http.conf /etc/fdfs/
请求头
cp /usr/local/fastdfs/fastdfs-6.06/conf/mime.types /etc/fdfs/
8.4.创建nginx
8.4.1.启动软连接
因为我们nginx启动的时候呢,会在默认的/usr/lib64目录下面去查找所需的so文件,那因为我们安装fastdfs的时候呢,已经修改了它的目录,所以我们一定要创建它的软链接,不然会失败
ln-s /usr/local/lib64/libfdfsclient.so /usr/lib64/libfdfsclient.so
8.4.2.网络服务启动软链接
这个软链接呢,我们的追踪服务返回的里面有一个M00,这个M00其实就是一个data,它是相当于windows的一个快捷方式,那我们需要把这个M00指定到data下面
ln -s /fastdfs/storage/store/data//fastdfs/storage/store/data/M00
我们可以去看一下我们创建的软连接是否生效
cd /fastdfs/storage/store/data
我们可以看到这边有个M00它指定的目录呢就是我们data目录下
9.配置nginx
9.1.修改nginx.conf
cd /usr/local/nginx/
进入conf目录
cd conf
修改nginx.conf
vim nginx.conf
修改哪些东西呢?
- **user nobby **【因为是学习原因,我们这边直接配置root用户即可,正式工作中肯定是根据实际情况来决定的,我们user改成root用户意思是nginx访问alias必须要有文件系统的权限,那我们改成root就表示文件系统的一个权限是root用户的一个权限,如果我们不开启这个可能会出现404错误,那实际工作中,我们肯定不可能把这个root用户权限放出来,可能会自己创建一个用户指派一些权限,所以我们基于学习的一个目的直接开放即可!!!】
- listen:8888【为什么是8888呢,我们cd /etc/fdfs,vim storage.conf,这边有一个htpp.server_port端口,它这边就是8888,如果我们这边改了,我们对应nginx配置文件也需要更改!!!】
加入以下配置
location ~/group[0-9]/M00{ ngx_fastdfs_module;}
- ~/group[0-9]表示我们group0-9组都可以
- M00表示data
- ngx_fastdfs_module 是我们fastdfs_nginx_module配置的nginx模块
当我们去访问8888端口时候呢,它就会找到我们的本地的group0/M00,然后通过我们的ngx_fastdfs_module这样的一个模块去找到我们storage上面存储的一个对应文件就可以预览了!!!
9.2.重启storage并启动nginx
保存并退出,我们nginx配置就完成了,我们需要重启一下我们的storage并且要启动nginx
/etc/init.d/fdfs_storaged restart
我们来到nginx-sbin目录启动nginx
./nginx
看到端口表示启动成功!!!查看启动状态
ps -ef |grep nginx
9.3.上传图片通过网页访问
我们通过客户端上传图片
/usr/local/bin/fdfs_upload_file /etc/fdfs/client.conf cat-114782_640.jpg
同理,**上传命令+client配置+文件名即可**查看上传的文件
cd /fastdfs/storage/store/data/00/00/
根据tracker给出的路径通过网页进行访问,ip+端口+卷名和文件名
10.Java API
10.1.依赖
org.csource fastdfs-client-java 1.30-SNAPSHOT
注意:这边我们maven仓库是没有这个依赖的,我们需要自己去下载然后安装到仓库
- 首先确保我们环境依赖没有问题
- 获取fastdfs依赖https://gitcode.net/mirrors/happyfish100/fastdfs-client-java?utm_source=csdn_github_accelerator
- 进入解压后的fastdfs-client-java-master文件夹,并在此路径打开cmd命令行窗口,执行命令mvn clean install
- 这里安装在了我本地的maven仓库里,如果没有配置maven本地仓库,那么他会默认下载到默认的本地仓库C:${user.home}.m2\repository
- 这是本地已经安装好的fastdfs-client-java-master依赖
- 然后重新加载maven依赖就可以啦,注意版本要对应
10.2.常用类
10.2.1.CLientGlobal【加载配置文件】
用于加载配置文件的公共客户端工具。常用方法:
- init(String conf_filename);根据配置文件路径及命名,加载配置文件并设置客户端公共参数,配置文件类型为conf文件。可以使用绝对路径或相对路径加载。
- initByProperties(Properties props);根据Properties对象设置客户端公共参数。
注意:使用conf或prooerties进行客户端参数配置时,参数key命名不同。
10.2.2.TrckerClient【跟踪器客户端类型】
跟踪器客户端类型。创建此类型对象时,需传递跟踪器组,就是跟踪器的访问地址信息。无参构造方法默认使用ClientGlobal_tracker_group 常量作为跟踪器组来构造对象。【就是我们上面ClientGlobal定义的常量】创建对象的方式为:
new TrackerClient();或new TrackerClient(ClientGlobal.g_tracker_group)
10.2.3.TrackerServer【跟踪器服务类型】
跟踪器服务类型,此类型的对象是通过跟踪器客户端对象创建的。实质上就是一个FastDFS Tracker Server的链接对象。是代码中与Tracker Server链接的工具构建对象的方式为:
trackerClient.getTrackerServer();
10.2.4.StorageServer【存储服务类型】
存储服务类型。此类型的对象是通过跟踪器客户端对象创建的。实质上就是一个FastDFS Storage Server的链接对象。是代码中与StorageServer链接的工具。获取具体存储服务链接,是由Tracker Server分配的,所以构建存储服务对象时,需要依赖跟踪器服务对象构建对象的方式为:
trackerClient.getStorage(trackerServer);
10.2.5.StorageClient【真正去操作文件的客户端类型,需要传入tracker和storage】
存储客户端类型,此类型对象是通过构造方法创建的。创建时,需传递跟踪服务对象和存储服务对象。此对象实质上就是一个访问FastDFS Storage Server的客户端对象,用于实现文件的读写操作创建对象的方式为:
new StorageClient(trackerServer,storageServer);
常用方法有:
- upload_file(String local_filename【文件路径】,String file_ext_name【文件扩展名,如果我们这里传null的话,那我们fastdfs就会自动解析文件扩展名,自动找.后面的一串】,NameValuePair[] meta_list【文件属性集合】);上传文件的方法,参数local_filename为要上传的本地文件路径及文件名,可使用绝对路径或相对路径;参数file_ext_name为上传文件的扩展名,如果扩展null,则自动解析文件扩展名;参数meta_list是用于设置上传文件的源数据,如上传用户、上传描述等。
- download_file(String group_name【卷名】,String remote_file_name【下载的文件名】);下载文件的方法,参数group为组名/卷名,就是Storage Server中/etc/fdfs/storage.conf配置文件中配置的group_name参数值,也是要下载的文件所在组/卷的命名;参数remote_file_name为要下载的文件路径及文件名。
- delete_file(String group_name【卷名】,String remote_file_name【删除的文件名】);删除文件的方法,参数含义同download_file方法参数
11.Demo
11.1.创建项目
创建fastdfsdemo,加上web和thymeleaf依赖
11.2.导入相关依赖
org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-web org.csource fastdfs-client-java 1.30-SNAPSHOT org.springframework.boot spring-boot-starter-test test
11.3.创建fdfs_client.conf配置文件
#连接超时connect_timeout = 2#网络超时network_timeout = 30#编码格式charset = utf-8#tracke端口号http.tracker_http_port = 8888#防盗链功能http.anti_steal_token = no#秘钥http.secret_key = FastDFS1234567890#tracker ip :端口号tracker_server = 192.168.248.101:22122#连接池配置connection_pool.enabled = trueconnection_pool.max_count_per_entry = 500connection_pool.max_idle_time = 3600connection_pool.max_wait_time_in_ms = 1000
11.4.加载配置文件,生成服务端和客户端
//日志private static Logger logger = LoggerFactory.getLogger(FastDFSClient.class);//初始化FastDFS,ClientGlobal.init方法会读取配置文件,并初始化对应的属性static { try { String filePath = new ClassPathResource("fdfs_client.conf").getFile().getAbsolutePath(); ClientGlobal.init(filePath); } catch (IOException | MyException e) { logger.error("FastDFS Client init Fail!", e); e.printStackTrace(); }}/*** 生成Storage客户端* @return* @throws IOException*/private static StorageClient getStorageClient() throws IOException { TrackerServer trackerServer = getTrackerServer();//我们可以通过trackerServer拿到StorageServer,不设置也能拿到StorageClientreturn new StorageClient(trackerServer, null);}/*** 生成Tracker服务器端* @return* @throws IOException*/private static TrackerServer getTrackerServer() throws IOException { TrackerClient trackerClient = new TrackerClient();return trackerClient.getTrackerServer();}
11.5.上传、下载、删除、查看文件
/** * 上传 * * @param file 文件对象 * @return */ private static String[] upload(FastDFSFile file) { //打印相关信息 logger.info("File Name:" + file.getName() + ",File lenght" + file.getContent().length); //文件属性信息 NameValuePair[] meta_list = new NameValuePair[1]; meta_list[0] = new NameValuePair("author", file.getAuthor()); //上传时间 long startTime = System.currentTimeMillis(); //返回一个String数组 String[] uploadResults = null; StorageClient storageClient = null; try { //拿到客户端 storageClient = getStorageClient(); //文件数组;后缀名;文件属性相关数组 uploadResults = storageClient.upload_file(file.getContent(), file.getExt(), meta_list); } catch (IOException | MyException e) { e.printStackTrace(); //上传失败打印文件名 logger.error("上传失败 File Name:" + file.getName(), e); } //上传时间 logger.info("上传时间:" + (System.currentTimeMillis() - startTime + "ms")); //验证上传结果 if (uploadResults == null && storageClient != null) {//判断上传结果是否为空 logger.error("上传失败" + storageClient.getErrorCode());//上传失败拿到错误结果 } //上传成功会返回相应信息 logger.info("上传成功,group_name:" + uploadResults[0] + "remoteFileName:" + uploadResults[1]);//打印卷名和完整名字 return uploadResults; } /** * 下载文件 * * @param groupName 卷 * @param remoteFileName 完整文件名 * @return */ public static InputStream downFile(String groupName, String remoteFileName) { try { //创建storage客户端对象 StorageClient storageClient = getStorageClient(); //调用下载方法,返回一个数组 byte[] fileByte = storageClient.download_file(groupName, remoteFileName); //输入要下载的文件 InputStream ins = new ByteArrayInputStream(fileByte); return ins; } catch (IOException | MyException e) { logger.error("下载失败", e); } return null; } /** * 删除文件 * * @param groupName 卷 * @param remoteFileName 完整文件名 */ public static void deleteFile(String groupName, String remoteFileName) { try { StorageClient storageClient = getStorageClient(); int i = storageClient.delete_file(groupName, remoteFileName); logger.info("删除成功" + i); } catch (IOException | MyException e) { logger.error("删除失败", e); } } /** * 查看文件信息 * @param groupName 卷名 * @param remoteFileName 完整文件名 * @return */ public static FileInfo getFile(String groupName, String remoteFileName) { try { StorageClient storageClient = getStorageClient(); //获取文件方法 FileInfo file_info = storageClient.get_file_info(groupName, remoteFileName); return file_info; } catch (Exception e) { logger.error("查看文件信息失败", e); } return null; }
11.6.网页查看图片方法
/** * 获取文件路径,上传至后返回的完整路径 * @return * @throws IOException */ public static String getTrackerUrl() throws IOException { return "http://"+getTrackerServer().getInetSocketAddress().getHostString()+":8888/"; }
11.7.完整代码
package com.zhang.fastdfsdemo.utils;import com.zhang.fastdfsdemo.pojo.FastDFSFile;import org.csource.common.MyException;import org.csource.common.NameValuePair;import org.csource.fastdfs.*;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.core.io.ClassPathResource;import java.io.ByteArrayInputStream;import java.io.IOException;import java.io.InputStream;/** * @author zb * @version 1.0 * @date 2023/3/9 18:22 * @ClassName FastDFSClient * @Description FastDFS工具类 * StorageServer也可以通过Tracker拿到,我们直接那StorageClient即可, * 因为我们后期操作我们的文件,不管是上传、下载、删除都是通过我们storageClient进行操作的 */public class FastDFSClient { //日志 private static Logger logger = LoggerFactory.getLogger(FastDFSClient.class); //初始化FastDFS,ClientGlobal.init方法会读取配置文件,并初始化对应的属性 static { try { String filePath = new ClassPathResource("fdfs_client.conf").getFile().getAbsolutePath(); ClientGlobal.init(filePath); } catch (IOException | MyException e) { logger.error("FastDFS Client init Fail!", e); e.printStackTrace(); } } /** * 生成Storage客户端 * * @return * @throws IOException */ private static StorageClient getStorageClient() throws IOException { TrackerServer trackerServer = getTrackerServer(); //我们可以通过trackerServer拿到StorageServer,不设置也能拿到StorageClient return new StorageClient(trackerServer, null); } /** * 生成Tracker服务器端 * * @return * @throws IOException */ private static TrackerServer getTrackerServer() throws IOException { TrackerClient trackerClient = new TrackerClient(); return trackerClient.getTrackerServer(); } /** * 上传 * * @param file 文件对象 * @return */ private static String[] upload(FastDFSFile file) { //打印相关信息 logger.info("File Name:" + file.getName() + ",File lenght" + file.getContent().length); //文件属性信息 NameValuePair[] meta_list = new NameValuePair[1]; meta_list[0] = new NameValuePair("author", file.getAuthor()); //上传时间 long startTime = System.currentTimeMillis(); //返回一个String数组 String[] uploadResults = null; StorageClient storageClient = null; try { //拿到客户端 storageClient = getStorageClient(); //文件数组;后缀名;文件属性相关数组 uploadResults = storageClient.upload_file(file.getContent(), file.getExt(), meta_list); } catch (IOException | MyException e) { e.printStackTrace(); //上传失败打印文件名 logger.error("上传失败 File Name:" + file.getName(), e); } //上传时间 logger.info("上传时间:" + (System.currentTimeMillis() - startTime + "ms")); //验证上传结果 if (uploadResults == null && storageClient != null) {//判断上传结果是否为空 logger.error("上传失败" + storageClient.getErrorCode());//上传失败拿到错误结果 } //上传成功会返回相应信息 logger.info("上传成功,group_name:" + uploadResults[0] + "remoteFileName:" + uploadResults[1]);//打印卷名和完整名字 return uploadResults; } /** * 下载文件 * * @param groupName 卷 * @param remoteFileName 完整文件名 * @return */ public static InputStream downFile(String groupName, String remoteFileName) { try { //创建storage客户端对象 StorageClient storageClient = getStorageClient(); //调用下载方法,返回一个数组 byte[] fileByte = storageClient.download_file(groupName, remoteFileName); //输入要下载的文件 InputStream ins = new ByteArrayInputStream(fileByte); return ins; } catch (IOException | MyException e) { logger.error("下载失败", e); } return null; } /** * 删除文件 * * @param groupName 卷 * @param remoteFileName 完整文件名 */ public static void deleteFile(String groupName, String remoteFileName) { try { StorageClient storageClient = getStorageClient(); int i = storageClient.delete_file(groupName, remoteFileName); logger.info("删除成功" + i); } catch (IOException | MyException e) { logger.error("删除失败", e); } } /** * 查看文件信息 * @param groupName 卷名 * @param remoteFileName 完整文件名 * @return */ public static FileInfo getFile(String groupName, String remoteFileName) { try { StorageClient storageClient = getStorageClient(); //获取文件方法 FileInfo file_info = storageClient.get_file_info(groupName, remoteFileName); return file_info; } catch (Exception e) { logger.error("查看文件信息失败", e); } return null; } /** * 获取文件路径,上传至后返回的完整路径 * @return * @throws IOException */ public static String getTrackerUrl() throws IOException { return "http://"+getTrackerServer().getInetSocketAddress().getHostString()+":8888/"; }}
11.8.前端测试
我们写一个contrller测试即可,service我们不写直接跳过,正常情况下应该要写service,service调我们工具类方法,contrller再调service,我们这里只是演示!!!controller
package com.zhang.fastdfsdemo.controller;import com.zhang.fastdfsdemo.pojo.FastDFSFile;import com.zhang.fastdfsdemo.utils.FastDFSClient;import org.csource.common.MyException;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.multipart.MultipartFile;import org.springframework.web.servlet.mvc.support.RedirectAttributes;import java.io.IOException;import java.io.InputStream;/** * @author zb * @version 1.0 * @date 2023/3/9 21:12 * @ClassName FileContrller * @Description TODD 测试文件上传、下载、删除、查看 */@Controllerpublic class FileContrller { private static Logger logger = LoggerFactory.getLogger(FileContrller.class); /** * 上传文件 * 我们现在入参是 MultipartFile ,但我们真正要传进去的fastdfs file,那我们可以把它提取出来调用 * * @param file * @param redirectAttributes * @return */ @PostMapping("upload") public String singFile(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) { if (file.isEmpty()) {//判断上传文件是否为空 redirectAttributes.addFlashAttribute("message", "请选择一个文件上传"); return "redirect:uploadStatus"; } //拿到客户端 try { //上传文件,拿到返回文件路径 String path = saveFile(file); redirectAttributes.addFlashAttribute("message", "上传成功" + file.getOriginalFilename()); redirectAttributes.addFlashAttribute("path", "路径:" + path); } catch (IOException | MyException e) { e.printStackTrace(); } return "redirect:uploadStatus"; } /** * 跳转上传文件状态也 * * @return */ @GetMapping("/uploadStatus") public String uploadStatus() { return "uploadStatus"; } /** * 跳转文件上传页 * * @return */ @GetMapping("/") public String upload() { return "upload"; } /** * MultipartFile转fastdfsfile * * @param multipartFile * @return * @throws IOException */ public String saveFile(MultipartFile multipartFile) throws IOException, MyException { String[] fileAbsolutePath = {}; String fileName = multipartFile.getOriginalFilename();//获取文件名 String ext = fileName.substring(fileName.lastIndexOf(".") + 1);//截取后缀 byte[] file_buff = null; InputStream inputStream = multipartFile.getInputStream(); if (inputStream != null) { int len1 = inputStream.available();//拿到长度 file_buff = new byte[len1]; inputStream.read(file_buff); } inputStream.close(); FastDFSFile file = new FastDFSFile(fileName, file_buff, ext);//转成fastdfs文件类型 //上传,//返回组名和文件名 fileAbsolutePath = FastDFSClient.upload(file); if (fileAbsolutePath == null) { logger.error("上传失败"); } String path = FastDFSClient.getTrackerUrl() + fileAbsolutePath[0] + "/" + fileAbsolutePath[1];//返回完整路径 return path; }}
html上传文件
Spring Boot file upload example
上传状态
Title SpringBoot - Upload Status
测试上传成功流览图
关键词:
爆肝两万字,详解fastdfs分布式文件系统
初识rollup 打包、配置vue脚手架
世界热议:62.类模板
当前热文:C语言——可变参函数
陕西招聘会现3万月薪岗位学生排长龙 招聘人员:半天收简历150份
当前动态:供不应求!真我GT Neo5 1TB版真香:二手用户也抢着要
“山药成了精”?男子买到奇葩山药外形酷似人脚掌
每日视点!快速读懂Redis分布式锁的实现和原理
美少女三消游戏 《Mirror 2: Project X》开发组宣布破产解散
酒店回应到211大学招服务员:符合流程 面向所有高校毕业生
快看点丨京东CEO徐雷:百亿补贴效果超过预期、要做天天低价
【新要闻】比亚迪加入降价大军!宋Pro DM-i限时优惠:88元折扣6888元
焦点简讯:耳机煲机一般要煲多久_耳机煲机方法是什么?买回来新耳机要怎样煲?耳机要煲多久?
世界热点评!前端如何相对优雅管理api
全球速看:浙四医院官网招聘2021_浙四医院官网
环球观点:京东2022年收入超1万亿:“百亿补贴”会一直有!
简约时尚 健身备一件:361°新款轻薄速干衣39元冲量
越来越卖不动了!最畅销十款数码相机一览:索尼成赢家 第一性价比绝
全球通讯!Intel的大小核CPU架构:最终还是把一些老游戏坑了
往返近1000元!景区回应坐滑竿上山按斤收费:网友力挺明码标价
天天实时:【翻译】发布 .NET 8 Preview 1
前沿资讯!海洋风筝爆火 一周暴涨498%超过秋裤!网友:春天的信号
天天消息!甜香丝滑 旺旺邦德低脂轻乳咖啡官方狂促:合2元一瓶
《银河护卫队3》导演回击网友言论:确定选角不因其是黑人
世界热议:东风系引发车市价格大战!纯电宝马i3终端大促销:最大降幅超10万
每日热点:日本原药温和驱蚊配方:超威电热蚊香液3瓶1器14.9元发车
快讯:uni-popup 遮不住头部标题的解决办法
环球焦点!网友晒空荡新房 各大品牌疯狂随份子 开局一套房其他全靠送
快播:女子在地铁上脱鞋抠脚死皮掉一地 杭州地铁回应:列车到站会打扫
世界速递!为拯救者Y9000P 2023量身打造:联想推出新140W氮化镓适配器
每日看点!富士康否认清退临时工拆除流水线:运转正常
全球即时:美国一特斯拉撞上消防车造成一死一伤 现场惨烈!调查结果让人心痛
Envisics获得5000万美元C轮融资
当前滚动:(数据库系统概论|王珊)第十一章并发控制-第二、三、四节:封锁、封锁协议活锁和死锁
每日焦点!「中华田园敏捷开发」,是老板无能还是程序员无力?
【世界聚看点】探究SMC局部代码加密技术以及在CTF中的运用
记录--Vue自定义指令实现加载中效果v-load(不使用Vue.extend)
【焦点热闻】十分钟读懂火山引擎 DataLeap 数据治理实践
老头环壶头哥:击败女武神超4千次 期待DLC到来
天天资讯:儿子沉迷手机爸爸帮请假“逼”他连玩17小时:效果很好
焦点消息!供应iPhone 15的OLED屏幕漏光?国产面板一哥京东方回应:不予评价
当前关注:纵享丝滑 回味愉悦:德芙巧克力37.5元/斤(官价5折)
每日速看!成龙进组20天把半年的封闭用完了!新电影《龙马精神》4月上映:有吴京参演
每日视点!通过案例讲解python循环语句
【全球独家】基于应用理解的协议栈优化
关注:串口登录提示"Login incorrect"
热推荐:节能降耗 | AIRIOT智慧电力综合管理解决方案
实时:C++笔记--函数、预处理
【天天播资讯】为啥人一上车就爱睡觉?原来是被“催眠”了
全球观察:95后大厂女生裸辞开麻将馆当保洁:很享受自由和成就感
全球今日报丨2030年前后我国将实施火星采样返回:难度很大
【全球播资讯】特斯拉中国2月销量出炉 比亚迪能打5个特斯拉
航班晚点 山航机长提速帮乘客5分钟极限转机:提前20分钟到达
速递!首钢股份:2月重点产品产量同比均提升 预计国内钢材价格短期震荡偏强
焦点播报:Python常见面试题012. 可迭代对象和迭代器有啥区别?
从5分钟到60秒,袋鼠云数栈在热重启技术上的提效探索之路
世界观天下!我的脑内恋碍选项第二季会出吗_我的脑内恋碍选项第二季
【天天报资讯】ChatGPT火出圈!人工智能工程师平均招聘月薪突破2.5万
环球热文:三星推出冰淇淋主题键鼠套装:薄荷配色如此清凉
集成ChatGPT威力显著:微软Bing日活跃首次破亿
每日快报!3月罕见!郑州今日冲上30°C高温:成北方首个30℃+省会级城市
世界球精选!RTX 50显卡有望使用 GDDR7显存验证方案来了:狂飙36Gbps
无界生态发布会即将举办 焕新后的星纪魅族将带来哪些惊喜?
java代码审计-XSS
全球今日讯!vue中将base64流数据转成pdf文件可打印
环球速讯:1 MySql基础介绍
世界观焦点:英媒:多特蒙德中场贝林厄姆将决定自己的未来
观察:今年五一档已定档10部新片 王一博《长空之王》超65万人想看
天天热讯:海上巨无霸 我国自主研制的全球最大集装箱船今天交付
全球讯息:日本H3火箭首秀失败初步调查结果公布:电源系统异常
重获新“声”!清华开发出可穿戴人工喉咙:还原准确率超90%
天天热点评!美国一邮轮300多人患病呕吐腹泻:疑似感染诺如病毒
React Hooks源码深度解析
当前热文:虹科分享 | B站崩了怎么办?Redis企业版数据库多云战略分析
每日速读!Math.atan2求角度解析
快速打包、发布和管理应用——AppUploader工具介绍
星纪魅族集团宣布成立 全面推行新战略,新理念
热资讯!说唱女歌手转行卖烧烤年入百万 直言:梦想和生活不矛盾
全球观察:索尼α7 IV发布2.00版本固件:增加Creators' App支持
全风化和强风化哪个硬?全风化和强风化的区别是什么?
牛转乾坤是什么意思?牛转乾坤放在家里哪个位置?
离不开你是哪部电视剧主题曲?离不开你的原唱是谁?
惊艳世俗的名字有哪些?惊艳世俗的小说句子有哪些?
卧铺有充电的地方吗?卧铺补票怎么补怎么收费?
每日关注!C++笔记--数组、函数、预处理
索爱mk16i支持通话录音吗?索爱mk16i拍照效果怎么样?
双胡须造型着实罕见!腾势N7申报:首款激光雷达比亚迪来了
网易3A级赛车手游《巅峰极速》今日首测 全球超百款跑车正版授权
汽车和小孩被劫 定位要先续流量费!大众汽车宣布重大决定
讯息:股价涨停 中国上市公司永鼎回应美国室温超导技术:目前不可行
当前看点!财运好的男人手相 什么手相最有财运
IM通讯协议专题学习(六):手把手教你如何在Android上从零使用Protobuf
GO语言学习笔记-方法篇 Study for Go ! Chapter five - Method
每日看点!面试被问到了解哪些开发模型?看这一篇就够了!
热键是什么意思?热键冲突怎么解决?
微信怎么群发消息?微信怎么分组管理好友?
环球微资讯!绝无系统广告!蔚来手机真机曝光:神似索尼
每日看点!比亚迪F品牌越野SUV谍照曝光:对标奔驰大G 价格40万起
每天少睡一两个小时算熬夜?专家提醒:大脑会变笨
A4车主免费变成“A5”车主?奥迪做出重大决定