作者:李继武
1
文档编写目的
Fayson在前面的文章《0553-6.1.0-如何使用Java代码同时访问安全和非安全CDH集群》,本篇文章介绍在同一Java进程中,通过多线程同时访问Kerberos认证集群和非认证集群时出现的一些异常及解决方法。
- 测试环境:CDH6.1.0
2
集群准备
1.非认证集群,在该集群中根目录下创建了一个NONEKRBCDH目录用以标识
2.认证集群,在该集群中根目录下创建了一个KRBCDH目录用以标识
3
环境准备
本次测试是将代码直接放在linux系统上运行,所以将两套集群的配置文件分别放在两个不同的目录下:
1.认证集群的配置信息包括krb5.conf和keytab文件放在/root/krbconf下
2.非认证集群的配置信息放在/root/conf下
4
工具类
1.初始化配置文件工具类
代码语言:javascript复制package com.cloudera.hdfs.utils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
public class HDFSUtils {
public static Configuration initConfiguration(String confPath) {
Configuration configuration = new Configuration();
System.out.println(confPath File.separator "core-site.xml");
configuration.addResource(new Path(confPath File.separator "core-site.xml"));
configuration.addResource(new Path(confPath File.separator "hdfs-site.xml"));
return configuration;
}
}
5
代码测试
1.如下测试是在两个线程循环访问两个集群,每个线程连接时都执行,UserGroupInformation.setConfiguration操作
代码语言:javascript复制package com.cloudera.hdfs;
import com.amazonaws.services.workdocs.model.User;
import com.cloudera.hdfs.utils.HDFSUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.security.UserGroupInformation;
import java.io.File;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
//初始化认证集群配置文件
Configuration configuration = HDFSUtils.initConfiguration(File.separator "root"
File.separator "krbconf");
configuration.set("fs.hdfs.impl", "org.apache.hadoop.hdfs.DistributedFileSystem");
//该线程循环访问认证集群
new Thread(() -> {
try {
while (true) {
//配置kerberos认证的配置文件
System.setProperty("java.security.krb5.conf", File.separator "root"
File.separator "krbconf" File.separator "krb5.conf");
//进行身份认证
UserGroupInformation.setConfiguration(configuration);
UserGroupInformation.loginUserFromKeytab("hive@MACRO.COM",
File.separator "root" File.separator "krbconf" File.separator "hive.keytab");
System.out.println("当前用户是:"
UserGroupInformation.getCurrentUser());
//列出根目录下所有文件
FileSystem fileSystem = FileSystem.get(configuration);
FileStatus[] files = fileSystem.listStatus(new Path("hdfs://nameservice1/"));
for (FileStatus file : files) {
System.out.println("KRB:" file.getPath());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}).start();
//初始化非认证配置文件
Configuration noAuthconfiguration = HDFSUtils.initConfiguration(File.separator "root"
File.separator "conf");
noAuthconfiguration.set("fs.hdfs.impl", "org.apache.hadoop.hdfs.DistributedFileSystem");
//该线程循环访问非认证集群
new Thread(() -> {
try {
while (true) {
System.out.println("当前用户是:" UserGroupInformation.getCurrentUser());
UserGroupInformation.setConfiguration(noAuthconfiguration);
//列出根目录下所有文件
FileSystem fileSystem = FileSystem.get(noAuthconfiguration);
FileStatus[] files = fileSystem.listStatus(new Path("hdfs://nameservice1/"));
for (FileStatus file : files) {
System.out.println("NONEKRB:" file.getPath());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
问题:
在访问认证集群的线程认证结束之后准备访问集群,这时访问非认证集群的线程将UserGroupInformation中的认证方式改成SIMPLE之后,导致访问认证集群的线程报错:认证方式不对
2.在上一步的基础上,将线程中认证访问集群的代码加锁
代码语言:javascript复制package com.cloudera.hdfs;
import com.amazonaws.services.workdocs.model.User;
import com.cloudera.hdfs.utils.HDFSUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.security.UserGroupInformation;
import java.io.File;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
//初始化认证集群配置文件
Configuration configuration = HDFSUtils.initConfiguration(File.separator "root"
File.separator "krbconf");
configuration.set("fs.hdfs.impl", "org.apache.hadoop.hdfs.DistributedFileSystem");
//该线程循环访问认证集群
new Thread(() -> {
try {
while (true) {
synchronized ("A") {
//配置kerberos认证的配置文件
System.setProperty("java.security.krb5.conf", File.separator "root"
File.separator "krbconf" File.separator "krb5.conf");
//进行身份认证
UserGroupInformation.setConfiguration(configuration);
UserGroupInformation.loginUserFromKeytab("hive@MACRO.COM",
File.separator "root" File.separator "krbconf" File.separator "hive.keytab");
System.out.println("当前用户是:"
UserGroupInformation.getCurrentUser());
//列出根目录下所有文件
FileSystem fileSystem = FileSystem.get(configuration);
FileStatus[] files = fileSystem.listStatus(new Path("hdfs://nameservice1/"));
for (FileStatus file : files) {
System.out.println("KRB:" file.getPath());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}).start();
//初始化非认证配置文件
Configuration noAuthconfiguration = HDFSUtils.initConfiguration(File.separator "root"
File.separator "conf");
noAuthconfiguration.set("fs.hdfs.impl", "org.apache.hadoop.hdfs.DistributedFileSystem");
//该线程循环访问非认证集群
new Thread(() -> {
try {
while (true) {
synchronized ("A") {
System.out.println("当前用户是:" UserGroupInformation.getCurrentUser());
UserGroupInformation.setConfiguration(noAuthconfiguration);
//列出根目录下所有文件
FileSystem fileSystem = FileSystem.get(noAuthconfiguration);
FileStatus[] files = fileSystem.listStatus(new Path("hdfs://nameservice1/"));
for (FileStatus file : files) {
System.out.println("NONEKRB:" file.getPath());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
问题:
此时虽然不再报错了,但是我们从结果中看出,两个线程访问的是同一个集群
原因在于没有重置UserGroupInformation
3.在上一步的基础上,访问非认证集群之前增加重置UserGroupInformation操作
代码语言:javascript复制package com.cloudera.hdfs;
import com.amazonaws.services.workdocs.model.User;
import com.cloudera.hdfs.utils.HDFSUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.security.UserGroupInformation;
import java.io.File;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
//初始化认证集群配置文件
Configuration configuration = HDFSUtils.initConfiguration(File.separator "root"
File.separator "krbconf");
configuration.set("fs.hdfs.impl", "org.apache.hadoop.hdfs.DistributedFileSystem");
//该线程循环访问认证集群
new Thread(() -> {
try {
while (true) {
synchronized ("A") {
//配置kerberos认证的配置文件
System.setProperty("java.security.krb5.conf", File.separator "root"
File.separator "krbconf" File.separator "krb5.conf");
//进行身份认证
UserGroupInformation.setConfiguration(configuration);
UserGroupInformation.loginUserFromKeytab("hive@MACRO.COM",
File.separator "root" File.separator "krbconf" File.separator "hive.keytab");
System.out.println("当前用户是:"
UserGroupInformation.getCurrentUser());
//列出根目录下所有文件
FileSystem fileSystem = FileSystem.get(configuration);
FileStatus[] files = fileSystem.listStatus(new Path("hdfs://nameservice1/"));
for (FileStatus file : files) {
System.out.println("KRB:" file.getPath());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}).start();
//初始化非认证配置文件
Configuration noAuthconfiguration = HDFSUtils.initConfiguration(File.separator "root"
File.separator "conf");
noAuthconfiguration.set("fs.hdfs.impl", "org.apache.hadoop.hdfs.DistributedFileSystem");
//该线程循环访问非认证集群
new Thread(() -> {
try {
while (true) {
synchronized ("A") {
//重置认证信息
UserGroupInformation.reset();
System.out.println("当前用户是:" UserGroupInformation.getCurrentUser());
UserGroupInformation.setConfiguration(noAuthconfiguration);
//列出根目录下所有文件
FileSystem fileSystem = FileSystem.get(noAuthconfiguration);
FileStatus[] files = fileSystem.listStatus(new Path("hdfs://nameservice1/"));
for (FileStatus file : files) {
System.out.println("NONEKRB:" file.getPath());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
从结果来看,测试成功:
4.进一步测试增加重置UserGroupInformation但是不加锁的情况
代码语言:javascript复制package com.cloudera.hdfs;
import com.amazonaws.services.workdocs.model.User;
import com.cloudera.hdfs.utils.HDFSUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.security.UserGroupInformation;
import java.io.File;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
//初始化认证集群配置文件
Configuration configuration = HDFSUtils.initConfiguration(File.separator "root"
File.separator "krbconf");
configuration.set("fs.hdfs.impl", "org.apache.hadoop.hdfs.DistributedFileSystem");
//该线程循环访问认证集群
new Thread(() -> {
try {
while (true) {
//synchronized ("A") {
//配置kerberos认证的配置文件
System.setProperty("java.security.krb5.conf", File.separator "root"
File.separator "krbconf" File.separator "krb5.conf");
//进行身份认证
UserGroupInformation.setConfiguration(configuration);
UserGroupInformation.loginUserFromKeytab("hive@MACRO.COM",
File.separator "root" File.separator "krbconf" File.separator "hive.keytab");
System.out.println("当前用户是:"
UserGroupInformation.getCurrentUser());
//列出根目录下所有文件
FileSystem fileSystem = FileSystem.get(configuration);
FileStatus[] files = fileSystem.listStatus(new Path("hdfs://nameservice1/"));
for (FileStatus file : files) {
System.out.println("KRB:" file.getPath());
}
//}
}
} catch (Exception e) {
e.printStackTrace();
}
}).start();
//初始化非认证配置文件
Configuration noAuthconfiguration = HDFSUtils.initConfiguration(File.separator "root"
File.separator "conf");
noAuthconfiguration.set("fs.hdfs.impl", "org.apache.hadoop.hdfs.DistributedFileSystem");
//该线程循环访问非认证集群
new Thread(() -> {
try {
while (true) {
//synchronized ("A") {
//重置认证信息
UserGroupInformation.reset();
System.out.println("当前用户是:" UserGroupInformation.getCurrentUser());
UserGroupInformation.setConfiguration(noAuthconfiguration);
//列出根目录下所有文件
FileSystem fileSystem = FileSystem.get(noAuthconfiguration);
FileStatus[] files = fileSystem.listStatus(new Path("hdfs://nameservice1/"));
for (FileStatus file : files) {
System.out.println("NONEKRB:" file.getPath());
}
//}
}
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
问题:
在访问认证集群的线程认证结束之后准备访问集群时,会出现这时正好被访问非认证集群的线程把认证信息清除的情况,无法找到用户,导致报错。
6
总结
1.因为java进程的kerberos身份认证信息存放在UserGroupInformation的静态字段中,因此该进程的内存中仅能存取一份身份信息,这也导致一个线程修改该身份信息之后会直接影响另一个线程。
2.如果要在不同的线程中访问认证集群和非认证集群,只能通过加锁和重置身份信息的方式,但这会显著影响程序执行效率。
提示:代码块部分可以左右滑动查看噢
为天地立心,为生民立命,为往圣继绝学,为万世开太平。 温馨提示:如果使用电脑查看图片不清晰,可以使用手机打开文章单击文中的图片放大查看高清原图。
推荐关注Hadoop实操,第一时间,分享更多Hadoop干货,欢迎转发和分享。
原创文章,欢迎转载,转载请注明:转载自微信公众号Hadoop实操