HDFS集群部署
Helm 部署hadoop集群
参考:
- https://gitee.com/hadoop-bigdata/hadoop-on-kubernetes
- https://artifacthub.io/packages/helm/apache-hadoop-helm/hadoop
Hadoop HDFS
HDFS的基础架构
全称:Hadoop Distributed File System
- NameNode:
- HDFS系统的主角色,是一个独立的进程
- 负责管理HDFS整个文件系统
- 负责管理DataNode
- DataNode:
- HDFS系统的从角色,是一个独立的进程
- 负责管理存储的数据块,即存入数据和取出数据
- 负责执行来自NameNode的读写请求
- Secondary NameNode:
- NameNode的辅助角色,是一个独立的进程
- 负责辅助NameNode管理元数据
HDFS集群部署流程
下载:
- https://mirrors.tuna.tsinghua.edu.cn/apache/hadoop/common/hadoop-3.3.6/hadoop-3.3.6.tar.gz
- https://dlcdn.apache.org/hadoop/common/hadoop-3.3.6/hadoop-3.3.6.tar.gz
准备环境:
dnf install https://home.vimll.com:9999/download/jdk/jdk-8u361-linux-x64.rpm
# hosts
192.168.9.20 Vimller-AnolisOS
useradd hadoop
# root hadoop 用户各节点免密互通:
su - hadoop
ssh-keygen -t rsa -b 4096
ssh-copy-id hadoop@Vimller-AnolisOS
ssh-copy-id root@Vimller-AnolisOS
# 创建数据存储目录
mkdir -p /data/{dn,nn}
解压:
tar -zxvf hadoop-3.3.6.tar.gz -C /usr/local/src/
ln -s /usr/local/src/hadoop-3.3.6 /usr/local/hadoop
cd /usr/local/hadoop
目录授权:
chown -R hadoop:hadoop /usr/local/hadoop
chown -R hadoop:hadoop /usr/local/src/hadoop-3.3.6
chown -R hadoop:hadoop /data
各个文件夹含义如下
- bin,存放Hadoop的各类程序(命令)
- etc,存放Hadoop的配置文件
- include,C语言的一些头文件
- lib,存放Linux系统的动态链接库(.so文件)
- libexec,存放配置Hadoop系统的脚本文件(.sh和.cmd)
- licenses-binary,存放许可证文件
- sbin,管理员程序(super bin)
- share,存放二进制源码(Java jar包)
配置HDFS集群,我们主要涉及到如下文件的修改: cd /usr/local/hadoop/etc/hadoop
- workers: 配置从节点(DataNode)有哪些
- hadoop-env.sh: 配置Hadoop的相关环境变量
- core-site.xml: Hadoop核心配置文件
- hdfs-site.xml: HDFS核心配置文件
这些文件均存在与$HADOOP_HOME/etc/hadoop文件夹中。
ps:$HADOOP_HOME是后续我们要设置的环境变量,表示Hadoop安装文件夹为/usr/local/hadoop
修改配置文件,应用自定义设置:
配置workers文件
# 编辑workers文件
vim /usr/local/hadoop/etc/hadoop/workers
# 填入各节点hostname,注意不要有空格
Vimller-AnolisOS
data-node1
data-node2
配置hadoop-env.sh文件
vim /usr/local/hadoop/etc/hadoop/hadoop-env.sh
# 填入如下内容
export JAVA_HOME=/usr/java/jdk1.8.0_361-amd64
export HADOOP_HOME=/usr/local/hadoop
export HADOOP_CONF_DIR=$HADOOP_HOME/etc/hadoop
export HADOOP_LOG_DIR=$HADOOP_HOME/logs
export HADOOP_PID_DIR=$HADOOP_HOME/pids
vim /etc/profile
# 在/etc/profile文件底部追加如下内容
export HADOOP_HOME=/usr/local/hadoop
export PATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin
配置core-site.xml文件
- key:fs.defaultFS
- 含义:HDFS文件系统的网络通讯路径
- 值:hdfs://Vimller-AnolisOS:8020
- 协议为hdfs://
- namenode为Vimller-AnolisOS
- namenode通讯端口为8020
- key:io.file.buffer.size
- 含义:io操作文件缓冲区大小
- 值:131072 bit
hdfs://Vimller-AnolisOS:8020为整个HDFS内部的通讯地址,应用协议为hdfs://(Hadoop内置协议)
表明DataNode将和Vimller-AnolisOS的8020端口通讯,Vimller-AnolisOS是NameNode所在机器
此配置固定了Vimller-AnolisOS必须启动NameNode进程
vim /usr/local/hadoop/etc/hadoop/core-site.xml
<configuration>
<property>
<name>fs.defaultFS</name>
<value>hdfs://Vimller-AnolisOS:8020</value>
</property>
<property>
<name>io.file.buffer.size</name>
<value>131072</value>
</property>
</configuration>
配置hdfs-site.xml文件
- key:dfs.replication
- 含义:HDFS副本数量
- 值:3
- key:dfs.namenode.name.dir
- 含义:NameNode元数据存放路径
- 值:/usr/local/hadoop/hdfs/name
- key:dfs.datanode.data.dir
- 含义:DataNode数据存放路径
- 值:/usr/local/hadoop/hdfs/data
- key:dfs.datanode.data.dir.perm
- 含义:DataNode数据存放路径权限
- 值:700
- key:dfs.namenode.handler.count
- 含义:NameNode处理RPC请求的线程数量
- 值:100
- key:dfs.blocksize
- 含义:HDFS数据块大小
- 值:268435456 bit
- key: dfs.namenode.hosts
- 含义:NameNode所在机器
- 值:node1,node2,node3 (具体机器名)
# 在文件内部填入如下内容
<configuration>
<property>
<name>dfs.replication</name>
<value>1</value>
</property>
<property>
<name>dfs.datanode.data.dir.perm</name>
<value>700</value>
</property>
<property>
<name>dfs.namenode.name.dir</name>
<value>/data/nn</value>
</property>
<property>
<name>dfs.namenode.hosts</name>
<value>Vimller-AnolisOS</value>
</property>
<property>
<name>dfs.blocksize</name>
<value>268435456</value>
</property>
<property>
<name>dfs.namenode.handler.count</name>
<value>100</value>
</property>
<property>
<name>dfs.datanode.data.dir</name>
<value>/data/dn</value>
</property>
</configuration>
其他datanode节点通用操作
- 增加hadoop用户与环境变量
- jdk环境变量配置
- 数据目录创建及授权
分发Hadoop文件夹
# 在node1执行如下命令
cd /usr/local/src
scp -r hadoop-3.3.6 data-node1:`pwd`/
scp -r hadoop-3.3.6 data-node2:`pwd`/
scp -r hadoop-3.3.6 kube-41:`pwd`/
scp -r hadoop-3.3.6 kube-42:`pwd`/
ln -s /usr/local/src/hadoop-3.3.6 /usr/local/hadoop
chown -R hadoop:hadoop /usr/local/hadoop
chown -R hadoop:hadoop /usr/local/src/hadoop-3.3.6
# 各节点操作
ln -s /usr/local/src/hadoop-3.3.6 /usr/local/hadoop
vim /etc/profile
# 在/etc/profile文件底部追加如下内容
export HADOOP_HOME=/usr/local/hadoop
export PATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin
注意:如果不使用HOSTNAME使用IP地址,需要修改hdfs-site.xml文件
在namenode的hdfs-site.xml 里面添加
<property>
<name>dfs.namenode.datanode.registration.ip-hostname-check</name>
<value>false</value>
</property>
HDFS集群初始化
# 确保以hadoop用户执行
su - hadoop
# namenode节点操作格式化
hdfs namenode -format
# 启动集群
# 一键启动hdfs集群
start-dfs.sh
# 一键关闭hdfs集群
stop-dfs.sh
# 如果遇到命令未找到的错误,表明环境变量未配置好,可以以绝对路径执行
/usr/local/hadoop/sbin/start-dfs.sh
/usr/local/hadoop/sbin/stop-dfs.sh
$ jps # 查看java进程
3457511 NameNode
3457649 DataNode
3458161 Jps
3457982 SecondaryNameNode
查看web界面 nameode:9870
- http://192.168.9.20:9870/dfshealth.html#tab-overview
HDFS 集群启停命令
一键启停脚本
# Hadoop HDFS组件内置了HDFS集群的一键启停脚本。 $HADOOP_HOME/sbin/start-dfs.sh # 一键启动HDFS集群 # 执行原理: # 在执行此脚本的机器上,启动SecondaryNameNode # 读取core-site.xml内容(fs.defaultFS项),确认NameNode所在机器,启动NameNode # 读取workers内容,确认DataNode所在机器,启动全部DataNode $HADOOP_HOME/sbin/stop-dfs.sh # 一键关闭HDFS集群 # 执行原理: # 在执行此脚本的机器上,关闭SecondaryNameNode # 读取core-site.xml内容(fs.defaultFS项),确认NameNode所在机器,关闭NameNode # 读取workers内容,确认DataNode所在机器,关闭全部NameNode
单进程启停
# 除了一键启停外,也可以单独控制进程的启停。已过时,不推荐使用。 $HADOOP_HOME/sbin/hadoop-daemon.sh # 此脚本可以单独控制所在机器的进程的启停 # 用法:hadoop-daemon.sh (start|status|stop) (namenode|secondarynamenode|datanode) # 推荐使用hdfs脚本,因为hdfs脚本会读取core-site.xml和workers文件,自动识别集群配置,而hadoop-daemon.sh脚本不会。 $HADOOP_HOME/bin/hdfs # 此程序也可以用以单独控制所在机器的进程的启停 # 用法:hdfs --daemon (start|status|stop) (namenode|secondarynamenode|datanode)
HDFS文件系统基本信息
HDFS同Linux文件系统类似,也有目录和文件的概念。也是以/作为目录的组织形式,HDFS的目录结构如下:
$ hdfs dfs -ls /
Found 1 items
drwxr-xr-x - hadoop supergroup 0 2022-03-28 17:10 /user
HDFS与Linux文件系统协议头区别:
- Linux:file:///
- HDFS:hdfs://namenode:port/
路径示例:
- Linux:file:///usr/local/hello.txt
- HDFS:hdfs://namenode:8020/usr/local/hello.txt
PS:
- 协议头file:/// 或 hdfs://namenode:8020/可以省略
- 需要提供Linux路径的参数,会自动识别为file://
- 需要提供HDFS路径的参数,会自动识别为hdfs://
- 除非你明确需要写或不写会有BUG,否则一般不用写协议头
HDFS文件系统的常用操作命令
- https://hadoop.apache.org/docs/r3.3.6/hadoop-project-dist/hadoop-common/FileSystemShell.html
- hadoop命令(老版本用法),用法:hadoop fs [generic options]
- hdfs命令(新版本用法),用法:hdfs dfs [generic options]
操作文件及文件夹命令:常用Llinx文件操作命令都可以使用
##### 创建文件夹
hadoop fs -mkdir [-p] <path> ...
hdfs dfs -mkdir [-p] <path> ...
hdfs dfs -mkdir -p /hdfs
##### 查看文件及文件夹
hdfs dfs -ls <path> ...
hdfs dfs -ls /hdfs
hdfs dfs -ls -h /hdfs # 以人类可读的方式显示文件大小
hdfs dfs -ls -R /hdfs # 递归查看文件夹
##### 上传文件
hdfs dfs -put <localsrc> ... <dst>
hdfs dfs -put /home/hadoop/hello.txt /hdfs
hdfs dfs -put -f /home/hadoop/hello.txt /hdfs # 如果目标文件存在,则覆盖
hdfs dfs -put -p /home/hadoop/hello.txt /hdfs # 保留文件的权限信息
##### 下载文件
hdfs dfs -get <src> ... <localdst>
hdfs dfs -get /hdfs/hello.txt /home/hadoop/hello.txt
hdfs dfs -get -f /hdfs/hello.txt /home/hadoop/hello.txt # 如果目标文件存在,则覆盖
hdfs dfs -get -p /hdfs/hello.txt /home/hadoop/hello.txt # 保留文件的权限信息
##### 复制文件
hdfs dfs -cp <src> ... <dst>
hdfs dfs -cp /hdfs/hello.txt /hdfs/hello.txt.bak
##### 移动文件
hdfs dfs -mv <src> ... <dst>
hdfs dfs -mv /hdfs/hello.txt.bak /hdfs/hello.txt
##### 删除文件/删除文件夹
hdfs dfs -rm <path> ...
hdfs dfs -rm /hdfs/hello.txt
hdfs dfs -rm -r /hdfs/test # 删除文件夹
hdfs dfs -rm -skipTrash /hdfs/hello.txt # 不经过回收站,直接删除文件
##### 追加数据到文件 无法修改文件内容,只能追加数据
hdfs dfs -appendToFile <localsrc> ... <dst>
hdfs dfs -appendToFile /home/hadoop/hello.txt /hdfs/hello.txt
##### 查看文件内容
hdfs dfs -cat <src> ...
hdfs dfs -cat /hdfs/hello.txt
hdfs dfs -cat /hdfs/hello.txt | more # 分页查看文件内容
hdfs dfs -cat /hdfs/hello.txt | head -n 10 # 查看文件前10行内容
##### 查看文件信息
hdfs dfs -stat [format] <path> ...
hdfs dfs -stat /hdfs/hello.txt
##### 修改文件权限
hdfs dfs -chmod [-R] <mode[,mode]... | OCTALMODE> <path> ...
hdfs dfs -chmod 777 /hdfs/hello.txt
##### 修改文件所属用户
hdfs dfs -chown [-R] [OWNER][:[GROUP]] <path> ...
hdfs dfs -chown hadoop:hadoop /hdfs/hello.txt
##### 修改文件所属组
hdfs dfs -chgrp [-R] GROUP <path> ...
hdfs dfs -chgrp hadoop /hdfs/hello.txt
##### 修改文件时间
hdfs dfs -touchz <path> ...
hdfs dfs -touchz /hdfs/hello.txt
#### HDFS文件系统的高级操作命令
##### 查看文件系统状态
hdfs dfsadmin -report
##### 查看文件系统健康状态
hdfs dfsadmin -report -liveNodes
##### 查看文件系统配置
hdfs getconf -confKey <key>
hdfs getconf -confKey dfs.replication
hdfs getconf -namenodes
hdfs getconf -backupNodes
##### 故障修复
hdfs dfsadmin -safemode enter # 进入安全模式
hdfs dfsadmin -safemode leave # 离开安全模式
hdfs dfsadmin -safemode get # 查看安全模式状态
hdfs dfsadmin -safemode wait # 等待安全模式结束
##### 文件系统修复
hdfs fsck / -files -blocks -locations -racks # 查看文件系统状态
hdfs fsck / -delete # 删除损坏的文件
hdfs fsck / -move # 移动损坏的文件到/lost+found目录
hdfs fsck / -openforwrite # 查看正在写入的文件
hdfs fsck / -files -blocks -locations -racks -openforwrite # 查看所有文件状态
HDFS文件系统回收站功能
回收站功能默认关闭,如果要开启需要在core-site.xml内配置:
<property>
<name>fs.trash.interval</name>
<value>1440</value>
</property>
<property>
<name>fs.trash.checkpoint.interval</name>
<value>120</value>
</property>
无需重启集群,在哪个机器配置的,在哪个机器执行命令就生效。
回收站默认位置在:/user/用户名(hadoop)/.Trash
HDFS WEB浏览
使用WEB浏览操作文件系统,一般会遇到权限问题
这是因为WEB浏览器中是以匿名用户(dr.who)登陆的,其只有只读权限,多数操作是做不了的。
如果需要以特权用户在浏览器中进行操作,需要配置如下内容到core-site.xml并重启集群
<property>
<name>hadoop.http.staticuser.user</name>
<value>hadoop</value>
</property>
但是,不推荐这样做,HDFS WEBUI,只读权限挺好的,简单浏览即可,如果给与高权限,会有很大的安全问题,造成数据泄露或丢失
Big Data Tools插件
Pycharm 设置->Plugins(插件)-> Marketplace(市场),搜索Big Data Tools,点击Install安装即可
需要对Windows系统做一些基础设置,配合插件使用
装Hadoop安装包复制到Windows系统,如解压到:D:\hadoop-3.3.6
设置$HADOOP_HOME环境变量指向:D:\hadoop-3.3.6
下载:
- hadoop.dll(https://github.com/steveloughran/winutils/blob/master/hadoop-3.0.0/bin/hadoop.dll)
- winutils.exe(https://github.com/steveloughran/winutils/blob/master/hadoop-3.0.0/bin/winutils.exe)
将hadoop.dll和winutils.exe放入$HADOOP_HOME/bin中
配置Big Data Tools插件
HDFS NFS Gateway
HDFS提供了基于NFS(Network File System)的插件,可以对外提供NFS网关,供其它系统挂载使用。
NFS 网关支持 NFSv3,并允许将 HDFS 作为客户机本地文件系统的一部分挂载
配置HDFS需要配置如下内容:
- core-site.xml,新增配置项 以及 hdfs-site.xml,新增配置项
- 开启portmap、nfs3两个新进程
在namenode进行如下操作
-
在core-site.xml 内新增如下两项
- 项目: hadoop.proxyuser.hadoop.groups 值:* 允许hadoop用户代理任何其它用户组
- 项目:hadoop.proxyuser.hadoop.hosts 值:* 允许代理任意服务器的请求
<property> <name>hadoop.proxyuser.hadoop.groups</name> <value>*</value> </property>
<property>
<name>hadoop.proxyuser.hadoop.hosts</name>
<value>*</value>
</property> - 在hdfs-site.xml中新增如下项
- nfs.suerpser:NFS操作HDFS系统,所使用的超级用户(hdfs的启动用户为超级用户)
- nfs.dump.dir:NFS接收数据上传时使用的临时目录
- nfs.exports.allowed.hosts:NFS允许连接的客户端IP和权限,rw表示读写,IP整体或部分可以以*代替
<property> <name>nfs.superuser</name> <value>hadoop</value> </property> <property> <name>nfs.dump.dir</name> <value>/tmp/.hdfs-nfs</value> </property> <property> <name>nfs.exports.allowed.hosts</name> <value>168.12.1.50 rw</value> </property>
启动NFS功能
- 将配置好的core-site.xml和hdfs-site.xml分发到datanode1和datanode2
- 重启Hadoop HDFS集群(先stop-dfs.sh,后start-dfs.sh)
- 停止系统的NFS相关进程
- systemctl stop nfs; systemctl disable nfs 关闭系统nfs并关闭其开机自启
- yum remove -y rpcbind 卸载系统自带rpcbind
- 启动portmap(HDFS自带的rpcbind功能)(必须以root执行):hdfs --daemon start portmap
- 启动nfs(HDFS自带的nfs功能)(必须以hadoop用户执行):hdfs --daemon start nfs3
检查NFS是否正常
- 在datanode1和datanode2执行(因为node1卸载了rpcbind,缺少了必要的2个命令)
- 执行:rpcinfo -p namenode 正常输出如下 有mountd和nfs出现
- 执行:showmount -e namenode
Windows客户端挂载NFS
- 控制面的,程序和功能,开启Windows功能,勾选NFS客户端
- 在Windows命令提示符(CMD)内输入:net use X: \namenode-ip!
- 完成后即可在文件管理器中看到盘符为X的网络位置
Linux客户端NFS挂载HDFS
mount -t nfs -o vers=3,proto=tcp,nolock,noacl namenode-ip:/hdfs /mnt/hdfs
HDFS副本块数量的配置
设置默认文件上传到HDFS中拥有的副本数量可以在hdfs-site.xml中配置如下属性:
<property>
<name>dfs.replication</name>
<value>3</value>
</property>
这个属性默认是3,一般情况下,我们无需主动配置(除非需要设置非3的数值)
如果需要自定义这个属性,请修改每一台服务器的hdfs-site.xml文件,并设置此属性。
除了配置文件外,我们还可以在上传文件的时候,临时决定被上传文件以多少个副本存储。
hadoop fs -D dfs.replication=2 -put test.txt /tmp/
如上命令,就可以在上传test.txt的时候,临时设置其副本数为2
对于已经存在HDFS的文件,修改dfs.replication属性不会生效,如果要修改已存在文件可以通过命令
hadoop fs -setrep [-R] 2 path
如上命令,指定path的内容将会被修改为2个副本存储。-R选项可选,使用-R表示对子目录也生效。
fsck命令检查文件的副本数
可以使用hdfs提供的fsck命令来检查文件的副本数
hdfs fsck path [-files [-blocks [-locations]]]
fsck可以检查指定路径是否正常
- -files可以列出路径内的文件状态
- -files -blocks 输出文件块报告(有几个块,多少副本)
- -files -blocks -locations 输出每一个block的详情
block配置
对于块(block),hdfs默认设置为256MB一个,也就是1GB文件会被划分为4个block存储。
hdfs-site.xml 块大小可以通过参数:
<property>
<name>dfs.blocksize</name>
<value>268435456</value>
<description>设置HDFS块大小,单位是b</description>
</property>
如上,设置为256MB
NameNode元数据
edits文件
在hdfs中,文件是被划分了一堆堆的block块,那如果文件很大、以及文件很多,Hadoop是如何记录和整理文件和block块的关系呢?
答案就在于NameNode
edits文件,是一个流水账文件,记录了hdfs中的每一次操作,以及本次操作影响的文件其对应的block
edits记录每一次HDFS的操作,逐渐变得越来越大,所以,会存在多个edits文件,为确保不会有超大edits的存在并保证检索性能,所以需要定期进行合并,合并成fsimage文件
fsimage文件
- fsimage文件,是NameNode的元数据镜像文件,记录了HDFS文件系统的所有目录和文件的信息,包括文件名、权限、修改日期、访问日期、块大小以及组成文件的块列表等
edits和fsimage的合并
- 每次对HDFS的操作,均被edits文件记录
- edits达到大小上线后,开启新的edits记录
- 定期进行edits的合并操作、合并过程
- 就是将edits文件中的内容,合并到fsimage文件中,生成新的fsimage文件,并清空edits文件
- 如当前已存在fsimage文件,将全部edits和已存在的fsimage进行合并,形成新的fsimage
SecondaryNameNode的作用
SecondaryNameNode会通过http从NameNode拉取数据(edits和fsimage)然后合并完成后提供给NameNode使用。
checkpoint
- checkpoint,检查点,是Hadoop中的一种机制,用于定期将edits文件合并到fsimage文件中,以减少edits文件的大小,并提高检索性能
checkpoint的触发条件
- edits文件的大小达到阈值
- 定时触发,例如每隔一段时间触发一次
- 手动触发,例如通过命令行手动触发
checkpoint的执行过程
- NameNode启动时,会检查是否有checkpoint文件,如果有,则加载checkpoint文件,否则加载fsimage文件
- NameNode会定期将edits文件合并到fsimage文件中,生成新的fsimage文件,并清空edits文件
- NameNode会将新的fsimage文件和edits文件保存到本地磁盘上
checkpoint的配置
- 在hdfs-site.xml文件中,可以配置checkpoint的相关参数,例如:
<property> <name>dfs.namenode.checkpoint.period</name> <value>3600</value> <description>checkpoint的周期,单位为秒</description> </property> <property> <name>dfs.namenode.checkpoint.txns</name> <value>1000000</value> <description>edits文件的大小达到阈值时,触发checkpoint</description> </property> <property> <name>dfs.namenode.checkpoint.check.period</name> <value>60</value> <description>检查是否达到条件,默认60秒检查一次</description> </property> <property> <name>dfs.namenode.checkpoint.dir</name> <value>/data/hadoop/checkpoint</value> <description>checkpoint文件的保存路径</description> </property>
客户端在HDFS上读、写数据的流程
数据写入流程
- 客户端向NameNode发起请求
- NameNode审核权限、剩余空间后,满足条件允许写入,并告知客户端写入的DataNode地址
- 客户端向指定的DataNode发送数据包
- 被写入数据的DataNode同时完成数据副本的复制工作,将其接收的数据分发给其它DataNode
- DataNode1复制给DataNode2,然后基于DataNode2复制给Datanode3和DataNode4
- 写入完成客户端通知NameNode,NameNode做元数据记录工作
关键信息点:
- NameNode不负责数据写入,只负责元数据记录和权限审批
- 客户端直接向1台DataNode写数据,这个DataNode一般是离客户端最近(网络距离)的那一个
- 数据块副本的复制工作,由DataNode之间自行完成(构建一个PipLine,按顺序复制分发)
数据读取流程
- 客户端向NameNode申请读取某文件
- NameNode判断客户端权限等细节后,允许读取,并返回此文件的block列表
- 客户端拿到block列表后自行寻找DataNode读取即可
关键点:
- 数据同样不通过NameNode提供
- NameNode提供的block列表,会基于网络距离计算尽量提供离客户端最近的,这是因为1个block有3份,会尽量找离客户端最近的那一份让其读取