3 Hadoop-HDFS

目录

3.1 Apache版本Hadoop重新编译

3.1.1 为什么要编译Hadoop

3.1.2 编译环境的准备

3.2 Hadoop安装

3.2.1 上传apache Hadoop包并压缩

3.2.2 修改配置文件

3.2.3 配置Hadoop的环境

3.2.4 启动集群

3.3 Hadoop核心-HDFS

3.3.1 HDFS概述

3.3.2 HDFS应用场景

适合的应用场景

不适合的应用场景

3.3.3 HDFS架构

3.3.4 NameNode和DataNode

3.3.5 HDFS的副本机制和机架感知

3.3.6 HDFS的命令行使用

3.3.7 HDFS的高级使用命令

HDFS文件限额配置

hdfs的安全模式

3.3.8 HDFS基准测试

3.3.9 HDFS文件写入过程

3.3.10 HDFS文件读取过程

3.3.11 HDFS的元数据辅助管理

3.3.12 HDFS的API操作

配置Windows下Hadoop环境

导入 Maven 依赖

使用文件系统方式访问数据(掌握)

3.3.13 HDFS的高可用机制

HDFS高可用介绍

组件介绍

3.3.14 HDFS的联邦机制

Federation架构设计


侠义上来说,Hadoop就是单独指代Hadoop这个软件,

HDFS:分布式文件系统

MapReduce:分布式计算系统

Yarn:分布式集群资源管理

广义上来说,Hadoop指代大数据的一个生态圈,包括很多其他的软件。

3.1 Apache版本Hadoop重新编译

3.1.1 为什么要编译Hadoop

由于apache给出的Hadoop的安装包没有提供带c程序访问的接口,所以我们在使用本地库(本地库可以用来压缩,以及支持c程序等等)的时候就会出问题,需要对Hadoop源码包进行重新编译。

3.1.2 编译环境的准备

1准备Linux环境

准备一台linux环境,内存4G或以上,硬盘40G或以上。此处使用node03

2虚拟机联网、关闭防火墙,关闭selinux

关闭防火墙命令 systemctl stop firewalld

禁止开机自启动 systemctl disable firewalld

修改selinux的配置文件 vim /etc/selinux/config

3安装jdk1.7

注意Hadoop-2.7.5这个版本的编译,只能使用jdk1.7,如果使用jdk1.8就会报错。

查看centos自带的openjdk,如果有的话要将其卸载掉。

rpm -qa | grep java

显示没有

统一两个路径,已经建好

不能上传,则需要

yum -y install lrzsz

rz -E 将所有的包都上传

进行解压 tar -xvf jdk-7u75-linux-x64.tar.gz -C ../servers/

配置环境变量 vim /etc/profile

/export/servers/jdk1.7.0_75

source /etc/profile 生效

4安装maven

解压 tar -xvf apache-maven-3.0.5-bin.tar.gz -C ../servers/

配置maven的环境变量 vim /etc/profile

/export/servers/apache-maven-3.0.5

将以下三行命令添加到最后

export MAVEN_HOME=/export/servers/apache-maven-3.0.5

export MAVEN_OPTS="-Xms4096m -Xmx4096m"

export PATH=:$MAVEN_HOME/bin:$PATH

生效 source /etc/profile

解压maven仓库

tar -xvf mvnrepository.tar.gz -C ../servers/

修改maven的配置文件

/export/servers/mvnrepository

添加一个阿里云的镜像地址,会让我们下载jar包更快

<mirror>
  <id>central</id>
  <mirrorOf>central</mirrorOf>
  <name>aliyun</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>

还是在vim settings.xml里找到

5安装findbugs

解压 tar -xvf findbugs-1.3.9.tar.gz -C ../servers/

修改配置文件 vim /etc/profile

export FINDBUGS_HOME=/export/servers/findbugs-1.3.9

export PATH=:$FINDBUGS_HOME/bin:$PATH

source /etc/profile 生效

6在线安装一些依赖包

yum install autoconf automake libtool cmake

yum install ncurses-devel

yum install openssl-devel

显示已经安装就不用管了

yum install lzo-devel zlib-devel gcc gcc-c++

bzip2压缩需要的依赖包 yum install -y bzip2-devel

7安装protobuf

解压protobuf并进行编译

8安装snappy

9编译Hadoop源码

对源码进行编译 cd /export/softwares

进行解压 tar -xvf hadoop-2.7.5-src.tar.gz -C ../servers/

cd /export/servers/hadoop-2.7.5

编译支持snappy压缩:

mvn package -DskipTests -Pdist,native -Dtar -Drequire.snappy -e -X

编译时间的长短跟网速有很大关系 因为要下载大量的包

出错啦!!

终于成功了!

安装成功之后,会有一个这个安装包,这个安装包里有很多常用的压缩算法

3.2 Hadoop安装

安装Hadoop-集群规划

服务器IP

192.168.245.100

192.168.245.110

192.168.245.120

主机名

node01

node02

node03

NameNode

SecondaryNameNode

dataNode

ResourceManager

NodeManager

3.2.1 上传apache Hadoop包并压缩

解压命令

cd /export/softwares

rz -E

tar -xvf hadoop-2.7.5.tar.gz -C ../servers/

3.2.2 修改配置文件

准备工作

在控制台修改这么多配置文件很容易出错,并且很不方便。因此可以借用一个工具,Notepad++来远程登录到linux,然后通过这个工具来直接进行配置文件。

注:用utf-8编码,记得保存

修改core-site.xml

第一台机器执行一下命令:

cd /export/servers/hadoop-2.7.5/etc/hadoop/core-site.xml

<configuration>
	<!--  指定集群的文件系统类型:分布式文件系统 -->
	<property>
		<name>fs.default.name</name>
		<value>hdfs://node01:8020</value>
	</property>
	<!--  指定临时文件存储目录 -->
	<property>
		<name>hadoop.tmp.dir</name>
		<value>/export/servers/hadoop-2.7.5/hadoopDatas/tempDatas</value>
	</property>
	<!--  缓冲区大小,实际工作中根据服务器性能动态调整 -->
	<property>
		<name>io.file.buffer.size</name>
		<value>4096</value>
	</property>

	<!--  开启hdfs的垃圾桶机制,删除掉的数据可以从垃圾桶中回收,单位分钟 -->
	<property>
		<name>fs.trash.interval</name>
		<value>10080</value>
	</property>
</configuration>

3d5d913e5f914d1967f01f718c15b23b.png

修改hdfs-site.xml

<configuration>

	 <property>
			<name>dfs.namenode.secondary.http-address</name>
			<value>node01:50090</value>
	</property>

	<!-- 指定namenode的访问地址和端口 -->
	<property>
		<name>dfs.namenode.http-address</name>
		<value>node01:50070</value>
	</property>
	<!-- 指定namenode元数据的存放位置 -->
	<property>
		<name>dfs.namenode.name.dir</name>
		<value>file:///export/servers/hadoop-2.7.5/hadoopDatas/namenodeDatas,file:///export/servers/hadoop-2.7.5/hadoopDatas/namenodeDatas2</value>
	</property>
	<!--  定义dataNode数据存储的节点位置,实际工作中,一般先确定磁盘的挂载目录,然后多个目录用,进行分割  -->
	<property>
		<name>dfs.datanode.data.dir</name>
		<value>file:///export/servers/hadoop-2.7.5/hadoopDatas/datanodeDatas,file:///export/servers/hadoop-2.7.5/hadoopDatas/datanodeDatas2</value>
	</property>
	
	<!-- 指定namenode日志文件的存放目录 -->
	<property>
		<name>dfs.namenode.edits.dir</name>
		<value>file:///export/servers/hadoop-2.7.5/hadoopDatas/nn/edits</value>
	</property>
	

	<property>
		<name>dfs.namenode.checkpoint.dir</name>
		<value>file:///export/servers/hadoop-2.7.5/hadoopDatas/snn/name</value>
	</property>
	<property>
		<name>dfs.namenode.checkpoint.edits.dir</name>
		<value>file:///export/servers/hadoop-2.7.5/hadoopDatas/dfs/snn/edits</value>
	</property>
	<!-- 文件切片的副本个数-->
	<property>
		<name>dfs.replication</name>
		<value>3</value>
	</property>

	<!-- 设置HDFS的文件权限-->
	<property>
		<name>dfs.permissions</name>
		<value>false</value>
	</property>

	<!-- 设置一个文件切片的大小:128M-->
	<property>
		<name>dfs.blocksize</name>
		<value>134217728</value>
	</property>
</configuration>

 

修改hadoop-env.sh

/export/servers/jdk1.8.0_141

修改mapred-site.xml

对其进行重命名

<configuration>
	<!-- 指定分布式计算使用的框架是yarn -->
	<property>
			<name>mapreduce.framework.name</name>
			<value>yarn</value>
	</property>

	<!-- 开启MapReduce小任务模式 -->
	<property>
		<name>mapreduce.job.ubertask.enable</name>
		<value>true</value>
	</property>
	
	<!-- 设置历史任务的主机和端口 -->
	<property>
		<name>mapreduce.jobhistory.address</name>
		<value>node01:10020</value>
	</property>

	<!-- 设置网页访问历史任务的主机和端口 -->
	<property>
		<name>mapreduce.jobhistory.webapp.address</name>
		<value>node01:19888</value>
	</property>
</configuration>

 

修改yarn-site.xml

<configuration>
	<!-- 配置yarn主节点的位置 -->
	<property>
		<name>yarn.resourcemanager.hostname</name>
		<value>node01</value>
	</property>

	<property>
		<name>yarn.nodemanager.aux-services</name>
		<value>mapreduce_shuffle</value>
	</property>
	
	<!-- 开启日志聚合功能 -->
	<property>
		<name>yarn.log-aggregation-enable</name>
		<value>true</value>
	</property>
	<!-- 设置聚合日志在hdfs上的保存时间 -->
	<property>
		<name>yarn.log-aggregation.retain-seconds</name>
		<value>604800</value>
	</property>
	<!-- 设置yarn集群的内存分配方案 -->
	<property>    
		<name>yarn.nodemanager.resource.memory-mb</name>    
		<value>20480</value>
	</property>

	<property>  
        	 <name>yarn.scheduler.minimum-allocation-mb</name>
         	<value>2048</value>
	</property>
	<property>
		<name>yarn.nodemanager.vmem-pmem-ratio</name>
		<value>2.1</value>
	</property>

</configuration>

 

修改mapred-env.sh

/export/servers/jdk1.8.0_141

修改slaves

第一台机器执行以下命令:

mkdir -p /export/servers/hadoop-2.7.5/hadoopDatas/tempDatas

mkdir -p /export/servers/hadoop-2.7.5/hadoopDatas/namenodeDatas

mkdir -p /export/servers/hadoop-2.7.5/hadoopDatas/namenodeDatas2

mkdir -p /export/servers/hadoop-2.7.5/hadoopDatas/datanodeDatas

mkdir -p /export/servers/hadoop-2.7.5/hadoopDatas/datanodeDatas2

mkdir -p /export/servers/hadoop-2.7.5/hadoopDatas/nn/edits

mkdir -p /export/servers/hadoop-2.7.5/hadoopDatas/snn/name

mkdir -p /export/servers/hadoop-2.7.5/hadoopDatas/dfs/snn/edits

进行安装包的分发,因为第一台机子创建了,所以第二台第三台也得创建。

cd /export/servers/

scp -r hadoop-2.7.5 node02:$PWD

scp -r hadoop-2.7.5 node03:$PWD

在node02和node03查看

3.2.3 配置Hadoop的环境

三台机器都要进行配置Hadoop的环境变量

三台机器都执行一下命令:

vim /etc/profile

export HADOOP_HOME=/export/servers/hadoop-2.7.5

export PATH=:$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$PATH

直接添加到最后

生效 source /etc/profile

第二台第三台一样的操作,此处不再赘述。

3.2.4 启动集群

需要启动Hadoop集群,需要启动HDFS和YARN两个模块,注意,首次启动HDFS时,必须对其进行格式化操作,(后面就不需要格式化了,否则会造成文件丢失),本质上是一些需要清理和准备工作,因此此时的HDFS在物理上还是不存在的。

hdfs namenode -format 或者 hadoop namenode -format

准备启动

第一台机器执行以下操作

初始化

开启hdfs

查看node02

查看node03

关闭

开启yarn

查看node02

查看node03

启动历史记录

三个端口查看界面

打开浏览器

输入ip可以显示,但是输入主机名不能显示

192.168.245.100:50070

192.168.245.100:8088

192.168.245.100:19888

反思下为什么输入ip可以访问输入主机名就不行呢,于是我去检查了下虚拟机里hosts文件,是这样的,

但是本地没有域名映射,因此我在本地的hosts里也设置了域名映射,最后就成功了!!

查看hdfs 输入node01:50070

查看yarn集群 输入node01:8088

查看历史完成的任务 输入node01:19888

3.3 Hadoop核心-HDFS

3.3.1 HDFS概述

在现代的企业环境中,单机容量往往无法存储大量数据,需要跨机器存储。统一管理分布在

集群上的文件系统称为分布式文件系统

HDFS(Hadoop Distributed File System)是 Apache Hadoop 项目的一个子项目. Hadoop 非常

适于存储大型数据 (比如 TB 和 PB), 其就是使用 HDFS 作为存储系统. HDFS 使用多台计算机存

储文件, 并且提供统一的访问接口, 像是访问一个普通文件系统一样使用分布式文件系统。

3.3.2 HDFS应用场景

适合的应用场景

1)存储非常大的文件:这里非常大指的是几百M、G、或者TB级别,需要高吞吐量,对延时

没有要求

2)采用流式的数据访问方式: 即一次写入、多次读取,数据集经常从数据源生成或者拷贝一

次,然后在其上做很多分析工作 。

3)运行于商业硬件上: Hadoop不需要特别贵的机器,可运行于普通廉价机器,可以处节约成

4)需要高容错性

5)为数据存储提供所需的扩展能力

不适合的应用场景

1) 低延时的数据访问对延时要求在毫秒级别的应用,不适合采用HDFS。HDFS是为高吞吐数

据传输设计的,因此可能牺牲延时

2)大量小文件文件的元数据保存在NameNode的内存中, 整个文件系统的文件数量会受限

于NameNode的内存大小。 经验而言,一个文件/目录/文件块一般占有150字节的元数据内存

空间。如果有100万个文件,每个文件占用1个文件块,则需要大约300M的内存。因此十亿级

别的文件数量在现有商用机器上难以支持。

3)多方读写,需要任意的文件修改 HDFS采用追加(append-only)的方式写入数据。不支持

文件任意owset的修改。不支持多个写入器(writer)

3.3.3 HDFS架构

HDFS是一个 主/从(Mater/Slave)体系结构 ,

HDFS由四部分组成,HDFS ClientNameNodeDataNodeSecondary NameNode

1、Client:就是客户端。

1)文件切分。文件上传 HDFS 的时候,Client 将文件切分成 一个一个的Block,然后进行存

储。

2)与 NameNode 交互,获取文件的位置信息。

3)与 DataNode 交互,读取或者写入数据。

4)Client 提供一些命令来管理 和访问HDFS,比如启动或者关闭HDFS。

2NameNode:就是 master,它是一个主管、管理者。

1)管理 HDFS 的名称空间

2)管理数据块(Block)映射信息

3)配置副本策略

4)处理客户端读写请求。

3DataNode:就是SlaveNameNode 下达命令,DataNode 执行实际的操作。

1)存储实际的数据块。

2)执行数据块的读/写操作。

4Secondary NameNode:并非 NameNode 的热备。当NameNode 挂掉的时候,它并不

能马上替换 NameNode 并提供服务。

1)辅助 NameNode,分担其工作量。

2)定期合并 fsimage和fsedits,并推送给NameNode。

3)在紧急情况下,可辅助恢复 NameNode。

3.3.4 NameNode和DataNode

NameNode作用

NameNode在内存中保存着整个文件系统的名称 空间和文件数据块的地址映射

整个HDFS可存储的文件数受限于NameNode的内存大小

1、NameNode元数据信息 文件名,文件目录结构,文件属性(生成时间,副本数,权限)每个

文件的块列表。 以及列表中的块与块所在的DataNode之间的地址映射关系在内存中加载文件

系统中每个文件和每个数据块的引用关系(文件、block、datanode之间的映射信息) 数据会定

期保存到本地磁盘(fsImage文件和edits文件)

2、NameNode文件操作 NameNode负责文件元数据的操作 DataNode负责处理文件内容的读写

请求,数据流不经过NameNode,会询问它跟那个DataNode联系

3、NameNode副本 文件数据块到底存放到哪些DataNode上,是由NameNode决定的,NN根

据全局情况做出放置副本的决定

4、NameNode心跳机制 全权管理数据块的复制,周期性的接受心跳和块的状态报告信息(包

含该DataNode上所有数据块的列表) 若接受到心跳信息,NameNode认为DataNode工作正

常,如果在10分钟后还接受到不到DN的心跳,那么NameNode认为DataNode已经宕机 ,这时候

NN准备要把DN上的数据块进行重新的复制。 块的状态报告包含了一个DN上所有数据块的列

表,blocks report 每个1小时发送一次.

DataNode作用

提供真实文件数据的存储服务

1、Data Node以数据块的形式存储HDFS文件

2、Data Node 响应HDFS 客户端读写请求

3、Data Node 周期性向NameNode汇报心跳信息

4、Data Node 周期性向NameNode汇报数据块信息

5、Data Node 周期性向NameNode汇报缓存数据块信息

3.3.5 HDFS的副本机制和机架感知

HDFS 文件副本机制
所有的文件都是以 block 块的方式存放在 HDFS 文件系统当中,作用如下

  1. 一个文件有可能大于集群中任意一个磁盘,引入块机制,可以很好的解决这个问题
  2. 使用块作为文件存储的逻辑单位可以简化存储子系统
  1. 块非常适合用于数据备份进而提供数据容错能力

在 Hadoop1 当中, 文件的 block 块默认大小是 64M, hadoop2 当中, 文件的 block 块大小默认是

128M, block 块的大小可以通过 hdfs-site.xml 当中的配置文件进行指定

<property>
    <name>dfs.block.size</name>
    <value>块大小 以字节为单位</value>
</property>

机架感知

HDFS分布式文件系统的内部有一个副本存放策略:以默认的副本数=3为例:

1、第一个副本块存本机

2、第二个副本块存跟本机同机架内的其他服务器节点

3、第三个副本块存不同机架的一个服务器节点上

3.3.6 HDFS的命令行使用

ls

格式: hdfs dfs -ls URI

作用:类似于Linux的ls命令,显示文件列表

 hdfs dfs   -ls /

lsr

格式 : hdfs dfs -lsr URI (命令已过时)

作用 : 在整个目录下递归执行ls, 与UNIX中的ls-R类似

hdfs dfs   -ls -R /

mkdir

格式 : hdfs dfs [-p] -mkdir

作用 : 以<path>中的URI作为参数,创建目录。使用-p参数可以递归创建目录

put

格式 : hdfs dfs -put <localsrc> ... <dst>

作用 : 将单个的源文件src或者多个源文件srcs从本地文件系统拷贝到目标文件系统中( 对应的路径)。也可以从标准输入中读取输入,写入目标文件系统中

hdfs dfs -put /rooot/a.txt /dir1

moveFromLocal

格式: hdfs dfs -moveFromLocal <localsrc> <dst>

作用: 和put命令类似,但是源文件localsrc拷贝之后自身被删除

hdfs dfs -moveFromLocal /root/install.log /

get

格式 hdfs dfs -get [-ignorecrc ] [-crc] <src> <localdst>

作用:将文件拷贝到本地文件系统。 CRC 校验失败的文件通过-ignorecrc选项拷贝。 文件和CRC 校验和可以通过-CRC选项拷贝

hdfs dfs  -get   /install.log /export/servers

mv

格式 : hdfs dfs -mv URI <dest>

作用: 将hdfs上的文件从原路径移动到目标路径(移动之后文件删除),该命令不能跨文件系统

hdfs dfs  -mv /dir1/a.txt   /

rm

格式: hdfs dfs -rm [-r] 【-skipTrash】 URI 【URI 。。。】

作用: 删除参数指定的文件,参数可以有多个。 此命令只删除文件和非空目录。 如果指定-skipTrash选项,那么在回收站可用的情况下,该选项将跳过回收站而直接删除文件; 否则,在回收站可用时,在HDFS Shell 中执行此命令,会将文件暂时放到回收站中。

hdfs dfs  -rm  -r /dir1

cp

格式: hdfs dfs -cp URI [URI ...]

作用: 将文件拷贝到目标路径中。如果 为目录的话,可以将多个文件拷贝到该目录 下。

-f 选项将覆盖目标,如果它已经存在。

-p 选项将保留文件属性(时间戳、所有权、许可、ACL、XAttr)。

hdfs dfs -cp /dir1/a.txt /dir2/b.txt

cat

hdfs dfs -cat URI [uri ...]

作用:将参数所指示的文件内容输出到stdout

hdfs dfs  -cat /install.log

chmod

格式: hdfs dfs -chmod [-R] URI[URI ...]

作用: 改变文件权限。如果使用 -R 选项,则对整个目录有效递归执行。使用这一命令的用户 必须是文件的所属用户,或者超级用户。

hdfs dfs -chmod -R 777 /install.log

chown

格式: hdfs dfs -chmod [-R] URI[URI ...]

作用: 改变文件的所属用户和用户组。如果使用 -R 选项,则对整个目录有效递归执行。使用 这一命令的用户必须是文件的所属用户,或者超级用户。

hdfs dfs  -chown  -R hadoop:hadoop /install.log

appendToFile

格式: hdfs dfs -appendToFile <localsrc> ... <dst> 作用: 追加一个或者多个文件到hdfs指定文件中.也可以从命令行读取输入.

hdfs dfs -appendToFile a.xml b.xml /big.xml

3.3.7 HDFS的高级使用命令

HDFS文件限额配置

在多人共用HDFS的环境下,配置设置非常重要。特别是在Hadoop处理大量资料的环境,如 果没有配额管理,很容易把所有的空间用完造成别人无法存取。Hdfs的配额设定是针对目录 而不是针对账号,可以 让每个账号仅操作某一个目录,然后对目录设置配置。 hdfs文件的限额配置允许我们以文件个数,或者文件大小来限制我们在某个目录下上传的文 件数量或者文件内容总量,以便达到我们类似百度网盘网盘等限制每个用户允许上传的最大 的文件的量。

 hdfs dfs -count -q -h /user/root/dir  #查看配额信息

数量限额

hdfs dfsadmin -setQuota 2 dir      # 给该文件夹下面设置最多上传两个文件,发现只能上传一个文件

测试-

注意:之前这里是1,现在是0,是因为上传了一个文件用掉了一个配额,而为什么创建的是2,初始化就是1呢。因为目录也算一个配额,所以每次创建配额,都是n-1

hdfs dfsadmin -clrQuota /user/root/dir  # 清除文件数量限制

空间大小限额

在设置空间配额时 (错误做法)

hdfs dfs -mkdir /user/root/dir2
hdfs dfsadmin -setSpaceQuota 4k /user/root/dir2   # 限制空间大小4KB

出错原因是在设置大小时候,必须注意设置的空间大小至少是block_size*3的大小。一个block_size大小是128,乘3则至少是384

先清除,重新做。

清除空间配额限制

hdfs dfsadmin -clrSpaceQuota /user/root/dir2

设置配额限制,设置的空间至少是block_size * 3大小

hdfs dfsadmin -setSpaceQuota 384M /user/root/dir2   # 限制空间大小4KB

还有个问题是,如果一个文件是129M,那么会切成2个block,而每个block又有三个副本,因此在设置空间限额的时候,2*block_size*3,即128M*6=768M

hdfs dfs -mkdir /user/root/dir3     #重新创建一个目录,来做演示

我本应该设置768M,但如果我少设置1M,这样我就会缺少129M的文件

生成任意大小文件的命令:

dd if=/dev/zero of=1.txt  bs=1M count=2     #生成2M的文件

成功生成了129M的文件,之后再尝试把1.txt上传到dir3中

但是失败了,提示要设置768MB的配额。

设置成功了!!

hdfs的安全模式

安全模式是hadoop的一种保护机制,用于保证集群中的数据块的安全性。当集群启动的时 候,会首先进入安全模式。

当系统处于安全模式时会检查数据块的完整性。 假设我们设置的副本数(即参数dfs.replication)是3,那么在datanode上就应该有3个副本存 在,假设只存在2个副本,那么比例就是2/3=0.666。hdfs默认的副本率0.999。我们的副本率 0.666明显小于0.999,因此系统会自动的复制副本到其他dataNode,使得副本率不小于0.999。 如果系统中有5个副本,超过我们设定的3个副本,那么系统也会删除多于的2个副本。 在安全模式状态下,文件系统只接受读数据请求,而不接受删除、修改等变更请求。在,当 整个系统达到安全标准时,HDFS自动离开安全模式。

安全模式操作命令

hdfs dfsadmin  -safemode  get #查看安全模式状态
hdfs dfsadmin  -safemode enter #进入安全模式
hdfs dfsadmin  -safemode leave #离开安全模式

3.3.8 HDFS基准测试

实际生产环境当中,hadoop的环境搭建完成之后,第一件事情就是进行压力测试,测试我们 的集群的读取和写入速度,测试我们的网络带宽是否足够等一些基准测试

测试写入速度

向HDFS文件系统中写入数据,10个文件,每个文件10MB,文件存放到/benchmarks/TestDFSIO中

hadoop jar /export/servers/hadoop-2.7.5/share/hadoop/mapreduce/hadoop-mapreduce-client-jobclient-2.7.5.jar TestDFSIO -write -nrFiles 10 - fileSize 10MB

但是出现了问题,没成功。看提示是说在安全模式下,接下来我把安全模式关闭,重新执行命令

成功了!!在根目录下有了这个文件,打开之后

完成之后查看写入速度结果

测试读取速度

测试hdfs的读取文件性能

在HDFS文件系统中读入10个文件,每个文件10M

hadoop jar /export/servers/hadoop-2.7.5/share/hadoop/mapreduce/hadoop-mapreduce-client-jobclient-2.7.5.jar TestDFSIO -read -nrFiles 10 -fileSize 10MB

清除测试数据

hadoop jar /export/servers/hadoop-2.7.5/share/hadoop/mapreduce/hadoop-mapreduce-client-jobclient-2.7.5.jar TestDFSIO -clean

3.3.9 HDFS文件写入过程

  1. Client 发起文件上传请求, 通过 RPC 与 NameNode 建立通讯, NameNode 检查目标文件是否已存在, 父目录是否存在, 返回是否可以上传
  2. Client 请求第一个 block 该传输到哪些 DataNode 服务器上
  1. NameNode 根据配置文件中指定的备份数量及机架感知原理进行文件分配, 返回可用的DataNode 的地址如: A, B, CHadoop 在设计时考虑到数据的安全与高效, 数据文件默认在 HDFS 上存放三份, 存储策略为本地一份, 同机架内其它某一节点上一份, 不同机架的某一节点上一份。
  2. Client 请求 3 台 DataNode 中的一台 A 上传数据(本质上是一个 RPC 调用,建立 pipeline), A 收到请求会继续调用 B, 然后 B 调用 C, 将整个 pipeline 建立完成, 后逐级返回 client
  1. Client 开始往 A 上传第一个 block(先从磁盘读取数据放到一个本地内存缓存), 以packet 为单位(默认64K), A 收到一个 packet 就会传给 B, B 传给 C. A 每传一个 packet 会放入一个应答队列等待应答
  2. 数据被分割成一个个 packet 数据包在 pipeline 上依次传输, 在 pipeline 反方向上, 逐个发送 ack(命令正确应答), 最终由 pipeline 中第一个 DataNode 节点 A 将 pipelineack 发送给 Client
  1. 当一个 block 传输完成之后, Client 再次请求 NameNode 上传第二个 block 到服务 1

3.3.10 HDFS文件读取过程

  1. Client向NameNode发起RPC请求,来确定请求文件block所在的位置;
  2. NameNode会视情况返回文件的部分或者全部block列表,对于每个block,NameNode 都会返回含有该 block 副本的 DataNode 地址; 这些返回的 DN 地址,会按照集群拓扑结构得出 DataNode 与客户端的距离,然后进行排序,排序两个规则:网络拓扑结构中距离Client 近的排靠前;心跳机制中超时汇报的 DN 状态为 STALE,这样的排靠后;
  1. Client 选取排序靠前的 DataNode 来读取 block,如果客户端本身就是DataNode,那么将从本地直接获取数据(短路读取特性);
  2. 底层上本质是建立 Socket Stream(FSDataInputStream),重复的调用父类DataInputStream 的 read 方法,直到这个块上的数据读取完毕;
  1. 当读完列表的 block 后,若文件读取还没有结束,客户端会继续向NameNode 获取下一批的 block 列表;
  2. 读取完一个 block 都会进行 checksum 验证,如果读取 DataNode 时出现错误,客户端会通知 NameNode,然后再从下一个拥有该 block 副本的DataNode 继续读。
  1. read 方法是并行的读取 block 信息,不是一块一块的读取;NameNode 只是返回Client请求包含块的DataNode地址,并不是返回请求块的数据;
  2. 最终读取来所有的 block 会合并成一个完整的最终文件。

3.3.11 HDFS的元数据辅助管理

当 Hadoop 的集群当中, NameNode的所有元数据信息都保存在了 FsImage 与 Eidts 文件当中, 这两个文件就记录了所有的数据的元数据信息, 元数据信息的保存目录配置在了 hdfs-site.xml 当中

FsImage 和 Edits 详解

  • edits

1)edits 存放了客户端最近一段时间的操作日志

2)客户端对 HDFS 进行写文件时会首先被记录在 edits 文件中

3)edits 修改时元数据也会更新

  • fsimage

1)NameNode 中关于元数据的镜像, 一般称为检查点, fsimage 存放了一份比较完整的元数据信息

2)因为 fsimage 是 NameNode 的完整的镜像, 如果每次都加载到内存生成树状拓扑结 构,这是非常耗内存和CPU, 所以一般开始时对 NameNode 的操作都放在 edits 中

3)fsimage 内容包含了 NameNode 管理下的所有 DataNode 文件及文件 block 及 block 所在的 DataNode 的元数据信息.

随着 edits 内容增大, 就需要在一定时间点和 fsimage 合并

fsimage 中的文件信息查看

使用命令 hdfs oiv

cd /export/servers/hadoop-2.7.5/hadoopDatas/namenodeDatas

hdfs oiv -i fsimage_0000000000000000107 -p XML -o hello.xml

edits 中的文件信息查看

使用命令 hdfs oev

cd /export/servers/hadoop2.7.5/hadoopDatas/namenodeDatas

hdfs oev -i edits_0000000000000000865-0000000000000000866 -p XML -o myedit.xml

SecondaryNameNode 如何辅助管理 fsimage 与 edits 文件?

SecondaryNameNode 定期合并 fsimage 和 edits, 把 edits 控制在一个范围内

· 配置 SecondaryNameNode

SecondaryNameNode 在 conf/masters 中指定

在 masters 指定的机器上, 修改 hdfs-site.xml

<property>
  <name>dfs.http.address</name>
  <value>host:50070</value>
</property>

修改 core-site.xml , 这一步不做配置保持默认也可以

<!-- 多久记录一次 HDFS 镜像, 默认 1小时 -->
<property>
  <name>fs.checkpoint.period</name>
  <value>3600</value>
</property>
<!-- 一次记录多大, 默认 64M -->
<property>
  <name>fs.checkpoint.size</name>
  <value>67108864</value>
</property>

  1. SecondaryNameNode 通知 NameNode 切换 editlog
  2. SecondaryNameNode 从 NameNode 中获得 fsimage 和 editlog(通过http方式)
  1. SecondaryNameNode 将 fsimage 载入内存, 然后开始合并 editlog, 合并之后成为新的
    fsimage
  2. SecondaryNameNode 将新的 fsimage 发回给 NameNode
  1. NameNode 用新的 fsimage 替换旧的 fsimage

特点

· 完成合并的是 SecondaryNameNode, 会请求 NameNode 停止使用 edits, 暂时将新写操作放 入一个新的文件中 edits.new

· SecondaryNameNode 从 NameNode 中通过 Http GET 获得 edits, 因为要和 fsimage 合并, 所 以也是通过 Http Get 的方式把 fsimage 加载到内存, 然后逐一执行具体对文件系统的操作, 与 fsimage 合并, 生成新的 fsimage, 然后通过 Http POST 的方式把 fsimage 发送给 NameNode. NameNode 从 SecondaryNameNode 获得了 fsimage 后会把原有的 fsimage 替 换为新的fsimage, 把 edits.new 变成 edits. 同时会更新 fstime

· Hadoop 进入安全模式时需要管理员使用 dfsadmin 的 save namespace 来创建新的检查点

· SecondaryNameNode 在合并 edits 和 fsimage 时需要消耗的内存和 NameNode 差不多, 所 以一般把 NameNode 和 SecondaryNameNode 放在不同的机器上

3.3.12 HDFS的API操作

配置Windows下Hadoop环境

在windows系统需要配置hadoop运行环境,否则直接运行代码会出现以下问题

缺少winutils.exe

Could not locate executable null \bin\winutils.exe in the hadoop binaries 

缺少hadoop.dll

Unable to load native-hadoop library for your platform… using builtin-Java 
classes where applicable

步骤:

第一步:将hadoop2.7.5文件夹拷贝到一个没有中文没有空格的路径下面

第二步:在windows上面配置hadoop的环境变量: HADOOP_HOME,并 将%HADOOP_HOME%\bin添加到path中

第三步:把hadoop2.7.5文件夹中bin目录下的hadoop.dll文件放到系统盘: C:\Windows\System32 目录

第四步:关闭windows重启

导入 Maven 依赖

打开idea,创建module,在pom.xml中导入maven依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.itcast</groupId>
    <artifactId>day04_hdfs_api_demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-common</artifactId>
            <version>2.7.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-client</artifactId>
            <version>2.7.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-hdfs</artifactId>
            <version>2.7.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-mapreduce-client-core</artifactId>
            <version>2.7.5</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                    <!--    <verbal>true</verbal>-->
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.4.3</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <minimizeJar>true</minimizeJar>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

        </plugins>
    </build>

</project>

 使用url方式访问数据(了解)

@Test
public void demo1()throws  Exception{
    //第一步:注册hdfs 的url
    URL.setURLStreamHandlerFactory(new FsUrlStreamHandlerFactory());
    //获取文件输入流
    InputStream inputStream  = new URL("hdfs://node01:8020/a.txt").openStream();
    //获取文件输出流
    FileOutputStream outputStream = new FileOutputStream(new File("D:\\hello.txt"));
    //实现文件的拷贝
    IOUtils.copy(inputStream, outputStream);
    //关闭流
    IOUtils.closeQuietly(inputStream);
    IOUtils.closeQuietly(outputStream);
}

使用文件系统方式访问数据(掌握)

涉及的主要类

在 Java 中操作 HDFS, 主要涉及以下 Class:

Configuration 该类的对象封转了客户端或者服务器的配置

FileSystem 该类的对象是一个文件系统对象, 可以用该对象的一些方法来对文件进行操作, 通过 FileSystem 的静态方法 get 获得该对象

FileSystem fs = FileSystem.get(conf)
get 方法从 conf 中的一个参数 fs.defaultFS 的配置值判断具体是什么类
型的文件系统
如果我们的代码中没有指定 fs.defaultFS , 并且工程 ClassPath 下也没有给定
相应的配置, conf 中的默认值就来自于 Hadoop 的 Jar 包中的 coredefault.xml
默认值为 file:/// , 则获取的不是一个 DistributedFileSystem 的实例, 而是一个
本地文件系统的客户端对象

获取 FileSystem 的几种方式

第一种方式

@Test
public void getFileSystem1() throws IOException {
    Configuration configuration = new Configuration();
    //指定我们使用的文件系统类型:
    configuration.set("fs.defaultFS", "hdfs://node01:8020/");
    //获取指定的文件系统
    FileSystem fileSystem = FileSystem.get(configuration);
    System.out.println(fileSystem.toString());
}

第二种方式

@Test
public void getFileSystem2() throws  Exception{
    FileSystem fileSystem = FileSystem.get(new URI("hdfs://node01:8020"),new Configuration());
    System.out.println("fileSystem:"+fileSystem);
}

第三种方式

@Test
public void getFileSystem3() throws  Exception{
    Configuration configuration = new Configuration();
    configuration.set("fs.defaultFS", "hdfs://node01:8020");
    FileSystem fileSystem = FileSystem.newInstance(configuration);
    System.out.println(fileSystem.toString());
}

第四种方式

@Test
public void getFileSystem4() throws  Exception{
    FileSystem fileSystem = FileSystem.newInstance(new URI("hdfs://node01:8020") ,new Configuration());
    System.out.println(fileSystem.toString());
}

遍历 HDFS 中所有文件

使用 API 遍历

@Test
public void listMyFiles()throws Exception{
    //获取fileSystem类
    FileSystem fileSystem = FileSystem.get(new URI("hdfs://node01:8020"), new Configuration());
    //获取RemoteIterator 得到所有的文件或者文件夹,第一个参数指定遍历的路径,第二个
参数表示是否要递归遍历
    RemoteIterator<LocatedFileStatus> locatedFileStatusRemoteIterator = fileSystem.listFiles(new Path("/"), true);
    while (locatedFileStatusRemoteIterator.hasNext()){
        LocatedFileStatus next = locatedFileStatusRemoteIterator.next();
        System.out.println(next.getPath().toString());
   }
    fileSystem.close();
}

HDFS 上创建文件夹

@Test
public void mkdirs() throws  Exception{
    FileSystem fileSystem = FileSystem.get(new URI("hdfs://node01:8020"), new Configuration());
    boolean mkdirs = fileSystem.mkdirs(new Path("/hello/mydir/test"));
    System.out.println(mkdirs);
    fileSystem.close();
}

下载文件

@Test
public void getFileToLocal()throws  Exception{
    FileSystem fileSystem = FileSystem.get(new URI("hdfs://node01:8020"),new Configuration());
    FSDataInputStream inputStream = fileSystem.open(new Path("/dir2/a.txt"));
    FileOutputStream  outputStream = new FileOutputStream(new File("e:\\a.txt"));
    IOUtils.copy(inputStream,outputStream );
    IOUtils.closeQuietly(inputStream);
    IOUtils.closeQuietly(outputStream);
    fileSystem.close();
}

 @Test
    public void getFileToLocal2()throws  Exception{
        FileSystem fileSystem = FileSystem.get(new URI("hdfs://node01:8020"),new Configuration());
        fileSystem.copyToLocalFile(new Path("/dir2/a.txt"),new Path("e:\\a.txt"));
        fileSystem.close();
    }

HDFS 文件上传

@Test
public void putData() throws  Exception{
    FileSystem fileSystem = FileSystem.get(new URI("hdfs://node01:8020"), new Configuration());
    fileSystem.copyFromLocalFile(new Path("e:\\hello.txt"),new Path("/hello/"));
    fileSystem.close();
}

hdfs访问权限控制

  1. 停止hdfs集群,在node01机器上执行以下命令
cd /export/servers/hadoop-2.7.5
sbin/stop-dfs.sh

  1. 修改node01机器上的hdfs-site.xml当中的配置文件
cd /export/servers/hadoop-2.7.5/etc/hadoop
vim hdfs-site.xml
<property>
 <name>dfs.permissions.enabled</name>
 <value>true</value>
</property>

  1. 修改完成之后配置文件发送到其他机器上面去
scp hdfs-site.xml node02:$PWD
scp hdfs-site.xml node03:$PWD

  1. 重启hdfs集群
cd /export/servers/hadoop-2.7.5
sbin/start-dfs.sh

  1. 随意上传一些文件到我们hadoop集群当中准备测试使用
cd /export/servers/hadoop-2.7.5/etc/hadoop
hdfs dfs -mkdir /config
hdfs dfs -put *.xml /config
hdfs dfs -chmod 600 /config/core-site.xml

  1. 使用代码准备下载文件
@Test
public void getConfig()throws  Exception{
    FileSystem fileSystem = FileSystem.get(new URI("hdfs://node01:8020"), 
new Configuration(),"root");
    fileSystem.copyToLocalFile(new Path("/config/core-site.xml"),new
Path("file:///c:/core-site.xml"));
    fileSystem.close();
}

小文件合并

由于 Hadoop 擅长存储大文件,因为大文件的元数据信息比较少,如果 Hadoop 集群当中有大 量的小文件,那么每个小文件都需要维护一份元数据信息,会大大的增加集群管理元数据的 内存压力,所以在实际工作当中,如果有必要一定要将小文件合并成大文件进行一起处理

在我们的 HDFS 的 Shell 命令模式下,可以通过命令行将很多的 hdfs 文件合并成一个大文件下 载到本地

cd /export/servers
hdfs dfs -getmerge /config/*.xml ./hello.xml

既然可以在下载的时候将这些小文件合并成一个大文件一起下载,那么肯定就可以在上传的 时候将小文件合并到一个大文件里面去

@Test
public void mergeFile() throws  Exception{
    //获取分布式文件系统
    FileSystem fileSystem = FileSystem.get(new URI("hdfs://192.168.52.250:8020"),new Configuration(),"root");
    //获取hdfs大文件的输出流
    FSDataOutputStream outputStream = fileSystem.create(new Path("/bigfile.txt"));
    //获取本地文件系统
    LocalFileSystem local = FileSystem.getLocal(new Configuration());
    //通过本地文件系统获取文件列表,为一个集合
    FileStatus[] fileStatuses = local.listStatus(new Path("file:///E:\\input"));
    //遍历每个文件,获取每个文件的输入流
    for (FileStatus fileStatus : fileStatuses) {
       FSDataInputStream inputStream = local.open(fileStatus.getPath());
        //将小文件的数据复制到大文件
       IOUtils.copy(inputStream,outputStream);
       IOUtils.closeQuietly(inputStream);
        }
    IOUtils.closeQuietly(outputStream);
    local.close();
    fileSystem.close();
}

3.3.13 HDFS的高可用机制

HDFS高可用介绍

在Hadoop 中,NameNode 所处的位置是非常重要的,整个HDFS文件系统的元数据信息都由 NameNode 来管理,NameNode的可用性直接决定了Hadoop 的可用性,一旦NameNode进程 不能工作了,就会影响整个集群的正常使用。 在典型的HA集群中,两台独立的机器被配置为NameNode。在工作集群中,NameNode机器中 的一个处于Active状态,另一个处于Standby状态。Active NameNode负责群集中的所有客户端 操作,而Standby充当从服务器。Standby机器保持足够的状态以提供快速故障切换(如果需要)。

组件介绍

ZKFailoverController

是基于Zookeeper的故障转移控制器,它负责控制NameNode的主备切换, ZKFailoverController会监测NameNode的健康状态,当发现Active NameNode出现异常时会通 过Zookeeper进行一次新的选举,完成Active和Standby状态的切换

HealthMonitor

周期性调用NameNode的HAServiceProtocol RPC接口(monitorHealth 和 getServiceStatus), 监控NameNode的健康状态并向ZKFailoverController反馈

ActiveStandbyElector

接收ZKFC的选举请求,通过Zookeeper自动完成主备选举,选举完成后回调 ZKFailoverController的主备切换方法对NameNode进行Active和Standby状态的切换.

DataNode

NameNode包含了HDFS的元数据信息和数据块信息(blockmap),其中数据块信息通过 DataNode主动向Active NameNode和Standby NameNode上报

共享存储系统

共享存储系统负责存储HDFS的元数据(EditsLog),Active NameNode(写入)和 Standby NameNode(读取)通过共享存储系统实现元数据同步,在主备切换过程中,新的Active NameNode必须确保元数据同步完成才能对外提供服务

3.3.14 HDFS的联邦机制

单NameNode的架构使得HDFS在集群扩展性和性能上都有潜在的问题,当集群大到一定程度 后,NameNode进程使用的内存可能会达到上百G,NameNode成为了性能的瓶颈。因而提出 了namenode水平扩展方案-- Federation。 Federation中文意思为联邦,联盟,是NameNode的Federation,也就是会有多个NameNode。多 个NameNode的情况意味着有多个namespace(命名空间),区别于HA模式下的多NameNode, 它们是拥有着同一个namespace。既然说到了NameNode的命名空间的概念,这里就看一下现有的HDFS数据管理架构,如下图所示

从上图中,我们可以很明显地看出现有的HDFS数据管理,数据存储2层分层的结构.也就是说,所有 关于存储数据的信息和管理是放在NameNode这边,而真实数据的存储则是在各个DataNode下. 而这些隶属于同一个NameNode所管理的数据都是在同一个命名空间下的.而一个namespace对 应一个block pool。Block Pool是同一个namespace下的block的集合.当然这是我们最常见的单 个namespace的情况,也就是一个NameNode管理集群中所有元数据信息的时候.如果我们遇到 了之前提到的NameNode内存使用过高的问题,这时候怎么办?元数据空间依然还是在不断增大, 一味调高NameNode的jvm大小绝对不是一个持久的办法.这时候就诞生了HDFS Federation的机制 .

Federation架构设计

HDFS Federation是解决namenode内存瓶颈问题的水平横向扩展方案。 Federation意味着在集群中将会有多个namenode/namespace。这些namenode之间是联合的, 也就是说,他们之间相互独立且不需要互相协调,各自分工,管理自己的区域。分布式的 datanode被用作通用的数据块存储存储设备。每个datanode要向集群中所有的namenode注 册,且周期性地向所有namenode发送心跳和块报告,并执行来自所有namenode的命令。

Federation一个典型的例子就是上面提到的NameNode内存过高问题,我们完全可以将上面部分 大的文件目录移到另外一个NameNode上做管理.更重要的一点在于,这些NameNode是共享集 群中所有的DataNode的,它们还是在同一个集群内的**。** 这时候在DataNode上就不仅仅存储一个Block Pool下的数据了,而是多个(在DataNode的datadir 所在目录里面查看BP-xx.xx.xx.xx打头的目录)。

概括起来:

多个NN共用一个集群里的存储资源,每个NN都可以单独对外提供服务。 每个NN都会定义一个存储池,有单独的id,每个DN都为所有存储池提供存储。 DN会按照存储池id向其对应的NN汇报块信息,同时,DN会向所有NN汇报本地存储可用资源 情况。

HDFS Federation不足

HDFS Federation并没有完全解决单点故障问题。虽然namenode/namespace存在多个,但是从 单个namenode/namespace看,仍然存在单点故障:如果某个namenode挂掉了,其管理的相 应的文件便不可以访问。Federation中每个namenode仍然像之前HDFS上实现一样,配有一个 secondary namenode,以便主namenode挂掉一下,用于还原元数据信息。 所以一般集群规模真的很大的时候,会采用HA+Federation的部署方案。也就是每个联合的 namenodes都是ha的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值