Winse Blog

走走停停都是风景, 熙熙攘攘都向最好, 忙忙碌碌都为明朝, 何畏之.

Elasticsearch Startguide

如果有Lucene的使用经历,elasticsearch的入门还是比较简单的。直接解压启动命令就安装好了,然后就是添加一些plugins就OK了。

安装

从官网下载 TAR包 ,解压后,运行 elasticsearch 脚本启动服务。

1
2
# -d 表示 daemonize 后台运行
[hadoop@cu2 elasticsearch-2.2.0]$ bin/elasticsearch -d

插件

大部分插件都是ajax方式的静态页面,可以通过plugin脚本安装,或者直接解压文件到plugins目录下面。

安装已经下载到本地的插件需要加file协议,不然程序会从官网下载。或者直接解压到plugins目录下:

1
2
3
4
5
6
7
[hadoop@cu2 elasticsearch-2.2.0]$ bin/plugin install file:///home/hadoop/elasticsearch-head-master.zip 
-> Installing from file:/home/hadoop/elasticsearch-head-master.zip...
Trying file:/home/hadoop/elasticsearch-head-master.zip ...
Downloading .........DONE
Verifying file:/home/hadoop/elasticsearch-head-master.zip checksums if available ...
NOTE: Unable to verify checksum for downloaded plugin (unable to find .sha1 or .md5 file to verify)
Installed head into /home/hadoop/elasticsearch-2.2.0/plugins/head

windows

1
E:\local\usr\share\elasticsearch-2.3.3\bin>plugin.bat install file:///D:/SOFTWARE/elasticsearch/elasticsearch-plugin/elasticsearch-head-master.zip

安装好plugin后,打开浏览器查看索引情况: http://localhost:9200/_plugin/head/

插件高阶

有些插件版本比较旧需要改一改,需要了解新版本的 elasticsearch-plugin 的规范:

https://www.elastic.co/guide/en/elasticsearch/plugins/2.3/installation.html

新版本插件主要是需要增加一个描述文件:

Plugins require descriptor file

遇到想安装的旧版本的plugin,描述文件写法可以参考 elasticsearch-head

可选插件:

常用URL请求

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
# https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html
# 创建
$ curl -XPUT 'http://localhost:9200/t_ods_idc_isp_log2/' -d '{
    "settings" : {
        "index" : {
            "number_of_shards" : 3,
            "number_of_replicas" : 0
        }
    }
}'
{"acknowledged":true}

# https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html
# 更新
# mapping.json
{
  "properties": {
      "author": {
          "type": "string"
      },
...
      "year": {
          "type": "long",
          "ignore_malformed": false,
          "index": "analyzed"
      },
      "avaiable": {
          "type": "boolean"
      }
  }
}
$ curl -XPUT 'localhost:9200/t_ods_idc_isp_log2/_mapping/default' -d @mapping.json

$ curl -XPUT 'localhost:9200/t_ods_idc_isp_log2/_mapping/default' -d '
{
  "properties": {
    "fDIID": {
      "type": "string"
    },
...
    "gatherTime": {
      "type": "long"
    }
  }
}
'

# https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates.html
# 索引
# documents.json
{ "index": {      "_index": "library",        "_type": "book",        "_id": "1"  } }
{     "title": "All Quiet on the Western Front",  "otitle": "Im Westen nichts Neues",     "author": "Erich Maria Remarque",   "year": 1929,   "characters": ["Paul Baumer",   "Albert Kropp",     "Haie Westhus",     "Fredrich Muller",  "Stanislaus Katczinsky",    "Tjaden"],  "tags": ["novel"],  "copies": 1,    "available": true,  "section": 3 }
{     "index": {      "_index": "library",        "_type": "book",        "_id": "2"  } }
{     "title": "Catch-22",    "author": "Joseph Heller",  "year": 1961,   "characters": ["John Yossarian",    "Captain Aardvark",     "Chaplain Tappman",     "Colonel Cathcart",     "Doctor Daneeka"],  "tags": ["novel"],  "copies": 6,    "available": false,     "section": 1 }

$ curl -s -XPOST localhost:9200/_bulk --data-binary @documents.json

# 删除
$ curl -XDELETE 'http://localhost:9200/t_ods_idc_isp_log2/'
{"acknowledged":true}

# https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-mapping.html
# https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-field-mapping.html
# 状态查看
http://localhost:9200/_cat/health?v
http://localhost:9200/_cat/nodes?v
http://localhost:9200/_cat/indices?v

curl -XGET 'http://localhost:9200/_all/_mapping/book/field/author'
curl -XHEAD -i 'http://localhost:9200/twitter/tweet'
curl localhost:9200/_stats
curl -XGET 'http://localhost:9200/_all/_mapping/[type]'

–END

[读读书]Apache Spark源码剖析-Shell

本来第二篇应该是与 [第1章 初识Spark] 有关,但我们运行helloworld、以及提交任务都是通过脚本 bin/spark-shell ,完全不知道那些脚本是干啥的?而且,在开发环境运行shell来启动应用总觉得怪怪的,这篇先来简单了解脚本的功能、以及Launcher模块。

其实每个大数据的框架,shell脚本都是通用入口,也是研读源码的第一个突破口 。掌握脚本功能相当于熟悉了基本的API功能,把 spark/bin 目录下面的脚本理清楚,然后再去写搭建开发环境、编写调试helloworld就事半功倍了。

官网 Quick Start 提供的简短例子都是通过 bin/spark-shell 来运行的。Submit页面提供了 bin/spark-submit 提交jar发布任务的方式。 spark-shell,spark-submit 就是两个非常重要的脚本,这里就来看下这两个脚本。

spark-shell - 对应[3.1 spark-shell]章节

spark-shell 脚本的内容相对多一些,主要代码如下(其他代码都是为了兼容cygwin弄的,我们这里不关注):

1
2
3
4
5
SPARK_SUBMIT_OPTS="$SPARK_SUBMIT_OPTS -Dscala.usejavacp=true"
trap onExit INT     # 程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl + C)时触发

export SPARK_SUBMIT_OPTS
"${SPARK_HOME}"/bin/spark-submit --class org.apache.spark.repl.Main --name "Spark shell" "$@"

最终调用 bin/spark-submit 脚本。其实和我们自己提交 helloworld.jar 命令一样:

1
2
3
4
$ bin/spark-submit \
  --class "HelloWorld" \
  --master local[2] \
  target/scala-2.10/helloworld_2.10-1.0.jar

不过通过 bin/spark-shell 提交运行的类是spark自带,没有附加(不需要)额外的jar。这个后面再讲,我们也可以通过这种方式类运行公共位置的jar,可以减少一些不必要的网络带宽。

spark-submit

submit脚本更简单。就是把 org.apache.spark.deploy.SparkSubmit输入参数 全部传递给脚本 bin/spark-class 。

1
exec "${SPARK_HOME}"/bin/spark-class org.apache.spark.deploy.SparkSubmit "$@"

spark-class

主要的功能都集中在 bin/spark-class。bin/spark-class脚本最终启动java、调用 Launcher模块 。而 Launcher模块 解析输入参数并输出 最终输出Driver启动的命令,然后shell再通过 exec 来运行Driver程序。

要讲清楚 bin/spark-class 相对复杂点:通过脚本传递参数,调用java处理参数,又输出脚本,最后运行脚本才真正运行了Driver。所以这里通过 脚本程序 来进行说明。

脚本

  • 先加载环境变量配置文件
  • 再获取 assembly.jar 位置
  • 然后调用 org.apache.spark.launcher.Main , Main类根据环境变量和传入参数算出真正执行的命令(具体在【程序】部分讲)。

下面是核心脚本的内容:

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
. "${SPARK_HOME}"/bin/load-spark-env.sh 
  # 把load-spark-env.sh展开
  . "${user_conf_dir}/spark-env.sh"
  
  ASSEMBLY_DIR1="${SPARK_HOME}/assembly/target/scala-2.10"  # 通过ASSEMBLY路径来判断SPARK_SCALA_VERSION,编译打包成tar的不需要这个变量
  export SPARK_SCALA_VERSION="2.10"

RUNNER="${JAVA_HOME}/bin/java"

SPARK_ASSEMBLY_JAR=
if [ -f "${SPARK_HOME}/RELEASE" ]; then
  ASSEMBLY_DIR="${SPARK_HOME}/lib"
else
  ASSEMBLY_DIR="${SPARK_HOME}/assembly/target/scala-$SPARK_SCALA_VERSION"
fi
ASSEMBLY_JARS="$(ls -1 "$ASSEMBLY_DIR" | grep "^spark-assembly.*hadoop.*\.jar$" || true)"
SPARK_ASSEMBLY_JAR="${ASSEMBLY_DIR}/${ASSEMBLY_JARS}"
LAUNCH_CLASSPATH="$SPARK_ASSEMBLY_JAR"

export _SPARK_ASSEMBLY="$SPARK_ASSEMBLY_JAR"

CMD=()
while IFS= read -d '' -r ARG; do
  CMD+=("$ARG")
done < <("$RUNNER" -cp "$LAUNCH_CLASSPATH" org.apache.spark.launcher.Main "$@")
exec "${CMD[@]}"

大部分内容都是准备环境变量,就最后几行代码比较复杂。这里设置DEBUG在脚本 while 循环打印每个输出的值看下输出的是什么。

1
2
3
4
5
6
7
8
# 修改后的效果
CMD=()
while IFS= read -d '' -r ARG; do
  echo "[DEBUG] $ARG"
  CMD+=("$ARG")
done < <(set -x; "$RUNNER" -cp "$LAUNCH_CLASSPATH" org.apache.spark.launcher.Main "$@")
echo "${CMD[@]}"
exec "${CMD[@]}"

启动 bin/spark-shell(最终会调用 bin/spark-class,上面已经讲过脚本之间的关系),查看输出的调试信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[hadoop@cu2 spark-1.6.0-bin-2.6.3]$ bin/spark-shell 
++ /opt/jdk1.8.0/bin/java -cp /home/hadoop/spark-1.6.0-bin-2.6.3/lib/spark-assembly-1.6.0-hadoop2.6.3-ext-2.1.jar org.apache.spark.launcher.Main org.apache.spark.deploy.SparkSubmit --class org.apache.spark.repl.Main --name 'Spark shell'
[DEBUG] /opt/jdk1.8.0/bin/java
[DEBUG] -cp
[DEBUG] /home/hadoop/spark/lib/mysql-connector-java-5.1.34.jar:/home/hadoop/spark-1.6.0-bin-2.6.3/conf/:/home/hadoop/spark-1.6.0-bin-2.6.3/lib/spark-assembly-1.6.0-hadoop2.6.3-ext-2.1.jar:/home/hadoop/spark-1.6.0-bin-2.6.3/lib/datanucleus-rdbms-3.2.9.jar:/home/hadoop/spark-1.6.0-bin-2.6.3/lib/datanucleus-core-3.2.10.jar:/home/hadoop/spark-1.6.0-bin-2.6.3/lib/datanucleus-api-jdo-3.2.6.jar:/home/hadoop/hadoop/etc/hadoop/
[DEBUG] -Dscala.usejavacp=true
[DEBUG] -Xms512m
[DEBUG] -Xmx512m
[DEBUG] org.apache.spark.deploy.SparkSubmit
[DEBUG] --class
[DEBUG] org.apache.spark.repl.Main
[DEBUG] --name
[DEBUG] Spark shell
[DEBUG] spark-shell
/opt/jdk1.8.0/bin/java -cp /home/hadoop/spark/lib/mysql-connector-java-5.1.34.jar:/home/hadoop/spark-1.6.0-bin-2.6.3/conf/:/home/hadoop/spark-1.6.0-bin-2.6.3/lib/spark-assembly-1.6.0-hadoop2.6.3-ext-2.1.jar:/home/hadoop/spark-1.6.0-bin-2.6.3/lib/datanucleus-rdbms-3.2.9.jar:/home/hadoop/spark-1.6.0-bin-2.6.3/lib/datanucleus-core-3.2.10.jar:/home/hadoop/spark-1.6.0-bin-2.6.3/lib/datanucleus-api-jdo-3.2.6.jar:/home/hadoop/hadoop/etc/hadoop/ -Dscala.usejavacp=true -Xms512m -Xmx512m org.apache.spark.deploy.SparkSubmit --class org.apache.spark.repl.Main --name 'Spark shell' spark-shell
...

从上面的调试信息可以看出:

  • org.apache.spark.launcher.Main 把传入参数整理后重新输出
  • 脚本把java输出内容保存到 CMD[@] 数组中
  • 最后使用exec来执行。

根据上面 bin/spark-class 产生的启动命令可以直接在idea里面运行,效果与直接运行 bin/spark-shell 一样:

注意: 这里的 spark-shell 是一个特殊的字符串,代码中会对其进行特殊处理不额外加载jar。类似的字符串还有: pyspark-shell, sparkr-shell, spark-internal(参看SparkSubmit),如果调用类就在SPARK_CLASSPATH可以使用它们减少不必要的网络传输。

Launcher模块

发现 shell 和 launcher的java代码 功能逻辑非常类似。比如说获取java程序路径的代码:

1
2
3
4
5
6
7
8
9
10
11
List<String> buildJavaCommand(String extraClassPath) throws IOException {
  ...
  if (javaHome != null) {
      cmd.add(join(File.separator, javaHome, "bin", "java"));
  } else if ((envJavaHome = System.getenv("JAVA_HOME")) != null) {
      cmd.add(join(File.separator, envJavaHome, "bin", "java"));
  } else {
      cmd.add(join(File.separator, System.getProperty("java.home"), "bin", "java"));
  }
  ...
}

在shell脚本里面的处理是:

1
2
3
4
5
6
7
8
9
10
11
# Find the java binary
if [ -n "${JAVA_HOME}" ]; then
  RUNNER="${JAVA_HOME}/bin/java"
else
  if [ `command -v java` ]; then
  RUNNER="java"
  else
  echo "JAVA_HOME is not set" >&2
  exit 1
  fi
fi

对比两者,其实是用脚本更加直观。但是使用java编写一个模块更便于管理和扩展,稍微调整下就能复用代码。比如说要添加windows的cmd脚本、又或者为了兼容多个操作系统/多语言(python,r 等)。所以提取一个公共的 Launcher模块 出来其实是个挺不错的选择。同时对于不是很熟悉shell的程序员来说也更方便了解系统运作。

Launcher模块 按功能可以分为 CommandBuilder 和 SparkLauncher 两个部分。

  1. CommandBuilder

  2. SparkSubmitCommandBuilder: 解析用户输入的参数并输出命令给脚本使用

  3. SparkClassCommandBuilder: 主要为后台进程产生启动命令(sbin目录下面的脚本)。

1.1 公共类

  • Main : 统一入口
  • AbstractCommandBuilder : 提供构造命令的公共基类
    • buildJavaCommand
      • buildClassPath
        • SPARK_CLASSPATH
        • extraClassPath
        • getConfDir : 等于环境变量 $SPARK_CONF_DIR 或者 $SPARK_HOME/conf 的值
        • classes
          • SPARK_PREPEND_CLASSES
          • SPARK_TESTING
        • findAssembly : 获取 spark-assembly-1.6.0-hadoop2.6.3.jar 的路径,lib 或者 assembly/target/scala-$SPARK_SCALA_VERSION 路径下
          • _SPARK_ASSEMBLY
        • datanucleus-* : 从 lib / lib_managed/jars 目录下获取
        • HADOOP_CONF_DIR
        • YARN_CONF_DIR
        • SPARK_DIST_CLASSPATH
    • getEffectiveConfig : 获取 spark-defaults.conf 的内容

1.2 SparkSubmitCommandBuilder

主要的类以及参数:

  • SparkSubmitCommandBuilder
    • 构造函数调用OptionParser解析参数,解析handle有处理specialClasses!
    • buildSparkSubmitCommand
      • getEffectiveConfig
      • extraClassPath : spark.driver.extraClassPath
      • SPARK_SUBMIT_OPTS
      • SPARK_JAVA_OPTS
      • client模式下加载配置
        • spark.driver.memory / SPARK_DRIVER_MEMORY / SPARK_MEM / DEFAULT_MEM(1g)
        • DRIVER_EXTRA_JAVA_OPTIONS
        • DRIVER_EXTRA_LIBRARY_PATH
      • buildSparkSubmitArgs
  • SparkSubmitOptionParser(子类需要实现handle方法)
  • SparkSubmitCommandBuilder$OptionParser 命令参数
    • bin/spark-submit -h 查看可以设置的参数
    • 直接查看官网文档

1.3 SparkClassCommandBuilder

主要CommandBuilder的功能上面已经都覆盖了,SparkClassCommandBuilder主要关注命令行可以设置哪些环境变量:

  • org.apache.spark.deploy.master.Master
    • SPARK_DAEMON_JAVA_OPTS
    • SPARK_MASTER_OPTS
    • SPARK_DAEMON_MEMORY
  • org.apache.spark.deploy.worker.Worker
    • SPARK_DAEMON_JAVA_OPTS
    • SPARK_WORKER_OPTS
    • SPARK_DAEMON_MEMORY
  • org.apache.spark.deploy.history.HistoryServer
    • SPARK_DAEMON_JAVA_OPTS
    • SPARK_HISTORY_OPTS
    • SPARK_DAEMON_MEMORY
  • org.apache.spark.executor.CoarseGrainedExecutorBackend
    • SPARK_JAVA_OPTS
    • SPARK_EXECUTOR_OPTS
    • SPARK_EXECUTOR_MEMORY
  • org.apache.spark.executor.MesosExecutorBackend
    • SPARK_EXECUTOR_OPTS
    • SPARK_EXECUTOR_MEMORY
  • org.apache.spark.deploy.ExternalShuffleService / org.apache.spark.deploy.mesos.MesosExternalShuffleService
    • SPARK_DAEMON_JAVA_OPTS
    • SPARK_SHUFFLE_OPTS
    • SPARK_DAEMON_MEMORY
  • org.apache.spark.tools.
    • extraClassPath : spark-tools_.*.jar
    • SPARK_JAVA_OPTS
    • DEFAULT_MEM(1g)
  • other
    • SPARK_JAVA_OPTS
    • SPARK_DRIVER_MEMORY

SparkLauncher

SparkLauncher提供了在程序中提交任务的方式。通过Driver端的支持获取程序执行动态(通过socket与Driver交互),为实现后端管理应用提供一种可行的方式。

SparkLauncher提交任务其中一部分还是使用spark-submit脚本,绕一圈又回到上面的参数解析生成命令然后exec执行。另外SparkLauncher通过启动 SocketServer(LauncherServer)接收来自Driver(LauncherBackend)任务执行情况的最新状态。

代码包括:

  • SparkLauncher 主要是startApplication。其他都是解析设置参数,相当于把shell的工作用java重写了一遍
  • LauncherServer 服务SocketServer类
  • LauncherServer$ServerConnection 状态处理类
  • LauncherConnection 通信基类:接收、发送消息
  • LauncherProtocol 通信协议
  • ChildProcAppHandle : SparkAppHandle 接收到Driver的状态后,请求分发类

具体功能的流转请下载代码 HelloWorldLauncher.scala ,然后本地调试一步步的追踪学习。

–END

[读读书]Apache Spark源码剖析-序

如何高效的阅读hadoop源代码? 先看看这篇。

今天去广州图书馆办了证,借了几本关于大数据的书。老实说,国家提供的便民基础设施应该发挥她的价值,国家建那么多公共设施,还有很多人在后台让这些服务运作起来。借书是一种最高性价比学习的方式,第一:不能乱写乱画必须做笔记或者背下来,把最有价值的东西汇集;第二:有时间限制,好书逼着我们持续的去读;第三:自然是读到烂书也不用花钱,有价值的书必然也是最多人看的,看到翻的很旧的新书你就借了吧。

其中一个《Apache Spark源码剖析-徐鹏》,大致翻了一下,老实说作者很牛逼啊,从那么多的代码里面挑出和主题相关的,不比鸡蛋里面挑石头容易,跟着作者的思路去读应该不错。打算每天读点代码,同时把看书和看代码也记录下来,每天一小结,同时希望对别人有些参考作用。

Spark更新的很快,书本介绍的是 spark-1.0 ,不过书中介绍的主要是思路,我们这里选择比较新的版本 1.6.0 来读(生产用的是1.6)。

说到思路,如果你对Redis也感兴趣,强烈推荐读读 《Redis设计与实现-黄建宏》

使用环境说明

和作者不同,我选择直接在windows来读/调试代码,为了读个代码还得整一套linux的开发环境挺累的(原来也试过整linux开发环境后来放弃了),Windows 积累的经验已经可以让我自由调试和看代码了。

吐槽下sbt,很讨厌这玩意又慢还用ivy,我X,大数据不都用 maven 嘛,难道我还得为 spark 整一套完全一样的jar本地缓冲?不过还好 spark-1.6 已经是用 maven 来管理了。

  • win10 + cygwin
  • jdk8_x64(内存可以调到大于1G)
  • maven3
  • scala_2.10
  • spark_1.6.0
  • hive_1.2.1
  • hadoop_2.6.3
  • JetBrains idea 看代码确实不错

Spark开发环境搭建 - 对应书本的[附录A Spark源码调试]部分

配置 idea-scala

优化idea启动参数

安装 最新版idea (当前最新版本是15.0.5)。在程序安装的 bin 目录下,有x64配置文件 idea64.exe.vmoptions ,在配置文件开头添加jdk8内存配置:

-server
-Xms1g
-Xmx2g
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=256m

由于机器 eclipse 原来使用的 jdk_x86,为了兼容,单独编写 idea64.exe 的启动脚本 idea.bat

set JAVA_HOME=D:\Java\jdk1.8.0_40
D:
cd "D:\Program Files\JetBrains\IntelliJ IDEA Community Edition 15.0.5\bin"
start idea64.exe"

exit

[IDEA的快键配置]:IDEA 适配 Eclipse 的快键集,通过 Settings -> Keymap -> Keymaps 配置。

安装scala插件
  1. 第一种方式:当然最好就是通过plugins的搜索框就能安装,但在中国这得看运气。
  2. 第二种方式:首先下载好插件,然后选择从硬盘安装插件。

  3. 从网络安装

打开 plugins 管理页面:(也可以通过 File -> Settings… -> Plugins 打开)

弹出的 Plugins 对话框显示了当前已经安装的插件:

在 Plugins 对话框页面选择 [Browse repositories…] 按钮,再在弹出的对话框中查找 Scala 的插件:

选择安装 Scala ,当然你也可以同时安装上 SBT 。

  • 从硬盘安装

运气好就算可以直接从网络安装,但是下载过程其实也挺慢的。

我们还可以先自己下载好插件再安装(或者从其他同学获取、迅雷分分钟下完)。首先需要查看自己 idea 的版本,再在 https://plugins.jetbrains.com/?idea_ce 查找下载符合自己版本的 scala 插件,最后通过 [Install plugin from disk…] 安装,然后重启IDEA即可。

下载 spark 源码,并导入idea

  1. 下载源码,检出 1.6.0 版本
1
2
$ git clone https://github.com/apache/spark.git
$ git checkout v1.6.0

如果你只想看 1.6.0 的内容,可以直接在clone命令添加参数指定版本:

$ git clone https://github.com/apache/spark.git -b v1.6.0
  1. 导入idea

导入之前先要生成arvo的java类(这里直接package编译一下):

E:\git\spark\external\flume-sink>mvn package -DskipTests

由于我使用 hadoop-2.6.3 ,并且导入过程中不能修改环境变量,直接修改 pom.xml 里面 hadoop.version 属性的值。

启动IDEA,使用 [Import Project] 导入源代码; 然后选择 E:/git/spark(刚刚下载的源码位置); 然后选择导入maven项目; 在 profile 页把必要的都选上(当然也可以后期通过 Maven Projects 面板来修改):

导入完成后,依赖关系maven已经处理好了,直接就能用了。也可以 Make Projects 再编译一次,并把运行application的make去掉,免得浪费编译时间)。

注意:mvn idea:idea 其实不咋的,生成的配置不兼容。最好不要用!!

  1. 调试/测试

在调试运行之前,先了解下并解决 idea maven-provided 的问题:

在idea里面直接运行 src/main/java 下面的类会被当做在生产环境运行,所以idea不会把这些 provided的依赖 加入到运行的classpath。

IDEA运行时是从 examples/spark-examples_2.10.iml 文件中读取classpath的配置,所以我们直接把 spark-examples_2.10.imlscope="PROVIDED" 全部删掉即可。

1
2
3
# 一次全部删掉!
winse@Lenovo-PC ~/git/spark
$ find . -name "*.iml"  | xargs -I{} sed -i 's/scope="PROVIDED"//' {}

首先右键 [Run LogQuery] 运行(由于缺少master的配置会报错的),主要用于生成启动的 LogQuery Configuration

然后选择上图中下拉选项的 [Edit Configurations…] ,在弹出配置对话框为中为 LogQuery 添加 VM options 配置: -Dspark.master=local ,接下来我们就可以打断点,Debug调试了。

运行结果如下:

遇到IDEA导入maven依赖有问题的,可以参考下 Import Maven dependencies in IntelliJ IDEA

–END

Flamegraphs Java Cpu

在MacTalk的公众号上读到了agentzh关于火焰图介绍(2016年5月6日07:57 动态追踪技术(中) - Dtrace、SystemTap、火焰图),挺新奇的,并且应该对于查询热线程还是有作用的。

先了解perf和flamegraphs基础知识:

perf好像有点类似java的btrace,不过perf是操作系统层面的。把操作系统当做服务,客户端通过perf来获取/查询系统的信息。

监控系统

perf包括在linux 2.6.31代码里面,没装的话redhat可以通过yum来安装/更新:

  • 虚拟机
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@hadoop-master2 ~]# yum install perf
...
Installed:
  perf.x86_64 0:2.6.32-573.26.1.el6  
  
[root@hadoop-master2 ~]# perf stat ls /dev/shm

 Performance counter stats for 'ls /dev/shm':

          0.697115 task-clock                #    0.613 CPUs utilized          
                 0 context-switches          #    0.000 K/sec                  
                 0 cpu-migrations            #    0.000 K/sec                  
               236 page-faults               #    0.339 M/sec                  
   <not supported> cycles                  
   <not supported> stalled-cycles-frontend 
   <not supported> stalled-cycles-backend  
   <not supported> instructions            
   <not supported> branches                
   <not supported> branch-misses           

       0.001137015 seconds time elapsed

虚拟机可能有一些event不能用,到真正的实体机上面应该是没问题的(网上有同学验证过)。可以通过 perf list 查看支持的event。

  • 实体机指标项:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@dacs ~]# perf stat ls /dev/shm
...

 Performance counter stats for 'ls /dev/shm':

          1.793297      task-clock (msec)         #    0.677 CPUs utilized          
                 1      context-switches          #    0.558 K/sec                  
                 0      cpu-migrations            #    0.000 K/sec                  
               255      page-faults               #    0.142 M/sec                  
           2765454      cycles                    #    1.542 GHz                     [44.66%]
           1544155      stalled-cycles-frontend   #   55.84% frontend cycles idle    [64.12%]
           1013635      stalled-cycles-backend    #   36.65% backend  cycles idle   
           2692743      instructions              #    0.97  insns per cycle        
                                                  #    0.57  stalled cycles per insn
            603340      branches                  #  336.442 M/sec                  
             12499      branch-misses             #    2.07% of all branches         [98.00%]

       0.002650313 seconds time elapsed

windows的话直接下载 UIforETW ,运行 UIforETW.exe 就可以用来采样了。把采样产生的etl文件传给xperf_to_collapsedstacks.py,最后用flamegraph.pl画图。

  • perf的常用命令:
1
2
3
4
5
6
7
8
9
10
# http://www.brendangregg.com/perf.html
perf list

perf stat ./t1 
perf stat -a -A ls

perf top
 
perf record – e cpu-clock ./t1 
perf report

参考:

绘制系统火焰图

1
2
3
4
5
6
7
8
9
10
11
12
13
# http://www.brendangregg.com/FlameGraphs/cpuflamegraphs.html
# https://github.com/brendangregg/FlameGraph
# 真实的机器效果还是挺不错的
perf record -F 99 -a -g -- sleep 60
perf script | ~/FlameGraph/stackcollapse-perf.pl >out.perf-folded
~/FlameGraph/flamegraph.pl out.perf-folded >perf.svg
sz perf.svg

# --
# perf script | ./stackcollapse-perf.pl > out.perf-folded
# grep -v cpu_idle out.perf-folded | ./flamegraph.pl > nonidle.svg
# grep ext4 out.perf-folded | ./flamegraph.pl > ext4internals.svg
# egrep 'system_call.*sys_(read|write)' out.perf-folded | ./flamegraph.pl > rw.svg

安装的虚拟机中操作没采集到有用的。虚拟机和真实机器两个图

实体机:

虚拟机:

监控java

首先需要jdk8_u60+,直接下载最新的jdk就好了。应用启动带上参数 -XX:+PreserveFramePointer :

1
2
3
4
5
6
7
[root@hadoop-master2 ~]# java -version
java version "1.8.0_92"
Java(TM) SE Runtime Environment (build 1.8.0_92-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.92-b14, mixed mode)
[root@hadoop-master2 ~]# cd /home/hadoop/spark-1.6.0-bin-2.6.3/
[root@hadoop-master2 spark-1.6.0-bin-2.6.3]# export SPARK_SUBMIT_OPTS=-XX:+PreserveFramePointer     
[root@hadoop-master2 spark-1.6.0-bin-2.6.3]# bin/spark-shell --master local   

这里java进程使用root启动的,如果是普通用户如hadoop,为了采样需要把hadoop用户加入sudoer,在采样时使用 sudo -u hadoop CMD

http://techblog.netflix.com/2015/07/java-in-flames.html

操作方法一:使用perf-map-agent(推荐指数:AAAAA)

老版本OLD 实际操作

1
2
3
4
5
6
7
8
9
10
11
12
git clone https://github.com/jrudolph/perf-map-agent.git
cd perf-map-agent/
export JAVA_HOME=/opt/jdk1.8.0_92
cmake .
make

perf record -F 99 -g -p 7661 -- sleep 120
bin/create-java-perf-map.sh 7661

sudo perf script | ~/FlameGraph/stackcollapse-perf.pl >out.perf-folded
cat out.perf-folded | ~/FlameGraph/flamegraph.pl --color=java >perf.svg
sz perf.svg

新版本NEW 再实践

  • NOTE: 2017-10-21 项目改名了,挂到更牛逼的一个组织下了:jvm-profiling-tools
  • NOTE: 2018-03-09 再实践
  • NOTE: 2019-6-19 再更
  • NOTE: 2020-03-15 再更

参考:

实际操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@dispatch-op-1 bigendian]# yum install git cmake make gcc gcc-c++ perf -y 
[root@dispatch-op-1 bigendian]# git clone https://github.com/jvm-profiling-tools/perf-map-agent.git
[root@dispatch-op-1 perf-map-agent]# cmake .
[root@dispatch-op-1 perf-map-agent]# make 

[root@dispatch-op-1 perf-map-agent]# mkdir -p /home/bigendian/jpt 
[root@dispatch-op-1 perf-map-agent]# bin/create-links-in /home/bigendian/jpt

[root@dispatch-op-1 bigendian]# git clone https://github.com/brendangregg/FlameGraph 

[bigendian@dispatch-op-1 jpt]$ ll
total 0
lrwxrwxrwx 1 root root 51 Mar 15 13:23 perf-java-flames -> /home/bigendian/perf-map-agent/bin/perf-java-flames
lrwxrwxrwx 1 root root 57 Mar 15 13:23 perf-java-record-stack -> /home/bigendian/perf-map-agent/bin/perf-java-record-stack
lrwxrwxrwx 1 root root 57 Mar 15 13:23 perf-java-report-stack -> /home/bigendian/perf-map-agent/bin/perf-java-report-stack
lrwxrwxrwx 1 root root 48 Mar 15 13:23 perf-java-top -> /home/bigendian/perf-map-agent/bin/perf-java-top

[bigendian@dispatch-op-1 ~]$ export FLAMEGRAPH_DIR=~/FlameGraph/
[bigendian@dispatch-op-1 ~]$ jpt/perf-java-flames 23652
Recording events for 15 seconds (adapt by setting PERF_RECORD_SECONDS)

JAVA_OPTS+=" -XX:+PreserveFramePointer  "
[bigendian@dispatch-op-1 ~]$ PERF_RECORD_SECONDS=360 jpt/perf-java-flames 25564 

然后把生成svg拷贝到本地看。

注意,x 轴表示抽样数, 如果一个函数在 x 轴占据的宽度越宽, 就表示它被抽到的次数多, 即执行的时间长. 注意, x 轴不代表时间, 而是所有的调用栈合并后, 按字母顺序排列的. 火焰图就是看顶层的哪个函数占据的宽度最大。只要有”平顶”(plateaus),就表示该函数可能存在性能问题,然后结合具体代码进行分析。

按下 Ctrl + F 会显示一个搜索框,用户可以输入关键词或正则表达式,所有符合条件的函数名会高亮显示.

调用栈不完整: 当调用栈过深时,某些系统只返回前面的一部分(比如前10层)。

操作方式二:使用perfj采样(不再推荐)

NOTE:2017-10-21 这个项目好久没更新了,用上面 perf-map-agent 吧。

参考:性能调优利器——PerfJ ,直接下载release-1.0的版本,解压后给 bin/perfj 加上执行权限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 测试的时刻可以把-F 99设置大一点
# java和perfj的用户得一致!!
# https://github.com/coderplay/perfj/wiki/CPU-Flame-Graph

[root@dacs ~]# export JAVA_HOME=/usr/java/jdk1.8.0_92 
[root@dacs ~]# wget http://blog.minzhou.info/perfj/leveldb-benchmark.jar
[root@dacs ~]# $JAVA_HOME/bin/java -cp leveldb-benchmark.jar -XX:+PreserveFramePointer org.iq80.leveldb.benchmark.DbBenchmark --benchmarks=fillrandom --num=100000000

[root@dacs ~]# export JAVA_HOME=/usr/java/jdk1.8.0_92 
[root@dacs ~]# perfj-1.0/bin/perfj record -F 999 -g -p `pgrep -f DbBenchmark` 

perf script | ~/FlameGraph/stackcollapse-perf.pl >out.perf-folded
~/FlameGraph/flamegraph.pl out.perf-folded  --color=java >perf.svg
sz perf.svg

还是挺有意思的。运行效果:

虚拟机的少了好多信息!一模一样的命令,得出来的东西差好远!!

另一个 Context Switch 案例:

1
2
3
4
5
6
7
8
9
10
# https://github.com/coderplay/perfj/wiki/Context-Switch-Analysis
# 在vmware虚拟机里面运行啥都看不到!实体机也看不到作者的那些栈信息
[root@dacs ~]# wget http://blog.minzhou.info/perfj/leveldb-benchmark.jar
[root@dacs ~]# export JAVA_HOME=/usr/java/jdk1.8.0_92 
[root@dacs ~]# $JAVA_HOME/bin/javac ContextSwitchTest.java 
[root@dacs ~]# $JAVA_HOME/bin/java -XX:+PreserveFramePointer ContextSwitchTest

[root@dacs ~]# export JAVA_HOME=/usr/java/jdk1.8.0_92 
[root@dacs ~]# perfj-1.0/bin/perfj record  -e sched:sched_switch -F 999 -g -p `pgrep -f ContextSwitchTest` 
[root@dacs ~]# perfj-1.0/bin/perfj report --stdio

–END

Hdfs异构存储实操

[注意] 查看官方文档一定要和自己使用的环境对应!操作 storagepolicies 不同版本对应的命令不同(2.6.3<->2.7.2)!

我这里测试环境使用的是 2.6.3 Heterogeneous Storage: Archival Storage, SSD & Memory

配置

直接把内存盘放到 /dev/shm 下,单独挂载一个 tmpfs 的效果也差不多。r2.7.2 Memory Storage Support in HDFS 2.6.3没有这个文档 概念都适应的。

1 调节系统参数

1
2
3
4
5
vi /etc/security/limits.conf

  hadoop           -       nofile          65535
  hadoop           -       nproc           65535
  hadoop           -       memlock         268435456

需要调节memlock的大小,否则启动datanode报错。

1
2
3
4
2016-05-05 19:22:22,674 FATAL org.apache.hadoop.hdfs.server.datanode.DataNode: Exception in secureMain
java.lang.RuntimeException: Cannot start datanode because the configured max locked memory size (dfs.datanode.max.locked.memory) of 134217728 bytes is more than the datanode's available RLIMIT_MEMLOCK ulimit of 65536 bytes.
        at org.apache.hadoop.hdfs.server.datanode.DataNode.startDataNode(DataNode.java:1067)
        at org.apache.hadoop.hdfs.server.datanode.DataNode.<init>(DataNode.java:417)

2 添加RAM_DISK

1
2
3
4
5
6
7
8
9
10
11
12
vi hdfs-site.xml

  <property>
  <name>dfs.datanode.data.dir</name>
  <value>/data/bigdata/hadoop/dfs/data,[RAM_DISK]/dev/shm/dfs/data</value>
  </property>

  <property>
  <name>dfs.datanode.max.locked.memory</name>
  <value>134217728</value>
  </property>
  

注意内存盘的写法,[RAM_DISK] 必须这些写,不然datanode不知道指定路径的storage的类型(默认是 DISK )。Storage_Types_and_Storage_Policies

The default storage type of a datanode storage location will be DISK if it does not have a storage type tagged explicitly.

3 同步配置并重启dfs

1
2
3
4
5
[root@cu2 ~]# scp /etc/security/limits.conf cu3:/etc/security/
[hadoop@cu2 hadoop-2.6.3]$ rsync -vaz etc cu3:~/hadoop-2.6.3/ 

[hadoop@cu2 hadoop-2.6.3] sbin/stop-dfs.sh
[hadoop@cu2 hadoop-2.6.3] sbin/start-dfs.sh

可以去到datanode查看日志,可以看到 /dev/shm/dfs/data 路径 StorageTypeRAM_DISK

1
2
3
4
2016-05-05 19:33:39,862 INFO org.apache.hadoop.hdfs.server.datanode.fsdataset.impl.FsDatasetImpl: Added new volume: /data/bigdata/hadoop/dfs/data/current
2016-05-05 19:33:39,862 INFO org.apache.hadoop.hdfs.server.datanode.fsdataset.impl.FsDatasetImpl: Added volume - /data/bigdata/hadoop/dfs/data/current, StorageType: DISK
2016-05-05 19:33:39,863 INFO org.apache.hadoop.hdfs.server.datanode.fsdataset.impl.FsDatasetImpl: Added new volume: /dev/shm/dfs/data/current
2016-05-05 19:33:39,863 INFO org.apache.hadoop.hdfs.server.datanode.fsdataset.impl.FsDatasetImpl: Added volume - /dev/shm/dfs/data/current, StorageType: RAM_DISK

同时查看 内存盘 的路径内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[hadoop@cu2 hadoop-2.6.3]$ ssh cu3 tree /dev/shm/dfs
/dev/shm/dfs
└── data
    ├── current
    │   ├── BP-1108852639-192.168.0.148-1452322889531
    │   │   ├── current
    │   │   │   ├── finalized
    │   │   │   ├── rbw
    │   │   │   └── VERSION
    │   │   └── tmp
    │   └── VERSION
    └── in_use.lock

7 directories, 3 files

测试使用

通过三个例子对比,简单描述下使用。

首先,使用默认的方式(主要用于对比),
第二,写文件时添加参数,
第三,设置目录的存储类型(目录/文件会继承父目录的存储类型)

1 测试1

1
2
3
4
5
6
7
8
9
10
[hadoop@cu2 hadoop-2.6.3]$ hdfs dfs -put README.txt /tmp/

[hadoop@cu2 hadoop-2.6.3]$ hdfs fsck /tmp/README.txt -files -blocks -locations
...
/tmp/README.txt 1366 bytes, 1 block(s):  OK
0. BP-1108852639-192.168.0.148-1452322889531:blk_1073752574_11776 len=1366 repl=1 [192.168.0.148:50010]

[hadoop@cu3 hadoop-2.6.3]$ find /data/bigdata/hadoop/dfs/data/ /dev/shm/dfs/data/ -name "*1073752574*"
/data/bigdata/hadoop/dfs/data/current/BP-1108852639-192.168.0.148-1452322889531/current/finalized/subdir0/subdir41/blk_1073752574_11776.meta
/data/bigdata/hadoop/dfs/data/current/BP-1108852639-192.168.0.148-1452322889531/current/finalized/subdir0/subdir41/blk_1073752574

2 写文件时添加 lazy_persist 标识

1
2
3
4
5
6
7
8
9
10
11
12
# 添加 -l 参数,后台代码会加上 LAZY_PERSIST 标识。
[hadoop@cu2 hadoop-2.6.3]$ hdfs dfs -help put 
-put [-f] [-p] [-l] <localsrc> ... <dst> :
  Copy files from the local file system into fs. Copying fails if the file already
  exists, unless the -f flag is given.
  Flags:
                                                                       
  -p  Preserves access and modification times, ownership and the mode. 
  -f  Overwrites the destination if it already exists.                 
  -l  Allow DataNode to lazily persist the file to disk. Forces        
         replication factor of 1. This flag will result in reduced
         durability. Use with care.

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
# -l 参数会把 replication 强制设置成数字1 !
[hadoop@cu2 hadoop-2.6.3]$ hdfs dfs -put -l README.txt /tmp/readme.txt2

# 查看namenode的日志,可以看到文件写入到 RAM_DISK 类型的存储
[hadoop@cu2 hadoop-2.6.3]$ less logs/hadoop-hadoop-namenode-cu2.log 

  2016-05-05 20:38:36,465 INFO org.apache.hadoop.hdfs.StateChange: BLOCK* allocateBlock: /tmp/readme.txt2._COPYING_. BP-1108852639-192.168.0.148-1452322889531 blk_1073752578_11780{blockUCState=UNDER_CONSTRUCTION, primaryNodeIndex=-1, replicas=[ReplicaUnderConstruction[[DISK]DS-dcb2673f-3297-4bd7-af1c-ac0ee3eebaf9:NORMAL:192.168.0.30:50010|RBW]]}
  2016-05-05 20:38:36,592 INFO BlockStateChange: BLOCK* addStoredBlock: blockMap updated: 192.168.0.30:50010 is added to blk_1073752578_11780{blockUCState=UNDER_CONSTRUCTION, primaryNodeIndex=-1, replicas=[ReplicaUnderConstruction[[RAM_DISK]DS-bf1ab64f-7eb3-41e0-8466-43287de9893d:NORMAL:192.168.0.30:50010|FINALIZED]]} size 0
  2016-05-05 20:38:36,594 INFO org.apache.hadoop.hdfs.StateChange: DIR* completeFile: /tmp/readme.txt2._COPYING_ is closed by DFSClient_NONMAPREDUCE_-1388277364_1

# 具体的内容所在位置
[hadoop@cu4 ~]$ tree /dev/shm/dfs/data/
/dev/shm/dfs/data/
├── current
│   ├── BP-1108852639-192.168.0.148-1452322889531
│   │   ├── current
│   │   │   ├── finalized
│   │   │   │   └── subdir0
│   │   │   │       └── subdir42
│   │   │   │           ├── blk_1073752578
│   │   │   │           └── blk_1073752578_11780.meta
│   │   │   ├── rbw
│   │   │   └── VERSION
│   │   └── tmp
│   └── VERSION
└── in_use.lock

3 设置目录的存储类型

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
[hadoop@cu2 hadoop-2.6.3]$ hdfs dfs -mkdir /ramdisk
[hadoop@cu2 hadoop-2.6.3]$ hdfs dfsadmin -setStoragePolicy /ramdisk LAZY_PERSIST 
Set storage policy LAZY_PERSIST on /ramdisk

[hadoop@cu2 hadoop-2.6.3]$ hdfs dfs -put README.txt /ramdisk

[hadoop@cu2 hadoop-2.6.3]$ hdfs dfsadmin -getStoragePolicy /ramdisk
The storage policy of /ramdisk:
BlockStoragePolicy{LAZY_PERSIST:15, storageTypes=[RAM_DISK, DISK], creationFallbacks=[DISK], replicationFallbacks=[DISK]}

# 不支持通配符
[hadoop@cu2 hadoop-2.6.3]$ hdfs dfsadmin -getStoragePolicy /ramdisk/*
getStoragePolicy: File/Directory does not exist: /ramdisk/*

[hadoop@cu2 hadoop-2.6.3]$ hdfs dfsadmin -getStoragePolicy /ramdisk/README.txt
The storage policy of /ramdisk/README.txt:
BlockStoragePolicy{LAZY_PERSIST:15, storageTypes=[RAM_DISK, DISK], creationFallbacks=[DISK], replicationFallbacks=[DISK]}


# 添加replication参数,再测试多个备份只有一个写ram_disk
[hadoop@cu2 hadoop-2.6.3]$ hdfs dfs -Ddfs.replication=3 -put README.txt /ramdisk/readme.txt2

[hadoop@cu2 hadoop-2.6.3]$ hdfs dfsadmin -getStoragePolicy /ramdisk/readme.txt2
The storage policy of /ramdisk/readme.txt2:
BlockStoragePolicy{LAZY_PERSIST:15, storageTypes=[RAM_DISK, DISK], creationFallbacks=[DISK], replicationFallbacks=[DISK]}

[hadoop@cu2 hadoop-2.6.3]$ hdfs fsck /ramdisk/readme.txt2 -files -blocks -locations

  /ramdisk/readme.txt2 1366 bytes, 1 block(s):  OK
  0. BP-1108852639-192.168.0.148-1452322889531:blk_1073752580_11782 len=1366 repl=3 [192.168.0.30:50010, 192.168.0.174:50010, 192.168.0.148:50010]

[hadoop@cu3 ~]$ find /data/bigdata/hadoop/dfs/data /dev/shm/dfs/data -name "*1073752580*"
/data/bigdata/hadoop/dfs/data/current/BP-1108852639-192.168.0.148-1452322889531/current/finalized/subdir0/subdir42/blk_1073752580
/data/bigdata/hadoop/dfs/data/current/BP-1108852639-192.168.0.148-1452322889531/current/finalized/subdir0/subdir42/blk_1073752580_11782.meta

# 已经把ram_disk的内容持久化到磁盘了("Lazy_Persist")
[hadoop@cu4 ~]$ find /data/bigdata/hadoop/dfs/data /dev/shm/dfs/data -name "*1073752580*"
/data/bigdata/hadoop/dfs/data/current/BP-1108852639-192.168.0.148-1452322889531/current/lazypersist/subdir0/subdir42/blk_1073752580
/data/bigdata/hadoop/dfs/data/current/BP-1108852639-192.168.0.148-1452322889531/current/lazypersist/subdir0/subdir42/blk_1073752580_11782.meta
/dev/shm/dfs/data/current/BP-1108852639-192.168.0.148-1452322889531/current/finalized/subdir0/subdir42/blk_1073752580
/dev/shm/dfs/data/current/BP-1108852639-192.168.0.148-1452322889531/current/finalized/subdir0/subdir42/blk_1073752580_11782.meta

[hadoop@cu5 ~]$ find /data/bigdata/hadoop/dfs/data /dev/shm/dfs/data -name "*1073752580*"
/data/bigdata/hadoop/dfs/data/current/BP-1108852639-192.168.0.148-1452322889531/current/finalized/subdir0/subdir42/blk_1073752580_11782.meta
/data/bigdata/hadoop/dfs/data/current/BP-1108852639-192.168.0.148-1452322889531/current/finalized/subdir0/subdir42/blk_1073752580

[设想] 对于那些处理完就删除的临时文件,可以把保存的时间设置的久一点 dfs.datanode.lazywriter.interval.sec。这样就不需要写磁盘了。

不要妄想了,反正都会持久化!就是缓冲的效果,其他没有了!!一次性存储并且不需要持久化的还是用alluxio吧。

1
2
org.apache.hadoop.hdfs.server.datanode.fsdataset.impl.FsDatasetImpl.LazyWriter#saveNextReplica
  org.apache.hadoop.hdfs.server.datanode.fsdataset.impl.RamDiskAsyncLazyPersistService#submitLazyPersistTask

参考

–END