hadoop集群搭建

Hadoop集群搭建与配置 

介绍如何搭建Hadoop集群。包括Hadoop对集群系统的硬件、软件要求,在搭建Hadoop集群前需要进行的准备工作,Hadoop集群软件包的安装与配置,以及Hadoop客户端的安装与配置。

0 名词解释 

1 集群规格 

Hadoop的设计目标是运行在通用硬件上,所以可以选择不同厂商、不同种类的硬件来搭建集群。在搭建集群前,首先需要了解目标集群的规格,包括主、从节点的硬件需求与选择,集群的网络拓扑,以及内核、JVM版本等。

1.1 内存 

在Hadoop集群中,NameNode一般对内存有较大的需求,因为Namenode将所有文件的元信息存储在内存中,以获取较高的响应速度。在构建集群前,需要根据集群的目标规模,以及期望存储的文件数来确定Namenode内存大小。以往的经验表明,为JVM(64-bit)开启1GB堆空间可以维护大约200万个文件的元信息,随着数据存储规模的增长,Namenode必须增加内存以服务更多的文件。 举例来说,假设一个Hadoop集群服务2000万个文件,要存储这些文件的元信息, JVM虚拟机的堆空间应该设为12GB左右来满足存储元信息的需求并提供额外的缓冲空间,即:Namenode服务器则至少应该保证有16GB的物理内存。

此外,Secondary Namenode在创建镜像时与NameNode有同阶的内存消耗,对于较大规模的集群来说,Secondary Namenode 与 Namenode 应该独立运行在不同的物理服务器上,而且两台服务器应该保证具备相同的内存配置。

集群中的TaskTracker (DataNode)节点的内存,应该根据应用作业的类型来确定。某些MapReduce作业(如:Map任务需要加载字典)需要消耗较多内存,如果集群需要处理这种类型的作业,就必须为TaskTracker节点配备足够大的内存,内存配置的具体大小根据应用来决定。反之,某些作业仅对数据进行流式处理,则不会对TaskTracker有太高的内存需求。为TaskTracker配备更多内存的另一个好处是,可以通过配置内存相关参数来降低Map/Reduce排序(Shuffle)阶段的I/O消耗,以提升框架的整体效率(参见4.4)。

为保证集群的稳定性,需要严格控制作业的内存消耗,尤其是在使用Streaming时。可以配合相应的limit工具来限制Map/Reduce任务的内存使用。具体可以参考2.5。

1.2 磁盘 

Hadoop可以充分利用多核、多磁盘服务器的资源,服务器可以同时运行多个Map/Reduce任务,集群节点可以被配置以并行使用多路磁盘从而提升I/O效率。例如,一台配备8块磁盘的服务器,分别挂载到/home/disk0, /home/disk1, …, /home/disk7目录下,通过配置HDFS与MapReduce数据目录参数(dfs.data.dir和mapred.data.dir),以逗号分隔各配置目录,如:/home/disk0/hdfs, /home/disk1/hdfs, …, /home/disk7/hdfs 和 /home/disk0/mapred, /home/disk1/mapred, …, /home/disk7/mapred,Hadoop会自动并行地使用这些磁盘(详见4.2)。

没有必要对Hadoop集群节点配置RAID或LVM,相反还有可能带来效率上的下降。RAID提供的冗余对于Hadoop集群来说是多余的,因为HDFS通过块副本来进行冗余备份。此外,尽管RAID条带(RAID 0)模式经常用来提升I/O效率,然而实践经验证明,对于使用所有磁盘的Hadoop集群,RAID 0的性能反而不如不做RAID或JBOD(Just a Bunch of Disks)。这是因为RAID 0的读写操作受限于最慢的一块磁盘的速度;而不做RAID情况下的多路磁盘操作是独立的,实际上平均速度要高于RAID 0。来自Yahoo!的测试数据表明,不做RAID在Gridmix测试中的性能表现要比RAID 0高10%,在HDFS写吞吐量测试中要高于30%。

此外,单块磁盘损坏会造成RAID 0节点所有磁盘失效,从而造成整个节点不可用。而不做RAID时,Hadoop可以跳过损坏的磁盘,其他磁盘仍可正常使用。

以往运维经验表明,Hadoop作业通常属于I/O密集型,磁盘的I/O负载较高,磁盘损坏时常发生,是Hadoop集群硬件故障的最主要来源。如果条件允许,可以考虑使用可靠性较高的SCSI或SAS磁盘,以降低由磁盘损坏所带来的运维成本。

1.3 网络拓扑 

一个典型的Hadoop集群由两级网络拓扑组成,通常每个机架部署30-40台服务器,以及一台1GB交换机。同一机架上节点之间的带宽要远大于跨机架节点之间的带宽。如果集群网络环境不可控,则不需为集群配置机架,目前dpf-op小组运维的所有集群都没有进行机架配置。

为了最大化利用集群性能,对Hadoop进行机架配置是十分有必要的。默认情况下,Hadoop认为所有节点运行在单一机架上(/default-rack),所以不需要对只有一个机架的集群进行配置。然而对于多机架集群,需要配置节点到机架的映射关系,这样Hadoop可以优先选择同一机架上的节点进行数据传输,HDFS也会根据机架信息来分布块副本。

1.4 Linux内核 

由于HDFS文件操作的顺序读写操作要远多于随机读写,为了提升顺序读写效率,DataNode的底层文件系统可以考虑使用支持大块文件系统的内核(百度 2.6.9_2系列),对HDFS数据挂载磁盘的文件系统进行分区时将块大小设为64K,以更高效地支持HDFS大块存储的特性。NameNode节点只存储文件系统的元信息,不需要大块存储,同样JobTracker也不需要大块文件系统。查询分区的Block Size可以通过/usr/sbin/tune2fs -l /dev/sda1 | grep "Block size"来查看。

为了限制Map/Reduce任务对物理内存的使用,通常需要部署用于控制物理内存的limit工具,同样需要内核的支持。

1.5 JVM 

Hadoop的大多数部件是用Java实现的,对JVM版本有一定要求。运行Hadoop的JVM版本不应低于1.6,并且推荐使用Sun官方的JVM。为了突破32-bit JVM对最大3G堆空间的限制,应该使用64-bit版本的JVM。一些新版本JVM(java6-u14),支持对象指针压缩的特性,可以有效降低64-bit指针带来的额外内存开销。当然,在使用新版本JVM时也要考虑版本不稳定而带来的bug等因素。

Hadoop-v2发布包中自带java6-u7虚拟机,并且已经在该虚拟机上测试过大规模集群应用,目前比较稳定。我们推荐使用Hadoop-v2自带的虚拟机,除非有特殊应用需求,不建议使用其他版本。

2 准备工作 

在安装Hadoop前,需要预先进行一些准备工作,包括操作系统准备、JVM部署、SSH配置等。

2.1 操作系统 

由于TaskTracker需要部署limit3控制内存使用,需要保证内核版本不低于2.6.9_2_5_0_4。同时,如果DataNode/TaskTracker需要使用大块文件系统提升HDFS性能,那么DataNode/TaskTracker可以使用2.6.9_2_8_0_0内核。内核下载页面为:http://ikernel.baidu.com/。详情请咨询STL接口人(汤丽婧)。

DataNode/TaskTracker使用多块磁盘时,可以为每块磁盘分一个区,以下是一种典型的分区布局:

$ df -h
Filesystem            Size  Used Avail Use% Mounted on
/dev/sda2             8.7G  3.3G  5.4G  38% /
/dev/sda3             922G  244G  669G  27% /home
/dev/sdb1             932G  217G  706G  24% /home/disk1
/dev/sdc1             932G  214G  709G  24% /home/disk2
/dev/sdd1             932G  210G  713G  23% /home/disk3
/dev/sde1             932G  219G  704G  24% /home/disk4
/dev/sdf1             932G  200G  723G  22% /home/disk5
/dev/sdg1             932G  214G  709G  24% /home/disk6
/dev/sdh1             932G  234G  690G  26% /home/disk7
/dev/sdi1             932G  214G  709G  24% /home/disk8
/dev/sdj1             932G  200G  723G  22% /home/disk9
/dev/sdk1             932G  216G  707G  24% /home/disk10
/dev/sdl1             932G  214G  709G  24% /home/disk11

# 如果使用2800内核,可以使用大块文件系统的方式格式化分区,参考以下命令:
$ /sbin/mke2fs -b 65536 -m 1 -Tlargefile -F /dev/sdb1

注意:e2fs工具包的版本要求不低于1.39(通过mke2fs –V查看),新版本可以从这里获取:ftp://jx-sos-scm.vm.baidu.com/home/public/tool/e2fsprogs-1.40.2-for64k.tar.gz。

简要解释下上述命令的含义(欲知详细内容,请查询 man mke2fs) mke2fs用来创建一个ext2/ext3的文件系统。参数“b”的全称为“Block size”,65536也就是64KB,表明是大块文件系统;参数“m”,表明为超级用户也就是管理员保留的文件块的比例,默认是5%,这里指定为1%;参数“T“紧跟文件系统类型,有3种(news/largefile/largefile4),这里选择的是largefile,b表明一个inode对应1MB的空间;参数”F“就是强制mke2fs执行,不管指定的设备是什么。

2.2 JVM 

Hadoop要求Java 6或以上版本,虽然其他厂商提供的JVM也可以使用,但应该优先考虑Sun官方的最新版本。Hadoop-v2的发布版本中自带java6-u7版本JVM,我们推荐使用自带JVM,所以一般不需要自行部署。如果需要使用其他版本JVM,可以在自行部署后使用以下命令来确认Java已经正确安装:

$ java -version
java version "1.6.0_12"
Java(TM) SE Runtime Environment (build 1.6.0_12-b04)
Java HotSpot(TM) 64-Bit Server VM (build 11.2-b01, mixed mode)

2.3 创建用户 

为了隔离系统环境并保证HDFS数据安全,推荐将Hadoop的HDFS和MapReduce分两个用户安装部署。在需要部署JobTracker和TaskTracker的节点上新建一个mapred用户;需要部署NameNode、SecondaryNameNode、DataNode的节点使用已存在的work用户。 通常Hadoop集群的从节点同时运行TaskTracker和DataNode,所以要保证在从节点上新建一个mapred用户:

# useradd mapred

主节点则根据需要部署的服务类型来新建相应用户。

Hadoop也可以使用单用户的方式来进行安装部署,但不推荐这种方式。因为用户提交的Map/Reduce任务有可能误删除HDFS数据(DataNode本地块文件),对于分布式执行的MapReduce作业,这种数据丢失是灾难性的。关于单用户安装Hadoop可以参考官方的Hadoop文档。

2.4 SSH配置 

Hadoop提供一组脚本来控制集群的启动、停止,这些脚本依赖于SSH来进行节点的控制操作。如果要使用Hadoop提供的集群控制脚本,需要保证主节点(NameNode/JobTracker)的work和mapred用户可以无密码登录到集群的从节点。最简单的方式是生成主节点work和mapred用户的公/私钥对,并将公钥追加到从节点work和mapred用户的~/.ssh/authorized_keys文件中(没有这个文件就新建一个)。

以mapred用户为例,在JobTracker上用mapred用户登录(su - mapred),并运行以下命令:

% ssh-keygen -t rsa       #生成ssh用于认证的密钥(公钥+私钥)
% cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
%chmod -R 755 ~          #对应账户家目录要求755
% chmod -R 700 ~/.ssh     # ~/.ssh目录要求700
% chmod 600 ~/.ssh/authorized_keys

注意:authorized_keys文件不可以有组和其他用户的写权限,否则会造成验证失败。

执行ssh localhost检查是否可以实现无密码登录,如果登录成功,则可以将authorized_keys分发到集群从节点mapred用户的~/.ssh/目录下,即可实现主节点到从节点的无密码登录,在执行Hadoop集群控制脚本时也不再需要为每个节点输入密码。

2.5 limit 

Hadoop集群允许用户自由提交作业,不能保证用户作业的内存使用,尤其是streaming类型的作业,由于Hadoop无法控制streaming子进程内存使用量的大小,有可能出现用户作业占用内存过多将将大量节点跑死的情况。通过为TaskTracker部署limit可以限制MapReduce任务物理内存的使用,当用户提交作业的某个MapReduce任务所占用的内存超过一个阈值(默认是800M),操作系统会将该任务的进程组杀死,以防止单个任务/作业跑死节点甚至整个集群的情况发生。

limit3下载limit3,安装到/bin目录下,并保证文件setuid位已设:

chmod a+s /bin/limit3

但要注意,上面的命令执行完后,要check下,limit3的权限是否正确: $ls -l /bin/limit3 -rwsr-xr-x 1 root root 16195 Jul 12 18:01 limit3 说点大家可能都清楚的。一般linux文件系统,有3种权限(rwx);还有一个种权限是setuid,setgid,其作用是让读取或者执行文件的用户拥有文件所有者的权限(最常见的例子就是ping命令,大家平时不管用哪个用户都可以使用,但是写过网络程序的同学应该知道,raw socket等必须有管理员的权限,也就是root权限,可以猜测ping的所有者必然是root等管理员帐户;但为什么任何用户都可以用呢?原因就在于setuid位。)。当原来文件是可执行文件时,也就是标志位中有x时,再设置setuid,x所在的位置就会变成s,记住是小写的s。那么大写的S呢?很容易猜到了吧,当文件原来并没有可执行权限,再设uid时,那一位就会变为S。

3 Hadoop安装与配置 

本节介绍如何对Hadoop进行安装以及启动集群前的必要配置。需要注意的是,dpf-rd小组提供的Hadoop-v2版本与社区版本在安装与配置上有些不同,本节主要针对Hadoop-v2版本,对于社区版本的安装配置请参见官方网站的文档。

3.1 Hadoop安装 

以hadoop-v2-12为例,在 getprod@product.scm.baidu.com:/data/prod-64/sys/dpf/hadoop/hadoop-core/hadoop-core_2-12-0_BL下载Hadoop-v2软件压缩包,具体命令:scp -r getprod@product.scm.baidu.com:/data/prod-64/sys/dpf/hadoop/hadoop-core/hadoop-core_2-12-0_BL 本地存储目录,然后进入本地存储目录下的/hadoop-core_2-12-0_BL/output目录下执行脚本:build4deploy.sh,执行完毕后会在当前deploy目录下生成搭建集群的四个角色的安装包及对应hadoop客户端: hadoop-datanode.tar.gz hadoop-jobtracker.tar.gz hadoop-namenode.tar.gz hadoop-tasktracker.tar.gz hadoop-client.tar.gz 

其中集群搭建需要上面四个角色的tar包,hadoop-client用于客户端部署(见“5 客户端配置”)。hadoop-namenode(jobtracker)用于主节点部署,hadoop-datanode(tasktracker)用于从节点部署。由于是采用分用户安装部署的方式,所以要为work和mapred用户安装两份独立的Hadoop环境。可以将hadoop-namenode(jobtracker)分发到Namenode和Secondary Namenode的/home/work目录下,以及JobTracker的/home/mapred目录下。将(DataNode/TaskTracker)分发到从节点的/home/work目录和/home/mapred目录下。因为启动HDFS和MapReduce的控制脚本需要保证所有节点Hadoop的安装路径相同,分发后需要将所有节点的安装根目录名由hadoop-namenode(jobtracker)、hadoop-datanode(tasktracker)统一改为hadoop-v2。以从节点(datanode/tasktracker)为例:

% mv /home/mapred/hadoop-tasktracker /home/mapred/hadoop-v2
% mv /home/work/hadoop-datanode /home/work/hadoop-v2

同时,应该确保work和mapred用户对安装目录的所属权:

% chown –R work:work /home/work/hadoop-v2
% chown –R mapred:mapred /home/mapred/hadoop-v2

3.2 配置管理 

Hadoop不维护单一、全局的配置信息,而是每个节点独立维护配置文件,配置文件的同步要由集群管理员来保持。一般来讲,Hadoop集群的所有从节点的配置文件内容相同,并且除了hadoop.job.ugi,主节点和从节点的大多数配置也相同。但在某些情况下,比如集群机器硬件配置不同,Hadoop也允许为每个节点修改自己专属的配置文件。

Hadoop配置文件存储在hadoop-v2/hadoop/conf目录下,一般包含以下文件:

文件名描述
hadoop-env.sh  Hadoop启动需要的环境变量 
hadoop-default.xml  Hadoop默认配置,模板文件,不要改动 
hadoop-site.xml  Hadoop主配置文件 
hadoop-user-info.properties  主节点维护的用户信息 
masters  Secondary Namenode的主机名列表 
slaves  所有从节点(DataNode/TaskTracker)的主机名列表 
hadoop-metrics.properties  metrics统计信息配置 
log4j.properties  HDFS审计日志log4j配置 
dfs.hosts.exclude  待下架DataNode列表 
mapred.hosts.exclude  待下架TaskTracker列表 

为了保证Hadoop正确安装、启动,需要保证hadoop-default.xml、hadoop-site.xml、hadoop-env.sh、hadoop-user-info.properties以及masters和slaves配置正确。需要注意的是,hadoop-default.xml一般不需要改动,这个文件保存的是Hadoop的默认配置,如果需要修改这些配置,应该在hadoop-site.xml中添加相应的属性,Hadoop在读取配置文件时,会用hadoop-site.xml中的内容覆盖默认配置。在启动集群前, 至少需要保证主、从节点的hadoop-site.xml文件中以下属性配置正确:

<configuration>
<property>
  <name>fs.default.name</name>
  <value>hdfs://bb-namenode-v.bb01.baidu.com:64310</value>
  <final>true</final>
</property>

<property>
  <name>dfs.http.address</name>
  <value>bb-namenode-v.bb01.baidu.com:8070</value>
  <description>
    The address and the base port where the dfs namenode web ui will listen on.
    If the port is 0 then the server will start on a free port.
  </description>
</property>

<property>
  <name>mapred.job.tracker</name>
  <value>bb-jobtracker-v.bb01.baidu.com:64312</value>
  <final>true</final>
</property>
<property>
  <name>hadoop.job.ugi</name>
  <value>user,pass</value>
</property>
</configuration>

mapred.job.tracker.http.address 0.0.0.0:8030 The job tracker http server address and port the server will listen on. If the port is 0 then the server will start on a free port.

其中fs.default.name指示了HDFS的URI,同时也指示NameNode的主机名和端口号。mapred.job.tracker则指示JobTracker的主机名和端口号,注意如果部署在同一台机器上Namenode和Jobtracker的端口号不要相邻。如果对NameNode或JobTracker进行双机备份防范单点失效,可以为Namenode和JobTracker的主/备机分配虚域名,这样在故障恢复时不需要更新集群配置或重启集群。(注意,凡是设计http访问的,端口号应约束在(8000-9000),测试端口是否已经被占用在本机上运行:telnet localhost 8070)。另外,hdfs只会读取work账户下的hadoop-site.xml中,对其有效的部分(比如mapred的一些属性它并不读取),所以只要保证相应账户下的相应属性设置正确即可(主节点上work->hdfs存储 , mapred-jobtracker计算)。

hadoop.job.ugi属性代表节点的用户信息,其中主节点的用户必须为root,从节点的用户必须为slave。关于用户信息的配置将在“3.5 用户/组信息”一节详细介绍。某些属性被标记为final,则该属性不能被用户提交的MapReduce作业配置覆盖。

3.3 主/从节点部署策略 

根据集群规模以及主节点的硬件配置,可以为Hadoop集群选择不同的主节点部署策略。一般来讲,对于30个节点以上的集群,需要将NameNode、SecondaryNameNode和JobTracker分别部署在3台服务器上,并且NameNode和SecondaryNameNode要具备较高的内存配置。如果集群规模较小(20台节点以内),可以将NameNode、SecondaryNameNode和JobTracker部署在同一台服务器上。但是随着集群规模以及HDFS文件个数的增长,NameNode和SecondaryNameNode占用内存资源会越来越多,并且当MapReduce作业较多时,JobTracker也会占用较多的CPU、内存资源。这时就需要将NameNode、SecondaryNameNode和JobTracker分开部署。

一般来讲,从节点同时运行DataNode和TaskTracker进程。不过Hadoop也可以配置为仅启动HDFS而不启动MapReduce,这时从节点只启动DataNode而不启动TaskTracker。反过来,也可能只启动MapReduce,此时从节点只运行TaskTracker进程。HDFS和MapReduce的启动方式参见“3.7 集群控制脚本”。

主节点的hadoop-v2/hadoop/conf目录下的slaves文件配置为所有从节点的主机名列表(每个主机一行),其中NameNode的slaves文件代表所有DataNode列表,JobTracker的slaves文件代表所有TaskTracker列表,由于从节点同时运行DataNode和TaskTracker,所以通常NameNode和JobTracker的slaves文件内容是相同的。在主控节点上执行集群控制脚本启动、停止集群时,会读取slaves文件来决定哪些从节点需要启动DataNode/TaskTracker进程。

conf目录下还有一个masters文件,该文件的命名很容易让人误解,这个文件仅用来配置SecondaryNameNode的主机名。如果配置SecondaryNameNode和Namenode,Jobtracker在同一服务器上,就需要建立到work账户到本机的信任关系,下面会提到”如何建立ssh的信任关系“。

3.4 环境变量 

通过修改conf目录下的hadoop-env.sh文件,可以配置Hadoop守护进程的环境变量。Hadoop控制脚本在启动相应的进程前,会使用这些环境变量。以下这些比较常用:

变量名描述
JAVA_HOME  指定Java虚拟机路径 
HADOOP_HEAPSIZE  虚拟机最大内存使用,默认是1000MB 
HADOOP_OPTS  Hadoop进程的Java参数,经常用于设置GC策略等 
HADOOP_LOG_DIR  Hadoop系统日志路径 

其中JAVA_HOME需要正确配置,以指定Java实现的路径。Hadoop的守护进程Java参数和使用的最大堆空间由HADOOP_OPTS和HADOOP_HEAPSIZE指定。

通过配置HADOOP_OPTS,可以设置Hadoop守护进程的Java参数,例如调整GC策略、开启JMX等。集群中大多数节点可以使用Hadoop-v2分发包中的默认配置。但为了保证Hadoop集群运行稳定,需要修改NameNode和SecondaryNameNode的虚拟机参数为:

-XX:+UseConcMarkSweepGC -XX:+UseParNewGC -Xnoclassgc -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 
-XX:+CMSClassUnloadingEnabled -XX:-CMSParallelRemarkEnabled -XX:CMSInitiatingOccupancyFraction=70 -XX:SoftRefLRUPolicyMSPerMB=0 
-XX:MaxTenuringThreshold=7 -Dcom.sun.management.jmxremote

通常NameNode和SecondaryNameNode会占用比较多的内存,可以通过修改NameNode和SecondaryNameNode的HADOOP_HEAPSIZE来分配更多的内存。1.1中已经讨论过NameNode和SecondaryNameNode的内存需求。其他节点对内存要求不高,使用默认的3GB即可。

TaskTracker在启动一个Map或Reduce计算任务时,会创建一个子JVM,该JVM的内存不受HADOOP_HEAPSIZE控制,而是在hadoop-site.xml中设置mapred.child.java.opts参数来指定,默认的设置是-Xmx 200M,也就是为子进程分配最高200M的堆空间。注意在运行Streaming任务时,用户提交的脚本或程序的内存使用不受该参数控制,为了控制Streaming任务的内存使用,需要部署limit工具(参见2.5)。

HADOOP_LOG_DIR指定Hadoop系统日志的路径。在集群安装、配置过程中如果有进程启动失败,都可以到相应节点的HADOOP_LOG_DIR目录下通过查看日志来进行问题定位。

注:不要使用rsync!官方版本支持基于rsync的集群配置文件同步,通过设置HADOOP_MASTER可以在启动服务前将集群中所有节点的配置文件同步为HADOOP_MASTER的/conf目录下的文件。但是由于Hadoop-v2版本引入用户验证,主/从节点配置略有不同。如果完全同步配置文件,会造成集群启动失败。

3.5 用户/组信息 

Hadoop-v2与社区版本的一个最主要的区别就是支持用户管理与口令认证,从而可以更好地控制客户端权限。NameNode的用户信息用于控制用户的HDFS的访问权限,JobTracker的用户信息用于控制用户提交MapReduce作业对集群计算资源的访问权限。

HDFS对用户的访问权限控制与Unix系统类似,所有文件都有相应的属主和权限掩码,并且HDFS也提供chown、chmod等工具用于文件权限修改,具体请参考HDFS的使用文档。Hadoop还允许管理员对集群的计算资源进行按用户分配。通过修改TaskTracker的hadoop-site.xml文件中的mapred.tasktracker.group属性,可以为TaskTracker分配一个组。用户在提交作业时,只能使用该用户所属组内的TaskTracker计算节点。用户所属的计算分组由mapred.job.groups确定,默认为default。

NameNode和JobTracker用于身份验证的用户/组信息和密码以明文的形式保存在hadoop-user-info.properties文件中,集群管理员通过修改该文件来进行用户的添加、删除、密码修改等操作。文件内容如下:

# Format: username=password,group1,group2,group3
root=rootpass,root
slave=slavepass,slave
user1=user1pass,usergroup1,usergroup2,…

其中至少应该包含root和slave两个用户,用于Hadoop系统进程与管理员操作的身份验证,root和slave的组必须设置为root和slave[1]。其余用户信息用于客户端连接的身份识别,每个用户可以同时属于多个组。

如果在集群启动后修改该文件,需要执行hadoop-v2/hadoop/bin/hadoop updateuser来更新用户信息。

Hadoop系统进程、客户端进程通过hadoop-site.xml中的hadoop.job.ugi属性配置用户名、密码以表明身份。其中NameNode、SecondaryNameNode和JobTracker必须配置为root用户,TaskTracker和DataTracker必须配置为slave用户。客户端通过配置管理员分配的用户名和密码,来获取HDFS文件访问以及MapReduce作业提交的相应权限。管理员通过在管理客户端(或直接在主控节点上)配置root用户,来获取对集群的管理权限。

[1] 为从节点分配slave用户,主要是为了屏蔽从节点对HDFS的访问权限。防止用户提交的任务通过调用从节点的hadoop客户端导致HDFS数据无意或恶意修改。

3.6 存储路径 

启动集群前通常需要正确配置Hadoop的存储路径,包括HDFS块文件、MapReduce中间结果等,关于存储路径的配置请参考4.2。

3.7 集群控制脚本 

Hadoop提供了若干脚本来负责Hadoop系统进程的启动与停止,这些脚本存放在hadoop-v2/hadoop/bin目录下。其中start-dfs.sh和stop-dfs.sh用于启动/停止HDFS,该脚本执行以下动作: 

  1. 在本地节点启动/停止NameNode
  2. 为slaves文件中的所有节点启动/停止DataNode
  3. 为masters文件中的所有节点启动/停止SecondaryNameNode

与之类似,start-mapred.sh和stop-mapred.sh用于启动/停止MapReduce系统: 

  1. 在本地节点启动/停止JobTracker
  2. 为slaves文件中的所有节点启动/停止TaskTracker

此外,还有一些脚本可以用来独立启动DataNode/TaskTracker以及NameNode/JobTracker,这样可以独立地控制节点是否启动相应的进程。例如,当有新的机器加入HDFS集群时,可以执行该节点上的start-datanode.sh来启动DataNode,NameNode会通过心跳检测到新加入节点并将之列入HDFS集群节点列表中。

3.8 启动集群 

正确配置后,就可以通过执行集群控制脚本来启动Hadoop集群。如果是第一次启动Hadoop集群,首先需要格式化HDFS。在NameNode以work用户执行:

$ cd /home/work/hadoop-v2/hadoop
$ bin/hadoop namenode –format

执行该命令后,会在dfs.name.dir中生成NameNode初始化元信息,HDFS只有在正确初始化才可以使用。接下来启动HDFS:

$ bin/start-dfs.sh

该控制脚本启动HDFS集群的NameNode、DataNode和SecondaryNameNode进程。如果集群正常启动,可以通过浏览器访问http://namenode.host:8070来查看集群状态。如果集群或有节点启动失败,可以查看$HADOOP_LOG_DIR中的日志来进行问题定位。NameNode HTTP服务器的端口号设置参考4.1。

在启动HDFS后,根据hadoop-site.xml中配置,HDFS可能会运行在安全模式,此时不能对HDFS进行元数据操作(新建、删除文件等)。关于安全模式的更多内容参考4.3。

在使用HDFS进行文件操作前,还需要初始化HDFS的目录结构:

$ bin/init-hdfs.sh

该命令会在hdfs中创建一些必要的目录及用户目录。使用以下命令确认HDFS是否初始化成功:

$ bin/hadoop fs –ls /
Found 5 items
drwxr-xr-x   3 root root          0 2010-01-03 00:04 /share
drwxr-xr-x   3 root root          0 2010-01-15 11:17 /system
drwxrwxrwx   2 root root          0 2010-01-03 00:04 /test
drwxrwxrwx   2 root root          0 2010-01-03 00:04 /tmp
drwxr-xr-x   3 root root          0 2010-01-06 10:52 /user

该命令将在终端输出HDFS根目录下的内容。可以通过上传下载文件来测试HDFS是否搭建成功:

$ echo ‘hdfs test’ > test_file
$ bin/hadoop fs -put test_file /user/root/
Found 6 items
drwxr-xr-x   3 root root          0 2010-01-03 00:04 /share
drwxr-xr-x   3 root root          0 2010-01-17 20:54 /system
drwxrwxrwx   2 root root          0 2010-01-03 00:04 /test
-rw-r--r--   3 root root          0 2010-01-18 10:21 /test_file
drwxrwxrwx   2 root root          0 2010-01-03 00:04 /tmp
drwxr-xr-x   3 root root          0 2010-01-06 10:52 /user
$ bin/hadoop fs -get /user/root/test_file ./

与HDFS类似,要启动MapReduce系统,在JobTracker上以hadoop用户执行:

$ cd /home/mapred/hadoop-v2/hadoop
$ bin/start-mapred.sh

JobTracker和所有TaskTracker将被启动,可以访问http://jobtracker.host:8030检查MapReduce是否启动成功。在实际操作中,建议不要使用start-dfs.sh来启动,而是分别单独启动Namenode,Secondary Namenode和Datanode,这样部分机器出了问题可以定位和解决,而不是全部重新来过。JobTracker HTTP服务器的端口号设置参考4.1。

4 其他Hadoop属性配置 

从hadoop-default.xml文件中可以看出,Hadoop提供了非常丰富的可配置选项。如果需要修改某个属性,一般不要直接改动hadoop-default.xml,而应该将要改动的属性拷贝到hadoop-site.xml中,再进行相应修改。可以将hadoop-site.xml的某个属性配置为final,来防止用户提交的MapReduce作业配置覆盖系统配置。本小节将介绍一些比较常用的配置参数。

4.1 地址与端口 

Hadoop系统进程通常使用两个端口,一个用于进程间的RPC通信,另一个HTTP端口用于浏览器访问。Hadoop允许配置进程使用的地址和端口,如果地址设为0.0.0.0,Hadoop将绑定到该主机的所有地址。否则,应该制定一个单独的绑定地址。如果端口号设为0, Hadoop将自由选择一个空闲的端口号,这并不是一种明智的做法。以下是与Hadoop进程地址/端口相关的配置列表:

属性描述
fs.default.name  HDFS URI,包括NameNode RPC主机名和端口 
dfs.http.address  NameNode的HTTP服务器地址和端口 
dfs.datanode.address  DataNode的数据传输地址和端口 
dfs.datanode.ipc.address  DataNode的RPC地址和端口 
dfs.datanode.http.address  DataNode的HTTP服务器地址和端口 
mapred.job.tracker  JobTracker的RPC地址和端口 
mapred.job.tarcker.http.address  JobTracker的HTTP服务器地址和端口 
mapred.task.tracker.report.address  TaskTracker的RPC地址/端口,用于子JVM通信 
mapred.task.tracker.http.address  TaskTracker的HTTP服务器地址和端口 
dfs.secondary.http.address  SecondaryNameNode的HTTP服务器地址和端口 

4.2 本地存储 

HDFS的元信息及数据物理上存储在NameNode和DataNode的本地磁盘上。此外运行Map/Reduce任务的过程中也会在TaskTracker本地生成大量临时文件。通过修改hadoop-site.xml可以更改HDFS和MapReduce的本地存储路径。

Hadoop允许配置多个存储路径,并尽量以Round-Robin的方式来使用这些路径。如果TaskTracker/DataNode有多块磁盘,并且将DataNode和TaskTracker的存储路径配置到多块磁盘上,那么Hadoop可以并行使用这些磁盘以提升I/O效率。NameNode的元数据editlog也可以配置在多个路径来进行必要的冗余,Hadoop官方推荐配置三个路径:本机分别配置在两块磁盘上,此外在NFS挂载点上配置一个路径,以在网络上提供一份备份。[1]HDFS的元数据操作在成功写到所有路径之前不会返回。与本地存储路径相关的属性包括:

属性描述
hadoop.tmp.dir  Hadoop本地存储的根目录 
dfs.name.dir  NameNode存储image的路径,多路径用逗号分隔 
dfs.name.edits.dir  NameNode存储editlog的路径,多路径用逗号分隔 
dfs.data.dir  DataNode存储块数据的路径,多路径用逗号分隔 
mapred.local.dir  TaskTracker存储中间结果的路径,多路径用逗号分隔 
mapred.temp.dir  MapReduce临时文件的共享路径 
fs.checkpoint.dir  SecondaryNameNode存储image的路径 
fs.checkpoint.edits.dir  SecondaryNameNode存储editlog的路径 

为了保证DataNode的磁盘空间不被HDFS数据块占满,造成磁盘错误,通常为DataNode数据所配置的磁盘卷设置一个预留空间,当磁盘卷空间不足时,再进行写入操作会跳过该卷。预留空间通过dfs.datanode.du.reserved参数设置,以KB为单位。

[1] 不推荐使用NFS作为NameNode元数据备份,因为NFS服务器故障会造成NameNode操作阻塞,导致HDFS服务中断。目前dpf推荐的方式是为只NameNode配置一个元数据路径,同时在SecondaryNameNode上配置rsync从NameNode拖数据,来进行HDFS元数据备份。

HDFS配置"> 4.3 HDFS配置 

HDFS是一种按大块存储的分布式文件系统,将一个大文件按固定块大小分布在集群的不同节点上。HDFS的块大小是一个比较重要的参数,它会影响HDFS和MapReduce的性能。块设置过大不利于数据存储、计算的负载均衡;块设置过小会带来TCP连接、计算任务初始化等额外开销。通常根据集群的计算、存储能力来设置块大小。HDFS块大小通过修改dfs.block.size配置。

HDFS提供块副本冗余以实现较好的容错性,默认每个块的副本个数为3。Hadoop-v2允许针对目录和文件来设置,目录和文件的块副本个数默认会继承其父目录的配置。 通过配置dfs.rootdir.replication可以设置HDFS根目录的块副本,也就是整个文件系统的默认块副本个数。

fs.trash.interval属性用来配置.Trash目录的checkpoint间隔。在删除文件时,HDFS并不将文件立即删除,而是放到.Trash目录中。如果用户发现误操作,可以从.Trash目录中恢复数据。HDFS会为.Trash目录中的文件以fs.trash.interval为间隔,进行删除操作。

HDFS在启动时会自动进入安全模式,在安全模式下,HDFS不会进行块复制操作,这就为DataNode登入提供一个时间窗,防止HDFS对块的误复制。此外,安全模式不允许用户对HDFS元数据进行写操作(包括新建、删除文件、修改权限等),但可以访问HDFS数据内容。通常在正式使用HDFS前,可以在安全模式下进行一些必要的检查。有两个参数控制NameNode何时退出安全模式:dfs.safemode.threshold.pct属性指示块登入百分比的阈值,当登入的块数足够多时,并且在等待dfs.safemode.extension属性设置一个额外的时间后,才退出安全模式。为了操作安全,推荐将dfs.safemode.threshold.pct设置为一个大于1的值,这就要求管理员手动退出安全模式,在这个时间内可以对进行一些必要的检查,可以使用以下命令退出安全模式:

% bin/hadoop dfsadmin -safemode leave

4.4 MapReduce配置 

Hadoop为MapReduce提供了丰富的配置参数,用户在提交作业时,可以根据自己的作业属性来配置这些参数,以最大化利用集群资源。这里介绍在搭建集群时需要注意配置的属性。

针对TaskTracker的硬件配置,可以为TaskTracker设置允许运行的最大作业个数,通过mapred.tasktracker.{map,reduce}.tasks.maximum两个参数来进行配置。这两个参数的设置通常需要兼顾节点的CPU数、内存大小和实际的作业特征。例如一台16GB物理内存配置的8核CPU的节点,mapred.tasktracker.map.tasks.maximum和mapred.tasktracker.reduce.task.maximum通常设置为8。

以下参数跟Map/Reduce的内存使用有关,可以根据集群的内存配置来调整,以减少I/O操作:

属性描述
io.sort.factor  Merge阶段同时排序的文件个数 
io.sort.mb  排序时的内存buffer大小 
io.sort.record.percent  当buffer中记录个数到达一定比例时,开启spill线程 
io.sort.spill.percent  当buffer占用达到一定比例时,开启spill线程 
fs.inmemory.size.mb  Reduce在归并Map输出时分配的in-memory文件系统大小 

5 客户端配置 

Hadoop集群允许在任意一台机器上安装Hadoop客户端来访问集群,也允许通过VFS挂载本地目录的方式来访问HDFS。

首先将hadoop-client目录分发到客户端节点。如果要访问Hadoop集群,首先要确保集群NameNode和JobTracker监听端口对客户端节点开放,之后通过一些简单的配置,就可以通过Hadoop客户端程序bin/hadoop来访问Hadoop集群,包括操作HDFS、提交MapReduce作业、集群管理等。在调用bin/hadoop前,至少需要修改conf/hadoop-site.xml文件中的fs.default.name、mapred.job.tracker以及hadoop.job.ugi,来配置Hadoop的NameNode、JobTracker地址,以及用户身份信息。关于bin/hadoop客户端工具的使用,请参考相应文档。

5.1 VFS 

hadoop-client中包含SOS开发的hadoop-vfs fuse模块,按照以下步骤安装、挂载hadoop-vfs: 

  1. 从ftp://jx-sos-scm.vm.baidu.com/home/public/tool/fuse下载内核对应的fuse模块,放到hadoop-client/hadoop-vfs/lib下
  2. 修改hadoop-site.xml的hadoop.job.ugi
  3. 执行hadoop-vfs/bin/mount-ro.sh NAMENODE_HOST:PORT MOUNT_PATH,将NAMENODE_HOST的HDFS根目录以只读的方式挂载到本地MOUNT_PATH下
  4. hadoop-vfs/bin目录下有相应的脚本来决定挂载方式,unmount.sh脚本用来卸载挂载点

关于hadoop-vfs的使用,请参考相应文档。

6 集群基准测试 

运行基准测试是检查集群是否正确配置的最佳方式,但是基准测试的结果跟集群的物理环境等有很大关系,所以该结果仅用于参考。同时,通过基准测试,可以帮助优化集群性能。在进行基准测试与性能优化时,经常需要监控系统的配合,来了解集群的资源使用情况。

为了获得最佳的测试结果,应该在集群正式交付使用前进行基准测试。一旦集群正式对外提供服务,那么就很难确定一个没有用户作业执行的时间,而用户提交的作业会影响基准测试结果。

Hadoop提供了若干个基准测试程序,打包在hadoop-v2-test.jar中。通过执行以下命令可以获得一个基准测试程序列表:

% bin/hadoop jar hadoop-*-test.jar

大多数测试程序的使用说明都可以通过不加参数运行该程序的方式来查看:

% bin/hadoop jar hadoop-*-test.jar TestDFSIO
TestFDSIO.0.0.4
Usage: TestFDSIO -read | -write | -clean [-nrFiles N] [-fileSize MB] [-resFile
resultFileName] [-bufferSize Bytes]

6.1 使用TestDFSIO进行HDFS基准测试 

经验表明,对于一个新集群来说,硬盘失效是最常发生的硬件故障。可以在正式使用集群前,运行I/O基准测试来“烧”集群,以发现潜在的硬件故障。TestDFSIO通过启动一个MapReduce作业并行执行文件读写来测试HDFS的I/O性能。每个文件在一个单独的Map任务中读或写,通过Map的输出来收集所处理文件的统计信息,该信息在Reduce中进行累加,并生成汇总报告。

以下命令并行写10个文件,每个文件1000MB:

% bin/hadoop jar hadoop-*-test.jar TestDFSIO -write -nrFiles 10 -fileSize 1000
% cat TestDFSIO_results.log
----- TestDFSIO ----- : write
Date & time: Sun Apr 12 07:14:09 EDT 2009
Number of files: 10
Total MBytes processed: 10000
Throughput mb/sec: 7.796340865378244
Average IO rate mb/sec: 7.8862199783325195
IO rate std deviation: 0.9101254683525547
Test exec time sec: 163.387

使用-read参数来进行读性能基准测试。注意读基准测试一定要使用已存在(由TestDFSIO -write写入)的文件:

% bin/hadoop jar hadoop-*-test.jar TestDFSIO -read -nrFiles 10 -fileSize 1000

完成测试后,可以执行-clean参数来删除已生成的文件:

% bin/hadoop jar hadoop-*-test.jar TestDFSIO -clean

6.2 使用Sort进行MapReduce基准测试 

通过Sort基准测试程序可以测试整个MapReduce系统,完整的输入数据集将会经过Shuffle阶段。完整运行Sort程序总共需要三个步骤:生成随机数据、排序以及验证结果。

首先使用RandomWriter来生成一些随机数据。RandomWriter在每个节点运行10个Map任务,每个Map产生大约10GB的随机二进制数据。执行以下命令将生成的随机数据写入random-data目录:

% bin/hadoop jar hadoop-*-examples.jar randomwriter random-data

接下来我们可以运行Sort 程序:

% bin/hadoop jar hadoop-*-examples.jar sort random-data sorted-data

最后通过SortValidator程序来验证结果:

% bin/hadoop jar hadoop-*-test.jar testmapredsort -sortInput random-data -sortOutput sorted-data

6.3 其他测试 

Hadoop提供了丰富的基准测试程序,但以下程序比较常用: 

  • MRBench运行多次小MapReduce任务。作为Sort测试的补充,用来测试MapReduce系统对小作业的响应速度。
  • NNBench是一个非常有用的测试程序,用来对NameNode进行负载测试。
  • Gridmix是一套根据实际应用中集群的负载来设计的测试系统,Gridmix模拟了现实中的多种数据访问模式。

除此之外,为了更好地测试集群并进行性能调优,还需要实际运行用户作业,针对用户作业来进行集群测试和性能调整。

DMOP通用项设置 其他HADOOP外部环境设置"> 7 DMOP通用项设置 & 其他HADOOP外部环境设置 

DMOP监管的集群机器必须符合DMOP小组所指定的通用项设置,主要指新服务器上线部署应用前各操作系统环境的检查项,适用于各产品线。 

7.1 系统账户 

DMOP集群的机器要求所有机器必须有root、work和rd账户,分别用在权限操作、管理和使用机器、提供DMOP组外的使用三种情况下。由于Hadoop环境的需要,在搭建了Hadoop平台的所有机器上也必须提供mapred账户。Hadoop平台使用work账户进行HDFS的相关操作,使用mapred账户进行Map/Reduce的相关操作。

各系统账户家目录的访问权限和所有权限如下:

账户访问权限所有权限
root  750  root:root 
work  755  work:work 
rd  755  rd:rd 
mapred  755  mapred:mapred 

另外,work账户下必须同时提供3个子目录,分别为:

子目录作用
~work/opdir/backup  用于备份日常操作中一些数据、文件或软件包 
~work/opdir/core_save  用于保存系统core文件 
~work/opbin/crontab  用于存储crontab的相关文件,如bash脚本 

7.2 SSH 信任关系 

DMOP集群的机器要求所有机器必须有root、work和rd账户,分别用在权限操作、管理和使用机器、提供DMOP组外的使用三种情况下。由于Hadoop环境的需要,在搭建了Hadoop平台的所有机器上也必须提供mapred账户。

Hadoop环境中,需要建立的信任关系:

  • 建立运维主控机dmop账户[1]到Hadoop Master机(namenode和job_tracker) root、work、mapred账号的信任关系: 
    • 将dmop账户生成的rsa公钥追加到Hadoop Master机root账户.ssh下
    • 将dmop账户生成的rsa公钥追加到Hadoop Master机work账户.ssh下
    • 将dmop账户生成的rsa公钥追加到Hadoop Master机mapred账户.ssh下 
  • 建立运维主控机dmop账户到所有Hadoop Slave机(datanode)work账户、所有Hadoop Slave机(task_tracker)mapred账户的信任关系: 
    • 将dmop账户生成的rsa公钥追加到所有Hadoop Slave机(datanode)work账户的.ssh下
    • 将dmop账户生成的rsa公钥追加到所有Hadoop Slave机(task_tracker)mapred账户的.ssh下
  • 建立Hadoop Master机work账户到所有Hadoop Slave机(datanode)work账户的信任关系: 
    • 将Hadoop Master机work账户生成的rsa公钥追加到所有Hadoop Slave机(datanode)work账户的.ssh下
  • 建立Hadoop Master机mapred账户到所有Hadoop Slave机(task_tracker)mapred账户的信任关系: 
    • 将Hadoop Master机mapred账户生成的rsa公钥追加到所有Hadoop Slave机(task_tracker)mapred账户的.ssh下

[1] 这里的dmop账户,是一个逻辑上的概念,即指运维管理者的账户(主控机)。

根据上述的规范,对于新增的节点,我们需要建立主控机到新增节点的work和mapred账户的信任关系,以及master机器到新增节点的work和mapred账户的信任关系。(当然,为了部署的方便,我们可以在Hadoop环境搭建过程中临时建立从主控机到各新增节点root账户的信任关系,但建议在开启服务前将该信任关系删除!)

7.3 sshd进程是否以init方式启动 

在/etc/inittab文件的action字段增加如下记录:

7:3:respawn:/usr/sbin/sshd -D 

然后执行以下语句:

chkconfig --level 3 sshd off
% bash -c '/etc/init.d/sshd stop && telinit q'

运行telinit q可以让init进程重新读取/etc/inittab。

另外,之所以使用-D选项启动sshd进程,是因为:

    -D      When this option is specified, sshd will not detach and does not become a daemon.  
            This allows easy monitoring of sshd.

最后,执行

% chkconfig --list sshd
% ps aux | grep sshd 

来对sshd进行检测。

7.4 与磁盘IO子系统有关的系统设置接口 

/proc/sys/vm/dirty_ratio

这个参数控制文件系统的文件系统写缓冲区的大小,单位是百分比,表示系统内存的百分比,表示当写缓冲使用到系统内存多少的时候,开始向磁盘写出数据。增大之会使用更多系统内存用于磁盘写缓冲,也可以极大提高系统的写性能。但是,当你需要持续、恒定的写入场合时,应该降低其数值,一般启动上缺省是 10。下面是增大的方法: 

% echo '40' > /proc/sys/vm/dirty_ratio

/proc/sys/vm/dirty_background_ratio

这个参数控制文件系统的pdflush进程,在何时刷新磁盘。单位是百分比,表示系统内存的百分比,意思是当写缓冲使用到系统内存多少的时候,pdflush开始向磁盘写出数据。增大之会使用更多系统内存用于磁盘写缓冲,也可以极大提高系统的写性能。但是,当你需要持续、恒定的写入场合时,应该降低其数值,一般启动上缺省是 5。下面是增大的方法: 

% echo '20' > /proc/sys/vm/dirty_background_ratio

7.5 部署diskcheck 

diskcheck主要完成对硬盘故障的自动检测与报修,此外diskcheck工具包中还包含了新硬盘重新上线的脚本程序recover.sh。参考以下步骤完成对diskcheck的部署:

  • 下载硬盘检测与恢复工具包:ftp://jx-sos-scm.vm.baidu.com/home/public/tool/diskcheck.tar.gz,解压并进入diskcheck目录: 
% tar -xzf diskcheck.tar.gz
% cd diskcheck
  • 解压后目录结构如下:
diskcheck/
    -- bin/                可执行文件及一些脚本工具
    -- conf/               配置文件目录
    -- diskcheck_contrl    硬盘自动检测与报修工具总控脚本
    -- log/                logcheck日志
    -- recover.sh          新磁盘恢复上线脚本
    -- rstatus/            logcheck保留,记录内核日志offset
    -- supervise/          supervise工具,用于启动硬盘检测工具
    -- tmp/
  • 修改conf/diskcheck.conf,根据集群具体情况进行配置
  • conf/logcheck.conf为logcheck配置文件,一般不用修改;唯一可能需要修改的配置项是MONITOR_PERIOD_0,该配置项控制对硬盘故障的检测周期(不建议修改)
  • 执行以下命令启动自动检测与报修程序: 
% ./diskcheck_contrl start

7.6 将机器列表加入到listhost 串 

以增加机器到串 dmop-dpf-test-machine为例,操作步骤如下:   

  1. 登陆hadoop@jx-dmop-mon00.jx,密码为:hadoop
  2. cd /home/hadoop/changelh
  3. 修改需要新增到lh串的机器列表,假设为new.lst文件,则new文件的每一行将作为一个机器域名来对待
  4. 执行增加机器到lh串的命令
$ sh run.sh add new.lst dmop-dpf-test-machine

  操作完成后,可以通过执行以下命令检查操作是否成功:

$ lh –h dmop-dpf-test-machine
原文地址:https://www.cnblogs.com/lushilin/p/6526024.html