概述

Namenode 在 hadoop 的 hdfs 体系中的作用是毋庸置疑的。本文主要分析 namenode 的启动过程,同时梳理一下Namenode的逻辑关系

Namenode 实体在代码实现中主要对应于三个类,即 NameNode 类、NameNodeRpcServer 类以及FSNamesystem 类

  • NameNode类:负责管理 Namenode 配置、RPC 接口以及 HTTP 接口等

  • NameNodeRpcServer 类:接收和处理所有的RPC请求

  • FSNamesystem 类:实现Namenode的所有逻辑

启动

Namenode 启动的入口这个很容易找,可以从启动命令的 shell 脚本开始找,比如从${HADOOP_HOME}/sbin/start-dfs.sh 脚本开始找起。最终的执行命令如下

  1. /Library/java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/bin/java
  2. -Dproc_namenode
  3. -Djava.net.preferIPv4Stack=true
  4. -Djava.security.krb5.realm=
  5. -Djava.security.krb5.kdc=
  6. -Djava.security.krb5.conf=
  7. -Dhdfs.audit.logger=INFO,NullAppender
  8. -Dhadoop.security.logger=INFO,RFAS
  9. -Dyarn.log.dir=/tools/hadoop-3.2.1/logs
  10. -Dyarn.log.file=hadoop-sysadmin-namenode-bogon.log
  11. -Dyarn.home.dir=/tools/hadoop-3.2.1
  12. -Dyarn.root.logger=INFO,console
  13. -Djava.library.path=/tools/hadoop-3.2.1/lib/native
  14. -Dhadoop.log.dir=/tools/hadoop-3.2.1/logs
  15. -Dhadoop.log.file=hadoop-sysadmin-namenode-bogon.log
  16. -Dhadoop.home.dir=/tools/hadoop-3.2.1
  17. -Dhadoop.id.str=sysadmin
  18. -Dhadoop.root.logger=INFO,RFA
  19. -Dhadoop.policy.file=hadoop-policy.xml
  20. org.apache.hadoop.hdfs.server.namenode.NameNode

最重要的就是 Namenode 的启动类,我们可以看到直接调用的是这个类,然后启动就可以了

  1. org.apache.hadoop.hdfs.server.namenode.NameNode

对应的 main 方法入口

  1. public static void main(String[] args) throws Exception {
  2. //参数校验
  3. if (DFSUtil.parseHelpArgument(args, Cli.USAGE, System.out, true)) {
  4. System.exit(0);
  5. }
  6. try {
  7. StringUtils.startupShutdownMessage(Namenode.class, args, Cli);
  8. //调用createNameNode创建NameNode对象
  9. NameNode namenode = createNameNode(args, null);
  10. if(namenode != null){
  11. //等待NameNode RPC服务结束
  12. namenode.join();
  13. }
  14. } catch (Throwable e) {
  15. Cli.printError("Failed to run " + Cli.COMMAND, e);
  16. System.exit(-2);
  17. }
  18. }
  19. }

重点在于

  1. NameNode namenode = createNameNode(args, null);

创建NameNode

NameNode 是通过 createNameNode 方法进行创建的

  1. public static NameNode createNameNode(String args[], Configuration conf)
  2. throws IOException {
  3. LOG.info("createNameNode " + Arrays.asList(args));
  4. // 构建配置文件
  5. if (conf == null)
  6. conf = new HdfsConfiguration();
  7. // Parse out some generic args into Configuration.
  8. GenericOptionsParser hParser = new GenericOptionsParser(conf, args);
  9. args = hParser.getRemainingArgs();
  10. // Parse the rest, NN specific args.
  11. // 解析命令行的参数
  12. StartupOption startOpt = parseArguments(args);
  13. if (startOpt == null) {
  14. printUsage(System.err);
  15. return null;
  16. }
  17. setStartupOption(conf, startOpt);
  18. boolean aborted = false;
  19. // 根据启动选项调用对应的方法执行操作
  20. switch (startOpt) {
  21. // 格式化当前Namenode,调用format()方法执行格式化操作
  22. case FORMAT:
  23. aborted = format(conf, startOpt.getForceFormat(),
  24. startOpt.getInteractiveFormat());
  25. terminate(aborted ? 1 : 0);
  26. return null; // avoid javac warning
  27. case GENCLUSTERID:
  28. System.err.println("Generating new cluster id:");
  29. System.out.println(NNStorage.newClusterID());
  30. terminate(0);
  31. return null;
  32. // 回滚上一次升级, 调用doRollback()方法执行回滚操作
  33. case ROLLBACK:
  34. aborted = doRollback(conf, true);
  35. terminate(aborted ? 1 : 0);
  36. return null; // avoid warning
  37. // 拷贝Active Namenode的最新命名空间数据到StandbyNamenode,
  38. // 调用BootstrapStandby.run()方法执行操作
  39. case BOOTSTRAPSTANDBY:
  40. String[] toolArgs = Arrays.copyOfRange(args, 1, args.length);
  41. int rc = BootstrapStandby.run(toolArgs, conf);
  42. terminate(rc);
  43. return null; // avoid warning
  44. // 初始化editlog的共享存储空间,并从Active
  45. // Namenode中拷贝足够的editlog数据,使得Standby节点能够顺利启动
  46. // 这里调用了静态方法initializeSharedEdits()执行操作
  47. case INITIALIZESHAREDEDITS:
  48. aborted = initializeSharedEdits(conf,
  49. startOpt.getForceFormat(),
  50. startOpt.getInteractiveFormat());
  51. terminate(aborted ? 1 : 0);
  52. return null; // avoid warning
  53. // 参看下面的CHECKPOINT代码 .
  54. case BACKUP:
  55. // 启动checkpoint节点,也是直接构造BackupNode对象并返回
  56. case CHECKPOINT:
  57. NamenodeRole role = startOpt.toNodeRole();
  58. DefaultMetricsSystem.initialize(role.toString().replace(" ", ""));
  59. return new BackupNode(conf, role);
  60. // 恢复损坏的元数据以及文件系统,这里调用了doRecovery()方法执行操作
  61. case RECOVER:
  62. NameNode.doRecovery(startOpt, conf);
  63. return null;
  64. // 确认配置文件夹存在,并且打印fsimage文件和文件系统的元数据版本
  65. case METADATAVERSION:
  66. printMetadataVersion(conf);
  67. terminate(0);
  68. return null; // avoid javac warning
  69. // 升级Namenode,升级完成后关闭Namenode
  70. case UPGRADEONLY:
  71. DefaultMetricsSystem.initialize("NameNode");
  72. new NameNode(conf);
  73. terminate(0);
  74. return null;
  75. // 在默认情况下直接构造NameNode对象并返回
  76. default:
  77. // 初始化 度量服务
  78. DefaultMetricsSystem.initialize("NameNode");
  79. return new NameNode(conf);
  80. }
  81. }

这里最重要的是根据输入的参数来执行不同的任务,任务类型如下:

FORMAT 格式化当前Namenode,调用format()方法执行格式化操作
GENCLUSTERID 生成集群id
ROLLBACK 回滚上一次升级, 调用doRollback()方法执行回滚操作
BOOTSTRAPSTANDBY 拷贝Active Namenode的最新命名空间数据到StandbyNamenode,调用BootstrapStandby.run()方法执行操作
INITIALIZESHAREDEDITS 初始化editlog的共享存储空间,并从ActiveNamenode中拷贝足够的editlog数据,使得Standby节点能够顺利启动。这里调用了静态方法initializeSharedEdits()执行操作
BACKUP
CHECKPOINT 启动checkpoint节点,也是直接构造BackupNode对象并返回
RECOVER 恢复损坏的元数据以及文件系统,这里调用了doRecovery()方法执行操作
METADATAVERSION 确认配置文件夹存在,并且打印fsimage文件和文件系统的元数据版本
UPGRADEONLY 升级Namenode,升级完成后关闭Namenode

构造方法

NameNode 的构建是通过其构造方法 NameNode(Configuration conf) 作为入口进行创建的

  1. protected NameNode(Configuration conf, NamenodeRole role) throws IOException {
  2. super(conf);
  3. this.tracer = new Tracer.Builder("NameNode").
  4. conf(TraceUtils.wrapHadoopConf(NAMENODE_HTRACE_PREFIX, conf)).
  5. build();
  6. this.tracerConfigurationManager =
  7. new TracerConfigurationManager(NAMENODE_HTRACE_PREFIX, conf);
  8. this.role = role;
  9. String nsId = getNameServiceId(conf);
  10. // 根据配置确认是否开启了HA
  11. String namenodeId = HAUtil.getNameNodeId(conf, nsId);
  12. // fs.defaultFS localhost:8020
  13. clientNamenodeAddress = NameNodeUtils.getClientNamenodeAddress(
  14. conf, nsId);
  15. if (clientNamenodeAddress != null) {
  16. LOG.info("Clients should use {} to access"
  17. + " this namenode/service.", clientNamenodeAddress);
  18. }
  19. // 是否启用ha
  20. this.haEnabled = HAUtil.isHAEnabled(conf, nsId);
  21. // 非HA ==> ACTIVE_STATE
  22. state = createHAState(getStartupOption(conf));
  23. // This is used only by tests at the moment.
  24. this.allowStaleStandbyReads = HAUtil.shouldAllowStandbyReads(conf);
  25. this.haContext = createHAContext();
  26. try {
  27. initializeGenericKeys(conf, nsId, namenodeId);
  28. // 执行初始化操作
  29. initialize(getConf());
  30. try {
  31. haContext.writeLock();
  32. // 初始化完成后,Namenode进入Standby状态
  33. // 在这里会开启StandbyCheckpointer里面的
  34. // checkpointer 线程,定时合并&处理images文件
  35. state.prepareToEnterState(haContext);
  36. state.enterState(haContext);
  37. } finally {
  38. haContext.writeUnlock();
  39. }
  40. } catch (IOException e) {
  41. // 出现异常,直接停止Namenode服务
  42. this.stopAtException(e);
  43. throw e;
  44. } catch (HadoopIllegalArgumentException e) {
  45. // 直接停止Namenode服务
  46. this.stopAtException(e);
  47. throw e;
  48. }
  49. this.started.set(true);
  50. }

里面最重要的就是调用了初始化的方法 initialize(getConf())

初始化 initialize

initialize(getConf()) 是 namenode 的初始化方法。Namenode的一些重要服务的启动都是通过他来完成的

  1. /**
  2. * Initialize name-node.
  3. *
  4. * @param conf the configuration
  5. */
  6. protected void initialize(Configuration conf) throws IOException {
  7. if (conf.get(HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS) == null) {
  8. String intervals = conf.get(DFS_METRICS_PERCENTILES_INTERVALS_KEY);
  9. if (intervals != null) {
  10. conf.set(HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS, intervals);
  11. }
  12. }
  13. UserGroupInformation.setConfiguration(conf);
  14. loginAsNameNodeUser(conf);
  15. NameNode.initMetrics(conf, this.getRole());
  16. StartupProgressMetrics.register(startupProgress);
  17. //构造JvmPauseMonitor对象,并启动
  18. pauseMonitor = new JvmPauseMonitor();
  19. pauseMonitor.init(conf);
  20. pauseMonitor.start();
  21. metrics.getJvmMetrics().setPauseMonitor(pauseMonitor);
  22. //启动HTTP服务
  23. if (NamenodeRole.NAMENODE == role) {
  24. startHttpServer(conf);
  25. }
  26. // 初始化FSNamesystem
  27. // NameNode将对文件系统的管理都委托给了FSNamesystem对象,
  28. // NameNode会调用FSNamesystem.loadFromDisk()创建FSNamesystem对象。
  29. //
  30. // FSNamesystem.loadFromDisk()首先调用构造方法构造FSNamesystem对象,
  31. // 然后将fsimage以及editlog文件加载到命名空间中。
  32. loadNamesystem(conf);
  33. startAliasMapServerIfNecessary(conf);
  34. //创建RPC服务
  35. rpcServer = createRpcServer(conf);
  36. initReconfigurableBackoffKey();
  37. if (clientNamenodeAddress == null) {
  38. // This is expected for MiniDFSCluster. Set it now using
  39. // the RPC server's bind address.
  40. clientNamenodeAddress =
  41. NetUtils.getHostPortString(getNameNodeAddress());
  42. LOG.info("Clients are to use " + clientNamenodeAddress + " to access"
  43. + " this namenode/service.");
  44. }
  45. if (NamenodeRole.NAMENODE == role) {
  46. httpServer.setNameNodeAddress(getNameNodeAddress());
  47. httpServer.setFSImage(getFSImage());
  48. }
  49. //启动httpServer以及 rpcServer
  50. startCommonServices(conf);
  51. //启动计时器定期将NameNode度量写入日志文件。此行为可由配置禁用。
  52. startMetricsLogger(conf);
  53. }

构建http服务:startHttpServer

其实就是构建一个 NameNodeHttpServer 对象,然后启动就可以了

  1. private void startHttpServer(final Configuration conf) throws IOException {
  2. httpServer = new NameNodeHttpServer(conf, this, getHttpServerBindAddress(conf));
  3. httpServer.start();
  4. httpServer.setStartupProgress(startupProgress);
  5. }
  1. /**
  2. * @see DFSUtil#getHttpPolicy(org.apache.hadoop.conf.Configuration)
  3. * for information related to the different configuration options and
  4. * Http Policy is decided.
  5. */
  6. void start() throws IOException {
  7. HttpConfig.Policy policy = DFSUtil.getHttpPolicy(conf);
  8. // 获取服务器host
  9. final String infoHost = bindAddress.getHostName();
  10. // 获取绑定地址
  11. final InetSocketAddress httpAddr = bindAddress;
  12. // 获取服务地址 0.0.0.0:9871
  13. final String httpsAddrString = conf.getTrimmed(
  14. DFSConfigKeys.DFS_NAMENODE_HTTPS_ADDRESS_KEY,
  15. DFSConfigKeys.DFS_NAMENODE_HTTPS_ADDRESS_DEFAULT);
  16. // 构建网络InetSocketAddress服务:
  17. InetSocketAddress httpsAddr = NetUtils.createSocketAddr(httpsAddrString);
  18. if (httpsAddr != null) {
  19. // 绑定地址 如果dfs.namenode.https-bind-host已绑定了地址的话,将会覆盖掉之前创建的
  20. final String bindHost = conf.getTrimmed(DFSConfigKeys.DFS_NAMENODE_HTTPS_BIND_HOST_KEY);
  21. if (bindHost != null && !bindHost.isEmpty()) {
  22. httpsAddr = new InetSocketAddress(bindHost, httpsAddr.getPort());
  23. }
  24. }
  25. HttpServer2.Builder builder = DFSUtil.httpServerTemplateForNNAndJN(conf,
  26. httpAddr, httpsAddr, "hdfs",
  27. DFSConfigKeys.DFS_NAMENODE_KERBEROS_INTERNAL_SPNEGO_PRINCIPAL_KEY,
  28. DFSConfigKeys.DFS_NAMENODE_KEYTAB_FILE_KEY);
  29. final boolean xFrameEnabled = conf.getBoolean(
  30. DFSConfigKeys.DFS_XFRAME_OPTION_ENABLED,
  31. DFSConfigKeys.DFS_XFRAME_OPTION_ENABLED_DEFAULT);
  32. final String xFrameOptionValue = conf.getTrimmed(
  33. DFSConfigKeys.DFS_XFRAME_OPTION_VALUE,
  34. DFSConfigKeys.DFS_XFRAME_OPTION_VALUE_DEFAULT);
  35. builder.configureXFrame(xFrameEnabled).setXFrameOption(xFrameOptionValue);
  36. // 构建http服务
  37. httpServer = builder.build();
  38. if (policy.isHttpsEnabled()) {
  39. // assume same ssl port for all datanodes
  40. InetSocketAddress datanodeSslPort = NetUtils.createSocketAddr(conf.getTrimmed(
  41. DFSConfigKeys.DFS_DATANODE_HTTPS_ADDRESS_KEY, infoHost + ":"
  42. + DFSConfigKeys.DFS_DATANODE_HTTPS_DEFAULT_PORT));
  43. httpServer.setAttribute(DFSConfigKeys.DFS_DATANODE_HTTPS_PORT_KEY,
  44. datanodeSslPort.getPort());
  45. }
  46. initWebHdfs(conf, bindAddress.getHostName(), httpServer,
  47. NamenodeWebHdfsMethods.class.getPackage().getName());
  48. httpServer.setAttribute(NAMENODE_ATTRIBUTE_KEY, nn);
  49. httpServer.setAttribute(JspHelper.CURRENT_CONF, conf);
  50. setupServlets(httpServer, conf);
  51. httpServer.start();
  52. int connIdx = 0;
  53. if (policy.isHttpEnabled()) {
  54. httpAddress = httpServer.getConnectorAddress(connIdx++);
  55. conf.set(DFSConfigKeys.DFS_NAMENODE_HTTP_ADDRESS_KEY,
  56. NetUtils.getHostPortString(httpAddress));
  57. }
  58. if (policy.isHttpsEnabled()) {
  59. httpsAddress = httpServer.getConnectorAddress(connIdx);
  60. conf.set(DFSConfigKeys.DFS_NAMENODE_HTTPS_ADDRESS_KEY,
  61. NetUtils.getHostPortString(httpsAddress));
  62. }
  63. }

构建FSNamesystem

从磁盘中加载 FSNamesystem

  1. protected void loadNamesystem(Configuration conf) throws IOException {
  2. // 从磁盘中加载 FSNamesystem
  3. this.namesystem = FSNamesystem.loadFromDisk(conf);
  4. }

在这里,是调用 FSNamesystem.loadFromDisk(conf) 加载文件系统信息

  1. /**
  2. * 从配置中加载image和edits目录,实例化FSNamesystem
  3. * Instantiates an FSNamesystem loaded from the image and edits
  4. * directories specified in the passed Configuration.
  5. *
  6. * @param conf the Configuration which specifies the storage directories
  7. * from which to load
  8. * @return an FSNamesystem which contains the loaded namespace
  9. * @throws IOException if loading fails
  10. */
  11. static FSNamesystem loadFromDisk(Configuration conf) throws IOException {
  12. checkConfiguration(conf);
  13. // 构建FSImage
  14. FSImage fsImage = new FSImage(conf,
  15. FSNamesystem.getNamespaceDirs(conf), //dfs.namenode.name.dir
  16. FSNamesystem.getNamespaceEditsDirs(conf));
  17. // FSNamesystem的构造方法比较长,但是逻辑很简单,主要是从配置文件中获取参数,
  18. // 然后构造FSDirectory、BlockManager、SnapshotManager、CacheManagerSafeModeInfo等对象
  19. // 需要注意的是,FSNamesystem的构造方法并不从磁盘上加载fsimage以及editlog文件,
  20. // 这些操作是在创建FSNamesystem对象成功后,在loadFromDisk()中执行的
  21. // 如果FSNamesystem初始化失败,则会调用FSNamesystem.close()方法
  22. // 关闭FSNamesystem启动的所有服务
  23. FSNamesystem namesystem = new FSNamesystem(conf, fsImage, false);
  24. StartupOption startOpt = NameNode.getStartupOption(conf);
  25. if (startOpt == StartupOption.RECOVER) {
  26. namesystem.setSafeMode(SafeModeAction.SAFEMODE_ENTER);
  27. }
  28. long loadStart = monotonicNow();
  29. try {
  30. // 加载fsimage以及editlog文件
  31. namesystem.loadFSImage(startOpt);
  32. } catch (IOException ioe) {
  33. LOG.warn("Encountered exception loading fsimage", ioe);
  34. fsImage.close();
  35. throw ioe;
  36. }
  37. long timeTakenToLoadFSImage = monotonicNow() - loadStart;
  38. LOG.info("Finished loading FSImage in " + timeTakenToLoadFSImage + " msecs");
  39. NameNodeMetrics nnMetrics = NameNode.getNameNodeMetrics();
  40. if (nnMetrics != null) {
  41. nnMetrics.setFsImageLoadTime((int) timeTakenToLoadFSImage);
  42. }
  43. namesystem.getFSDirectory().createReservedStatuses(namesystem.getCTime());
  44. return namesystem;
  45. }

首先是检查配置文件中的配置:checkConfiguration(conf)

根据配置文件中的配置信息如下

  1. private static void checkConfiguration(Configuration conf)
  2. throws IOException {
  3. final Collection<URI> namespaceDirs =
  4. FSNamesystem.getNamespaceDirs(conf);
  5. final Collection<URI> editsDirs =
  6. FSNamesystem.getNamespaceEditsDirs(conf);
  7. final Collection<URI> requiredEditsDirs =
  8. FSNamesystem.getRequiredNamespaceEditsDirs(conf);
  9. final Collection<URI> sharedEditsDirs =
  10. FSNamesystem.getSharedEditsDirs(conf);
  11. for (URI u : requiredEditsDirs) {
  12. if (u.toString().compareTo(
  13. DFSConfigKeys.DFS_NAMENODE_EDITS_DIR_DEFAULT) == 0) {
  14. continue;
  15. }

FSImage 由下面的代码构建

  1. // 构建 FSImage
  2. FSImage fsImage = new FSImage(conf,
  3. FSNamesystem.getNamespaceDirs(conf), //dfs.namenode.name.dir
  4. FSNamesystem.getNamespaceEditsDirs(conf));

FSImage 的初始化方法

  1. protected FSImage(Configuration conf, Collection<URI> imageDirs, List<URI> editsDirs)
  2. throws IOException{
  3. this.conf = conf;
  4. // 构建NNStorage,负责管理namenode使用的StorageDirectories
  5. storage = new NNStorage(conf, imageDirs, editsDirs);
  6. // dfs.namenode.name.dir.restore default: false
  7. // 设置为true可使namenode尝试恢复以前失败的 dfs.namenode.name.dir
  8. // 启用后,将在检查点期间尝试恢复任何失败的目录
  9. if(conf.getBoolean(DFSConfigKeys.DFS_NAMENODE_NAME_DIR_RESTORE_KEY,
  10. DFSConfigKeys.DFS_NAMENODE_NAME_DIR_RESTORE_DEFAULT)){
  11. storage.setRestoreFailedStorage(true);
  12. }
  13. // 构建FSEditLog
  14. this.editlog = FSEditLog.newInstance(conf, storage, editsDirs);
  15. }

在这里,可以看到设置的 image 的存储目录和 edits 的存储目录

上述代码之后,就是构建 FSEditLog

  1. this.editlog = FSEditLog.newInstance(conf, storage, editsDirs);

再回到 loadFromDisk 方法,到这里已经构建完 FSImage,接下来就是构建 FSNamesystem,加载 FSImage 以及editlog 文件

  1. // 构建FSImage
  2. FSImage fsImage = new FSImage(conf,
  3. FSNamesystem.getNamespaceDirs(conf), //dfs.namenode.name.dir
  4. FSNamesystem.getNamespaceEditsDirs(conf));
  5. // FSNamesystem的构造方法比较长,但是逻辑很简单,主要是从配置文件中获取参数,
  6. // 然后构造FSDirectory、BlockManager、SnapshotManager、CacheManagerSafeModeInfo等对象
  7. // 需要注意的是,FSNamesystem的构造方法并不从磁盘上加载fsimage以及editlog文件,
  8. // 这些操作是在创建FSNamesystem对象成功后,在loadFromDisk()中执行的
  9. // 如果FSNamesystem初始化失败,则会调用FSNamesystem.close()方法
  10. // 关闭FSNamesystem启动的所有服务
  11. FSNamesystem namesystem = new FSNamesystem(conf, fsImage, false);
  12. StartupOption startOpt = NameNode.getStartupOption(conf);
  13. if (startOpt == StartupOption.RECOVER) {
  14. namesystem.setSafeMode(SafeModeAction.SAFEMODE_ENTER);
  15. }
  16. long loadStart = monotonicNow();
  17. try {
  18. // 加载fsimage以及editlog文件
  19. namesystem.loadFSImage(startOpt);

构建NameNodeRpcServer服务:startHttpServer

  1. /**
  2. * Create the RPC server implementation. Used as an extension point for the
  3. * BackupNode.
  4. */
  5. protected NameNodeRpcServer createRpcServer(Configuration conf)
  6. throws IOException {
  7. return new NameNodeRpcServer(conf, this);
  8. }

创建 RPC 服务,这个供程序调用,常用的是 8020 端口

创建完 RPC 服务器,调用 startCommonServices 方法进行启动服务

  1. //启动httpServer以及 rpcServer
  2. startCommonServices(conf);

修改Namenode的状态为active

  1. try {
  2. haContext.writeLock();
  3. // 初始化完成后,Namenode进入Standby状态
  4. // 在这里会开启StandbyCheckpointer里面的
  5. // checkpointer线程,定时合并&处理images文件
  6. state.prepareToEnterState(haContext);
  7. state.enterState(haContext);
  8. } finally {
  9. haContext.writeUnlock();
  10. }