hadoop源码分析

废话不多说,直接进入org.apache.hadoop.hdfs.server.namenode包下NameNode类的main方法

 

 
  1. public static void main(String argv[]) throws Exception {

  2. if (DFSUtil.parseHelpArgument(argv, NameNode.USAGE, System.out, true)) {

  3. System.exit(0);

  4. }

  5.  
  6. try {

  7. StringUtils.startupShutdownMessage(NameNode.class, argv, LOG);

  8. NameNode namenode = createNameNode(argv, null);

  9. if (namenode != null) {

  10. namenode.join();

  11. }

  12. } catch (Throwable e) {

  13. LOG.fatal("Exception in namenode join", e);

  14. terminate(1, e);

  15. }

  16. }

 

DFSUtil的parseHelpArgument方法用于解析输入的命令行参数。接下来看try,catch语句块:

StringUtils的startupShutdownMessage方法字面很容易看出是打印启动关闭信息;NameNode

的主要工作由createNameNode方法完成,我们进入createNameNode可以发现主要是switch语句块,我们重点看下关于格式化format

 

 
  1. case FORMAT: {

  2. boolean aborted = format(conf, startOpt.getForceFormat(),

  3. startOpt.getInteractiveFormat());

  4. terminate(aborted ? 1 : 0);

  5. return null; // avoid javac warning

  6. }

 

然后进入format方法,每一步的注释写在代码后面,自己看

 
  1. private static boolean format(Configuration conf, boolean force,

  2. boolean isInteractive) throws IOException {

  3. String nsId = DFSUtil.getNamenodeNameServiceId(conf); // 获取nameserviceid,在hadoop ha中配置

  4. String namenodeId = HAUtil.getNameNodeId(conf, nsId); // 获取namenodeid,

  5. initializeGenericKeys(conf, nsId, namenodeId);

  6. checkAllowFormat(conf); // 判断是否允许格式化,也就是你不能把正在运行的hdfs给格了

  7.  
  8. if (UserGroupInformation.isSecurityEnabled()) { // 看到UserGroupInformation,我们知道这是hdfs系统权限相关的,

  9. // 判断是否使用Kerberos验证

  10. InetSocketAddress socAddr = getAddress(conf);

  11. SecurityUtil.login(conf, DFS_NAMENODE_KEYTAB_FILE_KEY,

  12. DFS_NAMENODE_USER_NAME_KEY, socAddr.getHostName());

  13. }

  14.  
  15. /*

  16. 获取hdfs-site.xml中dfs.namenode.name.dir设置的路径,如/home/hadoop/dfs/name

  17. 用于存储文件系统命名空间镜像

  18. */

  19. Collection<URI> nameDirsToFormat = FSNamesystem.getNamespaceDirs(conf);

  20. /*

  21. 获取hdfs-site.xml中dfs.namenode.shared.edits.dir设置的路径,如果使用的hadoop的ha配置,

  22. 那么值可以为qjournal://node1:8485;node2:8485;node3:8485/clusterid,其中clusterid是dfs.nameservices配置的值

  23. */

  24. List<URI> sharedDirs = FSNamesystem.getSharedEditsDirs(conf);

  25. List<URI> dirsToPrompt = new ArrayList<URI>();

  26. dirsToPrompt.addAll(nameDirsToFormat);

  27. dirsToPrompt.addAll(sharedDirs);

  28. List<URI> editDirsToFormat =

  29. FSNamesystem.getNamespaceEditsDirs(conf);

  30.  
  31. // if clusterID is not provided - see if you can find the current one

  32. String clusterId = StartupOption.FORMAT.getClusterId();

  33. if(clusterId == null || clusterId.equals("")) {

  34. //Generate a new cluster id

  35. clusterId = NNStorage.newClusterID();

  36. }

  37. System.out.println("Formatting using clusterid: " + clusterId);

  38. // 关于文件系统的创建,日后会详细分析

  39. FSImage fsImage = new FSImage(conf, nameDirsToFormat, editDirsToFormat);

  40. try {

  41. FSNamesystem fsn = new FSNamesystem(conf, fsImage);

  42. fsImage.getEditLog().initJournalsForWrite();

  43.  
  44. if (!fsImage.confirmFormat(force, isInteractive)) {

  45. return true; // aborted

  46. }

  47.  
  48. fsImage.format(fsn, clusterId);

  49. } catch (IOException ioe) {

  50. LOG.warn("Encountered exception during format: ", ioe);

  51. fsImage.close();

  52. throw ioe;

  53. }

  54. return false;

  55. }

回到NameNode的main方法,namenode.join最终启动的是RPC.Server serviceRpcServer,RPC.ServerclientRpcServer两大线程。

serviceRpcServer监听来自DataNodes的请求,clientRpcServer监听来自客户端的请求。

 

上一篇讲到了namenode的格式化,格式化方法中有

 

 
  1. FSImage fsImage = new FSImage(conf, nameDirsToFormat, editDirsToFormat);

  2. try {

  3. FSNamesystem fsn = new FSNamesystem(conf, fsImage);

 

 

今天主要讲讲FSImage ,FSNamesystem 分别在(1),(2)中

(1)先来看FSImage,FSImage处理checkpointing(检查点),并记录到文件命名空间编辑日志中。

fsimage在磁盘上对应上一篇文章提到的/home/hadoop/dfs/name路径。目录下有current,image,in_use.lock;在current目录下有edits日志,fsimage内存镜像,fstime镜像时间,VERSION版本信息。

FSImage常用操作有loadFSImage(加载文件系统镜像),saveFSImage(保存文件系统镜像)

在loadFSImage中,最终会调用FSImageFormat类中的load(File curFile)方法,代码如下:

 

 
  1. public void load(File curFile) throws IOException {

  2. checkNotLoaded(); // 保证是第一次加载时执行下面的语句

  3. assert curFile != null : "curFile is null"; // 断言

  4.  
  5. StartupProgress prog = NameNode.getStartupProgress(); // 获取启动进度

  6. Step step = new Step(StepType.INODES);

  7. prog.beginStep(Phase.LOADING_FSIMAGE, step);

  8. long startTime = now(); // 开始

  9.  
  10. //

  11. // Load in bits

  12. //

  13. MessageDigest digester = MD5Hash.getDigester();

  14. DigestInputStream fin = new DigestInputStream(

  15. new FileInputStream(curFile), digester); // 获取输入流

  16.  
  17. DataInputStream in = new DataInputStream(fin); // 包装输入流

  18. try {

  19. // read image version: first appeared in version -1

  20. int imgVersion = in.readInt(); // 读取镜像版本号

  21. if (getLayoutVersion() != imgVersion) { // 判断版本是否一致,不一致抛异常

  22. throw new InconsistentFSStateException(curFile,

  23. "imgVersion " + imgVersion +

  24. " expected to be " + getLayoutVersion());

  25. }

  26. boolean supportSnapshot = NameNodeLayoutVersion.supports( // 判断是否支持快照

  27. LayoutVersion.Feature.SNAPSHOT, imgVersion);

  28. if (NameNodeLayoutVersion.supports(

  29. LayoutVersion.Feature.ADD_LAYOUT_FLAGS, imgVersion)) {

  30. LayoutFlags.read(in);

  31. }

  32.  
  33. // read namespaceID: first appeared in version -2

  34. in.readInt(); // 读取命名空间编号

  35.  
  36. long numFiles = in.readLong(); // 文件数量

  37. <span style="white-space:pre"> </span>......

在saveFSImage中,最终调用FSImageFormatProtobuf中save(File file, FSImageCompression compression)方法,代码如下

 

 
  1. void save(File file, FSImageCompression compression) throws IOException {

  2. FileOutputStream fout = new FileOutputStream(file); // 创建输出流

  3. fileChannel = fout.getChannel(); // 获取网络套接字的通道,用过java nio的朋友应该清楚

  4. try {

  5. saveInternal(fout, compression, file.getAbsolutePath().toString()); // 在该方法中,underlyingOutputStream.write(FSImageUtil.MAGIC_HEADER)进行持久化操作

  6. } finally {

  7. fout.close();

  8. }

  9. }

(2)对于hadoop集群,master节点存储3种类型元数据:文件和数据块的命名空间,文件和数据块的对应关系,每个数据块副本的存放地点。所有的元数据都保存在内存中,前两种类型也会以记录变更日志的方式记录在系统日志文件中。

文件系统的存储和管理都交给了FSNameSystem类,我们就看看他的注释:

 

 
  1. /***************************************************

  2. * FSNamesystem does the actual bookkeeping work for the // 此类为datanode做实际的簿记工作

  3. * DataNode.

  4. *

  5. * It tracks several important tables.

  6. *

  7. * 1) valid fsname --> blocklist (kept on disk, logged) // 文件系统命名空间到数据块列表的映射,保存在磁盘上并记录日志

  8. * 2) Set of all valid blocks (inverted #1) // 合法数据块集合,上面的逆关系

  9. * 3) block --> machinelist (kept in memory, rebuilt dynamically from reports) // 数据块到datanode的映射,保存在内存中,由datanode上报动态重建

  10. * 4) machine --> blocklist (inverted #2) // datanode上保存的数据块列表,上面的逆关系

  11. * 5) LRU cache of updated-heartbeat machines 近期最少使用缓存队列,保存datanode的心跳信息

  12. ***************************************************/


 

 

 

FSNamesystem 有一个FSDirectory成员变量,它保存文件名到数据块列表的映射,类中有添加文命名空间,添加文件,添加数据块,创建目录等操作。

下面是数据块相关的方法

 

 
  1. @Override // FSNamesystemMBean

  2. @Metric

  3. public long getPendingReplicationBlocks() { // 返回正在复制的数据块

  4. return blockManager.getPendingReplicationBlocksCount();

  5. }

  6.  
  7. @Override // FSNamesystemMBean

  8. @Metric

  9. public long getUnderReplicatedBlocks() { // 返回需要复制的数据块

  10. return blockManager.getUnderReplicatedBlocksCount();

  11. }

  12.  
  13. /** Returns number of blocks with corrupt replicas */

  14. @Metric({"CorruptBlocks", "Number of blocks with corrupt replicas"})

  15. public long getCorruptReplicaBlocks() { // 返回损坏的数据块

  16. return blockManager.getCorruptReplicaBlocksCount();

  17. }

  18.  
  19. @Override // FSNamesystemMBean

  20. @Metric

  21. public long getScheduledReplicationBlocks() { // 返回当前正在处理的数据块复制数目

  22. return blockManager.getScheduledReplicationBlocksCount();

  23. }

  24.  
  25. @Override

  26. @Metric

  27. public long getPendingDeletionBlocks() { // 返回正在删除的数据块数目

  28. return blockManager.getPendingDeletionBlocksCount();

  29. }

  30.  
  31. @Metric

  32. public long getExcessBlocks() { // 返回超过配额的数据块数目

  33. return blockManager.getExcessBlocksCount();

  34. }

  35.  
  36. // HA-only metric

  37. @Metric

  38. public long getPostponedMisreplicatedBlocks() { // 返回延期或错过复制的数据块数目,仅在ha的情况下

  39. return blockManager.getPostponedMisreplicatedBlocksCount();

  40. }

 

本人博客针对的是hadoop2版本,比1版本略为复杂(采用了很多当下流行的设计模式,加入了新的序列化框架,ha配置,联邦特性,yarn框架,以及采用maven的工程划分结构等)。网上的源码分析大多针对的是1版本,由于是针对源码写出自己的理解,难免有错误或不当的地方,欢迎指正

前面两篇主要讲了namenode,现在来说说datanode。好了,直接打开idea,进入DataNode

首先我来翻译一下注释(有些是自己添加的):

 

 
  1. /**********************************************************

  2. * DataNode is a class (and program) that stores a set of

  3. * blocks for a DFS deployment. A single deployment can

  4. * have one or many DataNodes. Each DataNode communicates

  5. * regularly with a single NameNode. It also communicates

  6. * with client code and other DataNodes from time to time.

  7. *datanode是DFS调度存储一系列数据块的一个类或者说是程序。DFS调度可以有1个或多个数据节点。

  8. * 每个数据节点定期和唯一的namenode进行通信。有时它也和客户端或其他的数据节点通信。

  9. *

  10. * DataNodes store a series of named blocks. The DataNode

  11. * allows client code to read these blocks, or to write new

  12. * block data. The DataNode may also, in response to instructions

  13. * from its NameNode, delete blocks or copy blocks to/from other

  14. * DataNodes.

  15. *数据节点存储一系列命名数据块。它允许客户端读写或者创建新的数据块。而且,

  16. * 它还会执行来自namenode的删除数据块,拷贝或复制其他节点上的数据块的指令,

  17. * 当然了,这些指令是通过心跳的的响应时传达的

  18. *

  19. * The DataNode maintains just one critical table:

  20. * block-> stream of bytes (of BLOCK_SIZE or less)

  21. *数据节点保存了极其重要的一张表:数据块到字节流的映射

  22. *

  23. * This info is stored on a local disk. The DataNode

  24. * reports the table's contents to the NameNode upon startup

  25. * and every so often afterwards.

  26. *这个信息保存在本地磁盘上,数据节点在启动,以后定期把这些内容上报给namenode

  27. * 这就是前面文章中说的第三种元数据是由数据节点上报动态建立的

  28. *

  29. * DataNodes spend their lives in an endless loop of asking

  30. * the NameNode for something to do. A NameNode cannot connect

  31. * to a DataNode directly; a NameNode simply returns values from

  32. * functions invoked by a DataNode.

  33. *datanode是一个死循环并一直询问namenode有什么事吩咐。namenode不可以直接连接datanode,

  34. *它只能通过datanode的请求函数中返回值

  35. *

  36. * DataNodes maintain an open server socket so that client code

  37. * or other DataNodes can read/write data. The host/port for

  38. * this server is reported to the NameNode, which then sends that

  39. * information to clients or other DataNodes that might be interested.

  40. *数据节点保持一个打开的套接字供客户端和其他数据节点读写数据。当前的主机名,

  41. * 端口要上报给namenode,然后namenode再发给其他感兴趣的客户端或数据节点

  42. *

  43. **********************************************************/

接着,同namenode分析思路,直接进入main方法

 

 
  1. public static void main(String args[]) {

  2. if (DFSUtil.parseHelpArgument(args, DataNode.USAGE, System.out, true)) { // 在namenode中见过吧,解析命令行参数,其实在hadoop1中是没有这个if判断的

  3. System.exit(0);

  4. }

  5.  
  6. secureMain(args, null);

  7. }

  8. public static void secureMain(String args[], SecureResources resources) {

  9. int errorCode = 0;

  10. try {

  11. StringUtils.startupShutdownMessage(DataNode.class, args, LOG); // 打印启动关闭信息

  12. DataNode datanode = createDataNode(args, null, resources);

  13. // 看到没,跟namenode一个编码思路,

  14. // 创建datanode时会调用instantiateDataNode方法,进行初始化配置信息,权限设置。

  15. // 在hadoop1里面有行代码是

  16. // String[] dataDirs = conf.getStrings(DATA_DIR_KEY);而在hadoop2里面是

  17. // Collection<StorageLocation> dataLocations = getStorageLocations(conf);

  18. // hadoop2对其做了下封装,显得更规范。java你懂得,

  19. // 不抽出点接口、进行点包装显示不出自己的逼格。其实就是获取数据存储目录。

  20. if (datanode != null) {

  21. datanode.join();

  22. // 还记得namenode中是启动两大rpcserver吗,下面详细解析join方法

  23. } else {

  24. errorCode = 1;

  25. }

  26. } catch (Throwable e) {

  27. LOG.fatal("Exception in secureMain", e);

  28. terminate(1, e);

  29. } finally {

  30. // We need to terminate the process here because either shutdown was called

  31. // or some disk related conditions like volumes tolerated or volumes required

  32. // condition was not met. Also, In secure mode, control will go to Jsvc

  33. // and Datanode process hangs if it does not exit.

  34. LOG.warn("Exiting Datanode");

  35. terminate(errorCode);

  36. }

  37. }

在createDataNode中,你还会看到runDatanodeDaemon方法

 

 
  1. public void runDatanodeDaemon() throws IOException {

  2. blockPoolManager.startAll();

  3.  
  4. // start dataXceiveServer

  5. // DataXceiverServer类是DataNode的辅助类,

  6. // 它最主要是用来实现客户端或其他数据节点与当前节点通信,

  7. // 并负责接收/发送数据块。这个类的创建是为了监听来自客户端或其他数据节点的请求。

  8. // 它的实现通信的方法不是用hadoop IPC,而是用jdk本身就有的ServerSocket。

  9. // DataXceiverServer是一个简单的tcp socket server, 监听特定的端口,

  10. // 持有一个单独的线程,不断调用accept,如果有请求过来,

  11. // 就创建一个DataXceiver线程并启动,进行处理

  12. dataXceiverServer.start();

  13. if (localDataXceiverServer != null) {

  14. localDataXceiverServer.start();

  15. }

  16. ipcServer.start();

  17. startPlugins(conf);

  18. }

在datanode.join中有吊用下面的join方法

 

 
  1. void join() {

  2. while (shouldRun) {

  3. try {

  4. // BlockPoolManager类提供管理BlockPool的相关API,在hadoop2中允许存在多个namespace

  5. // 其中,BPOfferService为每个Namespace下每个BlockPool一个实例,

  6. // 提供BlockPool对它所对应的Namespace的操作的相关API,

  7. // BPOfferService为指定Namespace中每个namenode一个实例,

  8. // 自已持有线程,定时向它所对应的namenode发heartbeat, blockreport,

  9. // 并执行namenode通过heartbeat/blockreport response传回来的command

  10. blockPoolManager.joinAll();

  11. if (blockPoolManager.getAllNamenodeThreads() != null

  12. && blockPoolManager.getAllNamenodeThreads().length == 0) {

  13. shouldRun = false;

  14. }

  15. // Terminate if shutdown is complete or 2 seconds after all BPs

  16. // are shutdown.

  17. synchronized(this) {

  18. wait(2000);

  19. }

  20. } catch (InterruptedException ex) {

  21. LOG.warn("Received exception in Datanode#join: " + ex);

  22. }

  23. }

  24. }

 

在datanode类中还有DataBlockScanner线程,类型为volatile 。

(Java语言中的 volatile 变量可以被看作是一种 “程度较轻的 synchronized”,目前还挺有争议的。)

DataBlockScanner的主要功能是扫描并校验磁盘上的data block, 把发现的坏块报告给namenode. 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值