├── .gitattributes
├── .gitignore
├── .idea
├── .name
├── codeStyleSettings.xml
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── misc.xml
├── modules.xml
├── scopes
│ └── scope_settings.xml
├── thriftCompiler.xml
├── uiDesigner.xml
└── vcs.xml
├── README.md
├── Spark-Note.iml
├── hadoop
├── datanode.md
├── hadoop-ipc.md
├── metric-learn.md
├── namenode-ha.md
├── nodemanager-container-launch.md
├── nodemanager-container-localizer.md
├── nodemanager-container-monitor.md
└── nodemanager-container-withrm.md
├── hbase
├── hbase-bulk-loading.md
├── hbase-filter.md
└── hbase-learn.md
├── image
├── BlockPoolManager.png
├── Catalyst-Optimizer-diagram.png
├── edge_cut_vs_vertex_cut.png
├── fsdataset.png
├── hadoop-rpc.jpg
├── job.jpg
├── network-client.jpg
├── network-message.jpg
├── network-rpcenv.jpg
└── project.png
├── other
├── mvn-lib.md
├── point-estimation.md
└── scala-java-class-type.md
├── spark
├── class-from-root.md
├── function-closure-cleaner.md
├── mllib-pipeline.md
├── pregel-bagel.md
├── scala-implicit.md
├── shuffle-hash-sort.md
├── shuffle-study.md
├── spark-block-manager.md
├── spark-catalyst-optimizer.md
├── spark-catalyst.md
├── spark-experience.md
├── spark-important-issue.md
├── spark-join.md
├── spark-memory-manager.md
└── spark-network-netty.md
└── system
├── cpu.md
├── disk-io.md
├── java-memory.md
└── memory.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Windows image file caches
2 | Thumbs.db
3 | ehthumbs.db
4 |
5 | # Folder config file
6 | Desktop.ini
7 |
8 | # Recycle Bin used on file shares
9 | $RECYCLE.BIN/
10 |
11 | # Windows Installer files
12 | *.cab
13 | *.msi
14 | *.msm
15 | *.msp
16 |
17 | # =========================
18 | # Operating System Files
19 | # =========================
20 |
21 | # OSX
22 | # =========================
23 |
24 | .DS_Store
25 | .AppleDouble
26 | .LSOverride
27 |
28 | # Icon must ends with two \r.
29 | Icon
30 |
31 | # Thumbnails
32 | ._*
33 |
34 | # Files that might appear on external disk
35 | .Spotlight-V100
36 | .Trashes
37 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | Spark-Note
--------------------------------------------------------------------------------
/.idea/codeStyleSettings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | 1.6
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/scopes/scope_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/thriftCompiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/uiDesigner.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | -
6 |
7 |
8 | -
9 |
10 |
11 | -
12 |
13 |
14 | -
15 |
16 |
17 | -
18 |
19 |
20 |
21 |
22 |
23 | -
24 |
25 |
26 |
27 |
28 |
29 | -
30 |
31 |
32 |
33 |
34 |
35 | -
36 |
37 |
38 |
39 |
40 |
41 | -
42 |
43 |
44 |
45 |
46 | -
47 |
48 |
49 |
50 |
51 | -
52 |
53 |
54 |
55 |
56 | -
57 |
58 |
59 |
60 |
61 | -
62 |
63 |
64 |
65 |
66 | -
67 |
68 |
69 |
70 |
71 | -
72 |
73 |
74 | -
75 |
76 |
77 |
78 |
79 | -
80 |
81 |
82 |
83 |
84 | -
85 |
86 |
87 |
88 |
89 | -
90 |
91 |
92 |
93 |
94 | -
95 |
96 |
97 |
98 |
99 | -
100 |
101 |
102 | -
103 |
104 |
105 | -
106 |
107 |
108 | -
109 |
110 |
111 | -
112 |
113 |
114 |
115 |
116 | -
117 |
118 |
119 | -
120 |
121 |
122 |
123 |
124 |
125 |
126 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 填坑与埋坑
2 | ==========
3 |
4 | ### [Spark SQL Join原理分析](./spark/spark-join.md)
5 | 过了一下Spark SQL对Join的支持,相对来说原理比较简单,这里简单记录一下!
6 |
7 | + 开始埋坑日期:2016-8-14
8 | + 坑状态:done
9 |
10 | ### [Spark 使用经验](./spark/spark-experience.md)
11 | 在spark使用过程中,除了内存,网络一些大的主题引起大家注意以外还有很多细节,是可以多注意! 比如正确的使用flatmap!,reduceByKey一定比groupByKey好吗?,后面会持续总结...
12 |
13 | + 开始埋坑日期:2016-7-20
14 | + 坑状态:doing
15 |
16 | ### [Spark-Catalyst Optimizer](./spark/spark-catalyst-optimizer.md)
17 | Optimizer主要会对Logical Plan进行剪枝,合并等操作,从而从Logical Plan中删除掉一些无用计算,或对一些计算的多个步骤进行合并。由于优化的策略会随着知识的发现而逐渐引入,核心还是要理解原理!!
18 |
19 | + 开始埋坑日期:2016-7-10
20 | + 坑状态:done
21 |
22 | ### [Spark Catalyst的实现分析](./spark/spark-catalyst.md)
23 | Spark SQL是Spark内部最核心以及社区最为活跃的组件,也是未来Spark对End-User最好的接口,支持SQL语句和类RDD的Dataset/DataFrame接口。相比在传统的RDD上进行开发,Spark SQL的业务逻辑在执行前和执行过程中都有相应的优化工具对其进行自动优化(即Spark Catalyst以及Tungsten两个组件),因此未来Spark SQL肯定是主流。本文主要是针对SparkSQL核心组件Catalyst进行分析,算是SparkSQL实习分析的第一步吧
24 |
25 | + 开始埋坑日期:2016-7-1
26 | + 坑状态:done
27 |
28 |
29 | ### [Spark Memory解析](./spark/spark-memory-manager.md)
30 | 在Spark日常工作中(特别是处理大数据),内存算是最常见问题。看着日志里打着各种FullGC甚至OutOfMemory日志,但是却不能理解是在哪一块出了内存问题。其实也这是正常的,Spark内存管理某种程度上还是相当复杂了,涉及RDD-Cache,Shuffle,Off-Heap等逻辑,它贯穿在整个任务执行的每个环节中。
31 |
32 | + 开始埋坑日期:2016-5-1
33 | + 坑状态:done
34 |
35 |
36 | ### [Spark Network 模块分析(基于Netty的实现)](./spark/spark-network-netty.md)
37 | 一直以来,基于Akka实现的RPC通信框架是Spark引以为豪的主要特性,也是与Hadoop等分布式计算框架对比过程中一大亮点,但是时代和技术都在演化,从Spark1.3.1版本开始,为了解决大块数据(如Shuffle)的传输问题,Spark引入了Netty通信框架,到了1.6.0版本,Netty居然完成取代了Akka,承担Spark内部所有的RPC通信以及数据流传输。
38 |
39 | + 开始埋坑日期:2016-4-1
40 | + 坑状态:done
41 |
42 | ### [Hadoop DataNode分析](./hadoop/datanode.md)
43 | 在HDFS集群中,DataNode直接提供了磁盘文件的管理,也是性能的最大的瓶颈之一,对DataNode的分析显得尤为重要。这次对DataNode串读了一下,对DataNode基本逻辑有一定的了解
44 |
45 | + 开始埋坑日期:2015-12-25
46 | + 坑状态:done
47 |
48 | ### [Hadoop IPC分析](./hadoop/hadoop-ipc.md)
49 | 最近比较悠闲,想回头再看一下Hadoop源码,准备着重了解一下HDFS内部的细节,突然发现以前熟读的hadoop ipc又有点模糊了,为了加深记忆,还是在这里重新梳理一下,针对大体思路上做一下笔记,hadoop源码分析类的“笔记”好难写,代码量太大了,只能扣一下比较重要的细节解释一下,具体的还是要自己去阅读代码!!!!
50 |
51 | + 开始埋坑日期:2015-12-15
52 | + 坑状态:done
53 |
54 |
55 | ### [系统进程性能分析](./system/java-memory.md)
56 | 好久没有埋坑了,最近突然想把系统性能以及问题跟踪相关的原理和工具稍微整理,基本都是日常开发和运维不可缺少的东西,包括[《进程分析之内存》](./system/memory.md),[《进程分析之JAVA内存》](./system/java-memory.md),[《进程分析之CPU》](./system/cpu.md),[《进程分析之磁盘IO》](./system/disk-io.md),《进程分析之网络IO》;
57 |
58 | + 开始埋坑日期:2015-12-02
59 | + 坑状态:doing
60 |
61 |
62 | ### [Pregel原理分析与Bagel实现](./spark/pregel-bagel.md)
63 | [Pregel](http://people.apache.org/~edwardyoon/documents/pregel.pdf) 2010年就已经出来了, [Bagel](https://spark.apache.org/docs/latest/bagel-programming-guide.html)也2011年
64 | 就已经在spark项目中开源, 并且在最近的graphX项目中申明不再对Bagel进行支持, 使用GraphX的"高级API"进行取代, 种种迹象好像说明Pregel这门技术已经走向"末端", 其实个人的观点倒不是这样的;
65 |
66 | 最近因为项目的需要去调研了一下图计算框架,当看到Pregel的时候就有一种感叹原来"密密麻麻"的图计算可以被简化到这样. 虽然后面项目应该是用Graphx来做,但是还是想对Pregel做一个总结.
67 |
68 | + 开始埋坑日期:2014-12-12
69 | + 坑状态:done
70 |
71 | ### [MLLib Pipeline的实现分析](./spark/mllib-pipeline.md)
72 | Spark中的Mllib一直朝着可实践性的方法前进着, 而Pipeline是这个过程中一个很重要的功能. 在2014年11月,孟祥瑞在Spark MLLib代码中CI了一个全新的package:"org.apache.spark.ml",
73 | 和传统的"org.apache.spark.mllib"独立, 这个包即Spark MLLib的Pipeline and Parameters功能. 到目前为止,这个package只有三次ci, 代码量也较少,但是基本上可以清楚看到pipeline逻辑,
74 | 这里开第一个mllib的坑, 开始对mllib进行深入学习.
75 |
76 | + 开始埋坑日期:2014-12-10
77 | + 坑状态:done
78 |
79 | ### [Spark基础以及Shuffle实现分析](./spark/shuffle-study.md)
80 | 包括mapreduce和spark在内的所有离线计算工具,shuffle操作永远是设计最为笨重的,也是整体计算性能的瓶颈。主要原因是shuffle操作是不可避免的,
81 | 而且它涉及到大量的本地IO,网络IO,甚至会占用大量的内存,CPU来做sort-based shuffle相关的操作。
82 | 这里挖一个这个坑,由于第一个坑,所以我会在这个坑里面阐述大量的spark基础的东西,顺便对这些基础做一下整理,包括Job的执行过程中等;
83 |
84 | + 开始埋坑日期:2014-9-24
85 | + 坑状态:done
86 |
87 | ### [两种ShuffleManager的实现:Hash和Sort](./spark/shuffle-hash-sort.md)
88 | 在《[Spark基础以及Shuffle实现分析](./spark/shuffle-study.md)》中分析了Spark的Shuffle的实现,但是其中遗留了两个问题.
89 | 本文针对第二个问题:"具体shuffleManager和shuffleBlockManager的实现"进行分析, 即HashShuffle和SortShuffle当前Spark中支持了两种ShuffleManager的实现;
90 | 其中SortShuffle是spark1.1版本发布的,详情参见:[Sort-based shuffle implementation](https://issues.apache.org/jira/browse/SPARK-2045)
91 | 本文将对这两种Shuffle的实现进行分析.
92 |
93 | + 开始埋坑日期:2014-11-24
94 | + 坑状态:done
95 |
96 | ###[关于Scala的implicit(隐式转换)的思考](./spark/scala-implicit.md)
97 | Scala的隐式转换是一个很重要的语法糖,在Spark中也做了很多应用,其中最大的应用就是在RDD类型上提供了reduceByKey,groupByKey等函数接口, 如果不能对隐式
98 | 转换很好的理解, 基本上都无法理解为什么在RDD中不存在这类函数的基础上可以执行这类函数.文章内部做了解释, 同时针对隐式转换做了一些总结和思考
99 |
100 | + 开始埋坑日期:2014-12-01
101 | + 坑状态:done
102 |
103 | ### [Spark-Block管理](./spark/spark-block-manager.md)
104 | 在Spark里面,block的管理基本贯穿了整个计算模型,从cache的管理,shuffle的输出等等,都和block密切相关。这里挖一坑。
105 |
106 | + 开始埋坑日期:2014-9-25
107 | + 坑状态:done
108 |
109 | ### [Spark-Block的BlockTransferService](./spark/spark-block-manager.md)
110 | 在上面的Spark-BlockManager中,我们基本了解了整个BlockManager的结构,但是在分析Spark Shuffle时候,我发现我遗留了对BlockTransferService的分析,
111 | 毕竟Spark的Shuffle的reduce过程,需要从远程来获取Block;在上面的文章中,对于remoteGET,分析到BlockTransferService就停止了,这里补上;
112 |
113 | 其实个人在0.91版本就遇到一个remoteGet的bug, 即当时remoteGet没有进行超时控制,消息丢包导致假死, 当然目前版本没有这个问题了. 具体的我会在这篇文章中进行解释;
114 |
115 | + 开始埋坑日期:2014-11-25
116 | + 期望完成日期:2014-12-10
117 | + 坑状态:doing
118 |
119 | ### [Spark闭包清理的理解](./spark/function-closure-cleaner.md)
120 | scala是一门函数编程语言,当然函数,方法,闭包这些概念也是他们的核心,在阅读spark的代码过程,也充斥着大量关于scala函数相关的特性引用,比如:
121 |
122 | def map[U: ClassTag](f: T => U): RDD[U] = new MappedRDD(this, sc.clean(f))
123 | map函数的应用,每次我传入一个f都会做一次sc.clean的操作,那它到底做了什么事情呢?其实这些都和scala闭包有关系。
124 | 同时java8之前版本,java不对闭包的支持,那么java是通过内部类来实现,那么内部类与闭包到底有那些关系和区别呢?同时会深入剖析java的内部类与scala的内部类的区别
125 |
126 | + 开始埋坑日期:2014-9-25
127 | + 期望完成日期:2014-10-30
128 | + 坑状态:doing
129 |
130 | ### [HBase总结笔记](./hbase/hbase-learn.md)
131 | 在Hadoop系里面玩了几年了,但是HBase一直以来都不太原因去深入学习.这次借项目,系统的对HBase进行学习,这里对Hbase里面一些核心主题进行总结.
132 | 目前还没有打算从源码层面去深入研究的计划,后面有时间再一一研究.
133 |
134 | + 开始埋坑日期:2014-10-10
135 | + 坑状态:done
136 |
137 | ### [HBase Bulk Loading实践](./hbase/hbase-bulk-loading.md)
138 | 近期需要将mysql中的30T的数据导入到HBase中,一条条put,HDFS的IO负载太大,所以采用Hbase内部提供的Bulk Loading工具批量导入.
139 | Bulk Loading直接通过把HFile文件加载到已有的Hbase表中,因此我们只需要通过一个mapreduce将原始数据写为HFile格式,就可以轻易导入大量的数据.
140 |
141 | + 开始埋坑日期:2014-10-15
142 | + 坑状态:done
143 |
144 | ###[HBase Filter学习](./hbase/hbase-filter.md)
145 | HBase的逻辑查询是严格基于Filter来实现的,在HBase中,针对Filter提供了一个包来实现,类型还是挺多的,因为业务需要,这里做一个简单的整理.
146 | 对日常比较需要的Filter拿出来做一个分析
147 |
148 | + 开始埋坑日期:2014-11-10
149 | + 坑状态:done
150 |
151 | ### [NodeManager解析系列一:内存Monitor分析](./hadoop/nodemanager-container-monitor.md)
152 | 用过MapReduce都遇到因为task使用内存过多,导致container被kill,然后通过网上来找资料来设置mapreduce.map.memory.mb/mapreduce.reduce.memory.mb
153 | /mapreduce.map.java.opts/mapreduce.reduce.java.opts来解决问题。但是对于内部实现我们还是不清楚,这篇文章就是来解析NodeManager怎么
154 | 对container的内存使用进行Monitor
155 |
156 | + 开始埋坑日期:2014-10-20
157 | + 坑状态:done
158 |
159 | ### [NodeManager解析系列二:Container的启动](./hadoop/nodemanager-container-launch.md)
160 | Hadoop里面模块很多,为什么我优先对NodeManager进行解析呢?因为NodeManager与我提交的spark/mapreduce任务密切相关,
161 | 如果对NodeManager理解不透,都不能理解Spark的Executor是怎么被调度起来的。这篇文件就是对Container的启动进行分析
162 |
163 | + 开始埋坑日期:2014-11-2
164 | + 坑状态:done
165 |
166 | ### [NodeManager解析系列三:Localization的分析](./hadoop/nodemanager-container-localizer.md)
167 | 任何一个阅读过NodeManager源码的人都被Localization弄得晕头转向的,从LocalResource,LocalizedResource,LocalResourcesTracker,
168 | LocalizerTracker这些关键字开始,命名十分接近,稍不注意注意就搞糊涂了,这篇文章对Localization进行分析,从个人感受来看,对Localization
169 | 理解透了,基本上NodeManager都理解差不多了
170 |
171 | + 开始埋坑日期:2014-11-2
172 | + 坑状态:done
173 |
174 | ### [NodeManager解析系列四:与ResourceManager的交互](./hadoop/nodemanager-container-withrm.md)
175 | 在yarn模型中,NodeManager充当着slave的角色,与ResourceManager注册并提供了Containner服务。前面几部分核心分析NodeManager所提供的
176 | Containner服务,本节我们就NodeManager与ResourceManager交互进行分析。从逻辑上说,这块比Containner服务要简单很多。
177 |
178 | + 开始埋坑日期:2014-11-4
179 | + 坑状态:done
180 |
181 | ### [Hadoop的Metric系统的分析](./hadoop/metric-learn.md)
182 | 对于Hadoop/Spark/HBase此类的分布式计算系统的日常维护,熟读系统的metric信息应该是最重要的技能.本文对Hadoop的metric/metric2的实现进行深究,
183 | 但也仅仅是从实现的角度进行分析,对metric的完全理解需要时间积累,这样才能理解整个系统中每个metric的值对系统的影响.
184 | 在JVM内部,本身也有一套metric系统JMX,通过JMX可以远程查看甚至修改的应用运行时信息,本文将会从JMX开始,一步一步对这几套系统metric的实现进行分析.
185 |
186 | + 开始埋坑日期:2014-10-15
187 | + 期望完成日期:2014-10-30
188 | + 坑状态:doing
--------------------------------------------------------------------------------
/Spark-Note.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/hadoop/datanode.md:
--------------------------------------------------------------------------------
1 | # DataNode分析
2 |
3 | HDFS分为DataNode和NameNode,其中NameNode提供了Meta信息的维护,而DataNode提供了真实文件块的存储和读写;NameNode是在内存中维护整个文件树,很吃内存,但是在NameNode内存充足的时候,基本上NameNode不会成为性能的瓶颈,反而DataNode,它提供了真实Block的读写,整体HDFS性能是否满足需求,就要看DataNode了。
4 |
5 | ## DataNode向NameNode提供Block存储“管理”功能
6 | Namespaces和BlockPool是NameNode和DataNode管理文件目录树/Block的单位;每个NameNode都属于一个Namespace,它维护了该文件系统上的文件目录树,其中目录的信息只需要维护在NameNode上,而文件Block信息与读写操作需要DataNode参与,即每一个NameNode上的Namespace在DataNode上都对应一个BlockPool;
7 |
8 | 对于一个HDFS集群cluster来说,传统只有一组namenode(这里的一组指的是HA,但是任何时间都只有一个NameNode提供服务)维护一个Namespace和一组datanode,每个datanode都维护一个BlockPool;但是在现在支持Federation功能的集群上,一个cluster是可以包含多组namenode,每组namenode独立维护一个Namespace,但是datanode不是独立的,此时每个datanode需要针对每个Namespace都维护一个BlockPool;
9 |
10 | 
11 |
12 | DataNode通过BlockPool给NameNode提供了Block管理的功能,但是NameNode从不主动的去请求DataNode去做何种动作,而是DataNode针对每个BlockPool都维护一个与自己归属的NameNode之间心跳线程,定期的向NameNode汇报自身的状态,在NameNode返回的心跳值中会携带相关的Command命令信息,从而完成NameNode对DataNode控制。
13 |
14 | 
15 |
16 | 在DataNode中,对一组BlockPool的管理是通过BlockPoolManager这个类来提供的,在DataNode启动时,会初始化一个该对象,并从配置中读取该datanode需要服务的BlockPool,并针对每个BlockPool初始化一个BPOfferService,并与相应的NameNode完成注册以及心跳的维护。
17 |
18 | //DataNode中的逻辑
19 | blockPoolManager = new BlockPoolManager(this);
20 | //从配置中读取datanode需要服务的BlockPool
21 | blockPoolManager.refreshNamenodes(conf);
22 | //BlockPoolManager中的逻辑
23 | for (String nsToAdd : toAdd) {
24 | ArrayList addrs =
25 | Lists.newArrayList(addrMap.get(nsToAdd).values());
26 | BPOfferService bpos = new BPOfferService(nnAddrs, dn);
27 | bpByNameserviceId.put(nsToAdd, bpos);
28 | offerServices.add(bpos);
29 | }
30 |
31 | 对于支持HA的环境下,每一个NameSpace是有一组而不是一个NameNode,单个Active,多个Standby,那么问题来了?DataNode是只需要和Active注册,交互吗?不,对于每一个BPOfferService,它会与该组的每一个NameNode都维护一个连接以及心跳,其中每个连接表示一个BPServiceActor对象。但是在心跳协议的工作过程中,BPOfferService是不会响应来自Standby NameNode的任何命令信息。
32 |
33 | //BPOfferService中的逻辑
34 | for (InetSocketAddress addr : nnAddrs) {
35 | this.bpServices.add(new BPServiceActor(addr, this));
36 | }
37 | private BPServiceActor bpServiceToActive = null;//当前处于Active的BPServiceActor
38 |
39 | 对于具体BPServiceActor与NameNode之间的交互包括哪些功能呢?
40 |
41 | - 两次握手:在BPServiceActor初始化过程中,需要与NameNode建立起连接,并完成两次握手;第一次握手是获取NameNode的namespace信息(NamespaceInfo),并针对NameNode的版本进行验证;第二次握手是向NameNode进行register注册,获取注册以后的信息(DatanodeRegistration);
42 | - 心跳协议:BPServiceActor会定期与NameNode维持一个心跳协议;心跳信息除了维持一个节点存在性以外,还会在心跳信息中带上当前BlockPool每一个Volumn的StorageReport,容量信息和有问题的Volume,注意这些都只是信息;
43 | - blockReport:定期心跳只会上报基本容量等信息,而是否上报DataNode存储的Block信息需要根据心跳的返回值来确定(是否设置了fullBlockReportLeaseId);此时BPServiceActor通过Dataset().getBlockReports获取当前BlockPool的Block列表,并完成汇报。
44 | - cacheReport:汇报当前的BlockPool的Dataset().getCacheReport的信息
45 | - reportReceivedDeletedBlocks:向NameNode汇报已经响应删除的Block列表
46 | - reportBadBlocks:汇报有问题的block,在进行block扫描过程中,如果发现block的文件不存在就为有问题的block;(在上传的过程中,如果client没有正常关闭,此时datanode也会有一个处理坏掉block的过程,即reportRemoteBadBlock)
47 |
48 | NameNode对BPServiceActor的操作是通过心跳协议来返回的,其中主要的操作包括:
49 | - DNA_TRANSFER: Block的复制,将本地的一个Block复制(transferBlocks)到目标DataNode
50 | - DNA_INVALIDATE:回收删除Block
51 | - DNA_CACHE:将一组Block进行Cache
52 | - DNA_UNCACHE:将一组Block从Cache中删除
53 | - DNA_SHUTDOWN:关闭DataNode
54 | - DNA_FINALIZE:将当前的BlockPool置为finalize状态
55 | - DNA_RECOVERBLOCK:恢复一个Block,@todo后面会详细的分析Block的恢复
56 |
57 | 上面就是DataNode向NameNode提供Block存储“管理”功能
58 |
59 | ## DataNode提供对Replica副本操作的封装
60 | 在上面小结中,我们分析了DataNode可以为NameNode提供哪些Block管理功能,但是这些都仅是Block信息(BlockInfo)和Pool信息的交互而已,而对具体Block对应的磁盘文件的管理是通过FsDataset这个对象来提供的。
61 |
62 | 另外,在HDFS上,虽然Block是贯穿在整个系统中,但是在DataNode上,用Replica副本这个概念来解释可能更好点。何为副本?在HDFS上,每一个Block都有单副本或者多副本,这些副本分布在所有DataNode上,由NameNode来维护它的存在,而DataNode来提供副本的存储和读写。
63 |
64 | 
65 |
66 | FsDataset对象功能分为两个粒度,分别为对Pool和Replica的管理。
67 | - Pool层面的管理DataStorage:如上描述,针对每个BlockPool,DataNode都是独立维护一个Pool管理对象BlockPoolSliceStorage,比如DataNode第一次初始化时候,需要针对每个Pool创造pool根目录,DataNode的升级也需要BlockPoolSliceStorage的支持;
68 | - Replica层面的管理FsVolume:在HDFS的配置中,“dfs.datanode.data.dir”可以指定多个Volume目录,每一个目录就对应这里的一个FsVolume对象,在每一个FsVolume对象内部,针对每个BlockPool都创建一个目录和一个对象对Pool上的Replica文件进行物理层面上的管理,这里对象即为BlockPoolSlice;比如BlockPoolSlice.createRbwFile函数可以在磁盘上指定pool的目录下面创建相应的Block文件;DataNode对Pool磁盘空间的管理,比如一个Pool在磁盘使用多少空间,也是通过BlockPoolSlice.getDfsUsed获取每个pool当前占用的磁盘空间,然后进行求和来获取的。
69 |
70 | DataNode上每个Block都是落地在磁盘上,可以通过blockid快速定位到磁盘路径(DatanodeUtil.idToBlockDir),每一个Block在物理上由Meta和Data两个文件组成,其中Meta中存储了Block的版本,crc32,id,大小,生成时间等信息,从原则上来说,好像是可以不需要在内存中缓存DataNode上存储的Block文件列表,对于Block的操作可以逐步的根据BlockPoll和Blockid定位到磁盘上的文件,但是偏偏在DataNode上维护了一个大Map(ReplicaMap)存储了当前DataNode处理的副本(Map> map)。不懂!!而且需要消耗一个线程DirectoryScanner定期将ReplicaMap中的信息与磁盘中文件进行check(FsDatasetImpl.checkAndUpdate)。
71 |
72 | 针对整个DataNode上每个Volumn目录,都有一个VolumeScanner对象,并由BlockScanner进行集中管理,它们负责对磁盘上存储的副本进行校验(校验的方式和读文件时候逻辑一直,只是把读的文件写到/dev/null中),在写失败的过程中也会针对写失败的文件标记为markSuspectBlock,优先进行扫描,如果扫描过程中发现有问题的Block,会调用datanode.reportBadBlocks向NameNode标记坏掉的Block。
73 |
74 |
75 | fsDataset对磁盘上的Block文件的删除是采用异步线程来处理的即FsDatasetAsyncDiskService,从而不会因为大的Block删除阻塞DataNode的逻辑
76 |
77 |
78 | ## DataNode提供Client文件读写的功能
79 | 在整个HDFS文件操作过程中,DataNode提供了文件内容的最终读写的能力,在DataNode中,整个数据的流动由DataXceiverServer服务来承担,它是基于Peer的通信机制,差不多是唯一一个不基于IPC通信的通信模块。
80 |
81 | 在DataXceiverServer的启动时,会初始化一个peerServer,并循环阻塞在peerServer.accept()上,接受每一个Client的连接。在HDFS中,数据的读写是采用pipeline机制,所有Client可能是客户端,也可能是另外一个DataNode,同时在数据balancer过程中,Client也可能是DataNode,这也是peer的含义,即对等通信,DataNode之间互为Server和Client。关于Peer的细节这里就不分析了,比较简单,在日常的运维的过程中,只需要关注peer连接数是否达到上限(默认是4096,很大了!),peer之间读写是否超时就可以。
82 |
83 | DataXceiverServer是一个PeerServer容器,每一个与当前DataNode的连接都会创建Peer,并被DataXceiverServer封装为一个DataXceiver实例,该实例实现了DataTransferProtocol接口,由它来处理peer之间的交互逻辑,其中主要包括下面三个功能:
84 |
85 | public interface DataTransferProtocol {
86 | void readBlock(final ExtendedBlock blk,final long blockOffset,....)
87 | void writeBlock(final ExtendedBlock blk,DatanodeInfo[] targets,....)
88 | void transferBlock(final ExtendedBlock blk,final DatanodeInfo[] targets,...)
89 | ...
90 | }
91 |
92 | 读比较好理解,写和balancer的操作中有一个DatanodeInfo[] targets参数,它即为DataNode写文件的Pipeline特性,即当前DataNode写完以后,需要将Block写到targets目标DataNode。
93 |
94 | Peer之间连接是保持Keepalive,默认超时时间是4000ms,同时连接是是双向的,有Input/OutputStream两个流,那么对于一个Peer,是怎么确定这个Peer的功能呢?是读还是写?在DataXceiver内部是通过一个op的概念来标识的。每次一个流的第一个字节来表标识这个流的功能。目前包括:
95 |
96 | public enum Op {
97 | WRITE_BLOCK((byte)80),
98 | READ_BLOCK((byte)81),
99 | READ_METADATA((byte)82),
100 | REPLACE_BLOCK((byte)83),
101 | COPY_BLOCK((byte)84),
102 | BLOCK_CHECKSUM((byte)85),
103 | TRANSFER_BLOCK((byte)86),
104 | REQUEST_SHORT_CIRCUIT_FDS((byte)87),
105 | RELEASE_SHORT_CIRCUIT_FDS((byte)88),
106 | REQUEST_SHORT_CIRCUIT_SHM((byte)89),
107 | CUSTOM((byte)127);
108 | }
109 | 在DataXceiver.readOp()内部来获取流的功能,并在DataXceiver.processOp(op)中,针对不同的流进行switch匹配和处理。下面我们会针对两个重要的流READ_BLOCK/WRITE_BLOCK进行分析。
110 |
111 | ### READ_BLOCK
112 | 对于READ操作,InputStream在Op字节后有一个序列化的OpReadBlockProto,表示这次读操作的相关参数,其中包括以下参数:
113 |
114 | message OpReadBlockProto {
115 | message ClientOperationHeaderProto {
116 | message BaseHeaderProto {
117 | required ExtendedBlockProto block = 1;//操作的Block
118 | optional hadoop.common.TokenProto token = 2;
119 | optional DataTransferTraceInfoProto traceInfo = 3;
120 | }
121 | required string clientName = 2;//客户端唯一标识
122 | }
123 | required uint64 offset = 2;//对应Block的偏移量
124 | required uint64 len = 3;//读取的长度
125 | optional bool sendChecksums = 4 [default = true];
126 | optional CachingStrategyProto cachingStrategy = 5;
127 | }
128 |
129 | 其中核心参数就是block+offset+len,它指定需要读取的block的id,偏移量与大小。
130 |
131 | ReadBlock主要包括以下几个步骤:
132 | - DataXceiver首先会向Client发送一个READ操作响应流,其中响应了这次操作结果Status=SUCCESS以及后面发送数据的checksum。
133 | - 随后DataXceiver调用BlockSender将该Block的数据块发送到peer的outputStream中。
134 | - 最后需要等待Client返回一个ClientReadStatusProto来表示这次读取操作的Status结果(SUCCESS/ERROR/......)
135 |
136 | BlockSender内部实现了具体Block流在网络中怎么传输,后面再开一篇具体分析,里面重点逻辑是通过FsDataset.getBlockInputStream获取Block的数据流,并转化为一个一个的Packet发送到对方。
137 |
138 |
139 | ###WRITE_BLOCK
140 | 相比文件读操作,文件写操作要复杂的多,HDFS的Block写是采用pipeline,整个Pipeline包括建立,传输和关闭几个阶段。其中建立和关闭过程是一次请求,传输是反复的过程。
141 |
142 | 1. **Pipeline建立:PIPELINE_SETUP_CREATE/PIPELINE_SETUP_APPEN**
143 | Block写请求会沿着pipeline被多个DataNode接收,每个DataNode会针对写操作创建或打开一个block,并等待后面的数据写操作。末端DataNode接收建立请求以后,会发送一个ACK,并逐级返回给Client,从而完成整个pipeline建立。
144 |
145 | 2. **Pipeline数据传输:**
146 | 与BlockRead一致,Client与DataNode之间的数据传输是以Packet为单位进行传输。具体的细节是封装在BlockReceiver内部,和上面的BlockSender一样,后面会具体的去分析。
147 |
148 | 3. **Pipeline关闭:**
149 | 处于数据传输阶段的Block为RBW,如果当前的block写结束(即最后一个packet写成功:lastPacketInBlock == true),会将当前Block的状态转化为Finalized状态,并向NameNode进行Block汇报。
150 |
151 | Block写的核心内容还是在BlockReceiver内部,后面再详细分析吧,这里对WRITE_BLOCK的了解只需要理解到pipeline层面就够了。
152 |
--------------------------------------------------------------------------------
/hadoop/namenode-ha.md:
--------------------------------------------------------------------------------
1 | # NameNode HA解析
2 |
3 |
4 | HA即高可用性,Namenode维护了整个HDFS集群文件的Meta信息,早期设计它为单点结构,而且功能较重,无法做到快速重启。在后续迭代过程中,通过提供SecondNamenode和BackupNamenode的功能支持,可以实现对NamnodeMeta的备份和恢复的功能,但是它们无法做到线上故障的快速恢复,需要人工的介入,即它只是保证了整个HDFS集群Meta信息的不丢失和数据的完整性,而不能保证集群的可用性。
5 |
6 | Namespace是NameNode管理Meta信息的基本单位,通过NamespaceID进行唯一标示。在HA的架构下,对于一个Namespace的管理可以由多个Namenode实例进行管理,每一个NameNode实例被称为一个NamespaceServiceID。但是在任何时刻只有一个Namenode实例为Active,其他为Standby,只有处于Active状态的节点才可以接受Client的操作请求,而其他Standby节点则通过EditLog与Active节点保持同步。
7 |
8 | 同时我们知道,NameNode对Block的维护是不会持久化Block所在的DataNode列表,而是实时接受DataNode对Block的Report。在HA架构中,NameNode之间不会对Block的Report进行同步,而是依赖DataNode主动对每一个NameNode进行Report,即对于一个NameSpace下的BlockPool的管理,每一个DataNode需要明确知道有哪些NamespaceServiceID参与,并主动向其上报Block的信息。
9 |
10 | 因此在任何时刻,这一组NameNode都是处于热状态,随时都可以从Standby状态升级为Active从而为Client提供服务。
11 |
12 | 对于状态迁移,HA架构基于Zookeep提供一个轻量级的主竞选和Failover的功能,即通过一个锁节点,谁拿到这个节点的写权限,即为Active,而其他则降级为Standby,并监听该锁节点的状态变化,只要当前Active释放了写权限,所有Standby即立即竞选成为Active,并对外提供服务。
13 |
14 | ##1. Image与EditLog
15 | NameNode维护了一个Namespace的所有信息,如Namespace中包括的文件列表以及ACL权限信息,而Image即为内存中Namespace序列化到磁盘中的文件。和Mysql之类的存储不同,对Namespace的操作不会直接将其他操作后的结果写入到磁盘中Image文件中,而只是做内存数据结构的修改,并通过写EditLog的方式将操作写入日志文件。而只会在特定的情况下将内存中Namespace信息checkpoint到磁盘的Image文件中。
16 |
17 | 为什么不直接写入Image而转写EditLog文件呢?主要和两个文件格式设计有关系,其中Image文件设计较为紧凑,文件大小只会受NameSpace文件目录个数影响,而与操作次数无关,同时它的设计主要是为了易读取而不宜修改,比如删除一个文件,无法直接对Image文件中的一部分进行修改,需要重写整个Image文件。而EditLog为追加写类型的文件,它记录了对Namespace每一个操作,EditLog文件个数和大小与操作次数直接相关,在频繁的操作下,EditLog文件大小会很大。受此限制,NameNode提供了一定checkpoint机制,可以定时刷新内存中NameSpace信息到Image文件中,或者将老的Image文件与EditLog进行合并,生成一个新的Image文件,进而可以将过期的EditLog文件进行删除,减小对NameNode磁盘的浪费。
18 |
19 |
20 | 另外,在HA架构中,EditLog提供了Active与Standby之间的同步功能,即通过将EditLog重写到Standy节点中,下面开始一一分析它的实现原理。
21 |
22 | ### 1.1 初始化
23 | `dfs.namenode.name.dir`和`dfs.namenode.edits.dir`为NameNode存储Image和EditLog文件目录。在HDFS集群安装过程中,需要进行Format操作,即初始格式化这两个目录,主要有以下几个操作:
24 |
25 | 1. 初始化一个Namespace,即随机分配一个NamespaceID,上面我们谈到Active节点和Standby节点是同时为一个NamespaceID服务,因此我们肯定的是:Format操作只会发生在Active节点上,Standby节点是不需要Format节点。
26 | 2. 将NameSpace信息写入到VERSION文件,并初始化seen_txid文件内容为0。
27 |
28 | namespaceID=972780058
29 | clusterID=yundata
30 | cTime=0
31 | storageType=NAME_NODE
32 | blockpoolID=BP-102718850-10.54.38.52-1459226879449
33 | layoutVersion=-63
34 |
35 | > 关于ClusterID的补充:hdfs namenode -format [-clusterid cid ]。 format操作有一个可选的clusterid参数,默认不提供的情况下,即随机生成一个clusterid。
36 | > 那么为什么会有Cluster概念呢?它和Namespace什么关系?正常情况,我们说一个hdfs集群(cluster),它由一组NameNode维护一个Namespace和一组DataNode而组成。即Cluster和NameSpace是一一对应的。
37 | > 但是在支持Federation功能的集群下,一个Cluster可以由多个NameSpace,即多组Namenode,它主要是解决集群过大,NameNode内存压力过大的情况下,可以对NameNode进行物理性质的划分。
38 | > 你可能会问?那为什么不直接搞多套HDFS集群呢?在没有Federation功能情况下,一般都是这样做的,毕竟NameNode的内存是有限的。但是搞多套HDFS集群对外就由多个入口,而Federation可以保证对外只有一个入口。
39 |
40 | 3. 将当前初始化以后Image文件(只包含一个根目录/)写入到磁盘文件中,并初始化一个Edit文件接受后面的操作日志写。
41 | 4. 格式化其他非FileEditLog(即提供分享功能的EditLog目录。)
42 |
43 | ### 1.2 基于QJM的EditLog分享存储
44 | `dfs.namenode.name.dir`和`dfs.namenode.edits.dir`只是提供本地NameNode对Image和EditLog文件写的路径,而在HA架构中,NameNode之间是需要进行EditLog的同步,此时需要一个"shared storage "来存储EditLog,HDFS提供了NFS和"Quorum Journal Manager"两套解决方案,这里只会对"Quorum Journal Manager"进行分析。
45 |
46 | 如格式化的第四步的描述,需要对非FileEditLog进行格式化,而这里QJM即为非FileEditLog之一。
47 |
48 | 对于HDFS来说,QJM与Zookeeper性质接近,都是是一个独立的共享式服务,也由2N+1个Journal节点组成,为NameNode提供了EditLogOutputStream和EditLogInputStream读写接口。关于读写它由以下几个特点:
49 |
50 | 1. 对于2N+1个节点组成的QJM集群,在写的情况下,不需要保证所有节点都写成功,只需要其中N+1节点(超过1/2)写成功就表示写成功。这也是它为什么叫着Quorum的原因了,即它是基于Paxos算法。
51 | 2. 在读的情况下,它提供了组合接口,即可以将所有Journal节点上的EditLog按照txid进行排序,组合出一个有效的列表,提供NameNode进行读。
52 | 3. 写Journal失败对于NameNode是致命的,NameNode会直接退出。
53 |
54 | QJM是一个较为轻量的集群,运维也比较简单,挂了直接起起来就可以,只要不超过N个节节点挂了,集群都可以正常功能。
55 |
56 | ### 1.3 EditLog Roll的实现
57 |
58 | ### 1.4 Image Checkpoint的实现
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/hadoop/nodemanager-container-monitor.md:
--------------------------------------------------------------------------------
1 | NodeManager解析系列一:内存Monitor解析
2 | ====
3 |
4 | Yarn调度起来的任务以Container的方式运行在NodeManager节点中,NodeManager负责对Container的运行状态进行监控。在NodeManager中针对每个Container都有一个线程来阻塞并等待Container进程结束. 同时由于启动的进程会递归地生成新的进程,因此Yarn需要对整个进程树进行监控才能正确获取Container所占用的内存等信息。
5 |
6 | 为了控制Container在运行过程中所占用的Memory和Cpu使用情况,NodeManager有两种实现方式:
7 |
8 | + 使用Linux内部的Cgroup来进行监控和限制
9 | + NodeManager中的Monitor线程对运行在该NodeManager上所有的Container进行监控,在发现超过内存限制时,会请求NodeManager杀死相应Container。
10 |
11 | 基于Cgroup的工作方式除了控制内存还可以在Cpu等多个方面进行控制,但除非Yarn集群是完全公用化,需要进行很强度的控制,否则第二种方式基本满足业务的需求。本文也主要针对第二种方式中Container Monitor进行讨论。
12 |
13 | ### 进程基本信息和内存占用
14 | Container Monitor要对Container进行监控,首先第一个需要解决的问题即如何获取每个Container当前资源占用情况.
15 |
16 | NodeManager提供了ResourceCalculatorProcessTree接口来获取进程的基本信息,用户可以通过NodeManager的配置项"yarn.nodemanager.container-monitor.process-tree.class"进行自定义配置。目前,分别针对Window和Linux环境提供来默认的实现,分别为WindowsBasedProcessTree和ProcfsBasedProcessTree的实现。
17 |
18 | 这里简单分析一下Linux环境下的ProcfsBasedProcessTree的实现.
19 |
20 | Linux中,每个运行的进程都在/proc/目录下存在一个子目录,该目录下的所有的文件记录了该进程运行过程中所有信息,通过对这些子文件进行解析,就可以获取进程详细的信息。 其中,ProcfsBasedProcessTree利用到的文件有:`cmdline, stat, smaps`
21 |
22 | cmdline文件中记录该进程启动的命令行信息,如果是java程序,就相当于通过命令"jps -v"获取的进程信息,不过cmdline记录文件中用\0来代替空格,需要做一次反替代。
23 |
24 | work@node:~$ cat /proc/6929/cmdline
25 | /home/work/opt/jdk1.7/bin/java-Xms128m-Xmx750m-XX:MaxPermSize=350m-XX:ReservedCodeCacheSize=96m-ea
26 | -Dsun.io.useCanonCaches=false-Djava.net.preferIPv4Stack=true-Djsse.enableSNIExtension=false-XX:+UseCodeCacheFlushing
27 | -XX:+UseConcMarkSweepGC-XX:SoftRefLRUPolicyMSPerMB=50-Dawt.useSystemAAFontSettings=lcd
28 | -Xbootclasspath/a:/home/work/opt/idea/bin/../lib/boot.jar-Didea.paths.selector=IdeaIC13-Djb.restart.code=88com.intellij.idea.Main
29 |
30 | stat文件是一堆数字堆砌而成,其中包含的信息比较多,没有必要可以不全了解。如下
31 |
32 | work@node:~$ cat /proc/6929/stat
33 | 6929 (java) S 6892 1835 1835 0 -1 1077960704 254628 201687 317 391 120399 23093 3098 329 20 0 65 0 99371 3920023552
34 | 206380 18446744073709551615 4194304 4196452 140735679649776 140735679632336 140462360397419 0 0 4096 16796879
35 | 18446744073709551615 0 0 17 1 0 0 0 0 0 6293608 6294244 28815360 140735679656977 140735679657483
36 | 140735679657483 140735679659993 0
37 |
38 | ProcfsBasedProcessTree针对stat文件提供了ProcessInfo类的实现,它通过读取stat文件来动态更新每个进程的基本信息
39 |
40 | private static class ProcessInfo {
41 | private String pid; // process-id=6929 进程号
42 | private String name; // command name=(java) 进程名称
43 | //stat=S 进程状态,R:runnign,S:sleeping,D:disk sleep , T: stopped,T:tracing stop,Z:zombie,X:dead
44 | private String ppid; // parent process-id =6892 父进程ID
45 | private Integer pgrpId; // process group-id=1835 进程组号
46 | private Integer sessionId; // session-id=6723 c该任务所在的会话组ID
47 | private Long utime = 0L; // utime=120399 该任务在用户态运行的时间,单位为jiffies
48 | private BigInteger stime = new BigInteger("0"); // stime=23093 该任务在核心态运行的时间,单位为jiffies
49 | private Long vmem; // 单位(page) 该任务的虚拟地址空间大小
50 | private Long rssmemPage; // (page) 该任务当前驻留物理地址空间的大小
51 |
52 | 其中utime的单位为jiffies可以通过命令`getConf CLK_TCK`获取,page的页大小单位可以通过`getConf PAGESIZE`获得。
53 |
54 | 另外可以通过一定时间间隔内连续两次获取同一个进程的ProcessInfo,利用两次的utime+stime之和的增量值来表示该时间间隔中,进程所消耗的CPU时间片。
55 |
56 | `smaps`文件是在Linux内核 2.6.16中引入了进程内存接口,它相比stat文件中统计的rssmem要更加准确。但是当前的Hadoop版本是默认关闭该功能,用户可以配置yarn.nodemanage.container-monitor.procfs-tree.smaps-based-rss.enabled=true来启用。
57 |
58 | 对于每个进程,`smapes`在逻辑上是由多段虚拟内存端组成,因此统计一个进程树的真实内存大小,需要对进程树中的每个进程的所有虚拟机内存段进行遍历迭代,求出所有的内存和。因此通过`smaps`来获取rss的复杂度比stat文件要高。
59 | 下面为一个内存段的信息。
60 |
61 | 00400000-00401000 r-xp 00000000 08:07 131577 /home/work/opt/jdk1.7/bin/java
62 | //00400000-00401000表示该虚拟内存段的开始和结束位置。
63 | //00000000 该虚拟内存段在对应的映射文件中的偏移量,
64 | //08:07 映射文件的主设备和次设备号
65 | //131577 被映射到虚拟内存的文件的索引节点号
66 | //home/work/opt/jdk1.7/bin/java为被映射到虚拟内存的文件名称
67 | // r-xp为虚拟内存段的权限信息,其中第四个字段表示该端是私有的:p,还是共享的:s
68 |
69 | //进程使用内存空间,并不一定实际分配了内存(VSS)
70 | Size: 4 kB
71 | //实际分配的内存(不需要缺页中断就可以使用的)
72 | Rss: 4 kB
73 | //是平摊共享内存而计算后的使用内存(有些内存会和其他进程共享,例如mmap进来的)
74 | Pss: 4 kB
75 | //和其他进程共享的未改写页面
76 | Shared_Clean: 0 kB
77 | //和其他进程共享的已改写页面
78 | Shared_Dirty: 0 kB
79 | //未改写的私有页面页面
80 | Private_Clean: 4 kB
81 | //已改写的私有页面页面
82 | Private_Dirty: 0 kB
83 | //标记为已经访问的内存大小
84 | Referenced: 4 kB
85 | Anonymous: 0 kB
86 | AnonHugePages: 0 kB
87 | //存在于交换分区的数据大小(如果物理内存有限,可能存在一部分在主存一部分在交换分区)
88 | Swap: 0 kB
89 | //内核页大小
90 | KernelPageSize: 4 kB
91 | //MMU页大小,基本和Kernel页大小相同
92 | MMUPageSize: 4 kB
93 | Locked: 0 kB
94 | VmFlags: rd ex mr mw me dw sd
95 |
96 | 在NodeManager中,每个进程的内存段也由这几部分组成,参考ProcessSmapMemoryInfo的实现
97 |
98 | static class ProcessSmapMemoryInfo {
99 | private int size;
100 | private int rss;
101 | private int pss;
102 | private int sharedClean;
103 | private int sharedDirty;
104 | private int privateClean;
105 | private int privateDirty;
106 | private int referenced;
107 | private String regionName;
108 | private String permission;
109 | }
110 |
111 | 计算整个进程树的RSS,并不是简单的将所有rss相加,而是有一个计算规则。
112 |
113 | + 对于没有w权限的内存段不进行考虑,即权限为r--s和r-xs
114 | + 对于有写权限的内存段,该内存段对应的rss大小为Math.min(info.sharedDirty, info.pss) + info.privateDirty + info.privateClean;
115 |
116 | 如上所说,通过`smaps`文件计算的rss更加准确,但是复杂度要高。一般情况下没有必要开启整个开关,保持默认的关闭即可。
117 |
118 | 另外上述获取的RSS内存大小的大小都为pagesize,如下所示的超过内存被container-monitor杀死的日志:
119 |
120 | Container [pid=21831,containerID=container_1403615898540_0028_01_000044] is running beyond physical memory limits.
121 | Current usage: 1.0 GB of 1 GB physical memory used; 1.9 GB of 3 GB virtual memory used. Killing container.
122 | Dump of the process-tree for container_1403615898540_0028_01_000044 :
123 | |- PID PPID PGRPID SESSID CMD_NAME USER_MODE_TIME(MILLIS) SYSTEM_TIME(MILLIS) VMEM_USAGE(BYTES) RSSMEM_USAGE(PAGES) FULL_CMD_LINE
124 | |- 21837 21831 21831 21831 (java) 2111 116 1981988864 263056 java
125 |
126 | 打印的进程rss大小为263056,而该机器的页大小为4098,那么实际内存大小为1027m。
127 |
128 | ### Container-Monitor的实现
129 | 首先从NodeManager的逻辑结构来解释container-Monitor在其中的位置:
130 |
131 | + 每个NodeManager都一个ContainerManager,负责该节点上所有Container的管理,所有的Container的启停都需要通过ContainerManager进行调度
132 | + ContainerManager管理的Container的启停,在每个Container状态机内部,和向ContainerManager传递ContainerStartMonitoringEvent等事件。
133 | + ContainersMonitor如果接受到START_MONITORING_CONTAINER事件,则向Container-Monitor中提供该Container相关信息并进行监控;如果为STOP_MONITORING_CONTAINER,则将Container从Monitor中移除。
134 |
135 | 对 Container-Monitor有些配置参数可以进行设置:
136 |
137 | + yarn.nodemanager.contain_monitor.interval_ms,设置监控频率,默认为3000ms
138 | + yarn.nodemanager.resource.memory_MB,该项设置了整个NM可以配置调度的内存大小,如果监控发现超过物理内存的80%,会抛出warn信息。
139 | + yarn.nodemanager.vmem-pmem-ratio,默认为2.1,用户app设置单container内存大小是物理内存,通过该比例计算出每个container可以使用的虚拟内存大小。
140 | + yarn.nodemanager.pmem-check-enabled/vmem-check-enabled启停对物理内存/虚拟内存的使用量的监控
141 |
142 | 后面的工作就是启动一个线程(“Container Monitor”)调用ResourceCalculatorProcessTree接口获取每个container的进程树的内存。具体就不分析了,挺简单的!!!
143 |
144 | 这么简单,我写干嘛?好吧!!就当这回忆proc相关信息吧。
145 |
146 | 慢!!!还有一个逻辑很重要,Container是基于进程了来调度,创建子进程采用了“fork()+exec()”的方案,子进程启动的瞬间,它使用的内存量和父进程一致。一个进程使用的内存量可能瞬间翻倍,因此需要对进程进行"age"区分。参考如下代码:
147 |
148 | //其中curMemUsageOfAgedProcesses为age>0的进程占用内存大小,而currentMemUsage不区分age年龄大小
149 | boolean isProcessTreeOverLimit(String containerId,
150 | long currentMemUsage,
151 | long curMemUsageOfAgedProcesses,
152 | long vmemLimit)
153 | {
154 | boolean isOverLimit = false;
155 | if (currentMemUsage > (2 * vmemLimit)) {
156 | LOG.warn("Process tree for container: " + containerId + " running over twice " + "the configured limit. Limit=" + vmemLimit + ", current usage = " + currentMemUsage);
157 | isOverLimit = true;
158 | } else if (curMemUsageOfAgedProcesses > vmemLimit) {
159 | LOG.warn("Process tree for container: " + containerId + " has processes older than 1 " + "iteration running over the configured limit. Limit=" + vmemLimit + ", current usage = " + curMemUsageOfAgedProcesses);
160 | isOverLimit = true;
161 | }
162 | return isOverLimit;
163 | }
164 |
165 | 通过该逻辑,可以避免因为进程新启动瞬间占用的内存翻倍,导致进程被kill的风险。
--------------------------------------------------------------------------------
/hadoop/nodemanager-container-withrm.md:
--------------------------------------------------------------------------------
1 | NodeManager解析系列四:与ResourceManager的交互
2 | ====
3 |
4 | 在yarn模型中,NodeManager充当着slave的角色,与ResourceManager注册并提供了Containner服务。前面几部分核心分析NodeManager所提供的
5 | Containner服务,本节我们就NodeManager与ResourceManager交互进行分析。从逻辑上说,这块比Containner服务要简单很多。
6 |
7 | ## Containner服务什么时候开始提供服务?
8 | 在NodeManager中,核心服务模块是ContainnerManager,它可以接受来自AM的请求,并启动Containner来提供服务。那么Containner服务什么时候才可以
9 | 提供呢?答案很简单:NodeManager与ResourceManager注册成功以后就可以提供服务。
10 |
11 | 在ContainerManager模块有一个blockNewContainerRequests变量来控制是否可以提供Container服务,如下:
12 |
13 | public class ContainerManagerImpl {
14 | private AtomicBoolean blockNewContainerRequests = new AtomicBoolean(false);
15 | //
16 | startContainers(StartContainersRequest requests) {
17 | if (blockNewContainerRequests.get()) {
18 | throw new NMNotYetReadyException(
19 | "Rejecting new containers as NodeManager has not"
20 | + " yet connected with ResourceManager");
21 | }
22 | ...
23 |
24 | 在ContainerManager启动时,blockNewContainerRequests变量会被设置true,所有的startContainers操作都会被ContainerManager所拒绝,
25 | 那么该变量什么时候被设置为true呢?回答之前先看NodeManager与ResourceManager之间的通信协议
26 |
27 | ## NodeManager与ResourceManager之间的通信协议:ResourceTracker
28 | NodeManager以Client的角色与ResourceManager之间进行RPC通信,通信协议如下:
29 |
30 | public interface ResourceTracker {
31 | public RegisterNodeManagerResponse registerNodeManager(
32 | RegisterNodeManagerRequest request) throws YarnException,
33 | IOException;
34 | public NodeHeartbeatResponse nodeHeartbeat(NodeHeartbeatRequest request)
35 | throws YarnException, IOException;
36 | }
37 |
38 | NodeManager与ResourceManager之间操作由registerNodeManager和nodeHeartbeat组成,其中前者在NodeManager启动/同步时候进行注册,后者
39 | 在NodeManager运行过程中,周期性向ResourceManager进行汇报。
40 |
41 | ###先看registerNodeManager的参数和返回值:
42 |
43 | message RegisterNodeManagerRequestProto {
44 | optional NodeIdProto node_id = 1;
45 | optional int32 http_port = 3;
46 | optional ResourceProto resource = 4;
47 | optional string nm_version = 5;
48 | repeated NMContainerStatusProto container_statuses = 6;
49 | repeated ApplicationIdProto runningApplications = 7;
50 | }
51 | message RegisterNodeManagerResponseProto {
52 | optional MasterKeyProto container_token_master_key = 1;
53 | optional MasterKeyProto nm_token_master_key = 2;
54 | optional NodeActionProto nodeAction = 3;
55 | optional int64 rm_identifier = 4;
56 | optional string diagnostics_message = 5;
57 | optional string rm_version = 6;
58 | }
59 | message NodeIdProto {
60 | optional string host = 1;
61 | optional int32 port = 2;
62 | }
63 | message ResourceProto {
64 | optional int32 memory = 1;
65 | optional int32 virtual_cores = 2;
66 | }
67 |
68 | RegisterNodeManagerRequestProto为NodeManager注册提供的参数:
69 |
70 | + NodeIdProto表示当前的NodeManager的ID,它由当前NodeManager的RPC host和port组成。
71 | + ResourceProto为当前NodeManager总共可以用来分配的资源,分为内存资源和虚拟core个数:
72 | + yarn.nodemanager.resource.memory-mb配置可以使用的物理内存大小。
73 | + yarn.nodemanager.vmem-pmem-ratio可以使用的虚拟机内存比例,默认为2.1。
74 | + yarn.nodemanager.resource.cpu-vcores当前使用的虚拟机core个数。
75 | 其中ResourceProto由resource.memory-mb和resource.cpu-vcores组成
76 | + http_port为当前NodeManager的web http页面端口
77 | + NMContainerStatusProto和ApplicationIdProto为当前NodeManager中运行的Container和App状态,
78 | 对于新启动的NodeManager都为空,但是NodeManager可以多次注册,即RESYNC过程。此时这两项都不为空
79 |
80 | RegisterNodeManagerResponseProto为NodeManager注册返回信息:
81 |
82 | + MasterKeyProto/MasterKeyProto/都为相应的token信息
83 | + rm_identifier/rm_version为ResourceManager的标示和版本信息
84 | + NodeActionProto为ResourceManager通知NodeManager所进行的动作,包括RESYNC/SHUTDOWN和默认的NORMAL。
85 | + diagnostics_message为ResourceManager为该NodeManager提供的诊断信息
86 |
87 | 在NodeManager初始化过程中,即会发起注册的动作,如下:
88 |
89 | protected void registerWithRM(){
90 | List containerReports = getNMContainerStatuses();
91 | RegisterNodeManagerRequest request =
92 | RegisterNodeManagerRequest.newInstance(nodeId, httpPort, totalResource,
93 | nodeManagerVersionId, containerReports, getRunningApplications());
94 | if (containerReports != null) {
95 | LOG.info("Registering with RM using containers :" + containerReports);
96 | }
97 | RegisterNodeManagerResponse regNMResponse =
98 | resourceTracker.registerNodeManager(request);//发起注册
99 |
100 | this.rmIdentifier = regNMResponse.getRMIdentifier();
101 | // 被ResourceManager告知需要关闭当前NodeManager
102 | if (NodeAction.SHUTDOWN.equals(regNMResponse.getNodeAction())) {
103 | throw new YarnRuntimeException();
104 | }
105 |
106 | //NodeManager需要与ResourceManager之间必须版本兼容
107 | if (!minimumResourceManagerVersion.equals("NONE")){
108 | if (minimumResourceManagerVersion.equals("EqualToNM")){
109 | minimumResourceManagerVersion = nodeManagerVersionId;
110 | }
111 | String rmVersion = regNMResponse.getRMVersion();
112 | if (rmVersion == null) {
113 | throw new YarnRuntimeException("Shutting down the Node Manager. "
114 | + message);
115 | }
116 | if (VersionUtil.compareVersions(rmVersion,minimumResourceManagerVersion) < 0) {
117 | throw new YarnRuntimeException("Shutting down the Node Manager on RM "
118 | + "version error, " + message);
119 | }
120 | }
121 | MasterKey masterKey = regNMResponse.getContainerTokenMasterKey();
122 | if (masterKey != null) {
123 | this.context.getContainerTokenSecretManager().setMasterKey(masterKey);
124 | }
125 | masterKey = regNMResponse.getNMTokenMasterKey();
126 | if (masterKey != null) {
127 | this.context.getNMTokenSecretManager().setMasterKey(masterKey);
128 | }
129 | //设置blockNewContainerRequests为false
130 | ((ContainerManagerImpl) this.context.getContainerManager())
131 | .setBlockNewContainerRequests(false);
132 | }
133 |
134 | 从上面注释我们可以看到registerWithRM主要进行的操作有:
135 | + 向ResourceManager发送registerNodeManager RPC请求
136 | + 如果RPC返回的动作为ShutDown,即立即关闭NodeManager
137 | + 进行版本检查,保存ResourceManager返回的各种token。
138 | + 最后一个动作很重要,设置ContainerManager的blockNewContainerRequests为false,此时ContainerManager可以接受AM的请求。
139 |
140 | ###NodeManager与ResourceManager之间的心跳协议:nodeHeartbeat
141 |
142 | message NodeHeartbeatRequestProto {
143 | optional NodeStatusProto node_status = 1;
144 | optional MasterKeyProto last_known_container_token_master_key = 2;
145 | optional MasterKeyProto last_known_nm_token_master_key = 3;
146 | }
147 | message NodeStatusProto {
148 | optional NodeIdProto node_id = 1;
149 | optional int32 response_id = 2;
150 | repeated ContainerStatusProto containersStatuses = 3;
151 | optional NodeHealthStatusProto nodeHealthStatus = 4;
152 | repeated ApplicationIdProto keep_alive_applications = 5;
153 | }
154 | message NodeHeartbeatResponseProto {
155 | optional int32 response_id = 1;
156 | optional MasterKeyProto container_token_master_key = 2;
157 | optional MasterKeyProto nm_token_master_key = 3;
158 | optional NodeActionProto nodeAction = 4;
159 | repeated ContainerIdProto containers_to_cleanup = 5;
160 | repeated ApplicationIdProto applications_to_cleanup = 6;
161 | optional int64 nextHeartBeatInterval = 7;
162 | optional string diagnostics_message = 8;
163 | }
164 |
165 | nodeHeartbeat是一个周期性质的心跳协议,每次心跳最重要的是向ResourceManager汇报自己的NodeStatus,即NodeStatusProto,它包含当前
166 | NodeManager所有调度的Container的状态信息,Node的健康信息(后面会详细解析),以及当前处于running的App列表。
167 |
168 | NodeHeartbeatResponseProto为周期性心跳ResourceManager返回的信息,其中NodeActionProto可以实现ResourceManager关闭当前正在运行NodeManager的功能。
169 | 另外一个很重要的时候,containers_to_cleanup和applications_to_cleanup可以用来清理当前节点上相应的Container和App。
170 |
171 | 从实现上看,nodeHeartbeat应该是一个线程,周期性的调用nodeHeartbeat协议,逻辑代码如下:
172 |
173 | protected void startStatusUpdater() {
174 | statusUpdaterRunnable = new Runnable() {
175 | public void run() {
176 | int lastHeartBeatID = 0;
177 | while (!isStopped) {
178 | try {
179 | NodeHeartbeatResponse response = null;
180 | NodeStatus nodeStatus = getNodeStatus(lastHeartBeatID);
181 |
182 | NodeHeartbeatRequest request =;
183 | response = resourceTracker.nodeHeartbeat(request);
184 |
185 | //get next heartbeat interval from response
186 | nextHeartBeatInterval = response.getNextHeartBeatInterval();
187 | updateMasterKeys(response);
188 |
189 | if (response.getNodeAction() == NodeAction.SHUTDOWN) {
190 | context.setDecommissioned(true);
191 | dispatcher.getEventHandler().handle(
192 | new NodeManagerEvent(NodeManagerEventType.SHUTDOWN));
193 | break;
194 | }
195 | if (response.getNodeAction() == NodeAction.RESYNC) {
196 | dispatcher.getEventHandler().handle(
197 | new NodeManagerEvent(NodeManagerEventType.RESYNC));
198 | break;
199 | }
200 | removeCompletedContainersFromContext();
201 |
202 | lastHeartBeatID = response.getResponseId();
203 | List containersToCleanup = response.getContainersToCleanup();
204 | if (!containersToCleanup.isEmpty()) {
205 | dispatcher.getEventHandler().handle(
206 | new CMgrCompletedContainersEvent(containersToCleanup,
207 | CMgrCompletedContainersEvent.Reason.BY_RESOURCEMANAGER));
208 | }
209 | List appsToCleanup =response.getApplicationsToCleanup();
210 | trackAppsForKeepAlive(appsToCleanup);
211 | if (!appsToCleanup.isEmpty()) {
212 | dispatcher.getEventHandler().handle(
213 | new CMgrCompletedAppsEvent(appsToCleanup,
214 | CMgrCompletedAppsEvent.Reason.BY_RESOURCEMANAGER));
215 | }
216 | } catch (ConnectException e) {
217 | dispatcher.getEventHandler().handle(
218 | new NodeManagerEvent(NodeManagerEventType.SHUTDOWN));
219 | throw new YarnRuntimeException(e);
220 | } catch (Throwable e) {
221 | LOG.error("Caught exception in status-updater", e);
222 | } finally {
223 | synchronized (heartbeatMonitor) {
224 | nextHeartBeatInterval = nextHeartBeatInterval <= 0 ?
225 | YarnConfiguration.DEFAULT_RM_NM_HEARTBEAT_INTERVAL_MS :
226 | nextHeartBeatInterval;
227 | try {
228 | heartbeatMonitor.wait(nextHeartBeatInterval);
229 | }
230 | }
231 | }
232 | }
233 | }
234 | };
235 | statusUpdater =
236 | new Thread(statusUpdaterRunnable, "Node Status Updater");
237 | statusUpdater.start();
238 | }
239 |
240 | 主要的工作有:
241 | + 构造nodeStatus,其中包括当前节点所有的信息,并调用nodeHeartbeat向ResourceManager发送心跳协议
242 | + 处理ResourceManager对当前节点的SHUTDOWN和RESYNC
243 | + 处理ResourceManager返回的containers_to_cleanup和applications_to_cleanup
244 | + 每次心跳的间隔可以根据ResourceManager的当前情况,从心跳返回值中获取,从而可以控制NodeManager向ResourceManager的频率
245 |
246 | -----------
247 | end
--------------------------------------------------------------------------------
/hbase/hbase-bulk-loading.md:
--------------------------------------------------------------------------------
1 | HBase Bulk Loading的实践
2 | ===
3 |
4 | > ps:文章最后谈及BulkLoad实践过程中遇到的问题。
5 |
6 | 下面代码的功能启动一个mapreduce过程,将hdfs中的文件转化为符合指定table的分区的HFile,并调用LoadIncrementalHFiles将它导入到HBase已有的表中
7 |
8 | public static class ToHFileMapper
9 | extends Mapper