一 、hdfs的权限
hdfs是一个文件系统,类似于unix和linux。
1、有用户的概念
hdfs没有提供相关命令和接口去创建用户。它所采取的办法是,信任客户端,默认情况下使用的操作系统提供的用户。
当然hdfs支持扩展继承第三方用户认证系统,例如kerberos 、LDAP等。
在hdfs中有超级用户的概念,hdfs系统中的超级用户是namenode进程的启动用户
linux的超级用户是root
2、hdfs还有权限的概念
hdfs的权限是自己控制的,来自于hdfs的超级用户
3、实操
切换我们用root搭建的HDFS,使用god用户来启动
步骤:
- node1~node3添加god用户
useradd god
passwd god
- 将资源与用户绑定 (1、安装部署程序;2、数据存放的目录)
使用下面的命令递归的将某个目录的拥有者权限设置为god用户。
chown -R god src
我们如果想要让god用户启动并管理hdfs集群,就需要给/opt/bigdata下的hadoop安装目录下的所有文件夹赋予god用户访问权限
[root@node1 bigdata]# chown -R god hadoop-2.6.5/
[root@node1 bigdata]# ll
total 460
drwxrwxr-x 10 god haizhang 161 May 9 21:18 hadoop-2.6.5
drwxr-xr-x 14 2002 2002 4096 Apr 28 16:28 zookeeper-3.4.14
-rw-r--r-- 1 root root 463805 May 14 21:25 zookeeper.out
[root@node1 bigdata]# cd hadoop-2.6.5/
[root@node1 hadoop-2.6.5]# ll
total 112
drwxrwxr-x 2 god haizhang 194 Oct 3 2016 bin
drwxrwxr-x 4 god haizhang 40 May 10 21:15 etc
drwxrwxr-x 2 god haizhang 106 Oct 3 2016 include
drwxrwxr-x 3 god haizhang 20 Oct 3 2016 lib
drwxrwxr-x 2 god haizhang 239 Oct 3 2016 libexec
-rw-rw-r-- 1 god haizhang 84853 Oct 3 2016 LICENSE.txt
drwxr-xr-x 2 god root 4096 May 17 18:09 logs
-rw-rw-r-- 1 god haizhang 14978 Oct 3 2016 NOTICE.txt
-rw-rw-r-- 1 god haizhang 1366 Oct 3 2016 README.txt
drwxrwxr-x 2 god haizhang 4096 Oct 3 2016 sbin
drwxrwxr-x 4 god haizhang 31 Oct 3 2016 share
此时可以看到hadoop下的目录及其子目录的所有者,变成了god用户。当然这里root用户仍然是可以操作这些目录的。
除此之外,god用户也要能够有权限访问hdfs中存放数据的目录,也就是我们定义的namenode,datanode,jn这些目录。
[root@node1 bigdata]# cd /var/bigdata
[root@node1 bigdata]# chown -R god hadoop/
[root@node1 bigdata]# ll
total 0
drwxr-xr-x 6 god root 51 May 14 21:54 hadoop
此时god用户就获得了hadoop目录下的所有读写执行权限了
同样的在node2,node3机器上也执行上面的赋予god用户操作hadoop的权限。
全部搞定之后,god用户就相当于hadoop的管理者了,可以调用hadoop的脚本命令以及进行读写操作等。
- 切换到god去启动,其中启动start-dfs.sh的时候,需要god用户进行免密操作。
首先输入su命令切换root用户到god用户
su god
然后cd到.ssh/目录下,可以发现一开始目录下面是空的。我们首先使用ssh命令登陆localhost
[god@node1 .ssh]$ ssh localhost
发现是需要输入密码的,原因是我们没有进行ssh免密,没生成ssh密钥。我们需要为god用户生成ssh密钥
[god@node1 .ssh]$ ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa
然后将id_dsa文件内容加到authorized_keys文件中。
[god@node1 .ssh]$ cat id_dsa.pub > authorized_keys
此时如果你使用ssh localhost命令发现还需要输入密码,就说明你的authorized_keys文件权限不对,ssh对authorized_keys权限要求很苛刻,必须要为600.
[god@node1 .ssh]$ chmod 600 authorized_keys
此时使用ssh就可以免密登陆了。
如果希望node1上使用god用户免密登陆其他node机器上,就需要将id_dsa分发。我们使用另一种方式进行分发ssh-copy-id -i id_dsa 主机名
[god@node1 .ssh]$ ssh-copy-id -i id_dsa node2
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "id_dsa.pub"
The authenticity of host 'node2 (192.168.199.12)' can't be established.
ECDSA key fingerprint is SHA256:nMKJNE3w5g86k6FNTWHanxEHqCElITTKmoHO1o9Aky4.
ECDSA key fingerprint is MD5:44:3f:05:9c:45:e9:3a:2d:10:69:63:a9:38:d1:6c:7e.
Are you sure you want to continue connecting (yes/no)? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
god@node2's password:
Permission denied, please try again.
god@node2's password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh 'node2'"
and check to make sure that only the key(s) you wanted were added.
[god@node1 .ssh]$ ssh-copy-id -i id_dsa node3
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "id_dsa.pub"
The authenticity of host 'node3 (192.168.199.14)' can't be established.
ECDSA key fingerprint is SHA256:nMKJNE3w5g86k6FNTWHanxEHqCElITTKmoHO1o9Aky4.
ECDSA key fingerprint is MD5:44:3f:05:9c:45:e9:3a:2d:10:69:63:a9:38:d1:6c:7e.
Are you sure you want to continue connecting (yes/no)? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
god@node3's password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh 'node3'"
此时node2、node3上就有了node1的公钥了。
可以登陆node3中查看authorized_keys文件
[god@node3 .ssh]$ cat authorized_keys
ssh-dss AAAAB3NzaC1kc3MAAACBALsCYXAIHUmZge6ewfEhN6AU2cJIop0w+dyjxRlUBZkkjfsvPCrJ6NUo11reF3rNX7jbEC0Kz2lHZJ2khR2sPKLmm9HVVkCnbcHgG8EbNkRkIy3gK7tR2CeEB8XfIlmB8YVWxazHKnoi90WOeYEMwWT7xiYcuclSqY4MFgRRSZuZAAAAFQDcNM3gTFRZCdZS4vvXKox43S9TrQAAAIAWAYm+mmTn7oIJj8CfiL7yonuhsI06HBF6cZV87azO5VbxvQ24HtKbGprDN8iFdUAJoz4zyk7gi7Gj83kCZriulmDAPkVdDDJjZPDbIA66vsV/jXGiwfHQDLEUYNc3XHJJ9FMJJfKKEoYwH0BANGEzv0VqwY5dwLSP+xTR7g43PQAAAIAtz1J0W3osn8FvKcHO7TB2kxNi/xKYDJLOpeo3MRpXHlcSQB7a8i0q1vQtMr70sInDnkn28WCqj1bVdszHAgtTzZ/X5324sgtdlmw0R7yMigEg6nYWPvpfp7Ejewdu752T6lKb/prI6dF1cxSotHJ1t35I82mvinazeOxinszr3w== god@node1
发现确实有了node1传递过来的id_dsa文件的内容。
同理,node2也需要免密登陆node1.在node2中登陆god用户,并且为它生成ssh公钥
[god@node2 .ssh]$ ssh-keygen -t dsa -P '' -f ./id_dsa
然后操作如下
[god@node2 .ssh]$ ssh-copy-id -i id_dsa node2
[god@node2 .ssh]$ ssh-copy-id -i id_dsa node1
此时node2就可以免密用ssh登陆node2和node1了。
- 修改hdfs-site.xml
当我们做完上面的操作之后,登陆node1,需要修改hdfs-site.xml文件中的dfs.ha.fencing.ssh.private-key-files对应的value
<property>
<name>dfs.ha.fencing.ssh.private-key-files</name>
<value>/home/god/.ssh/id_dsa</value>
</property>
将上面的修改分发给node2、node3
[god@node1 .ssh]$ scp hdfs-site.xml node2:`pwd`
[god@node1 .ssh]$ scp hdfs-site.xml node3:`pwd`
- 使用god用户启动hdfs
[god@node1 .ssh]$ start-dfs.sh
Starting namenodes on [node1 node2]
node2: starting namenode, logging to /opt/bigdata/hadoop-2.6.5/logs/hadoop-god-namenode-node2.out
node1: starting namenode, logging to /opt/bigdata/hadoop-2.6.5/logs/hadoop-god-namenode-node1.out
node2: starting datanode, logging to /opt/bigdata/hadoop-2.6.5/logs/hadoop-god-datanode-node2.out
node3: starting datanode, logging to /opt/bigdata/hadoop-2.6.5/logs/hadoop-god-datanode-node3.out
Starting journal nodes [node1 node2 node3]
node2: starting journalnode, logging to /opt/bigdata/hadoop-2.6.5/logs/hadoop-god-journalnode-node2.out
node1: starting journalnode, logging to /opt/bigdata/hadoop-2.6.5/logs/hadoop-god-journalnode-node1.out
node3: starting journalnode, logging to /opt/bigdata/hadoop-2.6.5/logs/hadoop-god-journalnode-node3.out
Starting ZK Failover Controllers on NN hosts [node1 node2]
node2: starting zkfc, logging to /opt/bigdata/hadoop-2.6.5/logs/hadoop-god-zkfc-node2.out
node1: starting zkfc, logging to /opt/bigdata/hadoop-2.6.5/logs/hadoop-god-zkfc-node1.out
需要注意的是,node2、node3也要切换到god用户喔。
- 使用god用户创建一个/user/god目录在hdfs上
[god@node1 .ssh]$ hdfs dfs -mkdir -p /user/god
发现god用户可以在root用户创建的目录下新建子目录
那么我们如果使用root用户能否在/user/god目录下创建个abc目录呢?
[root@node1 ~]# hdfs dfs -mkdir -p /user/god/abc
mkdir: Permission denied: user=root, access=WRITE, inode="/user/god":god:supergroup:drwxr-xr-x
发现创建失败,原因是root用户虽然属于supergroup组,但是supergroup组并没有对god用户创建的目录中具备写权限。
谁启动hdfs的,谁就是老大
4、 hdfs中的组
hdfs中提供了创建组和修改组及组权限的接口
- 改变用户目录的持有者和所属组:
[god@node1 ~]$ hdfs dfs -mkdir -p /temp
[god@node1 ~]$ hdfs dfs -chown god:ooxx /temp
此时目录/temp下的组就变成了ooxx了
- 改变用户创建的目录对应的权限
[god@node1 ~]$ hdfs dfs -chmod 770 /temp
此时ooxx组的用户都对这个/temp目录有读写执行操作权限了。
- 查看当前用户在hdfs中的组
[god@node1 ~]$ hdfs groups
god : god
使用hdfs dfsadmin
[god@node1 ~]$ hdfs dfsadmin
Usage: hdfs dfsadmin
Note: Administrative commands can only be run as the HDFS superuser.
[-report [-live] [-dead] [-decommissioning]]
[-safemode <enter | leave | get | wait>]
[-saveNamespace]
[-rollEdits]
[-restoreFailedStorage true|false|check]
[-refreshNodes]
[-setQuota <quota> <dirname>...<dirname>]
[-clrQuota <dirname>...<dirname>]
[-setSpaceQuota <quota> <dirname>...<dirname>]
[-clrSpaceQuota <dirname>...<dirname>]
[-finalizeUpgrade]
[-rollingUpgrade [<query|prepare|finalize>]]
[-refreshServiceAcl]
[-refreshUserToGroupsMappings]
[-refreshSuperUserGroupsConfiguration]
[-refreshCallQueue]
[-refresh <host:ipc_port> <key> [arg1..argn]
[-reconfig <datanode|...> <host:ipc_port> <start|status>]
[-printTopology]
[-refreshNamenodes datanode_host:ipc_port]
[-deleteBlockPool datanode_host:ipc_port blockpoolId [force]]
[-setBalancerBandwidth <bandwidth in bytes per second>]
[-fetchImage <local directory>]
[-allowSnapshot <snapshotDir>]
[-disallowSnapshot <snapshotDir>]
[-shutdownDatanode <datanode_host:ipc_port> [upgrade]]
[-getDatanodeInfo <datanode_host:ipc_port>]
[-metasave filename]
[-setStoragePolicy path policyName]
[-getStoragePolicy path]
[-triggerBlockReport [-incremental] <datanode_host:ipc_port>]
[-help [cmd]]
Generic options supported are
-conf <configuration file> specify an application configuration file
-D <property=value> use value for given property
-fs <local|namenode:port> specify a namenode
-jt <local|resourcemanager:port> specify a ResourceManager
-files <comma separated list of files> specify comma separated files to be copied to the map reduce cluster
-libjars <comma separated list of jars> specify comma separated jar files to include in the classpath.
-archives <comma separated list of archives> specify comma separated archives to be unarchived on the compute machines.
The general command line syntax is
bin/hadoop command [genericOptions] [commandOptions]
上面给出的是hdfs dfsadmin可用的选项,注意,这里必须要是hdfs的超级用户才可以调用上面的选项,也就是启动hdfs的用户。
我们注意到有个参数refreshUserToGroupsMappings,这个参数的含义是将当前namenode所在的机器上的用户所属组,刷新同步到hdfs中。例子如下:
[root@node1 ~]# useradd good
[root@node1 ~]# groupadd ooxx
[root@node1 ~]# passwd good
[root@node1 ~]# usermod -a -G ooxx good
[root@node1 ~]# id good
uid=1002(good) gid=1002(good) groups=1002(good),1003(ooxx)
这里我在node1上创建了good用户,并且创建ooxx组,且让good用户修改其组为ooxx。不过就算你在本机上指定good用户为ooxx组的成员,hdfs中实际上还是没同步的(可以使用hdfs groups命令查)
故此使用下面的命令同步:
[god@node1 ~]$ hdfs dfsadmin -refreshUserToGroupsMappings
Refresh user to groups mapping successful for node1/192.168.199.11:8020
Refresh user to groups mapping successful for node2/192.168.199.12:8020
[good@node1 ~]$ hdfs groups
good : good ooxx
此时good用户就可以在god用户创建的/temp目录下新建目录了
[good@node3 root]$ hdfs dfs -mkdir -p /temp/abv
结论:默认hdfs依赖操作系统上的用户和用户组。
二、集成开发环境
我们需要在IDEA中集成hdfs的clien来调用hdfs的接口执行一些操作。
客户端向hdfs中汇报它自己是谁的时候,可以使用下面三种方式
1、配置hdfs中client向集群提供的用户名
- 参考系统登陆用户名
- 参考环境变量
我们可以配置环境变量如下: - 这一步必须要优先启动下idea
jdk版本:集群和开发环境jdk版本需要一致 - 代码中指明
使用FileSystem对象获取
//手动的指定连接fs的时候使用的用户名
fs = FileSystem.get(URI.create("hdfs://mycluster"),conf,"god")
2、配置hdfs的pom文件
其中hadoop包括(common,hdfs,yarn,mapreduce)
导入下面的pom依赖
<!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-common -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.6.5</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>2.6.5</version>
</dependency>
将node1节点上的hadoop目录下的core-site.xml 和 hdfs-site.xml引入到idea项目中的resources路径下;
3、使用代码连接hdfs并创建一个目录
代码如下:
public class TestHDFS {
public Configuration conf = null;
//这才是和hdfs连接的客户端
public FileSystem fs = null;
@Before
public void conn() throws IOException {
//这里Configuration构造函数传入loadDefaults为true表示启动之后会加载编译好的core-site.xml和hdfs-site.xml里的配置
conf = new Configuration(true);
/**
*创建fs对象,注意这里会根据core-site.xml配置的fs.defaultFS对应的值判断返回fs对象的具体类型。比如我们配置的这个值
* 有个hdfs://前缀,那就会返回一个分布式的文件系统fs对象回来。
* 并且这里的fs会去读取我们之前定义的HADOOP_USER_NAME的环境变量,来获取向hdfs提供的客户端角色(定义为god用户)
*/
fs = FileSystem.get(conf);
}
/**
* 模拟向hdfs中创建目录
*/
@Test
public void mkdir() throws IOException {
Path path = new Path("/luohaizhang");
//检查目录是否存在在hdfs中
if(fs.exists(path)){
//删除已存在的目录
fs.delete(path,true);
}
//创建目录
boolean mkdirs = fs.mkdirs(path);
}
@After
public void close() throws IOException {
fs.close();
}
}
上面代码的注解已经标注很清晰了,注意一点的是fs = FileSystem.get(conf);这种构造Filesystem对象的方式,默认是去你主机配置的环境变量获取用户信息的。
效果:
4、使用代码上传文件到hdfs
/**
* 上传文件
* @throws IOException
*/
@Test
public void uploadFile() throws IOException {
BufferedInputStream input = new BufferedInputStream(new FileInputStream("D:\\redislog.txt"));
Path path = new Path("/luohaizhang/redisLog");
FSDataOutputStream fsDataOutputStream = fs.create(path);
IOUtils.copyBytes(input,fsDataOutputStream,conf,true);
}
效果:
5、从hdfs读取下载文件
下面给出的是直接读取文件,文件块对用户来讲是隐藏的。用户感知不到:
/**
* 读取下载hdfs文件
* @throws IOException
*/
@Test
public void readFile() throws IOException {
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream("./data/redisLogHdfs.txt"));
Path path = new Path("/luohaizhang/redisLog");
FSDataInputStream open = fs.open(path);
IOUtils.copyBytes(open,outputStream,conf,true);
}
当然如果你想要知道文件块都在hdfs中的哪些节点上存储,也是很简单的。这里现在hdfs中上传一个比较大的文件,并定义文件块划分的大小
[god@node1 ~]$ for i in `seq 100000`;do echo "hello luohaizhang god $i" >> data.txt; done;
[god@node1 ~]$ ll -h
-rw-rw-r-- 1 god god 2.7M May 19 11:08 data.txt
[god@node1 ~]$ hdfs dfs -D dfs.blocksize=1048576 -put data.txt
此时data.txt默认上传到hdfs中的/user/god目录下。并且备拆分成3个block,每个block存在2个副本。
现在我们可以使用代码来获取这个文件的元信息
/**
* 读取下载hdfs文件
* @throws IOException
*/
@Test
public void readFileMetaInfo() throws IOException {
Path path = new Path("/user/god/data.txt");
FSDataInputStream open = fs.open(path);
//去fs中获取path指定的文件的元数据
FileStatus fss = fs.getFileStatus(path);
//这里会去fs中获取文件元数据指定长度的文件块存放的位置信息。
BlockLocation[] fileBlockLocations = fs.getFileBlockLocations(fss, 0, fss.getLen());
//遍历文件块位置信息
for(BlockLocation blockLocation : fileBlockLocations)
System.out.println(blockLocation);
}
对应的结果:
对上面结果进行分析:
0,1048576,node3,node2
块的起始location,块的末尾location,块的副本存放在node3上,块的副本存放在node2上。
这也是hdfs的强大之处,我们可以获取文件块的存放位置,按块拉取文件。也使得计算向数据移动(通过hdfs提供的文件块location)。提高处理效率。
我们可以进入到hdfs机器中确认下,上面生成的3个文件块里面存放的内容
[root@node2 subdir0]# pwd
/var/bigdata/hadoop/ha/dfs/data/current/BP-1181962499-192.168.199.11-1589603211493/current/finalized/subdir0/subdir0
[root@node2 subdir0]# ll -h
total 2.7M
-rw-rw-r-- 1 god god 1.1K May 19 10:45 blk_1073741825
-rw-rw-r-- 1 god god 19 May 19 10:45 blk_1073741825_1001.meta
-rw-rw-r-- 1 god god 1.0M May 19 11:09 blk_1073741826
-rw-rw-r-- 1 god god 8.1K May 19 11:09 blk_1073741826_1002.meta
-rw-rw-r-- 1 god god 1.0M May 19 11:09 blk_1073741827
-rw-rw-r-- 1 god god 8.1K May 19 11:09 blk_1073741827_1003.meta
-rw-rw-r-- 1 god god 676K May 19 11:09 blk_1073741828
-rw-rw-r-- 1 god god 5.3K May 19 11:09 blk_1073741828_1004.meta
可以看到node2中datanode存放了3个文件块以及校验和(后缀826-828的)。正是我们上面传递的文件。 首先可以访问下第一个块 blk_1073741826的文件末尾内容
[root@node2 subdir0]# cat blk_1073741826 | tail -10
hello luohaizhang god 37837
hello luohaizhang god 37838
hello luohaizhang god 37839
hello luohaizhang god 37840
hello luohaizhang god 37841
hello luohaizhang god 37842
hello luohaizhang god 37843
hello luohaizhang god 37844
hello luohaizhang god 37845
hello luohaizhang god
发现因为按块划分,部门溢出块大小的字节数将被移到块 blk_1073741827中,我们看下块 blk_1073741827的文件内容
[root@node2 subdir0]# cat blk_1073741827 |head -10
37846
hello luohaizhang god 37847
hello luohaizhang god 37848
hello luohaizhang god 37849
hello luohaizhang god 37850
hello luohaizhang god 37851
hello luohaizhang god 37852
hello luohaizhang god 37853
hello luohaizhang god 37854
hello luohaizhang god 37855
发现块 blk_1073741827的文件首行就是块 blk_1073741826缺少的部分。这个时候,如果程序A和程序B都想要使用HDFS提供的客户端FileSystem去读取不同块的内容,怎么实现呢?观察下面的代码
@Test
public void readFile3() throws IOException {
Path path = new Path("/user/god/data.txt");
FSDataInputStream in = fs.open(path);
System.out.println((char)in.readByte());
System.out.println((char)in.readByte());
System.out.println((char)in.readByte());
System.out.println((char)in.readByte());
System.out.println((char)in.readByte());
System.out.println((char)in.readByte());
System.out.println((char)in.readByte());
System.out.println((char)in.readByte());
System.out.println((char)in.readByte());
System.out.println((char)in.readByte());
System.out.println((char)in.readByte());
}
结果:
上面结果显示,如果直接调用fs提供的输出流的readByte方法,实际上只会从文件块的第一行开始读,那么同理,不管有多少个程序调用readByte方法,都是从第一个文件块的第一行读下去。这样就不能做到并行计算的目的。幸好FSDataInputStream提供了seek方法,让客户端提供他想要读取的文件块location,然后从该location往下读即可
@Test
public void readFile3() throws IOException {
Path path = new Path("/user/god/data.txt");
FSDataInputStream in = fs.open(path);
//假设想要从第二个文件块的首行开始读,这里一个文件块大小是1M = 1048576
in.seek(1048576);
System.out.println((char)in.readByte());
System.out.println((char)in.readByte());
System.out.println((char)in.readByte());
System.out.println((char)in.readByte());
System.out.println((char)in.readByte());
System.out.println((char)in.readByte());
System.out.println((char)in.readByte());
System.out.println((char)in.readByte());
System.out.println((char)in.readByte());
System.out.println((char)in.readByte());
System.out.println((char)in.readByte());
}
结果:
此时确实是第二个文件块blk_1073741827中首行的内容!这样就可以根据不同的location,定位想要获取的数据,并行计算即可!并且默认的是读取离自己最近的datanode,这是hdfs框架的默认机制。