这是我参与更文挑战的第26天,活动详情查看: 更文挑战
?
如果不熟悉hadoop的朋友,可以简单上手玩一玩
(精)我用1秒搭建了hadoop3节点集群,占用一个1G内存(适合初学者)
NameNode启动流程
从分析流程上来说我们主要关注两部分代码
static语句块,在执行主函数前
static{
HdfsConfiguration.init();
}
复制代码
通过函数的名称我们也可以看出上述代码作用是初始化HDFS的配置信息。
在执行完static语句块后执行main函数,其代码如下。(分析直接写在代码的注释当中)
public static void main(String argv[]) throws Exception{
//分析传入的参数是否为帮助参数,如果是帮助的话打印帮助信息,并退出。
if(DFSUtil.parseHelpArgument(argv,NameNode.USAGE,System.out,true)){
System.exit(0);
}
try{
//格式化输出启动信息,并且创建hook(打印节点关闭信息)
StringUtils.startupShutdownMessage(NameNode.class,argv,LOG);
//创建namenode
NameNode namenode=createNameNode(argv,null);
if(namenode!=null){
//加入集群
namenode.join()
}
}catch(Throwable e){
//异常处理
LOG.error("Failed to start namenode.",e)
terminate(1,e);
}
}
复制代码
启动配置分析
namenode 在启动前首先调用HdfsConfiguration.init();初始化hdfs配置。
查看HdfsConfiguration.java代码可发现init()函数内并没有内容,是一个空函数。
那它是如何起作用的?这就要看HdfsConfiguration.java中static语句块中的内容
static {
addDeprecatedKeys();
Configuration.addDefaultResource("hdfs-default.xml");
Configuration.addDefaultResource("hdfs-site.xml");
}
复制代码
我们知道我们在调用对象的一个静态方法时,JVM首先会将该类加载到内存中,并执行其内部由static标识的语句块(且仅会执行一次),然后才能执行我们所调用的方法。(这也给我们一个启示,写单例模式的时候可以用这种方法)。
所以在这里我们首次调用HdfsConfiguration.init()时会执行static语句块,之后我们再调用就就不会再执行了
(测试:我们在此修改NameNdoe.java 代码让其调用两次HdfsConfiguration.init()方法,在HdfsConfiguration.java中的static语句块内设置一个断点,观察HdfsConfiguration.java中的static语句块是否是仅执行一次)
这里有一个问题就是hadoop设计者为什么要这样设计呢?对于主要是考虑到配置文件仅需要加载一次就可以为整个系统提供服务,这是一个典型的单例模式,这种设计方法保证了多线程安全。另一方面我们看到在其static语句块中还包含了一句addDeprecatedKeys(); 这个是维护一个新旧配置命名规则的一个映射,防止部分粗心的开发者仅调用addDefaultResource()而忘记调用addDeprecatedKeys()造成旧的属性命名规则不起作用。
主函数分析
进入main函数之后呢首先执行的是下面语句
StringUtils.startupShutdownMessage(NameNode.class,argv,LOG);
复制代码
static void startupShutdownMessage(Class<?> clazz, String[] args,
final LogAdapter LOG) {
//使用NetUtils 获取主机名
final String hostname = NetUtils.getHostname();
//类名
final String classname = clazz.getSimpleName();
//输出日志信息
LOG.info(
toStartupShutdownString("STARTUP_MSG: ", new String[] {
"Starting " + classname,
" host = " + hostname,
" args = " + Arrays.asList(args),
" version = " + VersionInfo.getVersion(),
" classpath = " + System.getProperty("java.class.path"),
" build = " + VersionInfo.getUrl() + " -r "
+ VersionInfo.getRevision()
+ "; compiled by '" + VersionInfo.getUser()
+ "' on " + VersionInfo.getDate(),
" java = " + System.getProperty("java.version") }
)
);
if (SystemUtils.IS_OS_UNIX) {
try {
SignalLogger.INSTANCE.register(LOG);
} catch (Throwable t) {
LOG.warn("failed to register any UNIX signal loggers: ", t);
}
}
//创建钩子,节点关闭时调用打印关闭信息
ShutdownHookManager.get().addShutdownHook(
new Runnable() {
@Override
public void run() {
LOG.info(toStartupShutdownString("SHUTDOWN_MSG: ", new String[]{
"Shutting down " + classname + " at " + hostname}));
}
}, SHUTDOWN_HOOK_PRIORITY);
}
复制代码
整体来说比较简单。这里提出自己几个问题
-
我做数据挖掘的时候, 经常需要只对key分组,不必排序。目前把sort类给设为null能处理,但还是不尽人意。hadoop的机制是通过一个circle buffer 收集mapper输出的东西, 到了io.sort.mb * percent量的时候,就spill到disk, 而spill前使用排序,默认快排。 之后reduce下载spill的东西, 进行merge, merge的时候使用了堆排。我的想法是通过一个hash,每个hash的元素是一个链表,存放相同key的所有值。不过这样就没circle buffer了,用不到其对stream缓存的优点,这个要仔细想想。
-
map之后要么直接写到hdfs(reducer 个数为0时), 要么同一道作业指定的reducer去处理这些东西. 这个机制很不灵活。有时候我的mapper输出,让不同的reducer处理不同的任务,输出不同的结果。 目前hadoop虽然能处理,但太牵强了。如果mapper处理完之后,加一层转发机制。这时候可以少一次io, 而且灵活, NB. 如果能把数据像流一样处理,而且可以分流,汇集之类的,那更好。
-
还是某度提出的问题, 机器一般挂多块磁盘。 单块磁盘的故障会导致系统认为整个节点down了, 这个修改相应的代码应该可以实现。slave报告的时候准确一点, 就可以只复制坏了的磁盘的数据了。
-
hadoop的任务跟踪能力太弱了,如果能做到和erlang那么NB,就厉害了
-
mapper的个数实际上是根据block数来定的, 线程太多, 消耗太大。
真心感谢帅逼靓女们能看到这里,如果这个文章写得还不错,觉得有点东西的话
求点赞? 求关注❤️ 求分享? 对8块腹肌的我来说真的 非常有用!!!
如果本篇博客有任何错误,请批评指教,不胜感激 !❤️❤️❤️❤️