深入理解 HDFS(二):Replica

534 阅读6分钟

深入理解 HDFS(二):Replica

对于我们上传的文件,HDFS会复制多份,主要是为了降低数据丢失的风险,复制文件背后是复制 block,block 最终会存储在 datanode 中,那么在该过程中 namenode 参考了哪些参数选择的 datanode,内部处理的主要流程是我们今天研究的重点。

1. 复制粒度

对于原始的 block 和复制的 block,下面统一叫做 replication,通过设置相关参数可以细粒度的控制复制过程:

  • dfs.blocksize: replica 的大小,默认128m;
  • dfs.replication: 副本数量,默认 3,生成 replica 的过程是串行的,同一时刻只能有一个 writer;
  • dfs.replication.max: 副本最大数量,默认 512,主要用于对实际的 dfs.replication 进行校验;
  • dfs.namenode.replication.min: 副本最小数量,默认 1,主要用于 replica 生成后数量的校验。

2. 参考参数

参考的参数从类别上可分为:客户端参数和客户端参数。

客户端参数:

  • CreateFlag.NO_LOCAL_WRITE: 防止写入和客户端相同 IP 的 datanode,例如上传文件的应用程序所在节点运行着 datanode 实例,默认策略下如果未制定创建行为,这个 datanode 实例会存储着所有数据的 1 个副本;
  • **CreateFlag.NO_LOCAL_RACK:**防止写入和客户端相同机架的 datanode,和 NO_LOCAL_WRITE 类似,只不过 datanode 换做成同机架下的所有 datanode 实例。
  • favoredNodes: 如何服务端开启了 dfs.namenode.block-placement-policy.default.prefer-local-node 的设置,客户端上传文件时,通过 DistributedFileSystem.createFile -> HdfsDataOutputStreamBuilder.favoredNodes 进行设置,namenode 会根据上传的 favoredNodes 优先选择。

服务端参数:

  • dfs.block.replicator.classname: replication 分配策略,直接影响到 replication 分配到哪个 datanode 中,默认分配策略 BlockPlacementPolicyDefault,除此之外还有 BlockPlacementPolicyRackFaultTolerant 和BlockPlacementPolicyWithNodeGroup 比较常用这里会重点讲解,其余的分配策略还有AvailableSpaceBlockPlacementPolicy、AvailableSpaceRackFaultTolerantBlockPlacementPolicy和BlockPlacementPolicyWithUpgradeDomain,细节方面大家可查看下官方文档,后续我会酌情补充。
  • dfs.replication: 副本数量,默认 3,会决定选择 datanode 数量;
  • dfs.namenode.avoid.write.stale.datanode: 不允许写入处于 stale 状态的 datanode,默认 false;
  • dfs.namenode.write.stale.datanode.ratio: 按照 stale 和 liven 状态分别统计 datanode 数量,如果二者比值小于等于这个参数,即使设置了 dfs.namenode.avoid.write.stale.datanode = true ,也会选择处于 stale 状态下的 datanode。

3. Replica 分配策略

我们先分析其原理,之后探讨应用场景,假设搭建了下面的集群,集群的副本数为 3:

image-20230805181353301.png

3.1 BlockPlacementPolicyDefault:默认策略

3.1.1 未配置机架感知

所有 datanode 会默认分配到 /default-rack 虚拟机架中,屏蔽某些次要参数,该策略主要处理流程如下:

  1. 校验客户端所在节点是否有 datanode 实例:
    1. 如果有:选择该 datanode;
    2. 如果没有:从可用的 datanode 列表中随机选择 1 个。
  2. 对于每次选择的 datanode 都需要在可用列表中删除;
  3. 依次从可用列表中选择第 2 个、第 3 个。

image-20230805212104073.png

3.1.2 机架感知

如果想让你的 HDFS 集群开启机架感知,你需要做几件事:

  1. 编写脚本:主要目的是为了查询 datanode 所在机架,脚本只有 1 个输入参数主机 ip 或主机名,所以你需要根据主机 ip 或主机名输出匹配的机架信息,可参考下面的脚本进行配置:

    #!/bin/bash
    declare -A topology
    topology["172.17.48.1"]="rack1"
    topology["172.17.48.2"]="rack1"
    topology["172.17.48.3"]="rack2"
    topology["172.17.48.4"]="rack2"
    topology["172.17.48.5"]="rack3"
    topology["172.17.48.6"]="rack3"
    topology["datanode-001"]="rack1"
    topology["datanode-002"]="rack1"
    topology["datanode-003"]="rack2"
    topology["datanode-004"]="rack2"
    topology["datanode-005"]="rack3"
    topology["datanode-006"]="rack3"
    rack=${topology[$1]}
    [[ ${#rack} > 0 ]] && echo "/bj/$rack" || echo "/default-rack"
    
  2. 在 core-site.xml 中配置 net.topology.script.file.name 的值为脚本的完整路径名;

    <property>
      <name>net.topology.script.file.name</name>
      <value>/home/df66a0d7/topology.sh</value>
    </property>
    
  3. 重启集群后,输出集群拓补结构:hdfs dfsadmin -printTopology

image-20230805195507900.png

从结果可以看出 HDFS 已经开启了机架感知,这时候主要处理流程如下:

  1. 校验客户端所在节点是否有 datanode 实例:
    1. 如果有:选择该 datanode;
    2. 如果没有:从可用的 datanode 列表中随机选择 1 个。
  2. 从第 1 个 datanode 所在机架外的其他机架,随机选择 1 个 datanode 作为第 2 个;
  3. 在第 2 个 datanode 所在机架随机选择 1 个作为第 3 个。

image-20230805212144701.png

3.2 BlockPlacementPolicyRackFaultTolerant:贪婪策略

该策略的主要目是尽可能的在不同的机架上选择 datanode,主要处理流程:

  1. 通过调用 getMaxNodesPerRack 计算平均每个机架可选择的最大 datanode 数量;
  2. 根据计算的 maxNodesPerRack 调用 chooseTargetInOrder 一次性选择和副本数相同数量的 datanode;
  3. 为了保证选择的 datanode 和副本数相等,内部又做了很多保证机制。

image-20230805212205471.png 如果决定使用贪婪策略,你需要编写脚本,开启机架感知功能,同时在 hdfs-site.xml 中需要设置属性:

<property>
  <name>net.topology.script.file.name</name>
  <value>/home/df66a0d7/topology.sh</value>
</property>
<property>
  <name>dfs.block.replicator.classname</name>
  <value>org.apache.hadoop.hdfs.server.blockmanagement.BlockPlacementPolicyRackFaultTolerant</value>
</property>

3.3 BlockPlacementPolicyWithNodeGroup:分组策略

1 个 42U 19英寸的机柜,除了预留散热和交,换机位置,可以轻松部署 16 台 1U服务器。在同一个机架内,如果你想更细粒度的对 datanode 进行分组选择,可以试试分组策略。

image-20230805235920313.png

分组策略内部处理流程:

  1. 校验客户端所在节点是否有 datanode 实例:
    1. 如果有:选择该 datanode;
    2. 如果没有:从可用的 datanode 列表中随机选择 1 个。
  2. 从第 1 个 datanode 所在机架外的其他机架,随机选择 1 个 datanode 作为第 2 个;
  3. 在第 2 个 datanode 所在机架不同的 node-group 中选择 1 个 作为第 3 个。

image-20230806005439222.png

使用分组策略你需要进行如下设置:

  1. 开启机架感知:

    #!/bin/bash
    declare -A topology
    topology["172.17.48.1"]="/rack1/group11"
    topology["172.17.48.2"]="/rack1/group11"
    topology["172.17.48.3"]="/rack2/group21"
    topology["172.17.48.4"]="/rack2/group21"
    topology["172.17.48.5"]="/rack2/group22"
    topology["172.17.48.6"]="/rack2/group22"
    topology["datanode-001"]="/rack1/group11"
    topology["datanode-002"]="/rack1/group11"
    topology["datanode-003"]="/rack2/group21"
    topology["datanode-004"]="/rack2/group21"
    topology["datanode-005"]="/rack2/group22"
    topology["datanode-006"]="/rack2/group22"
    rack=${topology[$1]}
    [[ ${#rack} > 0 ]] && echo "$rack" || echo "/default-rack/group1"
    
  2. 在 core-site.xml 中设置:

    <property>
      <name>net.topology.impl</name>
      <value>org.apache.hadoop.net.NetworkTopologyWithNodeGroup</value>
    </property>
    <property>
      <name>net.topology.nodegroup.aware</name>
      <value>true</value>
    </property>
    
  3. 在 hdfs-site.xml 中设置:

    <property>
      <name>net.topology.script.file.name</name>
      <value>/home/df66a0d7/topology.sh</value>
    </property>
    <property>
      <name>dfs.block.replicator.classname</name>
      <value>
        org.apache.hadoop.hdfs.server.blockmanagement.BlockPlacementPolicyWithNodeGroup
      </value>
    </property>
    

4. 快速搭建 HDFS

4.1 主机列表

172.17.48.7 namenode

172.17.48.1 datanode-001
172.17.48.2 datanode-002
172.17.48.3 datanode-003
172.17.48.4 datanode-004
172.17.48.5 datanode-005
172.17.48.6 datanode-006

4.2 跳转主机环境准备

我通常会选择 namenode 作为跳转主机,下面使用 root 用户操作:

# 确保当前用户为 172.17.48.10 root用户
# 1. 生成密钥信息,一路回车即可
ssh-keygen

# 2. 安装 pdsh 可以批量操作主机
yum install -y pdsh

# 3. 将 namenode 的密钥复制到其他主机,包括自己的主机
for i in `seq 1 7`
do
  # 输出其他主机密码
  ssh-copy-id 172.17.48.$i
done

# 4. 编辑:/etc/hosts
echo "172.17.48.7 namenode" >> /etc/hosts
for i in `seq 1 6`
do
  echo "172.17.48.$i datanode-00$i" >> /etc/hosts
done

# 5. 编辑:/etc/profile
vim /etc/profile

export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.372.b07-1.el7_9.x86_64
export HADOOP_HOME=/home/5CC99CC1/hadoop-3.3.6
export PATH=$PATH:$HOME/.local/bin:$HOME/bin:$JAVA_HOME/bin:$HADOOP_HOME/bin
export ips="172.17.48.1,172.17.48.2,172.17.48.3,172.17.48.4,172.17.48.5,172.17.48.6,172.17.48.7"

alias n1="ssh namenode"
alias d1="ssh datanode-001"
alias d2="ssh datanode-002"
alias d3="ssh datanode-003"
alias d4="ssh datanode-004"
alias d5="ssh datanode-005"
alias d6="ssh datanode-007"

# 6. 传送给其他节点
for i in `seq 1 7`
do
  scp /etc/hosts 172.17.48.$i:/etc/hosts
  scp /etc/profile 172.17.48.$i:/etc/profile
done

4.3 安装 JDK

在跳转机上使用 root 用户操作。

#!/bin/bash
# 关闭防火墙,ips 参考 4.2
pdsh -w $ips "systemctl stop firewalld"
pdsh -w $ips "systemctl disable firewalld"

# 安装JDK
pdsh -w $ips "echo y | yum install java-1.8.0-openjdk-devel"

# 添加用户,最好随机生成用户名和密码,避免使用 hadoop:hadoop,很容易被破解
pdsh -w $ips "adduser -m 5CC99CC1 -G wheel"
pdsh -w $ips "echo '5CC99CC1:CFA02D2C68D5'|chpasswd"

4.4 安装 HDFS

在跳转机上使用 5CC99CC1 用户操作。

#!/bin/bash
# 1. 通用
NAME=hadoop-3.3.6
FILE=$NAME.tar.gz
HOME=/home/5CC99CC1
TMP_HOME=$HOME/dfs/tmp

# 2. NameNode
NAME_NODE_NAME=hadoop-3.3.6
NAME_NODE_HOST=namenode
NAME_NODE_PORT=8020
NAME_NODE_STORAGE=$HOME/dfs/name
NAME_NODE2_STORAGE=$HOME/dfs/namesecondary

# 3. DataNode
DATA_NODE_NAME=hadoop-3.3.6
DATA_NODE_HOST=datanode-001,datanode-002,datanode-003,datanode-004,datanode-005,datanode-006
DATA_NODE_STORAGE=$HOME/dfs/data
DATA_NODE_REPLICATION=3

# 4. 安装 DataNode 
target=$(cd "$(dirname "$0")"; pwd)
cd $target

# 5. 处理安装包
if [ ! -f $FILE ]
then
 wget https://mirrors.aliyun.com/apache/hadoop/common/hadoop-3.3.6/hadoop-3.3.6.tar.gz?spm=a2c6h.25603864.0.0.2e278a13NqaPHe -O hadoop-3.3.6.tar.gz
fi

tar -xvf $FILE

# 6. 设置 core-site.xml
cat << EOF > $NAME/etc/hadoop/core-site.xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<!--
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License. See accompanying LICENSE file.
-->

<!-- Put site-specific property overrides in this file. -->
<configuration>
    <property>
        <name>fs.defaultFS</name>
        <value>hdfs://$NAME_NODE_HOST:$NAME_NODE_PORT</value>
    </property>
    <property>
        <name>hadoop.tmp.dir</name>
        <value>$TMP_HOME</value>
    </property>
</configuration>
EOF

# 7. 设置 hdfs-site.xml
cat << EOF > $NAME/etc/hadoop/hdfs-site.xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<!--
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License. See accompanying LICENSE file.
-->

<!-- Put site-specific property overrides in this file. -->
<configuration>
  <property>
    <name>dfs.namenode.name.dir</name>
    <value>$NAME_NODE_STORAGE</value>
  </property>
  <property>
    <name>dfs.namenode.checkpoint.dir</name>
    <value>$NAME_NODE2_STORAGE</value>
  </property>
  <property>
    <name>dfs.datanode.data.dir</name>
    <value>$DATA_NODE_STORAGE</value>
  </property>
  <property>
    <name>dfs.replication</name>
    <value>$DATA_NODE_REPLICATION</value>
  </property>
</configuration>
EOF

# 8. 设置环境变量 hadoop-env.sh
HADOOP_ENV=$NAME/etc/hadoop/hadoop-env.sh
echo "JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.372.b07-1.el7_9.x86_64" >> $HADOOP_ENV
# 可以方便调试 namenode 和 datanode
echo 'export HDFS_NAMENODE_OPTS="-Xmx3g -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=9999"'  >> $HADOOP_ENV
echo 'export HDFS_DATANODE_OPTS="-Xmx3g -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=9998"' >> $HADOOP_ENV

# 9. 同步 datanode 节点
mv $NAME $DATA_NODE_NAME
for node in ${DATA_NODE_HOST//,/ }
do
  scp -r $DATA_NODE_NAME $node:$HOME
done

# 10. 同步 namenode 节点
for node in ${DATA_NODE_HOST//,/ }
do
  echo $node >> $DATA_NODE_NAME/etc/hadoop/workers.tmp
done
mv $DATA_NODE_NAME/etc/hadoop/workers.tmp $DATA_NODE_NAME/etc/hadoop/workers
mv $DATA_NODE_NAME $NAME_NODE_NAME
scp -r $NAME_NODE_NAME $NAME_NODE_HOST:$HOME

4.5 启动 HDFS

在跳转机上使用 5CC99CC1 用户操作。

# format 
$HADOOP_HOME/bin/hdfs namenode -format
# 启动 namenode 
$HADOOP_HOME/bin/hdfs --daemon start namenode

# 启动 datanode
pdsh -w $ips "/home/5CC99CC1/hadoop-3.3.6/bin/hdfs --daemon start datanode"

4.6 模拟上传测试

使用 root 用户安装fio

yum install -y fio

脚本功能:生成指定大小文件上传到 HDFS,之后查看该文件的 block 分配情况。

#!/bin/bash
# 上传 1m 文件:sh upload.sh 1m
fio -filename=$1 -direct=1 -ioengine=libaio -bs=4k -size=$1 -numjobs=1 -iodepth=16 -runtime=1 -thread -rw=write -group_reporting -name="write_test"
hdfs dfs -copyFromLocal $1 /tmp
hdfs fsck /tmp/$1  -files -locations -blocks

有问题欢迎大家留言,我们一起讨论。