From 68aedb4a259e6f3bc4f4446ba0ec6a6912d4661c Mon Sep 17 00:00:00 2001
From: wangzhiwubigdata <2827873682@qq.com>
Date: Sat, 4 Jan 2020 11:46:16 +0800
Subject: [PATCH] add notes
---
...0_\347\232\204\344\275\277\347\224\250.md" | 225 ++++++
...0_\347\232\204\344\275\277\347\224\250.md" | 296 +++++++
.../Azkaban\347\256\200\344\273\213.md" | 76 ++
.../Flink_Data_Sink.md" | 268 ++++++
.../Flink_Data_Source.md" | 284 +++++++
.../Flink_Data_Transformation.md" | 311 +++++++
.../Flink_Windows.md" | 128 +++
...57\345\242\203\346\220\255\345\273\272.md" | 304 +++++++
...02\345\277\265\347\273\274\350\277\260.md" | 173 ++++
...45\347\202\271\346\234\272\345\210\266.md" | 370 +++++++++
.../Flume\346\225\264\345\220\210Kafka.md" | 116 +++
...72\346\234\254\344\275\277\347\224\250.md" | 375 +++++++++
.../HDFS-Java-API.md" | 388 +++++++++
...7\224\250Shell\345\221\275\344\273\244.md" | 141 ++++
.../Hadoop-HDFS.md" | 176 ++++
.../Hadoop-MapReduce.md" | 384 +++++++++
.../Hadoop-YARN.md" | 128 +++
.../Hbase_Java_API.md" | 761 ++++++++++++++++++
.../Hbase_Shell.md" | 279 +++++++
...06\345\231\250\350\257\246\350\247\243.md" | 490 +++++++++++
...76\344\270\216\345\244\207\344\273\275.md" | 196 +++++
...70\255\351\227\264\345\261\202_Phoenix.md" | 241 ++++++
.../Hbase\347\256\200\344\273\213.md" | 88 ++
...60\346\215\256\347\273\223\346\236\204.md" | 222 +++++
...44\345\231\250\350\257\246\350\247\243.md" | 445 ++++++++++
...72\346\234\254\344\275\277\347\224\250.md" | 279 +++++++
...14\345\210\206\346\241\266\350\241\250.md" | 168 ++++
...347\224\250DDL\346\223\215\344\275\234.md" | 450 +++++++++++
...347\224\250DML\346\223\215\344\275\234.md" | 329 ++++++++
...45\350\257\242\350\257\246\350\247\243.md" | 396 +++++++++
...70\345\277\203\346\246\202\345\277\265.md" | 202 +++++
...76\345\222\214\347\264\242\345\274\225.md" | 236 ++++++
...71\350\200\205\350\257\246\350\247\243.md" | 392 +++++++++
...57\346\234\254\346\234\272\345\210\266.md" | 161 ++++
...47\350\200\205\350\257\246\350\247\243.md" | 364 +++++++++
.../Kafka\347\256\200\344\273\213.md" | 67 ++
...60\345\222\214\351\227\255\345\214\205.md" | 312 +++++++
...27\350\241\250\345\222\214\351\233\206.md" | 542 +++++++++++++
...14\350\277\220\347\256\227\347\254\246.md" | 274 +++++++
.../Scala\346\225\260\347\273\204.md" | 193 +++++
...04\345\222\214\345\205\203\347\273\204.md" | 282 +++++++
...41\345\274\217\345\214\271\351\205\215.md" | 172 ++++
...47\345\210\266\350\257\255\345\217\245.md" | 211 +++++
...57\345\242\203\351\205\215\347\275\256.md" | 133 +++
...73\345\222\214\345\257\271\350\261\241.md" | 412 ++++++++++
...73\345\236\213\345\217\202\346\225\260.md" | 467 +++++++++++
...77\345\222\214\347\211\271\350\264\250.md" | 418 ++++++++++
...20\345\274\217\345\217\202\346\225\260.md" | 356 ++++++++
...06\345\220\210\347\261\273\345\236\213.md" | 259 ++++++
...2\214DataFrame\347\256\200\344\273\213.md" | 147 ++++
...50\346\225\260\346\215\256\346\272\220.md" | 499 ++++++++++++
...32\345\220\210\345\207\275\346\225\260.md" | 339 ++++++++
...24\347\273\223\346\223\215\344\275\234.md" | 185 +++++
.../Spark_RDD.md" | 237 ++++++
...16\346\265\201\345\244\204\347\220\206.md" | 79 ++
...72\346\234\254\346\223\215\344\275\234.md" | 335 ++++++++
...Streaming\346\225\264\345\220\210Flume.md" | 359 +++++++++
...Streaming\346\225\264\345\220\210Kafka.md" | 321 ++++++++
...72\346\234\254\344\275\277\347\224\250.md" | 244 ++++++
...\222\214Action\347\256\227\345\255\220.md" | 418 ++++++++++
.../Spark\347\256\200\344\273\213.md" | 94 +++
...77\346\222\255\345\217\230\351\207\217.md" | 105 +++
...34\344\270\232\346\217\220\344\272\244.md" | 248 ++++++
...ybtais+Phoenix\346\225\264\345\220\210.md" | 386 +++++++++
...72\346\234\254\344\275\277\347\224\250.md" | 387 +++++++++
...13\344\270\216\345\256\211\350\243\205.md" | 147 ++++
...71\346\257\224\345\210\206\346\236\220.md" | 315 ++++++++
...04\347\220\206\347\256\200\344\273\213.md" | 98 +++
...02\345\277\265\350\257\246\350\247\243.md" | 159 ++++
...41\345\236\213\350\257\246\350\247\243.md" | 511 ++++++++++++
...3\206\346\210\220HBase\345\222\214HDFS.md" | 489 +++++++++++
.../Storm\351\233\206\346\210\220Kakfa.md" | 367 +++++++++
...6\210\220Redis\350\257\246\350\247\243.md" | 655 +++++++++++++++
...03\351\231\220\346\216\247\345\210\266.md" | 283 +++++++
...256\242\346\210\267\347\253\257Curator.md" | 336 ++++++++
...7\224\250Shell\345\221\275\344\273\244.md" | 265 ++++++
...70\345\277\203\346\246\202\345\277\265.md" | 207 +++++
...21\345\217\212\351\203\250\347\275\262.md" | 124 +++
.../installation/Flink_Standalone_Cluster.md" | 270 +++++++
...57\345\242\203\346\220\255\345\273\272.md" | 227 ++++++
...57\345\242\203\346\220\255\345\273\272.md" | 200 +++++
...57\345\242\203\346\220\255\345\273\272.md" | 262 ++++++
...57\345\242\203\346\220\255\345\273\272.md" | 233 ++++++
...me\347\232\204\345\256\211\350\243\205.md" | 68 ++
...344\270\213JDK\345\256\211\350\243\205.md" | 55 ++
...\270\213Python\345\256\211\350\243\205.md" | 71 ++
...11\350\243\205\351\203\250\347\275\262.md" | 181 +++++
...57\345\242\203\346\220\255\345\273\272.md" | 178 ++++
...57\345\242\203\346\220\255\345\273\272.md" | 190 +++++
...57\345\242\203\346\220\255\345\273\272.md" | 81 ++
...57\345\242\203\346\220\255\345\273\272.md" | 167 ++++
...57\345\242\203\346\220\255\345\273\272.md" | 187 +++++
...57\347\224\250\351\233\206\347\276\244.md" | 514 ++++++++++++
...57\347\224\250\351\233\206\347\276\244.md" | 239 ++++++
...\345\244\232IP\351\205\215\347\275\256.md" | 118 +++
...46\344\271\240\350\267\257\347\272\277.md" | 169 ++++
...11\350\243\205\346\214\207\345\215\227.md" | 67 ++
...23\345\214\205\346\226\271\345\274\217.md" | 306 +++++++
...35\347\273\264\345\257\274\345\233\276.md" | 2 +
...45\345\205\267\346\216\250\350\215\220.md" | 56 ++
100 files changed, 26120 insertions(+)
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Azkaban_Flow_1.0_\347\232\204\344\275\277\347\224\250.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Azkaban_Flow_2.0_\347\232\204\344\275\277\347\224\250.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Azkaban\347\256\200\344\273\213.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink_Data_Sink.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink_Data_Source.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink_Data_Transformation.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink_Windows.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink\345\274\200\345\217\221\347\216\257\345\242\203\346\220\255\345\273\272.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink\346\240\270\345\277\203\346\246\202\345\277\265\347\273\274\350\277\260.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink\347\212\266\346\200\201\347\256\241\347\220\206\344\270\216\346\243\200\346\237\245\347\202\271\346\234\272\345\210\266.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flume\346\225\264\345\220\210Kafka.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flume\347\256\200\344\273\213\345\217\212\345\237\272\346\234\254\344\275\277\347\224\250.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/HDFS-Java-API.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/HDFS\345\270\270\347\224\250Shell\345\221\275\344\273\244.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hadoop-HDFS.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hadoop-MapReduce.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hadoop-YARN.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase_Java_API.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase_Shell.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase\345\215\217\345\244\204\347\220\206\345\231\250\350\257\246\350\247\243.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase\345\256\271\347\201\276\344\270\216\345\244\207\344\273\275.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase\347\232\204SQL\344\270\255\351\227\264\345\261\202_Phoenix.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase\347\256\200\344\273\213.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase\347\263\273\347\273\237\346\236\266\346\236\204\345\217\212\346\225\260\346\215\256\347\273\223\346\236\204.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase\350\277\207\346\273\244\345\231\250\350\257\246\350\247\243.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/HiveCLI\345\222\214Beeline\345\221\275\344\273\244\350\241\214\347\232\204\345\237\272\346\234\254\344\275\277\347\224\250.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hive\345\210\206\345\214\272\350\241\250\345\222\214\345\210\206\346\241\266\350\241\250.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hive\345\270\270\347\224\250DDL\346\223\215\344\275\234.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hive\345\270\270\347\224\250DML\346\223\215\344\275\234.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hive\346\225\260\346\215\256\346\237\245\350\257\242\350\257\246\350\247\243.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hive\347\256\200\344\273\213\345\217\212\346\240\270\345\277\203\346\246\202\345\277\265.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hive\350\247\206\345\233\276\345\222\214\347\264\242\345\274\225.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Kafka\346\266\210\350\264\271\350\200\205\350\257\246\350\247\243.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Kafka\346\267\261\345\205\245\347\220\206\350\247\243\345\210\206\345\214\272\345\211\257\346\234\254\346\234\272\345\210\266.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Kafka\347\224\237\344\272\247\350\200\205\350\257\246\350\247\243.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Kafka\347\256\200\344\273\213.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\345\207\275\346\225\260\345\222\214\351\227\255\345\214\205.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\345\210\227\350\241\250\345\222\214\351\233\206.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\345\237\272\346\234\254\346\225\260\346\215\256\347\261\273\345\236\213\345\222\214\350\277\220\347\256\227\347\254\246.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\346\225\260\347\273\204.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\346\230\240\345\260\204\345\222\214\345\205\203\347\273\204.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\346\250\241\345\274\217\345\214\271\351\205\215.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\346\265\201\347\250\213\346\216\247\345\210\266\350\257\255\345\217\245.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\347\256\200\344\273\213\345\217\212\345\274\200\345\217\221\347\216\257\345\242\203\351\205\215\347\275\256.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\347\261\273\345\222\214\345\257\271\350\261\241.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\347\261\273\345\236\213\345\217\202\346\225\260.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\347\273\247\346\211\277\345\222\214\347\211\271\350\264\250.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\351\232\220\345\274\217\350\275\254\346\215\242\345\222\214\351\232\220\345\274\217\345\217\202\346\225\260.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\351\233\206\345\220\210\347\261\273\345\236\213.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/SparkSQL_Dataset\345\222\214DataFrame\347\256\200\344\273\213.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/SparkSQL\345\244\226\351\203\250\346\225\260\346\215\256\346\272\220.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/SparkSQL\345\270\270\347\224\250\350\201\232\345\220\210\345\207\275\346\225\260.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/SparkSQL\350\201\224\347\273\223\346\223\215\344\275\234.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_RDD.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_Streaming\344\270\216\346\265\201\345\244\204\347\220\206.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_Streaming\345\237\272\346\234\254\346\223\215\344\275\234.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_Streaming\346\225\264\345\220\210Flume.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_Streaming\346\225\264\345\220\210Kafka.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_Structured_API\347\232\204\345\237\272\346\234\254\344\275\277\347\224\250.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_Transformation\345\222\214Action\347\256\227\345\255\220.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark\347\256\200\344\273\213.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark\347\264\257\345\212\240\345\231\250\344\270\216\345\271\277\346\222\255\345\217\230\351\207\217.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark\351\203\250\347\275\262\346\250\241\345\274\217\344\270\216\344\275\234\344\270\232\346\217\220\344\272\244.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spring+Mybtais+Phoenix\346\225\264\345\220\210.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Sqoop\345\237\272\346\234\254\344\275\277\347\224\250.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Sqoop\347\256\200\344\273\213\344\270\216\345\256\211\350\243\205.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Storm\344\270\211\347\247\215\346\211\223\345\214\205\346\226\271\345\274\217\345\257\271\346\257\224\345\210\206\346\236\220.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Storm\345\222\214\346\265\201\345\244\204\347\220\206\347\256\200\344\273\213.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Storm\346\240\270\345\277\203\346\246\202\345\277\265\350\257\246\350\247\243.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Storm\347\274\226\347\250\213\346\250\241\345\236\213\350\257\246\350\247\243.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Storm\351\233\206\346\210\220HBase\345\222\214HDFS.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Storm\351\233\206\346\210\220Kakfa.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Storm\351\233\206\346\210\220Redis\350\257\246\350\247\243.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Zookeeper_ACL\346\235\203\351\231\220\346\216\247\345\210\266.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Zookeeper_Java\345\256\242\346\210\267\347\253\257Curator.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Zookeeper\345\270\270\347\224\250Shell\345\221\275\344\273\244.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Zookeeper\347\256\200\344\273\213\345\217\212\346\240\270\345\277\203\346\246\202\345\277\265.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/installation/Azkaban_3.x_\347\274\226\350\257\221\345\217\212\351\203\250\347\275\262.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/installation/Flink_Standalone_Cluster.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/installation/HBase\345\215\225\346\234\272\347\216\257\345\242\203\346\220\255\345\273\272.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/installation/HBase\351\233\206\347\276\244\347\216\257\345\242\203\346\220\255\345\273\272.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/installation/Hadoop\345\215\225\346\234\272\347\216\257\345\242\203\346\220\255\345\273\272.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/installation/Hadoop\351\233\206\347\276\244\347\216\257\345\242\203\346\220\255\345\273\272.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/installation/Linux\344\270\213Flume\347\232\204\345\256\211\350\243\205.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/installation/Linux\344\270\213JDK\345\256\211\350\243\205.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/installation/Linux\344\270\213Python\345\256\211\350\243\205.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/installation/Linux\347\216\257\345\242\203\344\270\213Hive\347\232\204\345\256\211\350\243\205\351\203\250\347\275\262.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/installation/Spark\345\274\200\345\217\221\347\216\257\345\242\203\346\220\255\345\273\272.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/installation/Spark\351\233\206\347\276\244\347\216\257\345\242\203\346\220\255\345\273\272.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/installation/Storm\345\215\225\346\234\272\347\216\257\345\242\203\346\220\255\345\273\272.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/installation/Storm\351\233\206\347\276\244\347\216\257\345\242\203\346\220\255\345\273\272.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/installation/Zookeeper\345\215\225\346\234\272\347\216\257\345\242\203\345\222\214\351\233\206\347\276\244\347\216\257\345\242\203\346\220\255\345\273\272.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/installation/\345\237\272\344\272\216Zookeeper\346\220\255\345\273\272Hadoop\351\253\230\345\217\257\347\224\250\351\233\206\347\276\244.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/installation/\345\237\272\344\272\216Zookeeper\346\220\255\345\273\272Kafka\351\253\230\345\217\257\347\224\250\351\233\206\347\276\244.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/installation/\350\231\232\346\213\237\346\234\272\351\235\231\346\200\201IP\345\217\212\345\244\232IP\351\205\215\347\275\256.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/\345\244\247\346\225\260\346\215\256\345\255\246\344\271\240\350\267\257\347\272\277.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/\345\244\247\346\225\260\346\215\256\345\270\270\347\224\250\350\275\257\344\273\266\345\256\211\350\243\205\346\214\207\345\215\227.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/\345\244\247\346\225\260\346\215\256\345\272\224\347\224\250\345\270\270\347\224\250\346\211\223\345\214\205\346\226\271\345\274\217.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/\345\244\247\346\225\260\346\215\256\346\212\200\346\234\257\346\240\210\346\200\235\347\273\264\345\257\274\345\233\276.md"
create mode 100644 "\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/\350\265\204\346\226\231\345\210\206\344\272\253\344\270\216\345\267\245\345\205\267\346\216\250\350\215\220.md"
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Azkaban_Flow_1.0_\347\232\204\344\275\277\347\224\250.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Azkaban_Flow_1.0_\347\232\204\344\275\277\347\224\250.md"
new file mode 100644
index 0000000..11a6d5e
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Azkaban_Flow_1.0_\347\232\204\344\275\277\347\224\250.md"
@@ -0,0 +1,225 @@
+# Azkaban Flow 1.0 的使用
+
+
+一、简介
+二、基本任务调度
+三、多任务调度
+四、调度HDFS作业
+五、调度MR作业
+六、调度Hive作业
+七、在线修改作业配置
+
+
+
+
+## 一、简介
+
+Azkaban 主要通过界面上传配置文件来进行任务的调度。它有两个重要的概念:
+
+- **Job**: 你需要执行的调度任务;
+- **Flow**:一个获取多个 Job 及它们之间的依赖关系所组成的图表叫做 Flow。
+
+目前 Azkaban 3.x 同时支持 Flow 1.0 和 Flow 2.0,本文主要讲解 Flow 1.0 的使用,下一篇文章会讲解 Flow 2.0 的使用。
+
+## 二、基本任务调度
+
+### 2.1 新建项目
+
+在 Azkaban 主界面可以创建对应的项目:
+
+
+
+### 2.2 任务配置
+
+新建任务配置文件 `Hello-Azkaban.job`,内容如下。这里的任务很简单,就是输出一句 `'Hello Azkaban!'` :
+
+```shell
+#command.job
+type=command
+command=echo 'Hello Azkaban!'
+```
+
+### 2.3 打包上传
+
+将 `Hello-Azkaban.job ` 打包为 `zip` 压缩文件:
+
+
+
+通过 Web UI 界面上传:
+
+
+
+上传成功后可以看到对应的 Flows:
+
+
+
+### 2.4 执行任务
+
+点击页面上的 `Execute Flow` 执行任务:
+
+
+
+### 2.5 执行结果
+
+点击 `detail` 可以查看到任务的执行日志:
+
+
+
+
+
+## 三、多任务调度
+
+### 3.1 依赖配置
+
+这里假设我们有五个任务(TaskA——TaskE),D 任务需要在 A,B,C 任务执行完成后才能执行,而 E 任务则需要在 D 任务执行完成后才能执行,这种情况下需要使用 `dependencies` 属性定义其依赖关系。各任务配置如下:
+
+**Task-A.job** :
+
+```shell
+type=command
+command=echo 'Task A'
+```
+
+**Task-B.job** :
+
+```shell
+type=command
+command=echo 'Task B'
+```
+
+**Task-C.job** :
+
+```shell
+type=command
+command=echo 'Task C'
+```
+
+**Task-D.job** :
+
+```shell
+type=command
+command=echo 'Task D'
+dependencies=Task-A,Task-B,Task-C
+```
+
+**Task-E.job** :
+
+```shell
+type=command
+command=echo 'Task E'
+dependencies=Task-D
+```
+
+### 3.2 压缩上传
+
+压缩后进行上传,这里需要注意的是一个 Project 只能接收一个压缩包,这里我还沿用上面的 Project,默认后面的压缩包会覆盖前面的压缩包:
+
+
+
+### 3.3 依赖关系
+
+多个任务存在依赖时,默认采用最后一个任务的文件名作为 Flow 的名称,其依赖关系如图:
+
+
+
+### 3.4 执行结果
+
+
+
+从这个案例可以看出,Flow1.0 无法通过一个 job 文件来完成多个任务的配置,但是 Flow 2.0 就很好的解决了这个问题。
+
+## 四、调度HDFS作业
+
+步骤与上面的步骤一致,这里以查看 HDFS 上的文件列表为例。命令建议采用完整路径,配置文件如下:
+
+```shell
+type=command
+command=/usr/app/hadoop-2.6.0-cdh5.15.2/bin/hadoop fs -ls /
+```
+
+执行结果:
+
+
+
+## 五、调度MR作业
+
+MR 作业配置:
+
+```shell
+type=command
+command=/usr/app/hadoop-2.6.0-cdh5.15.2/bin/hadoop jar /usr/app/hadoop-2.6.0-cdh5.15.2/share/hadoop/mapreduce/hadoop-mapreduce-examples-2.6.0-cdh5.15.2.jar pi 3 3
+```
+
+执行结果:
+
+
+
+## 六、调度Hive作业
+
+作业配置:
+
+```shell
+type=command
+command=/usr/app/hive-1.1.0-cdh5.15.2/bin/hive -f 'test.sql'
+```
+
+其中 `test.sql` 内容如下,创建一张雇员表,然后查看其结构:
+
+```sql
+CREATE DATABASE IF NOT EXISTS hive;
+use hive;
+drop table if exists emp;
+CREATE TABLE emp(
+empno int,
+ename string,
+job string,
+mgr int,
+hiredate string,
+sal double,
+comm double,
+deptno int
+) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t';
+-- 查看 emp 表的信息
+desc emp;
+```
+
+打包的时候将 `job` 文件与 `sql` 文件一并进行打包:
+
+
+
+执行结果如下:
+
+
+
+## 七、在线修改作业配置
+
+在测试时,我们可能需要频繁修改配置,如果每次修改都要重新打包上传,这会比较麻烦。所以 Azkaban 支持配置的在线修改,点击需要修改的 Flow,就可以进入详情页面:
+
+
+
+在详情页面点击 `Eidt` 按钮可以进入编辑页面:
+
+
+
+在编辑页面可以新增配置或者修改配置:
+
+
+
+## 附:可能出现的问题
+
+如果出现以下异常,多半是因为执行主机内存不足,Azkaban 要求执行主机的可用内存必须大于 3G 才能执行任务:
+
+```shell
+Cannot request memory (Xms 0 kb, Xmx 0 kb) from system for job
+```
+
+
+
+如果你的执行主机没办法增大内存,那么可以通过修改 `plugins/jobtypes/` 目录下的 `commonprivate.properties` 文件来关闭内存检查,配置如下:
+
+```shell
+memCheck.enabled=false
+```
+
+
+
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Azkaban_Flow_2.0_\347\232\204\344\275\277\347\224\250.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Azkaban_Flow_2.0_\347\232\204\344\275\277\347\224\250.md"
new file mode 100644
index 0000000..cfd236e
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Azkaban_Flow_2.0_\347\232\204\344\275\277\347\224\250.md"
@@ -0,0 +1,296 @@
+# Azkaban Flow 2.0的使用
+
+
+一、Flow 2.0 简介
+二、YAML语法
+三、简单任务调度
+四、多任务调度
+五、内嵌流
+
+
+
+## 一、Flow 2.0 简介
+
+### 1.1 Flow 2.0 的产生
+
+Azkaban 目前同时支持 Flow 1.0 和 Flow2.0 ,但是官方文档上更推荐使用 Flow 2.0,因为 Flow 1.0 会在将来的版本被移除。Flow 2.0 的主要设计思想是提供 1.0 所没有的流级定义。用户可以将属于给定流的所有 `job / properties` 文件合并到单个流定义文件中,其内容采用 YAML 语法进行定义,同时还支持在流中再定义流,称为为嵌入流或子流。
+
+### 1.2 基本结构
+
+项目 zip 将包含多个流 YAML 文件,一个项目 YAML 文件以及可选库和源代码。Flow YAML 文件的基本结构如下:
+
++ 每个 Flow 都在单个 YAML 文件中定义;
++ 流文件以流名称命名,如:`my-flow-name.flow`;
++ 包含 DAG 中的所有节点;
++ 每个节点可以是作业或流程;
++ 每个节点 可以拥有 name, type, config, dependsOn 和 nodes sections 等属性;
++ 通过列出 dependsOn 列表中的父节点来指定节点依赖性;
++ 包含与流相关的其他配置;
++ 当前 properties 文件中流的所有常见属性都将迁移到每个流 YAML 文件中的 config 部分。
+
+官方提供了一个比较完善的配置样例,如下:
+
+```yaml
+config:
+ user.to.proxy: azktest
+ param.hadoopOutData: /tmp/wordcounthadoopout
+ param.inData: /tmp/wordcountpigin
+ param.outData: /tmp/wordcountpigout
+
+# This section defines the list of jobs
+# A node can be a job or a flow
+# In this example, all nodes are jobs
+nodes:
+ # Job definition
+ # The job definition is like a YAMLified version of properties file
+ # with one major difference. All custom properties are now clubbed together
+ # in a config section in the definition.
+ # The first line describes the name of the job
+ - name: AZTest
+ type: noop
+ # The dependsOn section contains the list of parent nodes the current
+ # node depends on
+ dependsOn:
+ - hadoopWC1
+ - NoOpTest1
+ - hive2
+ - java1
+ - jobCommand2
+
+ - name: pigWordCount1
+ type: pig
+ # The config section contains custom arguments or parameters which are
+ # required by the job
+ config:
+ pig.script: src/main/pig/wordCountText.pig
+
+ - name: hadoopWC1
+ type: hadoopJava
+ dependsOn:
+ - pigWordCount1
+ config:
+ classpath: ./*
+ force.output.overwrite: true
+ input.path: ${param.inData}
+ job.class: com.linkedin.wordcount.WordCount
+ main.args: ${param.inData} ${param.hadoopOutData}
+ output.path: ${param.hadoopOutData}
+
+ - name: hive1
+ type: hive
+ config:
+ hive.script: src/main/hive/showdb.q
+
+ - name: NoOpTest1
+ type: noop
+
+ - name: hive2
+ type: hive
+ dependsOn:
+ - hive1
+ config:
+ hive.script: src/main/hive/showTables.sql
+
+ - name: java1
+ type: javaprocess
+ config:
+ Xms: 96M
+ java.class: com.linkedin.foo.HelloJavaProcessJob
+
+ - name: jobCommand1
+ type: command
+ config:
+ command: echo "hello world from job_command_1"
+
+ - name: jobCommand2
+ type: command
+ dependsOn:
+ - jobCommand1
+ config:
+ command: echo "hello world from job_command_2"
+```
+
+## 二、YAML语法
+
+想要使用 Flow 2.0 进行工作流的配置,首先需要了解 YAML 。YAML 是一种简洁的非标记语言,有着严格的格式要求的,如果你的格式配置失败,上传到 Azkaban 的时候就会抛出解析异常。
+
+### 2.1 基本规则
+
+1. 大小写敏感 ;
+2. 使用缩进表示层级关系 ;
+3. 缩进长度没有限制,只要元素对齐就表示这些元素属于一个层级;
+4. 使用#表示注释 ;
+5. 字符串默认不用加单双引号,但单引号和双引号都可以使用,双引号表示不需要对特殊字符进行转义;
+6. YAML 中提供了多种常量结构,包括:整数,浮点数,字符串,NULL,日期,布尔,时间。
+
+### 2.2 对象的写法
+
+```yaml
+# value 与 : 符号之间必须要有一个空格
+key: value
+```
+
+### 2.3 map的写法
+
+```yaml
+# 写法一 同一缩进的所有键值对属于一个map
+key:
+ key1: value1
+ key2: value2
+
+# 写法二
+{key1: value1, key2: value2}
+```
+
+### 2.3 数组的写法
+
+```yaml
+# 写法一 使用一个短横线加一个空格代表一个数组项
+- a
+- b
+- c
+
+# 写法二
+[a,b,c]
+```
+
+### 2.5 单双引号
+
+支持单引号和双引号,但双引号不会对特殊字符进行转义:
+
+```yaml
+s1: '内容\n 字符串'
+s2: "内容\n 字符串"
+
+转换后:
+{ s1: '内容\\n 字符串', s2: '内容\n 字符串' }
+```
+
+### 2.6 特殊符号
+
+一个 YAML 文件中可以包括多个文档,使用 `---` 进行分割。
+
+### 2.7 配置引用
+
+Flow 2.0 建议将公共参数定义在 `config` 下,并通过 `${}` 进行引用。
+
+
+
+## 三、简单任务调度
+
+### 3.1 任务配置
+
+新建 `flow` 配置文件:
+
+```yaml
+nodes:
+ - name: jobA
+ type: command
+ config:
+ command: echo "Hello Azkaban Flow 2.0."
+```
+
+在当前的版本中,Azkaban 同时支持 Flow 1.0 和 Flow 2.0,如果你希望以 2.0 的方式运行,则需要新建一个 `project` 文件,指明是使用的是 Flow 2.0:
+
+```shell
+azkaban-flow-version: 2.0
+```
+
+### 3.2 打包上传
+
+
+
+
+
+### 3.3 执行结果
+
+由于在 1.0 版本中已经介绍过 Web UI 的使用,这里就不再赘述。对于 1.0 和 2.0 版本,只有配置方式有所不同,其他上传执行的方式都是相同的。执行结果如下:
+
+
+
+## 四、多任务调度
+
+和 1.0 给出的案例一样,这里假设我们有五个任务(jobA——jobE), D 任务需要在 A,B,C 任务执行完成后才能执行,而 E 任务则需要在 D 任务执行完成后才能执行,相关配置文件应如下。可以看到在 1.0 中我们需要分别定义五个配置文件,而在 2.0 中我们只需要一个配置文件即可完成配置。
+
+```yaml
+nodes:
+ - name: jobE
+ type: command
+ config:
+ command: echo "This is job E"
+ # jobE depends on jobD
+ dependsOn:
+ - jobD
+
+ - name: jobD
+ type: command
+ config:
+ command: echo "This is job D"
+ # jobD depends on jobA、jobB、jobC
+ dependsOn:
+ - jobA
+ - jobB
+ - jobC
+
+ - name: jobA
+ type: command
+ config:
+ command: echo "This is job A"
+
+ - name: jobB
+ type: command
+ config:
+ command: echo "This is job B"
+
+ - name: jobC
+ type: command
+ config:
+ command: echo "This is job C"
+```
+
+## 五、内嵌流
+
+Flow2.0 支持在一个 Flow 中定义另一个 Flow,称为内嵌流或者子流。这里给出一个内嵌流的示例,其 `Flow` 配置如下:
+
+```yaml
+nodes:
+ - name: jobC
+ type: command
+ config:
+ command: echo "This is job C"
+ dependsOn:
+ - embedded_flow
+
+ - name: embedded_flow
+ type: flow
+ config:
+ prop: value
+ nodes:
+ - name: jobB
+ type: command
+ config:
+ command: echo "This is job B"
+ dependsOn:
+ - jobA
+
+ - name: jobA
+ type: command
+ config:
+ command: echo "This is job A"
+```
+
+内嵌流的 DAG 图如下:
+
+
+
+执行情况如下:
+
+
+
+
+
+## 参考资料
+
+1. [Azkaban Flow 2.0 Design](https://github.com/azkaban/azkaban/wiki/Azkaban-Flow-2.0-Design)
+2. [Getting started with Azkaban Flow 2.0](https://github.com/azkaban/azkaban/wiki/Getting-started-with-Azkaban-Flow-2.0)
+
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Azkaban\347\256\200\344\273\213.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Azkaban\347\256\200\344\273\213.md"
new file mode 100644
index 0000000..08b26fa
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Azkaban\347\256\200\344\273\213.md"
@@ -0,0 +1,76 @@
+# Azkaban简介
+
+
+## 一、Azkaban 介绍
+
+#### 1.1 背景
+
+一个完整的大数据分析系统,必然由很多任务单元 (如数据收集、数据清洗、数据存储、数据分析等) 组成,所有的任务单元及其之间的依赖关系组成了复杂的工作流。复杂的工作流管理涉及到很多问题:
+
+- 如何定时调度某个任务?
+- 如何在某个任务执行完成后再去执行另一个任务?
+- 如何在任务失败时候发出预警?
+- ......
+
+面对这些问题,工作流调度系统应运而生。Azkaban 就是其中之一。
+
+#### 1.2 功能
+
+Azkaban 产生于 LinkedIn,并经过多年生产环境的检验,它具备以下功能:
+
+- 兼容任何版本的 Hadoop
+- 易于使用的 Web UI
+- 可以使用简单的 Web 页面进行工作流上传
+- 支持按项目进行独立管理
+- 定时任务调度
+- 模块化和可插入
+- 身份验证和授权
+- 跟踪用户操作
+- 支持失败和成功的电子邮件提醒
+- SLA 警报和自动查杀失败任务
+- 重试失败的任务
+
+Azkaban 的设计理念是在保证功能实现的基础上兼顾易用性,其页面风格清晰明朗,下面是其 WEB UI 界面:
+
+
+
+## 二、Azkaban 和 Oozie
+
+Azkaban 和 Oozie 都是目前使用最为广泛的工作流调度程序,其主要区别如下:
+
+#### 功能对比
+
+- 两者均可以调度 Linux 命令、MapReduce、Spark、Pig、Java、Hive 等工作流任务;
+- 两者均可以定时执行工作流任务。
+
+#### 工作流定义
+
+- Azkaban 使用 Properties(Flow 1.0) 和 YAML(Flow 2.0) 文件定义工作流;
+- Oozie 使用 Hadoop 流程定义语言(hadoop process defination language,HPDL)来描述工作流,HPDL 是一种 XML 流程定义语言。
+
+#### 资源管理
+
+- Azkaban 有较严格的权限控制,如用户对工作流进行读/写/执行等操作;
+- Oozie 暂无严格的权限控制。
+
+#### 运行模式
+
++ Azkaban 3.x 提供了两种运行模式:
+ + **solo server model(单服务模式)** :元数据默认存放在内置的 H2 数据库(可以修改为 MySQL),该模式中 `webServer`(管理服务器) 和 `executorServer`(执行服务器) 运行在同一个进程中,进程名是 `AzkabanSingleServer`。该模式适用于小规模工作流的调度。
+ + **multiple-executor(分布式多服务模式)** :存放元数据的数据库为 MySQL,MySQL 应采用主从模式进行备份和容错。这种模式下 `webServer` 和 `executorServer` 在不同进程中运行,彼此之间互不影响,适合用于生产环境。
+
++ Oozie 使用 Tomcat 等 Web 容器来展示 Web 页面,默认使用 derby 存储工作流的元数据,由于 derby 过于轻量,实际使用中通常用 MySQL 代替。
+
+
+
+
+
+## 三、总结
+
+如果你的工作流不是特别复杂,推荐使用轻量级的 Azkaban,主要有以下原因:
+
++ **安装方面**:Azkaban 3.0 之前都是提供安装包的,直接解压部署即可。Azkaban 3.0 之后的版本需要编译,这个编译是基于 gradle 的,自动化程度比较高;
++ **页面设计**:所有任务的依赖关系、执行结果、执行日志都可以从界面上直观查看到;
++ **配置方面**:Azkaban Flow 1.0 基于 Properties 文件来定义工作流,这个时候的限制可能会多一点。但是在 Flow 2.0 就支持了 YARM。YARM 语法更加灵活简单,著名的微服务框架 Spring Boot 就采用的 YAML 代替了繁重的 XML。
+
+
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink_Data_Sink.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink_Data_Sink.md"
new file mode 100644
index 0000000..12e5699
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink_Data_Sink.md"
@@ -0,0 +1,268 @@
+# Flink Sink
+
+一、Data Sinks
+ 1.1 writeAsText
+ 1.2 writeAsCsv
+ 1.3 print printToErr
+ 1.4 writeUsingOutputFormat
+ 1.5 writeToSocket
+二、Streaming Connectors
+三、整合 Kafka Sink
+ 3.1 addSink
+ 3.2 创建输出主题
+ 3.3 启动消费者
+ 3.4 测试结果
+四、自定义 Sink
+ 4.1 导入依赖
+ 4.2 自定义 Sink
+ 4.3 使用自定义 Sink
+ 4.4 测试结果
+
+
+
+
+## 一、Data Sinks
+
+在使用 Flink 进行数据处理时,数据经 Data Source 流入,然后通过系列 Transformations 的转化,最终可以通过 Sink 将计算结果进行输出,Flink Data Sinks 就是用于定义数据流最终的输出位置。Flink 提供了几个较为简单的 Sink API 用于日常的开发,具体如下:
+
+### 1.1 writeAsText
+
+`writeAsText` 用于将计算结果以文本的方式并行地写入到指定文件夹下,除了路径参数是必选外,该方法还可以通过指定第二个参数来定义输出模式,它有以下两个可选值:
+
++ **WriteMode.NO_OVERWRITE**:当指定路径上不存在任何文件时,才执行写出操作;
++ **WriteMode.OVERWRITE**:不论指定路径上是否存在文件,都执行写出操作;如果原来已有文件,则进行覆盖。
+
+使用示例如下:
+
+```java
+ streamSource.writeAsText("D:\\out", FileSystem.WriteMode.OVERWRITE);
+```
+
+以上写出是以并行的方式写出到多个文件,如果想要将输出结果全部写出到一个文件,需要设置其并行度为 1:
+
+```java
+streamSource.writeAsText("D:\\out", FileSystem.WriteMode.OVERWRITE).setParallelism(1);
+```
+
+### 1.2 writeAsCsv
+
+`writeAsCsv` 用于将计算结果以 CSV 的文件格式写出到指定目录,除了路径参数是必选外,该方法还支持传入输出模式,行分隔符,和字段分隔符三个额外的参数,其方法定义如下:
+
+```java
+writeAsCsv(String path, WriteMode writeMode, String rowDelimiter, String fieldDelimiter)
+```
+
+### 1.3 print \ printToErr
+
+`print \ printToErr` 是测试当中最常用的方式,用于将计算结果以标准输出流或错误输出流的方式打印到控制台上。
+
+### 1.4 writeUsingOutputFormat
+
+采用自定义的输出格式将计算结果写出,上面介绍的 `writeAsText` 和 `writeAsCsv` 其底层调用的都是该方法,源码如下:
+
+```java
+public DataStreamSink writeAsText(String path, WriteMode writeMode) {
+ TextOutputFormat tof = new TextOutputFormat<>(new Path(path));
+ tof.setWriteMode(writeMode);
+ return writeUsingOutputFormat(tof);
+}
+```
+
+### 1.5 writeToSocket
+
+`writeToSocket` 用于将计算结果以指定的格式写出到 Socket 中,使用示例如下:
+
+```shell
+streamSource.writeToSocket("192.168.0.226", 9999, new SimpleStringSchema());
+```
+
+
+
+## 二、Streaming Connectors
+
+除了上述 API 外,Flink 中还内置了系列的 Connectors 连接器,用于将计算结果输入到常用的存储系统或者消息中间件中,具体如下:
+
+- Apache Kafka (支持 source 和 sink)
+- Apache Cassandra (sink)
+- Amazon Kinesis Streams (source/sink)
+- Elasticsearch (sink)
+- Hadoop FileSystem (sink)
+- RabbitMQ (source/sink)
+- Apache NiFi (source/sink)
+- Google PubSub (source/sink)
+
+除了内置的连接器外,你还可以通过 Apache Bahir 的连接器扩展 Flink。Apache Bahir 旨在为分布式数据分析系统 (如 Spark,Flink) 等提供功能上的扩展,当前其支持的与 Flink Sink 相关的连接器如下:
+
+- Apache ActiveMQ (source/sink)
+- Apache Flume (sink)
+- Redis (sink)
+- Akka (sink)
+
+这里接着在 Data Sources 章节介绍的整合 Kafka Source 的基础上,将 Kafka Sink 也一并进行整合,具体步骤如下。
+
+
+
+## 三、整合 Kafka Sink
+
+### 3.1 addSink
+
+Flink 提供了 addSink 方法用来调用自定义的 Sink 或者第三方的连接器,想要将计算结果写出到 Kafka,需要使用该方法来调用 Kafka 的生产者 FlinkKafkaProducer,具体代码如下:
+
+```java
+final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
+
+// 1.指定Kafka的相关配置属性
+Properties properties = new Properties();
+properties.setProperty("bootstrap.servers", "192.168.200.0:9092");
+
+// 2.接收Kafka上的数据
+DataStream stream = env
+ .addSource(new FlinkKafkaConsumer<>("flink-stream-in-topic", new SimpleStringSchema(), properties));
+
+// 3.定义计算结果到 Kafka ProducerRecord 的转换
+KafkaSerializationSchema kafkaSerializationSchema = new KafkaSerializationSchema() {
+ @Override
+ public ProducerRecord serialize(String element, @Nullable Long timestamp) {
+ return new ProducerRecord<>("flink-stream-out-topic", element.getBytes());
+ }
+};
+// 4. 定义Flink Kafka生产者
+FlinkKafkaProducer kafkaProducer = new FlinkKafkaProducer<>("flink-stream-out-topic",
+ kafkaSerializationSchema,
+ properties,
+ FlinkKafkaProducer.Semantic.AT_LEAST_ONCE, 5);
+// 5. 将接收到输入元素*2后写出到Kafka
+stream.map((MapFunction) value -> value + value).addSink(kafkaProducer);
+env.execute("Flink Streaming");
+```
+
+### 3.2 创建输出主题
+
+创建用于输出测试的主题:
+
+```shell
+bin/kafka-topics.sh --create \
+ --bootstrap-server hadoop001:9092 \
+ --replication-factor 1 \
+ --partitions 1 \
+ --topic flink-stream-out-topic
+
+# 查看所有主题
+ bin/kafka-topics.sh --list --bootstrap-server hadoop001:9092
+```
+
+### 3.3 启动消费者
+
+启动一个 Kafka 消费者,用于查看 Flink 程序的输出情况:
+
+```java
+bin/kafka-console-consumer.sh --bootstrap-server hadoop001:9092 --topic flink-stream-out-topic
+```
+
+### 3.4 测试结果
+
+在 Kafka 生产者上发送消息到 Flink 程序,观察 Flink 程序转换后的输出情况,具体如下:
+
+
+
+
+可以看到 Kafka 生成者发出的数据已经被 Flink 程序正常接收到,并经过转换后又输出到 Kafka 对应的 Topic 上。
+
+## 四、自定义 Sink
+
+除了使用内置的第三方连接器外,Flink 还支持使用自定义的 Sink 来满足多样化的输出需求。想要实现自定义的 Sink ,需要直接或者间接实现 SinkFunction 接口。通常情况下,我们都是实现其抽象类 RichSinkFunction,相比于 SinkFunction ,其提供了更多的与生命周期相关的方法。两者间的关系如下:
+
+
+
+
+这里我们以自定义一个 FlinkToMySQLSink 为例,将计算结果写出到 MySQL 数据库中,具体步骤如下:
+
+### 4.1 导入依赖
+
+首先需要导入 MySQL 相关的依赖:
+
+```xml
+
+ mysql
+ mysql-connector-java
+ 8.0.16
+
+```
+
+### 4.2 自定义 Sink
+
+继承自 RichSinkFunction,实现自定义的 Sink :
+
+```java
+public class FlinkToMySQLSink extends RichSinkFunction {
+
+ private PreparedStatement stmt;
+ private Connection conn;
+
+ @Override
+ public void open(Configuration parameters) throws Exception {
+ Class.forName("com.mysql.cj.jdbc.Driver");
+ conn = DriverManager.getConnection("jdbc:mysql://192.168.0.229:3306/employees" +
+ "?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false",
+ "root",
+ "123456");
+ String sql = "insert into emp(name, age, birthday) values(?, ?, ?)";
+ stmt = conn.prepareStatement(sql);
+ }
+
+ @Override
+ public void invoke(Employee value, Context context) throws Exception {
+ stmt.setString(1, value.getName());
+ stmt.setInt(2, value.getAge());
+ stmt.setDate(3, value.getBirthday());
+ stmt.executeUpdate();
+ }
+
+ @Override
+ public void close() throws Exception {
+ super.close();
+ if (stmt != null) {
+ stmt.close();
+ }
+ if (conn != null) {
+ conn.close();
+ }
+ }
+
+}
+```
+
+### 4.3 使用自定义 Sink
+
+想要使用自定义的 Sink,同样是需要调用 addSink 方法,具体如下:
+
+```java
+final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
+Date date = new Date(System.currentTimeMillis());
+DataStreamSource streamSource = env.fromElements(
+ new Employee("hei", 10, date),
+ new Employee("bai", 20, date),
+ new Employee("ying", 30, date));
+streamSource.addSink(new FlinkToMySQLSink());
+env.execute();
+```
+
+### 4.4 测试结果
+
+启动程序,观察数据库写入情况:
+
+
+
+
+数据库成功写入,代表自定义 Sink 整合成功。
+
+> 以上所有用例的源码见本仓库:[flink-kafka-integration]( https://github.com/heibaiying/BigData-Notes/tree/master/code/Flink/flink-kafka-integration)
+
+
+
+## 参考资料
+
+1. data-sinks: https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/datastream_api.html#data-sinks
+2. Streaming Connectors:https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/connectors/index.html
+3. Apache Kafka Connector: https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/connectors/kafka.html
+
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink_Data_Source.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink_Data_Source.md"
new file mode 100644
index 0000000..ebd70e5
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink_Data_Source.md"
@@ -0,0 +1,284 @@
+# Flink Data Source
+
+一、内置 Data Source
+ 1.1 基于文件构建
+ 1.2 基于集合构建
+ 1.3 基于 Socket 构建
+二、自定义 Data Source
+ 2.1 SourceFunction
+ 2.2 ParallelSourceFunction 和 RichParallelSourceFunction
+三、Streaming Connectors
+ 3.1 内置连接器
+ 3.2 整合 Kakfa
+ 3.3 整合测试
+
+
+
+
+## 一、内置 Data Source
+
+Flink Data Source 用于定义 Flink 程序的数据来源,Flink 官方提供了多种数据获取方法,用于帮助开发者简单快速地构建输入流,具体如下:
+
+### 1.1 基于文件构建
+
+**1. readTextFile(path)**:按照 TextInputFormat 格式读取文本文件,并将其内容以字符串的形式返回。示例如下:
+
+```java
+env.readTextFile(filePath).print();
+```
+
+**2. readFile(fileInputFormat, path)** :按照指定格式读取文件。
+
+**3. readFile(inputFormat, filePath, watchType, interval, typeInformation)**:按照指定格式周期性的读取文件。其中各个参数的含义如下:
+
++ **inputFormat**:数据流的输入格式。
++ **filePath**:文件路径,可以是本地文件系统上的路径,也可以是 HDFS 上的文件路径。
++ **watchType**:读取方式,它有两个可选值,分别是 `FileProcessingMode.PROCESS_ONCE` 和 `FileProcessingMode.PROCESS_CONTINUOUSLY`:前者表示对指定路径上的数据只读取一次,然后退出;后者表示对路径进行定期地扫描和读取。需要注意的是如果 watchType 被设置为 `PROCESS_CONTINUOUSLY`,那么当文件被修改时,其所有的内容 (包含原有的内容和新增的内容) 都将被重新处理,因此这会打破 Flink 的 *exactly-once* 语义。
++ **interval**:定期扫描的时间间隔。
++ **typeInformation**:输入流中元素的类型。
+
+使用示例如下:
+
+```java
+final String filePath = "D:\\log4j.properties";
+final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
+env.readFile(new TextInputFormat(new Path(filePath)),
+ filePath,
+ FileProcessingMode.PROCESS_ONCE,
+ 1,
+ BasicTypeInfo.STRING_TYPE_INFO).print();
+env.execute();
+```
+
+### 1.2 基于集合构建
+
+**1. fromCollection(Collection)**:基于集合构建,集合中的所有元素必须是同一类型。示例如下:
+
+```java
+env.fromCollection(Arrays.asList(1,2,3,4,5)).print();
+```
+
+**2. fromElements(T ...)**: 基于元素构建,所有元素必须是同一类型。示例如下:
+
+```java
+env.fromElements(1,2,3,4,5).print();
+```
+**3. generateSequence(from, to)**:基于给定的序列区间进行构建。示例如下:
+
+```java
+env.generateSequence(0,100);
+```
+
+**4. fromCollection(Iterator, Class)**:基于迭代器进行构建。第一个参数用于定义迭代器,第二个参数用于定义输出元素的类型。使用示例如下:
+
+```java
+env.fromCollection(new CustomIterator(), BasicTypeInfo.INT_TYPE_INFO).print();
+```
+
+其中 CustomIterator 为自定义的迭代器,这里以产生 1 到 100 区间内的数据为例,源码如下。需要注意的是自定义迭代器除了要实现 Iterator 接口外,还必须要实现序列化接口 Serializable ,否则会抛出序列化失败的异常:
+
+```java
+import java.io.Serializable;
+import java.util.Iterator;
+
+public class CustomIterator implements Iterator, Serializable {
+ private Integer i = 0;
+
+ @Override
+ public boolean hasNext() {
+ return i < 100;
+ }
+
+ @Override
+ public Integer next() {
+ i++;
+ return i;
+ }
+}
+```
+
+**5. fromParallelCollection(SplittableIterator, Class)**:方法接收两个参数,第二个参数用于定义输出元素的类型,第一个参数 SplittableIterator 是迭代器的抽象基类,它用于将原始迭代器的值拆分到多个不相交的迭代器中。
+
+### 1.3 基于 Socket 构建
+
+Flink 提供了 socketTextStream 方法用于构建基于 Socket 的数据流,socketTextStream 方法有以下四个主要参数:
+
+- **hostname**:主机名;
+- **port**:端口号,设置为 0 时,表示端口号自动分配;
+- **delimiter**:用于分隔每条记录的分隔符;
+- **maxRetry**:当 Socket 临时关闭时,程序的最大重试间隔,单位为秒。设置为 0 时表示不进行重试;设置为负值则表示一直重试。示例如下:
+
+```shell
+ env.socketTextStream("192.168.0.229", 9999, "\n", 3).print();
+```
+
+
+
+## 二、自定义 Data Source
+
+### 2.1 SourceFunction
+
+除了内置的数据源外,用户还可以使用 `addSource` 方法来添加自定义的数据源。自定义的数据源必须要实现 SourceFunction 接口,这里以产生 [0 , 1000) 区间内的数据为例,代码如下:
+
+```java
+final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
+
+env.addSource(new SourceFunction() {
+
+ private long count = 0L;
+ private volatile boolean isRunning = true;
+
+ public void run(SourceContext ctx) {
+ while (isRunning && count < 1000) {
+ // 通过collect将输入发送出去
+ ctx.collect(count);
+ count++;
+ }
+ }
+
+ public void cancel() {
+ isRunning = false;
+ }
+
+}).print();
+env.execute();
+```
+
+### 2.2 ParallelSourceFunction 和 RichParallelSourceFunction
+
+上面通过 SourceFunction 实现的数据源是不具有并行度的,即不支持在得到的 DataStream 上调用 `setParallelism(n)` 方法,此时会抛出如下的异常:
+
+```shell
+Exception in thread "main" java.lang.IllegalArgumentException: Source: 1 is not a parallel source
+```
+
+如果你想要实现具有并行度的输入流,则需要实现 ParallelSourceFunction 或 RichParallelSourceFunction 接口,其与 SourceFunction 的关系如下图:
+
+
+ParallelSourceFunction 直接继承自 ParallelSourceFunction,具有并行度的功能。RichParallelSourceFunction 则继承自 AbstractRichFunction,同时实现了 ParallelSourceFunction 接口,所以其除了具有并行度的功能外,还提供了额外的与生命周期相关的方法,如 open() ,closen() 。
+
+## 三、Streaming Connectors
+
+### 3.1 内置连接器
+
+除了自定义数据源外, Flink 还内置了多种连接器,用于满足大多数的数据收集场景。当前内置连接器的支持情况如下:
+
+- Apache Kafka (支持 source 和 sink)
+- Apache Cassandra (sink)
+- Amazon Kinesis Streams (source/sink)
+- Elasticsearch (sink)
+- Hadoop FileSystem (sink)
+- RabbitMQ (source/sink)
+- Apache NiFi (source/sink)
+- Twitter Streaming API (source)
+- Google PubSub (source/sink)
+
+除了上述的连接器外,你还可以通过 Apache Bahir 的连接器扩展 Flink。Apache Bahir 旨在为分布式数据分析系统 (如 Spark,Flink) 等提供功能上的扩展,当前其支持的与 Flink 相关的连接器如下:
+
+- Apache ActiveMQ (source/sink)
+- Apache Flume (sink)
+- Redis (sink)
+- Akka (sink)
+- Netty (source)
+
+随着 Flink 的不断发展,可以预见到其会支持越来越多类型的连接器,关于连接器的后续发展情况,可以查看其官方文档:[Streaming Connectors]( https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/connectors/index.html) 。在所有 DataSource 连接器中,使用的广泛的就是 Kafka,所以这里我们以其为例,来介绍 Connectors 的整合步骤。
+
+### 3.2 整合 Kakfa
+
+#### 1. 导入依赖
+
+整合 Kafka 时,一定要注意所使用的 Kafka 的版本,不同版本间所需的 Maven 依赖和开发时所调用的类均不相同,具体如下:
+
+| Maven 依赖 | Flink 版本 | Consumer and Producer 类的名称 | Kafka 版本 |
+| :------------------------------ | :--------- | :----------------------------------------------- | :--------- |
+| flink-connector-kafka-0.8_2.11 | 1.0.0 + | FlinkKafkaConsumer08 FlinkKafkaProducer08 | 0.8.x |
+| flink-connector-kafka-0.9_2.11 | 1.0.0 + | FlinkKafkaConsumer09 FlinkKafkaProducer09 | 0.9.x |
+| flink-connector-kafka-0.10_2.11 | 1.2.0 + | FlinkKafkaConsumer010 FlinkKafkaProducer010 | 0.10.x |
+| flink-connector-kafka-0.11_2.11 | 1.4.0 + | FlinkKafkaConsumer011 FlinkKafkaProducer011 | 0.11.x |
+| flink-connector-kafka_2.11 | 1.7.0 + | FlinkKafkaConsumer FlinkKafkaProducer | >= 1.0.0 |
+
+这里我使用的 Kafka 版本为 kafka_2.12-2.2.0,添加的依赖如下:
+
+```xml
+
+ org.apache.flink
+ flink-connector-kafka_2.11
+ 1.9.0
+
+```
+
+#### 2. 代码开发
+
+这里以最简单的场景为例,接收 Kafka 上的数据并打印,代码如下:
+
+```java
+final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
+Properties properties = new Properties();
+// 指定Kafka的连接位置
+properties.setProperty("bootstrap.servers", "hadoop001:9092");
+// 指定监听的主题,并定义Kafka字节消息到Flink对象之间的转换规则
+DataStream stream = env
+ .addSource(new FlinkKafkaConsumer<>("flink-stream-in-topic", new SimpleStringSchema(), properties));
+stream.print();
+env.execute("Flink Streaming");
+```
+
+### 3.3 整合测试
+
+#### 1. 启动 Kakfa
+
+Kafka 的运行依赖于 zookeeper,需要预先启动,可以启动 Kafka 内置的 zookeeper,也可以启动自己安装的:
+
+```shell
+# zookeeper启动命令
+bin/zkServer.sh start
+
+# 内置zookeeper启动命令
+bin/zookeeper-server-start.sh config/zookeeper.properties
+```
+
+启动单节点 kafka 用于测试:
+
+```shell
+# bin/kafka-server-start.sh config/server.properties
+```
+
+#### 2. 创建 Topic
+
+```shell
+# 创建用于测试主题
+bin/kafka-topics.sh --create \
+ --bootstrap-server hadoop001:9092 \
+ --replication-factor 1 \
+ --partitions 1 \
+ --topic flink-stream-in-topic
+
+# 查看所有主题
+ bin/kafka-topics.sh --list --bootstrap-server hadoop001:9092
+```
+
+#### 3. 启动 Producer
+
+这里 启动一个 Kafka 生产者,用于发送测试数据:
+
+```shell
+bin/kafka-console-producer.sh --broker-list hadoop001:9092 --topic flink-stream-in-topic
+```
+
+#### 4. 测试结果
+
+在 Producer 上输入任意测试数据,之后观察程序控制台的输出:
+
+
+程序控制台的输出如下:
+
+
+可以看到已经成功接收并打印出相关的数据。
+
+
+
+## 参考资料
+
+1. data-sources:https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/datastream_api.html#data-sources
+2. Streaming Connectors:https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/connectors/index.html
+3. Apache Kafka Connector: https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/connectors/kafka.html
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink_Data_Transformation.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink_Data_Transformation.md"
new file mode 100644
index 0000000..23c58bf
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink_Data_Transformation.md"
@@ -0,0 +1,311 @@
+# Flink Transformation
+
+一、Transformations 分类
+二、DataStream Transformations
+ 2.1 Map [DataStream → DataStream]
+ 2.2 FlatMap [DataStream → DataStream]
+ 2.3 Filter [DataStream → DataStream]
+ 2.4 KeyBy 和 Reduce
+ 2.5 Aggregations [KeyedStream → DataStream]
+ 2.6 Union [DataStream* → DataStream]
+ 2.7 Connect [DataStream,DataStream → ConnectedStreams]
+ 2.8 Split 和 Select
+ 2.9 project [DataStream → DataStream]
+三、物理分区
+ 3.1 Random partitioning [DataStream → DataStream]
+ 3.2 Rebalancing [DataStream → DataStream]
+ 3.3 Rescaling [DataStream → DataStream]
+ 3.4 Broadcasting [DataStream → DataStream]
+ 3.5 Custom partitioning [DataStream → DataStream]
+四、任务链和资源组
+ 4.1 startNewChain
+ 4.2 disableChaining
+ 4.3 slotSharingGroup
+
+
+
+
+## 一、Transformations 分类
+
+Flink 的 Transformations 操作主要用于将一个和多个 DataStream 按需转换成新的 DataStream。它主要分为以下三类:
+
+- **DataStream Transformations**:进行数据流相关转换操作;
+- **Physical partitioning**:物理分区。Flink 提供的底层 API ,允许用户定义数据的分区规则;
+- **Task chaining and resource groups**:任务链和资源组。允许用户进行任务链和资源组的细粒度的控制。
+
+以下分别对其主要 API 进行介绍:
+
+## 二、DataStream Transformations
+
+### 2.1 Map [DataStream → DataStream]
+
+对一个 DataStream 中的每个元素都执行特定的转换操作:
+
+```java
+DataStream integerDataStream = env.fromElements(1, 2, 3, 4, 5);
+integerDataStream.map((MapFunction) value -> value * 2).print();
+// 输出 2,4,6,8,10
+```
+
+### 2.2 FlatMap [DataStream → DataStream]
+
+FlatMap 与 Map 类似,但是 FlatMap 中的一个输入元素可以被映射成一个或者多个输出元素,示例如下:
+
+```java
+String string01 = "one one one two two";
+String string02 = "third third third four";
+DataStream stringDataStream = env.fromElements(string01, string02);
+stringDataStream.flatMap(new FlatMapFunction() {
+ @Override
+ public void flatMap(String value, Collector out) throws Exception {
+ for (String s : value.split(" ")) {
+ out.collect(s);
+ }
+ }
+}).print();
+// 输出每一个独立的单词,为节省排版,这里去掉换行,后文亦同
+one one one two two third third third four
+```
+
+### 2.3 Filter [DataStream → DataStream]
+
+用于过滤符合条件的数据:
+
+```java
+env.fromElements(1, 2, 3, 4, 5).filter(x -> x > 3).print();
+```
+
+### 2.4 KeyBy 和 Reduce
+
+- **KeyBy [DataStream → KeyedStream]** :用于将相同 Key 值的数据分到相同的分区中;
+- **Reduce [KeyedStream → DataStream]** :用于对数据执行归约计算。
+
+如下例子将数据按照 key 值分区后,滚动进行求和计算:
+
+```java
+DataStream> tuple2DataStream = env.fromElements(new Tuple2<>("a", 1),
+ new Tuple2<>("a", 2),
+ new Tuple2<>("b", 3),
+ new Tuple2<>("b", 5));
+KeyedStream, Tuple> keyedStream = tuple2DataStream.keyBy(0);
+keyedStream.reduce((ReduceFunction>) (value1, value2) ->
+ new Tuple2<>(value1.f0, value1.f1 + value2.f1)).print();
+
+// 持续进行求和计算,输出:
+(a,1)
+(a,3)
+(b,3)
+(b,8)
+```
+
+KeyBy 操作存在以下两个限制:
+
+- KeyBy 操作用于用户自定义的 POJOs 类型时,该自定义类型必须重写 hashCode 方法;
+- KeyBy 操作不能用于数组类型。
+
+### 2.5 Aggregations [KeyedStream → DataStream]
+
+Aggregations 是官方提供的聚合算子,封装了常用的聚合操作,如上利用 Reduce 进行求和的操作也可以利用 Aggregations 中的 sum 算子重写为下面的形式:
+
+```java
+tuple2DataStream.keyBy(0).sum(1).print();
+```
+
+除了 sum 外,Flink 还提供了 min , max , minBy,maxBy 等常用聚合算子:
+
+```java
+// 滚动计算指定key的最小值,可以通过index或者fieldName来指定key
+keyedStream.min(0);
+keyedStream.min("key");
+// 滚动计算指定key的最大值
+keyedStream.max(0);
+keyedStream.max("key");
+// 滚动计算指定key的最小值,并返回其对应的元素
+keyedStream.minBy(0);
+keyedStream.minBy("key");
+// 滚动计算指定key的最大值,并返回其对应的元素
+keyedStream.maxBy(0);
+keyedStream.maxBy("key");
+
+```
+
+### 2.6 Union [DataStream* → DataStream]
+
+用于连接两个或者多个元素类型相同的 DataStream 。当然一个 DataStream 也可以与其本生进行连接,此时该 DataStream 中的每个元素都会被获取两次:
+
+```shell
+DataStreamSource> streamSource01 = env.fromElements(new Tuple2<>("a", 1),
+ new Tuple2<>("a", 2));
+DataStreamSource> streamSource02 = env.fromElements(new Tuple2<>("b", 1),
+ new Tuple2<>("b", 2));
+streamSource01.union(streamSource02);
+streamSource01.union(streamSource01,streamSource02);
+```
+
+### 2.7 Connect [DataStream,DataStream → ConnectedStreams]
+
+Connect 操作用于连接两个或者多个类型不同的 DataStream ,其返回的类型是 ConnectedStreams ,此时被连接的多个 DataStreams 可以共享彼此之间的数据状态。但是需要注意的是由于不同 DataStream 之间的数据类型是不同的,如果想要进行后续的计算操作,还需要通过 CoMap 或 CoFlatMap 将 ConnectedStreams 转换回 DataStream:
+
+```java
+DataStreamSource> streamSource01 = env.fromElements(new Tuple2<>("a", 3),
+ new Tuple2<>("b", 5));
+DataStreamSource streamSource02 = env.fromElements(2, 3, 9);
+// 使用connect进行连接
+ConnectedStreams, Integer> connect = streamSource01.connect(streamSource02);
+connect.map(new CoMapFunction, Integer, Integer>() {
+ @Override
+ public Integer map1(Tuple2 value) throws Exception {
+ return value.f1;
+ }
+
+ @Override
+ public Integer map2(Integer value) throws Exception {
+ return value;
+ }
+}).map(x -> x * 100).print();
+
+// 输出:
+300 500 200 900 300
+```
+
+### 2.8 Split 和 Select
+
+- **Split [DataStream → SplitStream]**:用于将一个 DataStream 按照指定规则进行拆分为多个 DataStream,需要注意的是这里进行的是逻辑拆分,即 Split 只是将数据贴上不同的类型标签,但最终返回的仍然只是一个 SplitStream;
+- **Select [SplitStream → DataStream]**:想要从逻辑拆分的 SplitStream 中获取真实的不同类型的 DataStream,需要使用 Select 算子,示例如下:
+
+```java
+DataStreamSource streamSource = env.fromElements(1, 2, 3, 4, 5, 6, 7, 8);
+// 标记
+SplitStream split = streamSource.split(new OutputSelector() {
+ @Override
+ public Iterable select(Integer value) {
+ List output = new ArrayList();
+ output.add(value % 2 == 0 ? "even" : "odd");
+ return output;
+ }
+});
+// 获取偶数数据集
+split.select("even").print();
+// 输出 2,4,6,8
+```
+
+### 2.9 project [DataStream → DataStream]
+
+project 主要用于获取 tuples 中的指定字段集,示例如下:
+
+```java
+DataStreamSource> streamSource = env.fromElements(
+ new Tuple3<>("li", 22, "2018-09-23"),
+ new Tuple3<>("ming", 33, "2020-09-23"));
+streamSource.project(0,2).print();
+
+// 输出
+(li,2018-09-23)
+(ming,2020-09-23)
+```
+
+## 三、物理分区
+
+物理分区 (Physical partitioning) 是 Flink 提供的底层的 API,允许用户采用内置的分区规则或者自定义的分区规则来对数据进行分区,从而避免数据在某些分区上过于倾斜,常用的分区规则如下:
+
+### 3.1 Random partitioning [DataStream → DataStream]
+
+随机分区 (Random partitioning) 用于随机的将数据分布到所有下游分区中,通过 shuffle 方法来进行实现:
+
+```java
+dataStream.shuffle();
+```
+
+### 3.2 Rebalancing [DataStream → DataStream]
+
+Rebalancing 采用轮询的方式将数据进行分区,其适合于存在数据倾斜的场景下,通过 rebalance 方法进行实现:
+
+```java
+dataStream.rebalance();
+```
+
+### 3.3 Rescaling [DataStream → DataStream]
+
+当采用 Rebalancing 进行分区平衡时,其实现的是全局性的负载均衡,数据会通过网络传输到其他节点上并完成分区数据的均衡。 而 Rescaling 则是低配版本的 rebalance,它不需要额外的网络开销,它只会对上下游的算子之间进行重新均衡,通过 rescale 方法进行实现:
+
+```java
+dataStream.rescale();
+```
+
+ReScale 这个单词具有重新缩放的意义,其对应的操作也是如此,具体如下:如果上游 operation 并行度为 2,而下游的 operation 并行度为 6,则其中 1 个上游的 operation 会将元素分发到 3 个下游 operation,另 1 个上游 operation 则会将元素分发到另外 3 个下游 operation。反之亦然,如果上游的 operation 并行度为 6,而下游 operation 并行度为 2,则其中 3 个上游 operation 会将元素分发到 1 个下游 operation,另 3 个上游 operation 会将元素分发到另外 1 个下游operation:
+
+
+
+
+### 3.4 Broadcasting [DataStream → DataStream]
+
+将数据分发到所有分区上。通常用于小数据集与大数据集进行关联的情况下,此时可以将小数据集广播到所有分区上,避免频繁的跨分区关联,通过 broadcast 方法进行实现:
+
+```java
+dataStream.broadcast();
+```
+
+### 3.5 Custom partitioning [DataStream → DataStream]
+
+Flink 运行用户采用自定义的分区规则来实现分区,此时需要通过实现 Partitioner 接口来自定义分区规则,并指定对应的分区键,示例如下:
+
+```java
+ DataStreamSource> streamSource = env.fromElements(new Tuple2<>("Hadoop", 1),
+ new Tuple2<>("Spark", 1),
+ new Tuple2<>("Flink-streaming", 2),
+ new Tuple2<>("Flink-batch", 4),
+ new Tuple2<>("Storm", 4),
+ new Tuple2<>("HBase", 3));
+streamSource.partitionCustom(new Partitioner() {
+ @Override
+ public int partition(String key, int numPartitions) {
+ // 将第一个字段包含flink的Tuple2分配到同一个分区
+ return key.toLowerCase().contains("flink") ? 0 : 1;
+ }
+}, 0).print();
+
+
+// 输出如下:
+1> (Flink-streaming,2)
+1> (Flink-batch,4)
+2> (Hadoop,1)
+2> (Spark,1)
+2> (Storm,4)
+2> (HBase,3)
+```
+
+
+
+## 四、任务链和资源组
+
+任务链和资源组 ( Task chaining and resource groups ) 也是 Flink 提供的底层 API,用于控制任务链和资源分配。默认情况下,如果操作允许 (例如相邻的两次 map 操作) ,则 Flink 会尝试将它们在同一个线程内进行,从而可以获取更好的性能。但是 Flink 也允许用户自己来控制这些行为,这就是任务链和资源组 API:
+
+### 4.1 startNewChain
+
+startNewChain 用于基于当前 operation 开启一个新的任务链。如下所示,基于第一个 map 开启一个新的任务链,此时前一个 map 和 后一个 map 将处于同一个新的任务链中,但它们与 filter 操作则分别处于不同的任务链中:
+
+```java
+someStream.filter(...).map(...).startNewChain().map(...);
+```
+
+### 4.2 disableChaining
+
+ disableChaining 操作用于禁止将其他操作与当前操作放置于同一个任务链中,示例如下:
+
+```java
+someStream.map(...).disableChaining();
+```
+
+### 4.3 slotSharingGroup
+
+slot 是任务管理器 (TaskManager) 所拥有资源的固定子集,每个操作 (operation) 的子任务 (sub task) 都需要获取 slot 来执行计算,但每个操作所需要资源的大小都是不相同的,为了更好地利用资源,Flink 允许不同操作的子任务被部署到同一 slot 中。slotSharingGroup 用于设置操作的 slot 共享组 (slot sharing group) ,Flink 会将具有相同 slot 共享组的操作放到同一个 slot 中 。示例如下:
+
+```java
+someStream.filter(...).slotSharingGroup("slotSharingGroupName");
+```
+
+
+
+## 参考资料
+
+Flink Operators: https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/stream/operators/
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink_Windows.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink_Windows.md"
new file mode 100644
index 0000000..4fb9cb8
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink_Windows.md"
@@ -0,0 +1,128 @@
+# Flink Windows
+
+一、窗口概念
+二、Time Windows
+ 2.1 Tumbling Windows
+ 2.2 Sliding Windows
+ 2.3 Session Windows
+ 2.4 Global Windows
+三、Count Windows
+
+
+
+
+## 一、窗口概念
+
+在大多数场景下,我们需要统计的数据流都是无界的,因此我们无法等待整个数据流终止后才进行统计。通常情况下,我们只需要对某个时间范围或者数量范围内的数据进行统计分析:如每隔五分钟统计一次过去一小时内所有商品的点击量;或者每发生1000次点击后,都去统计一下每个商品点击率的占比。在 Flink 中,我们使用窗口 (Window) 来实现这类功能。按照统计维度的不同,Flink 中的窗口可以分为 时间窗口 (Time Windows) 和 计数窗口 (Count Windows) 。
+
+## 二、Time Windows
+
+Time Windows 用于以时间为维度来进行数据聚合,具体分为以下四类:
+
+### 2.1 Tumbling Windows
+
+滚动窗口 (Tumbling Windows) 是指彼此之间没有重叠的窗口。例如:每隔1小时统计过去1小时内的商品点击量,那么 1 天就只能分为 24 个窗口,每个窗口彼此之间是不存在重叠的,具体如下:
+
+
+
+
+这里我们以词频统计为例,给出一个具体的用例,代码如下:
+
+```java
+final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
+// 接收socket上的数据输入
+DataStreamSource streamSource = env.socketTextStream("hadoop001", 9999, "\n", 3);
+streamSource.flatMap(new FlatMapFunction>() {
+ @Override
+ public void flatMap(String value, Collector> out) throws Exception {
+ String[] words = value.split("\t");
+ for (String word : words) {
+ out.collect(new Tuple2<>(word, 1L));
+ }
+ }
+}).keyBy(0).timeWindow(Time.seconds(3)).sum(1).print(); //每隔3秒统计一次每个单词出现的数量
+env.execute("Flink Streaming");
+```
+
+测试结果如下:
+
+
+
+
+
+
+### 2.2 Sliding Windows
+
+滑动窗口用于滚动进行聚合分析,例如:每隔 6 分钟统计一次过去一小时内所有商品的点击量,那么统计窗口彼此之间就是存在重叠的,即 1天可以分为 240 个窗口。图示如下:
+
+
+
+
+可以看到 window 1 - 4 这四个窗口彼此之间都存在着时间相等的重叠部分。想要实现滑动窗口,只需要在使用 timeWindow 方法时额外传递第二个参数作为滚动时间即可,具体如下:
+
+```java
+// 每隔3秒统计一次过去1分钟内的数据
+timeWindow(Time.minutes(1),Time.seconds(3))
+```
+
+### 2.3 Session Windows
+
+当用户在进行持续浏览时,可能每时每刻都会有点击数据,例如在活动区间内,用户可能频繁的将某类商品加入和移除购物车,而你只想知道用户本次浏览最终的购物车情况,此时就可以在用户持有的会话结束后再进行统计。想要实现这类统计,可以通过 Session Windows 来进行实现。
+
+
+
+
+具体的实现代码如下:
+
+```java
+// 以处理时间为衡量标准,如果10秒内没有任何数据输入,就认为会话已经关闭,此时触发统计
+window(ProcessingTimeSessionWindows.withGap(Time.seconds(10)))
+// 以事件时间为衡量标准
+window(EventTimeSessionWindows.withGap(Time.seconds(10)))
+```
+
+### 2.4 Global Windows
+
+最后一个窗口是全局窗口, 全局窗口会将所有 key 相同的元素分配到同一个窗口中,其通常配合触发器 (trigger) 进行使用。如果没有相应触发器,则计算将不会被执行。
+
+
+
+
+这里继续以上面词频统计的案例为例,示例代码如下:
+
+```java
+// 当单词累计出现的次数每达到10次时,则触发计算,计算整个窗口内该单词出现的总数
+window(GlobalWindows.create()).trigger(CountTrigger.of(10)).sum(1).print();
+```
+
+## 三、Count Windows
+
+Count Windows 用于以数量为维度来进行数据聚合,同样也分为滚动窗口和滑动窗口,实现方式也和时间窗口完全一致,只是调用的 API 不同,具体如下:
+
+```java
+// 滚动计数窗口,每1000次点击则计算一次
+countWindow(1000)
+// 滑动计数窗口,每10次点击发生后,则计算过去1000次点击的情况
+countWindow(1000,10)
+```
+
+实际上计数窗口内部就是调用的我们上一部分介绍的全局窗口来实现的,其源码如下:
+
+```java
+public WindowedStream countWindow(long size) {
+ return window(GlobalWindows.create()).trigger(PurgingTrigger.of(CountTrigger.of(size)));
+}
+
+
+public WindowedStream countWindow(long size, long slide) {
+ return window(GlobalWindows.create())
+ .evictor(CountEvictor.of(size))
+ .trigger(CountTrigger.of(slide));
+}
+```
+
+
+
+## 参考资料
+
+Flink Windows: https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/stream/operators/windows.html
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink\345\274\200\345\217\221\347\216\257\345\242\203\346\220\255\345\273\272.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink\345\274\200\345\217\221\347\216\257\345\242\203\346\220\255\345\273\272.md"
new file mode 100644
index 0000000..58185a9
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink\345\274\200\345\217\221\347\216\257\345\242\203\346\220\255\345\273\272.md"
@@ -0,0 +1,304 @@
+# Flink 开发环境搭建
+
+
+一、安装 Scala 插件
+二、Flink 项目初始化
+ 2.1 使用官方脚本构建
+ 2.2 使用 IDEA 构建
+三、项目结构
+ 3.1 项目结构
+ 3.2 主要依赖
+四、词频统计案例
+ 4.1 批处理示例
+ 4.2 流处理示例
+五、使用 Scala Shell
+
+
+
+
+## 一、安装 Scala 插件
+
+Flink 分别提供了基于 Java 语言和 Scala 语言的 API ,如果想要使用 Scala 语言来开发 Flink 程序,可以通过在 IDEA 中安装 Scala 插件来提供语法提示,代码高亮等功能。打开 IDEA , 依次点击 `File => settings => plugins` 打开插件安装页面,搜索 Scala 插件并进行安装,安装完成后,重启 IDEA 即可生效。
+
+
+
+## 二、Flink 项目初始化
+
+### 2.1 使用官方脚本构建
+
+Flink 官方支持使用 Maven 和 Gradle 两种构建工具来构建基于 Java 语言的 Flink 项目;支持使用 SBT 和 Maven 两种构建工具来构建基于 Scala 语言的 Flink 项目。 这里以 Maven 为例进行说明,因为其可以同时支持 Java 语言和 Scala 语言项目的构建。需要注意的是 Flink 1.9 只支持 Maven 3.0.4 以上的版本,Maven 安装完成后,可以通过以下两种方式来构建项目:
+
+**1. 直接基于 Maven Archetype 构建**
+
+直接使用下面的 mvn 语句来进行构建,然后根据交互信息的提示,依次输入 groupId , artifactId 以及包名等信息后等待初始化的完成:
+
+```bash
+$ mvn archetype:generate \
+ -DarchetypeGroupId=org.apache.flink \
+ -DarchetypeArtifactId=flink-quickstart-java \
+ -DarchetypeVersion=1.9.0
+```
+
+> 注:如果想要创建基于 Scala 语言的项目,只需要将 flink-quickstart-java 换成 flink-quickstart-scala 即可,后文亦同。
+
+**2. 使用官方脚本快速构建**
+
+为了更方便的初始化项目,官方提供了快速构建脚本,可以直接通过以下命令来进行调用:
+
+```shell
+$ curl https://flink.apache.org/q/quickstart.sh | bash -s 1.9.0
+```
+
+该方式其实也是通过执行 maven archetype 命令来进行初始化,其脚本内容如下:
+
+```shell
+PACKAGE=quickstart
+
+mvn archetype:generate \
+ -DarchetypeGroupId=org.apache.flink \
+ -DarchetypeArtifactId=flink-quickstart-java \
+ -DarchetypeVersion=${1:-1.8.0} \
+ -DgroupId=org.myorg.quickstart \
+ -DartifactId=$PACKAGE \
+ -Dversion=0.1 \
+ -Dpackage=org.myorg.quickstart \
+ -DinteractiveMode=false
+```
+
+可以看到相比于第一种方式,该种方式只是直接指定好了 groupId ,artifactId ,version 等信息而已。
+
+### 2.2 使用 IDEA 构建
+
+如果你使用的是开发工具是 IDEA ,可以直接在项目创建页面选择 Maven Flink Archetype 进行项目初始化:
+
+
+
+如果你的 IDEA 没有上述 Archetype, 可以通过点击右上角的 `ADD ARCHETYPE` ,来进行添加,依次填入所需信息,这些信息都可以从上述的 `archetype:generate ` 语句中获取。点击 `OK` 保存后,该 Archetype 就会一直存在于你的 IDEA 中,之后每次创建项目时,只需要直接选择该 Archetype 即可:
+
+
+
+选中 Flink Archetype ,然后点击 `NEXT` 按钮,之后的所有步骤都和正常的 Maven 工程相同。
+
+## 三、项目结构
+
+### 3.1 项目结构
+
+创建完成后的自动生成的项目结构如下:
+
+
+
+其中 BatchJob 为批处理的样例代码,源码如下:
+
+```scala
+import org.apache.flink.api.scala._
+
+object BatchJob {
+ def main(args: Array[String]) {
+ val env = ExecutionEnvironment.getExecutionEnvironment
+ ....
+ env.execute("Flink Batch Scala API Skeleton")
+ }
+}
+```
+
+getExecutionEnvironment 代表获取批处理的执行环境,如果是本地运行则获取到的就是本地的执行环境;如果在集群上运行,得到的就是集群的执行环境。如果想要获取流处理的执行环境,则只需要将 `ExecutionEnvironment` 替换为 `StreamExecutionEnvironment`, 对应的代码样例在 StreamingJob 中:
+
+```scala
+import org.apache.flink.streaming.api.scala._
+
+object StreamingJob {
+ def main(args: Array[String]) {
+ val env = StreamExecutionEnvironment.getExecutionEnvironment
+ ...
+ env.execute("Flink Streaming Scala API Skeleton")
+ }
+}
+
+```
+
+需要注意的是对于流处理项目 `env.execute()` 这句代码是必须的,否则流处理程序就不会被执行,但是对于批处理项目则是可选的。
+
+### 3.2 主要依赖
+
+基于 Maven 骨架创建的项目主要提供了以下核心依赖:其中 `flink-scala` 用于支持开发批处理程序 ;`flink-streaming-scala` 用于支持开发流处理程序 ;`scala-library` 用于提供 Scala 语言所需要的类库。如果在使用 Maven 骨架创建时选择的是 Java 语言,则默认提供的则是 `flink-java` 和 `flink-streaming-java` 依赖。
+
+```xml
+
+
+
+ org.apache.flink
+ flink-scala_${scala.binary.version}
+ ${flink.version}
+ provided
+
+
+ org.apache.flink
+ flink-streaming-scala_${scala.binary.version}
+ ${flink.version}
+ provided
+
+
+
+
+ org.scala-lang
+ scala-library
+ ${scala.version}
+ provided
+
+```
+
+需要特别注意的以上依赖的 `scope` 标签全部被标识为 provided ,这意味着这些依赖都不会被打入最终的 JAR 包。因为 Flink 的安装包中已经提供了这些依赖,位于其 lib 目录下,名为 `flink-dist_*.jar` ,它包含了 Flink 的所有核心类和依赖:
+
+
+
+ `scope` 标签被标识为 provided 会导致你在 IDEA 中启动项目时会抛出 ClassNotFoundException 异常。基于这个原因,在使用 IDEA 创建项目时还自动生成了以下 profile 配置:
+
+```xml
+
+
+
+
+
+ add-dependencies-for-IDEA
+
+
+
+ idea.version
+
+
+
+
+
+ org.apache.flink
+ flink-scala_${scala.binary.version}
+ ${flink.version}
+ compile
+
+
+ org.apache.flink
+ flink-streaming-scala_${scala.binary.version}
+ ${flink.version}
+ compile
+
+
+ org.scala-lang
+ scala-library
+ ${scala.version}
+ compile
+
+
+
+
+```
+
+在 id 为 `add-dependencies-for-IDEA` 的 profile 中,所有的核心依赖都被标识为 compile,此时你可以无需改动任何代码,只需要在 IDEA 的 Maven 面板中勾选该 profile,即可直接在 IDEA 中运行 Flink 项目:
+
+
+
+## 四、词频统计案例
+
+项目创建完成后,可以先书写一个简单的词频统计的案例来尝试运行 Flink 项目,以下以 Scala 语言为例,分别介绍流处理程序和批处理程序的编程示例:
+
+### 4.1 批处理示例
+
+```scala
+import org.apache.flink.api.scala._
+
+object WordCountBatch {
+
+ def main(args: Array[String]): Unit = {
+ val benv = ExecutionEnvironment.getExecutionEnvironment
+ val dataSet = benv.readTextFile("D:\\wordcount.txt")
+ dataSet.flatMap { _.toLowerCase.split(",")}
+ .filter (_.nonEmpty)
+ .map { (_, 1) }
+ .groupBy(0)
+ .sum(1)
+ .print()
+ }
+}
+```
+
+其中 `wordcount.txt` 中的内容如下:
+
+```shell
+a,a,a,a,a
+b,b,b
+c,c
+d,d
+```
+
+本机不需要配置其他任何的 Flink 环境,直接运行 Main 方法即可,结果如下:
+
+
+
+### 4.2 流处理示例
+
+```scala
+import org.apache.flink.streaming.api.scala._
+import org.apache.flink.streaming.api.windowing.time.Time
+
+object WordCountStreaming {
+
+ def main(args: Array[String]): Unit = {
+
+ val senv = StreamExecutionEnvironment.getExecutionEnvironment
+
+ val dataStream: DataStream[String] = senv.socketTextStream("192.168.0.229", 9999, '\n')
+ dataStream.flatMap { line => line.toLowerCase.split(",") }
+ .filter(_.nonEmpty)
+ .map { word => (word, 1) }
+ .keyBy(0)
+ .timeWindow(Time.seconds(3))
+ .sum(1)
+ .print()
+ senv.execute("Streaming WordCount")
+ }
+}
+```
+
+这里以监听指定端口号上的内容为例,使用以下命令来开启端口服务:
+
+```shell
+nc -lk 9999
+```
+
+之后输入测试数据即可观察到流处理程序的处理情况。
+
+## 五、使用 Scala Shell
+
+对于日常的 Demo 项目,如果你不想频繁地启动 IDEA 来观察测试结果,可以像 Spark 一样,直接使用 Scala Shell 来运行程序,这对于日常的学习来说,效果更加直观,也更省时。Flink 安装包的下载地址如下:
+
+```shell
+https://flink.apache.org/downloads.html
+```
+
+Flink 大多数版本都提供有 Scala 2.11 和 Scala 2.12 两个版本的安装包可供下载:
+
+
+
+下载完成后进行解压即可,Scala Shell 位于安装目录的 bin 目录下,直接使用以下命令即可以本地模式启动:
+
+```shell
+./start-scala-shell.sh local
+```
+
+命令行启动完成后,其已经提供了批处理 (benv 和 btenv)和流处理(senv 和 stenv)的运行环境,可以直接运行 Scala Flink 程序,示例如下:
+
+
+
+最后解释一个常见的异常:这里我使用的 Flink 版本为 1.9.1,启动时会抛出如下异常。这里因为按照官方的说明,目前所有 Scala 2.12 版本的安装包暂时都不支持 Scala Shell,所以如果想要使用 Scala Shell,只能选择 Scala 2.11 版本的安装包。
+
+```shell
+[root@hadoop001 bin]# ./start-scala-shell.sh local
+错误: 找不到或无法加载主类 org.apache.flink.api.scala.FlinkShell
+```
+
+
+
+
+
+
+
+
+
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink\346\240\270\345\277\203\346\246\202\345\277\265\347\273\274\350\277\260.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink\346\240\270\345\277\203\346\246\202\345\277\265\347\273\274\350\277\260.md"
new file mode 100644
index 0000000..dd94a54
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink\346\240\270\345\277\203\346\246\202\345\277\265\347\273\274\350\277\260.md"
@@ -0,0 +1,173 @@
+# Flink 核心概念综述
+
+一、Flink 简介
+二、Flink 核心架构
+ 2.1 API & Libraries 层
+ 2.2 Runtime 核心层
+ 2.3 物理部署层
+三、Flink 分层 API
+ 3.1 SQL & Table API
+ 3.2 DataStream & DataSet API
+ 3.3 Stateful Stream Processing
+四、Flink 集群架构
+ 4.1 核心组件
+ 4.2 Task & SubTask
+ 4.3 资源管理
+ 4.4 组件通讯
+五、Flink 的优点
+
+
+
+## 一、Flink 简介
+
+Apache Flink 诞生于柏林工业大学的一个研究性项目,原名 StratoSphere 。2014 年,由 StratoSphere 项目孵化出 Flink,并于同年捐赠 Apache,之后成为 Apache 的顶级项目。2019 年 1 年,阿里巴巴收购了 Flink 的母公司 Data Artisans,并宣布开源内部的 Blink,Blink 是阿里巴巴基于 Flink 优化后的版本,增加了大量的新功能,并在性能和稳定性上进行了各种优化,经历过阿里内部多种复杂业务的挑战和检验。同时阿里巴巴也表示会逐步将这些新功能和特性 Merge 回社区版本的 Flink 中,因此 Flink 成为目前最为火热的大数据处理框架。
+
+简单来说,Flink 是一个分布式的流处理框架,它能够对有界和无界的数据流进行高效的处理。Flink 的核心是流处理,当然它也能支持批处理,Flink 将批处理看成是流处理的一种特殊情况,即数据流是有明确界限的。这和 Spark Streaming 的思想是完全相反的,Spark Streaming 的核心是批处理,它将流处理看成是批处理的一种特殊情况, 即把数据流进行极小粒度的拆分,拆分为多个微批处理。
+
+Flink 有界数据流和无界数据流:
+
+
+
+
+
+
+Spark Streaming 数据流的拆分:
+
+
+
+
+
+
+## 二、Flink 核心架构
+
+Flink 采用分层的架构设计,从而保证各层在功能和职责上的清晰。如下图所示,由上而下分别是 API & Libraries 层、Runtime 核心层以及物理部署层:
+
+
+
+
+
+
+### 2.1 API & Libraries 层
+
+这一层主要提供了编程 API 和 顶层类库:
+
++ 编程 API : 用于进行流处理的 DataStream API 和用于进行批处理的 DataSet API;
++ 顶层类库:包括用于复杂事件处理的 CEP 库;用于结构化数据查询的 SQL & Table 库,以及基于批处理的机器学习库 FlinkML 和 图形处理库 Gelly。
+
+### 2.2 Runtime 核心层
+
+这一层是 Flink 分布式计算框架的核心实现层,包括作业转换,任务调度,资源分配,任务执行等功能,基于这一层的实现,可以在流式引擎下同时运行流处理程序和批处理程序。
+
+### 2.3 物理部署层
+
+Flink 的物理部署层,用于支持在不同平台上部署运行 Flink 应用。
+
+## 三、Flink 分层 API
+
+在上面介绍的 API & Libraries 这一层,Flink 又进行了更为具体的划分。具体如下:
+
+
+
+
+
+
+按照如上的层次结构,API 的一致性由下至上依次递增,接口的表现能力由下至上依次递减,各层的核心功能如下:
+
+### 3.1 SQL & Table API
+
+SQL & Table API 同时适用于批处理和流处理,这意味着你可以对有界数据流和无界数据流以相同的语义进行查询,并产生相同的结果。除了基本查询外, 它还支持自定义的标量函数,聚合函数以及表值函数,可以满足多样化的查询需求。
+
+### 3.2 DataStream & DataSet API
+
+DataStream & DataSet API 是 Flink 数据处理的核心 API,支持使用 Java 语言或 Scala 语言进行调用,提供了数据读取,数据转换和数据输出等一系列常用操作的封装。
+
+### 3.3 Stateful Stream Processing
+
+Stateful Stream Processing 是最低级别的抽象,它通过 Process Function 函数内嵌到 DataStream API 中。 Process Function 是 Flink 提供的最底层 API,具有最大的灵活性,允许开发者对于时间和状态进行细粒度的控制。
+
+## 四、Flink 集群架构
+
+### 4.1 核心组件
+
+按照上面的介绍,Flink 核心架构的第二层是 Runtime 层, 该层采用标准的 Master - Slave 结构, 其中,Master 部分又包含了三个核心组件:Dispatcher、ResourceManager 和 JobManager,而 Slave 则主要是 TaskManager 进程。它们的功能分别如下:
+
+- **JobManagers** (也称为 *masters*) :JobManagers 接收由 Dispatcher 传递过来的执行程序,该执行程序包含了作业图 (JobGraph),逻辑数据流图 (logical dataflow graph) 及其所有的 classes 文件以及第三方类库 (libraries) 等等 。紧接着 JobManagers 会将 JobGraph 转换为执行图 (ExecutionGraph),然后向 ResourceManager 申请资源来执行该任务,一旦申请到资源,就将执行图分发给对应的 TaskManagers 。因此每个作业 (Job) 至少有一个 JobManager;高可用部署下可以有多个 JobManagers,其中一个作为 *leader*,其余的则处于 *standby* 状态。
+- **TaskManagers** (也称为 *workers*) : TaskManagers 负责实际的子任务 (subtasks) 的执行,每个 TaskManagers 都拥有一定数量的 slots。Slot 是一组固定大小的资源的合集 (如计算能力,存储空间)。TaskManagers 启动后,会将其所拥有的 slots 注册到 ResourceManager 上,由 ResourceManager 进行统一管理。
+- **Dispatcher**:负责接收客户端提交的执行程序,并传递给 JobManager 。除此之外,它还提供了一个 WEB UI 界面,用于监控作业的执行情况。
+- **ResourceManager** :负责管理 slots 并协调集群资源。ResourceManager 接收来自 JobManager 的资源请求,并将存在空闲 slots 的 TaskManagers 分配给 JobManager 执行任务。Flink 基于不同的部署平台,如 YARN , Mesos,K8s 等提供了不同的资源管理器,当 TaskManagers 没有足够的 slots 来执行任务时,它会向第三方平台发起会话来请求额外的资源。
+
+
+
+
+### 4.2 Task & SubTask
+
+上面我们提到:TaskManagers 实际执行的是 SubTask,而不是 Task,这里解释一下两者的区别:
+
+在执行分布式计算时,Flink 将可以链接的操作 (operators) 链接到一起,这就是 Task。之所以这样做, 是为了减少线程间切换和缓冲而导致的开销,在降低延迟的同时可以提高整体的吞吐量。 但不是所有的 operator 都可以被链接,如下 keyBy 等操作会导致网络 shuffle 和重分区,因此其就不能被链接,只能被单独作为一个 Task。 简单来说,一个 Task 就是一个可以链接的最小的操作链 (Operator Chains) 。如下图,source 和 map 算子被链接到一块,因此整个作业就只有三个 Task:
+
+
+
+
+解释完 Task ,我们在解释一下什么是 SubTask,其准确的翻译是: *A subtask is one parallel slice of a task*,即一个 Task 可以按照其并行度拆分为多个 SubTask。如上图,source & map 具有两个并行度,KeyBy 具有两个并行度,Sink 具有一个并行度,因此整个虽然只有 3 个 Task,但是却有 5 个 SubTask。Jobmanager 负责定义和拆分这些 SubTask,并将其交给 Taskmanagers 来执行,每个 SubTask 都是一个单独的线程。
+
+### 4.3 资源管理
+
+理解了 SubTasks ,我们再来看看其与 Slots 的对应情况。一种可能的分配情况如下:
+
+
+
+
+
+
+这时每个 SubTask 线程运行在一个独立的 TaskSlot, 它们共享所属的 TaskManager 进程的TCP 连接(通过多路复用技术)和心跳信息 (heartbeat messages),从而可以降低整体的性能开销。此时看似是最好的情况,但是每个操作需要的资源都是不尽相同的,这里假设该作业 keyBy 操作所需资源的数量比 Sink 多很多 ,那么此时 Sink 所在 Slot 的资源就没有得到有效的利用。
+
+基于这个原因,Flink 允许多个 subtasks 共享 slots,即使它们是不同 tasks 的 subtasks,但只要它们来自同一个 Job 就可以。假设上面 souce & map 和 keyBy 的并行度调整为 6,而 Slot 的数量不变,此时情况如下:
+
+
+
+
+
+
+可以看到一个 Task Slot 中运行了多个 SubTask 子任务,此时每个子任务仍然在一个独立的线程中执行,只不过共享一组 Sot 资源而已。那么 Flink 到底如何确定一个 Job 至少需要多少个 Slot 呢?Flink 对于这个问题的处理很简单,默认情况一个 Job 所需要的 Slot 的数量就等于其 Operation 操作的最高并行度。如下, A,B,D 操作的并行度为 4,而 C,E 操作的并行度为 2,那么此时整个 Job 就需要至少四个 Slots 来完成。通过这个机制,Flink 就可以不必去关心一个 Job 到底会被拆分为多少个 Tasks 和 SubTasks。
+
+
+
+
+
+
+
+
+### 4.4 组件通讯
+
+Flink 的所有组件都基于 Actor System 来进行通讯。Actor system是多种角色的 actor 的容器,它提供调度,配置,日志记录等多种服务,并包含一个可以启动所有 actor 的线程池,如果 actor 是本地的,则消息通过共享内存进行共享,但如果 actor 是远程的,则通过 RPC 的调用来传递消息。
+
+
+
+
+
+
+## 五、Flink 的优点
+
+最后基于上面的介绍,来总结一下 Flink 的优点:
+
++ Flink 是基于事件驱动 (Event-driven) 的应用,能够同时支持流处理和批处理;
++ 基于内存的计算,能够保证高吞吐和低延迟,具有优越的性能表现;
++ 支持精确一次 (Exactly-once) 语意,能够完美地保证一致性和正确性;
++ 分层 API ,能够满足各个层次的开发需求;
++ 支持高可用配置,支持保存点机制,能够提供安全性和稳定性上的保证;
++ 多样化的部署方式,支持本地,远端,云端等多种部署方案;
++ 具有横向扩展架构,能够按照用户的需求进行动态扩容;
++ 活跃度极高的社区和完善的生态圈的支持。
+
+
+
+## 参考资料
+
++ [Dataflow Programming Model](https://ci.apache.org/projects/flink/flink-docs-release-1.9/concepts/programming-model.html)
++ [Distributed Runtime Environment](https://ci.apache.org/projects/flink/flink-docs-release-1.9/concepts/runtime.html)
++ [Component Stack](https://ci.apache.org/projects/flink/flink-docs-release-1.9/internals/components.html)
++ Fabian Hueske , Vasiliki Kalavri . 《Stream Processing with Apache Flink》. O'Reilly Media . 2019-4-30
+
+
+
+
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink\347\212\266\346\200\201\347\256\241\347\220\206\344\270\216\346\243\200\346\237\245\347\202\271\346\234\272\345\210\266.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink\347\212\266\346\200\201\347\256\241\347\220\206\344\270\216\346\243\200\346\237\245\347\202\271\346\234\272\345\210\266.md"
new file mode 100644
index 0000000..e19d9d5
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flink\347\212\266\346\200\201\347\256\241\347\220\206\344\270\216\346\243\200\346\237\245\347\202\271\346\234\272\345\210\266.md"
@@ -0,0 +1,370 @@
+# Flink 状态管理
+
+一、状态分类
+ 2.1 算子状态
+ 2.2 键控状态
+二、状态编程
+ 2.1 键控状态
+ 2.2 状态有效期
+ 2.3 算子状态
+三、检查点机制
+ 3.1 CheckPoints
+ 3.2 开启检查点
+ 3.3 保存点机制
+四、状态后端
+ 4.1 状态管理器分类
+ 4.2 配置方式
+
+
+
+## 一、状态分类
+
+相对于其他流计算框架,Flink 一个比较重要的特性就是其支持有状态计算。即你可以将中间的计算结果进行保存,并提供给后续的计算使用:
+
+
+
+
+
+具体而言,Flink 又将状态 (State) 分为 Keyed State 与 Operator State:
+
+### 2.1 算子状态
+
+算子状态 (Operator State):顾名思义,状态是和算子进行绑定的,一个算子的状态不能被其他算子所访问到。官方文档上对 Operator State 的解释是:*each operator state is bound to one parallel operator instance*,所以更为确切的说一个算子状态是与一个并发的算子实例所绑定的,即假设算子的并行度是 2,那么其应有两个对应的算子状态:
+
+
+
+
+
+### 2.2 键控状态
+
+键控状态 (Keyed State) :是一种特殊的算子状态,即状态是根据 key 值进行区分的,Flink 会为每类键值维护一个状态实例。如下图所示,每个颜色代表不同 key 值,对应四个不同的状态实例。需要注意的是键控状态只能在 `KeyedStream` 上进行使用,我们可以通过 `stream.keyBy(...)` 来得到 `KeyedStream` 。
+
+
+
+
+
+## 二、状态编程
+
+### 2.1 键控状态
+
+Flink 提供了以下数据格式来管理和存储键控状态 (Keyed State):
+
+- **ValueState**:存储单值类型的状态。可以使用 `update(T)` 进行更新,并通过 `T value()` 进行检索。
+- **ListState**:存储列表类型的状态。可以使用 `add(T)` 或 `addAll(List)` 添加元素;并通过 `get()` 获得整个列表。
+- **ReducingState**:用于存储经过 ReduceFunction 计算后的结果,使用 `add(T)` 增加元素。
+- **AggregatingState**:用于存储经过 AggregatingState 计算后的结果,使用 `add(IN)` 添加元素。
+- **FoldingState**:已被标识为废弃,会在未来版本中移除,官方推荐使用 `AggregatingState` 代替。
+- **MapState**:维护 Map 类型的状态。
+
+以上所有增删改查方法不必硬记,在使用时通过语法提示来调用即可。这里给出一个具体的使用示例:假设我们正在开发一个监控系统,当监控数据超过阈值一定次数后,需要发出报警信息。这里之所以要达到一定次数,是因为由于偶发原因,偶尔一次超过阈值并不能代表什么,故需要达到一定次数后才触发报警,这就需要使用到 Flink 的状态编程。相关代码如下:
+
+```java
+public class ThresholdWarning extends
+ RichFlatMapFunction, Tuple2>> {
+
+ // 通过ListState来存储非正常数据的状态
+ private transient ListState abnormalData;
+ // 需要监控的阈值
+ private Long threshold;
+ // 触发报警的次数
+ private Integer numberOfTimes;
+
+ ThresholdWarning(Long threshold, Integer numberOfTimes) {
+ this.threshold = threshold;
+ this.numberOfTimes = numberOfTimes;
+ }
+
+ @Override
+ public void open(Configuration parameters) {
+ // 通过状态名称(句柄)获取状态实例,如果不存在则会自动创建
+ abnormalData = getRuntimeContext().getListState(
+ new ListStateDescriptor<>("abnormalData", Long.class));
+ }
+
+ @Override
+ public void flatMap(Tuple2 value, Collector>> out)
+ throws Exception {
+ Long inputValue = value.f1;
+ // 如果输入值超过阈值,则记录该次不正常的数据信息
+ if (inputValue >= threshold) {
+ abnormalData.add(inputValue);
+ }
+ ArrayList list = Lists.newArrayList(abnormalData.get().iterator());
+ // 如果不正常的数据出现达到一定次数,则输出报警信息
+ if (list.size() >= numberOfTimes) {
+ out.collect(Tuple2.of(value.f0 + " 超过指定阈值 ", list));
+ // 报警信息输出后,清空状态
+ abnormalData.clear();
+ }
+ }
+}
+```
+
+调用自定义的状态监控,这里我们使用 a,b 来代表不同类型的监控数据,分别对其数据进行监控:
+
+```java
+final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
+DataStreamSource> tuple2DataStreamSource = env.fromElements(
+ Tuple2.of("a", 50L), Tuple2.of("a", 80L), Tuple2.of("a", 400L),
+ Tuple2.of("a", 100L), Tuple2.of("a", 200L), Tuple2.of("a", 200L),
+ Tuple2.of("b", 100L), Tuple2.of("b", 200L), Tuple2.of("b", 200L),
+ Tuple2.of("b", 500L), Tuple2.of("b", 600L), Tuple2.of("b", 700L));
+tuple2DataStreamSource
+ .keyBy(0)
+ .flatMap(new ThresholdWarning(100L, 3)) // 超过100的阈值3次后就进行报警
+ .printToErr();
+env.execute("Managed Keyed State");
+```
+
+输出如下结果如下:
+
+
+
+
+
+### 2.2 状态有效期
+
+以上任何类型的 keyed state 都支持配置有效期 (TTL) ,示例如下:
+
+```java
+StateTtlConfig ttlConfig = StateTtlConfig
+ // 设置有效期为 10 秒
+ .newBuilder(Time.seconds(10))
+ // 设置有效期更新规则,这里设置为当创建和写入时,都重置其有效期到规定的10秒
+ .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
+ /*设置只要值过期就不可见,另外一个可选值是ReturnExpiredIfNotCleanedUp,
+ 代表即使值过期了,但如果还没有被物理删除,就是可见的*/
+ .setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
+ .build();
+ListStateDescriptor descriptor = new ListStateDescriptor<>("abnormalData", Long.class);
+descriptor.enableTimeToLive(ttlConfig);
+```
+
+### 2.3 算子状态
+
+相比于键控状态,算子状态目前支持的存储类型只有以下三种:
+
+- **ListState**:存储列表类型的状态。
+- **UnionListState**:存储列表类型的状态,与 ListState 的区别在于:如果并行度发生变化,ListState 会将该算子的所有并发的状态实例进行汇总,然后均分给新的 Task;而 UnionListState 只是将所有并发的状态实例汇总起来,具体的划分行为则由用户进行定义。
+- **BroadcastState**:用于广播的算子状态。
+
+这里我们继续沿用上面的例子,假设此时我们不需要区分监控数据的类型,只要有监控数据超过阈值并达到指定的次数后,就进行报警,代码如下:
+
+```java
+public class ThresholdWarning extends RichFlatMapFunction,
+Tuple2>>> implements CheckpointedFunction {
+
+ // 非正常数据
+ private List> bufferedData;
+ // checkPointedState
+ private transient ListState> checkPointedState;
+ // 需要监控的阈值
+ private Long threshold;
+ // 次数
+ private Integer numberOfTimes;
+
+ ThresholdWarning(Long threshold, Integer numberOfTimes) {
+ this.threshold = threshold;
+ this.numberOfTimes = numberOfTimes;
+ this.bufferedData = new ArrayList<>();
+ }
+
+ @Override
+ public void initializeState(FunctionInitializationContext context) throws Exception {
+ // 注意这里获取的是OperatorStateStore
+ checkPointedState = context.getOperatorStateStore().
+ getListState(new ListStateDescriptor<>("abnormalData",
+ TypeInformation.of(new TypeHint>() {
+ })));
+ // 如果发生重启,则需要从快照中将状态进行恢复
+ if (context.isRestored()) {
+ for (Tuple2 element : checkPointedState.get()) {
+ bufferedData.add(element);
+ }
+ }
+ }
+
+ @Override
+ public void flatMap(Tuple2 value,
+ Collector>>> out) {
+ Long inputValue = value.f1;
+ // 超过阈值则进行记录
+ if (inputValue >= threshold) {
+ bufferedData.add(value);
+ }
+ // 超过指定次数则输出报警信息
+ if (bufferedData.size() >= numberOfTimes) {
+ // 顺便输出状态实例的hashcode
+ out.collect(Tuple2.of(checkPointedState.hashCode() + "阈值警报!", bufferedData));
+ bufferedData.clear();
+ }
+ }
+
+ @Override
+ public void snapshotState(FunctionSnapshotContext context) throws Exception {
+ // 在进行快照时,将数据存储到checkPointedState
+ checkPointedState.clear();
+ for (Tuple2 element : bufferedData) {
+ checkPointedState.add(element);
+ }
+ }
+}
+```
+
+调用自定义算子状态,这里需要将并行度设置为 1:
+
+```java
+final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
+// 开启检查点机制
+env.enableCheckpointing(1000);
+// 设置并行度为1
+DataStreamSource> tuple2DataStreamSource = env.setParallelism(1).fromElements(
+ Tuple2.of("a", 50L), Tuple2.of("a", 80L), Tuple2.of("a", 400L),
+ Tuple2.of("a", 100L), Tuple2.of("a", 200L), Tuple2.of("a", 200L),
+ Tuple2.of("b", 100L), Tuple2.of("b", 200L), Tuple2.of("b", 200L),
+ Tuple2.of("b", 500L), Tuple2.of("b", 600L), Tuple2.of("b", 700L));
+tuple2DataStreamSource
+ .flatMap(new ThresholdWarning(100L, 3))
+ .printToErr();
+env.execute("Managed Keyed State");
+}
+```
+
+此时输出如下:
+
+
+
+
+
+在上面的调用代码中,我们将程序的并行度设置为 1,可以看到三次输出中状态实例的 hashcode 全是一致的,证明它们都同一个状态实例。假设将并行度设置为 2,此时输出如下:
+
+
+
+
+
+可以看到此时两次输出中状态实例的 hashcode 是不一致的,代表它们不是同一个状态实例,这也就是上文提到的,一个算子状态是与一个并发的算子实例所绑定的。同时这里只输出两次,是因为在并发处理的情况下,线程 1 可能拿到 5 个非正常值,线程 2 可能拿到 4 个非正常值,因为要大于 3 次才能输出,所以在这种情况下就会出现只输出两条记录的情况,所以需要将程序的并行度设置为 1。
+
+## 三、检查点机制
+
+### 3.1 CheckPoints
+
+为了使 Flink 的状态具有良好的容错性,Flink 提供了检查点机制 (CheckPoints) 。通过检查点机制,Flink 定期在数据流上生成 checkpoint barrier ,当某个算子收到 barrier 时,即会基于当前状态生成一份快照,然后再将该 barrier 传递到下游算子,下游算子接收到该 barrier 后,也基于当前状态生成一份快照,依次传递直至到最后的 Sink 算子上。当出现异常后,Flink 就可以根据最近的一次的快照数据将所有算子恢复到先前的状态。
+
+
+
+
+
+
+
+### 3.2 开启检查点
+
+默认情况下,检查点机制是关闭的,需要在程序中进行开启:
+
+```java
+// 开启检查点机制,并指定状态检查点之间的时间间隔
+env.enableCheckpointing(1000);
+
+// 其他可选配置如下:
+// 设置语义
+env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
+// 设置两个检查点之间的最小时间间隔
+env.getCheckpointConfig().setMinPauseBetweenCheckpoints(500);
+// 设置执行Checkpoint操作时的超时时间
+env.getCheckpointConfig().setCheckpointTimeout(60000);
+// 设置最大并发执行的检查点的数量
+env.getCheckpointConfig().setMaxConcurrentCheckpoints(1);
+// 将检查点持久化到外部存储
+env.getCheckpointConfig().enableExternalizedCheckpoints(
+ ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);
+// 如果有更近的保存点时,是否将作业回退到该检查点
+env.getCheckpointConfig().setPreferCheckpointForRecovery(true);
+```
+
+### 3.3 保存点机制
+
+保存点机制 (Savepoints) 是检查点机制的一种特殊的实现,它允许你通过手工的方式来触发 Checkpoint,并将结果持久化存储到指定路径中,主要用于避免 Flink 集群在重启或升级时导致状态丢失。示例如下:
+
+```shell
+# 触发指定id的作业的Savepoint,并将结果存储到指定目录下
+bin/flink savepoint :jobId [:targetDirectory]
+```
+
+更多命令和配置可以参考官方文档:[savepoints]( https://ci.apache.org/projects/flink/flink-docs-release-1.9/zh/ops/state/savepoints.html )
+
+## 四、状态后端
+
+### 4.1 状态管理器分类
+
+默认情况下,所有的状态都存储在 JVM 的堆内存中,在状态数据过多的情况下,这种方式很有可能导致内存溢出,因此 Flink 该提供了其它方式来存储状态数据,这些存储方式统一称为状态后端 (或状态管理器):
+
+
+
+
+
+主要有以下三种:
+
+#### 1. MemoryStateBackend
+
+默认的方式,即基于 JVM 的堆内存进行存储,主要适用于本地开发和调试。
+
+#### 2. FsStateBackend
+
+基于文件系统进行存储,可以是本地文件系统,也可以是 HDFS 等分布式文件系统。 需要注意而是虽然选择使用了 FsStateBackend ,但正在进行的数据仍然是存储在 TaskManager 的内存中的,只有在 checkpoint 时,才会将状态快照写入到指定文件系统上。
+
+#### 3. RocksDBStateBackend
+
+RocksDBStateBackend 是 Flink 内置的第三方状态管理器,采用嵌入式的 key-value 型数据库 RocksDB 来存储正在进行的数据。等到 checkpoint 时,再将其中的数据持久化到指定的文件系统中,所以采用 RocksDBStateBackend 时也需要配置持久化存储的文件系统。之所以这样做是因为 RocksDB 作为嵌入式数据库安全性比较低,但比起全文件系统的方式,其读取速率更快;比起全内存的方式,其存储空间更大,因此它是一种比较均衡的方案。
+
+### 4.2 配置方式
+
+Flink 支持使用两种方式来配置后端管理器:
+
+**第一种方式**:基于代码方式进行配置,只对当前作业生效:
+
+```java
+// 配置 FsStateBackend
+env.setStateBackend(new FsStateBackend("hdfs://namenode:40010/flink/checkpoints"));
+// 配置 RocksDBStateBackend
+env.setStateBackend(new RocksDBStateBackend("hdfs://namenode:40010/flink/checkpoints"));
+```
+
+配置 RocksDBStateBackend 时,需要额外导入下面的依赖:
+
+```xml
+
+ org.apache.flink
+ flink-statebackend-rocksdb_2.11
+ 1.9.0
+
+```
+
+**第二种方式**:基于 `flink-conf.yaml` 配置文件的方式进行配置,对所有部署在该集群上的作业都生效:
+
+```yaml
+state.backend: filesystem
+state.checkpoints.dir: hdfs://namenode:40010/flink/checkpoints
+```
+
+
+
+> 注:本篇文章所有示例代码下载地址:[flink-state-management]( https://github.com/heibaiying/BigData-Notes/tree/master/code/Flink/flink-state-management)
+
+
+
+## 参考资料
+
++ [Working with State](https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/stream/state/state.html)
++ [Checkpointing](https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/stream/state/checkpointing.html)
++ [Savepoints](https://ci.apache.org/projects/flink/flink-docs-release-1.9/ops/state/savepoints.html#savepoints)
++ [State Backends](https://ci.apache.org/projects/flink/flink-docs-release-1.9/ops/state/state_backends.html)
++ Fabian Hueske , Vasiliki Kalavri . 《Stream Processing with Apache Flink》. O'Reilly Media . 2019-4-30
+
+
+
+
+
+
+
+
+
+
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flume\346\225\264\345\220\210Kafka.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flume\346\225\264\345\220\210Kafka.md"
new file mode 100644
index 0000000..63e244b
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flume\346\225\264\345\220\210Kafka.md"
@@ -0,0 +1,116 @@
+# Flume 整合 Kafka
+
+
+一、背景
+二、整合流程
+ 1. 启动Zookeeper和Kafka
+ 2. 创建主题
+ 3. 启动kafka消费者
+ 4. 配置Flume
+ 5. 启动Flume
+ 6. 测试
+
+
+## 一、背景
+
+先说一下,为什么要使用 Flume + Kafka?
+
+以实时流处理项目为例,由于采集的数据量可能存在峰值和峰谷,假设是一个电商项目,那么峰值通常出现在秒杀时,这时如果直接将 Flume 聚合后的数据输入到 Storm 等分布式计算框架中,可能就会超过集群的处理能力,这时采用 Kafka 就可以起到削峰的作用。Kafka 天生为大数据场景而设计,具有高吞吐的特性,能很好地抗住峰值数据的冲击。
+
+
+
+
+
+## 二、整合流程
+
+Flume 发送数据到 Kafka 上主要是通过 `KafkaSink` 来实现的,主要步骤如下:
+
+### 1. 启动Zookeeper和Kafka
+
+这里启动一个单节点的 Kafka 作为测试:
+
+```shell
+# 启动Zookeeper
+zkServer.sh start
+
+# 启动kafka
+bin/kafka-server-start.sh config/server.properties
+```
+
+### 2. 创建主题
+
+创建一个主题 `flume-kafka`,之后 Flume 收集到的数据都会发到这个主题上:
+
+```shell
+# 创建主题
+bin/kafka-topics.sh --create \
+--zookeeper hadoop001:2181 \
+--replication-factor 1 \
+--partitions 1 --topic flume-kafka
+
+# 查看创建的主题
+bin/kafka-topics.sh --zookeeper hadoop001:2181 --list
+```
+
+
+
+### 3. 启动kafka消费者
+
+启动一个消费者,监听我们刚才创建的 `flume-kafka` 主题:
+
+```shell
+# bin/kafka-console-consumer.sh --bootstrap-server hadoop001:9092 --topic flume-kafka
+```
+
+
+
+### 4. 配置Flume
+
+新建配置文件 `exec-memory-kafka.properties`,文件内容如下。这里我们监听一个名为 `kafka.log` 的文件,当文件内容有变化时,将新增加的内容发送到 Kafka 的 `flume-kafka` 主题上。
+
+```properties
+a1.sources = s1
+a1.channels = c1
+a1.sinks = k1
+
+a1.sources.s1.type=exec
+a1.sources.s1.command=tail -F /tmp/kafka.log
+a1.sources.s1.channels=c1
+
+#设置Kafka接收器
+a1.sinks.k1.type= org.apache.flume.sink.kafka.KafkaSink
+#设置Kafka地址
+a1.sinks.k1.brokerList=hadoop001:9092
+#设置发送到Kafka上的主题
+a1.sinks.k1.topic=flume-kafka
+#设置序列化方式
+a1.sinks.k1.serializer.class=kafka.serializer.StringEncoder
+a1.sinks.k1.channel=c1
+
+a1.channels.c1.type=memory
+a1.channels.c1.capacity=10000
+a1.channels.c1.transactionCapacity=100
+```
+
+
+
+### 5. 启动Flume
+
+```shell
+flume-ng agent \
+--conf conf \
+--conf-file /usr/app/apache-flume-1.6.0-cdh5.15.2-bin/examples/exec-memory-kafka.properties \
+--name a1 -Dflume.root.logger=INFO,console
+```
+
+
+
+### 6. 测试
+
+向监听的 `/tmp/kafka.log ` 文件中追加内容,查看 Kafka 消费者的输出:
+
+
+
+可以看到 `flume-kafka` 主题的消费端已经收到了对应的消息:
+
+
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flume\347\256\200\344\273\213\345\217\212\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flume\347\256\200\344\273\213\345\217\212\345\237\272\346\234\254\344\275\277\347\224\250.md"
new file mode 100644
index 0000000..19e94d1
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Flume\347\256\200\344\273\213\345\217\212\345\237\272\346\234\254\344\275\277\347\224\250.md"
@@ -0,0 +1,375 @@
+# Flume 简介及基本使用
+
+
+一、Flume简介
+二、Flume架构和基本概念
+ 2.1 基本架构
+ 2.2 基本概念
+ 2.3 组件种类
+三、Flume架构模式
+四、Flume配置格式
+五、Flume安装部署
+六、Flume使用案例
+
+
+
+## 一、Flume简介
+
+Apache Flume 是一个分布式,高可用的数据收集系统。它可以从不同的数据源收集数据,经过聚合后发送到存储系统中,通常用于日志数据的收集。Flume 分为 NG 和 OG (1.0 之前) 两个版本,NG 在 OG 的基础上进行了完全的重构,是目前使用最为广泛的版本。下面的介绍均以 NG 为基础。
+
+## 二、Flume架构和基本概念
+
+下图为 Flume 的基本架构图:
+
+
+
+
+
+### 2.1 基本架构
+
+外部数据源以特定格式向 Flume 发送 `events` (事件),当 `source` 接收到 `events` 时,它将其存储到一个或多个 `channel`,`channe` 会一直保存 `events` 直到它被 `sink` 所消费。`sink` 的主要功能从 `channel` 中读取 `events`,并将其存入外部存储系统或转发到下一个 `source`,成功后再从 `channel` 中移除 `events`。
+
+
+
+### 2.2 基本概念
+
+**1. Event**
+
+`Event` 是 Flume NG 数据传输的基本单元。类似于 JMS 和消息系统中的消息。一个 `Event` 由标题和正文组成:前者是键/值映射,后者是任意字节数组。
+
+**2. Source**
+
+数据收集组件,从外部数据源收集数据,并存储到 Channel 中。
+
+**3. Channel**
+
+`Channel` 是源和接收器之间的管道,用于临时存储数据。可以是内存或持久化的文件系统:
+
++ `Memory Channel` : 使用内存,优点是速度快,但数据可能会丢失 (如突然宕机);
++ `File Channel` : 使用持久化的文件系统,优点是能保证数据不丢失,但是速度慢。
+
+**4. Sink**
+
+`Sink` 的主要功能从 `Channel` 中读取 `Event`,并将其存入外部存储系统或将其转发到下一个 `Source`,成功后再从 `Channel` 中移除 `Event`。
+
+**5. Agent**
+
+是一个独立的 (JVM) 进程,包含 `Source`、 `Channel`、 `Sink` 等组件。
+
+
+
+### 2.3 组件种类
+
+Flume 中的每一个组件都提供了丰富的类型,适用于不同场景:
+
+- Source 类型 :内置了几十种类型,如 `Avro Source`,`Thrift Source`,`Kafka Source`,`JMS Source`;
+
+- Sink 类型 :`HDFS Sink`,`Hive Sink`,`HBaseSinks`,`Avro Sink` 等;
+
+- Channel 类型 :`Memory Channel`,`JDBC Channel`,`Kafka Channel`,`File Channel` 等。
+
+对于 Flume 的使用,除非有特别的需求,否则通过组合内置的各种类型的 Source,Sink 和 Channel 就能满足大多数的需求。在 [Flume 官网](http://flume.apache.org/releases/content/1.9.0/FlumeUserGuide.html) 上对所有类型组件的配置参数均以表格的方式做了详尽的介绍,并附有配置样例;同时不同版本的参数可能略有所不同,所以使用时建议选取官网对应版本的 User Guide 作为主要参考资料。
+
+
+
+## 三、Flume架构模式
+
+Flume 支持多种架构模式,分别介绍如下
+
+### 3.1 multi-agent flow
+
+
+
+
+
+
+
+Flume 支持跨越多个 Agent 的数据传递,这要求前一个 Agent 的 Sink 和下一个 Agent 的 Source 都必须是 `Avro` 类型,Sink 指向 Source 所在主机名 (或 IP 地址) 和端口(详细配置见下文案例三)。
+
+### 3.2 Consolidation
+
+
+
+
+
+
+
+日志收集中常常存在大量的客户端(比如分布式 web 服务),Flume 支持使用多个 Agent 分别收集日志,然后通过一个或者多个 Agent 聚合后再存储到文件系统中。
+
+### 3.3 Multiplexing the flow
+
+
+
+Flume 支持从一个 Source 向多个 Channel,也就是向多个 Sink 传递事件,这个操作称之为 `Fan Out`(扇出)。默认情况下 `Fan Out` 是向所有的 Channel 复制 `Event`,即所有 Channel 收到的数据都是相同的。同时 Flume 也支持在 `Source` 上自定义一个复用选择器 (multiplexing selector) 来实现自定义的路由规则。
+
+
+
+## 四、Flume配置格式
+
+Flume 配置通常需要以下两个步骤:
+
+1. 分别定义好 Agent 的 Sources,Sinks,Channels,然后将 Sources 和 Sinks 与通道进行绑定。需要注意的是一个 Source 可以配置多个 Channel,但一个 Sink 只能配置一个 Channel。基本格式如下:
+
+```shell
+.sources =
+.sinks =
+.channels =
+
+# set channel for source
+.sources..channels = ...
+
+# set channel for sink
+.sinks..channel =
+```
+
+2. 分别定义 Source,Sink,Channel 的具体属性。基本格式如下:
+
+```shell
+
+.sources.. =
+
+# properties for channels
+.channel.. =
+
+# properties for sinks
+.sources.. =
+```
+
+
+
+## 五、Flume的安装部署
+
+为方便大家后期查阅,本仓库中所有软件的安装均单独成篇,Flume 的安装见:
+
+[Linux 环境下 Flume 的安装部署](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Linux%E4%B8%8BFlume%E7%9A%84%E5%AE%89%E8%A3%85.md)
+
+
+
+## 六、Flume使用案例
+
+介绍几个 Flume 的使用案例:
+
++ 案例一:使用 Flume 监听文件内容变动,将新增加的内容输出到控制台。
++ 案例二:使用 Flume 监听指定目录,将目录下新增加的文件存储到 HDFS。
++ 案例三:使用 Avro 将本服务器收集到的日志数据发送到另外一台服务器。
+
+### 6.1 案例一
+
+需求: 监听文件内容变动,将新增加的内容输出到控制台。
+
+实现: 主要使用 `Exec Source` 配合 `tail` 命令实现。
+
+#### 1. 配置
+
+新建配置文件 `exec-memory-logger.properties`,其内容如下:
+
+```properties
+#指定agent的sources,sinks,channels
+a1.sources = s1
+a1.sinks = k1
+a1.channels = c1
+
+#配置sources属性
+a1.sources.s1.type = exec
+a1.sources.s1.command = tail -F /tmp/log.txt
+a1.sources.s1.shell = /bin/bash -c
+
+#将sources与channels进行绑定
+a1.sources.s1.channels = c1
+
+#配置sink
+a1.sinks.k1.type = logger
+
+#将sinks与channels进行绑定
+a1.sinks.k1.channel = c1
+
+#配置channel类型
+a1.channels.c1.type = memory
+```
+
+#### 2. 启动
+
+```shell
+flume-ng agent \
+--conf conf \
+--conf-file /usr/app/apache-flume-1.6.0-cdh5.15.2-bin/examples/exec-memory-logger.properties \
+--name a1 \
+-Dflume.root.logger=INFO,console
+```
+
+#### 3. 测试
+
+向文件中追加数据:
+
+
+
+控制台的显示:
+
+
+
+
+
+### 6.2 案例二
+
+需求: 监听指定目录,将目录下新增加的文件存储到 HDFS。
+
+实现:使用 `Spooling Directory Source` 和 `HDFS Sink`。
+
+#### 1. 配置
+
+```properties
+#指定agent的sources,sinks,channels
+a1.sources = s1
+a1.sinks = k1
+a1.channels = c1
+
+#配置sources属性
+a1.sources.s1.type =spooldir
+a1.sources.s1.spoolDir =/tmp/logs
+a1.sources.s1.basenameHeader = true
+a1.sources.s1.basenameHeaderKey = fileName
+#将sources与channels进行绑定
+a1.sources.s1.channels =c1
+
+
+#配置sink
+a1.sinks.k1.type = hdfs
+a1.sinks.k1.hdfs.path = /flume/events/%y-%m-%d/%H/
+a1.sinks.k1.hdfs.filePrefix = %{fileName}
+#生成的文件类型,默认是Sequencefile,可用DataStream,则为普通文本
+a1.sinks.k1.hdfs.fileType = DataStream
+a1.sinks.k1.hdfs.useLocalTimeStamp = true
+#将sinks与channels进行绑定
+a1.sinks.k1.channel = c1
+
+#配置channel类型
+a1.channels.c1.type = memory
+```
+
+#### 2. 启动
+
+```shell
+flume-ng agent \
+--conf conf \
+--conf-file /usr/app/apache-flume-1.6.0-cdh5.15.2-bin/examples/spooling-memory-hdfs.properties \
+--name a1 -Dflume.root.logger=INFO,console
+```
+
+#### 3. 测试
+
+拷贝任意文件到监听目录下,可以从日志看到文件上传到 HDFS 的路径:
+
+```shell
+# cp log.txt logs/
+```
+
+
+
+查看上传到 HDFS 上的文件内容与本地是否一致:
+
+```shell
+# hdfs dfs -cat /flume/events/19-04-09/13/log.txt.1554788567801
+```
+
+
+
+
+
+### 6.3 案例三
+
+需求: 将本服务器收集到的数据发送到另外一台服务器。
+
+实现:使用 `avro sources` 和 `avro Sink` 实现。
+
+#### 1. 配置日志收集Flume
+
+新建配置 `netcat-memory-avro.properties`,监听文件内容变化,然后将新的文件内容通过 `avro sink` 发送到 hadoop001 这台服务器的 8888 端口:
+
+```properties
+#指定agent的sources,sinks,channels
+a1.sources = s1
+a1.sinks = k1
+a1.channels = c1
+
+#配置sources属性
+a1.sources.s1.type = exec
+a1.sources.s1.command = tail -F /tmp/log.txt
+a1.sources.s1.shell = /bin/bash -c
+a1.sources.s1.channels = c1
+
+#配置sink
+a1.sinks.k1.type = avro
+a1.sinks.k1.hostname = hadoop001
+a1.sinks.k1.port = 8888
+a1.sinks.k1.batch-size = 1
+a1.sinks.k1.channel = c1
+
+#配置channel类型
+a1.channels.c1.type = memory
+a1.channels.c1.capacity = 1000
+a1.channels.c1.transactionCapacity = 100
+```
+
+#### 2. 配置日志聚合Flume
+
+使用 `avro source` 监听 hadoop001 服务器的 8888 端口,将获取到内容输出到控制台:
+
+```properties
+#指定agent的sources,sinks,channels
+a2.sources = s2
+a2.sinks = k2
+a2.channels = c2
+
+#配置sources属性
+a2.sources.s2.type = avro
+a2.sources.s2.bind = hadoop001
+a2.sources.s2.port = 8888
+
+#将sources与channels进行绑定
+a2.sources.s2.channels = c2
+
+#配置sink
+a2.sinks.k2.type = logger
+
+#将sinks与channels进行绑定
+a2.sinks.k2.channel = c2
+
+#配置channel类型
+a2.channels.c2.type = memory
+a2.channels.c2.capacity = 1000
+a2.channels.c2.transactionCapacity = 100
+```
+
+#### 3. 启动
+
+启动日志聚集 Flume:
+
+```shell
+flume-ng agent \
+--conf conf \
+--conf-file /usr/app/apache-flume-1.6.0-cdh5.15.2-bin/examples/avro-memory-logger.properties \
+--name a2 -Dflume.root.logger=INFO,console
+```
+
+在启动日志收集 Flume:
+
+```shell
+flume-ng agent \
+--conf conf \
+--conf-file /usr/app/apache-flume-1.6.0-cdh5.15.2-bin/examples/netcat-memory-avro.properties \
+--name a1 -Dflume.root.logger=INFO,console
+```
+
+这里建议按以上顺序启动,原因是 `avro.source` 会先与端口进行绑定,这样 `avro sink` 连接时才不会报无法连接的异常。但是即使不按顺序启动也是没关系的,`sink` 会一直重试,直至建立好连接。
+
+
+
+#### 4.测试
+
+向文件 `tmp/log.txt` 中追加内容:
+
+
+
+可以看到已经从 8888 端口监听到内容,并成功输出到控制台:
+
+
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/HDFS-Java-API.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/HDFS-Java-API.md"
new file mode 100644
index 0000000..f5ec70d
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/HDFS-Java-API.md"
@@ -0,0 +1,388 @@
+# HDFS Java API
+
+
+一、 简介
+二、API的使用
+ 2.1 FileSystem
+ 2.2 创建目录
+ 2.3 创建指定权限的目录
+ 2.4 创建文件,并写入内容
+ 2.5 判断文件是否存在
+ 2.6 查看文件内容
+ 2.7 文件重命名
+ 2.8 删除目录或文件
+ 2.9 上传文件到HDFS
+ 2.10 上传大文件并显示上传进度
+ 2.11 从HDFS上下载文件
+ 2.12 查看指定目录下所有文件的信息
+ 2.13 递归查看指定目录下所有文件的信息
+ 2.14 查看文件的块信息
+
+
+## 一、 简介
+
+想要使用 HDFS API,需要导入依赖 `hadoop-client`。如果是 CDH 版本的 Hadoop,还需要额外指明其仓库地址:
+
+```xml
+
+
+ 4.0.0
+
+ com.heibaiying
+ hdfs-java-api
+ 1.0
+
+
+
+ UTF-8
+ 2.6.0-cdh5.15.2
+
+
+
+
+
+
+ cloudera
+ https://repository.cloudera.com/artifactory/cloudera-repos/
+
+
+
+
+
+
+
+ org.apache.hadoop
+ hadoop-client
+ ${hadoop.version}
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+
+
+```
+
+
+
+## 二、API的使用
+
+### 2.1 FileSystem
+
+FileSystem 是所有 HDFS 操作的主入口。由于之后的每个单元测试都需要用到它,这里使用 `@Before` 注解进行标注。
+
+```java
+private static final String HDFS_PATH = "hdfs://192.168.0.106:8020";
+private static final String HDFS_USER = "root";
+private static FileSystem fileSystem;
+
+@Before
+public void prepare() {
+ try {
+ Configuration configuration = new Configuration();
+ // 这里我启动的是单节点的 Hadoop,所以副本系数设置为 1,默认值为 3
+ configuration.set("dfs.replication", "1");
+ fileSystem = FileSystem.get(new URI(HDFS_PATH), configuration, HDFS_USER);
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (URISyntaxException e) {
+ e.printStackTrace();
+ }
+}
+
+
+@After
+public void destroy() {
+ fileSystem = null;
+}
+```
+
+
+
+### 2.2 创建目录
+
+支持递归创建目录:
+
+```java
+@Test
+public void mkDir() throws Exception {
+ fileSystem.mkdirs(new Path("/hdfs-api/test0/"));
+}
+```
+
+
+
+### 2.3 创建指定权限的目录
+
+`FsPermission(FsAction u, FsAction g, FsAction o)` 的三个参数分别对应:创建者权限,同组其他用户权限,其他用户权限,权限值定义在 `FsAction` 枚举类中。
+
+```java
+@Test
+public void mkDirWithPermission() throws Exception {
+ fileSystem.mkdirs(new Path("/hdfs-api/test1/"),
+ new FsPermission(FsAction.READ_WRITE, FsAction.READ, FsAction.READ));
+}
+```
+
+
+
+### 2.4 创建文件,并写入内容
+
+```java
+@Test
+public void create() throws Exception {
+ // 如果文件存在,默认会覆盖, 可以通过第二个参数进行控制。第三个参数可以控制使用缓冲区的大小
+ FSDataOutputStream out = fileSystem.create(new Path("/hdfs-api/test/a.txt"),
+ true, 4096);
+ out.write("hello hadoop!".getBytes());
+ out.write("hello spark!".getBytes());
+ out.write("hello flink!".getBytes());
+ // 强制将缓冲区中内容刷出
+ out.flush();
+ out.close();
+}
+```
+
+
+
+### 2.5 判断文件是否存在
+
+```java
+@Test
+public void exist() throws Exception {
+ boolean exists = fileSystem.exists(new Path("/hdfs-api/test/a.txt"));
+ System.out.println(exists);
+}
+```
+
+
+
+### 2.6 查看文件内容
+
+查看小文本文件的内容,直接转换成字符串后输出:
+
+```java
+@Test
+public void readToString() throws Exception {
+ FSDataInputStream inputStream = fileSystem.open(new Path("/hdfs-api/test/a.txt"));
+ String context = inputStreamToString(inputStream, "utf-8");
+ System.out.println(context);
+}
+```
+
+`inputStreamToString` 是一个自定义方法,代码如下:
+
+```java
+/**
+ * 把输入流转换为指定编码的字符
+ *
+ * @param inputStream 输入流
+ * @param encode 指定编码类型
+ */
+private static String inputStreamToString(InputStream inputStream, String encode) {
+ try {
+ if (encode == null || ("".equals(encode))) {
+ encode = "utf-8";
+ }
+ BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, encode));
+ StringBuilder builder = new StringBuilder();
+ String str = "";
+ while ((str = reader.readLine()) != null) {
+ builder.append(str).append("\n");
+ }
+ return builder.toString();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+}
+```
+
+
+
+### 2.7 文件重命名
+
+```java
+@Test
+public void rename() throws Exception {
+ Path oldPath = new Path("/hdfs-api/test/a.txt");
+ Path newPath = new Path("/hdfs-api/test/b.txt");
+ boolean result = fileSystem.rename(oldPath, newPath);
+ System.out.println(result);
+}
+```
+
+
+
+### 2.8 删除目录或文件
+
+```java
+public void delete() throws Exception {
+ /*
+ * 第二个参数代表是否递归删除
+ * + 如果 path 是一个目录且递归删除为 true, 则删除该目录及其中所有文件;
+ * + 如果 path 是一个目录但递归删除为 false,则会则抛出异常。
+ */
+ boolean result = fileSystem.delete(new Path("/hdfs-api/test/b.txt"), true);
+ System.out.println(result);
+}
+```
+
+
+
+### 2.9 上传文件到HDFS
+
+```java
+@Test
+public void copyFromLocalFile() throws Exception {
+ // 如果指定的是目录,则会把目录及其中的文件都复制到指定目录下
+ Path src = new Path("D:\\BigData-Notes\\notes\\installation");
+ Path dst = new Path("/hdfs-api/test/");
+ fileSystem.copyFromLocalFile(src, dst);
+}
+```
+
+
+
+### 2.10 上传大文件并显示上传进度
+
+```java
+@Test
+ public void copyFromLocalBigFile() throws Exception {
+
+ File file = new File("D:\\kafka.tgz");
+ final float fileSize = file.length();
+ InputStream in = new BufferedInputStream(new FileInputStream(file));
+
+ FSDataOutputStream out = fileSystem.create(new Path("/hdfs-api/test/kafka5.tgz"),
+ new Progressable() {
+ long fileCount = 0;
+
+ public void progress() {
+ fileCount++;
+ // progress 方法每上传大约 64KB 的数据后就会被调用一次
+ System.out.println("上传进度:" + (fileCount * 64 * 1024 / fileSize) * 100 + " %");
+ }
+ });
+
+ IOUtils.copyBytes(in, out, 4096);
+
+ }
+```
+
+
+
+### 2.11 从HDFS上下载文件
+
+```java
+@Test
+public void copyToLocalFile() throws Exception {
+ Path src = new Path("/hdfs-api/test/kafka.tgz");
+ Path dst = new Path("D:\\app\\");
+ /*
+ * 第一个参数控制下载完成后是否删除源文件,默认是 true,即删除;
+ * 最后一个参数表示是否将 RawLocalFileSystem 用作本地文件系统;
+ * RawLocalFileSystem 默认为 false,通常情况下可以不设置,
+ * 但如果你在执行时候抛出 NullPointerException 异常,则代表你的文件系统与程序可能存在不兼容的情况 (window 下常见),
+ * 此时可以将 RawLocalFileSystem 设置为 true
+ */
+ fileSystem.copyToLocalFile(false, src, dst, true);
+}
+```
+
+
+
+### 2.12 查看指定目录下所有文件的信息
+
+```java
+public void listFiles() throws Exception {
+ FileStatus[] statuses = fileSystem.listStatus(new Path("/hdfs-api"));
+ for (FileStatus fileStatus : statuses) {
+ //fileStatus 的 toString 方法被重写过,直接打印可以看到所有信息
+ System.out.println(fileStatus.toString());
+ }
+}
+```
+
+`FileStatus` 中包含了文件的基本信息,比如文件路径,是否是文件夹,修改时间,访问时间,所有者,所属组,文件权限,是否是符号链接等,输出内容示例如下:
+
+```properties
+FileStatus{
+path=hdfs://192.168.0.106:8020/hdfs-api/test;
+isDirectory=true;
+modification_time=1556680796191;
+access_time=0;
+owner=root;
+group=supergroup;
+permission=rwxr-xr-x;
+isSymlink=false
+}
+```
+
+
+
+### 2.13 递归查看指定目录下所有文件的信息
+
+```java
+@Test
+public void listFilesRecursive() throws Exception {
+ RemoteIterator files = fileSystem.listFiles(new Path("/hbase"), true);
+ while (files.hasNext()) {
+ System.out.println(files.next());
+ }
+}
+```
+
+和上面输出类似,只是多了文本大小,副本系数,块大小信息。
+
+```properties
+LocatedFileStatus{
+path=hdfs://192.168.0.106:8020/hbase/hbase.version;
+isDirectory=false;
+length=7;
+replication=1;
+blocksize=134217728;
+modification_time=1554129052916;
+access_time=1554902661455;
+owner=root; group=supergroup;
+permission=rw-r--r--;
+isSymlink=false}
+```
+
+
+
+### 2.14 查看文件的块信息
+
+```java
+@Test
+public void getFileBlockLocations() throws Exception {
+
+ FileStatus fileStatus = fileSystem.getFileStatus(new Path("/hdfs-api/test/kafka.tgz"));
+ BlockLocation[] blocks = fileSystem.getFileBlockLocations(fileStatus, 0, fileStatus.getLen());
+ for (BlockLocation block : blocks) {
+ System.out.println(block);
+ }
+}
+```
+
+块输出信息有三个值,分别是文件的起始偏移量 (offset),文件大小 (length),块所在的主机名 (hosts)。
+
+```
+0,57028557,hadoop001
+```
+
+这里我上传的文件只有 57M(小于 128M),且程序中设置了副本系数为 1,所有只有一个块信息。
+
+
+
+
+
+**以上所有测试用例下载地址**:[HDFS Java API](https://github.com/heibaiying/BigData-Notes/tree/master/code/Hadoop/hdfs-java-api)
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/HDFS\345\270\270\347\224\250Shell\345\221\275\344\273\244.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/HDFS\345\270\270\347\224\250Shell\345\221\275\344\273\244.md"
new file mode 100644
index 0000000..933eceb
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/HDFS\345\270\270\347\224\250Shell\345\221\275\344\273\244.md"
@@ -0,0 +1,141 @@
+# HDFS 常用 shell 命令
+
+**1. 显示当前目录结构**
+
+```shell
+# 显示当前目录结构
+hadoop fs -ls
+# 递归显示当前目录结构
+hadoop fs -ls -R
+# 显示根目录下内容
+hadoop fs -ls /
+```
+
+**2. 创建目录**
+
+```shell
+# 创建目录
+hadoop fs -mkdir
+# 递归创建目录
+hadoop fs -mkdir -p
+```
+
+**3. 删除操作**
+
+```shell
+# 删除文件
+hadoop fs -rm
+# 递归删除目录和文件
+hadoop fs -rm -R
+```
+
+**4. 从本地加载文件到 HDFS**
+
+```shell
+# 二选一执行即可
+hadoop fs -put [localsrc] [dst]
+hadoop fs - copyFromLocal [localsrc] [dst]
+```
+
+
+**5. 从 HDFS 导出文件到本地**
+
+```shell
+# 二选一执行即可
+hadoop fs -get [dst] [localsrc]
+hadoop fs -copyToLocal [dst] [localsrc]
+```
+
+**6. 查看文件内容**
+
+```shell
+# 二选一执行即可
+hadoop fs -text
+hadoop fs -cat
+```
+
+**7. 显示文件的最后一千字节**
+
+```shell
+hadoop fs -tail
+# 和Linux下一样,会持续监听文件内容变化 并显示文件的最后一千字节
+hadoop fs -tail -f
+```
+
+**8. 拷贝文件**
+
+```shell
+hadoop fs -cp [src] [dst]
+```
+
+**9. 移动文件**
+
+```shell
+hadoop fs -mv [src] [dst]
+```
+
+
+**10. 统计当前目录下各文件大小**
++ 默认单位字节
++ -s : 显示所有文件大小总和,
++ -h : 将以更友好的方式显示文件大小(例如 64.0m 而不是 67108864)
+```shell
+hadoop fs -du
+```
+
+**11. 合并下载多个文件**
++ -nl 在每个文件的末尾添加换行符(LF)
++ -skip-empty-file 跳过空文件
+
+```shell
+hadoop fs -getmerge
+# 示例 将HDFS上的hbase-policy.xml和hbase-site.xml文件合并后下载到本地的/usr/test.xml
+hadoop fs -getmerge -nl /test/hbase-policy.xml /test/hbase-site.xml /usr/test.xml
+```
+
+**12. 统计文件系统的可用空间信息**
+
+```shell
+hadoop fs -df -h /
+```
+
+**13. 更改文件复制因子**
+```shell
+hadoop fs -setrep [-R] [-w]
+```
++ 更改文件的复制因子。如果 path 是目录,则更改其下所有文件的复制因子
++ -w : 请求命令是否等待复制完成
+
+```shell
+# 示例
+hadoop fs -setrep -w 3 /user/hadoop/dir1
+```
+
+**14. 权限控制**
+```shell
+# 权限控制和Linux上使用方式一致
+# 变更文件或目录的所属群组。 用户必须是文件的所有者或超级用户。
+hadoop fs -chgrp [-R] GROUP URI [URI ...]
+# 修改文件或目录的访问权限 用户必须是文件的所有者或超级用户。
+hadoop fs -chmod [-R] URI [URI ...]
+# 修改文件的拥有者 用户必须是超级用户。
+hadoop fs -chown [-R] [OWNER][:[GROUP]] URI [URI ]
+```
+
+**15. 文件检测**
+```shell
+hadoop fs -test - [defsz] URI
+```
+可选选项:
++ -d:如果路径是目录,返回 0。
++ -e:如果路径存在,则返回 0。
++ -f:如果路径是文件,则返回 0。
++ -s:如果路径不为空,则返回 0。
++ -r:如果路径存在且授予读权限,则返回 0。
++ -w:如果路径存在且授予写入权限,则返回 0。
++ -z:如果文件长度为零,则返回 0。
+
+```shell
+# 示例
+hadoop fs -test -e filename
+```
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hadoop-HDFS.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hadoop-HDFS.md"
new file mode 100644
index 0000000..a4826a6
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hadoop-HDFS.md"
@@ -0,0 +1,176 @@
+# Hadoop分布式文件系统——HDFS
+
+
+一、介绍
+二、HDFS 设计原理
+ 2.1 HDFS 架构
+ 2.2 文件系统命名空间
+ 2.3 数据复制
+ 2.4 数据复制的实现原理
+ 2.5 副本的选择
+ 2.6 架构的稳定性
+ 1. 心跳机制和重新复制
+ 2. 数据的完整性
+ 3.元数据的磁盘故障
+ 4.支持快照
+三、HDFS 的特点
+ 3.1 高容错
+ 3.2 高吞吐量
+ 3.3 大文件支持
+ 3.3 简单一致性模型
+ 3.4 跨平台移植性
+附:图解HDFS存储原理
+ 1. HDFS写数据原理
+ 2. HDFS读数据原理
+ 3. HDFS故障类型和其检测方法
+
+
+
+
+## 一、介绍
+
+**HDFS** (**Hadoop Distributed File System**)是 Hadoop 下的分布式文件系统,具有高容错、高吞吐量等特性,可以部署在低成本的硬件上。
+
+
+
+## 二、HDFS 设计原理
+
+
+
+### 2.1 HDFS 架构
+
+HDFS 遵循主/从架构,由单个 NameNode(NN) 和多个 DataNode(DN) 组成:
+
+- **NameNode** : 负责执行有关 ` 文件系统命名空间 ` 的操作,例如打开,关闭、重命名文件和目录等。它同时还负责集群元数据的存储,记录着文件中各个数据块的位置信息。
+- **DataNode**:负责提供来自文件系统客户端的读写请求,执行块的创建,删除等操作。
+
+
+
+### 2.2 文件系统命名空间
+
+HDFS 的 ` 文件系统命名空间 ` 的层次结构与大多数文件系统类似 (如 Linux), 支持目录和文件的创建、移动、删除和重命名等操作,支持配置用户和访问权限,但不支持硬链接和软连接。`NameNode` 负责维护文件系统名称空间,记录对名称空间或其属性的任何更改。
+
+
+
+### 2.3 数据复制
+
+由于 Hadoop 被设计运行在廉价的机器上,这意味着硬件是不可靠的,为了保证容错性,HDFS 提供了数据复制机制。HDFS 将每一个文件存储为一系列**块**,每个块由多个副本来保证容错,块的大小和复制因子可以自行配置(默认情况下,块大小是 128M,默认复制因子是 3)。
+
+
+
+### 2.4 数据复制的实现原理
+
+大型的 HDFS 实例在通常分布在多个机架的多台服务器上,不同机架上的两台服务器之间通过交换机进行通讯。在大多数情况下,同一机架中的服务器间的网络带宽大于不同机架中的服务器之间的带宽。因此 HDFS 采用机架感知副本放置策略,对于常见情况,当复制因子为 3 时,HDFS 的放置策略是:
+
+在写入程序位于 `datanode` 上时,就优先将写入文件的一个副本放置在该 `datanode` 上,否则放在随机 `datanode` 上。之后在另一个远程机架上的任意一个节点上放置另一个副本,并在该机架上的另一个节点上放置最后一个副本。此策略可以减少机架间的写入流量,从而提高写入性能。
+
+
+
+如果复制因子大于 3,则随机确定第 4 个和之后副本的放置位置,同时保持每个机架的副本数量低于上限,上限值通常为 `(复制系数 - 1)/机架数量 + 2`,需要注意的是不允许同一个 `dataNode` 上具有同一个块的多个副本。
+
+
+
+### 2.5 副本的选择
+
+为了最大限度地减少带宽消耗和读取延迟,HDFS 在执行读取请求时,优先读取距离读取器最近的副本。如果在与读取器节点相同的机架上存在副本,则优先选择该副本。如果 HDFS 群集跨越多个数据中心,则优先选择本地数据中心上的副本。
+
+
+
+### 2.6 架构的稳定性
+
+#### 1. 心跳机制和重新复制
+
+每个 DataNode 定期向 NameNode 发送心跳消息,如果超过指定时间没有收到心跳消息,则将 DataNode 标记为死亡。NameNode 不会将任何新的 IO 请求转发给标记为死亡的 DataNode,也不会再使用这些 DataNode 上的数据。 由于数据不再可用,可能会导致某些块的复制因子小于其指定值,NameNode 会跟踪这些块,并在必要的时候进行重新复制。
+
+#### 2. 数据的完整性
+
+由于存储设备故障等原因,存储在 DataNode 上的数据块也会发生损坏。为了避免读取到已经损坏的数据而导致错误,HDFS 提供了数据完整性校验机制来保证数据的完整性,具体操作如下:
+
+当客户端创建 HDFS 文件时,它会计算文件的每个块的 ` 校验和 `,并将 ` 校验和 ` 存储在同一 HDFS 命名空间下的单独的隐藏文件中。当客户端检索文件内容时,它会验证从每个 DataNode 接收的数据是否与存储在关联校验和文件中的 ` 校验和 ` 匹配。如果匹配失败,则证明数据已经损坏,此时客户端会选择从其他 DataNode 获取该块的其他可用副本。
+
+#### 3.元数据的磁盘故障
+
+`FsImage` 和 `EditLog` 是 HDFS 的核心数据,这些数据的意外丢失可能会导致整个 HDFS 服务不可用。为了避免这个问题,可以配置 NameNode 使其支持 `FsImage` 和 `EditLog` 多副本同步,这样 `FsImage` 或 `EditLog` 的任何改变都会引起每个副本 `FsImage` 和 `EditLog` 的同步更新。
+
+#### 4.支持快照
+
+快照支持在特定时刻存储数据副本,在数据意外损坏时,可以通过回滚操作恢复到健康的数据状态。
+
+
+
+## 三、HDFS 的特点
+
+### 3.1 高容错
+
+由于 HDFS 采用数据的多副本方案,所以部分硬件的损坏不会导致全部数据的丢失。
+
+### 3.2 高吞吐量
+
+HDFS 设计的重点是支持高吞吐量的数据访问,而不是低延迟的数据访问。
+
+### 3.3 大文件支持
+
+HDFS 适合于大文件的存储,文档的大小应该是是 GB 到 TB 级别的。
+
+### 3.3 简单一致性模型
+
+HDFS 更适合于一次写入多次读取 (write-once-read-many) 的访问模型。支持将内容追加到文件末尾,但不支持数据的随机访问,不能从文件任意位置新增数据。
+
+### 3.4 跨平台移植性
+
+HDFS 具有良好的跨平台移植性,这使得其他大数据计算框架都将其作为数据持久化存储的首选方案。
+
+
+
+## 附:图解HDFS存储原理
+
+> 说明:以下图片引用自博客:[翻译经典 HDFS 原理讲解漫画](https://blog.csdn.net/hudiefenmu/article/details/37655491)
+
+### 1. HDFS写数据原理
+
+
+
+
+
+
+
+
+
+### 2. HDFS读数据原理
+
+
+
+
+
+### 3. HDFS故障类型和其检测方法
+
+
+
+
+
+
+
+**第二部分:读写故障的处理**
+
+
+
+
+
+**第三部分:DataNode 故障处理**
+
+
+
+
+
+**副本布局策略**:
+
+
+
+
+
+## 参考资料
+
+1. [Apache Hadoop 2.9.2 > HDFS Architecture](http://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-hdfs/HdfsDesign.html)
+2. Tom White . hadoop 权威指南 [M] . 清华大学出版社 . 2017.
+3. [翻译经典 HDFS 原理讲解漫画](https://blog.csdn.net/hudiefenmu/article/details/37655491)
+
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hadoop-MapReduce.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hadoop-MapReduce.md"
new file mode 100644
index 0000000..74f5ab9
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hadoop-MapReduce.md"
@@ -0,0 +1,384 @@
+# 分布式计算框架——MapReduce
+
+
+一、MapReduce概述
+二、MapReduce编程模型简述
+三、combiner & partitioner
+四、MapReduce词频统计案例
+ 4.1 项目简介
+ 4.2 项目依赖
+ 4.3 WordCountMapper
+ 4.4 WordCountReducer
+ 4.4 WordCountApp
+ 4.5 提交到服务器运行
+五、词频统计案例进阶之Combiner
+六、词频统计案例进阶之Partitioner
+
+
+
+
+
+## 一、MapReduce概述
+
+Hadoop MapReduce 是一个分布式计算框架,用于编写批处理应用程序。编写好的程序可以提交到 Hadoop 集群上用于并行处理大规模的数据集。
+
+MapReduce 作业通过将输入的数据集拆分为独立的块,这些块由 `map` 以并行的方式处理,框架对 `map` 的输出进行排序,然后输入到 `reduce` 中。MapReduce 框架专门用于 `` 键值对处理,它将作业的输入视为一组 `` 对,并生成一组 `` 对作为输出。输出和输出的 `key` 和 `value` 都必须实现[Writable](http://hadoop.apache.org/docs/stable/api/org/apache/hadoop/io/Writable.html) 接口。
+
+```
+(input) -> map -> -> combine -> -> reduce -> (output)
+```
+
+
+
+## 二、MapReduce编程模型简述
+
+这里以词频统计为例进行说明,MapReduce 处理的流程如下:
+
+
+
+1. **input** : 读取文本文件;
+
+2. **splitting** : 将文件按照行进行拆分,此时得到的 `K1` 行数,`V1` 表示对应行的文本内容;
+
+3. **mapping** : 并行将每一行按照空格进行拆分,拆分得到的 `List(K2,V2)`,其中 `K2` 代表每一个单词,由于是做词频统计,所以 `V2` 的值为 1,代表出现 1 次;
+4. **shuffling**:由于 `Mapping` 操作可能是在不同的机器上并行处理的,所以需要通过 `shuffling` 将相同 `key` 值的数据分发到同一个节点上去合并,这样才能统计出最终的结果,此时得到 `K2` 为每一个单词,`List(V2)` 为可迭代集合,`V2` 就是 Mapping 中的 V2;
+5. **Reducing** : 这里的案例是统计单词出现的总次数,所以 `Reducing` 对 `List(V2)` 进行归约求和操作,最终输出。
+
+MapReduce 编程模型中 `splitting` 和 `shuffing` 操作都是由框架实现的,需要我们自己编程实现的只有 `mapping` 和 `reducing`,这也就是 MapReduce 这个称呼的来源。
+
+
+
+## 三、combiner & partitioner
+
+
+
+### 3.1 InputFormat & RecordReaders
+
+`InputFormat` 将输出文件拆分为多个 `InputSplit`,并由 `RecordReaders` 将 `InputSplit` 转换为标准的键值对,作为 map 的输出。这一步的意义在于只有先进行逻辑拆分并转为标准的键值对格式后,才能为多个 `map` 提供输入,以便进行并行处理。
+
+
+
+### 3.2 Combiner
+
+`combiner` 是 `map` 运算后的可选操作,它实际上是一个本地化的 `reduce` 操作,它主要是在 `map` 计算出中间文件后做一个简单的合并重复 `key` 值的操作。这里以词频统计为例:
+
+`map` 在遇到一个 hadoop 的单词时就会记录为 1,但是这篇文章里 hadoop 可能会出现 n 多次,那么 `map` 输出文件冗余就会很多,因此在 `reduce` 计算前对相同的 key 做一个合并操作,那么需要传输的数据量就会减少,传输效率就可以得到提升。
+
+但并非所有场景都适合使用 `combiner`,使用它的原则是 `combiner` 的输出不会影响到 `reduce` 计算的最终输入,例如:求总数,最大值,最小值时都可以使用 `combiner`,但是做平均值计算则不能使用 `combiner`。
+
+不使用 combiner 的情况:
+
+
+
+使用 combiner 的情况:
+
+
+
+
+
+可以看到使用 combiner 的时候,需要传输到 reducer 中的数据由 12keys,降低到 10keys。降低的幅度取决于你 keys 的重复率,下文词频统计案例会演示用 combiner 降低数百倍的传输量。
+
+### 3.3 Partitioner
+
+`partitioner` 可以理解成分类器,将 `map` 的输出按照 key 值的不同分别分给对应的 `reducer`,支持自定义实现,下文案例会给出演示。
+
+
+
+## 四、MapReduce词频统计案例
+
+### 4.1 项目简介
+
+这里给出一个经典的词频统计的案例:统计如下样本数据中每个单词出现的次数。
+
+```properties
+Spark HBase
+Hive Flink Storm Hadoop HBase Spark
+Flink
+HBase Storm
+HBase Hadoop Hive Flink
+HBase Flink Hive Storm
+Hive Flink Hadoop
+HBase Hive
+Hadoop Spark HBase Storm
+HBase Hadoop Hive Flink
+HBase Flink Hive Storm
+Hive Flink Hadoop
+HBase Hive
+```
+
+为方便大家开发,我在项目源码中放置了一个工具类 `WordCountDataUtils`,用于模拟产生词频统计的样本,生成的文件支持输出到本地或者直接写到 HDFS 上。
+
+> 项目完整源码下载地址:[hadoop-word-count](https://github.com/heibaiying/BigData-Notes/tree/master/code/Hadoop/hadoop-word-count)
+
+
+
+### 4.2 项目依赖
+
+想要进行 MapReduce 编程,需要导入 `hadoop-client` 依赖:
+
+```xml
+
+ org.apache.hadoop
+ hadoop-client
+ ${hadoop.version}
+
+```
+
+### 4.3 WordCountMapper
+
+将每行数据按照指定分隔符进行拆分。这里需要注意在 MapReduce 中必须使用 Hadoop 定义的类型,因为 Hadoop 预定义的类型都是可序列化,可比较的,所有类型均实现了 `WritableComparable` 接口。
+
+```java
+public class WordCountMapper extends Mapper {
+
+ @Override
+ protected void map(LongWritable key, Text value, Context context) throws IOException,
+ InterruptedException {
+ String[] words = value.toString().split("\t");
+ for (String word : words) {
+ context.write(new Text(word), new IntWritable(1));
+ }
+ }
+
+}
+```
+
+`WordCountMapper` 对应下图的 Mapping 操作:
+
+
+
+
+
+`WordCountMapper` 继承自 `Mappe` 类,这是一个泛型类,定义如下:
+
+```java
+WordCountMapper extends Mapper
+
+public class Mapper {
+ ......
+}
+```
+
++ **KEYIN** : `mapping` 输入 key 的类型,即每行的偏移量 (每行第一个字符在整个文本中的位置),`Long` 类型,对应 Hadoop 中的 `LongWritable` 类型;
++ **VALUEIN** : `mapping` 输入 value 的类型,即每行数据;`String` 类型,对应 Hadoop 中 `Text` 类型;
++ **KEYOUT** :`mapping` 输出的 key 的类型,即每个单词;`String` 类型,对应 Hadoop 中 `Text` 类型;
++ **VALUEOUT**:`mapping` 输出 value 的类型,即每个单词出现的次数;这里用 `int` 类型,对应 `IntWritable` 类型。
+
+
+
+### 4.4 WordCountReducer
+
+在 Reduce 中进行单词出现次数的统计:
+
+```java
+public class WordCountReducer extends Reducer {
+
+ @Override
+ protected void reduce(Text key, Iterable values, Context context) throws IOException,
+ InterruptedException {
+ int count = 0;
+ for (IntWritable value : values) {
+ count += value.get();
+ }
+ context.write(key, new IntWritable(count));
+ }
+}
+```
+
+如下图,`shuffling` 的输出是 reduce 的输入。这里的 key 是每个单词,values 是一个可迭代的数据类型,类似 `(1,1,1,...)`。
+
+
+
+### 4.4 WordCountApp
+
+组装 MapReduce 作业,并提交到服务器运行,代码如下:
+
+```java
+
+/**
+ * 组装作业 并提交到集群运行
+ */
+public class WordCountApp {
+
+
+ // 这里为了直观显示参数 使用了硬编码,实际开发中可以通过外部传参
+ private static final String HDFS_URL = "hdfs://192.168.0.107:8020";
+ private static final String HADOOP_USER_NAME = "root";
+
+ public static void main(String[] args) throws Exception {
+
+ // 文件输入路径和输出路径由外部传参指定
+ if (args.length < 2) {
+ System.out.println("Input and output paths are necessary!");
+ return;
+ }
+
+ // 需要指明 hadoop 用户名,否则在 HDFS 上创建目录时可能会抛出权限不足的异常
+ System.setProperty("HADOOP_USER_NAME", HADOOP_USER_NAME);
+
+ Configuration configuration = new Configuration();
+ // 指明 HDFS 的地址
+ configuration.set("fs.defaultFS", HDFS_URL);
+
+ // 创建一个 Job
+ Job job = Job.getInstance(configuration);
+
+ // 设置运行的主类
+ job.setJarByClass(WordCountApp.class);
+
+ // 设置 Mapper 和 Reducer
+ job.setMapperClass(WordCountMapper.class);
+ job.setReducerClass(WordCountReducer.class);
+
+ // 设置 Mapper 输出 key 和 value 的类型
+ job.setMapOutputKeyClass(Text.class);
+ job.setMapOutputValueClass(IntWritable.class);
+
+ // 设置 Reducer 输出 key 和 value 的类型
+ job.setOutputKeyClass(Text.class);
+ job.setOutputValueClass(IntWritable.class);
+
+ // 如果输出目录已经存在,则必须先删除,否则重复运行程序时会抛出异常
+ FileSystem fileSystem = FileSystem.get(new URI(HDFS_URL), configuration, HADOOP_USER_NAME);
+ Path outputPath = new Path(args[1]);
+ if (fileSystem.exists(outputPath)) {
+ fileSystem.delete(outputPath, true);
+ }
+
+ // 设置作业输入文件和输出文件的路径
+ FileInputFormat.setInputPaths(job, new Path(args[0]));
+ FileOutputFormat.setOutputPath(job, outputPath);
+
+ // 将作业提交到群集并等待它完成,参数设置为 true 代表打印显示对应的进度
+ boolean result = job.waitForCompletion(true);
+
+ // 关闭之前创建的 fileSystem
+ fileSystem.close();
+
+ // 根据作业结果,终止当前运行的 Java 虚拟机,退出程序
+ System.exit(result ? 0 : -1);
+
+ }
+}
+```
+
+需要注意的是:如果不设置 `Mapper` 操作的输出类型,则程序默认它和 `Reducer` 操作输出的类型相同。
+
+### 4.5 提交到服务器运行
+
+在实际开发中,可以在本机配置 hadoop 开发环境,直接在 IDE 中启动进行测试。这里主要介绍一下打包提交到服务器运行。由于本项目没有使用除 Hadoop 外的第三方依赖,直接打包即可:
+
+```shell
+# mvn clean package
+```
+
+使用以下命令提交作业:
+
+```shell
+hadoop jar /usr/appjar/hadoop-word-count-1.0.jar \
+com.heibaiying.WordCountApp \
+/wordcount/input.txt /wordcount/output/WordCountApp
+```
+
+作业完成后查看 HDFS 上生成目录:
+
+```shell
+# 查看目录
+hadoop fs -ls /wordcount/output/WordCountApp
+
+# 查看统计结果
+hadoop fs -cat /wordcount/output/WordCountApp/part-r-00000
+```
+
+
+
+
+
+## 五、词频统计案例进阶之Combiner
+
+### 5.1 代码实现
+
+想要使用 `combiner` 功能只要在组装作业时,添加下面一行代码即可:
+
+```java
+// 设置 Combiner
+job.setCombinerClass(WordCountReducer.class);
+```
+
+### 5.2 执行结果
+
+加入 `combiner` 后统计结果是不会有变化的,但是可以从打印的日志看出 `combiner` 的效果:
+
+没有加入 `combiner` 的打印日志:
+
+
+
+加入 `combiner` 后的打印日志如下:
+
+
+
+这里我们只有一个输入文件并且小于 128M,所以只有一个 Map 进行处理。可以看到经过 combiner 后,records 由 `3519` 降低为 `6`(样本中单词种类就只有 6 种),在这个用例中 combiner 就能极大地降低需要传输的数据量。
+
+
+
+## 六、词频统计案例进阶之Partitioner
+
+### 6.1 默认的Partitioner
+
+这里假设有个需求:将不同单词的统计结果输出到不同文件。这种需求实际上比较常见,比如统计产品的销量时,需要将结果按照产品种类进行拆分。要实现这个功能,就需要用到自定义 `Partitioner`。
+
+这里先介绍下 MapReduce 默认的分类规则:在构建 job 时候,如果不指定,默认的使用的是 `HashPartitioner`:对 key 值进行哈希散列并对 `numReduceTasks` 取余。其实现如下:
+
+```java
+public class HashPartitioner extends Partitioner {
+
+ public int getPartition(K key, V value,
+ int numReduceTasks) {
+ return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
+ }
+
+}
+```
+
+### 6.2 自定义Partitioner
+
+这里我们继承 `Partitioner` 自定义分类规则,这里按照单词进行分类:
+
+```java
+public class CustomPartitioner extends Partitioner {
+
+ public int getPartition(Text text, IntWritable intWritable, int numPartitions) {
+ return WordCountDataUtils.WORD_LIST.indexOf(text.toString());
+ }
+}
+```
+
+在构建 `job` 时候指定使用我们自己的分类规则,并设置 `reduce` 的个数:
+
+```java
+// 设置自定义分区规则
+job.setPartitionerClass(CustomPartitioner.class);
+// 设置 reduce 个数
+job.setNumReduceTasks(WordCountDataUtils.WORD_LIST.size());
+```
+
+
+
+### 6.3 执行结果
+
+执行结果如下,分别生成 6 个文件,每个文件中为对应单词的统计结果:
+
+
+
+
+
+
+
+## 参考资料
+
+1. [分布式计算框架 MapReduce](https://zhuanlan.zhihu.com/p/28682581)
+2. [Apache Hadoop 2.9.2 > MapReduce Tutorial](http://hadoop.apache.org/docs/stable/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html)
+3. [MapReduce - Combiners]( https://www.tutorialscampus.com/tutorials/map-reduce/combiners.htm)
+
+
+
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hadoop-YARN.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hadoop-YARN.md"
new file mode 100644
index 0000000..91e879e
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hadoop-YARN.md"
@@ -0,0 +1,128 @@
+# 集群资源管理器——YARN
+
+
+一、hadoop yarn 简介
+二、YARN架构
+ 1. ResourceManager
+ 2. NodeManager
+ 3. ApplicationMaster
+ 4. Contain
+三、YARN工作原理简述
+四、YARN工作原理详述
+五、提交作业到YARN上运行
+
+
+
+
+## 一、hadoop yarn 简介
+
+**Apache YARN** (Yet Another Resource Negotiator) 是 hadoop 2.0 引入的集群资源管理系统。用户可以将各种服务框架部署在 YARN 上,由 YARN 进行统一地管理和资源分配。
+
+
+
+
+
+## 二、YARN架构
+
+
+
+### 1. ResourceManager
+
+`ResourceManager` 通常在独立的机器上以后台进程的形式运行,它是整个集群资源的主要协调者和管理者。`ResourceManager` 负责给用户提交的所有应用程序分配资源,它根据应用程序优先级、队列容量、ACLs、数据位置等信息,做出决策,然后以共享的、安全的、多租户的方式制定分配策略,调度集群资源。
+
+### 2. NodeManager
+
+`NodeManager` 是 YARN 集群中的每个具体节点的管理者。主要负责该节点内所有容器的生命周期的管理,监视资源和跟踪节点健康。具体如下:
+
+- 启动时向 `ResourceManager` 注册并定时发送心跳消息,等待 `ResourceManager` 的指令;
+- 维护 `Container` 的生命周期,监控 `Container` 的资源使用情况;
+- 管理任务运行时的相关依赖,根据 `ApplicationMaster` 的需要,在启动 `Container` 之前将需要的程序及其依赖拷贝到本地。
+
+### 3. ApplicationMaster
+
+在用户提交一个应用程序时,YARN 会启动一个轻量级的进程 `ApplicationMaster`。`ApplicationMaster` 负责协调来自 `ResourceManager` 的资源,并通过 `NodeManager` 监视容器内资源的使用情况,同时还负责任务的监控与容错。具体如下:
+
+- 根据应用的运行状态来决定动态计算资源需求;
+- 向 `ResourceManager` 申请资源,监控申请的资源的使用情况;
+- 跟踪任务状态和进度,报告资源的使用情况和应用的进度信息;
+- 负责任务的容错。
+
+### 4. Contain
+
+`Container` 是 YARN 中的资源抽象,它封装了某个节点上的多维度资源,如内存、CPU、磁盘、网络等。当 AM 向 RM 申请资源时,RM 为 AM 返回的资源是用 `Container` 表示的。YARN 会为每个任务分配一个 `Container`,该任务只能使用该 `Container` 中描述的资源。`ApplicationMaster` 可在 `Container` 内运行任何类型的任务。例如,`MapReduce ApplicationMaster` 请求一个容器来启动 map 或 reduce 任务,而 `Giraph ApplicationMaster` 请求一个容器来运行 Giraph 任务。
+
+
+
+
+
+## 三、YARN工作原理简述
+
+
+
+1. `Client` 提交作业到 YARN 上;
+
+2. `Resource Manager` 选择一个 `Node Manager`,启动一个 `Container` 并运行 `Application Master` 实例;
+
+3. `Application Master` 根据实际需要向 `Resource Manager` 请求更多的 `Container` 资源(如果作业很小, 应用管理器会选择在其自己的 JVM 中运行任务);
+
+4. `Application Master` 通过获取到的 `Container` 资源执行分布式计算。
+
+
+
+## 四、YARN工作原理详述
+
+
+
+
+
+#### 1. 作业提交
+
+client 调用 job.waitForCompletion 方法,向整个集群提交 MapReduce 作业 (第 1 步) 。新的作业 ID(应用 ID) 由资源管理器分配 (第 2 步)。作业的 client 核实作业的输出, 计算输入的 split, 将作业的资源 (包括 Jar 包,配置文件, split 信息) 拷贝给 HDFS(第 3 步)。 最后, 通过调用资源管理器的 submitApplication() 来提交作业 (第 4 步)。
+
+#### 2. 作业初始化
+
+当资源管理器收到 submitApplciation() 的请求时, 就将该请求发给调度器 (scheduler), 调度器分配 container, 然后资源管理器在该 container 内启动应用管理器进程, 由节点管理器监控 (第 5 步)。
+
+MapReduce 作业的应用管理器是一个主类为 MRAppMaster 的 Java 应用,其通过创造一些 bookkeeping 对象来监控作业的进度, 得到任务的进度和完成报告 (第 6 步)。然后其通过分布式文件系统得到由客户端计算好的输入 split(第 7 步),然后为每个输入 split 创建一个 map 任务, 根据 mapreduce.job.reduces 创建 reduce 任务对象。
+
+#### 3. 任务分配
+
+如果作业很小, 应用管理器会选择在其自己的 JVM 中运行任务。
+
+如果不是小作业, 那么应用管理器向资源管理器请求 container 来运行所有的 map 和 reduce 任务 (第 8 步)。这些请求是通过心跳来传输的, 包括每个 map 任务的数据位置,比如存放输入 split 的主机名和机架 (rack),调度器利用这些信息来调度任务,尽量将任务分配给存储数据的节点, 或者分配给和存放输入 split 的节点相同机架的节点。
+
+#### 4. 任务运行
+
+当一个任务由资源管理器的调度器分配给一个 container 后,应用管理器通过联系节点管理器来启动 container(第 9 步)。任务由一个主类为 YarnChild 的 Java 应用执行, 在运行任务之前首先本地化任务需要的资源,比如作业配置,JAR 文件, 以及分布式缓存的所有文件 (第 10 步。 最后, 运行 map 或 reduce 任务 (第 11 步)。
+
+YarnChild 运行在一个专用的 JVM 中, 但是 YARN 不支持 JVM 重用。
+
+#### 5. 进度和状态更新
+
+YARN 中的任务将其进度和状态 (包括 counter) 返回给应用管理器, 客户端每秒 (通 mapreduce.client.progressmonitor.pollinterval 设置) 向应用管理器请求进度更新, 展示给用户。
+
+#### 6. 作业完成
+
+除了向应用管理器请求作业进度外, 客户端每 5 分钟都会通过调用 waitForCompletion() 来检查作业是否完成,时间间隔可以通过 mapreduce.client.completion.pollinterval 来设置。作业完成之后, 应用管理器和 container 会清理工作状态, OutputCommiter 的作业清理方法也会被调用。作业的信息会被作业历史服务器存储以备之后用户核查。
+
+
+
+## 五、提交作业到YARN上运行
+
+这里以提交 Hadoop Examples 中计算 Pi 的 MApReduce 程序为例,相关 Jar 包在 Hadoop 安装目录的 `share/hadoop/mapreduce` 目录下:
+
+```shell
+# 提交格式: hadoop jar jar包路径 主类名称 主类参数
+# hadoop jar hadoop-mapreduce-examples-2.6.0-cdh5.15.2.jar pi 3 3
+```
+
+
+
+## 参考资料
+
+1. [初步掌握 Yarn 的架构及原理](https://www.cnblogs.com/codeOfLife/p/5492740.html)
+
+2. [Apache Hadoop 2.9.2 > Apache Hadoop YARN](http://hadoop.apache.org/docs/stable/hadoop-yarn/hadoop-yarn-site/YARN.html)
+
+
+
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase_Java_API.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase_Java_API.md"
new file mode 100644
index 0000000..d73cb58
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase_Java_API.md"
@@ -0,0 +1,761 @@
+# HBase Java API 的基本使用
+
+
+一、简述
+二、Java API 1.x 基本使用
+三、Java API 2.x 基本使用
+四、正确连接Hbase
+
+
+
+
+## 一、简述
+
+截至到目前 (2019.04),HBase 有两个主要的版本,分别是 1.x 和 2.x ,两个版本的 Java API 有所不同,1.x 中某些方法在 2.x 中被标识为 `@deprecated` 过时。所以下面关于 API 的样例,我会分别给出 1.x 和 2.x 两个版本。完整的代码见本仓库:
+
+>+ [Java API 1.x Examples](https://github.com/heibaiying/BigData-Notes/tree/master/code/Hbase/hbase-java-api-1.x)
+>
+>+ [Java API 2.x Examples](https://github.com/heibaiying/BigData-Notes/tree/master/code/Hbase/hbase-java-api-2.x)
+
+同时你使用的客户端的版本必须与服务端版本保持一致,如果用 2.x 版本的客户端代码去连接 1.x 版本的服务端,会抛出 `NoSuchColumnFamilyException` 等异常。
+
+## 二、Java API 1.x 基本使用
+
+#### 2.1 新建Maven工程,导入项目依赖
+
+要使用 Java API 操作 HBase,需要引入 `hbase-client`。这里选取的 `HBase Client` 的版本为 `1.2.0`。
+
+```xml
+
+ org.apache.hbase
+ hbase-client
+ 1.2.0
+
+```
+
+#### 2.2 API 基本使用
+
+```java
+public class HBaseUtils {
+
+ private static Connection connection;
+
+ static {
+ Configuration configuration = HBaseConfiguration.create();
+ configuration.set("hbase.zookeeper.property.clientPort", "2181");
+ // 如果是集群 则主机名用逗号分隔
+ configuration.set("hbase.zookeeper.quorum", "hadoop001");
+ try {
+ connection = ConnectionFactory.createConnection(configuration);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 创建 HBase 表
+ *
+ * @param tableName 表名
+ * @param columnFamilies 列族的数组
+ */
+ public static boolean createTable(String tableName, List columnFamilies) {
+ try {
+ HBaseAdmin admin = (HBaseAdmin) connection.getAdmin();
+ if (admin.tableExists(tableName)) {
+ return false;
+ }
+ HTableDescriptor tableDescriptor = new HTableDescriptor(TableName.valueOf(tableName));
+ columnFamilies.forEach(columnFamily -> {
+ HColumnDescriptor columnDescriptor = new HColumnDescriptor(columnFamily);
+ columnDescriptor.setMaxVersions(1);
+ tableDescriptor.addFamily(columnDescriptor);
+ });
+ admin.createTable(tableDescriptor);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return true;
+ }
+
+
+ /**
+ * 删除 hBase 表
+ *
+ * @param tableName 表名
+ */
+ public static boolean deleteTable(String tableName) {
+ try {
+ HBaseAdmin admin = (HBaseAdmin) connection.getAdmin();
+ // 删除表前需要先禁用表
+ admin.disableTable(tableName);
+ admin.deleteTable(tableName);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return true;
+ }
+
+ /**
+ * 插入数据
+ *
+ * @param tableName 表名
+ * @param rowKey 唯一标识
+ * @param columnFamilyName 列族名
+ * @param qualifier 列标识
+ * @param value 数据
+ */
+ public static boolean putRow(String tableName, String rowKey, String columnFamilyName, String qualifier,
+ String value) {
+ try {
+ Table table = connection.getTable(TableName.valueOf(tableName));
+ Put put = new Put(Bytes.toBytes(rowKey));
+ put.addColumn(Bytes.toBytes(columnFamilyName), Bytes.toBytes(qualifier), Bytes.toBytes(value));
+ table.put(put);
+ table.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return true;
+ }
+
+
+ /**
+ * 插入数据
+ *
+ * @param tableName 表名
+ * @param rowKey 唯一标识
+ * @param columnFamilyName 列族名
+ * @param pairList 列标识和值的集合
+ */
+ public static boolean putRow(String tableName, String rowKey, String columnFamilyName, List> pairList) {
+ try {
+ Table table = connection.getTable(TableName.valueOf(tableName));
+ Put put = new Put(Bytes.toBytes(rowKey));
+ pairList.forEach(pair -> put.addColumn(Bytes.toBytes(columnFamilyName), Bytes.toBytes(pair.getKey()), Bytes.toBytes(pair.getValue())));
+ table.put(put);
+ table.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return true;
+ }
+
+
+ /**
+ * 根据 rowKey 获取指定行的数据
+ *
+ * @param tableName 表名
+ * @param rowKey 唯一标识
+ */
+ public static Result getRow(String tableName, String rowKey) {
+ try {
+ Table table = connection.getTable(TableName.valueOf(tableName));
+ Get get = new Get(Bytes.toBytes(rowKey));
+ return table.get(get);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+
+ /**
+ * 获取指定行指定列 (cell) 的最新版本的数据
+ *
+ * @param tableName 表名
+ * @param rowKey 唯一标识
+ * @param columnFamily 列族
+ * @param qualifier 列标识
+ */
+ public static String getCell(String tableName, String rowKey, String columnFamily, String qualifier) {
+ try {
+ Table table = connection.getTable(TableName.valueOf(tableName));
+ Get get = new Get(Bytes.toBytes(rowKey));
+ if (!get.isCheckExistenceOnly()) {
+ get.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(qualifier));
+ Result result = table.get(get);
+ byte[] resultValue = result.getValue(Bytes.toBytes(columnFamily), Bytes.toBytes(qualifier));
+ return Bytes.toString(resultValue);
+ } else {
+ return null;
+ }
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+
+ /**
+ * 检索全表
+ *
+ * @param tableName 表名
+ */
+ public static ResultScanner getScanner(String tableName) {
+ try {
+ Table table = connection.getTable(TableName.valueOf(tableName));
+ Scan scan = new Scan();
+ return table.getScanner(scan);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+
+ /**
+ * 检索表中指定数据
+ *
+ * @param tableName 表名
+ * @param filterList 过滤器
+ */
+
+ public static ResultScanner getScanner(String tableName, FilterList filterList) {
+ try {
+ Table table = connection.getTable(TableName.valueOf(tableName));
+ Scan scan = new Scan();
+ scan.setFilter(filterList);
+ return table.getScanner(scan);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * 检索表中指定数据
+ *
+ * @param tableName 表名
+ * @param startRowKey 起始 RowKey
+ * @param endRowKey 终止 RowKey
+ * @param filterList 过滤器
+ */
+
+ public static ResultScanner getScanner(String tableName, String startRowKey, String endRowKey,
+ FilterList filterList) {
+ try {
+ Table table = connection.getTable(TableName.valueOf(tableName));
+ Scan scan = new Scan();
+ scan.setStartRow(Bytes.toBytes(startRowKey));
+ scan.setStopRow(Bytes.toBytes(endRowKey));
+ scan.setFilter(filterList);
+ return table.getScanner(scan);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * 删除指定行记录
+ *
+ * @param tableName 表名
+ * @param rowKey 唯一标识
+ */
+ public static boolean deleteRow(String tableName, String rowKey) {
+ try {
+ Table table = connection.getTable(TableName.valueOf(tableName));
+ Delete delete = new Delete(Bytes.toBytes(rowKey));
+ table.delete(delete);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return true;
+ }
+
+
+ /**
+ * 删除指定行的指定列
+ *
+ * @param tableName 表名
+ * @param rowKey 唯一标识
+ * @param familyName 列族
+ * @param qualifier 列标识
+ */
+ public static boolean deleteColumn(String tableName, String rowKey, String familyName,
+ String qualifier) {
+ try {
+ Table table = connection.getTable(TableName.valueOf(tableName));
+ Delete delete = new Delete(Bytes.toBytes(rowKey));
+ delete.addColumn(Bytes.toBytes(familyName), Bytes.toBytes(qualifier));
+ table.delete(delete);
+ table.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return true;
+ }
+
+}
+```
+
+### 2.3 单元测试
+
+以单元测试的方式对上面封装的 API 进行测试。
+
+```java
+public class HBaseUtilsTest {
+
+ private static final String TABLE_NAME = "class";
+ private static final String TEACHER = "teacher";
+ private static final String STUDENT = "student";
+
+ @Test
+ public void createTable() {
+ // 新建表
+ List columnFamilies = Arrays.asList(TEACHER, STUDENT);
+ boolean table = HBaseUtils.createTable(TABLE_NAME, columnFamilies);
+ System.out.println("表创建结果:" + table);
+ }
+
+ @Test
+ public void insertData() {
+ List> pairs1 = Arrays.asList(new Pair<>("name", "Tom"),
+ new Pair<>("age", "22"),
+ new Pair<>("gender", "1"));
+ HBaseUtils.putRow(TABLE_NAME, "rowKey1", STUDENT, pairs1);
+
+ List> pairs2 = Arrays.asList(new Pair<>("name", "Jack"),
+ new Pair<>("age", "33"),
+ new Pair<>("gender", "2"));
+ HBaseUtils.putRow(TABLE_NAME, "rowKey2", STUDENT, pairs2);
+
+ List> pairs3 = Arrays.asList(new Pair<>("name", "Mike"),
+ new Pair<>("age", "44"),
+ new Pair<>("gender", "1"));
+ HBaseUtils.putRow(TABLE_NAME, "rowKey3", STUDENT, pairs3);
+ }
+
+
+ @Test
+ public void getRow() {
+ Result result = HBaseUtils.getRow(TABLE_NAME, "rowKey1");
+ if (result != null) {
+ System.out.println(Bytes
+ .toString(result.getValue(Bytes.toBytes(STUDENT), Bytes.toBytes("name"))));
+ }
+
+ }
+
+ @Test
+ public void getCell() {
+ String cell = HBaseUtils.getCell(TABLE_NAME, "rowKey2", STUDENT, "age");
+ System.out.println("cell age :" + cell);
+
+ }
+
+ @Test
+ public void getScanner() {
+ ResultScanner scanner = HBaseUtils.getScanner(TABLE_NAME);
+ if (scanner != null) {
+ scanner.forEach(result -> System.out.println(Bytes.toString(result.getRow()) + "->" + Bytes
+ .toString(result.getValue(Bytes.toBytes(STUDENT), Bytes.toBytes("name")))));
+ scanner.close();
+ }
+ }
+
+
+ @Test
+ public void getScannerWithFilter() {
+ FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL);
+ SingleColumnValueFilter nameFilter = new SingleColumnValueFilter(Bytes.toBytes(STUDENT),
+ Bytes.toBytes("name"), CompareOperator.EQUAL, Bytes.toBytes("Jack"));
+ filterList.addFilter(nameFilter);
+ ResultScanner scanner = HBaseUtils.getScanner(TABLE_NAME, filterList);
+ if (scanner != null) {
+ scanner.forEach(result -> System.out.println(Bytes.toString(result.getRow()) + "->" + Bytes
+ .toString(result.getValue(Bytes.toBytes(STUDENT), Bytes.toBytes("name")))));
+ scanner.close();
+ }
+ }
+
+ @Test
+ public void deleteColumn() {
+ boolean b = HBaseUtils.deleteColumn(TABLE_NAME, "rowKey2", STUDENT, "age");
+ System.out.println("删除结果: " + b);
+ }
+
+ @Test
+ public void deleteRow() {
+ boolean b = HBaseUtils.deleteRow(TABLE_NAME, "rowKey2");
+ System.out.println("删除结果: " + b);
+ }
+
+ @Test
+ public void deleteTable() {
+ boolean b = HBaseUtils.deleteTable(TABLE_NAME);
+ System.out.println("删除结果: " + b);
+ }
+}
+```
+
+
+
+## 三、Java API 2.x 基本使用
+
+#### 3.1 新建Maven工程,导入项目依赖
+
+这里选取的 `HBase Client` 的版本为最新的 `2.1.4`。
+
+```xml
+
+ org.apache.hbase
+ hbase-client
+ 2.1.4
+
+```
+
+#### 3.2 API 的基本使用
+
+2.x 版本相比于 1.x 废弃了一部分方法,关于废弃的方法在源码中都会指明新的替代方法,比如,在 2.x 中创建表时:`HTableDescriptor` 和 `HColumnDescriptor` 等类都标识为废弃,取而代之的是使用 `TableDescriptorBuilder` 和 `ColumnFamilyDescriptorBuilder` 来定义表和列族。
+
+
+
+
+
+以下为 HBase 2.x 版本 Java API 的使用示例:
+
+```java
+public class HBaseUtils {
+
+ private static Connection connection;
+
+ static {
+ Configuration configuration = HBaseConfiguration.create();
+ configuration.set("hbase.zookeeper.property.clientPort", "2181");
+ // 如果是集群 则主机名用逗号分隔
+ configuration.set("hbase.zookeeper.quorum", "hadoop001");
+ try {
+ connection = ConnectionFactory.createConnection(configuration);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 创建 HBase 表
+ *
+ * @param tableName 表名
+ * @param columnFamilies 列族的数组
+ */
+ public static boolean createTable(String tableName, List columnFamilies) {
+ try {
+ HBaseAdmin admin = (HBaseAdmin) connection.getAdmin();
+ if (admin.tableExists(TableName.valueOf(tableName))) {
+ return false;
+ }
+ TableDescriptorBuilder tableDescriptor = TableDescriptorBuilder.newBuilder(TableName.valueOf(tableName));
+ columnFamilies.forEach(columnFamily -> {
+ ColumnFamilyDescriptorBuilder cfDescriptorBuilder = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(columnFamily));
+ cfDescriptorBuilder.setMaxVersions(1);
+ ColumnFamilyDescriptor familyDescriptor = cfDescriptorBuilder.build();
+ tableDescriptor.setColumnFamily(familyDescriptor);
+ });
+ admin.createTable(tableDescriptor.build());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return true;
+ }
+
+
+ /**
+ * 删除 hBase 表
+ *
+ * @param tableName 表名
+ */
+ public static boolean deleteTable(String tableName) {
+ try {
+ HBaseAdmin admin = (HBaseAdmin) connection.getAdmin();
+ // 删除表前需要先禁用表
+ admin.disableTable(TableName.valueOf(tableName));
+ admin.deleteTable(TableName.valueOf(tableName));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return true;
+ }
+
+ /**
+ * 插入数据
+ *
+ * @param tableName 表名
+ * @param rowKey 唯一标识
+ * @param columnFamilyName 列族名
+ * @param qualifier 列标识
+ * @param value 数据
+ */
+ public static boolean putRow(String tableName, String rowKey, String columnFamilyName, String qualifier,
+ String value) {
+ try {
+ Table table = connection.getTable(TableName.valueOf(tableName));
+ Put put = new Put(Bytes.toBytes(rowKey));
+ put.addColumn(Bytes.toBytes(columnFamilyName), Bytes.toBytes(qualifier), Bytes.toBytes(value));
+ table.put(put);
+ table.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return true;
+ }
+
+
+ /**
+ * 插入数据
+ *
+ * @param tableName 表名
+ * @param rowKey 唯一标识
+ * @param columnFamilyName 列族名
+ * @param pairList 列标识和值的集合
+ */
+ public static boolean putRow(String tableName, String rowKey, String columnFamilyName, List> pairList) {
+ try {
+ Table table = connection.getTable(TableName.valueOf(tableName));
+ Put put = new Put(Bytes.toBytes(rowKey));
+ pairList.forEach(pair -> put.addColumn(Bytes.toBytes(columnFamilyName), Bytes.toBytes(pair.getKey()), Bytes.toBytes(pair.getValue())));
+ table.put(put);
+ table.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return true;
+ }
+
+
+ /**
+ * 根据 rowKey 获取指定行的数据
+ *
+ * @param tableName 表名
+ * @param rowKey 唯一标识
+ */
+ public static Result getRow(String tableName, String rowKey) {
+ try {
+ Table table = connection.getTable(TableName.valueOf(tableName));
+ Get get = new Get(Bytes.toBytes(rowKey));
+ return table.get(get);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+
+ /**
+ * 获取指定行指定列 (cell) 的最新版本的数据
+ *
+ * @param tableName 表名
+ * @param rowKey 唯一标识
+ * @param columnFamily 列族
+ * @param qualifier 列标识
+ */
+ public static String getCell(String tableName, String rowKey, String columnFamily, String qualifier) {
+ try {
+ Table table = connection.getTable(TableName.valueOf(tableName));
+ Get get = new Get(Bytes.toBytes(rowKey));
+ if (!get.isCheckExistenceOnly()) {
+ get.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(qualifier));
+ Result result = table.get(get);
+ byte[] resultValue = result.getValue(Bytes.toBytes(columnFamily), Bytes.toBytes(qualifier));
+ return Bytes.toString(resultValue);
+ } else {
+ return null;
+ }
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+
+ /**
+ * 检索全表
+ *
+ * @param tableName 表名
+ */
+ public static ResultScanner getScanner(String tableName) {
+ try {
+ Table table = connection.getTable(TableName.valueOf(tableName));
+ Scan scan = new Scan();
+ return table.getScanner(scan);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+
+ /**
+ * 检索表中指定数据
+ *
+ * @param tableName 表名
+ * @param filterList 过滤器
+ */
+
+ public static ResultScanner getScanner(String tableName, FilterList filterList) {
+ try {
+ Table table = connection.getTable(TableName.valueOf(tableName));
+ Scan scan = new Scan();
+ scan.setFilter(filterList);
+ return table.getScanner(scan);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * 检索表中指定数据
+ *
+ * @param tableName 表名
+ * @param startRowKey 起始 RowKey
+ * @param endRowKey 终止 RowKey
+ * @param filterList 过滤器
+ */
+
+ public static ResultScanner getScanner(String tableName, String startRowKey, String endRowKey,
+ FilterList filterList) {
+ try {
+ Table table = connection.getTable(TableName.valueOf(tableName));
+ Scan scan = new Scan();
+ scan.withStartRow(Bytes.toBytes(startRowKey));
+ scan.withStopRow(Bytes.toBytes(endRowKey));
+ scan.setFilter(filterList);
+ return table.getScanner(scan);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * 删除指定行记录
+ *
+ * @param tableName 表名
+ * @param rowKey 唯一标识
+ */
+ public static boolean deleteRow(String tableName, String rowKey) {
+ try {
+ Table table = connection.getTable(TableName.valueOf(tableName));
+ Delete delete = new Delete(Bytes.toBytes(rowKey));
+ table.delete(delete);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return true;
+ }
+
+
+ /**
+ * 删除指定行指定列
+ *
+ * @param tableName 表名
+ * @param rowKey 唯一标识
+ * @param familyName 列族
+ * @param qualifier 列标识
+ */
+ public static boolean deleteColumn(String tableName, String rowKey, String familyName,
+ String qualifier) {
+ try {
+ Table table = connection.getTable(TableName.valueOf(tableName));
+ Delete delete = new Delete(Bytes.toBytes(rowKey));
+ delete.addColumn(Bytes.toBytes(familyName), Bytes.toBytes(qualifier));
+ table.delete(delete);
+ table.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return true;
+ }
+
+}
+```
+
+
+
+## 四、正确连接Hbase
+
+在上面的代码中,在类加载时就初始化了 Connection 连接,并且之后的方法都是复用这个 Connection,这时我们可能会考虑是否可以使用自定义连接池来获取更好的性能表现?实际上这是没有必要的。
+
+首先官方对于 `Connection` 的使用说明如下:
+
+```properties
+Connection Pooling For applications which require high-end multithreaded
+access (e.g., web-servers or application servers that may serve many
+application threads in a single JVM), you can pre-create a Connection,
+as shown in the following example:
+
+对于高并发多线程访问的应用程序(例如,在单个 JVM 中存在的为多个线程服务的 Web 服务器或应用程序服务器),
+您只需要预先创建一个 Connection。例子如下:
+
+// Create a connection to the cluster.
+Configuration conf = HBaseConfiguration.create();
+try (Connection connection = ConnectionFactory.createConnection(conf);
+ Table table = connection.getTable(TableName.valueOf(tablename))) {
+ // use table as needed, the table returned is lightweight
+}
+```
+
+之所以能这样使用,这是因为 Connection 并不是一个简单的 socket 连接,[接口文档](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Connection.html) 中对 Connection 的表述是:
+
+```properties
+A cluster connection encapsulating lower level individual connections to actual servers and a
+connection to zookeeper. Connections are instantiated through the ConnectionFactory class.
+The lifecycle of the connection is managed by the caller, who has to close() the connection
+to release the resources.
+
+Connection 是一个集群连接,封装了与多台服务器(Matser/Region Server)的底层连接以及与 zookeeper 的连接。
+连接通过 ConnectionFactory 类实例化。连接的生命周期由调用者管理,调用者必须使用 close() 关闭连接以释放资源。
+```
+
+之所以封装这些连接,是因为 HBase 客户端需要连接三个不同的服务角色:
+
++ **Zookeeper** :主要用于获取 `meta` 表的位置信息,Master 的信息;
++ **HBase Master** :主要用于执行 HBaseAdmin 接口的一些操作,例如建表等;
++ **HBase RegionServer** :用于读、写数据。
+
+
+
+Connection 对象和实际的 Socket 连接之间的对应关系如下图:
+
+
+
+> 上面两张图片引用自博客:[连接 HBase 的正确姿势](https://yq.aliyun.com/articles/581702?spm=a2c4e.11157919.spm-cont-list.1.146c27aeFxoMsN%20%E8%BF%9E%E6%8E%A5HBase%E7%9A%84%E6%AD%A3%E7%A1%AE%E5%A7%BF%E5%8A%BF)
+
+在 HBase 客户端代码中,真正对应 Socket 连接的是 `RpcConnection` 对象。HBase 使用 `PoolMap` 这种数据结构来存储客户端到 HBase 服务器之间的连接。`PoolMap` 的内部有一个 `ConcurrentHashMap` 实例,其 key 是 `ConnectionId`(封装了服务器地址和用户 ticket),value 是一个 `RpcConnection` 对象的资源池。当 HBase 需要连接一个服务器时,首先会根据 `ConnectionId` 找到对应的连接池,然后从连接池中取出一个连接对象。
+
+```java
+@InterfaceAudience.Private
+public class PoolMap implements Map {
+ private PoolType poolType;
+
+ private int poolMaxSize;
+
+ private Map> pools = new ConcurrentHashMap<>();
+
+ public PoolMap(PoolType poolType) {
+ this.poolType = poolType;
+ }
+ .....
+```
+
+HBase 中提供了三种资源池的实现,分别是 `Reusable`,`RoundRobin` 和 `ThreadLocal`。具体实现可以通 `hbase.client.ipc.pool.type` 配置项指定,默认为 `Reusable`。连接池的大小也可以通过 `hbase.client.ipc.pool.size` 配置项指定,默认为 1,即每个 Server 1 个连接。也可以通过修改配置实现:
+
+```java
+config.set("hbase.client.ipc.pool.type",...);
+config.set("hbase.client.ipc.pool.size",...);
+connection = ConnectionFactory.createConnection(config);
+```
+
+由此可以看出 HBase 中 Connection 类已经实现了对连接的管理功能,所以我们不必在 Connection 上在做额外的管理。
+
+另外,Connection 是线程安全的,但 Table 和 Admin 却不是线程安全的,因此正确的做法是一个进程共用一个 Connection 对象,而在不同的线程中使用单独的 Table 和 Admin 对象。Table 和 Admin 的获取操作 `getTable()` 和 `getAdmin()` 都是轻量级,所以不必担心性能的消耗,同时建议在使用完成后显示的调用 `close()` 方法来关闭它们。
+
+
+
+## 参考资料
+
+1. [连接 HBase 的正确姿势](https://yq.aliyun.com/articles/581702?spm=a2c4e.11157919.spm-cont-list.1.146c27aeFxoMsN%20%E8%BF%9E%E6%8E%A5HBase%E7%9A%84%E6%AD%A3%E7%A1%AE%E5%A7%BF%E5%8A%BF)
+2. [Apache HBase ™ Reference Guide](http://hbase.apache.org/book.htm)
+
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase_Shell.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase_Shell.md"
new file mode 100644
index 0000000..d9417f2
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase_Shell.md"
@@ -0,0 +1,279 @@
+# Hbase 常用 Shell 命令
+
+一、基本命令
+ 1.1 获取帮助
+ 1.2 查看服务器状态
+ 1.3 查看版本信息
+二、关于表的操作
+ 2.1 查看所有表
+ 2.2 创建表
+ 2.3 查看表的基本信息
+ 2.4 表的启用/禁用
+ 2.5 检查表是否存在
+ 2.6 删除表
+三、增删改
+ 3.1 添加列族
+ 3.2 删除列族
+ 3.3 更改列族存储版本的限制
+ 3.4 插入数据
+ 3.5 获取指定行、指定行中的列族、列的信息
+ 3.6 删除指定行、指定行中的列
+四、查询
+ 4.1Get查询
+ 4.2 查询整表数据
+ 4.3 查询指定列簇的数据
+ 4.4 条件查询
+ 4.5 条件过滤
+
+
+
+## 一、基本命令
+
+打开 Hbase Shell:
+
+```shell
+# hbase shell
+```
+
+#### 1.1 获取帮助
+
+```shell
+# 获取帮助
+help
+# 获取命令的详细信息
+help 'status'
+```
+
+#### 1.2 查看服务器状态
+
+```shell
+status
+```
+
+#### 1.3 查看版本信息
+```shell
+version
+```
+
+
+
+## 二、关于表的操作
+
+
+#### 2.1 查看所有表
+
+```shell
+list
+```
+
+#### 2.2 创建表
+
+ **命令格式**: create '表名称', '列族名称 1','列族名称 2','列名称 N'
+
+```shell
+# 创建一张名为Student的表,包含基本信息(baseInfo)、学校信息(schoolInfo)两个列族
+create 'Student','baseInfo','schoolInfo'
+```
+
+#### 2.3 查看表的基本信息
+
+ **命令格式**:desc '表名'
+
+```shell
+describe 'Student'
+```
+
+#### 2.4 表的启用/禁用
+
+enable 和 disable 可以启用/禁用这个表,is_enabled 和 is_disabled 来检查表是否被禁用
+
+```shell
+# 禁用表
+disable 'Student'
+# 检查表是否被禁用
+is_disabled 'Student'
+# 启用表
+enable 'Student'
+# 检查表是否被启用
+is_enabled 'Student'
+```
+
+#### 2.5 检查表是否存在
+
+```shell
+exists 'Student'
+```
+
+#### 2.6 删除表
+
+```shell
+# 删除表前需要先禁用表
+disable 'Student'
+# 删除表
+drop 'Student'
+```
+
+
+
+## 三、增删改
+
+
+#### 3.1 添加列族
+
+ **命令格式**: alter '表名', '列族名'
+
+```shell
+alter 'Student', 'teacherInfo'
+```
+
+#### 3.2 删除列族
+
+ **命令格式**:alter '表名', {NAME => '列族名', METHOD => 'delete'}
+
+```shell
+alter 'Student', {NAME => 'teacherInfo', METHOD => 'delete'}
+```
+
+#### 3.3 更改列族存储版本的限制
+
+默认情况下,列族只存储一个版本的数据,如果需要存储多个版本的数据,则需要修改列族的属性。修改后可通过 `desc` 命令查看。
+
+```shell
+alter 'Student',{NAME=>'baseInfo',VERSIONS=>3}
+```
+
+#### 3.4 插入数据
+
+ **命令格式**:put '表名', '行键','列族:列','值'
+
+**注意:如果新增数据的行键值、列族名、列名与原有数据完全相同,则相当于更新操作**
+
+```shell
+put 'Student', 'rowkey1','baseInfo:name','tom'
+put 'Student', 'rowkey1','baseInfo:birthday','1990-01-09'
+put 'Student', 'rowkey1','baseInfo:age','29'
+put 'Student', 'rowkey1','schoolInfo:name','Havard'
+put 'Student', 'rowkey1','schoolInfo:localtion','Boston'
+
+put 'Student', 'rowkey2','baseInfo:name','jack'
+put 'Student', 'rowkey2','baseInfo:birthday','1998-08-22'
+put 'Student', 'rowkey2','baseInfo:age','21'
+put 'Student', 'rowkey2','schoolInfo:name','yale'
+put 'Student', 'rowkey2','schoolInfo:localtion','New Haven'
+
+put 'Student', 'rowkey3','baseInfo:name','maike'
+put 'Student', 'rowkey3','baseInfo:birthday','1995-01-22'
+put 'Student', 'rowkey3','baseInfo:age','24'
+put 'Student', 'rowkey3','schoolInfo:name','yale'
+put 'Student', 'rowkey3','schoolInfo:localtion','New Haven'
+
+put 'Student', 'wrowkey4','baseInfo:name','maike-jack'
+```
+
+#### 3.5 获取指定行、指定行中的列族、列的信息
+
+```shell
+# 获取指定行中所有列的数据信息
+get 'Student','rowkey3'
+# 获取指定行中指定列族下所有列的数据信息
+get 'Student','rowkey3','baseInfo'
+# 获取指定行中指定列的数据信息
+get 'Student','rowkey3','baseInfo:name'
+```
+
+#### 3.6 删除指定行、指定行中的列
+
+```shell
+# 删除指定行
+delete 'Student','rowkey3'
+# 删除指定行中指定列的数据
+delete 'Student','rowkey3','baseInfo:name'
+```
+
+
+
+## 四、查询
+
+hbase 中访问数据有两种基本的方式:
+
++ 按指定 rowkey 获取数据:get 方法;
+
++ 按指定条件获取数据:scan 方法。
+
+`scan` 可以设置 begin 和 end 参数来访问一个范围内所有的数据。get 本质上就是 begin 和 end 相等的一种特殊的 scan。
+
+#### 4.1Get查询
+
+```shell
+# 获取指定行中所有列的数据信息
+get 'Student','rowkey3'
+# 获取指定行中指定列族下所有列的数据信息
+get 'Student','rowkey3','baseInfo'
+# 获取指定行中指定列的数据信息
+get 'Student','rowkey3','baseInfo:name'
+```
+
+#### 4.2 查询整表数据
+
+```shell
+scan 'Student'
+```
+
+#### 4.3 查询指定列簇的数据
+
+```shell
+scan 'Student', {COLUMN=>'baseInfo'}
+```
+
+#### 4.4 条件查询
+
+```shell
+# 查询指定列的数据
+scan 'Student', {COLUMNS=> 'baseInfo:birthday'}
+```
+
+除了列 `(COLUMNS)` 修饰词外,HBase 还支持 `Limit`(限制查询结果行数),`STARTROW`(`ROWKEY` 起始行,会先根据这个 `key` 定位到 `region`,再向后扫描)、`STOPROW`(结束行)、`TIMERANGE`(限定时间戳范围)、`VERSIONS`(版本数)、和 `FILTER`(按条件过滤行)等。
+
+如下代表从 `rowkey2` 这个 `rowkey` 开始,查找下两个行的最新 3 个版本的 name 列的数据:
+
+```shell
+scan 'Student', {COLUMNS=> 'baseInfo:name',STARTROW => 'rowkey2',STOPROW => 'wrowkey4',LIMIT=>2, VERSIONS=>3}
+```
+
+#### 4.5 条件过滤
+
+Filter 可以设定一系列条件来进行过滤。如我们要查询值等于 24 的所有数据:
+
+```shell
+scan 'Student', FILTER=>"ValueFilter(=,'binary:24')"
+```
+
+值包含 yale 的所有数据:
+
+```shell
+scan 'Student', FILTER=>"ValueFilter(=,'substring:yale')"
+```
+
+列名中的前缀为 birth 的:
+
+```shell
+scan 'Student', FILTER=>"ColumnPrefixFilter('birth')"
+```
+
+FILTER 中支持多个过滤条件通过括号、AND 和 OR 进行组合:
+
+```shell
+# 列名中的前缀为birth且列值中包含1998的数据
+scan 'Student', FILTER=>"ColumnPrefixFilter('birth') AND ValueFilter ValueFilter(=,'substring:1998')"
+```
+
+`PrefixFilter` 用于对 Rowkey 的前缀进行判断:
+
+```shell
+scan 'Student', FILTER=>"PrefixFilter('wr')"
+```
+
+
+
+
+
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase\345\215\217\345\244\204\347\220\206\345\231\250\350\257\246\350\247\243.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase\345\215\217\345\244\204\347\220\206\345\231\250\350\257\246\350\247\243.md"
new file mode 100644
index 0000000..6949916
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase\345\215\217\345\244\204\347\220\206\345\231\250\350\257\246\350\247\243.md"
@@ -0,0 +1,490 @@
+# Hbase 协处理器
+
+
+一、简述
+二、协处理器类型
+ 2.1 Observer协处理器
+ 2.2 Endpoint协处理器
+三、协处理的加载方式
+四、静态加载与卸载
+ 4.1 静态加载
+ 4.2 静态卸载
+五、动态加载与卸载
+ 5.1 HBase Shell动态加载
+ 5.2 HBase Shell动态卸载
+ 5.3 Java API 动态加载
+ 5.4 Java API 动态卸载
+六、协处理器案例
+
+
+
+## 一、简述
+
+在使用 HBase 时,如果你的数据量达到了数十亿行或数百万列,此时能否在查询中返回大量数据将受制于网络的带宽,即便网络状况允许,但是客户端的计算处理也未必能够满足要求。在这种情况下,协处理器(Coprocessors)应运而生。它允许你将业务计算代码放入在 RegionServer 的协处理器中,将处理好的数据再返回给客户端,这可以极大地降低需要传输的数据量,从而获得性能上的提升。同时协处理器也允许用户扩展实现 HBase 目前所不具备的功能,如权限校验、二级索引、完整性约束等。
+
+
+
+## 二、协处理器类型
+
+### 2.1 Observer协处理器
+
+#### 1. 功能
+
+Observer 协处理器类似于关系型数据库中的触发器,当发生某些事件的时候这类协处理器会被 Server 端调用。通常可以用来实现下面功能:
+
++ **权限校验**:在执行 `Get` 或 `Put` 操作之前,您可以使用 `preGet` 或 `prePut` 方法检查权限;
++ **完整性约束**: HBase 不支持关系型数据库中的外键功能,可以通过触发器在插入或者删除数据的时候,对关联的数据进行检查;
++ **二级索引**: 可以使用协处理器来维护二级索引。
+
+
+
+#### 2. 类型
+
+当前 Observer 协处理器有以下四种类型:
+
++ **RegionObserver** :
+ 允许您观察 Region 上的事件,例如 Get 和 Put 操作。
++ **RegionServerObserver** :
+ 允许您观察与 RegionServer 操作相关的事件,例如启动,停止或执行合并,提交或回滚。
++ **MasterObserver** :
+ 允许您观察与 HBase Master 相关的事件,例如表创建,删除或 schema 修改。
++ **WalObserver** :
+ 允许您观察与预写日志(WAL)相关的事件。
+
+
+
+#### 3. 接口
+
+以上四种类型的 Observer 协处理器均继承自 `Coprocessor` 接口,这四个接口中分别定义了所有可用的钩子方法,以便在对应方法前后执行特定的操作。通常情况下,我们并不会直接实现上面接口,而是继承其 Base 实现类,Base 实现类只是简单空实现了接口中的方法,这样我们在实现自定义的协处理器时,就不必实现所有方法,只需要重写必要方法即可。
+
+
+
+这里以 `RegionObservers ` 为例,其接口类中定义了所有可用的钩子方法,下面截取了部分方法的定义,多数方法都是成对出现的,有 `pre` 就有 `post`:
+
+
+
+
+
+#### 4. 执行流程
+
+
+
++ 客户端发出 put 请求
++ 该请求被分派给合适的 RegionServer 和 region
++ coprocessorHost 拦截该请求,然后在该表的每个 RegionObserver 上调用 prePut()
++ 如果没有被 `prePut()` 拦截,该请求继续送到 region,然后进行处理
++ region 产生的结果再次被 CoprocessorHost 拦截,调用 `postPut()`
++ 假如没有 `postPut()` 拦截该响应,最终结果被返回给客户端
+
+如果大家了解 Spring,可以将这种执行方式类比于其 AOP 的执行原理即可,官方文档当中也是这样类比的:
+
+>If you are familiar with Aspect Oriented Programming (AOP), you can think of a coprocessor as applying advice by intercepting a request and then running some custom code,before passing the request on to its final destination (or even changing the destination).
+>
+>如果您熟悉面向切面编程(AOP),您可以将协处理器视为通过拦截请求然后运行一些自定义代码来使用 Advice,然后将请求传递到其最终目标(或者更改目标)。
+
+
+
+### 2.2 Endpoint协处理器
+
+Endpoint 协处理器类似于关系型数据库中的存储过程。客户端可以调用 Endpoint 协处理器在服务端对数据进行处理,然后再返回。
+
+以聚集操作为例,如果没有协处理器,当用户需要找出一张表中的最大数据,即 max 聚合操作,就必须进行全表扫描,然后在客户端上遍历扫描结果,这必然会加重了客户端处理数据的压力。利用 Coprocessor,用户可以将求最大值的代码部署到 HBase Server 端,HBase 将利用底层 cluster 的多个节点并发执行求最大值的操作。即在每个 Region 范围内执行求最大值的代码,将每个 Region 的最大值在 Region Server 端计算出来,仅仅将该 max 值返回给客户端。之后客户端只需要将每个 Region 的最大值进行比较而找到其中最大的值即可。
+
+
+
+## 三、协处理的加载方式
+
+要使用我们自己开发的协处理器,必须通过静态(使用 HBase 配置)或动态(使用 HBase Shell 或 Java API)加载它。
+
++ 静态加载的协处理器称之为 **System Coprocessor**(系统级协处理器),作用范围是整个 HBase 上的所有表,需要重启 HBase 服务;
++ 动态加载的协处理器称之为 **Table Coprocessor**(表处理器),作用于指定的表,不需要重启 HBase 服务。
+
+其加载和卸载方式分别介绍如下。
+
+
+
+## 四、静态加载与卸载
+
+### 4.1 静态加载
+
+静态加载分以下三步:
+
+1. 在 `hbase-site.xml` 定义需要加载的协处理器。
+
+```xml
+
+ hbase.coprocessor.region.classes
+ org.myname.hbase.coprocessor.endpoint.SumEndPoint
+
+```
+
+ ` ` 标签的值必须是下面其中之一:
+
+ + RegionObservers 和 Endpoints 协处理器:`hbase.coprocessor.region.classes`
+ + WALObservers 协处理器: `hbase.coprocessor.wal.classes`
+ + MasterObservers 协处理器:`hbase.coprocessor.master.classes`
+
+ `` 必须是协处理器实现类的全限定类名。如果为加载指定了多个类,则类名必须以逗号分隔。
+
+2. 将 jar(包含代码和所有依赖项) 放入 HBase 安装目录中的 `lib` 目录下;
+
+3. 重启 HBase。
+
+
+
+### 4.2 静态卸载
+
+1. 从 hbase-site.xml 中删除配置的协处理器的\元素及其子元素;
+
+2. 从类路径或 HBase 的 lib 目录中删除协处理器的 JAR 文件(可选);
+
+3. 重启 HBase。
+
+
+
+
+## 五、动态加载与卸载
+
+使用动态加载协处理器,不需要重新启动 HBase。但动态加载的协处理器是基于每个表加载的,只能用于所指定的表。
+此外,在使用动态加载必须使表脱机(disable)以加载协处理器。动态加载通常有两种方式:Shell 和 Java API 。
+
+> 以下示例基于两个前提:
+>
+> 1. coprocessor.jar 包含协处理器实现及其所有依赖项。
+> 2. JAR 包存放在 HDFS 上的路径为:hdfs:// \:\ / user / \ /coprocessor.jar
+
+### 5.1 HBase Shell动态加载
+
+1. 使用 HBase Shell 禁用表
+
+```shell
+hbase > disable 'tableName'
+```
+
+2. 使用如下命令加载协处理器
+
+```shell
+hbase > alter 'tableName', METHOD => 'table_att', 'Coprocessor'=>'hdfs://:/
+user//coprocessor.jar| org.myname.hbase.Coprocessor.RegionObserverExample|1073741823|
+arg1=1,arg2=2'
+```
+
+`Coprocessor` 包含由管道(|)字符分隔的四个参数,按顺序解释如下:
+
++ **JAR 包路径**:通常为 JAR 包在 HDFS 上的路径。关于路径以下两点需要注意:
++ 允许使用通配符,例如:`hdfs://:/user//*.jar` 来添加指定的 JAR 包;
+
++ 可以使指定目录,例如:`hdfs://:/user//` ,这会添加目录中的所有 JAR 包,但不会搜索子目录中的 JAR 包。
+
++ **类名**:协处理器的完整类名。
++ **优先级**:协处理器的优先级,遵循数字的自然序,即值越小优先级越高。可以为空,在这种情况下,将分配默认优先级值。
++ **可选参数** :传递的协处理器的可选参数。
+
+3. 启用表
+
+```shell
+hbase > enable 'tableName'
+```
+
+4. 验证协处理器是否已加载
+
+```shell
+hbase > describe 'tableName'
+```
+
+协处理器出现在 `TABLE_ATTRIBUTES` 属性中则代表加载成功。
+
+
+
+### 5.2 HBase Shell动态卸载
+
+1. 禁用表
+
+ ```shell
+hbase> disable 'tableName'
+ ```
+
+2. 移除表协处理器
+
+```shell
+hbase> alter 'tableName', METHOD => 'table_att_unset', NAME => 'coprocessor$1'
+```
+
+3. 启用表
+
+```shell
+hbase> enable 'tableName'
+```
+
+
+
+### 5.3 Java API 动态加载
+
+```java
+TableName tableName = TableName.valueOf("users");
+String path = "hdfs://:/user//coprocessor.jar";
+Configuration conf = HBaseConfiguration.create();
+Connection connection = ConnectionFactory.createConnection(conf);
+Admin admin = connection.getAdmin();
+admin.disableTable(tableName);
+HTableDescriptor hTableDescriptor = new HTableDescriptor(tableName);
+HColumnDescriptor columnFamily1 = new HColumnDescriptor("personalDet");
+columnFamily1.setMaxVersions(3);
+hTableDescriptor.addFamily(columnFamily1);
+HColumnDescriptor columnFamily2 = new HColumnDescriptor("salaryDet");
+columnFamily2.setMaxVersions(3);
+hTableDescriptor.addFamily(columnFamily2);
+hTableDescriptor.setValue("COPROCESSOR$1", path + "|"
++ RegionObserverExample.class.getCanonicalName() + "|"
++ Coprocessor.PRIORITY_USER);
+admin.modifyTable(tableName, hTableDescriptor);
+admin.enableTable(tableName);
+```
+
+在 HBase 0.96 及其以后版本中,HTableDescriptor 的 addCoprocessor() 方法提供了一种更为简便的加载方法。
+
+```java
+TableName tableName = TableName.valueOf("users");
+Path path = new Path("hdfs://:/user//coprocessor.jar");
+Configuration conf = HBaseConfiguration.create();
+Connection connection = ConnectionFactory.createConnection(conf);
+Admin admin = connection.getAdmin();
+admin.disableTable(tableName);
+HTableDescriptor hTableDescriptor = new HTableDescriptor(tableName);
+HColumnDescriptor columnFamily1 = new HColumnDescriptor("personalDet");
+columnFamily1.setMaxVersions(3);
+hTableDescriptor.addFamily(columnFamily1);
+HColumnDescriptor columnFamily2 = new HColumnDescriptor("salaryDet");
+columnFamily2.setMaxVersions(3);
+hTableDescriptor.addFamily(columnFamily2);
+hTableDescriptor.addCoprocessor(RegionObserverExample.class.getCanonicalName(), path,
+Coprocessor.PRIORITY_USER, null);
+admin.modifyTable(tableName, hTableDescriptor);
+admin.enableTable(tableName);
+```
+
+
+
+### 5.4 Java API 动态卸载
+
+卸载其实就是重新定义表但不设置协处理器。这会删除所有表上的协处理器。
+
+```java
+TableName tableName = TableName.valueOf("users");
+String path = "hdfs://:/user//coprocessor.jar";
+Configuration conf = HBaseConfiguration.create();
+Connection connection = ConnectionFactory.createConnection(conf);
+Admin admin = connection.getAdmin();
+admin.disableTable(tableName);
+HTableDescriptor hTableDescriptor = new HTableDescriptor(tableName);
+HColumnDescriptor columnFamily1 = new HColumnDescriptor("personalDet");
+columnFamily1.setMaxVersions(3);
+hTableDescriptor.addFamily(columnFamily1);
+HColumnDescriptor columnFamily2 = new HColumnDescriptor("salaryDet");
+columnFamily2.setMaxVersions(3);
+hTableDescriptor.addFamily(columnFamily2);
+admin.modifyTable(tableName, hTableDescriptor);
+admin.enableTable(tableName);
+```
+
+
+
+## 六、协处理器案例
+
+这里给出一个简单的案例,实现一个类似于 Redis 中 `append` 命令的协处理器,当我们对已有列执行 put 操作时候,HBase 默认执行的是 update 操作,这里我们修改为执行 append 操作。
+
+```shell
+# redis append 命令示例
+redis> EXISTS mykey
+(integer) 0
+redis> APPEND mykey "Hello"
+(integer) 5
+redis> APPEND mykey " World"
+(integer) 11
+redis> GET mykey
+"Hello World"
+```
+
+### 6.1 创建测试表
+
+```shell
+# 创建一张杂志表 有文章和图片两个列族
+hbase > create 'magazine','article','picture'
+```
+
+### 6.2 协处理器编程
+
+> 完整代码可见本仓库:[hbase-observer-coprocessor](https://github.com/heibaiying/BigData-Notes/tree/master/code/Hbase\hbase-observer-coprocessor)
+
+新建 Maven 工程,导入下面依赖:
+
+```xml
+
+ org.apache.hbase
+ hbase-common
+ 1.2.0
+
+
+ org.apache.hbase
+ hbase-server
+ 1.2.0
+
+```
+
+继承 `BaseRegionObserver` 实现我们自定义的 `RegionObserver`,对相同的 `article:content` 执行 put 命令时,将新插入的内容添加到原有内容的末尾,代码如下:
+
+```java
+public class AppendRegionObserver extends BaseRegionObserver {
+
+ private byte[] columnFamily = Bytes.toBytes("article");
+ private byte[] qualifier = Bytes.toBytes("content");
+
+ @Override
+ public void prePut(ObserverContext e, Put put, WALEdit edit,
+ Durability durability) throws IOException {
+ if (put.has(columnFamily, qualifier)) {
+ // 遍历查询结果,获取指定列的原值
+ Result rs = e.getEnvironment().getRegion().get(new Get(put.getRow()));
+ String oldValue = "";
+ for (Cell cell : rs.rawCells())
+ if (CellUtil.matchingColumn(cell, columnFamily, qualifier)) {
+ oldValue = Bytes.toString(CellUtil.cloneValue(cell));
+ }
+
+ // 获取指定列新插入的值
+ List cells = put.get(columnFamily, qualifier);
+ String newValue = "";
+ for (Cell cell : cells) {
+ if (CellUtil.matchingColumn(cell, columnFamily, qualifier)) {
+ newValue = Bytes.toString(CellUtil.cloneValue(cell));
+ }
+ }
+
+ // Append 操作
+ put.addColumn(columnFamily, qualifier, Bytes.toBytes(oldValue + newValue));
+ }
+ }
+}
+```
+
+### 6.3 打包项目
+
+使用 maven 命令进行打包,打包后的文件名为 `hbase-observer-coprocessor-1.0-SNAPSHOT.jar`
+
+```shell
+# mvn clean package
+```
+
+### 6.4 上传JAR包到HDFS
+
+```shell
+# 上传项目到HDFS上的hbase目录
+hadoop fs -put /usr/app/hbase-observer-coprocessor-1.0-SNAPSHOT.jar /hbase
+# 查看上传是否成功
+hadoop fs -ls /hbase
+```
+
+
+
+### 6.5 加载协处理器
+
+1. 加载协处理器前需要先禁用表
+
+```shell
+hbase > disable 'magazine'
+```
+2. 加载协处理器
+
+```shell
+hbase > alter 'magazine', METHOD => 'table_att', 'Coprocessor'=>'hdfs://hadoop001:8020/hbase/hbase-observer-coprocessor-1.0-SNAPSHOT.jar|com.heibaiying.AppendRegionObserver|1001|'
+```
+
+3. 启用表
+
+```shell
+hbase > enable 'magazine'
+```
+
+4. 查看协处理器是否加载成功
+
+```shell
+hbase > desc 'magazine'
+```
+
+协处理器出现在 `TABLE_ATTRIBUTES` 属性中则代表加载成功,如下图:
+
+
+
+### 6.6 测试加载结果
+
+插入一组测试数据:
+
+```shell
+hbase > put 'magazine', 'rowkey1','article:content','Hello'
+hbase > get 'magazine','rowkey1','article:content'
+hbase > put 'magazine', 'rowkey1','article:content','World'
+hbase > get 'magazine','rowkey1','article:content'
+```
+
+可以看到对于指定列的值已经执行了 append 操作:
+
+
+
+插入一组对照数据:
+
+```shell
+hbase > put 'magazine', 'rowkey1','article:author','zhangsan'
+hbase > get 'magazine','rowkey1','article:author'
+hbase > put 'magazine', 'rowkey1','article:author','lisi'
+hbase > get 'magazine','rowkey1','article:author'
+```
+
+可以看到对于正常的列还是执行 update 操作:
+
+
+
+### 6.7 卸载协处理器
+1. 卸载协处理器前需要先禁用表
+
+```shell
+hbase > disable 'magazine'
+```
+2. 卸载协处理器
+
+```shell
+hbase > alter 'magazine', METHOD => 'table_att_unset', NAME => 'coprocessor$1'
+```
+
+3. 启用表
+
+```shell
+hbase > enable 'magazine'
+```
+
+4. 查看协处理器是否卸载成功
+
+```shell
+hbase > desc 'magazine'
+```
+
+
+
+### 6.8 测试卸载结果
+
+依次执行下面命令可以测试卸载是否成功
+
+```shell
+hbase > get 'magazine','rowkey1','article:content'
+hbase > put 'magazine', 'rowkey1','article:content','Hello'
+hbase > get 'magazine','rowkey1','article:content'
+```
+
+
+
+
+
+## 参考资料
+
+1. [Apache HBase Coprocessors](http://hbase.apache.org/book.html#cp)
+2. [Apache HBase Coprocessor Introduction](https://blogs.apache.org/hbase/entry/coprocessor_introduction)
+3. [HBase 高階知識](https://www.itread01.com/content/1546245908.html)
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase\345\256\271\347\201\276\344\270\216\345\244\207\344\273\275.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase\345\256\271\347\201\276\344\270\216\345\244\207\344\273\275.md"
new file mode 100644
index 0000000..0dfb055
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase\345\256\271\347\201\276\344\270\216\345\244\207\344\273\275.md"
@@ -0,0 +1,196 @@
+# Hbase容灾与备份
+
+
+一、前言
+二、CopyTable
+ 2.1 简介
+ 2.2 命令格式
+ 2.3 常用命令
+ 2.4 更多参数
+三、Export/Import
+ 3.1 简介
+ 3.2 命令格式
+ 3.3 常用命令
+四、Snapshot
+ 4.1 简介
+ 4.2 配置
+ 4.3 常用命令
+
+
+## 一、前言
+
+本文主要介绍 Hbase 常用的三种简单的容灾备份方案,即**CopyTable**、**Export**/**Import**、**Snapshot**。分别介绍如下:
+
+
+
+## 二、CopyTable
+
+### 2.1 简介
+
+**CopyTable**可以将现有表的数据复制到新表中,具有以下特点:
+
+- 支持时间区间 、row 区间 、改变表名称 、改变列族名称 、以及是否 Copy 已被删除的数据等功能;
+- 执行命令前,需先创建与原表结构相同的新表;
+- `CopyTable` 的操作是基于 HBase Client API 进行的,即采用 `scan` 进行查询, 采用 `put` 进行写入。
+
+### 2.2 命令格式
+
+```shell
+Usage: CopyTable [general options] [--starttime=X] [--endtime=Y] [--new.name=NEW] [--peer.adr=ADR]
+```
+
+### 2.3 常用命令
+
+1. 同集群下 CopyTable
+
+```shell
+hbase org.apache.hadoop.hbase.mapreduce.CopyTable --new.name=tableCopy tableOrig
+```
+
+2. 不同集群下 CopyTable
+
+```shell
+# 两表名称相同的情况
+hbase org.apache.hadoop.hbase.mapreduce.CopyTable \
+--peer.adr=dstClusterZK:2181:/hbase tableOrig
+
+# 也可以指新的表名
+hbase org.apache.hadoop.hbase.mapreduce.CopyTable \
+--peer.adr=dstClusterZK:2181:/hbase \
+--new.name=tableCopy tableOrig
+```
+
+
+3. 下面是一个官方给的比较完整的例子,指定开始和结束时间,集群地址,以及只复制指定的列族:
+
+```shell
+hbase org.apache.hadoop.hbase.mapreduce.CopyTable \
+--starttime=1265875194289 \
+--endtime=1265878794289 \
+--peer.adr=server1,server2,server3:2181:/hbase \
+--families=myOldCf:myNewCf,cf2,cf3 TestTable
+```
+
+### 2.4 更多参数
+
+可以通过 `--help` 查看更多支持的参数
+
+```shell
+# hbase org.apache.hadoop.hbase.mapreduce.CopyTable --help
+```
+
+
+
+
+
+## 三、Export/Import
+
+### 3.1 简介
+
+- `Export` 支持导出数据到 HDFS, `Import` 支持从 HDFS 导入数据。`Export` 还支持指定导出数据的开始时间和结束时间,因此可以用于增量备份。
+- `Export` 导出与 `CopyTable` 一样,依赖 HBase 的 `scan` 操作
+
+### 3.2 命令格式
+
+```shell
+# Export
+hbase org.apache.hadoop.hbase.mapreduce.Export [ [ []]]
+
+# Inport
+hbase org.apache.hadoop.hbase.mapreduce.Import
+```
+
++ 导出的 `outputdir` 目录可以不用预先创建,程序会自动创建。导出完成后,导出文件的所有权将由执行导出命令的用户所拥有。
++ 默认情况下,仅导出给定 `Cell` 的最新版本,而不管历史版本。要导出多个版本,需要将 `` 参数替换为所需的版本数。
+
+### 3.3 常用命令
+
+1. 导出命令
+
+```shell
+hbase org.apache.hadoop.hbase.mapreduce.Export tableName hdfs 路径/tableName.db
+```
+
+2. 导入命令
+
+```
+hbase org.apache.hadoop.hbase.mapreduce.Import tableName hdfs 路径/tableName.db
+```
+
+
+
+## 四、Snapshot
+
+### 4.1 简介
+
+HBase 的快照 (Snapshot) 功能允许您获取表的副本 (包括内容和元数据),并且性能开销很小。因为快照存储的仅仅是表的元数据和 HFiles 的信息。快照的 `clone` 操作会从该快照创建新表,快照的 `restore` 操作会将表的内容还原到快照节点。`clone` 和 `restore` 操作不需要复制任何数据,因为底层 HFiles(包含 HBase 表数据的文件) 不会被修改,修改的只是表的元数据信息。
+
+### 4.2 配置
+
+HBase 快照功能默认没有开启,如果要开启快照,需要在 `hbase-site.xml` 文件中添加如下配置项:
+
+```xml
+
+ hbase.snapshot.enabled
+ true
+
+```
+
+
+
+### 4.3 常用命令
+
+快照的所有命令都需要在 Hbase Shell 交互式命令行中执行。
+
+#### 1. Take a Snapshot
+
+```shell
+# 拍摄快照
+hbase> snapshot '表名', '快照名'
+```
+
+默认情况下拍摄快照之前会在内存中执行数据刷新。以保证内存中的数据包含在快照中。但是如果你不希望包含内存中的数据,则可以使用 `SKIP_FLUSH` 选项禁止刷新。
+
+```shell
+# 禁止内存刷新
+hbase> snapshot '表名', '快照名', {SKIP_FLUSH => true}
+```
+
+#### 2. Listing Snapshots
+
+```shell
+# 获取快照列表
+hbase> list_snapshots
+```
+
+#### 3. Deleting Snapshots
+
+```shell
+# 删除快照
+hbase> delete_snapshot '快照名'
+```
+
+#### 4. Clone a table from snapshot
+
+```shell
+# 从现有的快照创建一张新表
+hbase> clone_snapshot '快照名', '新表名'
+```
+
+#### 5. Restore a snapshot
+
+将表恢复到快照节点,恢复操作需要先禁用表
+
+```shell
+hbase> disable '表名'
+hbase> restore_snapshot '快照名'
+```
+
+这里需要注意的是:是如果 HBase 配置了基于 Replication 的主从复制,由于 Replication 在日志级别工作,而快照在文件系统级别工作,因此在还原之后,会出现副本与主服务器处于不同的状态的情况。这时候可以先停止同步,所有服务器还原到一致的数据点后再重新建立同步。
+
+
+
+## 参考资料
+
+1. [Online Apache HBase Backups with CopyTable](https://blog.cloudera.com/blog/2012/06/online-hbase-backups-with-copytable-2/)
+2. [Apache HBase ™ Reference Guide](http://hbase.apache.org/book.htm)
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase\347\232\204SQL\344\270\255\351\227\264\345\261\202_Phoenix.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase\347\232\204SQL\344\270\255\351\227\264\345\261\202_Phoenix.md"
new file mode 100644
index 0000000..e89c512
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase\347\232\204SQL\344\270\255\351\227\264\345\261\202_Phoenix.md"
@@ -0,0 +1,241 @@
+# Hbase的SQL中间层——Phoenix
+
+
+一、Phoenix简介
+二、Phoenix安装
+ 2.1 下载并解压
+ 2.2 拷贝Jar包
+ 2.3 重启 Region Servers
+ 2.4 启动Phoenix
+ 2.5 启动结果
+三、Phoenix 简单使用
+ 3.1 创建表
+ 3.2 插入数据
+ 3.3 修改数据
+ 3.4 删除数据
+ 3.5 查询数据
+ 3.6 退出命令
+ 3.7 扩展
+四、Phoenix Java API
+ 4.1 引入Phoenix core JAR包
+ 4.2 简单的Java API实例
+
+
+## 一、Phoenix简介
+
+`Phoenix` 是 HBase 的开源 SQL 中间层,它允许你使用标准 JDBC 的方式来操作 HBase 上的数据。在 `Phoenix` 之前,如果你要访问 HBase,只能调用它的 Java API,但相比于使用一行 SQL 就能实现数据查询,HBase 的 API 还是过于复杂。`Phoenix` 的理念是 `we put sql SQL back in NOSQL`,即你可以使用标准的 SQL 就能完成对 HBase 上数据的操作。同时这也意味着你可以通过集成 `Spring Data JPA` 或 `Mybatis` 等常用的持久层框架来操作 HBase。
+
+其次 `Phoenix` 的性能表现也非常优异,`Phoenix` 查询引擎会将 SQL 查询转换为一个或多个 HBase Scan,通过并行执行来生成标准的 JDBC 结果集。它通过直接使用 HBase API 以及协处理器和自定义过滤器,可以为小型数据查询提供毫秒级的性能,为千万行数据的查询提供秒级的性能。同时 Phoenix 还拥有二级索引等 HBase 不具备的特性,因为以上的优点,所以 `Phoenix` 成为了 HBase 最优秀的 SQL 中间层。
+
+
+
+
+## 二、Phoenix安装
+
+> 我们可以按照官方安装说明进行安装,官方说明如下:
+>
+> - download and expand our installation tar
+> - copy the phoenix server jar that is compatible with your HBase installation into the lib directory of every region server
+> - restart the region servers
+> - add the phoenix client jar to the classpath of your HBase client
+> - download and setup SQuirrel as your SQL client so you can issue adhoc SQL against your HBase cluster
+
+### 2.1 下载并解压
+
+官方针对 Apache 版本和 CDH 版本的 HBase 均提供了安装包,按需下载即可。官方下载地址: http://phoenix.apache.org/download.html
+
+```shell
+# 下载
+wget http://mirror.bit.edu.cn/apache/phoenix/apache-phoenix-4.14.0-cdh5.14.2/bin/apache-phoenix-4.14.0-cdh5.14.2-bin.tar.gz
+# 解压
+tar tar apache-phoenix-4.14.0-cdh5.14.2-bin.tar.gz
+```
+
+### 2.2 拷贝Jar包
+
+按照官方文档的说明,需要将 `phoenix server jar` 添加到所有 `Region Servers` 的安装目录的 `lib` 目录下。
+
+这里由于我搭建的是 HBase 伪集群,所以只需要拷贝到当前机器的 HBase 的 lib 目录下。如果是真实集群,则使用 scp 命令分发到所有 `Region Servers` 机器上。
+
+```shell
+cp /usr/app/apache-phoenix-4.14.0-cdh5.14.2-bin/phoenix-4.14.0-cdh5.14.2-server.jar /usr/app/hbase-1.2.0-cdh5.15.2/lib
+```
+
+### 2.3 重启 Region Servers
+
+```shell
+# 停止Hbase
+stop-hbase.sh
+# 启动Hbase
+start-hbase.sh
+```
+
+### 2.4 启动Phoenix
+
+在 Phoenix 解压目录下的 `bin` 目录下执行如下命令,需要指定 Zookeeper 的地址:
+
++ 如果 HBase 采用 Standalone 模式或者伪集群模式搭建,则默认采用内置的 Zookeeper 服务,端口为 2181;
++ 如果是 HBase 是集群模式并采用外置的 Zookeeper 集群,则按照自己的实际情况进行指定。
+
+```shell
+# ./sqlline.py hadoop001:2181
+```
+
+### 2.5 启动结果
+
+启动后则进入了 Phoenix 交互式 SQL 命令行,可以使用 `!table` 或 `!tables` 查看当前所有表的信息
+
+
+
+
+## 三、Phoenix 简单使用
+
+### 3.1 创建表
+
+```sql
+CREATE TABLE IF NOT EXISTS us_population (
+ state CHAR(2) NOT NULL,
+ city VARCHAR NOT NULL,
+ population BIGINT
+ CONSTRAINT my_pk PRIMARY KEY (state, city));
+```
+
+
+新建的表会按照特定的规则转换为 HBase 上的表,关于表的信息,可以通过 Hbase Web UI 进行查看:
+
+
+### 3.2 插入数据
+
+Phoenix 中插入数据采用的是 `UPSERT` 而不是 `INSERT`,因为 Phoenix 并没有更新操作,插入相同主键的数据就视为更新,所以 `UPSERT` 就相当于 `UPDATE`+`INSERT`
+
+```shell
+UPSERT INTO us_population VALUES('NY','New York',8143197);
+UPSERT INTO us_population VALUES('CA','Los Angeles',3844829);
+UPSERT INTO us_population VALUES('IL','Chicago',2842518);
+UPSERT INTO us_population VALUES('TX','Houston',2016582);
+UPSERT INTO us_population VALUES('PA','Philadelphia',1463281);
+UPSERT INTO us_population VALUES('AZ','Phoenix',1461575);
+UPSERT INTO us_population VALUES('TX','San Antonio',1256509);
+UPSERT INTO us_population VALUES('CA','San Diego',1255540);
+UPSERT INTO us_population VALUES('TX','Dallas',1213825);
+UPSERT INTO us_population VALUES('CA','San Jose',912332);
+```
+
+### 3.3 修改数据
+
+```sql
+-- 插入主键相同的数据就视为更新
+UPSERT INTO us_population VALUES('NY','New York',999999);
+```
+
+
+### 3.4 删除数据
+
+```sql
+DELETE FROM us_population WHERE city='Dallas';
+```
+
+
+### 3.5 查询数据
+
+```sql
+SELECT state as "州",count(city) as "市",sum(population) as "热度"
+FROM us_population
+GROUP BY state
+ORDER BY sum(population) DESC;
+```
+
+
+
+
+### 3.6 退出命令
+
+```sql
+!quit
+```
+
+
+
+### 3.7 扩展
+
+从上面的操作中可以看出,Phoenix 支持大多数标准的 SQL 语法。关于 Phoenix 支持的语法、数据类型、函数、序列等详细信息,因为涉及内容很多,可以参考其官方文档,官方文档上有详细的说明:
+
++ **语法 (Grammar)** :https://phoenix.apache.org/language/index.html
+
++ **函数 (Functions)** :http://phoenix.apache.org/language/functions.html
+
++ **数据类型 (Datatypes)** :http://phoenix.apache.org/language/datatypes.html
+
++ **序列 (Sequences)** :http://phoenix.apache.org/sequences.html
+
++ **联结查询 (Joins)** :http://phoenix.apache.org/joins.html
+
+
+
+## 四、Phoenix Java API
+
+因为 Phoenix 遵循 JDBC 规范,并提供了对应的数据库驱动 `PhoenixDriver`,这使得采用 Java 语言对其进行操作的时候,就如同对其他关系型数据库一样,下面给出基本的使用示例。
+
+### 4.1 引入Phoenix core JAR包
+
+如果是 maven 项目,直接在 maven 中央仓库找到对应的版本,导入依赖即可:
+
+```xml
+
+
+ org.apache.phoenix
+ phoenix-core
+ 4.14.0-cdh5.14.2
+
+```
+
+如果是普通项目,则可以从 Phoenix 解压目录下找到对应的 JAR 包,然后手动引入:
+
+
+### 4.2 简单的Java API实例
+
+```java
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+
+
+public class PhoenixJavaApi {
+
+ public static void main(String[] args) throws Exception {
+
+ // 加载数据库驱动
+ Class.forName("org.apache.phoenix.jdbc.PhoenixDriver");
+
+ /*
+ * 指定数据库地址,格式为 jdbc:phoenix:Zookeeper 地址
+ * 如果 HBase 采用 Standalone 模式或者伪集群模式搭建,则 HBase 默认使用内置的 Zookeeper,默认端口为 2181
+ */
+ Connection connection = DriverManager.getConnection("jdbc:phoenix:192.168.200.226:2181");
+
+ PreparedStatement statement = connection.prepareStatement("SELECT * FROM us_population");
+
+ ResultSet resultSet = statement.executeQuery();
+
+ while (resultSet.next()) {
+ System.out.println(resultSet.getString("city") + " "
+ + resultSet.getInt("population"));
+ }
+
+ statement.close();
+ connection.close();
+ }
+}
+```
+
+结果如下:
+
+
+
+
+实际的开发中我们通常都是采用第三方框架来操作数据库,如 `mybatis`,`Hibernate`,`Spring Data` 等。关于 Phoenix 与这些框架的整合步骤参见下一篇文章:[Spring/Spring Boot + Mybatis + Phoenix](https://github.com/heibaiying/BigData-Notes/blob/master/notes/Spring+Mybtais+Phoenix整合.md)
+
+# 参考资料
+
+1. http://phoenix.apache.org/
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase\347\256\200\344\273\213.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase\347\256\200\344\273\213.md"
new file mode 100644
index 0000000..436e4a0
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase\347\256\200\344\273\213.md"
@@ -0,0 +1,88 @@
+# HBase简介
+
+
+一、Hadoop的局限
+二、HBase简介
+三、HBase Table
+四、Phoenix
+
+
+## 一、Hadoop的局限
+
+HBase 是一个构建在 Hadoop 文件系统之上的面向列的数据库管理系统。
+
+
+
+要想明白为什么产生 HBase,就需要先了解一下 Hadoop 存在的限制?Hadoop 可以通过 HDFS 来存储结构化、半结构甚至非结构化的数据,它是传统数据库的补充,是海量数据存储的最佳方法,它针对大文件的存储,批量访问和流式访问都做了优化,同时也通过多副本解决了容灾问题。
+
+但是 Hadoop 的缺陷在于它只能执行批处理,并且只能以顺序方式访问数据,这意味着即使是最简单的工作,也必须搜索整个数据集,无法实现对数据的随机访问。实现数据的随机访问是传统的关系型数据库所擅长的,但它们却不能用于海量数据的存储。在这种情况下,必须有一种新的方案来解决海量数据存储和随机访问的问题,HBase 就是其中之一 (HBase,Cassandra,couchDB,Dynamo 和 MongoDB 都能存储海量数据并支持随机访问)。
+
+> 注:数据结构分类:
+>
+> - 结构化数据:即以关系型数据库表形式管理的数据;
+> - 半结构化数据:非关系模型的,有基本固定结构模式的数据,例如日志文件、XML 文档、JSON 文档、Email 等;
+> - 非结构化数据:没有固定模式的数据,如 WORD、PDF、PPT、EXL,各种格式的图片、视频等。
+
+
+
+## 二、HBase简介
+
+HBase 是一个构建在 Hadoop 文件系统之上的面向列的数据库管理系统。
+
+HBase 是一种类似于 `Google’s Big Table` 的数据模型,它是 Hadoop 生态系统的一部分,它将数据存储在 HDFS 上,客户端可以通过 HBase 实现对 HDFS 上数据的随机访问。它具有以下特性:
+
++ 不支持复杂的事务,只支持行级事务,即单行数据的读写都是原子性的;
++ 由于是采用 HDFS 作为底层存储,所以和 HDFS 一样,支持结构化、半结构化和非结构化的存储;
++ 支持通过增加机器进行横向扩展;
++ 支持数据分片;
++ 支持 RegionServers 之间的自动故障转移;
++ 易于使用的 Java 客户端 API;
++ 支持 BlockCache 和布隆过滤器;
++ 过滤器支持谓词下推。
+
+
+
+## 三、HBase Table
+
+HBase 是一个面向 ` 列 ` 的数据库管理系统,这里更为确切的而说,HBase 是一个面向 ` 列族 ` 的数据库管理系统。表 schema 仅定义列族,表具有多个列族,每个列族可以包含任意数量的列,列由多个单元格(cell )组成,单元格可以存储多个版本的数据,多个版本数据以时间戳进行区分。
+
+下图为 HBase 中一张表的:
+
++ RowKey 为行的唯一标识,所有行按照 RowKey 的字典序进行排序;
++ 该表具有两个列族,分别是 personal 和 office;
++ 其中列族 personal 拥有 name、city、phone 三个列,列族 office 拥有 tel、addres 两个列。
+
+
+
+> *图片引用自 : HBase 是列式存储数据库吗* *https://www.iteblog.com/archives/2498.html*
+
+Hbase 的表具有以下特点:
+
+- 容量大:一个表可以有数十亿行,上百万列;
+
+- 面向列:数据是按照列存储,每一列都单独存放,数据即索引,在查询时可以只访问指定列的数据,有效地降低了系统的 I/O 负担;
+
+- 稀疏性:空 (null) 列并不占用存储空间,表可以设计的非常稀疏 ;
+
+- 数据多版本:每个单元中的数据可以有多个版本,按照时间戳排序,新的数据在最上面;
+
+- 存储类型:所有数据的底层存储格式都是字节数组 (byte[])。
+
+
+
+## 四、Phoenix
+
+`Phoenix` 是 HBase 的开源 SQL 中间层,它允许你使用标准 JDBC 的方式来操作 HBase 上的数据。在 `Phoenix` 之前,如果你要访问 HBase,只能调用它的 Java API,但相比于使用一行 SQL 就能实现数据查询,HBase 的 API 还是过于复杂。`Phoenix` 的理念是 `we put sql SQL back in NOSQL`,即你可以使用标准的 SQL 就能完成对 HBase 上数据的操作。同时这也意味着你可以通过集成 `Spring Data JPA` 或 `Mybatis` 等常用的持久层框架来操作 HBase。
+
+其次 `Phoenix` 的性能表现也非常优异,`Phoenix` 查询引擎会将 SQL 查询转换为一个或多个 HBase Scan,通过并行执行来生成标准的 JDBC 结果集。它通过直接使用 HBase API 以及协处理器和自定义过滤器,可以为小型数据查询提供毫秒级的性能,为千万行数据的查询提供秒级的性能。同时 Phoenix 还拥有二级索引等 HBase 不具备的特性,因为以上的优点,所以 `Phoenix` 成为了 HBase 最优秀的 SQL 中间层。
+
+
+
+
+
+## 参考资料
+
+1. [HBase - Overview](https://www.tutorialspoint.com/hbase/hbase_overview.htm)
+
+
+
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase\347\263\273\347\273\237\346\236\266\346\236\204\345\217\212\346\225\260\346\215\256\347\273\223\346\236\204.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase\347\263\273\347\273\237\346\236\266\346\236\204\345\217\212\346\225\260\346\215\256\347\273\223\346\236\204.md"
new file mode 100644
index 0000000..1b871bd
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase\347\263\273\347\273\237\346\236\266\346\236\204\345\217\212\346\225\260\346\215\256\347\273\223\346\236\204.md"
@@ -0,0 +1,222 @@
+# Hbase系统架构及数据结构
+
+
+一、基本概念
+ 1.1 Row Key (行键)
+ 1.2 Column Family(列族)
+ 1.3 Column Qualifier (列限定符)
+ 1.4 Column(列)
+ 1.5 Cell
+ 1.6 Timestamp(时间戳)
+二、存储结构
+ 2.1 Regions
+ 2.2 Region Server
+三、Hbase系统架构
+ 3.1 系统架构
+ 3.2 组件间的协作
+四、数据的读写流程简述
+ 4.1 写入数据的流程
+ 4.2 读取数据的流程
+
+
+## 一、基本概念
+
+一个典型的 Hbase Table 表如下:
+
+
+
+### 1.1 Row Key (行键)
+
+`Row Key` 是用来检索记录的主键。想要访问 HBase Table 中的数据,只有以下三种方式:
+
++ 通过指定的 `Row Key` 进行访问;
+
++ 通过 Row Key 的 range 进行访问,即访问指定范围内的行;
+
++ 进行全表扫描。
+
+`Row Key` 可以是任意字符串,存储时数据按照 `Row Key` 的字典序进行排序。这里需要注意以下两点:
+
++ 因为字典序对 Int 排序的结果是 1,10,100,11,12,13,14,15,16,17,18,19,2,20,21,…,9,91,92,93,94,95,96,97,98,99。如果你使用整型的字符串作为行键,那么为了保持整型的自然序,行键必须用 0 作左填充。
+
++ 行的一次读写操作时原子性的 (不论一次读写多少列)。
+
+
+
+### 1.2 Column Family(列族)
+
+HBase 表中的每个列,都归属于某个列族。列族是表的 Schema 的一部分,所以列族需要在创建表时进行定义。列族的所有列都以列族名作为前缀,例如 `courses:history`,`courses:math` 都属于 `courses` 这个列族。
+
+
+
+### 1.3 Column Qualifier (列限定符)
+
+列限定符,你可以理解为是具体的列名,例如 `courses:history`,`courses:math` 都属于 `courses` 这个列族,它们的列限定符分别是 `history` 和 `math`。需要注意的是列限定符不是表 Schema 的一部分,你可以在插入数据的过程中动态创建列。
+
+
+
+### 1.4 Column(列)
+
+HBase 中的列由列族和列限定符组成,它们由 `:`(冒号) 进行分隔,即一个完整的列名应该表述为 ` 列族名 :列限定符 `。
+
+
+
+### 1.5 Cell
+
+`Cell` 是行,列族和列限定符的组合,并包含值和时间戳。你可以等价理解为关系型数据库中由指定行和指定列确定的一个单元格,但不同的是 HBase 中的一个单元格是由多个版本的数据组成的,每个版本的数据用时间戳进行区分。
+
+
+
+### 1.6 Timestamp(时间戳)
+
+HBase 中通过 `row key` 和 `column` 确定的为一个存储单元称为 `Cell`。每个 `Cell` 都保存着同一份数据的多个版本。版本通过时间戳来索引,时间戳的类型是 64 位整型,时间戳可以由 HBase 在数据写入时自动赋值,也可以由客户显式指定。每个 `Cell` 中,不同版本的数据按照时间戳倒序排列,即最新的数据排在最前面。
+
+
+
+## 二、存储结构
+
+### 2.1 Regions
+
+HBase Table 中的所有行按照 `Row Key` 的字典序排列。HBase Tables 通过行键的范围 (row key range) 被水平切分成多个 `Region`, 一个 `Region` 包含了在 start key 和 end key 之间的所有行。
+
+
+
+每个表一开始只有一个 `Region`,随着数据不断增加,`Region` 会不断增大,当增大到一个阀值的时候,`Region` 就会等分为两个新的 `Region`。当 Table 中的行不断增多,就会有越来越多的 `Region`。
+
+
+
+`Region` 是 HBase 中**分布式存储和负载均衡的最小单元**。这意味着不同的 `Region` 可以分布在不同的 `Region Server` 上。但一个 `Region` 是不会拆分到多个 Server 上的。
+
+
+
+### 2.2 Region Server
+
+`Region Server` 运行在 HDFS 的 DataNode 上。它具有以下组件:
+
+- **WAL(Write Ahead Log,预写日志)**:用于存储尚未进持久化存储的数据记录,以便在发生故障时进行恢复。
+- **BlockCache**:读缓存。它将频繁读取的数据存储在内存中,如果存储不足,它将按照 ` 最近最少使用原则 ` 清除多余的数据。
+- **MemStore**:写缓存。它存储尚未写入磁盘的新数据,并会在数据写入磁盘之前对其进行排序。每个 Region 上的每个列族都有一个 MemStore。
+- **HFile** :将行数据按照 Key\Values 的形式存储在文件系统上。
+
+
+
+
+
+Region Server 存取一个子表时,会创建一个 Region 对象,然后对表的每个列族创建一个 `Store` 实例,每个 `Store` 会有 0 个或多个 `StoreFile` 与之对应,每个 `StoreFile` 则对应一个 `HFile`,HFile 就是实际存储在 HDFS 上的文件。
+
+
+
+
+
+## 三、Hbase系统架构
+
+### 3.1 系统架构
+
+HBase 系统遵循 Master/Salve 架构,由三种不同类型的组件组成:
+
+**Zookeeper**
+
+1. 保证任何时候,集群中只有一个 Master;
+
+2. 存贮所有 Region 的寻址入口;
+
+3. 实时监控 Region Server 的状态,将 Region Server 的上线和下线信息实时通知给 Master;
+
+4. 存储 HBase 的 Schema,包括有哪些 Table,每个 Table 有哪些 Column Family 等信息。
+
+**Master**
+
+1. 为 Region Server 分配 Region ;
+
+2. 负责 Region Server 的负载均衡 ;
+
+3. 发现失效的 Region Server 并重新分配其上的 Region;
+
+4. GFS 上的垃圾文件回收;
+
+5. 处理 Schema 的更新请求。
+
+**Region Server**
+
+1. Region Server 负责维护 Master 分配给它的 Region ,并处理发送到 Region 上的 IO 请求;
+
+2. Region Server 负责切分在运行过程中变得过大的 Region。
+
+
+
+### 3.2 组件间的协作
+
+ HBase 使用 ZooKeeper 作为分布式协调服务来维护集群中的服务器状态。 Zookeeper 负责维护可用服务列表,并提供服务故障通知等服务:
+
++ 每个 Region Server 都会在 ZooKeeper 上创建一个临时节点,Master 通过 Zookeeper 的 Watcher 机制对节点进行监控,从而可以发现新加入的 Region Server 或故障退出的 Region Server;
+
++ 所有 Masters 会竞争性地在 Zookeeper 上创建同一个临时节点,由于 Zookeeper 只能有一个同名节点,所以必然只有一个 Master 能够创建成功,此时该 Master 就是主 Master,主 Master 会定期向 Zookeeper 发送心跳。备用 Masters 则通过 Watcher 机制对主 HMaster 所在节点进行监听;
+
++ 如果主 Master 未能定时发送心跳,则其持有的 Zookeeper 会话会过期,相应的临时节点也会被删除,这会触发定义在该节点上的 Watcher 事件,使得备用的 Master Servers 得到通知。所有备用的 Master Servers 在接到通知后,会再次去竞争性地创建临时节点,完成主 Master 的选举。
+
+
+
+
+
+## 四、数据的读写流程简述
+
+### 4.1 写入数据的流程
+
+1. Client 向 Region Server 提交写请求;
+
+2. Region Server 找到目标 Region;
+
+3. Region 检查数据是否与 Schema 一致;
+
+4. 如果客户端没有指定版本,则获取当前系统时间作为数据版本;
+
+5. 将更新写入 WAL Log;
+
+6. 将更新写入 Memstore;
+
+7. 判断 Memstore 存储是否已满,如果存储已满则需要 flush 为 Store Hfile 文件。
+
+> 更为详细写入流程可以参考:[HBase - 数据写入流程解析](http://hbasefly.com/2016/03/23/hbase_writer/)
+
+
+
+### 4.2 读取数据的流程
+
+以下是客户端首次读写 HBase 上数据的流程:
+
+1. 客户端从 Zookeeper 获取 `META` 表所在的 Region Server;
+
+2. 客户端访问 `META` 表所在的 Region Server,从 `META` 表中查询到访问行键所在的 Region Server,之后客户端将缓存这些信息以及 `META` 表的位置;
+
+3. 客户端从行键所在的 Region Server 上获取数据。
+
+如果再次读取,客户端将从缓存中获取行键所在的 Region Server。这样客户端就不需要再次查询 `META` 表,除非 Region 移动导致缓存失效,这样的话,则将会重新查询并更新缓存。
+
+注:`META` 表是 HBase 中一张特殊的表,它保存了所有 Region 的位置信息,META 表自己的位置信息则存储在 ZooKeeper 上。
+
+
+
+> 更为详细读取数据流程参考:
+>
+> [HBase 原理-数据读取流程解析](http://hbasefly.com/2016/12/21/hbase-getorscan/)
+>
+> [HBase 原理-迟到的‘数据读取流程部分细节](http://hbasefly.com/2017/06/11/hbase-scan-2/)
+
+
+
+
+
+## 参考资料
+
+本篇文章内容主要参考自官方文档和以下两篇博客,图片也主要引用自以下两篇博客:
+
++ [HBase Architectural Components](https://mapr.com/blog/in-depth-look-hbase-architecture/#.VdMxvWSqqko)
+
++ [Hbase 系统架构及数据结构](https://www.open-open.com/lib/view/open1346821084631.html)
+
+官方文档:
+
++ [Apache HBase ™ Reference Guide](https://hbase.apache.org/2.1/book.html)
+
+
+
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase\350\277\207\346\273\244\345\231\250\350\257\246\350\247\243.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase\350\277\207\346\273\244\345\231\250\350\257\246\350\247\243.md"
new file mode 100644
index 0000000..85149be
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hbase\350\277\207\346\273\244\345\231\250\350\257\246\350\247\243.md"
@@ -0,0 +1,445 @@
+# Hbase 过滤器详解
+
+
+一、HBase过滤器简介
+二、过滤器基础
+ 2.1 Filter接口和FilterBase抽象类
+ 2.2 过滤器分类
+三、比较过滤器
+ 3.1 比较运算符
+ 3.2 比较器
+ 3.3 比较过滤器种类
+ 3.4 DependentColumnFilter
+四、专用过滤器
+ 4.1 单列列值过滤器 (SingleColumnValueFilter)
+ 4.2 单列列值排除器 (SingleColumnValueExcludeFilter)
+ 4.3 行键前缀过滤器 (PrefixFilter)
+ 4.4 列名前缀过滤器 (ColumnPrefixFilter)
+ 4.5 分页过滤器 (PageFilter)
+ 4.6 时间戳过滤器 (TimestampsFilter)
+ 4.7 首次行键过滤器 (FirstKeyOnlyFilter)
+五、包装过滤器
+ 5.1 SkipFilter过滤器
+ 5.2 WhileMatchFilter过滤器
+六、FilterList
+
+
+
+
+## 一、HBase过滤器简介
+
+Hbase 提供了种类丰富的过滤器(filter)来提高数据处理的效率,用户可以通过内置或自定义的过滤器来对数据进行过滤,所有的过滤器都在服务端生效,即谓词下推(predicate push down)。这样可以保证过滤掉的数据不会被传送到客户端,从而减轻网络传输和客户端处理的压力。
+
+
+
+
+
+## 二、过滤器基础
+
+### 2.1 Filter接口和FilterBase抽象类
+
+Filter 接口中定义了过滤器的基本方法,FilterBase 抽象类实现了 Filter 接口。所有内置的过滤器则直接或者间接继承自 FilterBase 抽象类。用户只需要将定义好的过滤器通过 `setFilter` 方法传递给 `Scan` 或 `put` 的实例即可。
+
+```java
+setFilter(Filter filter)
+```
+
+```java
+ // Scan 中定义的 setFilter
+ @Override
+ public Scan setFilter(Filter filter) {
+ super.setFilter(filter);
+ return this;
+ }
+```
+
+```java
+ // Get 中定义的 setFilter
+ @Override
+ public Get setFilter(Filter filter) {
+ super.setFilter(filter);
+ return this;
+ }
+```
+
+FilterBase 的所有子类过滤器如下:
+
+> 说明:上图基于当前时间点(2019.4)最新的 Hbase-2.1.4 ,下文所有说明均基于此版本。
+
+
+
+### 2.2 过滤器分类
+
+HBase 内置过滤器可以分为三类:分别是比较过滤器,专用过滤器和包装过滤器。分别在下面的三个小节中做详细的介绍。
+
+
+
+## 三、比较过滤器
+
+所有比较过滤器均继承自 `CompareFilter`。创建一个比较过滤器需要两个参数,分别是**比较运算符**和**比较器实例**。
+
+```java
+ public CompareFilter(final CompareOp compareOp,final ByteArrayComparable comparator) {
+ this.compareOp = compareOp;
+ this.comparator = comparator;
+ }
+```
+
+### 3.1 比较运算符
+
+- LESS (<)
+- LESS_OR_EQUAL (<=)
+- EQUAL (=)
+- NOT_EQUAL (!=)
+- GREATER_OR_EQUAL (>=)
+- GREATER (>)
+- NO_OP (排除所有符合条件的值)
+
+比较运算符均定义在枚举类 `CompareOperator` 中
+
+```java
+@InterfaceAudience.Public
+public enum CompareOperator {
+ LESS,
+ LESS_OR_EQUAL,
+ EQUAL,
+ NOT_EQUAL,
+ GREATER_OR_EQUAL,
+ GREATER,
+ NO_OP,
+}
+```
+
+> 注意:在 1.x 版本的 HBase 中,比较运算符定义在 `CompareFilter.CompareOp` 枚举类中,但在 2.0 之后这个类就被标识为 @deprecated ,并会在 3.0 移除。所以 2.0 之后版本的 HBase 需要使用 `CompareOperator` 这个枚举类。
+>
+
+### 3.2 比较器
+
+所有比较器均继承自 `ByteArrayComparable` 抽象类,常用的有以下几种:
+
+
+
+- **BinaryComparator** : 使用 `Bytes.compareTo(byte [],byte [])` 按字典序比较指定的字节数组。
+- **BinaryPrefixComparator** : 按字典序与指定的字节数组进行比较,但只比较到这个字节数组的长度。
+- **RegexStringComparator** : 使用给定的正则表达式与指定的字节数组进行比较。仅支持 `EQUAL` 和 `NOT_EQUAL` 操作。
+- **SubStringComparator** : 测试给定的子字符串是否出现在指定的字节数组中,比较不区分大小写。仅支持 `EQUAL` 和 `NOT_EQUAL` 操作。
+- **NullComparator** :判断给定的值是否为空。
+- **BitComparator** :按位进行比较。
+
+`BinaryPrefixComparator` 和 `BinaryComparator` 的区别不是很好理解,这里举例说明一下:
+
+在进行 `EQUAL` 的比较时,如果比较器传入的是 `abcd` 的字节数组,但是待比较数据是 `abcdefgh`:
+
++ 如果使用的是 `BinaryPrefixComparator` 比较器,则比较以 `abcd` 字节数组的长度为准,即 `efgh` 不会参与比较,这时候认为 `abcd` 与 `abcdefgh` 是满足 `EQUAL` 条件的;
++ 如果使用的是 `BinaryComparator` 比较器,则认为其是不相等的。
+
+### 3.3 比较过滤器种类
+
+比较过滤器共有五个(Hbase 1.x 版本和 2.x 版本相同),见下图:
+
+
+
++ **RowFilter** :基于行键来过滤数据;
++ **FamilyFilterr** :基于列族来过滤数据;
++ **QualifierFilterr** :基于列限定符(列名)来过滤数据;
++ **ValueFilterr** :基于单元格 (cell) 的值来过滤数据;
++ **DependentColumnFilter** :指定一个参考列来过滤其他列的过滤器,过滤的原则是基于参考列的时间戳来进行筛选 。
+
+前四种过滤器的使用方法相同,均只要传递比较运算符和运算器实例即可构建,然后通过 `setFilter` 方法传递给 `scan`:
+
+```java
+ Filter filter = new RowFilter(CompareOperator.LESS_OR_EQUAL,
+ new BinaryComparator(Bytes.toBytes("xxx")));
+ scan.setFilter(filter);
+```
+
+`DependentColumnFilter` 的使用稍微复杂一点,这里单独做下说明。
+
+### 3.4 DependentColumnFilter
+
+可以把 `DependentColumnFilter` 理解为**一个 valueFilter 和一个时间戳过滤器的组合**。`DependentColumnFilter` 有三个带参构造器,这里选择一个参数最全的进行说明:
+
+```java
+DependentColumnFilter(final byte [] family, final byte[] qualifier,
+ final boolean dropDependentColumn, final CompareOperator op,
+ final ByteArrayComparable valueComparator)
+```
+
++ **family** :列族
++ **qualifier** :列限定符(列名)
++ **dropDependentColumn** :决定参考列是否被包含在返回结果内,为 true 时表示参考列被返回,为 false 时表示被丢弃
++ **op** :比较运算符
++ **valueComparator** :比较器
+
+这里举例进行说明:
+
+```java
+DependentColumnFilter dependentColumnFilter = new DependentColumnFilter(
+ Bytes.toBytes("student"),
+ Bytes.toBytes("name"),
+ false,
+ CompareOperator.EQUAL,
+ new BinaryPrefixComparator(Bytes.toBytes("xiaolan")));
+```
+
++ 首先会去查找 `student:name` 中值以 `xiaolan` 开头的所有数据获得 ` 参考数据集 `,这一步等同于 valueFilter 过滤器;
+
++ 其次再用参考数据集中所有数据的时间戳去检索其他列,获得时间戳相同的其他列的数据作为 ` 结果数据集 `,这一步等同于时间戳过滤器;
+
++ 最后如果 `dropDependentColumn` 为 true,则返回 ` 参考数据集 `+` 结果数据集 `,若为 false,则抛弃参考数据集,只返回 ` 结果数据集 `。
+
+
+
+## 四、专用过滤器
+
+专用过滤器通常直接继承自 `FilterBase`,适用于范围更小的筛选规则。
+
+### 4.1 单列列值过滤器 (SingleColumnValueFilter)
+
+基于某列(参考列)的值决定某行数据是否被过滤。其实例有以下方法:
+
++ **setFilterIfMissing(boolean filterIfMissing)** :默认值为 false,即如果该行数据不包含参考列,其依然被包含在最后的结果中;设置为 true 时,则不包含;
++ **setLatestVersionOnly(boolean latestVersionOnly)** :默认为 true,即只检索参考列的最新版本数据;设置为 false,则检索所有版本数据。
+
+```shell
+SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter(
+ "student".getBytes(),
+ "name".getBytes(),
+ CompareOperator.EQUAL,
+ new SubstringComparator("xiaolan"));
+singleColumnValueFilter.setFilterIfMissing(true);
+scan.setFilter(singleColumnValueFilter);
+```
+
+### 4.2 单列列值排除器 (SingleColumnValueExcludeFilter)
+
+`SingleColumnValueExcludeFilter` 继承自上面的 `SingleColumnValueFilter`,过滤行为与其相反。
+
+### 4.3 行键前缀过滤器 (PrefixFilter)
+
+基于 RowKey 值决定某行数据是否被过滤。
+
+```java
+PrefixFilter prefixFilter = new PrefixFilter(Bytes.toBytes("xxx"));
+scan.setFilter(prefixFilter);
+```
+
+### 4.4 列名前缀过滤器 (ColumnPrefixFilter)
+
+基于列限定符(列名)决定某行数据是否被过滤。
+
+```java
+ColumnPrefixFilter columnPrefixFilter = new ColumnPrefixFilter(Bytes.toBytes("xxx"));
+ scan.setFilter(columnPrefixFilter);
+```
+
+### 4.5 分页过滤器 (PageFilter)
+
+可以使用这个过滤器实现对结果按行进行分页,创建 PageFilter 实例的时候需要传入每页的行数。
+
+```java
+public PageFilter(final long pageSize) {
+ Preconditions.checkArgument(pageSize >= 0, "must be positive %s", pageSize);
+ this.pageSize = pageSize;
+ }
+```
+
+下面的代码体现了客户端实现分页查询的主要逻辑,这里对其进行一下解释说明:
+
+客户端进行分页查询,需要传递 `startRow`(起始 RowKey),知道起始 `startRow` 后,就可以返回对应的 pageSize 行数据。这里唯一的问题就是,对于第一次查询,显然 `startRow` 就是表格的第一行数据,但是之后第二次、第三次查询我们并不知道 `startRow`,只能知道上一次查询的最后一条数据的 RowKey(简单称之为 `lastRow`)。
+
+我们不能将 `lastRow` 作为新一次查询的 `startRow` 传入,因为 scan 的查询区间是[startRow,endRow) ,即前开后闭区间,这样 `startRow` 在新的查询也会被返回,这条数据就重复了。
+
+同时在不使用第三方数据库存储 RowKey 的情况下,我们是无法通过知道 `lastRow` 的下一个 RowKey 的,因为 RowKey 的设计可能是连续的也有可能是不连续的。
+
+由于 Hbase 的 RowKey 是按照字典序进行排序的。这种情况下,就可以在 `lastRow` 后面加上 `0` ,作为 `startRow` 传入,因为按照字典序的规则,某个值加上 `0` 后的新值,在字典序上一定是这个值的下一个值,对于 HBase 来说下一个 RowKey 在字典序上一定也是等于或者大于这个新值的。
+
+所以最后传入 `lastRow`+`0`,如果等于这个值的 RowKey 存在就从这个值开始 scan,否则从字典序的下一个 RowKey 开始 scan。
+
+> 25 个字母以及数字字符,字典排序如下:
+>
+> `'0' < '1' < '2' < ... < '9' < 'a' < 'b' < ... < 'z'`
+
+分页查询主要实现逻辑:
+
+```java
+byte[] POSTFIX = new byte[] { 0x00 };
+Filter filter = new PageFilter(15);
+
+int totalRows = 0;
+byte[] lastRow = null;
+while (true) {
+ Scan scan = new Scan();
+ scan.setFilter(filter);
+ if (lastRow != null) {
+ // 如果不是首行 则 lastRow + 0
+ byte[] startRow = Bytes.add(lastRow, POSTFIX);
+ System.out.println("start row: " +
+ Bytes.toStringBinary(startRow));
+ scan.withStartRow(startRow);
+ }
+ ResultScanner scanner = table.getScanner(scan);
+ int localRows = 0;
+ Result result;
+ while ((result = scanner.next()) != null) {
+ System.out.println(localRows++ + ": " + result);
+ totalRows++;
+ lastRow = result.getRow();
+ }
+ scanner.close();
+ //最后一页,查询结束
+ if (localRows == 0) break;
+}
+System.out.println("total rows: " + totalRows);
+```
+
+>需要注意的是在多台 Regin Services 上执行分页过滤的时候,由于并行执行的过滤器不能共享它们的状态和边界,所以有可能每个过滤器都会在完成扫描前获取了 PageCount 行的结果,这种情况下会返回比分页条数更多的数据,分页过滤器就有失效的可能。
+
+
+
+### 4.6 时间戳过滤器 (TimestampsFilter)
+
+```java
+List list = new ArrayList<>();
+list.add(1554975573000L);
+TimestampsFilter timestampsFilter = new TimestampsFilter(list);
+scan.setFilter(timestampsFilter);
+```
+
+### 4.7 首次行键过滤器 (FirstKeyOnlyFilter)
+
+`FirstKeyOnlyFilter` 只扫描每行的第一列,扫描完第一列后就结束对当前行的扫描,并跳转到下一行。相比于全表扫描,其性能更好,通常用于行数统计的场景,因为如果某一行存在,则行中必然至少有一列。
+
+```java
+FirstKeyOnlyFilter firstKeyOnlyFilter = new FirstKeyOnlyFilter();
+scan.set(firstKeyOnlyFilter);
+```
+
+## 五、包装过滤器
+
+包装过滤器就是通过包装其他过滤器以实现某些拓展的功能。
+
+### 5.1 SkipFilter过滤器
+
+`SkipFilter` 包装一个过滤器,当被包装的过滤器遇到一个需要过滤的 KeyValue 实例时,则拓展过滤整行数据。下面是一个使用示例:
+
+```java
+// 定义 ValueFilter 过滤器
+Filter filter1 = new ValueFilter(CompareOperator.NOT_EQUAL,
+ new BinaryComparator(Bytes.toBytes("xxx")));
+// 使用 SkipFilter 进行包装
+Filter filter2 = new SkipFilter(filter1);
+```
+
+
+
+### 5.2 WhileMatchFilter过滤器
+
+`WhileMatchFilter` 包装一个过滤器,当被包装的过滤器遇到一个需要过滤的 KeyValue 实例时,`WhileMatchFilter` 则结束本次扫描,返回已经扫描到的结果。下面是其使用示例:
+
+```java
+Filter filter1 = new RowFilter(CompareOperator.NOT_EQUAL,
+ new BinaryComparator(Bytes.toBytes("rowKey4")));
+
+Scan scan = new Scan();
+scan.setFilter(filter1);
+ResultScanner scanner1 = table.getScanner(scan);
+for (Result result : scanner1) {
+ for (Cell cell : result.listCells()) {
+ System.out.println(cell);
+ }
+}
+scanner1.close();
+
+System.out.println("--------------------");
+
+// 使用 WhileMatchFilter 进行包装
+Filter filter2 = new WhileMatchFilter(filter1);
+
+scan.setFilter(filter2);
+ResultScanner scanner2 = table.getScanner(scan);
+for (Result result : scanner1) {
+ for (Cell cell : result.listCells()) {
+ System.out.println(cell);
+ }
+}
+scanner2.close();
+```
+
+```properties
+rowKey0/student:name/1555035006994/Put/vlen=8/seqid=0
+rowKey1/student:name/1555035007019/Put/vlen=8/seqid=0
+rowKey2/student:name/1555035007025/Put/vlen=8/seqid=0
+rowKey3/student:name/1555035007037/Put/vlen=8/seqid=0
+rowKey5/student:name/1555035007051/Put/vlen=8/seqid=0
+rowKey6/student:name/1555035007057/Put/vlen=8/seqid=0
+rowKey7/student:name/1555035007062/Put/vlen=8/seqid=0
+rowKey8/student:name/1555035007068/Put/vlen=8/seqid=0
+rowKey9/student:name/1555035007073/Put/vlen=8/seqid=0
+--------------------
+rowKey0/student:name/1555035006994/Put/vlen=8/seqid=0
+rowKey1/student:name/1555035007019/Put/vlen=8/seqid=0
+rowKey2/student:name/1555035007025/Put/vlen=8/seqid=0
+rowKey3/student:name/1555035007037/Put/vlen=8/seqid=0
+```
+
+可以看到被包装后,只返回了 `rowKey4` 之前的数据。
+
+## 六、FilterList
+
+以上都是讲解单个过滤器的作用,当需要多个过滤器共同作用于一次查询的时候,就需要使用 `FilterList`。`FilterList` 支持通过构造器或者 `addFilter` 方法传入多个过滤器。
+
+```java
+// 构造器传入
+public FilterList(final Operator operator, final List filters)
+public FilterList(final List filters)
+public FilterList(final Filter... filters)
+
+// 方法传入
+ public void addFilter(List filters)
+ public void addFilter(Filter filter)
+```
+
+多个过滤器组合的结果由 `operator` 参数定义 ,其可选参数定义在 `Operator` 枚举类中。只有 `MUST_PASS_ALL` 和 `MUST_PASS_ONE` 两个可选的值:
+
++ **MUST_PASS_ALL** :相当于 AND,必须所有的过滤器都通过才认为通过;
++ **MUST_PASS_ONE** :相当于 OR,只有要一个过滤器通过则认为通过。
+
+```java
+@InterfaceAudience.Public
+ public enum Operator {
+ /** !AND */
+ MUST_PASS_ALL,
+ /** !OR */
+ MUST_PASS_ONE
+ }
+```
+
+使用示例如下:
+
+```java
+List filters = new ArrayList();
+
+Filter filter1 = new RowFilter(CompareOperator.GREATER_OR_EQUAL,
+ new BinaryComparator(Bytes.toBytes("XXX")));
+filters.add(filter1);
+
+Filter filter2 = new RowFilter(CompareOperator.LESS_OR_EQUAL,
+ new BinaryComparator(Bytes.toBytes("YYY")));
+filters.add(filter2);
+
+Filter filter3 = new QualifierFilter(CompareOperator.EQUAL,
+ new RegexStringComparator("ZZZ"));
+filters.add(filter3);
+
+FilterList filterList = new FilterList(filters);
+
+Scan scan = new Scan();
+scan.setFilter(filterList);
+```
+
+
+
+## 参考资料
+
+[HBase: The Definitive Guide _> Chapter 4. Client API: Advanced Features](https://www.oreilly.com/library/view/hbase-the-definitive/9781449314682/ch04.html)
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/HiveCLI\345\222\214Beeline\345\221\275\344\273\244\350\241\214\347\232\204\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/HiveCLI\345\222\214Beeline\345\221\275\344\273\244\350\241\214\347\232\204\345\237\272\346\234\254\344\275\277\347\224\250.md"
new file mode 100644
index 0000000..2dd706b
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/HiveCLI\345\222\214Beeline\345\221\275\344\273\244\350\241\214\347\232\204\345\237\272\346\234\254\344\275\277\347\224\250.md"
@@ -0,0 +1,279 @@
+# Hive CLI和Beeline命令行的基本使用
+
+
+一、Hive CLI
+ 1.1 Help
+ 1.2 交互式命令行
+ 1.3 执行SQL命令
+ 1.4 执行SQL脚本
+ 1.5 配置Hive变量
+ 1.6 配置文件启动
+ 1.7 用户自定义变量
+二、Beeline
+ 2.1 HiveServer2
+ 2.1 Beeline
+ 2.3 常用参数
+三、Hive配置
+ 3.1 配置文件
+ 3.2 hiveconf
+ 3.3 set
+ 3.4 配置优先级
+ 3.5 配置参数
+
+
+## 一、Hive CLI
+
+### 1.1 Help
+
+使用 `hive -H` 或者 `hive --help` 命令可以查看所有命令的帮助,显示如下:
+
+```
+usage: hive
+ -d,--define Variable subsitution to apply to hive
+ commands. e.g. -d A=B or --define A=B --定义用户自定义变量
+ --database Specify the database to use -- 指定使用的数据库
+ -e SQL from command line -- 执行指定的 SQL
+ -f SQL from files --执行 SQL 脚本
+ -H,--help Print help information -- 打印帮助信息
+ --hiveconf Use value for given property --自定义配置
+ --hivevar Variable subsitution to apply to hive --自定义变量
+ commands. e.g. --hivevar A=B
+ -i Initialization SQL file --在进入交互模式之前运行初始化脚本
+ -S,--silent Silent mode in interactive shell --静默模式
+ -v,--verbose Verbose mode (echo executed SQL to the console) --详细模式
+```
+
+### 1.2 交互式命令行
+
+直接使用 `Hive` 命令,不加任何参数,即可进入交互式命令行。
+
+### 1.3 执行SQL命令
+
+在不进入交互式命令行的情况下,可以使用 `hive -e ` 执行 SQL 命令。
+
+```sql
+hive -e 'select * from emp';
+```
+
+
+
+
+
+### 1.4 执行SQL脚本
+
+用于执行的 sql 脚本可以在本地文件系统,也可以在 HDFS 上。
+
+```shell
+# 本地文件系统
+hive -f /usr/file/simple.sql;
+
+# HDFS文件系统
+hive -f hdfs://hadoop001:8020/tmp/simple.sql;
+```
+
+其中 `simple.sql` 内容如下:
+
+```sql
+select * from emp;
+```
+
+### 1.5 配置Hive变量
+
+可以使用 `--hiveconf` 设置 Hive 运行时的变量。
+
+```sql
+hive -e 'select * from emp' \
+--hiveconf hive.exec.scratchdir=/tmp/hive_scratch \
+--hiveconf mapred.reduce.tasks=4;
+```
+
+> hive.exec.scratchdir:指定 HDFS 上目录位置,用于存储不同 map/reduce 阶段的执行计划和这些阶段的中间输出结果。
+
+### 1.6 配置文件启动
+
+使用 `-i` 可以在进入交互模式之前运行初始化脚本,相当于指定配置文件启动。
+
+```shell
+hive -i /usr/file/hive-init.conf;
+```
+
+其中 `hive-init.conf` 的内容如下:
+
+```sql
+set hive.exec.mode.local.auto = true;
+```
+
+> hive.exec.mode.local.auto 默认值为 false,这里设置为 true ,代表开启本地模式。
+
+### 1.7 用户自定义变量
+
+`--define ` 和 `--hivevar ` 在功能上是等价的,都是用来实现自定义变量,这里给出一个示例:
+
+定义变量:
+
+```sql
+hive --define n=ename --hiveconf --hivevar j=job;
+```
+
+在查询中引用自定义变量:
+
+```sql
+# 以下两条语句等价
+hive > select ${n} from emp;
+hive > select ${hivevar:n} from emp;
+
+# 以下两条语句等价
+hive > select ${j} from emp;
+hive > select ${hivevar:j} from emp;
+```
+
+结果如下:
+
+
+
+## 二、Beeline
+
+### 2.1 HiveServer2
+
+Hive 内置了 HiveServer 和 HiveServer2 服务,两者都允许客户端使用多种编程语言进行连接,但是 HiveServer 不能处理多个客户端的并发请求,所以产生了 HiveServer2。
+
+HiveServer2(HS2)允许远程客户端可以使用各种编程语言向 Hive 提交请求并检索结果,支持多客户端并发访问和身份验证。HS2 是由多个服务组成的单个进程,其包括基于 Thrift 的 Hive 服务(TCP 或 HTTP)和用于 Web UI 的 Jetty Web 服务器。
+
+ HiveServer2 拥有自己的 CLI(Beeline),Beeline 是一个基于 SQLLine 的 JDBC 客户端。由于 HiveServer2 是 Hive 开发维护的重点 (Hive0.15 后就不再支持 hiveserver),所以 Hive CLI 已经不推荐使用了,官方更加推荐使用 Beeline。
+
+### 2.1 Beeline
+
+Beeline 拥有更多可使用参数,可以使用 `beeline --help` 查看,完整参数如下:
+
+```properties
+Usage: java org.apache.hive.cli.beeline.BeeLine
+ -u the JDBC URL to connect to
+ -r reconnect to last saved connect url (in conjunction with !save)
+ -n the username to connect as
+ -p the password to connect as
+ -d the driver class to use
+ -i script file for initialization
+ -e query that should be executed
+ -f script file that should be executed
+ -w (or) --password-file the password file to read password from
+ --hiveconf property=value Use value for given property
+ --hivevar name=value hive variable name and value
+ This is Hive specific settings in which variables
+ can be set at session level and referenced in Hive
+ commands or queries.
+ --property-file= the file to read connection properties (url, driver, user, password) from
+ --color=[true/false] control whether color is used for display
+ --showHeader=[true/false] show column names in query results
+ --headerInterval=ROWS; the interval between which heades are displayed
+ --fastConnect=[true/false] skip building table/column list for tab-completion
+ --autoCommit=[true/false] enable/disable automatic transaction commit
+ --verbose=[true/false] show verbose error messages and debug info
+ --showWarnings=[true/false] display connection warnings
+ --showNestedErrs=[true/false] display nested errors
+ --numberFormat=[pattern] format numbers using DecimalFormat pattern
+ --force=[true/false] continue running script even after errors
+ --maxWidth=MAXWIDTH the maximum width of the terminal
+ --maxColumnWidth=MAXCOLWIDTH the maximum width to use when displaying columns
+ --silent=[true/false] be more silent
+ --autosave=[true/false] automatically save preferences
+ --outputformat=[table/vertical/csv2/tsv2/dsv/csv/tsv] format mode for result display
+ --incrementalBufferRows=NUMROWS the number of rows to buffer when printing rows on stdout,
+ defaults to 1000; only applicable if --incremental=true
+ and --outputformat=table
+ --truncateTable=[true/false] truncate table column when it exceeds length
+ --delimiterForDSV=DELIMITER specify the delimiter for delimiter-separated values output format (default: |)
+ --isolation=LEVEL set the transaction isolation level
+ --nullemptystring=[true/false] set to true to get historic behavior of printing null as empty string
+ --maxHistoryRows=MAXHISTORYROWS The maximum number of rows to store beeline history.
+ --convertBinaryArrayToString=[true/false] display binary column data as string or as byte array
+ --help display this message
+
+```
+
+### 2.3 常用参数
+
+在 Hive CLI 中支持的参数,Beeline 都支持,常用的参数如下。更多参数说明可以参见官方文档 [Beeline Command Options](https://cwiki.apache.org/confluence/display/Hive/HiveServer2+Clients#HiveServer2Clients-Beeline%E2%80%93NewCommandLineShell)
+
+| 参数 | 说明 |
+| -------------------------------------- | ------------------------------------------------------------ |
+| **-u \** | 数据库地址 |
+| **-n \** | 用户名 |
+| **-p \** | 密码 |
+| **-d \** | 驱动 (可选) |
+| **-e \** | 执行 SQL 命令 |
+| **-f \** | 执行 SQL 脚本 |
+| **-i (or)--init \** | 在进入交互模式之前运行初始化脚本 |
+| **--property-file \** | 指定配置文件 |
+| **--hiveconf** *property**=**value* | 指定配置属性 |
+| **--hivevar** *name**=**value* | 用户自定义属性,在会话级别有效 |
+
+示例: 使用用户名和密码连接 Hive
+
+```shell
+$ beeline -u jdbc:hive2://localhost:10000 -n username -p password
+```
+
+
+
+## 三、Hive配置
+
+可以通过三种方式对 Hive 的相关属性进行配置,分别介绍如下:
+
+### 3.1 配置文件
+
+方式一为使用配置文件,使用配置文件指定的配置是永久有效的。Hive 有以下三个可选的配置文件:
+
++ hive-site.xml :Hive 的主要配置文件;
+
++ hivemetastore-site.xml: 关于元数据的配置;
++ hiveserver2-site.xml:关于 HiveServer2 的配置。
+
+示例如下,在 hive-site.xml 配置 `hive.exec.scratchdir`:
+
+```xml
+
+ hive.exec.scratchdir
+ /tmp/mydir
+ Scratch space for Hive jobs
+
+```
+
+### 3.2 hiveconf
+
+方式二为在启动命令行 (Hive CLI / Beeline) 的时候使用 `--hiveconf` 指定配置,这种方式指定的配置作用于整个 Session。
+
+```
+hive --hiveconf hive.exec.scratchdir=/tmp/mydir
+```
+
+### 3.3 set
+
+方式三为在交互式环境下 (Hive CLI / Beeline),使用 set 命令指定。这种设置的作用范围也是 Session 级别的,配置对于执行该命令后的所有命令生效。set 兼具设置参数和查看参数的功能。如下:
+
+```shell
+0: jdbc:hive2://hadoop001:10000> set hive.exec.scratchdir=/tmp/mydir;
+No rows affected (0.025 seconds)
+0: jdbc:hive2://hadoop001:10000> set hive.exec.scratchdir;
++----------------------------------+--+
+| set |
++----------------------------------+--+
+| hive.exec.scratchdir=/tmp/mydir |
++----------------------------------+--+
+```
+
+### 3.4 配置优先级
+
+配置的优先顺序如下 (由低到高):
+`hive-site.xml` - >` hivemetastore-site.xml `- > `hiveserver2-site.xml` - >` -- hiveconf`- > `set`
+
+### 3.5 配置参数
+
+Hive 可选的配置参数非常多,在用到时查阅官方文档即可[AdminManual Configuration](https://cwiki.apache.org/confluence/display/Hive/AdminManual+Configuration)
+
+
+
+## 参考资料
+
+1. [HiveServer2 Clients](https://cwiki.apache.org/confluence/display/Hive/HiveServer2+Clients)
+2. [LanguageManual Cli](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Cli)
+3. [AdminManual Configuration](https://cwiki.apache.org/confluence/display/Hive/AdminManual+Configuration)
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hive\345\210\206\345\214\272\350\241\250\345\222\214\345\210\206\346\241\266\350\241\250.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hive\345\210\206\345\214\272\350\241\250\345\222\214\345\210\206\346\241\266\350\241\250.md"
new file mode 100644
index 0000000..633d07b
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hive\345\210\206\345\214\272\350\241\250\345\222\214\345\210\206\346\241\266\350\241\250.md"
@@ -0,0 +1,168 @@
+# Hive分区表和分桶表
+
+
+一、分区表
+二、分桶表
+三、分区表和分桶表结合使用
+
+
+
+## 一、分区表
+
+### 1.1 概念
+
+Hive 中的表对应为 HDFS 上的指定目录,在查询数据时候,默认会对全表进行扫描,这样时间和性能的消耗都非常大。
+
+**分区为 HDFS 上表目录的子目录**,数据按照分区存储在子目录中。如果查询的 `where` 字句的中包含分区条件,则直接从该分区去查找,而不是扫描整个表目录,合理的分区设计可以极大提高查询速度和性能。
+
+>这里说明一下分区表并 Hive 独有的概念,实际上这个概念非常常见。比如在我们常用的 Oracle 数据库中,当表中的数据量不断增大,查询数据的速度就会下降,这时也可以对表进行分区。表进行分区后,逻辑上表仍然是一张完整的表,只是将表中的数据存放到多个表空间(物理文件上),这样查询数据时,就不必要每次都扫描整张表,从而提升查询性能。
+
+### 1.2 使用场景
+
+通常,在管理大规模数据集的时候都需要进行分区,比如将日志文件按天进行分区,从而保证数据细粒度的划分,使得查询性能得到提升。
+
+### 1.3 创建分区表
+
+在 Hive 中可以使用 `PARTITIONED BY` 子句创建分区表。表可以包含一个或多个分区列,程序会为分区列中的每个不同值组合创建单独的数据目录。下面的我们创建一张雇员表作为测试:
+
+```shell
+ CREATE EXTERNAL TABLE emp_partition(
+ empno INT,
+ ename STRING,
+ job STRING,
+ mgr INT,
+ hiredate TIMESTAMP,
+ sal DECIMAL(7,2),
+ comm DECIMAL(7,2)
+ )
+ PARTITIONED BY (deptno INT) -- 按照部门编号进行分区
+ ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t"
+ LOCATION '/hive/emp_partition';
+```
+
+### 1.4 加载数据到分区表
+
+加载数据到分区表时候必须要指定数据所处的分区:
+
+```shell
+# 加载部门编号为20的数据到表中
+LOAD DATA LOCAL INPATH "/usr/file/emp20.txt" OVERWRITE INTO TABLE emp_partition PARTITION (deptno=20)
+# 加载部门编号为30的数据到表中
+LOAD DATA LOCAL INPATH "/usr/file/emp30.txt" OVERWRITE INTO TABLE emp_partition PARTITION (deptno=30)
+```
+
+### 1.5 查看分区目录
+
+这时候我们直接查看表目录,可以看到表目录下存在两个子目录,分别是 `deptno=20` 和 `deptno=30`,这就是分区目录,分区目录下才是我们加载的数据文件。
+
+```shell
+# hadoop fs -ls hdfs://hadoop001:8020/hive/emp_partition/
+```
+
+这时候当你的查询语句的 `where` 包含 `deptno=20`,则就去对应的分区目录下进行查找,而不用扫描全表。
+
+
+
+
+
+## 二、分桶表
+
+### 1.1 简介
+
+分区提供了一个隔离数据和优化查询的可行方案,但是并非所有的数据集都可以形成合理的分区,分区的数量也不是越多越好,过多的分区条件可能会导致很多分区上没有数据。同时 Hive 会限制动态分区可以创建的最大分区数,用来避免过多分区文件对文件系统产生负担。鉴于以上原因,Hive 还提供了一种更加细粒度的数据拆分方案:分桶表 (bucket Table)。
+
+分桶表会将指定列的值进行哈希散列,并对 bucket(桶数量)取余,然后存储到对应的 bucket(桶)中。
+
+### 1.2 理解分桶表
+
+单从概念上理解分桶表可能会比较晦涩,其实和分区一样,分桶这个概念同样不是 Hive 独有的,对于 Java 开发人员而言,这可能是一个每天都会用到的概念,因为 Hive 中的分桶概念和 Java 数据结构中的 HashMap 的分桶概念是一致的。
+
+当调用 HashMap 的 put() 方法存储数据时,程序会先对 key 值调用 hashCode() 方法计算出 hashcode,然后对数组长度取模计算出 index,最后将数据存储在数组 index 位置的链表上,链表达到一定阈值后会转换为红黑树 (JDK1.8+)。下图为 HashMap 的数据结构图:
+
+
+
+> 图片引用自:[HashMap vs. Hashtable](http://www.itcuties.com/java/hashmap-hashtable/)
+
+### 1.3 创建分桶表
+
+在 Hive 中,我们可以通过 `CLUSTERED BY` 指定分桶列,并通过 `SORTED BY` 指定桶中数据的排序参考列。下面为分桶表建表语句示例:
+
+```sql
+ CREATE EXTERNAL TABLE emp_bucket(
+ empno INT,
+ ename STRING,
+ job STRING,
+ mgr INT,
+ hiredate TIMESTAMP,
+ sal DECIMAL(7,2),
+ comm DECIMAL(7,2),
+ deptno INT)
+ CLUSTERED BY(empno) SORTED BY(empno ASC) INTO 4 BUCKETS --按照员工编号散列到四个 bucket 中
+ ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t"
+ LOCATION '/hive/emp_bucket';
+```
+
+### 1.4 加载数据到分桶表
+
+这里直接使用 `Load` 语句向分桶表加载数据,数据时可以加载成功的,但是数据并不会分桶。
+
+这是由于分桶的实质是对指定字段做了 hash 散列然后存放到对应文件中,这意味着向分桶表中插入数据是必然要通过 MapReduce,且 Reducer 的数量必须等于分桶的数量。由于以上原因,分桶表的数据通常只能使用 CTAS(CREATE TABLE AS SELECT) 方式插入,因为 CTAS 操作会触发 MapReduce。加载数据步骤如下:
+
+#### 1. 设置强制分桶
+
+```sql
+set hive.enforce.bucketing = true; --Hive 2.x 不需要这一步
+```
+在 Hive 0.x and 1.x 版本,必须使用设置 `hive.enforce.bucketing = true`,表示强制分桶,允许程序根据表结构自动选择正确数量的 Reducer 和 cluster by column 来进行分桶。
+
+#### 2. CTAS导入数据
+
+```sql
+INSERT INTO TABLE emp_bucket SELECT * FROM emp; --这里的 emp 表就是一张普通的雇员表
+```
+
+可以从执行日志看到 CTAS 触发 MapReduce 操作,且 Reducer 数量和建表时候指定 bucket 数量一致:
+
+
+
+### 1.5 查看分桶文件
+
+bucket(桶) 本质上就是表目录下的具体文件:
+
+
+
+
+
+## 三、分区表和分桶表结合使用
+
+分区表和分桶表的本质都是将数据按照不同粒度进行拆分,从而使得在查询时候不必扫描全表,只需要扫描对应的分区或分桶,从而提升查询效率。两者可以结合起来使用,从而保证表数据在不同粒度上都能得到合理的拆分。下面是 Hive 官方给出的示例:
+
+```sql
+CREATE TABLE page_view_bucketed(
+ viewTime INT,
+ userid BIGINT,
+ page_url STRING,
+ referrer_url STRING,
+ ip STRING )
+ PARTITIONED BY(dt STRING)
+ CLUSTERED BY(userid) SORTED BY(viewTime) INTO 32 BUCKETS
+ ROW FORMAT DELIMITED
+ FIELDS TERMINATED BY '\001'
+ COLLECTION ITEMS TERMINATED BY '\002'
+ MAP KEYS TERMINATED BY '\003'
+ STORED AS SEQUENCEFILE;
+```
+
+此时导入数据时需要指定分区:
+
+```shell
+INSERT OVERWRITE page_view_bucketed
+PARTITION (dt='2009-02-25')
+SELECT * FROM page_view WHERE dt='2009-02-25';
+```
+
+
+
+## 参考资料
+
+1. [LanguageManual DDL BucketedTables](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL+BucketedTables)
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hive\345\270\270\347\224\250DDL\346\223\215\344\275\234.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hive\345\270\270\347\224\250DDL\346\223\215\344\275\234.md"
new file mode 100644
index 0000000..cc72196
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hive\345\270\270\347\224\250DDL\346\223\215\344\275\234.md"
@@ -0,0 +1,450 @@
+# Hive常用DDL操作
+
+
+一、Database
+ 1.1 查看数据列表
+ 1.2 使用数据库
+ 1.3 新建数据库
+ 1.4 查看数据库信息
+ 1.5 删除数据库
+二、创建表
+ 2.1 建表语法
+ 2.2 内部表
+ 2.3 外部表
+ 2.4 分区表
+ 2.5 分桶表
+ 2.6 倾斜表
+ 2.7 临时表
+ 2.8 CTAS创建表
+ 2.9 复制表结构
+ 2.10 加载数据到表
+三、修改表
+ 3.1 重命名表
+ 3.2 修改列
+ 3.3 新增列
+四、清空表/删除表
+ 4.1 清空表
+ 4.2 删除表
+五、其他命令
+ 5.1 Describe
+ 5.2 Show
+
+
+## 一、Database
+
+### 1.1 查看数据列表
+
+```sql
+show databases;
+```
+
+
+
+### 1.2 使用数据库
+
+```sql
+USE database_name;
+```
+
+### 1.3 新建数据库
+
+语法:
+
+```sql
+CREATE (DATABASE|SCHEMA) [IF NOT EXISTS] database_name --DATABASE|SCHEMA 是等价的
+ [COMMENT database_comment] --数据库注释
+ [LOCATION hdfs_path] --存储在 HDFS 上的位置
+ [WITH DBPROPERTIES (property_name=property_value, ...)]; --指定额外属性
+```
+
+示例:
+
+```sql
+CREATE DATABASE IF NOT EXISTS hive_test
+ COMMENT 'hive database for test'
+ WITH DBPROPERTIES ('create'='heibaiying');
+```
+
+
+
+### 1.4 查看数据库信息
+
+语法:
+
+```sql
+DESC DATABASE [EXTENDED] db_name; --EXTENDED 表示是否显示额外属性
+```
+
+示例:
+
+```sql
+DESC DATABASE EXTENDED hive_test;
+```
+
+
+
+### 1.5 删除数据库
+
+语法:
+
+```sql
+DROP (DATABASE|SCHEMA) [IF EXISTS] database_name [RESTRICT|CASCADE];
+```
+
++ 默认行为是 RESTRICT,如果数据库中存在表则删除失败。要想删除库及其中的表,可以使用 CASCADE 级联删除。
+
+示例:
+
+```sql
+ DROP DATABASE IF EXISTS hive_test CASCADE;
+```
+
+
+
+## 二、创建表
+
+### 2.1 建表语法
+
+```sql
+CREATE [TEMPORARY] [EXTERNAL] TABLE [IF NOT EXISTS] [db_name.]table_name --表名
+ [(col_name data_type [COMMENT col_comment],
+ ... [constraint_specification])] --列名 列数据类型
+ [COMMENT table_comment] --表描述
+ [PARTITIONED BY (col_name data_type [COMMENT col_comment], ...)] --分区表分区规则
+ [
+ CLUSTERED BY (col_name, col_name, ...)
+ [SORTED BY (col_name [ASC|DESC], ...)] INTO num_buckets BUCKETS
+ ] --分桶表分桶规则
+ [SKEWED BY (col_name, col_name, ...) ON ((col_value, col_value, ...), (col_value, col_value, ...), ...)
+ [STORED AS DIRECTORIES]
+ ] --指定倾斜列和值
+ [
+ [ROW FORMAT row_format]
+ [STORED AS file_format]
+ | STORED BY 'storage.handler.class.name' [WITH SERDEPROPERTIES (...)]
+ ] -- 指定行分隔符、存储文件格式或采用自定义存储格式
+ [LOCATION hdfs_path] -- 指定表的存储位置
+ [TBLPROPERTIES (property_name=property_value, ...)] --指定表的属性
+ [AS select_statement]; --从查询结果创建表
+```
+
+### 2.2 内部表
+
+```sql
+ CREATE TABLE emp(
+ empno INT,
+ ename STRING,
+ job STRING,
+ mgr INT,
+ hiredate TIMESTAMP,
+ sal DECIMAL(7,2),
+ comm DECIMAL(7,2),
+ deptno INT)
+ ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t";
+```
+
+### 2.3 外部表
+
+```sql
+ CREATE EXTERNAL TABLE emp_external(
+ empno INT,
+ ename STRING,
+ job STRING,
+ mgr INT,
+ hiredate TIMESTAMP,
+ sal DECIMAL(7,2),
+ comm DECIMAL(7,2),
+ deptno INT)
+ ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t"
+ LOCATION '/hive/emp_external';
+```
+
+使用 `desc format emp_external` 命令可以查看表的详细信息如下:
+
+
+
+### 2.4 分区表
+
+```sql
+ CREATE EXTERNAL TABLE emp_partition(
+ empno INT,
+ ename STRING,
+ job STRING,
+ mgr INT,
+ hiredate TIMESTAMP,
+ sal DECIMAL(7,2),
+ comm DECIMAL(7,2)
+ )
+ PARTITIONED BY (deptno INT) -- 按照部门编号进行分区
+ ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t"
+ LOCATION '/hive/emp_partition';
+```
+
+### 2.5 分桶表
+
+```sql
+ CREATE EXTERNAL TABLE emp_bucket(
+ empno INT,
+ ename STRING,
+ job STRING,
+ mgr INT,
+ hiredate TIMESTAMP,
+ sal DECIMAL(7,2),
+ comm DECIMAL(7,2),
+ deptno INT)
+ CLUSTERED BY(empno) SORTED BY(empno ASC) INTO 4 BUCKETS --按照员工编号散列到四个 bucket 中
+ ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t"
+ LOCATION '/hive/emp_bucket';
+```
+
+### 2.6 倾斜表
+
+通过指定一个或者多个列经常出现的值(严重偏斜),Hive 会自动将涉及到这些值的数据拆分为单独的文件。在查询时,如果涉及到倾斜值,它就直接从独立文件中获取数据,而不是扫描所有文件,这使得性能得到提升。
+
+```sql
+ CREATE EXTERNAL TABLE emp_skewed(
+ empno INT,
+ ename STRING,
+ job STRING,
+ mgr INT,
+ hiredate TIMESTAMP,
+ sal DECIMAL(7,2),
+ comm DECIMAL(7,2)
+ )
+ SKEWED BY (empno) ON (66,88,100) --指定 empno 的倾斜值 66,88,100
+ ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t"
+ LOCATION '/hive/emp_skewed';
+```
+
+### 2.7 临时表
+
+临时表仅对当前 session 可见,临时表的数据将存储在用户的暂存目录中,并在会话结束后删除。如果临时表与永久表表名相同,则对该表名的任何引用都将解析为临时表,而不是永久表。临时表还具有以下两个限制:
+
++ 不支持分区列;
++ 不支持创建索引。
+
+```sql
+ CREATE TEMPORARY TABLE emp_temp(
+ empno INT,
+ ename STRING,
+ job STRING,
+ mgr INT,
+ hiredate TIMESTAMP,
+ sal DECIMAL(7,2),
+ comm DECIMAL(7,2)
+ )
+ ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t";
+```
+
+### 2.8 CTAS创建表
+
+支持从查询语句的结果创建表:
+
+```sql
+CREATE TABLE emp_copy AS SELECT * FROM emp WHERE deptno='20';
+```
+
+### 2.9 复制表结构
+
+语法:
+
+```sql
+CREATE [TEMPORARY] [EXTERNAL] TABLE [IF NOT EXISTS] [db_name.]table_name --创建表表名
+ LIKE existing_table_or_view_name --被复制表的表名
+ [LOCATION hdfs_path]; --存储位置
+```
+
+示例:
+
+```sql
+CREATE TEMPORARY EXTERNAL TABLE IF NOT EXISTS emp_co LIKE emp
+```
+
+
+
+### 2.10 加载数据到表
+
+加载数据到表中属于 DML 操作,这里为了方便大家测试,先简单介绍一下加载本地数据到表中:
+
+```sql
+-- 加载数据到 emp 表中
+load data local inpath "/usr/file/emp.txt" into table emp;
+```
+
+其中 emp.txt 的内容如下,你可以直接复制使用,也可以到本仓库的[resources](https://github.com/heibaiying/BigData-Notes/tree/master/resources) 目录下载:
+
+```txt
+7369 SMITH CLERK 7902 1980-12-17 00:00:00 800.00 20
+7499 ALLEN SALESMAN 7698 1981-02-20 00:00:00 1600.00 300.00 30
+7521 WARD SALESMAN 7698 1981-02-22 00:00:00 1250.00 500.00 30
+7566 JONES MANAGER 7839 1981-04-02 00:00:00 2975.00 20
+7654 MARTIN SALESMAN 7698 1981-09-28 00:00:00 1250.00 1400.00 30
+7698 BLAKE MANAGER 7839 1981-05-01 00:00:00 2850.00 30
+7782 CLARK MANAGER 7839 1981-06-09 00:00:00 2450.00 10
+7788 SCOTT ANALYST 7566 1987-04-19 00:00:00 1500.00 20
+7839 KING PRESIDENT 1981-11-17 00:00:00 5000.00 10
+7844 TURNER SALESMAN 7698 1981-09-08 00:00:00 1500.00 0.00 30
+7876 ADAMS CLERK 7788 1987-05-23 00:00:00 1100.00 20
+7900 JAMES CLERK 7698 1981-12-03 00:00:00 950.00 30
+7902 FORD ANALYST 7566 1981-12-03 00:00:00 3000.00 20
+7934 MILLER CLERK 7782 1982-01-23 00:00:00 1300.00 10
+```
+
+加载后可查询表中数据:
+
+
+
+
+
+## 三、修改表
+
+### 3.1 重命名表
+
+语法:
+
+```sql
+ALTER TABLE table_name RENAME TO new_table_name;
+```
+
+示例:
+
+```sql
+ALTER TABLE emp_temp RENAME TO new_emp; --把 emp_temp 表重命名为 new_emp
+```
+
+
+
+### 3.2 修改列
+
+语法:
+
+```sql
+ALTER TABLE table_name [PARTITION partition_spec] CHANGE [COLUMN] col_old_name col_new_name column_type
+ [COMMENT col_comment] [FIRST|AFTER column_name] [CASCADE|RESTRICT];
+```
+
+示例:
+
+```sql
+-- 修改字段名和类型
+ALTER TABLE emp_temp CHANGE empno empno_new INT;
+
+-- 修改字段 sal 的名称 并将其放置到 empno 字段后
+ALTER TABLE emp_temp CHANGE sal sal_new decimal(7,2) AFTER ename;
+
+-- 为字段增加注释
+ALTER TABLE emp_temp CHANGE mgr mgr_new INT COMMENT 'this is column mgr';
+```
+
+
+
+### 3.3 新增列
+
+示例:
+
+```sql
+ALTER TABLE emp_temp ADD COLUMNS (address STRING COMMENT 'home address');
+```
+
+
+
+## 四、清空表/删除表
+
+### 4.1 清空表
+
+语法:
+
+```sql
+-- 清空整个表或表指定分区中的数据
+TRUNCATE TABLE table_name [PARTITION (partition_column = partition_col_value, ...)];
+```
+
++ 目前只有内部表才能执行 TRUNCATE 操作,外部表执行时会抛出异常 `Cannot truncate non-managed table XXXX`。
+
+示例:
+
+```sql
+TRUNCATE TABLE emp_mgt_ptn PARTITION (deptno=20);
+```
+
+
+
+### 4.2 删除表
+
+语法:
+
+```sql
+DROP TABLE [IF EXISTS] table_name [PURGE];
+```
+
++ 内部表:不仅会删除表的元数据,同时会删除 HDFS 上的数据;
++ 外部表:只会删除表的元数据,不会删除 HDFS 上的数据;
++ 删除视图引用的表时,不会给出警告(但视图已经无效了,必须由用户删除或重新创建)。
+
+
+
+## 五、其他命令
+
+### 5.1 Describe
+
+查看数据库:
+
+```sql
+DESCRIBE|Desc DATABASE [EXTENDED] db_name; --EXTENDED 是否显示额外属性
+```
+
+查看表:
+
+```sql
+DESCRIBE|Desc [EXTENDED|FORMATTED] table_name --FORMATTED 以友好的展现方式查看表详情
+```
+
+
+
+### 5.2 Show
+
+**1. 查看数据库列表**
+
+```sql
+-- 语法
+SHOW (DATABASES|SCHEMAS) [LIKE 'identifier_with_wildcards'];
+
+-- 示例:
+SHOW DATABASES like 'hive*';
+```
+
+LIKE 子句允许使用正则表达式进行过滤,但是 SHOW 语句当中的 LIKE 子句只支持 `*`(通配符)和 `|`(条件或)两个符号。例如 `employees`,`emp *`,`emp * | * ees`,所有这些都将匹配名为 `employees` 的数据库。
+
+**2. 查看表的列表**
+
+```sql
+-- 语法
+SHOW TABLES [IN database_name] ['identifier_with_wildcards'];
+
+-- 示例
+SHOW TABLES IN default;
+```
+
+**3. 查看视图列表**
+
+```sql
+SHOW VIEWS [IN/FROM database_name] [LIKE 'pattern_with_wildcards']; --仅支持 Hive 2.2.0 +
+```
+
+**4. 查看表的分区列表**
+
+```sql
+SHOW PARTITIONS table_name;
+```
+
+**5. 查看表/视图的创建语句**
+
+```sql
+SHOW CREATE TABLE ([db_name.]table_name|view_name);
+```
+
+
+
+## 参考资料
+
+[LanguageManual DDL](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL)
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hive\345\270\270\347\224\250DML\346\223\215\344\275\234.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hive\345\270\270\347\224\250DML\346\223\215\344\275\234.md"
new file mode 100644
index 0000000..2d67973
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hive\345\270\270\347\224\250DML\346\223\215\344\275\234.md"
@@ -0,0 +1,329 @@
+# Hive 常用DML操作
+
+
+一、加载文件数据到表
+二、查询结果插入到表
+三、使用SQL语句插入值
+四、更新和删除数据
+五、查询结果写出到文件系统
+
+
+
+## 一、加载文件数据到表
+
+### 1.1 语法
+
+```shell
+LOAD DATA [LOCAL] INPATH 'filepath' [OVERWRITE]
+INTO TABLE tablename [PARTITION (partcol1=val1, partcol2=val2 ...)]
+```
+
+- `LOCAL` 关键字代表从本地文件系统加载文件,省略则代表从 HDFS 上加载文件:
++ 从本地文件系统加载文件时, `filepath` 可以是绝对路径也可以是相对路径 (建议使用绝对路径);
+
++ 从 HDFS 加载文件时候,`filepath` 为文件完整的 URL 地址:如 `hdfs://namenode:port/user/hive/project/ data1`
+
+- `filepath` 可以是文件路径 (在这种情况下 Hive 会将文件移动到表中),也可以目录路径 (在这种情况下,Hive 会将该目录中的所有文件移动到表中);
+
+- 如果使用 OVERWRITE 关键字,则将删除目标表(或分区)的内容,使用新的数据填充;不使用此关键字,则数据以追加的方式加入;
+
+- 加载的目标可以是表或分区。如果是分区表,则必须指定加载数据的分区;
+
+- 加载文件的格式必须与建表时使用 ` STORED AS` 指定的存储格式相同。
+
+> 使用建议:
+>
+> **不论是本地路径还是 URL 都建议使用完整的**。虽然可以使用不完整的 URL 地址,此时 Hive 将使用 hadoop 中的 fs.default.name 配置来推断地址,但是为避免不必要的错误,建议使用完整的本地路径或 URL 地址;
+>
+> **加载对象是分区表时建议显示指定分区**。在 Hive 3.0 之后,内部将加载 (LOAD) 重写为 INSERT AS SELECT,此时如果不指定分区,INSERT AS SELECT 将假设最后一组列是分区列,如果该列不是表定义的分区,它将抛出错误。为避免错误,还是建议显示指定分区。
+
+### 1.2 示例
+
+新建分区表:
+
+```sql
+ CREATE TABLE emp_ptn(
+ empno INT,
+ ename STRING,
+ job STRING,
+ mgr INT,
+ hiredate TIMESTAMP,
+ sal DECIMAL(7,2),
+ comm DECIMAL(7,2)
+ )
+ PARTITIONED BY (deptno INT) -- 按照部门编号进行分区
+ ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t";
+```
+
+从 HDFS 上加载数据到分区表:
+
+```sql
+LOAD DATA INPATH "hdfs://hadoop001:8020/mydir/emp.txt" OVERWRITE INTO TABLE emp_ptn PARTITION (deptno=20);
+```
+
+> emp.txt 文件可在本仓库的 resources 目录中下载
+
+加载后表中数据如下,分区列 deptno 全部赋值成 20:
+
+
+
+## 二、查询结果插入到表
+
+### 2.1 语法
+
+```sql
+INSERT OVERWRITE TABLE tablename1 [PARTITION (partcol1=val1, partcol2=val2 ...) [IF NOT EXISTS]]
+select_statement1 FROM from_statement;
+
+INSERT INTO TABLE tablename1 [PARTITION (partcol1=val1, partcol2=val2 ...)]
+select_statement1 FROM from_statement;
+```
+
++ Hive 0.13.0 开始,建表时可以通过使用 TBLPROPERTIES(“immutable”=“true”)来创建不可变表 (immutable table) ,如果不可以变表中存在数据,则 INSERT INTO 失败。(注:INSERT OVERWRITE 的语句不受 `immutable` 属性的影响);
+
++ 可以对表或分区执行插入操作。如果表已分区,则必须通过指定所有分区列的值来指定表的特定分区;
+
++ 从 Hive 1.1.0 开始,TABLE 关键字是可选的;
+
++ 从 Hive 1.2.0 开始 ,可以采用 INSERT INTO tablename(z,x,c1) 指明插入列;
+
++ 可以将 SELECT 语句的查询结果插入多个表(或分区),称为多表插入。语法如下:
+
+ ```sql
+ FROM from_statement
+ INSERT OVERWRITE TABLE tablename1
+ [PARTITION (partcol1=val1, partcol2=val2 ...) [IF NOT EXISTS]] select_statement1
+ [INSERT OVERWRITE TABLE tablename2 [PARTITION ... [IF NOT EXISTS]] select_statement2]
+ [INSERT INTO TABLE tablename2 [PARTITION ...] select_statement2] ...;
+ ```
+
+### 2.2 动态插入分区
+
+```sql
+INSERT OVERWRITE TABLE tablename PARTITION (partcol1[=val1], partcol2[=val2] ...)
+select_statement FROM from_statement;
+
+INSERT INTO TABLE tablename PARTITION (partcol1[=val1], partcol2[=val2] ...)
+select_statement FROM from_statement;
+```
+
+在向分区表插入数据时候,分区列名是必须的,但是列值是可选的。如果给出了分区列值,我们将其称为静态分区,否则它是动态分区。动态分区列必须在 SELECT 语句的列中最后指定,并且与它们在 PARTITION() 子句中出现的顺序相同。
+
+注意:Hive 0.9.0 之前的版本动态分区插入是默认禁用的,而 0.9.0 之后的版本则默认启用。以下是动态分区的相关配置:
+
+| 配置 | 默认值 | 说明 |
+| ------------------------------------------ | -------- | ------------------------------------------------------------ |
+| `hive.exec.dynamic.partition` | `true` | 需要设置为 true 才能启用动态分区插入 |
+| `hive.exec.dynamic.partition.mode` | `strict` | 在严格模式 (strict) 下,用户必须至少指定一个静态分区,以防用户意外覆盖所有分区,在非严格模式下,允许所有分区都是动态的 |
+| `hive.exec.max.dynamic.partitions.pernode` | 100 | 允许在每个 mapper/reducer 节点中创建的最大动态分区数 |
+| `hive.exec.max.dynamic.partitions` | 1000 | 允许总共创建的最大动态分区数 |
+| `hive.exec.max.created.files` | 100000 | 作业中所有 mapper/reducer 创建的 HDFS 文件的最大数量 |
+| `hive.error.on.empty.partition` | `false` | 如果动态分区插入生成空结果,是否抛出异常 |
+
+### 2.3 示例
+
+1. 新建 emp 表,作为查询对象表
+
+```sql
+CREATE TABLE emp(
+ empno INT,
+ ename STRING,
+ job STRING,
+ mgr INT,
+ hiredate TIMESTAMP,
+ sal DECIMAL(7,2),
+ comm DECIMAL(7,2),
+ deptno INT)
+ ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t";
+
+ -- 加载数据到 emp 表中 这里直接从本地加载
+load data local inpath "/usr/file/emp.txt" into table emp;
+```
+ 完成后 `emp` 表中数据如下:
+
+
+2. 为清晰演示,先清空 `emp_ptn` 表中加载的数据:
+
+```sql
+TRUNCATE TABLE emp_ptn;
+```
+
+3. 静态分区演示:从 `emp` 表中查询部门编号为 20 的员工数据,并插入 `emp_ptn` 表中,语句如下:
+
+```sql
+INSERT OVERWRITE TABLE emp_ptn PARTITION (deptno=20)
+SELECT empno,ename,job,mgr,hiredate,sal,comm FROM emp WHERE deptno=20;
+```
+
+ 完成后 `emp_ptn` 表中数据如下:
+
+
+
+4. 接着演示动态分区:
+
+```sql
+-- 由于我们只有一个分区,且还是动态分区,所以需要关闭严格默认。因为在严格模式下,用户必须至少指定一个静态分区
+set hive.exec.dynamic.partition.mode=nonstrict;
+
+-- 动态分区 此时查询语句的最后一列为动态分区列,即 deptno
+INSERT OVERWRITE TABLE emp_ptn PARTITION (deptno)
+SELECT empno,ename,job,mgr,hiredate,sal,comm,deptno FROM emp WHERE deptno=30;
+```
+
+ 完成后 `emp_ptn` 表中数据如下:
+
+
+
+
+
+## 三、使用SQL语句插入值
+
+```sql
+INSERT INTO TABLE tablename [PARTITION (partcol1[=val1], partcol2[=val2] ...)]
+VALUES ( value [, value ...] )
+```
+
++ 使用时必须为表中的每个列都提供值。不支持只向部分列插入值(可以为缺省值的列提供空值来消除这个弊端);
++ 如果目标表表支持 ACID 及其事务管理器,则插入后自动提交;
++ 不支持支持复杂类型 (array, map, struct, union) 的插入。
+
+
+
+## 四、更新和删除数据
+
+### 4.1 语法
+
+更新和删除的语法比较简单,和关系型数据库一致。需要注意的是这两个操作都只能在支持 ACID 的表,也就是事务表上才能执行。
+
+```sql
+-- 更新
+UPDATE tablename SET column = value [, column = value ...] [WHERE expression]
+
+--删除
+DELETE FROM tablename [WHERE expression]
+```
+
+### 4.2 示例
+
+**1. 修改配置**
+
+首先需要更改 `hive-site.xml`,添加如下配置,开启事务支持,配置完成后需要重启 Hive 服务。
+
+```xml
+
+ hive.support.concurrency
+ true
+
+
+ hive.enforce.bucketing
+ true
+
+
+ hive.exec.dynamic.partition.mode
+ nonstrict
+
+
+ hive.txn.manager
+ org.apache.hadoop.hive.ql.lockmgr.DbTxnManager
+
+
+ hive.compactor.initiator.on
+ true
+
+
+ hive.in.test
+ true
+
+```
+
+**2. 创建测试表**
+
+创建用于测试的事务表,建表时候指定属性 `transactional = true` 则代表该表是事务表。需要注意的是,按照[官方文档](https://cwiki.apache.org/confluence/display/Hive/Hive+Transactions) 的说明,目前 Hive 中的事务表有以下限制:
+
++ 必须是 buckets Table;
++ 仅支持 ORC 文件格式;
++ 不支持 LOAD DATA ...语句。
+
+```sql
+CREATE TABLE emp_ts(
+ empno int,
+ ename String
+)
+CLUSTERED BY (empno) INTO 2 BUCKETS STORED AS ORC
+TBLPROPERTIES ("transactional"="true");
+```
+
+**3. 插入测试数据**
+
+```sql
+INSERT INTO TABLE emp_ts VALUES (1,"ming"),(2,"hong");
+```
+
+插入数据依靠的是 MapReduce 作业,执行成功后数据如下:
+
+
+
+**4. 测试更新和删除**
+
+```sql
+--更新数据
+UPDATE emp_ts SET ename = "lan" WHERE empno=1;
+
+--删除数据
+DELETE FROM emp_ts WHERE empno=2;
+```
+
+更新和删除数据依靠的也是 MapReduce 作业,执行成功后数据如下:
+
+
+
+
+## 五、查询结果写出到文件系统
+
+### 5.1 语法
+
+```sql
+INSERT OVERWRITE [LOCAL] DIRECTORY directory1
+ [ROW FORMAT row_format] [STORED AS file_format]
+ SELECT ... FROM ...
+```
+
++ OVERWRITE 关键字表示输出文件存在时,先删除后再重新写入;
+
++ 和 Load 语句一样,建议无论是本地路径还是 URL 地址都使用完整的;
+
++ 写入文件系统的数据被序列化为文本,其中列默认由^A 分隔,行由换行符分隔。如果列不是基本类型,则将其序列化为 JSON 格式。其中行分隔符不允许自定义,但列分隔符可以自定义,如下:
+
+ ```sql
+ -- 定义列分隔符为'\t'
+ insert overwrite local directory './test-04'
+ row format delimited
+ FIELDS TERMINATED BY '\t'
+ COLLECTION ITEMS TERMINATED BY ','
+ MAP KEYS TERMINATED BY ':'
+ select * from src;
+ ```
+
+### 5.2 示例
+
+这里我们将上面创建的 `emp_ptn` 表导出到本地文件系统,语句如下:
+
+```sql
+INSERT OVERWRITE LOCAL DIRECTORY '/usr/file/ouput'
+ROW FORMAT DELIMITED
+FIELDS TERMINATED BY '\t'
+SELECT * FROM emp_ptn;
+```
+
+导出结果如下:
+
+
+
+
+
+
+
+## 参考资料
+
+1. [Hive Transactions](https://cwiki.apache.org/confluence/display/Hive/Hive+Transactions)
+2. [Hive Data Manipulation Language](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DML)
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hive\346\225\260\346\215\256\346\237\245\350\257\242\350\257\246\350\247\243.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hive\346\225\260\346\215\256\346\237\245\350\257\242\350\257\246\350\247\243.md"
new file mode 100644
index 0000000..b8c7750
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hive\346\225\260\346\215\256\346\237\245\350\257\242\350\257\246\350\247\243.md"
@@ -0,0 +1,396 @@
+# Hive数据查询详解
+
+
+一、数据准备
+二、单表查询
+ 2.1 SELECT
+ 2.2 WHERE
+ 2.3 DISTINCT
+ 2.4 分区查询
+ 2.5 LIMIT
+ 2.6 GROUP BY
+ 2.7 ORDER AND SORT
+ 2.8 HAVING
+ 2.9 DISTRIBUTE BY
+ 2.10 CLUSTER BY
+三、多表联结查询
+ 3.1 INNER JOIN
+ 3.2 LEFT OUTER JOIN
+ 3.3 RIGHT OUTER JOIN
+ 3.4 FULL OUTER JOIN
+ 3.5 LEFT SEMI JOIN
+ 3.6 JOIN
+四、JOIN优化
+ 4.1 STREAMTABLE
+ 4.2 MAPJOIN
+五、SELECT的其他用途
+六、本地模式
+
+
+
+
+## 一、数据准备
+
+为了演示查询操作,这里需要预先创建三张表,并加载测试数据。
+
+> 数据文件 emp.txt 和 dept.txt 可以从本仓库的[resources](https://github.com/heibaiying/BigData-Notes/tree/master/resources) 目录下载。
+
+### 1.1 员工表
+
+```sql
+ -- 建表语句
+ CREATE TABLE emp(
+ empno INT, -- 员工表编号
+ ename STRING, -- 员工姓名
+ job STRING, -- 职位类型
+ mgr INT,
+ hiredate TIMESTAMP, --雇佣日期
+ sal DECIMAL(7,2), --工资
+ comm DECIMAL(7,2),
+ deptno INT) --部门编号
+ ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t";
+
+ --加载数据
+LOAD DATA LOCAL INPATH "/usr/file/emp.txt" OVERWRITE INTO TABLE emp;
+```
+
+### 1.2 部门表
+
+```sql
+ -- 建表语句
+ CREATE TABLE dept(
+ deptno INT, --部门编号
+ dname STRING, --部门名称
+ loc STRING --部门所在的城市
+ )
+ ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t";
+
+ --加载数据
+ LOAD DATA LOCAL INPATH "/usr/file/dept.txt" OVERWRITE INTO TABLE dept;
+```
+
+### 1.3 分区表
+
+这里需要额外创建一张分区表,主要是为了演示分区查询:
+
+```sql
+CREATE EXTERNAL TABLE emp_ptn(
+ empno INT,
+ ename STRING,
+ job STRING,
+ mgr INT,
+ hiredate TIMESTAMP,
+ sal DECIMAL(7,2),
+ comm DECIMAL(7,2)
+ )
+ PARTITIONED BY (deptno INT) -- 按照部门编号进行分区
+ ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t";
+
+
+--加载数据
+LOAD DATA LOCAL INPATH "/usr/file/emp.txt" OVERWRITE INTO TABLE emp_ptn PARTITION (deptno=20)
+LOAD DATA LOCAL INPATH "/usr/file/emp.txt" OVERWRITE INTO TABLE emp_ptn PARTITION (deptno=30)
+LOAD DATA LOCAL INPATH "/usr/file/emp.txt" OVERWRITE INTO TABLE emp_ptn PARTITION (deptno=40)
+LOAD DATA LOCAL INPATH "/usr/file/emp.txt" OVERWRITE INTO TABLE emp_ptn PARTITION (deptno=50)
+```
+
+
+
+## 二、单表查询
+
+### 2.1 SELECT
+
+```sql
+-- 查询表中全部数据
+SELECT * FROM emp;
+```
+
+
+
+### 2.2 WHERE
+
+```sql
+-- 查询 10 号部门中员工编号大于 7782 的员工信息
+SELECT * FROM emp WHERE empno > 7782 AND deptno = 10;
+```
+
+
+
+### 2.3 DISTINCT
+
+Hive 支持使用 DISTINCT 关键字去重。
+
+```sql
+-- 查询所有工作类型
+SELECT DISTINCT job FROM emp;
+```
+
+
+
+### 2.4 分区查询
+
+分区查询 (Partition Based Queries),可以指定某个分区或者分区范围。
+
+```sql
+-- 查询分区表中部门编号在[20,40]之间的员工
+SELECT emp_ptn.* FROM emp_ptn
+WHERE emp_ptn.deptno >= 20 AND emp_ptn.deptno <= 40;
+```
+
+
+
+### 2.5 LIMIT
+
+```sql
+-- 查询薪资最高的 5 名员工
+SELECT * FROM emp ORDER BY sal DESC LIMIT 5;
+```
+
+
+
+### 2.6 GROUP BY
+
+Hive 支持使用 GROUP BY 进行分组聚合操作。
+
+```sql
+set hive.map.aggr=true;
+
+-- 查询各个部门薪酬综合
+SELECT deptno,SUM(sal) FROM emp GROUP BY deptno;
+```
+
+`hive.map.aggr` 控制程序如何进行聚合。默认值为 false。如果设置为 true,Hive 会在 map 阶段就执行一次聚合。这可以提高聚合效率,但需要消耗更多内存。
+
+
+
+### 2.7 ORDER AND SORT
+
+可以使用 ORDER BY 或者 Sort BY 对查询结果进行排序,排序字段可以是整型也可以是字符串:如果是整型,则按照大小排序;如果是字符串,则按照字典序排序。ORDER BY 和 SORT BY 的区别如下:
+
++ 使用 ORDER BY 时会有一个 Reducer 对全部查询结果进行排序,可以保证数据的全局有序性;
++ 使用 SORT BY 时只会在每个 Reducer 中进行排序,这可以保证每个 Reducer 的输出数据是有序的,但不能保证全局有序。
+
+由于 ORDER BY 的时间可能很长,如果你设置了严格模式 (hive.mapred.mode = strict),则其后面必须再跟一个 `limit` 子句。
+
+> 注 :hive.mapred.mode 默认值是 nonstrict ,也就是非严格模式。
+
+```sql
+-- 查询员工工资,结果按照部门升序,按照工资降序排列
+SELECT empno, deptno, sal FROM emp ORDER BY deptno ASC, sal DESC;
+```
+
+
+
+### 2.8 HAVING
+
+可以使用 HAVING 对分组数据进行过滤。
+
+```sql
+-- 查询工资总和大于 9000 的所有部门
+SELECT deptno,SUM(sal) FROM emp GROUP BY deptno HAVING SUM(sal)>9000;
+```
+
+
+
+### 2.9 DISTRIBUTE BY
+
+默认情况下,MapReduce 程序会对 Map 输出结果的 Key 值进行散列,并均匀分发到所有 Reducer 上。如果想要把具有相同 Key 值的数据分发到同一个 Reducer 进行处理,这就需要使用 DISTRIBUTE BY 字句。
+
+需要注意的是,DISTRIBUTE BY 虽然能保证具有相同 Key 值的数据分发到同一个 Reducer,但是不能保证数据在 Reducer 上是有序的。情况如下:
+
+把以下 5 个数据发送到两个 Reducer 上进行处理:
+
+```properties
+k1
+k2
+k4
+k3
+k1
+```
+
+Reducer1 得到如下乱序数据:
+
+```properties
+k1
+k2
+k1
+```
+
+
+Reducer2 得到数据如下:
+
+```properties
+k4
+k3
+```
+
+如果想让 Reducer 上的数据时有序的,可以结合 `SORT BY` 使用 (示例如下),或者使用下面我们将要介绍的 CLUSTER BY。
+
+```sql
+-- 将数据按照部门分发到对应的 Reducer 上处理
+SELECT empno, deptno, sal FROM emp DISTRIBUTE BY deptno SORT BY deptno ASC;
+```
+
+
+
+### 2.10 CLUSTER BY
+
+如果 `SORT BY` 和 `DISTRIBUTE BY` 指定的是相同字段,且 SORT BY 排序规则是 ASC,此时可以使用 `CLUSTER BY` 进行替换,同时 `CLUSTER BY` 可以保证数据在全局是有序的。
+
+```sql
+SELECT empno, deptno, sal FROM emp CLUSTER BY deptno ;
+```
+
+
+
+## 三、多表联结查询
+
+Hive 支持内连接,外连接,左外连接,右外连接,笛卡尔连接,这和传统数据库中的概念是一致的,可以参见下图。
+
+需要特别强调:JOIN 语句的关联条件必须用 ON 指定,不能用 WHERE 指定,否则就会先做笛卡尔积,再过滤,这会导致你得不到预期的结果 (下面的演示会有说明)。
+
+
+
+### 3.1 INNER JOIN
+
+```sql
+-- 查询员工编号为 7369 的员工的详细信息
+SELECT e.*,d.* FROM
+emp e JOIN dept d
+ON e.deptno = d.deptno
+WHERE empno=7369;
+
+--如果是三表或者更多表连接,语法如下
+SELECT a.val, b.val, c.val FROM a JOIN b ON (a.key = b.key1) JOIN c ON (c.key = b.key1)
+```
+
+### 3.2 LEFT OUTER JOIN
+
+LEFT OUTER JOIN 和 LEFT JOIN 是等价的。
+
+```sql
+-- 左连接
+SELECT e.*,d.*
+FROM emp e LEFT OUTER JOIN dept d
+ON e.deptno = d.deptno;
+```
+
+### 3.3 RIGHT OUTER JOIN
+
+```sql
+--右连接
+SELECT e.*,d.*
+FROM emp e RIGHT OUTER JOIN dept d
+ON e.deptno = d.deptno;
+```
+
+执行右连接后,由于 40 号部门下没有任何员工,所以此时员工信息为 NULL。这个查询可以很好的复述上面提到的——JOIN 语句的关联条件必须用 ON 指定,不能用 WHERE 指定。你可以把 ON 改成 WHERE,你会发现无论如何都查不出 40 号部门这条数据,因为笛卡尔运算不会有 (NULL, 40) 这种情况。
+
+
+### 3.4 FULL OUTER JOIN
+
+```sql
+SELECT e.*,d.*
+FROM emp e FULL OUTER JOIN dept d
+ON e.deptno = d.deptno;
+```
+
+### 3.5 LEFT SEMI JOIN
+
+LEFT SEMI JOIN (左半连接)是 IN/EXISTS 子查询的一种更高效的实现。
+
++ JOIN 子句中右边的表只能在 ON 子句中设置过滤条件;
++ 查询结果只包含左边表的数据,所以只能 SELECT 左表中的列。
+
+```sql
+-- 查询在纽约办公的所有员工信息
+SELECT emp.*
+FROM emp LEFT SEMI JOIN dept
+ON emp.deptno = dept.deptno AND dept.loc="NEW YORK";
+
+--上面的语句就等价于
+SELECT emp.* FROM emp
+WHERE emp.deptno IN (SELECT deptno FROM dept WHERE loc="NEW YORK");
+```
+
+### 3.6 JOIN
+
+笛卡尔积连接,这个连接日常的开发中可能很少遇到,且性能消耗比较大,基于这个原因,如果在严格模式下 (hive.mapred.mode = strict),Hive 会阻止用户执行此操作。
+
+```sql
+SELECT * FROM emp JOIN dept;
+```
+
+
+
+## 四、JOIN优化
+
+### 4.1 STREAMTABLE
+
+在多表进行联结的时候,如果每个 ON 字句都使用到共同的列(如下面的 `b.key`),此时 Hive 会进行优化,将多表 JOIN 在同一个 map / reduce 作业上进行。同时假定查询的最后一个表(如下面的 c 表)是最大的一个表,在对每行记录进行 JOIN 操作时,它将尝试将其他的表缓存起来,然后扫描最后那个表进行计算。因此用户需要保证查询的表的大小从左到右是依次增加的。
+
+```sql
+`SELECT a.val, b.val, c.val FROM a JOIN b ON (a.key = b.key) JOIN c ON (c.key = b.key)`
+```
+
+然后,用户并非需要总是把最大的表放在查询语句的最后面,Hive 提供了 `/*+ STREAMTABLE() */` 标志,用于标识最大的表,示例如下:
+
+```sql
+SELECT /*+ STREAMTABLE(d) */ e.*,d.*
+FROM emp e JOIN dept d
+ON e.deptno = d.deptno
+WHERE job='CLERK';
+```
+
+
+
+### 4.2 MAPJOIN
+
+如果所有表中只有一张表是小表,那么 Hive 把这张小表加载到内存中。这时候程序会在 map 阶段直接拿另外一个表的数据和内存中表数据做匹配,由于在 map 就进行了 JOIN 操作,从而可以省略 reduce 过程,这样效率可以提升很多。Hive 中提供了 `/*+ MAPJOIN() */` 来标记小表,示例如下:
+
+```sql
+SELECT /*+ MAPJOIN(d) */ e.*,d.*
+FROM emp e JOIN dept d
+ON e.deptno = d.deptno
+WHERE job='CLERK';
+```
+
+
+
+## 五、SELECT的其他用途
+
+查看当前数据库:
+
+```sql
+SELECT current_database()
+```
+
+
+
+## 六、本地模式
+
+在上面演示的语句中,大多数都会触发 MapReduce, 少部分不会触发,比如 `select * from emp limit 5` 就不会触发 MR,此时 Hive 只是简单的读取数据文件中的内容,然后格式化后进行输出。在需要执行 MapReduce 的查询中,你会发现执行时间可能会很长,这时候你可以选择开启本地模式。
+
+```sql
+--本地模式默认关闭,需要手动开启此功能
+SET hive.exec.mode.local.auto=true;
+```
+
+启用后,Hive 将分析查询中每个 map-reduce 作业的大小,如果满足以下条件,则可以在本地运行它:
+
+- 作业的总输入大小低于:hive.exec.mode.local.auto.inputbytes.max(默认为 128MB);
+- map-tasks 的总数小于:hive.exec.mode.local.auto.tasks.max(默认为 4);
+- 所需的 reduce 任务总数为 1 或 0。
+
+因为我们测试的数据集很小,所以你再次去执行上面涉及 MR 操作的查询,你会发现速度会有显著的提升。
+
+
+
+
+
+## 参考资料
+
+1. [LanguageManual Select](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Select)
+2. [LanguageManual Joins](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Joins)
+3. [LanguageManual GroupBy](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+GroupBy)
+4. [LanguageManual SortBy](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+SortBy)
\ No newline at end of file
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hive\347\256\200\344\273\213\345\217\212\346\240\270\345\277\203\346\246\202\345\277\265.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hive\347\256\200\344\273\213\345\217\212\346\240\270\345\277\203\346\246\202\345\277\265.md"
new file mode 100644
index 0000000..63c49f4
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hive\347\256\200\344\273\213\345\217\212\346\240\270\345\277\203\346\246\202\345\277\265.md"
@@ -0,0 +1,202 @@
+# Hive简介及核心概念
+
+
+一、简介
+二、Hive的体系架构
+三、数据类型
+ 3.1 基本数据类型
+ 3.2 隐式转换
+ 3.3 复杂类型
+四、内容格式
+五、存储格式
+六、内部表和外部表
+
+
+
+## 一、简介
+
+Hive 是一个构建在 Hadoop 之上的数据仓库,它可以将结构化的数据文件映射成表,并提供类 SQL 查询功能,用于查询的 SQL 语句会被转化为 MapReduce 作业,然后提交到 Hadoop 上运行。
+
+**特点**:
+
+1. 简单、容易上手 (提供了类似 sql 的查询语言 hql),使得精通 sql 但是不了解 Java 编程的人也能很好地进行大数据分析;
+3. 灵活性高,可以自定义用户函数 (UDF) 和存储格式;
+4. 为超大的数据集设计的计算和存储能力,集群扩展容易;
+5. 统一的元数据管理,可与 presto/impala/sparksql 等共享数据;
+5. 执行延迟高,不适合做数据的实时处理,但适合做海量数据的离线处理。
+
+
+
+## 二、Hive的体系架构
+
+
+
+### 2.1 command-line shell & thrift/jdbc
+
+可以用 command-line shell 和 thrift/jdbc 两种方式来操作数据:
+
++ **command-line shell**:通过 hive 命令行的的方式来操作数据;
++ **thrift/jdbc**:通过 thrift 协议按照标准的 JDBC 的方式操作数据。
+
+### 2.2 Metastore
+
+在 Hive 中,表名、表结构、字段名、字段类型、表的分隔符等统一被称为元数据。所有的元数据默认存储在 Hive 内置的 derby 数据库中,但由于 derby 只能有一个实例,也就是说不能有多个命令行客户端同时访问,所以在实际生产环境中,通常使用 MySQL 代替 derby。
+
+Hive 进行的是统一的元数据管理,就是说你在 Hive 上创建了一张表,然后在 presto/impala/sparksql 中都是可以直接使用的,它们会从 Metastore 中获取统一的元数据信息,同样的你在 presto/impala/sparksql 中创建一张表,在 Hive 中也可以直接使用。
+
+### 2.3 HQL的执行流程
+
+Hive 在执行一条 HQL 的时候,会经过以下步骤:
+
+1. 语法解析:Antlr 定义 SQL 的语法规则,完成 SQL 词法,语法解析,将 SQL 转化为抽象 语法树 AST Tree;
+2. 语义解析:遍历 AST Tree,抽象出查询的基本组成单元 QueryBlock;
+3. 生成逻辑执行计划:遍历 QueryBlock,翻译为执行操作树 OperatorTree;
+4. 优化逻辑执行计划:逻辑层优化器进行 OperatorTree 变换,合并不必要的 ReduceSinkOperator,减少 shuffle 数据量;
+5. 生成物理执行计划:遍历 OperatorTree,翻译为 MapReduce 任务;
+6. 优化物理执行计划:物理层优化器进行 MapReduce 任务的变换,生成最终的执行计划。
+
+> 关于 Hive SQL 的详细执行流程可以参考美团技术团队的文章:[Hive SQL 的编译过程](https://tech.meituan.com/2014/02/12/hive-sql-to-mapreduce.html)
+
+
+
+## 三、数据类型
+
+### 3.1 基本数据类型
+
+Hive 表中的列支持以下基本数据类型:
+
+| 大类 | 类型 |
+| --------------------------------------- | ------------------------------------------------------------ |
+| **Integers(整型)** | TINYINT—1 字节的有符号整数 SMALLINT—2 字节的有符号整数 INT—4 字节的有符号整数 BIGINT—8 字节的有符号整数 |
+| **Boolean(布尔型)** | BOOLEAN—TRUE/FALSE |
+| **Floating point numbers(浮点型)** | FLOAT— 单精度浮点型 DOUBLE—双精度浮点型 |
+| **Fixed point numbers(定点数)** | DECIMAL—用户自定义精度定点数,比如 DECIMAL(7,2) |
+| **String types(字符串)** | STRING—指定字符集的字符序列 VARCHAR—具有最大长度限制的字符序列 CHAR—固定长度的字符序列 |
+| **Date and time types(日期时间类型)** | TIMESTAMP — 时间戳 TIMESTAMP WITH LOCAL TIME ZONE — 时间戳,纳秒精度 DATE—日期类型 |
+| **Binary types(二进制类型)** | BINARY—字节序列 |
+
+> TIMESTAMP 和 TIMESTAMP WITH LOCAL TIME ZONE 的区别如下:
+>
+> - **TIMESTAMP WITH LOCAL TIME ZONE**:用户提交时间给数据库时,会被转换成数据库所在的时区来保存。查询时则按照查询客户端的不同,转换为查询客户端所在时区的时间。
+> - **TIMESTAMP** :提交什么时间就保存什么时间,查询时也不做任何转换。
+
+### 3.2 隐式转换
+
+Hive 中基本数据类型遵循以下的层次结构,按照这个层次结构,子类型到祖先类型允许隐式转换。例如 INT 类型的数据允许隐式转换为 BIGINT 类型。额外注意的是:按照类型层次结构允许将 STRING 类型隐式转换为 DOUBLE 类型。
+
+
+
+
+
+### 3.3 复杂类型
+
+| 类型 | 描述 | 示例 |
+| ---------- | ------------------------------------------------------------ | -------------------------------------- |
+| **STRUCT** | 类似于对象,是字段的集合,字段的类型可以不同,可以使用 ` 名称.字段名 ` 方式进行访问 | STRUCT ('xiaoming', 12 , '2018-12-12') |
+| **MAP** | 键值对的集合,可以使用 ` 名称[key]` 的方式访问对应的值 | map('a', 1, 'b', 2) |
+| **ARRAY** | 数组是一组具有相同类型和名称的变量的集合,可以使用 ` 名称[index]` 访问对应的值 | ARRAY('a', 'b', 'c', 'd') |
+
+
+
+### 3.4 示例
+
+如下给出一个基本数据类型和复杂数据类型的使用示例:
+
+```sql
+CREATE TABLE students(
+ name STRING, -- 姓名
+ age INT, -- 年龄
+ subject ARRAY, --学科
+ score MAP, --各个学科考试成绩
+ address STRUCT --家庭居住地址
+) ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t";
+```
+
+
+
+## 四、内容格式
+
+当数据存储在文本文件中,必须按照一定格式区别行和列,如使用逗号作为分隔符的 CSV 文件 (Comma-Separated Values) 或者使用制表符作为分隔值的 TSV 文件 (Tab-Separated Values)。但此时也存在一个缺点,就是正常的文件内容中也可能出现逗号或者制表符。
+
+所以 Hive 默认使用了几个平时很少出现的字符,这些字符一般不会作为内容出现在文件中。Hive 默认的行和列分隔符如下表所示。
+
+| 分隔符 | 描述 |
+| --------------- | ------------------------------------------------------------ |
+| **\n** | 对于文本文件来说,每行是一条记录,所以可以使用换行符来分割记录 |
+| **^A (Ctrl+A)** | 分割字段 (列),在 CREATE TABLE 语句中也可以使用八进制编码 `\001` 来表示 |
+| **^B** | 用于分割 ARRAY 或者 STRUCT 中的元素,或者用于 MAP 中键值对之间的分割, 在 CREATE TABLE 语句中也可以使用八进制编码 `\002` 表示 |
+| **^C** | 用于 MAP 中键和值之间的分割,在 CREATE TABLE 语句中也可以使用八进制编码 `\003` 表示 |
+
+使用示例如下:
+
+```sql
+CREATE TABLE page_view(viewTime INT, userid BIGINT)
+ ROW FORMAT DELIMITED
+ FIELDS TERMINATED BY '\001'
+ COLLECTION ITEMS TERMINATED BY '\002'
+ MAP KEYS TERMINATED BY '\003'
+ STORED AS SEQUENCEFILE;
+```
+
+
+
+## 五、存储格式
+
+### 5.1 支持的存储格式
+
+Hive 会在 HDFS 为每个数据库上创建一个目录,数据库中的表是该目录的子目录,表中的数据会以文件的形式存储在对应的表目录下。Hive 支持以下几种文件存储格式:
+
+| 格式 | 说明 |
+| ---------------- | ------------------------------------------------------------ |
+| **TextFile** | 存储为纯文本文件。 这是 Hive 默认的文件存储格式。这种存储方式数据不做压缩,磁盘开销大,数据解析开销大。 |
+| **SequenceFile** | SequenceFile 是 Hadoop API 提供的一种二进制文件,它将数据以的形式序列化到文件中。这种二进制文件内部使用 Hadoop 的标准的 Writable 接口实现序列化和反序列化。它与 Hadoop API 中的 MapFile 是互相兼容的。Hive 中的 SequenceFile 继承自 Hadoop API 的 SequenceFile,不过它的 key 为空,使用 value 存放实际的值,这样是为了避免 MR 在运行 map 阶段进行额外的排序操作。 |
+| **RCFile** | RCFile 文件格式是 FaceBook 开源的一种 Hive 的文件存储格式,首先将表分为几个行组,对每个行组内的数据按列存储,每一列的数据都是分开存储。 |
+| **ORC Files** | ORC 是在一定程度上扩展了 RCFile,是对 RCFile 的优化。 |
+| **Avro Files** | Avro 是一个数据序列化系统,设计用于支持大批量数据交换的应用。它的主要特点有:支持二进制序列化方式,可以便捷,快速地处理大量数据;动态语言友好,Avro 提供的机制使动态语言可以方便地处理 Avro 数据。 |
+| **Parquet** | Parquet 是基于 Dremel 的数据模型和算法实现的,面向分析型业务的列式存储格式。它通过按列进行高效压缩和特殊的编码技术,从而在降低存储空间的同时提高了 IO 效率。 |
+
+> 以上压缩格式中 ORC 和 Parquet 的综合性能突出,使用较为广泛,推荐使用这两种格式。
+
+### 5.2 指定存储格式
+
+通常在创建表的时候使用 `STORED AS` 参数指定:
+
+```sql
+CREATE TABLE page_view(viewTime INT, userid BIGINT)
+ ROW FORMAT DELIMITED
+ FIELDS TERMINATED BY '\001'
+ COLLECTION ITEMS TERMINATED BY '\002'
+ MAP KEYS TERMINATED BY '\003'
+ STORED AS SEQUENCEFILE;
+```
+
+各个存储文件类型指定方式如下:
+
+- STORED AS TEXTFILE
+- STORED AS SEQUENCEFILE
+- STORED AS ORC
+- STORED AS PARQUET
+- STORED AS AVRO
+- STORED AS RCFILE
+
+
+
+## 六、内部表和外部表
+
+内部表又叫做管理表 (Managed/Internal Table),创建表时不做任何指定,默认创建的就是内部表。想要创建外部表 (External Table),则需要使用 External 进行修饰。 内部表和外部表主要区别如下:
+
+| | 内部表 | 外部表 |
+| ------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
+| 数据存储位置 | 内部表数据存储的位置由 hive.metastore.warehouse.dir 参数指定,默认情况下表的数据存储在 HDFS 的 `/user/hive/warehouse/数据库名.db/表名/` 目录下 | 外部表数据的存储位置创建表时由 `Location` 参数指定; |
+| 导入数据 | 在导入数据到内部表,内部表将数据移动到自己的数据仓库目录下,数据的生命周期由 Hive 来进行管理 | 外部表不会将数据移动到自己的数据仓库目录下,只是在元数据中存储了数据的位置 |
+| 删除表 | 删除元数据(metadata)和文件 | 只删除元数据(metadata) |
+
+
+
+## 参考资料
+
+1. [Hive Getting Started](https://cwiki.apache.org/confluence/display/Hive/GettingStarted)
+2. [Hive SQL 的编译过程](https://tech.meituan.com/2014/02/12/hive-sql-to-mapreduce.html)
+3. [LanguageManual DDL](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL)
+4. [LanguageManual Types](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Types)
+5. [Managed vs. External Tables](https://cwiki.apache.org/confluence/display/Hive/Managed+vs.+External+Tables)
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hive\350\247\206\345\233\276\345\222\214\347\264\242\345\274\225.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hive\350\247\206\345\233\276\345\222\214\347\264\242\345\274\225.md"
new file mode 100644
index 0000000..c2fd9a0
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Hive\350\247\206\345\233\276\345\222\214\347\264\242\345\274\225.md"
@@ -0,0 +1,236 @@
+# Hive 视图和索引
+
+
+一、视图
+二、索引
+三、索引案例
+四、索引的缺陷
+
+
+## 一、视图
+
+### 1.1 简介
+
+Hive 中的视图和 RDBMS 中视图的概念一致,都是一组数据的逻辑表示,本质上就是一条 SELECT 语句的结果集。视图是纯粹的逻辑对象,没有关联的存储 (Hive 3.0.0 引入的物化视图除外),当查询引用视图时,Hive 可以将视图的定义与查询结合起来,例如将查询中的过滤器推送到视图中。
+
+### 1.2 创建视图
+
+```sql
+CREATE VIEW [IF NOT EXISTS] [db_name.]view_name -- 视图名称
+ [(column_name [COMMENT column_comment], ...) ] --列名
+ [COMMENT view_comment] --视图注释
+ [TBLPROPERTIES (property_name = property_value, ...)] --额外信息
+ AS SELECT ...;
+```
+
+在 Hive 中可以使用 `CREATE VIEW` 创建视图,如果已存在具有相同名称的表或视图,则会抛出异常,建议使用 `IF NOT EXISTS` 预做判断。在使用视图时候需要注意以下事项:
+
+- 视图是只读的,不能用作 LOAD / INSERT / ALTER 的目标;
+
+- 在创建视图时候视图就已经固定,对基表的后续更改(如添加列)将不会反映在视图;
+
+- 删除基表并不会删除视图,需要手动删除视图;
+
+- 视图可能包含 ORDER BY 和 LIMIT 子句。如果引用视图的查询语句也包含这类子句,其执行优先级低于视图对应字句。例如,视图 `custom_view` 指定 LIMIT 5,查询语句为 `select * from custom_view LIMIT 10`,此时结果最多返回 5 行。
+
+- 创建视图时,如果未提供列名,则将从 SELECT 语句中自动派生列名;
+
+- 创建视图时,如果 SELECT 语句中包含其他表达式,例如 x + y,则列名称将以\_C0,\_C1 等形式生成;
+
+ ```sql
+ CREATE VIEW IF NOT EXISTS custom_view AS SELECT empno, empno+deptno , 1+2 FROM emp;
+ ```
+
+
+
+
+
+### 1.3 查看视图
+
+```sql
+-- 查看所有视图: 没有单独查看视图列表的语句,只能使用 show tables
+show tables;
+-- 查看某个视图
+desc view_name;
+-- 查看某个视图详细信息
+desc formatted view_name;
+```
+
+
+
+### 1.4 删除视图
+
+```sql
+DROP VIEW [IF EXISTS] [db_name.]view_name;
+```
+
+删除视图时,如果被删除的视图被其他视图所引用,这时候程序不会发出警告,但是引用该视图其他视图已经失效,需要进行重建或者删除。
+
+
+
+### 1.5 修改视图
+
+```sql
+ALTER VIEW [db_name.]view_name AS select_statement;
+```
+
+ 被更改的视图必须存在,且视图不能具有分区,如果视图具有分区,则修改失败。
+
+
+
+### 1.6 修改视图属性
+
+语法:
+
+```sql
+ALTER VIEW [db_name.]view_name SET TBLPROPERTIES table_properties;
+
+table_properties:
+ : (property_name = property_value, property_name = property_value, ...)
+```
+
+示例:
+
+```sql
+ALTER VIEW custom_view SET TBLPROPERTIES ('create'='heibaiying','date'='2019-05-05');
+```
+
+
+
+
+
+
+
+## 二、索引
+
+### 2.1 简介
+
+Hive 在 0.7.0 引入了索引的功能,索引的设计目标是提高表某些列的查询速度。如果没有索引,带有谓词的查询(如'WHERE table1.column = 10')会加载整个表或分区并处理所有行。但是如果 column 存在索引,则只需要加载和处理文件的一部分。
+
+### 2.2 索引原理
+
+在指定列上建立索引,会产生一张索引表(表结构如下),里面的字段包括:索引列的值、该值对应的 HDFS 文件路径、该值在文件中的偏移量。在查询涉及到索引字段时,首先到索引表查找索引列值对应的 HDFS 文件路径及偏移量,这样就避免了全表扫描。
+
+```properties
++--------------+----------------+----------+--+
+| col_name | data_type | comment |
++--------------+----------------+----------+--+
+| empno | int | 建立索引的列 |
+| _bucketname | string | HDFS 文件路径 |
+| _offsets | array | 偏移量 |
++--------------+----------------+----------+--+
+```
+
+### 2.3 创建索引
+
+```sql
+CREATE INDEX index_name --索引名称
+ ON TABLE base_table_name (col_name, ...) --建立索引的列
+ AS index_type --索引类型
+ [WITH DEFERRED REBUILD] --重建索引
+ [IDXPROPERTIES (property_name=property_value, ...)] --索引额外属性
+ [IN TABLE index_table_name] --索引表的名字
+ [
+ [ ROW FORMAT ...] STORED AS ...
+ | STORED BY ...
+ ] --索引表行分隔符 、 存储格式
+ [LOCATION hdfs_path] --索引表存储位置
+ [TBLPROPERTIES (...)] --索引表表属性
+ [COMMENT "index comment"]; --索引注释
+```
+
+### 2.4 查看索引
+
+```sql
+--显示表上所有列的索引
+SHOW FORMATTED INDEX ON table_name;
+```
+
+### 2.4 删除索引
+
+删除索引会删除对应的索引表。
+
+```sql
+DROP INDEX [IF EXISTS] index_name ON table_name;
+```
+
+如果存在索引的表被删除了,其对应的索引和索引表都会被删除。如果被索引表的某个分区被删除了,那么分区对应的分区索引也会被删除。
+
+### 2.5 重建索引
+
+```sql
+ALTER INDEX index_name ON table_name [PARTITION partition_spec] REBUILD;
+```
+
+重建索引。如果指定了 PARTITION,则仅重建该分区的索引。
+
+
+
+## 三、索引案例
+
+### 3.1 创建索引
+
+在 emp 表上针对 `empno` 字段创建名为 `emp_index`,索引数据存储在 `emp_index_table` 索引表中
+
+```sql
+create index emp_index on table emp(empno) as
+'org.apache.hadoop.hive.ql.index.compact.CompactIndexHandler'
+with deferred rebuild
+in table emp_index_table ;
+```
+
+此时索引表中是没有数据的,需要重建索引才会有索引的数据。
+
+### 3.2 重建索引
+
+```sql
+alter index emp_index on emp rebuild;
+```
+
+Hive 会启动 MapReduce 作业去建立索引,建立好后查看索引表数据如下。三个表字段分别代表:索引列的值、该值对应的 HDFS 文件路径、该值在文件中的偏移量。
+
+
+
+### 3.3 自动使用索引
+
+默认情况下,虽然建立了索引,但是 Hive 在查询时候是不会自动去使用索引的,需要开启相关配置。开启配置后,涉及到索引列的查询就会使用索引功能去优化查询。
+
+```sql
+SET hive.input.format=org.apache.hadoop.hive.ql.io.HiveInputFormat;
+SET hive.optimize.index.filter=true;
+SET hive.optimize.index.filter.compact.minsize=0;
+```
+
+### 3.4 查看索引
+
+```sql
+SHOW INDEX ON emp;
+```
+
+
+
+
+
+
+
+## 四、索引的缺陷
+
+索引表最主要的一个缺陷在于:索引表无法自动 rebuild,这也就意味着如果表中有数据新增或删除,则必须手动 rebuild,重新执行 MapReduce 作业,生成索引表数据。
+
+同时按照[官方文档](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Indexing) 的说明,Hive 会从 3.0 开始移除索引功能,主要基于以下两个原因:
+
+- 具有自动重写的物化视图 (Materialized View) 可以产生与索引相似的效果(Hive 2.3.0 增加了对物化视图的支持,在 3.0 之后正式引入)。
+- 使用列式存储文件格式(Parquet,ORC)进行存储时,这些格式支持选择性扫描,可以跳过不需要的文件或块。
+
+> ORC 内置的索引功能可以参阅这篇文章:[Hive 性能优化之 ORC 索引–Row Group Index vs Bloom Filter Index](http://lxw1234.com/archives/2016/04/632.htm)
+
+
+
+
+
+## 参考资料
+
+1. [Create/Drop/Alter View](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL#LanguageManualDDL-Create/Drop/AlterView)
+2. [Materialized views](https://cwiki.apache.org/confluence/display/Hive/Materialized+views)
+3. [Hive 索引](http://lxw1234.com/archives/2015/05/207.htm)
+4. [Overview of Hive Indexes](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Indexing)
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Kafka\346\266\210\350\264\271\350\200\205\350\257\246\350\247\243.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Kafka\346\266\210\350\264\271\350\200\205\350\257\246\350\247\243.md"
new file mode 100644
index 0000000..ea9fb2c
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Kafka\346\266\210\350\264\271\350\200\205\350\257\246\350\247\243.md"
@@ -0,0 +1,392 @@
+# Kafka消费者详解
+
+
+一、消费者和消费者群组
+二、分区再均衡
+三、创建Kafka消费者
+三、 自动提交偏移量
+四、手动提交偏移量
+ 4.1 同步提交
+ 4.2 异步提交
+ 4.3 同步加异步提交
+ 4.4 提交特定偏移量
+五、监听分区再均衡
+六、退出轮询
+七、独立的消费者
+附录 : Kafka消费者可选属性
+
+
+
+## 一、消费者和消费者群组
+
+在 Kafka 中,消费者通常是消费者群组的一部分,多个消费者群组共同读取同一个主题时,彼此之间互不影响。Kafka 之所以要引入消费者群组这个概念是因为 Kafka 消费者经常会做一些高延迟的操作,比如把数据写到数据库或 HDFS ,或者进行耗时的计算,在这些情况下,单个消费者无法跟上数据生成的速度。此时可以增加更多的消费者,让它们分担负载,分别处理部分分区的消息,这就是 Kafka 实现横向伸缩的主要手段。
+
+
+
+需要注意的是:同一个分区只能被同一个消费者群组里面的一个消费者读取,不可能存在同一个分区被同一个消费者群里多个消费者共同读取的情况,如图:
+
+
+
+可以看到即便消费者 Consumer5 空闲了,但是也不会去读取任何一个分区的数据,这同时也提醒我们在使用时应该合理设置消费者的数量,以免造成闲置和额外开销。
+
+## 二、分区再均衡
+
+因为群组里的消费者共同读取主题的分区,所以当一个消费者被关闭或发生崩溃时,它就离开了群组,原本由它读取的分区将由群组里的其他消费者来读取。同时在主题发生变化时 , 比如添加了新的分区,也会发生分区与消费者的重新分配,分区的所有权从一个消费者转移到另一个消费者,这样的行为被称为再均衡。正是因为再均衡,所以消费费者群组才能保证高可用性和伸缩性。
+
+消费者通过向群组协调器所在的 broker 发送心跳来维持它们和群组的从属关系以及它们对分区的所有权。只要消费者以正常的时间间隔发送心跳,就被认为是活跃的,说明它还在读取分区里的消息。消费者会在轮询消息或提交偏移量时发送心跳。如果消费者停止发送心跳的时间足够长,会话就会过期,群组协调器认为它已经死亡,就会触发再均衡。
+
+
+## 三、创建Kafka消费者
+
+在创建消费者的时候以下以下三个选项是必选的:
+
+- **bootstrap.servers** :指定 broker 的地址清单,清单里不需要包含所有的 broker 地址,生产者会从给定的 broker 里查找 broker 的信息。不过建议至少要提供两个 broker 的信息作为容错;
+- **key.deserializer** :指定键的反序列化器;
+- **value.deserializer** :指定值的反序列化器。
+
+除此之外你还需要指明你需要想订阅的主题,可以使用如下两个 API :
+
++ **consumer.subscribe(Collection\ topics)** :指明需要订阅的主题的集合;
++ **consumer.subscribe(Pattern pattern)** :使用正则来匹配需要订阅的集合。
+
+最后只需要通过轮询 API(`poll`) 向服务器定时请求数据。一旦消费者订阅了主题,轮询就会处理所有的细节,包括群组协调、分区再均衡、发送心跳和获取数据,这使得开发者只需要关注从分区返回的数据,然后进行业务处理。 示例如下:
+
+```scala
+String topic = "Hello-Kafka";
+String group = "group1";
+Properties props = new Properties();
+props.put("bootstrap.servers", "hadoop001:9092");
+/*指定分组 ID*/
+props.put("group.id", group);
+props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
+props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
+KafkaConsumer consumer = new KafkaConsumer<>(props);
+
+/*订阅主题 (s)*/
+consumer.subscribe(Collections.singletonList(topic));
+
+try {
+ while (true) {
+ /*轮询获取数据*/
+ ConsumerRecords records = consumer.poll(Duration.of(100, ChronoUnit.MILLIS));
+ for (ConsumerRecord record : records) {
+ System.out.printf("topic = %s,partition = %d, key = %s, value = %s, offset = %d,\n",
+ record.topic(), record.partition(), record.key(), record.value(), record.offset());
+ }
+ }
+} finally {
+ consumer.close();
+}
+```
+
+> 本篇文章的所有示例代码可以从 Github 上进行下载:[kafka-basis](https://github.com/heibaiying/BigData-Notes/tree/master/code/Kafka/kafka-basis)
+
+## 三、 自动提交偏移量
+
+### 3.1 偏移量的重要性
+
+Kafka 的每一条消息都有一个偏移量属性,记录了其在分区中的位置,偏移量是一个单调递增的整数。消费者通过往一个叫作 `_consumer_offset` 的特殊主题发送消息,消息里包含每个分区的偏移量。 如果消费者一直处于运行状态,那么偏移量就没有
+什么用处。不过,如果有消费者退出或者新分区加入,此时就会触发再均衡。完成再均衡之后,每个消费者可能分配到新的分区,而不是之前处理的那个。为了能够继续之前的工作,消费者需要读取每个分区最后一次提交的偏移量,然后从偏移量指定的地方继续处理。 因为这个原因,所以如果不能正确提交偏移量,就可能会导致数据丢失或者重复出现消费,比如下面情况:
+
++ 如果提交的偏移量小于客户端处理的最后一个消息的偏移量 ,那么处于两个偏移量之间的消息就会被重复消费;
++ 如果提交的偏移量大于客户端处理的最后一个消息的偏移量,那么处于两个偏移量之间的消息将会丢失。
+
+### 3.2 自动提交偏移量
+
+Kafka 支持自动提交和手动提交偏移量两种方式。这里先介绍比较简单的自动提交:
+
+只需要将消费者的 `enable.auto.commit` 属性配置为 `true` 即可完成自动提交的配置。 此时每隔固定的时间,消费者就会把 `poll()` 方法接收到的最大偏移量进行提交,提交间隔由 `auto.commit.interval.ms` 属性进行配置,默认值是 5s。
+
+使用自动提交是存在隐患的,假设我们使用默认的 5s 提交时间间隔,在最近一次提交之后的 3s 发生了再均衡,再均衡之后,消费者从最后一次提交的偏移量位置开始读取消息。这个时候偏移量已经落后了 3s ,所以在这 3s 内到达的消息会被重复处理。可以通过修改提交时间间隔来更频繁地提交偏移量,减小可能出现重复消息的时间窗,不过这种情况是无法完全避免的。基于这个原因,Kafka 也提供了手动提交偏移量的 API,使得用户可以更为灵活的提交偏移量。
+
+
+
+## 四、手动提交偏移量
+
+用户可以通过将 `enable.auto.commit` 设为 `false`,然后手动提交偏移量。基于用户需求手动提交偏移量可以分为两大类:
+
++ 手动提交当前偏移量:即手动提交当前轮询的最大偏移量;
++ 手动提交固定偏移量:即按照业务需求,提交某一个固定的偏移量。
+
+而按照 Kafka API,手动提交偏移量又可以分为同步提交和异步提交。
+
+### 4.1 同步提交
+
+通过调用 `consumer.commitSync()` 来进行同步提交,不传递任何参数时提交的是当前轮询的最大偏移量。
+
+```java
+while (true) {
+ ConsumerRecords records = consumer.poll(Duration.of(100, ChronoUnit.MILLIS));
+ for (ConsumerRecord record : records) {
+ System.out.println(record);
+ }
+ /*同步提交*/
+ consumer.commitSync();
+}
+```
+
+如果某个提交失败,同步提交还会进行重试,这可以保证数据能够最大限度提交成功,但是同时也会降低程序的吞吐量。基于这个原因,Kafka 还提供了异步提交的 API。
+
+### 4.2 异步提交
+
+异步提交可以提高程序的吞吐量,因为此时你可以尽管请求数据,而不用等待 Broker 的响应。代码如下:
+
+```java
+while (true) {
+ ConsumerRecords records = consumer.poll(Duration.of(100, ChronoUnit.MILLIS));
+ for (ConsumerRecord record : records) {
+ System.out.println(record);
+ }
+ /*异步提交并定义回调*/
+ consumer.commitAsync(new OffsetCommitCallback() {
+ @Override
+ public void onComplete(Map offsets, Exception exception) {
+ if (exception != null) {
+ System.out.println("错误处理");
+ offsets.forEach((x, y) -> System.out.printf("topic = %s,partition = %d, offset = %s \n",
+ x.topic(), x.partition(), y.offset()));
+ }
+ }
+ });
+}
+```
+
+异步提交存在的问题是,在提交失败的时候不会进行自动重试,实际上也不能进行自动重试。假设程序同时提交了 200 和 300 的偏移量,此时 200 的偏移量失败的,但是紧随其后的 300 的偏移量成功了,此时如果重试就会存在 200 覆盖 300 偏移量的可能。同步提交就不存在这个问题,因为在同步提交的情况下,300 的提交请求必须等待服务器返回 200 提交请求的成功反馈后才会发出。基于这个原因,某些情况下,需要同时组合同步和异步两种提交方式。
+
+> 注:虽然程序不能在失败时候进行自动重试,但是我们是可以手动进行重试的,你可以通过一个 Map offsets 来维护你提交的每个分区的偏移量,然后当失败时候,你可以判断失败的偏移量是否小于你维护的同主题同分区的最后提交的偏移量,如果小于则代表你已经提交了更大的偏移量请求,此时不需要重试,否则就可以进行手动重试。
+
+### 4.3 同步加异步提交
+
+下面这种情况,在正常的轮询中使用异步提交来保证吞吐量,但是因为在最后即将要关闭消费者了,所以此时需要用同步提交来保证最大限度的提交成功。
+
+```scala
+try {
+ while (true) {
+ ConsumerRecords records = consumer.poll(Duration.of(100, ChronoUnit.MILLIS));
+ for (ConsumerRecord record : records) {
+ System.out.println(record);
+ }
+ // 异步提交
+ consumer.commitAsync();
+ }
+} catch (Exception e) {
+ e.printStackTrace();
+} finally {
+ try {
+ // 因为即将要关闭消费者,所以要用同步提交保证提交成功
+ consumer.commitSync();
+ } finally {
+ consumer.close();
+ }
+}
+```
+
+### 4.4 提交特定偏移量
+
+在上面同步和异步提交的 API 中,实际上我们都没有对 commit 方法传递参数,此时默认提交的是当前轮询的最大偏移量,如果你需要提交特定的偏移量,可以调用它们的重载方法。
+
+```java
+/*同步提交特定偏移量*/
+commitSync(Map offsets)
+/*异步提交特定偏移量*/
+commitAsync(Map offsets, OffsetCommitCallback callback)
+```
+
+需要注意的是,因为你可以订阅多个主题,所以 `offsets` 中必须要包含所有主题的每个分区的偏移量,示例代码如下:
+
+```java
+try {
+ while (true) {
+ ConsumerRecords records = consumer.poll(Duration.of(100, ChronoUnit.MILLIS));
+ for (ConsumerRecord record : records) {
+ System.out.println(record);
+ /*记录每个主题的每个分区的偏移量*/
+ TopicPartition topicPartition = new TopicPartition(record.topic(), record.partition());
+ OffsetAndMetadata offsetAndMetadata = new OffsetAndMetadata(record.offset()+1, "no metaData");
+ /*TopicPartition 重写过 hashCode 和 equals 方法,所以能够保证同一主题和分区的实例不会被重复添加*/
+ offsets.put(topicPartition, offsetAndMetadata);
+ }
+ /*提交特定偏移量*/
+ consumer.commitAsync(offsets, null);
+ }
+} finally {
+ consumer.close();
+}
+```
+
+
+
+## 五、监听分区再均衡
+
+因为分区再均衡会导致分区与消费者的重新划分,有时候你可能希望在再均衡前执行一些操作:比如提交已经处理但是尚未提交的偏移量,关闭数据库连接等。此时可以在订阅主题时候,调用 `subscribe` 的重载方法传入自定义的分区再均衡监听器。
+
+```java
+ /*订阅指定集合内的所有主题*/
+subscribe(Collection topics, ConsumerRebalanceListener listener)
+ /*使用正则匹配需要订阅的主题*/
+subscribe(Pattern pattern, ConsumerRebalanceListener listener)
+```
+
+代码示例如下:
+
+```java
+Map offsets = new HashMap<>();
+
+consumer.subscribe(Collections.singletonList(topic), new ConsumerRebalanceListener() {
+ /*该方法会在消费者停止读取消息之后,再均衡开始之前就调用*/
+ @Override
+ public void onPartitionsRevoked(Collection partitions) {
+ System.out.println("再均衡即将触发");
+ // 提交已经处理的偏移量
+ consumer.commitSync(offsets);
+ }
+
+ /*该方法会在重新分配分区之后,消费者开始读取消息之前被调用*/
+ @Override
+ public void onPartitionsAssigned(Collection partitions) {
+
+ }
+});
+
+try {
+ while (true) {
+ ConsumerRecords records = consumer.poll(Duration.of(100, ChronoUnit.MILLIS));
+ for (ConsumerRecord record : records) {
+ System.out.println(record);
+ TopicPartition topicPartition = new TopicPartition(record.topic(), record.partition());
+ OffsetAndMetadata offsetAndMetadata = new OffsetAndMetadata(record.offset() + 1, "no metaData");
+ /*TopicPartition 重写过 hashCode 和 equals 方法,所以能够保证同一主题和分区的实例不会被重复添加*/
+ offsets.put(topicPartition, offsetAndMetadata);
+ }
+ consumer.commitAsync(offsets, null);
+ }
+} finally {
+ consumer.close();
+}
+```
+
+
+
+## 六 、退出轮询
+
+Kafka 提供了 `consumer.wakeup()` 方法用于退出轮询,它通过抛出 `WakeupException` 异常来跳出循环。需要注意的是,在退出线程时最好显示的调用 `consumer.close()` , 此时消费者会提交任何还没有提交的东西,并向群组协调器发送消息,告知自己要离开群组,接下来就会触发再均衡 ,而不需要等待会话超时。
+
+下面的示例代码为监听控制台输出,当输入 `exit` 时结束轮询,关闭消费者并退出程序:
+
+```java
+/*调用 wakeup 优雅的退出*/
+final Thread mainThread = Thread.currentThread();
+new Thread(() -> {
+ Scanner sc = new Scanner(System.in);
+ while (sc.hasNext()) {
+ if ("exit".equals(sc.next())) {
+ consumer.wakeup();
+ try {
+ /*等待主线程完成提交偏移量、关闭消费者等操作*/
+ mainThread.join();
+ break;
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}).start();
+
+try {
+ while (true) {
+ ConsumerRecords records = consumer.poll(Duration.of(100, ChronoUnit.MILLIS));
+ for (ConsumerRecord rd : records) {
+ System.out.printf("topic = %s,partition = %d, key = %s, value = %s, offset = %d,\n",
+ rd.topic(), rd.partition(), rd.key(), rd.value(), rd.offset());
+ }
+ }
+} catch (WakeupException e) {
+ //对于 wakeup() 调用引起的 WakeupException 异常可以不必处理
+} finally {
+ consumer.close();
+ System.out.println("consumer 关闭");
+}
+```
+
+
+
+## 七、独立的消费者
+
+因为 Kafka 的设计目标是高吞吐和低延迟,所以在 Kafka 中,消费者通常都是从属于某个群组的,这是因为单个消费者的处理能力是有限的。但是某些时候你的需求可能很简单,比如可能只需要一个消费者从一个主题的所有分区或者某个特定的分区读取数据,这个时候就不需要消费者群组和再均衡了, 只需要把主题或者分区分配给消费者,然后开始读取消息井提交偏移量即可。
+
+在这种情况下,就不需要订阅主题, 取而代之的是消费者为自己分配分区。 一个消费者可以订阅主题(井加入消费者群组),或者为自己分配分区,但不能同时做这两件事情。 分配分区的示例代码如下:
+
+```java
+List partitions = new ArrayList<>();
+List partitionInfos = consumer.partitionsFor(topic);
+
+/*可以指定读取哪些分区 如这里假设只读取主题的 0 分区*/
+for (PartitionInfo partition : partitionInfos) {
+ if (partition.partition()==0){
+ partitions.add(new TopicPartition(partition.topic(), partition.partition()));
+ }
+}
+
+// 为消费者指定分区
+consumer.assign(partitions);
+
+
+while (true) {
+ ConsumerRecords records = consumer.poll(Duration.of(100, ChronoUnit.MILLIS));
+ for (ConsumerRecord record : records) {
+ System.out.printf("partition = %s, key = %d, value = %s\n",
+ record.partition(), record.key(), record.value());
+ }
+ consumer.commitSync();
+}
+```
+
+
+
+## 附录 : Kafka消费者可选属性
+
+### 1. fetch.min.byte
+
+消费者从服务器获取记录的最小字节数。如果可用的数据量小于设置值,broker 会等待有足够的可用数据时才会把它返回给消费者。
+
+### 2. fetch.max.wait.ms
+
+broker 返回给消费者数据的等待时间,默认是 500ms。
+
+### 3. max.partition.fetch.bytes
+
+该属性指定了服务器从每个分区返回给消费者的最大字节数,默认为 1MB。
+
+### 4. session.timeout.ms
+
+消费者在被认为死亡之前可以与服务器断开连接的时间,默认是 3s。
+
+### 5. auto.offset.reset
+
+该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下该作何处理:
+
+- latest (默认值) :在偏移量无效的情况下,消费者将从最新的记录开始读取数据(在消费者启动之后生成的最新记录);
+- earliest :在偏移量无效的情况下,消费者将从起始位置读取分区的记录。
+
+### 6. enable.auto.commit
+
+是否自动提交偏移量,默认值是 true。为了避免出现重复消费和数据丢失,可以把它设置为 false。
+
+### 7. client.id
+
+客户端 id,服务器用来识别消息的来源。
+
+### 8. max.poll.records
+
+单次调用 `poll()` 方法能够返回的记录数量。
+
+### 9. receive.buffer.bytes & send.buffer.byte
+
+这两个参数分别指定 TCP socket 接收和发送数据包缓冲区的大小,-1 代表使用操作系统的默认值。
+
+
+
+## 参考资料
+
+1. Neha Narkhede, Gwen Shapira ,Todd Palino(著) , 薛命灯 (译) . Kafka 权威指南 . 人民邮电出版社 . 2017-12-26
+
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Kafka\346\267\261\345\205\245\347\220\206\350\247\243\345\210\206\345\214\272\345\211\257\346\234\254\346\234\272\345\210\266.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Kafka\346\267\261\345\205\245\347\220\206\350\247\243\345\210\206\345\214\272\345\211\257\346\234\254\346\234\272\345\210\266.md"
new file mode 100644
index 0000000..c6d5531
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Kafka\346\267\261\345\205\245\347\220\206\350\247\243\345\210\206\345\214\272\345\211\257\346\234\254\346\234\272\345\210\266.md"
@@ -0,0 +1,161 @@
+# 深入理解Kafka副本机制
+
+
+一、Kafka集群
+二、副本机制
+ 2.1 分区和副本
+ 2.2 ISR机制
+ 2.3 不完全的首领选举
+ 2.4 最少同步副本
+ 2.5 发送确认
+三、数据请求
+ 3.1 元数据请求机制
+ 3.2 数据可见性
+ 3.3 零拷贝
+四、物理存储
+ 4.1 分区分配
+ 4.2 分区数据保留规则
+ 4.3 文件格式
+
+
+
+
+## 一、Kafka集群
+
+Kafka 使用 Zookeeper 来维护集群成员 (brokers) 的信息。每个 broker 都有一个唯一标识 `broker.id`,用于标识自己在集群中的身份,可以在配置文件 `server.properties` 中进行配置,或者由程序自动生成。下面是 Kafka brokers 集群自动创建的过程:
+
++ 每一个 broker 启动的时候,它会在 Zookeeper 的 `/brokers/ids` 路径下创建一个 ` 临时节点 `,并将自己的 `broker.id` 写入,从而将自身注册到集群;
++ 当有多个 broker 时,所有 broker 会竞争性地在 Zookeeper 上创建 `/controller` 节点,由于 Zookeeper 上的节点不会重复,所以必然只会有一个 broker 创建成功,此时该 broker 称为 controller broker。它除了具备其他 broker 的功能外,**还负责管理主题分区及其副本的状态**。
++ 当 broker 出现宕机或者主动退出从而导致其持有的 Zookeeper 会话超时时,会触发注册在 Zookeeper 上的 watcher 事件,此时 Kafka 会进行相应的容错处理;如果宕机的是 controller broker 时,还会触发新的 controller 选举。
+
+## 二、副本机制
+
+为了保证高可用,kafka 的分区是多副本的,如果一个副本丢失了,那么还可以从其他副本中获取分区数据。但是这要求对应副本的数据必须是完整的,这是 Kafka 数据一致性的基础,所以才需要使用 `controller broker` 来进行专门的管理。下面将详解介绍 Kafka 的副本机制。
+
+### 2.1 分区和副本
+
+Kafka 的主题被分为多个分区 ,分区是 Kafka 最基本的存储单位。每个分区可以有多个副本 (可以在创建主题时使用 ` replication-factor` 参数进行指定)。其中一个副本是首领副本 (Leader replica),所有的事件都直接发送给首领副本;其他副本是跟随者副本 (Follower replica),需要通过复制来保持与首领副本数据一致,当首领副本不可用时,其中一个跟随者副本将成为新首领。
+
+
+
+### 2.2 ISR机制
+
+每个分区都有一个 ISR(in-sync Replica) 列表,用于维护所有同步的、可用的副本。首领副本必然是同步副本,而对于跟随者副本来说,它需要满足以下条件才能被认为是同步副本:
+
++ 与 Zookeeper 之间有一个活跃的会话,即必须定时向 Zookeeper 发送心跳;
++ 在规定的时间内从首领副本那里低延迟地获取过消息。
+
+如果副本不满足上面条件的话,就会被从 ISR 列表中移除,直到满足条件才会被再次加入。
+
+这里给出一个主题创建的示例:使用 `--replication-factor` 指定副本系数为 3,创建成功后使用 `--describe ` 命令可以看到分区 0 的有 0,1,2 三个副本,且三个副本都在 ISR 列表中,其中 1 为首领副本。
+
+
+
+### 2.3 不完全的首领选举
+
+对于副本机制,在 broker 级别有一个可选的配置参数 `unclean.leader.election.enable`,默认值为 fasle,代表禁止不完全的首领选举。这是针对当首领副本挂掉且 ISR 中没有其他可用副本时,是否允许某个不完全同步的副本成为首领副本,这可能会导致数据丢失或者数据不一致,在某些对数据一致性要求较高的场景 (如金融领域),这可能无法容忍的,所以其默认值为 false,如果你能够允许部分数据不一致的话,可以配置为 true。
+
+### 2.4 最少同步副本
+
+ISR 机制的另外一个相关参数是 `min.insync.replicas` , 可以在 broker 或者主题级别进行配置,代表 ISR 列表中至少要有几个可用副本。这里假设设置为 2,那么当可用副本数量小于该值时,就认为整个分区处于不可用状态。此时客户端再向分区写入数据时候就会抛出异常 `org.apache.kafka.common.errors.NotEnoughReplicasExceptoin: Messages are rejected since there are fewer in-sync replicas than required。`
+
+### 2.5 发送确认
+
+Kafka 在生产者上有一个可选的参数 ack,该参数指定了必须要有多少个分区副本收到消息,生产者才会认为消息写入成功:
+
+- **acks=0** :消息发送出去就认为已经成功了,不会等待任何来自服务器的响应;
+- **acks=1** : 只要集群的首领节点收到消息,生产者就会收到一个来自服务器成功响应;
+- **acks=all** :只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应。
+
+## 三、数据请求
+
+### 3.1 元数据请求机制
+
+在所有副本中,只有领导副本才能进行消息的读写处理。由于不同分区的领导副本可能在不同的 broker 上,如果某个 broker 收到了一个分区请求,但是该分区的领导副本并不在该 broker 上,那么它就会向客户端返回一个 `Not a Leader for Partition` 的错误响应。 为了解决这个问题,Kafka 提供了元数据请求机制。
+
+首先集群中的每个 broker 都会缓存所有主题的分区副本信息,客户端会定期发送发送元数据请求,然后将获取的元数据进行缓存。定时刷新元数据的时间间隔可以通过为客户端配置 `metadata.max.age.ms` 来进行指定。有了元数据信息后,客户端就知道了领导副本所在的 broker,之后直接将读写请求发送给对应的 broker 即可。
+
+如果在定时请求的时间间隔内发生的分区副本的选举,则意味着原来缓存的信息可能已经过时了,此时还有可能会收到 `Not a Leader for Partition` 的错误响应,这种情况下客户端会再次求发出元数据请求,然后刷新本地缓存,之后再去正确的 broker 上执行对应的操作,过程如下图:
+
+
+
+### 3.2 数据可见性
+
+需要注意的是,并不是所有保存在分区首领上的数据都可以被客户端读取到,为了保证数据一致性,只有被所有同步副本 (ISR 中所有副本) 都保存了的数据才能被客户端读取到。
+
+
+
+### 3.3 零拷贝
+
+Kafka 所有数据的写入和读取都是通过零拷贝来实现的。传统拷贝与零拷贝的区别如下:
+
+#### 传统模式下的四次拷贝与四次上下文切换
+
+以将磁盘文件通过网络发送为例。传统模式下,一般使用如下伪代码所示的方法先将文件数据读入内存,然后通过 Socket 将内存中的数据发送出去。
+
+```java
+buffer = File.read
+Socket.send(buffer)
+```
+
+这一过程实际上发生了四次数据拷贝。首先通过系统调用将文件数据读入到内核态 Buffer(DMA 拷贝),然后应用程序将内存态 Buffer 数据读入到用户态 Buffer(CPU 拷贝),接着用户程序通过 Socket 发送数据时将用户态 Buffer 数据拷贝到内核态 Buffer(CPU 拷贝),最后通过 DMA 拷贝将数据拷贝到 NIC Buffer。同时,还伴随着四次上下文切换,如下图所示:
+
+
+
+#### sendfile和transferTo实现零拷贝
+
+Linux 2.4+ 内核通过 `sendfile` 系统调用,提供了零拷贝。数据通过 DMA 拷贝到内核态 Buffer 后,直接通过 DMA 拷贝到 NIC Buffer,无需 CPU 拷贝。这也是零拷贝这一说法的来源。除了减少数据拷贝外,因为整个读文件到网络发送由一个 `sendfile` 调用完成,整个过程只有两次上下文切换,因此大大提高了性能。零拷贝过程如下图所示:
+
+
+
+从具体实现来看,Kafka 的数据传输通过 TransportLayer 来完成,其子类 `PlaintextTransportLayer` 的 `transferFrom` 方法通过调用 Java NIO 中 FileChannel 的 `transferTo` 方法实现零拷贝,如下所示:
+
+```java
+@Override
+public long transferFrom(FileChannel fileChannel, long position, long count) throws IOException {
+ return fileChannel.transferTo(position, count, socketChannel);
+}
+```
+
+**注:** `transferTo` 和 `transferFrom` 并不保证一定能使用零拷贝。实际上是否能使用零拷贝与操作系统相关,如果操作系统提供 `sendfile` 这样的零拷贝系统调用,则这两个方法会通过这样的系统调用充分利用零拷贝的优势,否则并不能通过这两个方法本身实现零拷贝。
+
+## 四、物理存储
+
+### 4.1 分区分配
+
+在创建主题时,Kafka 会首先决定如何在 broker 间分配分区副本,它遵循以下原则:
+
++ 在所有 broker 上均匀地分配分区副本;
++ 确保分区的每个副本分布在不同的 broker 上;
++ 如果使用了 `broker.rack` 参数为 broker 指定了机架信息,那么会尽可能的把每个分区的副本分配到不同机架的 broker 上,以避免一个机架不可用而导致整个分区不可用。
+
+基于以上原因,如果你在一个单节点上创建一个 3 副本的主题,通常会抛出下面的异常:
+
+```properties
+Error while executing topic command : org.apache.kafka.common.errors.InvalidReplicationFactor
+Exception: Replication factor: 3 larger than available brokers: 1.
+```
+
+### 4.2 分区数据保留规则
+
+保留数据是 Kafka 的一个基本特性, 但是 Kafka 不会一直保留数据,也不会等到所有消费者都读取了消息之后才删除消息。相反, Kafka 为每个主题配置了数据保留期限,规定数据被删除之前可以保留多长时间,或者清理数据之前可以保留的数据量大小。分别对应以下四个参数:
+
+- `log.retention.bytes` :删除数据前允许的最大数据量;默认值-1,代表没有限制;
+- `log.retention.ms`:保存数据文件的毫秒数,如果未设置,则使用 `log.retention.minutes` 中的值,默认为 null;
+- `log.retention.minutes`:保留数据文件的分钟数,如果未设置,则使用 `log.retention.hours` 中的值,默认为 null;
+- `log.retention.hours`:保留数据文件的小时数,默认值为 168,也就是一周。
+
+因为在一个大文件里查找和删除消息是很费时的,也很容易出错,所以 Kafka 把分区分成若干个片段,当前正在写入数据的片段叫作活跃片段。活动片段永远不会被删除。如果按照默认值保留数据一周,而且每天使用一个新片段,那么你就会看到,在每天使用一个新片段的同时会删除一个最老的片段,所以大部分时间该分区会有 7 个片段存在。
+
+### 4.3 文件格式
+
+通常保存在磁盘上的数据格式与生产者发送过来消息格式是一样的。 如果生产者发送的是压缩过的消息,那么同一个批次的消息会被压缩在一起,被当作“包装消息”进行发送 (格式如下所示) ,然后保存到磁盘上。之后消费者读取后再自己解压这个包装消息,获取每条消息的具体信息。
+
+
+
+
+
+## 参考资料
+
+1. Neha Narkhede, Gwen Shapira ,Todd Palino(著) , 薛命灯 (译) . Kafka 权威指南 . 人民邮电出版社 . 2017-12-26
+2. [Kafka 高性能架构之道](http://www.jasongj.com/kafka/high_throughput/)
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Kafka\347\224\237\344\272\247\350\200\205\350\257\246\350\247\243.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Kafka\347\224\237\344\272\247\350\200\205\350\257\246\350\247\243.md"
new file mode 100644
index 0000000..e9f68d8
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Kafka\347\224\237\344\272\247\350\200\205\350\257\246\350\247\243.md"
@@ -0,0 +1,364 @@
+# Kafka生产者详解
+
+
+一、生产者发送消息的过程
+二、创建生产者
+二、发送消息
+ 2.1 同步发送
+ 2.2 异步发送
+三、自定义分区器
+四、生产者其他属性
+
+
+
+## 一、生产者发送消息的过程
+
+首先介绍一下 Kafka 生产者发送消息的过程:
+
++ Kafka 会将发送消息包装为 ProducerRecord 对象, ProducerRecord 对象包含了目标主题和要发送的内容,同时还可以指定键和分区。在发送 ProducerRecord 对象前,生产者会先把键和值对象序列化成字节数组,这样它们才能够在网络上传输。
++ 接下来,数据被传给分区器。如果之前已经在 ProducerRecord 对象里指定了分区,那么分区器就不会再做任何事情。如果没有指定分区 ,那么分区器会根据 ProducerRecord 对象的键来选择一个分区,紧接着,这条记录被添加到一个记录批次里,这个批次里的所有消息会被发送到相同的主题和分区上。有一个独立的线程负责把这些记录批次发送到相应的 broker 上。
++ 服务器在收到这些消息时会返回一个响应。如果消息成功写入 Kafka,就返回一个 RecordMetaData 对象,它包含了主题和分区信息,以及记录在分区里的偏移量。如果写入失败,则会返回一个错误。生产者在收到错误之后会尝试重新发送消息,如果达到指定的重试次数后还没有成功,则直接抛出异常,不再重试。
+
+
+
+## 二、创建生产者
+
+### 2.1 项目依赖
+
+本项目采用 Maven 构建,想要调用 Kafka 生产者 API,需要导入 `kafka-clients` 依赖,如下:
+
+```xml
+
+ org.apache.kafka
+ kafka-clients
+ 2.2.0
+
+```
+
+### 2.2 创建生产者
+
+创建 Kafka 生产者时,以下三个属性是必须指定的:
+
++ **bootstrap.servers** :指定 broker 的地址清单,清单里不需要包含所有的 broker 地址,生产者会从给定的 broker 里查找 broker 的信息。不过建议至少要提供两个 broker 的信息作为容错;
++ **key.serializer** :指定键的序列化器;
++ **value.serializer** :指定值的序列化器。
+
+创建的示例代码如下:
+
+```scala
+public class SimpleProducer {
+
+ public static void main(String[] args) {
+
+ String topicName = "Hello-Kafka";
+
+ Properties props = new Properties();
+ props.put("bootstrap.servers", "hadoop001:9092");
+ props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
+ props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
+ /*创建生产者*/
+ Producer producer = new KafkaProducer<>(props);
+
+ for (int i = 0; i < 10; i++) {
+ ProducerRecord record = new ProducerRecord<>(topicName, "hello" + i,
+ "world" + i);
+ /* 发送消息*/
+ producer.send(record);
+ }
+ /*关闭生产者*/
+ producer.close();
+ }
+}
+```
+
+> 本篇文章的所有示例代码可以从 Github 上进行下载:[kafka-basis](https://github.com/heibaiying/BigData-Notes/tree/master/code/Kafka/kafka-basis)
+
+### 2.3 测试
+
+#### 1. 启动Kakfa
+
+Kafka 的运行依赖于 zookeeper,需要预先启动,可以启动 Kafka 内置的 zookeeper,也可以启动自己安装的:
+
+```shell
+# zookeeper启动命令
+bin/zkServer.sh start
+
+# 内置zookeeper启动命令
+bin/zookeeper-server-start.sh config/zookeeper.properties
+```
+
+启动单节点 kafka 用于测试:
+
+```shell
+# bin/kafka-server-start.sh config/server.properties
+```
+
+#### 2. 创建topic
+
+```shell
+# 创建用于测试主题
+bin/kafka-topics.sh --create \
+ --bootstrap-server hadoop001:9092 \
+ --replication-factor 1 --partitions 1 \
+ --topic Hello-Kafka
+
+# 查看所有主题
+ bin/kafka-topics.sh --list --bootstrap-server hadoop001:9092
+```
+
+#### 3. 启动消费者
+
+ 启动一个控制台消费者用于观察写入情况,启动命令如下:
+
+```shell
+# bin/kafka-console-consumer.sh --bootstrap-server hadoop001:9092 --topic Hello-Kafka --from-beginning
+```
+
+#### 4. 运行项目
+
+此时可以看到消费者控制台,输出如下,这里 `kafka-console-consumer` 只会打印出值信息,不会打印出键信息。
+
+
+
+
+
+### 2.4 可能出现的问题
+
+在这里可能出现的一个问题是:生产者程序在启动后,一直处于等待状态。这通常出现在你使用默认配置启动 Kafka 的情况下,此时需要对 `server.properties` 文件中的 `listeners` 配置进行更改:
+
+```shell
+# hadoop001 为我启动kafka服务的主机名,你可以换成自己的主机名或者ip地址
+listeners=PLAINTEXT://hadoop001:9092
+```
+
+
+
+## 二、发送消息
+
+上面的示例程序调用了 `send` 方法发送消息后没有做任何操作,在这种情况下,我们没有办法知道消息发送的结果。想要知道消息发送的结果,可以使用同步发送或者异步发送来实现。
+
+### 2.1 同步发送
+
+在调用 `send` 方法后可以接着调用 `get()` 方法,`send` 方法的返回值是一个 Future\对象,RecordMetadata 里面包含了发送消息的主题、分区、偏移量等信息。改写后的代码如下:
+
+```scala
+for (int i = 0; i < 10; i++) {
+ try {
+ ProducerRecord record = new ProducerRecord<>(topicName, "k" + i, "world" + i);
+ /*同步发送消息*/
+ RecordMetadata metadata = producer.send(record).get();
+ System.out.printf("topic=%s, partition=%d, offset=%s \n",
+ metadata.topic(), metadata.partition(), metadata.offset());
+ } catch (InterruptedException | ExecutionException e) {
+ e.printStackTrace();
+ }
+}
+```
+
+此时得到的输出如下:偏移量和调用次数有关,所有记录都分配到了 0 分区,这是因为在创建 `Hello-Kafka` 主题时候,使用 `--partitions` 指定其分区数为 1,即只有一个分区。
+
+```shell
+topic=Hello-Kafka, partition=0, offset=40
+topic=Hello-Kafka, partition=0, offset=41
+topic=Hello-Kafka, partition=0, offset=42
+topic=Hello-Kafka, partition=0, offset=43
+topic=Hello-Kafka, partition=0, offset=44
+topic=Hello-Kafka, partition=0, offset=45
+topic=Hello-Kafka, partition=0, offset=46
+topic=Hello-Kafka, partition=0, offset=47
+topic=Hello-Kafka, partition=0, offset=48
+topic=Hello-Kafka, partition=0, offset=49
+```
+
+### 2.2 异步发送
+
+通常我们并不关心发送成功的情况,更多关注的是失败的情况,因此 Kafka 提供了异步发送和回调函数。 代码如下:
+
+```scala
+for (int i = 0; i < 10; i++) {
+ ProducerRecord record = new ProducerRecord<>(topicName, "k" + i, "world" + i);
+ /*异步发送消息,并监听回调*/
+ producer.send(record, new Callback() {
+ @Override
+ public void onCompletion(RecordMetadata metadata, Exception exception) {
+ if (exception != null) {
+ System.out.println("进行异常处理");
+ } else {
+ System.out.printf("topic=%s, partition=%d, offset=%s \n",
+ metadata.topic(), metadata.partition(), metadata.offset());
+ }
+ }
+ });
+}
+```
+
+
+
+## 三、自定义分区器
+
+Kafka 有着默认的分区机制:
+
++ 如果键值为 null, 则使用轮询 (Round Robin) 算法将消息均衡地分布到各个分区上;
++ 如果键值不为 null,那么 Kafka 会使用内置的散列算法对键进行散列,然后分布到各个分区上。
+
+某些情况下,你可能有着自己的分区需求,这时候可以采用自定义分区器实现。这里给出一个自定义分区器的示例:
+
+### 3.1 自定义分区器
+
+```java
+/**
+ * 自定义分区器
+ */
+public class CustomPartitioner implements Partitioner {
+
+ private int passLine;
+
+ @Override
+ public void configure(Map configs) {
+ /*从生产者配置中获取分数线*/
+ passLine = (Integer) configs.get("pass.line");
+ }
+
+ @Override
+ public int partition(String topic, Object key, byte[] keyBytes, Object value,
+ byte[] valueBytes, Cluster cluster) {
+ /*key 值为分数,当分数大于分数线时候,分配到 1 分区,否则分配到 0 分区*/
+ return (Integer) key >= passLine ? 1 : 0;
+ }
+
+ @Override
+ public void close() {
+ System.out.println("分区器关闭");
+ }
+}
+```
+
+需要在创建生产者时指定分区器,和分区器所需要的配置参数:
+
+```java
+public class ProducerWithPartitioner {
+
+ public static void main(String[] args) {
+
+ String topicName = "Kafka-Partitioner-Test";
+
+ Properties props = new Properties();
+ props.put("bootstrap.servers", "hadoop001:9092");
+ props.put("key.serializer", "org.apache.kafka.common.serialization.IntegerSerializer");
+ props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
+
+ /*传递自定义分区器*/
+ props.put("partitioner.class", "com.heibaiying.producers.partitioners.CustomPartitioner");
+ /*传递分区器所需的参数*/
+ props.put("pass.line", 6);
+
+ Producer producer = new KafkaProducer<>(props);
+
+ for (int i = 0; i <= 10; i++) {
+ String score = "score:" + i;
+ ProducerRecord record = new ProducerRecord<>(topicName, i, score);
+ /*异步发送消息*/
+ producer.send(record, (metadata, exception) ->
+ System.out.printf("%s, partition=%d, \n", score, metadata.partition()));
+ }
+
+ producer.close();
+ }
+}
+```
+
+### 3.2 测试
+
+需要创建一个至少有两个分区的主题:
+
+```shell
+ bin/kafka-topics.sh --create \
+ --bootstrap-server hadoop001:9092 \
+ --replication-factor 1 --partitions 2 \
+ --topic Kafka-Partitioner-Test
+```
+
+此时输入如下,可以看到分数大于等于 6 分的都被分到 1 分区,而小于 6 分的都被分到了 0 分区。
+
+```shell
+score:6, partition=1,
+score:7, partition=1,
+score:8, partition=1,
+score:9, partition=1,
+score:10, partition=1,
+score:0, partition=0,
+score:1, partition=0,
+score:2, partition=0,
+score:3, partition=0,
+score:4, partition=0,
+score:5, partition=0,
+分区器关闭
+```
+
+
+
+## 四、生产者其他属性
+
+上面生产者的创建都仅指定了服务地址,键序列化器、值序列化器,实际上 Kafka 的生产者还有很多可配置属性,如下:
+
+### 1. acks
+
+acks 参数指定了必须要有多少个分区副本收到消息,生产者才会认为消息写入是成功的:
+
+- **acks=0** : 消息发送出去就认为已经成功了,不会等待任何来自服务器的响应;
+- **acks=1** : 只要集群的首领节点收到消息,生产者就会收到一个来自服务器成功响应;
+- **acks=all** :只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应。
+
+### 2. buffer.memory
+
+设置生产者内存缓冲区的大小。
+
+### 3. compression.type
+
+默认情况下,发送的消息不会被压缩。如果想要进行压缩,可以配置此参数,可选值有 snappy,gzip,lz4。
+
+### 4. retries
+
+发生错误后,消息重发的次数。如果达到设定值,生产者就会放弃重试并返回错误。
+
+### 5. batch.size
+
+当有多个消息需要被发送到同一个分区时,生产者会把它们放在同一个批次里。该参数指定了一个批次可以使用的内存大小,按照字节数计算。
+
+### 6. linger.ms
+
+该参数制定了生产者在发送批次之前等待更多消息加入批次的时间。
+
+### 7. clent.id
+
+客户端 id,服务器用来识别消息的来源。
+
+### 8. max.in.flight.requests.per.connection
+
+指定了生产者在收到服务器响应之前可以发送多少个消息。它的值越高,就会占用越多的内存,不过也会提升吞吐量,把它设置为 1 可以保证消息是按照发送的顺序写入服务器,即使发生了重试。
+
+### 9. timeout.ms, request.timeout.ms & metadata.fetch.timeout.ms
+
+- timeout.ms 指定了 borker 等待同步副本返回消息的确认时间;
+- request.timeout.ms 指定了生产者在发送数据时等待服务器返回响应的时间;
+- metadata.fetch.timeout.ms 指定了生产者在获取元数据(比如分区首领是谁)时等待服务器返回响应的时间。
+
+### 10. max.block.ms
+
+指定了在调用 `send()` 方法或使用 `partitionsFor()` 方法获取元数据时生产者的阻塞时间。当生产者的发送缓冲区已满,或者没有可用的元数据时,这些方法会阻塞。在阻塞时间达到 max.block.ms 时,生产者会抛出超时异常。
+
+### 11. max.request.size
+
+该参数用于控制生产者发送的请求大小。它可以指发送的单个消息的最大值,也可以指单个请求里所有消息总的大小。例如,假设这个值为 1000K ,那么可以发送的单个最大消息为 1000K ,或者生产者可以在单个请求里发送一个批次,该批次包含了 1000 个消息,每个消息大小为 1K。
+
+### 12. receive.buffer.bytes & send.buffer.byte
+
+这两个参数分别指定 TCP socket 接收和发送数据包缓冲区的大小,-1 代表使用操作系统的默认值。
+
+
+
+
+
+## 参考资料
+
+1. Neha Narkhede, Gwen Shapira ,Todd Palino(著) , 薛命灯 (译) . Kafka 权威指南 . 人民邮电出版社 . 2017-12-26
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Kafka\347\256\200\344\273\213.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Kafka\347\256\200\344\273\213.md"
new file mode 100644
index 0000000..5ef5ef3
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Kafka\347\256\200\344\273\213.md"
@@ -0,0 +1,67 @@
+# Kafka简介
+
+
+一、Kafka简介
+二、Kafka核心概念
+ 2.1 Messages And Batches
+ 2.2 Topics And Partitions
+ 2.3 Producers And Consumers
+ 2.4 Brokers And Clusters
+
+
+
+## 一、简介
+
+ApacheKafka 是一个分布式的流处理平台。它具有以下特点:
+
++ 支持消息的发布和订阅,类似于 RabbtMQ、ActiveMQ 等消息队列;
++ 支持数据实时处理;
++ 能保证消息的可靠性投递;
++ 支持消息的持久化存储,并通过多副本分布式的存储方案来保证消息的容错;
++ 高吞吐率,单 Broker 可以轻松处理数千个分区以及每秒百万级的消息量。
+
+## 二、基本概念
+
+### 2.1 Messages And Batches
+
+Kafka 的基本数据单元被称为 message(消息),为减少网络开销,提高效率,多个消息会被放入同一批次 (Batch) 中后再写入。
+
+### 2.2 Topics And Partitions
+
+Kafka 的消息通过 Topics(主题) 进行分类,一个主题可以被分为若干个 Partitions(分区),一个分区就是一个提交日志 (commit log)。消息以追加的方式写入分区,然后以先入先出的顺序读取。Kafka 通过分区来实现数据的冗余和伸缩性,分区可以分布在不同的服务器上,这意味着一个 Topic 可以横跨多个服务器,以提供比单个服务器更强大的性能。
+
+由于一个 Topic 包含多个分区,因此无法在整个 Topic 范围内保证消息的顺序性,但可以保证消息在单个分区内的顺序性。
+
+
+
+### 2.3 Producers And Consumers
+
+#### 1. 生产者
+
+生产者负责创建消息。一般情况下,生产者在把消息均衡地分布到在主题的所有分区上,而并不关心消息会被写到哪个分区。如果我们想要把消息写到指定的分区,可以通过自定义分区器来实现。
+
+#### 2. 消费者
+
+消费者是消费者群组的一部分,消费者负责消费消息。消费者可以订阅一个或者多个主题,并按照消息生成的顺序来读取它们。消费者通过检查消息的偏移量 (offset) 来区分读取过的消息。偏移量是一个不断递增的数值,在创建消息时,Kafka 会把它添加到其中,在给定的分区里,每个消息的偏移量都是唯一的。消费者把每个分区最后读取的偏移量保存在 Zookeeper 或 Kafka 上,如果消费者关闭或者重启,它还可以重新获取该偏移量,以保证读取状态不会丢失。
+
+
+
+一个分区只能被同一个消费者群组里面的一个消费者读取,但可以被不同消费者群组中所组成的多个消费者共同读取。多个消费者群组中消费者共同读取同一个主题时,彼此之间互不影响。
+
+
+
+### 2.4 Brokers And Clusters
+
+一个独立的 Kafka 服务器被称为 Broker。Broker 接收来自生产者的消息,为消息设置偏移量,并提交消息到磁盘保存。Broker 为消费者提供服务,对读取分区的请求做出响应,返回已经提交到磁盘的消息。
+
+Broker 是集群 (Cluster) 的组成部分。每一个集群都会选举出一个 Broker 作为集群控制器 (Controller),集群控制器负责管理工作,包括将分区分配给 Broker 和监控 Broker。
+
+在集群中,一个分区 (Partition) 从属一个 Broker,该 Broker 被称为分区的首领 (Leader)。一个分区可以分配给多个 Brokers,这个时候会发生分区复制。这种复制机制为分区提供了消息冗余,如果有一个 Broker 失效,其他 Broker 可以接管领导权。
+
+
+
+
+
+## 参考资料
+
+Neha Narkhede, Gwen Shapira ,Todd Palino(著) , 薛命灯 (译) . Kafka 权威指南 . 人民邮电出版社 . 2017-12-26
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\345\207\275\346\225\260\345\222\214\351\227\255\345\214\205.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\345\207\275\346\225\260\345\222\214\351\227\255\345\214\205.md"
new file mode 100644
index 0000000..482255c
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\345\207\275\346\225\260\345\222\214\351\227\255\345\214\205.md"
@@ -0,0 +1,312 @@
+# 函数和闭包
+
+
+一、函数
+ 1.1 函数与方法
+ 1.2 函数类型
+ 1.3 一等公民&匿名函数
+ 1.4 特殊的函数表达式
+二、闭包
+ 2.1 闭包的定义
+ 2.2 修改自由变量
+ 2.3 自由变量多副本
+三、高阶函数
+ 3.1 使用函数作为参数
+ 3.2 函数柯里化
+
+
+
+## 一、函数
+
+### 1.1 函数与方法
+
+Scala 中函数与方法的区别非常小,如果函数作为某个对象的成员,这样的函数被称为方法,否则就是一个正常的函数。
+
+```scala
+// 定义方法
+def multi1(x:Int) = {x * x}
+// 定义函数
+val multi2 = (x: Int) => {x * x}
+
+println(multi1(3)) //输出 9
+println(multi2(3)) //输出 9
+```
+
+也可以使用 `def` 定义函数:
+
+```scala
+def multi3 = (x: Int) => {x * x}
+println(multi3(3)) //输出 9
+```
+
+`multi2` 和 `multi3` 本质上没有区别,这是因为函数是一等公民,`val multi2 = (x: Int) => {x * x}` 这个语句相当于是使用 `def` 预先定义了函数,之后赋值给变量 `multi2`。
+
+### 1.2 函数类型
+
+上面我们说过 `multi2` 和 `multi3` 本质上是一样的,那么作为函数它们是什么类型的?两者的类型实际上都是 `Int => Int`,前面一个 Int 代表输入参数类型,后面一个 Int 代表返回值类型。
+
+```scala
+scala> val multi2 = (x: Int) => {x * x}
+multi2: Int => Int = $$Lambda$1092/594363215@1dd1a777
+
+scala> def multi3 = (x: Int) => {x * x}
+multi3: Int => Int
+
+// 如果有多个参数,则类型为:(参数类型,参数类型 ...)=>返回值类型
+scala> val multi4 = (x: Int,name: String) => {name + x * x }
+multi4: (Int, String) => String = $$Lambda$1093/1039732747@2eb4fe7
+```
+
+### 1.3 一等公民&匿名函数
+
+在 Scala 中函数是一等公民,这意味着不仅可以定义函数并调用它们,还可以将它们作为值进行传递:
+
+```scala
+import scala.math.ceil
+object ScalaApp extends App {
+ // 将函数 ceil 赋值给变量 fun,使用下划线 (_) 指明是 ceil 函数但不传递参数
+ val fun = ceil _
+ println(fun(2.3456)) //输出 3.0
+
+}
+```
+
+在 Scala 中你不必给每一个函数都命名,如 `(x: Int) => 3 * x` 就是一个匿名函数:
+
+```scala
+object ScalaApp extends App {
+ // 1.匿名函数
+ (x: Int) => 3 * x
+ // 2.具名函数
+ val fun = (x: Int) => 3 * x
+ // 3.直接使用匿名函数
+ val array01 = Array(1, 2, 3).map((x: Int) => 3 * x)
+ // 4.使用占位符简写匿名函数
+ val array02 = Array(1, 2, 3).map(_ * 3)
+ // 5.使用具名函数
+ val array03 = Array(1, 2, 3).map(fun)
+
+}
+```
+
+### 1.4 特殊的函数表达式
+
+#### 1. 可变长度参数列表
+
+在 Java 中如果你想要传递可变长度的参数,需要使用 `String ...args` 这种形式,Scala 中等效的表达为 `args: String*`。
+
+```scala
+object ScalaApp extends App {
+ def echo(args: String*): Unit = {
+ for (arg <- args) println(arg)
+ }
+ echo("spark","hadoop","flink")
+}
+// 输出
+spark
+hadoop
+flink
+```
+
+#### 2. 传递具名参数
+
+向函数传递参数时候可以指定具体的参数名。
+
+```scala
+object ScalaApp extends App {
+
+ def detail(name: String, age: Int): Unit = println(name + ":" + age)
+
+ // 1.按照参数定义的顺序传入
+ detail("heibaiying", 12)
+ // 2.传递参数的时候指定具体的名称,则不必遵循定义的顺序
+ detail(age = 12, name = "heibaiying")
+
+}
+```
+
+#### 3. 默认值参数
+
+在定义函数时,可以为参数指定默认值。
+
+```scala
+object ScalaApp extends App {
+
+ def detail(name: String, age: Int = 88): Unit = println(name + ":" + age)
+
+ // 如果没有传递 age 值,则使用默认值
+ detail("heibaiying")
+ detail("heibaiying", 12)
+
+}
+```
+
+## 二、闭包
+
+### 2.1 闭包的定义
+
+```scala
+var more = 10
+// addMore 一个闭包函数:因为其捕获了自由变量 more 从而闭合了该函数字面量
+val addMore = (x: Int) => x + more
+```
+
+如上函数 `addMore` 中有两个变量 x 和 more:
+
++ **x** : 是一个绑定变量 (bound variable),因为其是该函数的入参,在函数的上下文中有明确的定义;
++ **more** : 是一个自由变量 (free variable),因为函数字面量本生并没有给 more 赋予任何含义。
+
+按照定义:在创建函数时,如果需要捕获自由变量,那么包含指向被捕获变量的引用的函数就被称为闭包函数。
+
+### 2.2 修改自由变量
+
+这里需要注意的是,闭包捕获的是变量本身,即是对变量本身的引用,这意味着:
+
++ 闭包外部对自由变量的修改,在闭包内部是可见的;
++ 闭包内部对自由变量的修改,在闭包外部也是可见的。
+
+```scala
+// 声明 more 变量
+scala> var more = 10
+more: Int = 10
+
+// more 变量必须已经被声明,否则下面的语句会报错
+scala> val addMore = (x: Int) => {x + more}
+addMore: Int => Int = $$Lambda$1076/1844473121@876c4f0
+
+scala> addMore(10)
+res7: Int = 20
+
+// 注意这里是给 more 变量赋值,而不是重新声明 more 变量
+scala> more=1000
+more: Int = 1000
+
+scala> addMore(10)
+res8: Int = 1010
+```
+
+### 2.3 自由变量多副本
+
+自由变量可能随着程序的改变而改变,从而产生多个副本,但是闭包永远指向创建时候有效的那个变量副本。
+
+```scala
+// 第一次声明 more 变量
+scala> var more = 10
+more: Int = 10
+
+// 创建闭包函数
+scala> val addMore10 = (x: Int) => {x + more}
+addMore10: Int => Int = $$Lambda$1077/1144251618@1bdaa13c
+
+// 调用闭包函数
+scala> addMore10(9)
+res9: Int = 19
+
+// 重新声明 more 变量
+scala> var more = 100
+more: Int = 100
+
+// 创建新的闭包函数
+scala> val addMore100 = (x: Int) => {x + more}
+addMore100: Int => Int = $$Lambda$1078/626955849@4d0be2ac
+
+// 引用的是重新声明 more 变量
+scala> addMore100(9)
+res10: Int = 109
+
+// 引用的还是第一次声明的 more 变量
+scala> addMore10(9)
+res11: Int = 19
+
+// 对于全局而言 more 还是 100
+scala> more
+res12: Int = 100
+```
+
+从上面的示例可以看出重新声明 `more` 后,全局的 `more` 的值是 100,但是对于闭包函数 `addMore10` 还是引用的是值为 10 的 `more`,这是由虚拟机来实现的,虚拟机会保证 `more` 变量在重新声明后,原来的被捕获的变量副本继续在堆上保持存活。
+
+## 三、高阶函数
+
+### 3.1 使用函数作为参数
+
+定义函数时候支持传入函数作为参数,此时新定义的函数被称为高阶函数。
+
+```scala
+object ScalaApp extends App {
+
+ // 1.定义函数
+ def square = (x: Int) => {
+ x * x
+ }
+
+ // 2.定义高阶函数: 第一个参数是类型为 Int => Int 的函数
+ def multi(fun: Int => Int, x: Int) = {
+ fun(x) * 100
+ }
+
+ // 3.传入具名函数
+ println(multi(square, 5)) // 输出 2500
+
+ // 4.传入匿名函数
+ println(multi(_ * 100, 5)) // 输出 50000
+
+}
+```
+
+### 3.2 函数柯里化
+
+我们上面定义的函数都只支持一个参数列表,而柯里化函数则支持多个参数列表。柯里化指的是将原来接受两个参数的函数变成接受一个参数的函数的过程。新的函数以原有第二个参数作为参数。
+
+```scala
+object ScalaApp extends App {
+ // 定义柯里化函数
+ def curriedSum(x: Int)(y: Int) = x + y
+ println(curriedSum(2)(3)) //输出 5
+}
+```
+
+这里当你调用 curriedSum 时候,实际上是连着做了两次传统的函数调用,实际执行的柯里化过程如下:
+
++ 第一次调用接收一个名为 `x` 的 Int 型参数,返回一个用于第二次调用的函数,假设 `x` 为 2,则返回函数 `2+y`;
++ 返回的函数接收参数 `y`,并计算并返回值 `2+3` 的值。
+
+想要获得柯里化的中间返回的函数其实也比较简单:
+
+```scala
+object ScalaApp extends App {
+ // 定义柯里化函数
+ def curriedSum(x: Int)(y: Int) = x + y
+ println(curriedSum(2)(3)) //输出 5
+
+ // 获取传入值为 10 返回的中间函数 10 + y
+ val plus: Int => Int = curriedSum(10)_
+ println(plus(3)) //输出值 13
+}
+```
+
+柯里化支持多个参数列表,多个参数按照从左到右的顺序依次执行柯里化操作:
+
+```scala
+object ScalaApp extends App {
+ // 定义柯里化函数
+ def curriedSum(x: Int)(y: Int)(z: String) = x + y + z
+ println(curriedSum(2)(3)("name")) // 输出 5name
+
+}
+```
+
+
+
+
+
+## 参考资料
+
+1. Martin Odersky . Scala 编程 (第 3 版)[M] . 电子工业出版社 . 2018-1-1
+2. 凯.S.霍斯特曼 . 快学 Scala(第 2 版)[M] . 电子工业出版社 . 2017-7
+
+
+
+
+
+
+
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\345\210\227\350\241\250\345\222\214\351\233\206.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\345\210\227\350\241\250\345\222\214\351\233\206.md"
new file mode 100644
index 0000000..208d42e
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\345\210\227\350\241\250\345\222\214\351\233\206.md"
@@ -0,0 +1,542 @@
+# List & Set
+
+
+一、List字面量
+二、List类型
+三、构建List
+四、模式匹配
+五、列表的基本操作
+六、列表的高级操作
+七、List对象的方法
+八、处理多个List
+九、缓冲列表ListBuffer
+十、集(Set)
+
+
+
+
+## 一、List字面量
+
+List 是 Scala 中非常重要的一个数据结构,其与 Array(数组) 非常类似,但是 List 是不可变的,和 Java 中的 List 一样,其底层实现是链表。
+
+```scala
+scala> val list = List("hadoop", "spark", "storm")
+list: List[String] = List(hadoop, spark, storm)
+
+// List 是不可变
+scala> list(1) = "hive"
+:9: error: value update is not a member of List[String]
+```
+
+## 二、List类型
+
+Scala 中 List 具有以下两个特性:
+
++ **同构 (homogeneous)**:同一个 List 中的所有元素都必须是相同的类型;
++ **协变 (covariant)**:如果 S 是 T 的子类型,那么 `List[S]` 就是 `List[T]` 的子类型,例如 `List[String]` 是 `List[Object]` 的子类型。
+
+需要特别说明的是空列表的类型为 `List[Nothing]`:
+
+```scala
+scala> List()
+res1: List[Nothing] = List()
+```
+
+## 三、构建List
+
+所有 List 都由两个基本单元构成:`Nil` 和 `::`(读作"cons")。即列表要么是空列表 (Nil),要么是由一个 head 加上一个 tail 组成,而 tail 又是一个 List。我们在上面使用的 `List("hadoop", "spark", "storm")` 最终也是被解释为 ` "hadoop"::"spark":: "storm"::Nil`。
+
+```scala
+scala> val list01 = "hadoop"::"spark":: "storm"::Nil
+list01: List[String] = List(hadoop, spark, storm)
+
+// :: 操作符号是右结合的,所以上面的表达式和下面的等同
+scala> val list02 = "hadoop"::("spark":: ("storm"::Nil))
+list02: List[String] = List(hadoop, spark, storm)
+```
+
+## 四、模式匹配
+
+Scala 支持展开列表以实现模式匹配。
+
+```scala
+scala> val list = List("hadoop", "spark", "storm")
+list: List[String] = List(hadoop, spark, storm)
+
+scala> val List(a,b,c)=list
+a: String = hadoop
+b: String = spark
+c: String = storm
+```
+
+如果只需要匹配部分内容,可以如下:
+
+```scala
+scala> val a::rest=list
+a: String = hadoop
+rest: List[String] = List(spark, storm)
+```
+
+## 五、列表的基本操作
+
+### 5.1 常用方法
+
+```scala
+object ScalaApp extends App {
+
+ val list = List("hadoop", "spark", "storm")
+
+ // 1.列表是否为空
+ list.isEmpty
+
+ // 2.返回列表中的第一个元素
+ list.head
+
+ // 3.返回列表中除第一个元素外的所有元素 这里输出 List(spark, storm)
+ list.tail
+
+ // 4.tail 和 head 可以结合使用
+ list.tail.head
+
+ // 5.返回列表中的最后一个元素 与 head 相反
+ list.init
+
+ // 6.返回列表中除了最后一个元素之外的其他元素 与 tail 相反 这里输出 List(hadoop, spark)
+ list.last
+
+ // 7.使用下标访问元素
+ list(2)
+
+ // 8.获取列表长度
+ list.length
+
+ // 9. 反转列表
+ list.reverse
+
+}
+```
+
+### 5.2 indices
+
+indices 方法返回所有下标。
+
+```scala
+scala> list.indices
+res2: scala.collection.immutable.Range = Range(0, 1, 2)
+```
+
+### 5.3 take & drop & splitAt
+
+- **take**:获取前 n 个元素;
+- **drop**:删除前 n 个元素;
+- **splitAt**:从第几个位置开始拆分。
+
+```scala
+scala> list take 2
+res3: List[String] = List(hadoop, spark)
+
+scala> list drop 2
+res4: List[String] = List(storm)
+
+scala> list splitAt 2
+res5: (List[String], List[String]) = (List(hadoop, spark),List(storm))
+```
+
+### 5.4 flatten
+
+flatten 接收一个由列表组成的列表,并将其进行扁平化操作,返回单个列表。
+
+```scala
+scala> List(List(1, 2), List(3), List(), List(4, 5)).flatten
+res6: List[Int] = List(1, 2, 3, 4, 5)
+```
+
+### 5.5 zip & unzip
+
+对两个 List 执行 `zip` 操作结果如下,返回对应位置元素组成的元组的列表,`unzip` 则执行反向操作。
+
+```scala
+scala> val list = List("hadoop", "spark", "storm")
+scala> val score = List(10,20,30)
+
+scala> val zipped=list zip score
+zipped: List[(String, Int)] = List((hadoop,10), (spark,20), (storm,30))
+
+scala> zipped.unzip
+res7: (List[String], List[Int]) = (List(hadoop, spark, storm),List(10, 20, 30))
+```
+
+### 5.6 toString & mkString
+
+toString 返回 List 的字符串表现形式。
+
+```scala
+scala> list.toString
+res8: String = List(hadoop, spark, storm)
+```
+
+如果想改变 List 的字符串表现形式,可以使用 mkString。mkString 有三个重载方法,方法定义如下:
+
+```scala
+// start:前缀 sep:分隔符 end:后缀
+def mkString(start: String, sep: String, end: String): String =
+ addString(new StringBuilder(), start, sep, end).toString
+
+// seq 分隔符
+def mkString(sep: String): String = mkString("", sep, "")
+
+// 如果不指定分隔符 默认使用""分隔
+def mkString: String = mkString("")
+```
+
+使用示例如下:
+
+```scala
+scala> list.mkString
+res9: String = hadoopsparkstorm
+
+scala> list.mkString(",")
+res10: String = hadoop,spark,storm
+
+scala> list.mkString("{",",","}")
+res11: String = {hadoop,spark,storm}
+```
+
+### 5.7 iterator & toArray & copyToArray
+
+iterator 方法返回的是迭代器,这和其他语言的使用是一样的。
+
+```scala
+object ScalaApp extends App {
+
+ val list = List("hadoop", "spark", "storm")
+
+ val iterator: Iterator[String] = list.iterator
+
+ while (iterator.hasNext) {
+ println(iterator.next)
+ }
+
+}
+```
+
+toArray 和 toList 用于 List 和数组之间的互相转换。
+
+```scala
+scala> val array = list.toArray
+array: Array[String] = Array(hadoop, spark, storm)
+
+scala> array.toList
+res13: List[String] = List(hadoop, spark, storm)
+```
+
+copyToArray 将 List 中的元素拷贝到数组中指定位置。
+
+```scala
+object ScalaApp extends App {
+
+ val list = List("hadoop", "spark", "storm")
+ val array = Array("10", "20", "30")
+
+ list.copyToArray(array,1)
+
+ println(array.toBuffer)
+}
+
+// 输出 :ArrayBuffer(10, hadoop, spark)
+```
+
+## 六、列表的高级操作
+
+### 6.1 列表转换:map & flatMap & foreach
+
+map 与 Java 8 函数式编程中的 map 类似,都是对 List 中每一个元素执行指定操作。
+
+```scala
+scala> List(1,2,3).map(_+10)
+res15: List[Int] = List(11, 12, 13)
+```
+
+flatMap 与 map 类似,但如果 List 中的元素还是 List,则会对其进行 flatten 操作。
+
+```scala
+scala> list.map(_.toList)
+res16: List[List[Char]] = List(List(h, a, d, o, o, p), List(s, p, a, r, k), List(s, t, o, r, m))
+
+scala> list.flatMap(_.toList)
+res17: List[Char] = List(h, a, d, o, o, p, s, p, a, r, k, s, t, o, r, m)
+```
+
+foreach 要求右侧的操作是一个返回值为 Unit 的函数,你也可以简单理解为执行一段没有返回值代码。
+
+```scala
+scala> var sum = 0
+sum: Int = 0
+
+scala> List(1, 2, 3, 4, 5) foreach (sum += _)
+
+scala> sum
+res19: Int = 15
+```
+
+### 6.2 列表过滤:filter & partition & find & takeWhile & dropWhile & span
+
+filter 用于筛选满足条件元素,返回新的 List。
+
+```scala
+scala> List(1, 2, 3, 4, 5) filter (_ % 2 == 0)
+res20: List[Int] = List(2, 4)
+```
+
+partition 会按照筛选条件对元素进行分组,返回类型是 tuple(元组)。
+
+```scala
+scala> List(1, 2, 3, 4, 5) partition (_ % 2 == 0)
+res21: (List[Int], List[Int]) = (List(2, 4),List(1, 3, 5))
+```
+
+find 查找第一个满足条件的值,由于可能并不存在这样的值,所以返回类型是 `Option`,可以通过 `getOrElse` 在不存在满足条件值的情况下返回默认值。
+
+```scala
+scala> List(1, 2, 3, 4, 5) find (_ % 2 == 0)
+res22: Option[Int] = Some(2)
+
+val result: Option[Int] = List(1, 2, 3, 4, 5) find (_ % 2 == 0)
+result.getOrElse(10)
+```
+
+takeWhile 遍历元素,直到遇到第一个不符合条件的值则结束遍历,返回所有遍历到的值。
+
+```scala
+scala> List(1, 2, 3, -4, 5) takeWhile (_ > 0)
+res23: List[Int] = List(1, 2, 3)
+```
+
+dropWhile 遍历元素,直到遇到第一个不符合条件的值则结束遍历,返回所有未遍历到的值。
+
+```scala
+// 第一个值就不满足条件,所以返回列表中所有的值
+scala> List(1, 2, 3, -4, 5) dropWhile (_ < 0)
+res24: List[Int] = List(1, 2, 3, -4, 5)
+
+
+scala> List(1, 2, 3, -4, 5) dropWhile (_ < 3)
+res26: List[Int] = List(3, -4, 5)
+```
+
+span 遍历元素,直到遇到第一个不符合条件的值则结束遍历,将遍历到的值和未遍历到的值分别放入两个 List 中返回,返回类型是 tuple(元组)。
+
+```scala
+scala> List(1, 2, 3, -4, 5) span (_ > 0)
+res27: (List[Int], List[Int]) = (List(1, 2, 3),List(-4, 5))
+```
+
+
+
+### 6.3 列表检查:forall & exists
+
+forall 检查 List 中所有元素,如果所有元素都满足条件,则返回 true。
+
+```scala
+scala> List(1, 2, 3, -4, 5) forall ( _ > 0 )
+res28: Boolean = false
+```
+
+exists 检查 List 中的元素,如果某个元素已经满足条件,则返回 true。
+
+```scala
+scala> List(1, 2, 3, -4, 5) exists (_ > 0 )
+res29: Boolean = true
+```
+
+
+
+### 6.4 列表排序:sortWith
+
+sortWith 对 List 中所有元素按照指定规则进行排序,由于 List 是不可变的,所以排序返回一个新的 List。
+
+```scala
+scala> List(1, -3, 4, 2, 6) sortWith (_ < _)
+res30: List[Int] = List(-3, 1, 2, 4, 6)
+
+scala> val list = List( "hive","spark","azkaban","hadoop")
+list: List[String] = List(hive, spark, azkaban, hadoop)
+
+scala> list.sortWith(_.length>_.length)
+res33: List[String] = List(azkaban, hadoop, spark, hive)
+```
+
+
+
+## 七、List对象的方法
+
+上面介绍的所有方法都是 List 类上的方法,下面介绍的是 List 伴生对象中的方法。
+
+### 7.1 List.range
+
+List.range 可以产生指定的前闭后开区间内的值组成的 List,它有三个可选参数: start(开始值),end(结束值,不包含),step(步长)。
+
+```scala
+scala> List.range(1, 5)
+res34: List[Int] = List(1, 2, 3, 4)
+
+scala> List.range(1, 9, 2)
+res35: List[Int] = List(1, 3, 5, 7)
+
+scala> List.range(9, 1, -3)
+res36: List[Int] = List(9, 6, 3)
+```
+
+### 7.2 List.fill
+
+List.fill 使用指定值填充 List。
+
+```scala
+scala> List.fill(3)("hello")
+res37: List[String] = List(hello, hello, hello)
+
+scala> List.fill(2,3)("world")
+res38: List[List[String]] = List(List(world, world, world), List(world, world, world))
+```
+
+### 7.3 List.concat
+
+List.concat 用于拼接多个 List。
+
+```scala
+scala> List.concat(List('a', 'b'), List('c'))
+res39: List[Char] = List(a, b, c)
+
+scala> List.concat(List(), List('b'), List('c'))
+res40: List[Char] = List(b, c)
+
+scala> List.concat()
+res41: List[Nothing] = List()
+```
+
+
+
+## 八、处理多个List
+
+当多个 List 被放入同一个 tuple 中时候,可以通过 zipped 对多个 List 进行关联处理。
+
+```scala
+// 两个 List 对应位置的元素相乘
+scala> (List(10, 20), List(3, 4, 5)).zipped.map(_ * _)
+res42: List[Int] = List(30, 80)
+
+// 三个 List 的操作也是一样的
+scala> (List(10, 20), List(3, 4, 5), List(100, 200)).zipped.map(_ * _ + _)
+res43: List[Int] = List(130, 280)
+
+// 判断第一个 List 中元素的长度与第二个 List 中元素的值是否相等
+scala> (List("abc", "de"), List(3, 2)).zipped.forall(_.length == _)
+res44: Boolean = true
+```
+
+
+
+## 九、缓冲列表ListBuffer
+
+上面介绍的 List,由于其底层实现是链表,这意味着能快速访问 List 头部元素,但对尾部元素的访问则比较低效,这时候可以采用 `ListBuffer`,ListBuffer 提供了在常量时间内往头部和尾部追加元素。
+
+```scala
+import scala.collection.mutable.ListBuffer
+
+object ScalaApp extends App {
+
+ val buffer = new ListBuffer[Int]
+ // 1.在尾部追加元素
+ buffer += 1
+ buffer += 2
+ // 2.在头部追加元素
+ 3 +=: buffer
+ // 3. ListBuffer 转 List
+ val list: List[Int] = buffer.toList
+ println(list)
+}
+
+//输出:List(3, 1, 2)
+```
+
+
+
+## 十、集(Set)
+
+Set 是不重复元素的集合。分为可变 Set 和不可变 Set。
+
+### 10.1 可变Set
+
+```scala
+object ScalaApp extends App {
+
+ // 可变 Set
+ val mutableSet = new collection.mutable.HashSet[Int]
+
+ // 1.添加元素
+ mutableSet.add(1)
+ mutableSet.add(2)
+ mutableSet.add(3)
+ mutableSet.add(3)
+ mutableSet.add(4)
+
+ // 2.移除元素
+ mutableSet.remove(2)
+
+ // 3.调用 mkString 方法 输出 1,3,4
+ println(mutableSet.mkString(","))
+
+ // 4. 获取 Set 中最小元素
+ println(mutableSet.min)
+
+ // 5. 获取 Set 中最大元素
+ println(mutableSet.max)
+
+}
+```
+
+### 10.2 不可变Set
+
+不可变 Set 没有 add 方法,可以使用 `+` 添加元素,但是此时会返回一个新的不可变 Set,原来的 Set 不变。
+
+```scala
+object ScalaApp extends App {
+
+ // 不可变 Set
+ val immutableSet = new collection.immutable.HashSet[Int]
+
+ val ints: HashSet[Int] = immutableSet+1
+
+ println(ints)
+
+}
+
+// 输出 Set(1)
+```
+
+### 10.3 Set间操作
+
+多个 Set 之间可以进行求交集或者合集等操作。
+
+```scala
+object ScalaApp extends App {
+
+ // 声明有序 Set
+ val mutableSet = collection.mutable.SortedSet(1, 2, 3, 4, 5)
+ val immutableSet = collection.immutable.SortedSet(3, 4, 5, 6, 7)
+
+ // 两个 Set 的合集 输出:TreeSet(1, 2, 3, 4, 5, 6, 7)
+ println(mutableSet ++ immutableSet)
+
+ // 两个 Set 的交集 输出:TreeSet(3, 4, 5)
+ println(mutableSet intersect immutableSet)
+
+}
+```
+
+
+
+## 参考资料
+
+1. Martin Odersky . Scala 编程 (第 3 版)[M] . 电子工业出版社 . 2018-1-1
+2. 凯.S.霍斯特曼 . 快学 Scala(第 2 版)[M] . 电子工业出版社 . 2017-7
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\345\237\272\346\234\254\346\225\260\346\215\256\347\261\273\345\236\213\345\222\214\350\277\220\347\256\227\347\254\246.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\345\237\272\346\234\254\346\225\260\346\215\256\347\261\273\345\236\213\345\222\214\350\277\220\347\256\227\347\254\246.md"
new file mode 100644
index 0000000..a303e97
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\345\237\272\346\234\254\346\225\260\346\215\256\347\261\273\345\236\213\345\222\214\350\277\220\347\256\227\347\254\246.md"
@@ -0,0 +1,274 @@
+# Scala基本数据类型和运算符
+
+
+一、数据类型
+二、字面量
+三、运算符
+
+
+## 一、数据类型
+
+### 1.1 类型支持
+
+Scala 拥有下表所示的数据类型,其中 Byte、Short、Int、Long 和 Char 类型统称为整数类型,整数类型加上 Float 和 Double 统称为数值类型。Scala 数值类型的取值范围和 Java 对应类型的取值范围相同。
+
+| 数据类型 | 描述 |
+| -------- | ------------------------------------------------------------ |
+| Byte | 8 位有符号补码整数。数值区间为 -128 到 127 |
+| Short | 16 位有符号补码整数。数值区间为 -32768 到 32767 |
+| Int | 32 位有符号补码整数。数值区间为 -2147483648 到 2147483647 |
+| Long | 64 位有符号补码整数。数值区间为 -9223372036854775808 到 9223372036854775807 |
+| Float | 32 位, IEEE 754 标准的单精度浮点数 |
+| Double | 64 位 IEEE 754 标准的双精度浮点数 |
+| Char | 16 位无符号 Unicode 字符, 区间值为 U+0000 到 U+FFFF |
+| String | 字符序列 |
+| Boolean | true 或 false |
+| Unit | 表示无值,等同于 Java 中的 void。用作不返回任何结果的方法的结果类型。Unit 只有一个实例值,写成 ()。 |
+| Null | null 或空引用 |
+| Nothing | Nothing 类型在 Scala 的类层级的最低端;它是任何其他类型的子类型。 |
+| Any | Any 是所有其他类的超类 |
+| AnyRef | AnyRef 类是 Scala 里所有引用类 (reference class) 的基类 |
+
+### 1.2 定义变量
+
+Scala 的变量分为两种,val 和 var,其区别如下:
+
++ **val** : 类似于 Java 中的 final 变量,一旦初始化就不能被重新赋值;
++ **var** :类似于 Java 中的非 final 变量,在整个声明周期内 var 可以被重新赋值;
+
+```scala
+scala> val a=1
+a: Int = 1
+
+scala> a=2
+:8: error: reassignment to val // 不允许重新赋值
+
+scala> var b=1
+b: Int = 1
+
+scala> b=2
+b: Int = 2
+```
+
+### 1.3 类型推断
+
+在上面的演示中,并没有声明 a 是 Int 类型,但是程序还是把 a 当做 Int 类型,这就是 Scala 的类型推断。在大多数情况下,你都无需指明变量的类型,程序会自动进行推断。如果你想显式的声明类型,可以在变量后面指定,如下:
+
+```scala
+scala> val c:String="hello scala"
+c: String = hello scala
+```
+
+### 1.4 Scala解释器
+
+在 scala 命令行中,如果没有对输入的值指定赋值的变量,则输入的值默认会赋值给 `resX`(其中 X 是一个从 0 开始递增的整数),`res` 是 result 的缩写,这个变量可以在后面的语句中进行引用。
+
+```scala
+scala> 5
+res0: Int = 5
+
+scala> res0*6
+res1: Int = 30
+
+scala> println(res1)
+30
+```
+
+
+
+## 二、字面量
+
+Scala 和 Java 字面量在使用上很多相似,比如都使用 F 或 f 表示浮点型,都使用 L 或 l 表示 Long 类型。下文主要介绍两者差异部分。
+
+```scala
+scala> 1.2
+res0: Double = 1.2
+
+scala> 1.2f
+res1: Float = 1.2
+
+scala> 1.4F
+res2: Float = 1.4
+
+scala> 1
+res3: Int = 1
+
+scala> 1l
+res4: Long = 1
+
+scala> 1L
+res5: Long = 1
+```
+
+### 2.1 整数字面量
+
+Scala 支持 10 进制和 16 进制,但不支持八进制字面量和以 0 开头的整数字面量。
+
+```scala
+scala> 012
+:1: error: Decimal integer literals may not have a leading zero. (Octal syntax is obsolete.)
+```
+
+### 2.2 字符串字面量
+
+#### 1. 字符字面量
+
+字符字面量由一对单引号和中间的任意 Unicode 字符组成。你可以显式的给出原字符、也可以使用字符的 Unicode 码来表示,还可以包含特殊的转义字符。
+
+```scala
+scala> '\u0041'
+res0: Char = A
+
+scala> 'a'
+res1: Char = a
+
+scala> '\n'
+res2: Char =
+```
+
+#### 2. 字符串字面量
+
+字符串字面量由双引号包起来的字符组成。
+
+```scala
+scala> "hello world"
+res3: String = hello world
+```
+
+#### 3.原生字符串
+
+Scala 提供了 `""" ... """` 语法,通过三个双引号来表示原生字符串和多行字符串,使用该种方式,原生字符串中的特殊字符不会被转义。
+
+```scala
+scala> "hello \tool"
+res4: String = hello ool
+
+scala> """hello \tool"""
+res5: String = hello \tool
+
+scala> """hello
+ | world"""
+res6: String =
+hello
+world
+```
+
+### 2.3 符号字面量
+
+符号字面量写法为: `'标识符 ` ,这里 标识符可以是任何字母或数字的组合。符号字面量会被映射成 `scala.Symbol` 的实例,如:符号字面量 `'x ` 会被编译器翻译为 `scala.Symbol("x")`。符号字面量可选方法很少,只能通过 `.name` 获取其名称。
+
+注意:具有相同 `name` 的符号字面量一定指向同一个 Symbol 对象,不同 `name` 的符号字面量一定指向不同的 Symbol 对象。
+
+```scala
+scala> val sym = 'ID008
+sym: Symbol = 'ID008
+
+scala> sym.name
+res12: String = ID008
+```
+
+### 2.4 插值表达式
+
+Scala 支持插值表达式。
+
+```scala
+scala> val name="xiaoming"
+name: String = xiaoming
+
+scala> println(s"My name is $name,I'm ${2*9}.")
+My name is xiaoming,I'm 18.
+```
+
+## 三、运算符
+
+Scala 和其他语言一样,支持大多数的操作运算符:
+
+- 算术运算符(+,-,*,/,%)
+- 关系运算符(==,!=,>,<,>=,<=)
+- 逻辑运算符 (&&,||,!,&,|)
+- 位运算符 (~,&,|,^,<<,>>,>>>)
+- 赋值运算符 (=,+=,-=,*=,/=,%=,<<=,>>=,&=,^=,|=)
+
+以上操作符的基本使用与 Java 类似,下文主要介绍差异部分和注意事项。
+
+### 3.1 运算符即方法
+
+Scala 的面向对象比 Java 更加纯粹,在 Scala 中一切都是对象。所以对于 `1+2`,实际上是调用了 Int 类中名为 `+` 的方法,所以 1+2,也可以写成 `1.+(2)`。
+
+```scala
+scala> 1+2
+res14: Int = 3
+
+scala> 1.+(2)
+res15: Int = 3
+```
+
+Int 类中包含了多个重载的 `+` 方法,用于分别接收不同类型的参数。
+
+
+
+### 3.2 逻辑运算符
+
+和其他语言一样,在 Scala 中 `&&`,`||` 的执行是短路的,即如果左边的表达式能确定整个结果,右边的表达式就不会被执行,这满足大多数使用场景。但是如果你需要在无论什么情况下,都执行右边的表达式,则可以使用 `&` 或 `|` 代替。
+
+### 3.3 赋值运算符
+
+在 Scala 中没有 Java 中的 `++` 和 `--` 运算符,如果你想要实现类似的操作,只能使用 `+=1`,或者 `-=1`。
+
+```scala
+scala> var a=1
+a: Int = 1
+
+scala> a+=1
+
+scala> a
+res8: Int = 2
+
+scala> a-=1
+
+scala> a
+res10: Int = 1
+```
+
+### 3.4 运算符优先级
+
+操作符的优先级如下:优先级由上至下,逐级递减。
+
+
+
+在表格中某个字符的优先级越高,那么以这个字符打头的方法就拥有更高的优先级。如 `+` 的优先级大于 `<`,也就意味则 `+` 的优先级大于以 `<` 开头的 `<<`,所以 `2<<2+2` , 实际上等价于 `2<<(2+2)` :
+
+```scala
+scala> 2<<2+2
+res0: Int = 32
+
+scala> 2<<(2+2)
+res1: Int = 32
+```
+
+### 3.5 对象相等性
+
+如果想要判断两个对象是否相等,可以使用 `==` 和 `!=`,这两个操作符可以用于所有的对象,包括 null。
+
+```scala
+scala> 1==2
+res2: Boolean = false
+
+scala> List(1,2,3)==List(1,2,3)
+res3: Boolean = true
+
+scala> 1==1.0
+res4: Boolean = true
+
+scala> List(1,2,3)==null
+res5: Boolean = false
+
+scala> null==null
+res6: Boolean = true
+```
+
+
+
+## 参考资料
+
+1. Martin Odersky . Scala 编程 (第 3 版)[M] . 电子工业出版社 . 2018-1-1
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\346\225\260\347\273\204.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\346\225\260\347\273\204.md"
new file mode 100644
index 0000000..d7215a8
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\346\225\260\347\273\204.md"
@@ -0,0 +1,193 @@
+# Scala 数组相关操作
+
+
+一、定长数组
+二、变长数组
+三、数组遍历
+四、数组转换
+五、多维数组
+六、与Java互操作
+
+
+## 一、定长数组
+
+在 Scala 中,如果你需要一个长度不变的数组,可以使用 Array。但需要注意以下两点:
+
+- 在 Scala 中使用 `(index)` 而不是 `[index]` 来访问数组中的元素,因为访问元素,对于 Scala 来说是方法调用,`(index)` 相当于执行了 `.apply(index)` 方法。
+- Scala 中的数组与 Java 中的是等价的,`Array[Int]()` 在虚拟机层面就等价于 Java 的 `int[]`。
+
+```scala
+// 10 个整数的数组,所有元素初始化为 0
+scala> val nums=new Array[Int](10)
+nums: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+
+// 10 个元素的字符串数组,所有元素初始化为 null
+scala> val strings=new Array[String](10)
+strings: Array[String] = Array(null, null, null, null, null, null, null, null, null, null)
+
+// 使用指定值初始化,此时不需要 new 关键字
+scala> val a=Array("hello","scala")
+a: Array[String] = Array(hello, scala)
+
+// 使用 () 来访问元素
+scala> a(0)
+res3: String = hello
+```
+
+## 二、变长数组
+
+在 scala 中通过 ArrayBuffer 实现变长数组 (又称缓冲数组)。在构建 ArrayBuffer 时必须给出类型参数,但不必指定长度,因为 ArrayBuffer 会在需要的时候自动扩容和缩容。变长数组的构建方式及常用操作如下:
+
+```java
+import scala.collection.mutable.ArrayBuffer
+
+object ScalaApp {
+
+ // 相当于 Java 中的 main 方法
+ def main(args: Array[String]): Unit = {
+ // 1.声明变长数组 (缓冲数组)
+ val ab = new ArrayBuffer[Int]()
+ // 2.在末端增加元素
+ ab += 1
+ // 3.在末端添加多个元素
+ ab += (2, 3, 4)
+ // 4.可以使用 ++=追加任何集合
+ ab ++= Array(5, 6, 7)
+ // 5.缓冲数组可以直接打印查看
+ println(ab)
+ // 6.移除最后三个元素
+ ab.trimEnd(3)
+ // 7.在第 1 个元素之后插入多个新元素
+ ab.insert(1, 8, 9)
+ // 8.从第 2 个元素开始,移除 3 个元素,不指定第二个参数的话,默认值为 1
+ ab.remove(2, 3)
+ // 9.缓冲数组转定长数组
+ val abToA = ab.toArray
+ // 10. 定长数组打印为其 hashcode 值
+ println(abToA)
+ // 11. 定长数组转缓冲数组
+ val aToAb = abToA.toBuffer
+ }
+}
+```
+
+需要注意的是:使用 `+= ` 在末尾插入元素是一个高效的操作,其时间复杂度是 O(1)。而使用 `insert` 随机插入元素的时间复杂度是 O(n),因为在其插入位置之后的所有元素都要进行对应的后移,所以在 `ArrayBuffer` 中随机插入元素是一个低效的操作。
+
+## 三、数组遍历
+
+```scala
+object ScalaApp extends App {
+
+ val a = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+
+ // 1.方式一 相当于 Java 中的增强 for 循环
+ for (elem <- a) {
+ print(elem)
+ }
+
+ // 2.方式二
+ for (index <- 0 until a.length) {
+ print(a(index))
+ }
+
+ // 3.方式三, 是第二种方式的简写
+ for (index <- a.indices) {
+ print(a(index))
+ }
+
+ // 4.反向遍历
+ for (index <- a.indices.reverse) {
+ print(a(index))
+ }
+
+}
+```
+
+这里我们没有将代码写在 main 方法中,而是继承自 App.scala,这是 Scala 提供的一种简写方式,此时将代码写在类中,等价于写在 main 方法中,直接运行该类即可。
+
+
+
+## 四、数组转换
+
+数组转换是指由现有数组产生新的数组。假设当前拥有 a 数组,想把 a 中的偶数元素乘以 10 后产生一个新的数组,可以采用下面两种方式来实现:
+
+```scala
+object ScalaApp extends App {
+
+ val a = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+
+ // 1.方式一 yield 关键字
+ val ints1 = for (elem <- a if elem % 2 == 0) yield 10 * elem
+ for (elem <- ints1) {
+ println(elem)
+ }
+
+ // 2.方式二 采用函数式编程的方式,这和 Java 8 中的函数式编程是类似的,这里采用下划线标表示其中的每个元素
+ val ints2 = a.filter(_ % 2 == 0).map(_ * 10)
+ for (elem <- ints1) {
+ println(elem)
+ }
+}
+```
+
+
+
+## 五、多维数组
+
+和 Java 中一样,多维数组由单维数组组成。
+
+```scala
+object ScalaApp extends App {
+
+ val matrix = Array(Array(11, 12, 13, 14, 15, 16, 17, 18, 19, 20),
+ Array(21, 22, 23, 24, 25, 26, 27, 28, 29, 30),
+ Array(31, 32, 33, 34, 35, 36, 37, 38, 39, 40))
+
+
+ for (elem <- matrix) {
+
+ for (elem <- elem) {
+ print(elem + "-")
+ }
+ println()
+ }
+
+}
+
+打印输出如下:
+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-
+```
+
+
+
+## 六、与Java互操作
+
+由于 Scala 的数组是使用 Java 的数组来实现的,所以两者之间可以相互转换。
+
+```scala
+import java.util
+
+import scala.collection.mutable.ArrayBuffer
+import scala.collection.{JavaConverters, mutable}
+
+object ScalaApp extends App {
+
+ val element = ArrayBuffer("hadoop", "spark", "storm")
+ // Scala 转 Java
+ val javaList: util.List[String] = JavaConverters.bufferAsJavaList(element)
+ // Java 转 Scala
+ val scalaBuffer: mutable.Buffer[String] = JavaConverters.asScalaBuffer(javaList)
+ for (elem <- scalaBuffer) {
+ println(elem)
+ }
+}
+```
+
+
+
+## 参考资料
+
+1. Martin Odersky . Scala 编程 (第 3 版)[M] . 电子工业出版社 . 2018-1-1
+2. 凯.S.霍斯特曼 . 快学 Scala(第 2 版)[M] . 电子工业出版社 . 2017-7
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\346\230\240\345\260\204\345\222\214\345\205\203\347\273\204.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\346\230\240\345\260\204\345\222\214\345\205\203\347\273\204.md"
new file mode 100644
index 0000000..ec47e12
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\346\230\240\345\260\204\345\222\214\345\205\203\347\273\204.md"
@@ -0,0 +1,282 @@
+# Map & Tuple
+
+
+一、映射(Map)
+ 1.1 构造Map
+ 1.2 获取值
+ 1.3 新增/修改/删除值
+ 1.4 遍历Map
+ 1.5 yield关键字
+ 1.6 其他Map结构
+ 1.7 可选方法
+ 1.8 与Java互操作
+二、元组(Tuple)
+ 2.1 模式匹配
+ 2.2 zip方法
+
+
+
+
+## 一、映射(Map)
+
+### 1.1 构造Map
+
+```scala
+// 初始化一个空 map
+val scores01 = new HashMap[String, Int]
+
+// 从指定的值初始化 Map(方式一)
+val scores02 = Map("hadoop" -> 10, "spark" -> 20, "storm" -> 30)
+
+// 从指定的值初始化 Map(方式二)
+val scores03 = Map(("hadoop", 10), ("spark", 20), ("storm", 30))
+```
+
+采用上面方式得到的都是不可变 Map(immutable map),想要得到可变 Map(mutable map),则需要使用:
+
+```scala
+val scores04 = scala.collection.mutable.Map("hadoop" -> 10, "spark" -> 20, "storm" -> 30)
+```
+
+### 1.2 获取值
+
+```scala
+object ScalaApp extends App {
+
+ val scores = Map("hadoop" -> 10, "spark" -> 20, "storm" -> 30)
+
+ // 1.获取指定 key 对应的值
+ println(scores("hadoop"))
+
+ // 2. 如果对应的值不存在则使用默认值
+ println(scores.getOrElse("hadoop01", 100))
+}
+```
+
+### 1.3 新增/修改/删除值
+
+可变 Map 允许进行新增、修改、删除等操作。
+
+```scala
+object ScalaApp extends App {
+
+ val scores = scala.collection.mutable.Map("hadoop" -> 10, "spark" -> 20, "storm" -> 30)
+
+ // 1.如果 key 存在则更新
+ scores("hadoop") = 100
+
+ // 2.如果 key 不存在则新增
+ scores("flink") = 40
+
+ // 3.可以通过 += 来进行多个更新或新增操作
+ scores += ("spark" -> 200, "hive" -> 50)
+
+ // 4.可以通过 -= 来移除某个键和值
+ scores -= "storm"
+
+ for (elem <- scores) {println(elem)}
+}
+
+// 输出内容如下
+(spark,200)
+(hadoop,100)
+(flink,40)
+(hive,50)
+```
+
+不可变 Map 不允许进行新增、修改、删除等操作,但是允许由不可变 Map 产生新的 Map。
+
+```scala
+object ScalaApp extends App {
+
+ val scores = Map("hadoop" -> 10, "spark" -> 20, "storm" -> 30)
+
+ val newScores = scores + ("spark" -> 200, "hive" -> 50)
+
+ for (elem <- scores) {println(elem)}
+
+}
+
+// 输出内容如下
+(hadoop,10)
+(spark,200)
+(storm,30)
+(hive,50)
+```
+
+### 1.4 遍历Map
+
+```java
+object ScalaApp extends App {
+
+ val scores = Map("hadoop" -> 10, "spark" -> 20, "storm" -> 30)
+
+ // 1. 遍历键
+ for (key <- scores.keys) { println(key) }
+
+ // 2. 遍历值
+ for (value <- scores.values) { println(value) }
+
+ // 3. 遍历键值对
+ for ((key, value) <- scores) { println(key + ":" + value) }
+
+}
+```
+
+### 1.5 yield关键字
+
+可以使用 `yield` 关键字从现有 Map 产生新的 Map。
+
+```scala
+object ScalaApp extends App {
+
+ val scores = Map("hadoop" -> 10, "spark" -> 20, "storm" -> 30)
+
+ // 1.将 scores 中所有的值扩大 10 倍
+ val newScore = for ((key, value) <- scores) yield (key, value * 10)
+ for (elem <- newScore) { println(elem) }
+
+
+ // 2.将键和值互相调换
+ val reversalScore: Map[Int, String] = for ((key, value) <- scores) yield (value, key)
+ for (elem <- reversalScore) { println(elem) }
+
+}
+
+// 输出
+(hadoop,100)
+(spark,200)
+(storm,300)
+
+(10,hadoop)
+(20,spark)
+(30,storm)
+```
+
+### 1.6 其他Map结构
+
+在使用 Map 时候,如果不指定,默认使用的是 HashMap,如果想要使用 `TreeMap` 或者 `LinkedHashMap`,则需要显式的指定。
+
+```scala
+object ScalaApp extends App {
+
+ // 1.使用 TreeMap,按照键的字典序进行排序
+ val scores01 = scala.collection.mutable.TreeMap("B" -> 20, "A" -> 10, "C" -> 30)
+ for (elem <- scores01) {println(elem)}
+
+ // 2.使用 LinkedHashMap,按照键值对的插入顺序进行排序
+ val scores02 = scala.collection.mutable.LinkedHashMap("B" -> 20, "A" -> 10, "C" -> 30)
+ for (elem <- scores02) {println(elem)}
+}
+
+// 输出
+(A,10)
+(B,20)
+(C,30)
+
+(B,20)
+(A,10)
+(C,30)
+```
+
+### 1.7 可选方法
+
+```scala
+object ScalaApp extends App {
+
+ val scores = scala.collection.mutable.TreeMap("B" -> 20, "A" -> 10, "C" -> 30)
+
+ // 1. 获取长度
+ println(scores.size)
+
+ // 2. 判断是否为空
+ println(scores.isEmpty)
+
+ // 3. 判断是否包含特定的 key
+ println(scores.contains("A"))
+
+}
+```
+
+### 1.8 与Java互操作
+
+```scala
+import java.util
+import scala.collection.{JavaConverters, mutable}
+
+object ScalaApp extends App {
+
+ val scores = Map("hadoop" -> 10, "spark" -> 20, "storm" -> 30)
+
+ // scala map 转 java map
+ val javaMap: util.Map[String, Int] = JavaConverters.mapAsJavaMap(scores)
+
+ // java map 转 scala map
+ val scalaMap: mutable.Map[String, Int] = JavaConverters.mapAsScalaMap(javaMap)
+
+ for (elem <- scalaMap) {println(elem)}
+}
+```
+
+
+
+## 二、元组(Tuple)
+
+元组与数组类似,但是数组中所有的元素必须是同一种类型,而元组则可以包含不同类型的元素。
+
+```scala
+scala> val tuple=(1,3.24f,"scala")
+tuple: (Int, Float, String) = (1,3.24,scala)
+```
+
+### 2.1 模式匹配
+
+可以通过模式匹配来获取元组中的值并赋予对应的变量:
+
+```scala
+scala> val (a,b,c)=tuple
+a: Int = 1
+b: Float = 3.24
+c: String = scala
+```
+
+如果某些位置不需要赋值,则可以使用下划线代替:
+
+```scala
+scala> val (a,_,_)=tuple
+a: Int = 1
+```
+
+### 2.2 zip方法
+
+```scala
+object ScalaApp extends App {
+
+ val array01 = Array("hadoop", "spark", "storm")
+ val array02 = Array(10, 20, 30)
+
+ // 1.zip 方法得到的是多个 tuple 组成的数组
+ val tuples: Array[(String, Int)] = array01.zip(array02)
+ // 2.也可以在 zip 后调用 toMap 方法转换为 Map
+ val map: Map[String, Int] = array01.zip(array02).toMap
+
+ for (elem <- tuples) { println(elem) }
+ for (elem <- map) {println(elem)}
+}
+
+// 输出
+(hadoop,10)
+(spark,20)
+(storm,30)
+
+(hadoop,10)
+(spark,20)
+(storm,30)
+```
+
+
+
+## 参考资料
+
+1. Martin Odersky . Scala 编程 (第 3 版)[M] . 电子工业出版社 . 2018-1-1
+2. 凯.S.霍斯特曼 . 快学 Scala(第 2 版)[M] . 电子工业出版社 . 2017-7
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\346\250\241\345\274\217\345\214\271\351\205\215.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\346\250\241\345\274\217\345\214\271\351\205\215.md"
new file mode 100644
index 0000000..7c65267
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\346\250\241\345\274\217\345\214\271\351\205\215.md"
@@ -0,0 +1,172 @@
+# Scala模式匹配
+
+
+一、模式匹配
+ 1.1 更好的swith
+ 1.2 用作类型检查
+ 1.3 匹配数据结构
+ 1.4 提取器
+二、样例类
+ 2.1 样例类
+ 2.3 用于模式匹配
+
+
+## 一、模式匹配
+
+Scala 支持模式匹配机制,可以代替 swith 语句、执行类型检查、以及支持析构表达式等。
+
+### 1.1 更好的swith
+
+Scala 不支持 swith,可以使用模式匹配 `match...case` 语法代替。但是 match 语句与 Java 中的 switch 有以下三点不同:
+
+- Scala 中的 case 语句支持任何类型;而 Java 中 case 语句仅支持整型、枚举和字符串常量;
+- Scala 中每个分支语句后面不需要写 break,因为在 case 语句中 break 是隐含的,默认就有;
+- 在 Scala 中 match 语句是有返回值的,而 Java 中 switch 语句是没有返回值的。如下:
+
+```scala
+object ScalaApp extends App {
+
+ def matchTest(x: Int) = x match {
+ case 1 => "one"
+ case 2 => "two"
+ case _ if x > 9 && x < 100 => "两位数" //支持条件表达式 这被称为模式守卫
+ case _ => "other"
+ }
+
+ println(matchTest(1)) //输出 one
+ println(matchTest(10)) //输出 两位数
+ println(matchTest(200)) //输出 other
+}
+```
+
+### 1.2 用作类型检查
+
+```scala
+object ScalaApp extends App {
+
+ def matchTest[T](x: T) = x match {
+ case x: Int => "数值型"
+ case x: String => "字符型"
+ case x: Float => "浮点型"
+ case _ => "other"
+ }
+
+ println(matchTest(1)) //输出 数值型
+ println(matchTest(10.3f)) //输出 浮点型
+ println(matchTest("str")) //输出 字符型
+ println(matchTest(2.1)) //输出 other
+}
+```
+
+### 1.3 匹配数据结构
+
+匹配元组示例:
+
+```scala
+object ScalaApp extends App {
+
+ def matchTest(x: Any) = x match {
+ case (0, _, _) => "匹配第一个元素为 0 的元组"
+ case (a, b, c) => println(a + "~" + b + "~" + c)
+ case _ => "other"
+ }
+
+ println(matchTest((0, 1, 2))) // 输出: 匹配第一个元素为 0 的元组
+ matchTest((1, 2, 3)) // 输出: 1~2~3
+ println(matchTest(Array(10, 11, 12, 14))) // 输出: other
+}
+```
+
+匹配数组示例:
+
+```scala
+object ScalaApp extends App {
+
+ def matchTest[T](x: Array[T]) = x match {
+ case Array(0) => "匹配只有一个元素 0 的数组"
+ case Array(a, b) => println(a + "~" + b)
+ case Array(10, _*) => "第一个元素为 10 的数组"
+ case _ => "other"
+ }
+
+ println(matchTest(Array(0))) // 输出: 匹配只有一个元素 0 的数组
+ matchTest(Array(1, 2)) // 输出: 1~2
+ println(matchTest(Array(10, 11, 12))) // 输出: 第一个元素为 10 的数组
+ println(matchTest(Array(3, 2, 1))) // 输出: other
+}
+```
+
+### 1.4 提取器
+
+数组、列表和元组能使用模式匹配,都是依靠提取器 (extractor) 机制,它们伴生对象中定义了 `unapply` 或 `unapplySeq` 方法:
+
++ **unapply**:用于提取固定数量的对象;
++ **unapplySeq**:用于提取一个序列;
+
+这里以数组为例,`Array.scala` 定义了 `unapplySeq` 方法:
+
+```scala
+def unapplySeq[T](x : scala.Array[T]) : scala.Option[scala.IndexedSeq[T]] = { /* compiled code */ }
+```
+
+`unapplySeq` 返回一个序列,包含数组中的所有值,这样在模式匹配时,才能知道对应位置上的值。
+
+
+
+## 二、样例类
+
+### 2.1 样例类
+
+样例类是一种的特殊的类,它们被经过优化以用于模式匹配,样例类的声明比较简单,只需要在 `class` 前面加上关键字 `case`。下面给出一个样例类及其用于模式匹配的示例:
+
+```scala
+//声明一个抽象类
+abstract class Person{}
+```
+
+```scala
+// 样例类 Employee
+case class Employee(name: String, age: Int, salary: Double) extends Person {}
+```
+
+```scala
+// 样例类 Student
+case class Student(name: String, age: Int) extends Person {}
+```
+
+当你声明样例类后,编译器自动进行以下配置:
+
+- 构造器中每个参数都默认为 `val`;
+- 自动地生成 `equals, hashCode, toString, copy` 等方法;
+- 伴生对象中自动生成 `apply` 方法,使得可以不用 new 关键字就能构造出相应的对象;
+- 伴生对象中自动生成 `unapply` 方法,以支持模式匹配。
+
+除了上面的特征外,样例类和其他类相同,可以任意添加方法和字段,扩展它们。
+
+### 2.3 用于模式匹配
+
+样例的伴生对象中自动生成 `unapply` 方法,所以样例类可以支持模式匹配,使用如下:
+
+```scala
+object ScalaApp extends App {
+
+ def matchTest(person: Person) = person match {
+ case Student(name, _) => "student:" + name
+ case Employee(_, _, salary) => "employee salary:" + salary
+ case _ => "other"
+ }
+
+ println(matchTest(Student("heibai", 12))) //输出: student:heibai
+ println(matchTest(Employee("ying", 22, 999999))) //输出: employee salary:999999.0
+}
+```
+
+
+
+
+
+## 参考资料
+
+1. Martin Odersky . Scala 编程 (第 3 版)[M] . 电子工业出版社 . 2018-1-1
+2. 凯.S.霍斯特曼 . 快学 Scala(第 2 版)[M] . 电子工业出版社 . 2017-7
+
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\346\265\201\347\250\213\346\216\247\345\210\266\350\257\255\345\217\245.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\346\265\201\347\250\213\346\216\247\345\210\266\350\257\255\345\217\245.md"
new file mode 100644
index 0000000..8b1c219
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\346\265\201\347\250\213\346\216\247\345\210\266\350\257\255\345\217\245.md"
@@ -0,0 +1,211 @@
+# 流程控制语句
+
+
+一、条件表达式if
+二、块表达式
+三、循环表达式while
+四、循环表达式for
+五、异常处理try
+六、条件选择表达式match
+七、没有break和continue
+八、输入与输出
+
+
+## 一、条件表达式if
+
+Scala 中的 if/else 语法结构与 Java 中的一样,唯一不同的是,Scala 中的 if 表达式是有返回值的。
+
+```scala
+object ScalaApp extends App {
+
+ val x = "scala"
+ val result = if (x.length == 5) "true" else "false"
+ print(result)
+
+}
+```
+
+在 Java 中,每行语句都需要使用 `;` 表示结束,但是在 Scala 中并不需要。除非你在单行语句中写了多行代码。
+
+
+
+## 二、块表达式
+
+在 Scala 中,可以使用 `{}` 块包含一系列表达式,块中最后一个表达式的值就是块的值。
+
+```scala
+object ScalaApp extends App {
+
+ val result = {
+ val a = 1 + 1; val b = 2 + 2; a + b
+ }
+ print(result)
+}
+
+// 输出: 6
+```
+
+如果块中的最后一个表达式没有返回值,则块的返回值是 Unit 类型。
+
+```scala
+scala> val result ={ val a = 1 + 1; val b = 2 + 2 }
+result: Unit = ()
+```
+
+
+
+## 三、循环表达式while
+
+Scala 和大多数语言一样,支持 `while` 和 `do ... while` 表达式。
+
+```scala
+object ScalaApp extends App {
+
+ var n = 0
+
+ while (n < 10) {
+ n += 1
+ println(n)
+ }
+
+ // 循环至少要执行一次
+ do {
+ println(n)
+ } while (n > 10)
+}
+```
+
+
+
+## 四、循环表达式for
+
+for 循环的基本使用如下:
+
+```scala
+object ScalaApp extends App {
+
+ // 1.基本使用 输出[1,9)
+ for (n <- 1 until 10) {print(n)}
+
+ // 2.使用多个表达式生成器 输出: 11 12 13 21 22 23 31 32 33
+ for (i <- 1 to 3; j <- 1 to 3) print(f"${10 * i + j}%3d")
+
+ // 3.使用带条件的表达式生成器 输出: 12 13 21 23 31 32
+ for (i <- 1 to 3; j <- 1 to 3 if i != j) print(f"${10 * i + j}%3d")
+
+}
+```
+
+除了基本使用外,还可以使用 `yield` 关键字从 for 循环中产生 Vector,这称为 for 推导式。
+
+```scala
+scala> for (i <- 1 to 10) yield i * 6
+res1: scala.collection.immutable.IndexedSeq[Int] = Vector(6, 12, 18, 24, 30, 36, 42, 48, 54, 60)
+```
+
+
+
+## 五、异常处理try
+
+和 Java 中一样,支持 `try...catch...finally` 语句。
+
+```scala
+import java.io.{FileNotFoundException, FileReader}
+
+object ScalaApp extends App {
+
+ try {
+ val reader = new FileReader("wordCount.txt")
+ } catch {
+ case ex: FileNotFoundException =>
+ ex.printStackTrace()
+ println("没有找到对应的文件!")
+ } finally {
+ println("finally 语句一定会被执行!")
+ }
+}
+```
+
+这里需要注意的是因为 finally 语句一定会被执行,所以不要在该语句中返回值,否则返回值会被作为整个 try 语句的返回值,如下:
+
+```scala
+scala> def g():Int = try return 1 finally return 2
+g: ()Int
+
+// 方法 g() 总会返回 2
+scala> g()
+res3: Int = 2
+```
+
+
+
+## 六、条件选择表达式match
+
+match 类似于 java 中的 switch 语句。
+
+```scala
+object ScalaApp extends App {
+
+ val elements = Array("A", "B", "C", "D", "E")
+
+ for (elem <- elements) {
+ elem match {
+ case "A" => println(10)
+ case "B" => println(20)
+ case "C" => println(30)
+ case _ => println(50)
+ }
+ }
+}
+
+```
+
+但是与 Java 中的 switch 有以下三点不同:
+
++ Scala 中的 case 语句支持任何类型;而 Java 中 case 语句仅支持整型、枚举和字符串常量;
++ Scala 中每个分支语句后面不需要写 break,因为在 case 语句中 break 是隐含的,默认就有;
++ 在 Scala 中 match 语句是有返回值的,而 Java 中 switch 语句是没有返回值的。如下:
+
+```scala
+object ScalaApp extends App {
+
+ val elements = Array("A", "B", "C", "D", "E")
+
+ for (elem <- elements) {
+ val score = elem match {
+ case "A" => 10
+ case "B" => 20
+ case "C" => 30
+ case _ => 50
+ }
+ print(elem + ":" + score + ";")
+ }
+}
+// 输出: A:10;B:20;C:30;D:50;E:50;
+```
+
+
+
+## 七、没有break和continue
+
+额外注意一下:Scala 中并不支持 Java 中的 break 和 continue 关键字。
+
+
+
+## 八、输入与输出
+
+在 Scala 中可以使用 print、println、printf 打印输出,这与 Java 中是一样的。如果需要从控制台中获取输入,则可以使用 `StdIn` 中定义的各种方法。
+
+```scala
+val name = StdIn.readLine("Your name: ")
+print("Your age: ")
+val age = StdIn.readInt()
+println(s"Hello, ${name}! Next year, you will be ${age + 1}.")
+```
+
+
+
+## 参考资料
+
+1. Martin Odersky . Scala 编程 (第 3 版)[M] . 电子工业出版社 . 2018-1-1
+2. 凯.S.霍斯特曼 . 快学 Scala(第 2 版)[M] . 电子工业出版社 . 2017-7
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\347\256\200\344\273\213\345\217\212\345\274\200\345\217\221\347\216\257\345\242\203\351\205\215\347\275\256.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\347\256\200\344\273\213\345\217\212\345\274\200\345\217\221\347\216\257\345\242\203\351\205\215\347\275\256.md"
new file mode 100644
index 0000000..003beda
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\347\256\200\344\273\213\345\217\212\345\274\200\345\217\221\347\216\257\345\242\203\351\205\215\347\275\256.md"
@@ -0,0 +1,133 @@
+# Scala简介及开发环境配置
+
+
+一、Scala简介
+二、配置IDEA开发环境
+
+
+
+## 一、Scala简介
+
+### 1.1 概念
+
+Scala 全称为 Scalable Language,即“可伸缩的语言”,之所以这样命名,是因为它的设计目标是希望伴随着用户的需求一起成长。Scala 是一门综合了**面向对象**和**函数式编程概念**的**静态类型**的编程语言,它运行在标准的 Java 平台上,可以与所有的 Java 类库无缝协作。
+
+
+
+### 1.2 特点
+
+#### 1. Scala是面向对象的
+
+Scala 是一种面向对象的语言,每个值都是对象,每个方法都是调用。举例来说,如果你执行 `1+2`,则对于 Scala 而言,实际是在调用 Int 类里定义的名为 `+` 的方法。
+
+#### 2. Scala是函数式的
+
+Scala 不只是一门纯的面对对象的语言,它也是功能完整的函数式编程语言。函数式编程以两大核心理念为指导:
+
++ 函数是一等公民;
++ 程序中的操作应该将输入值映射成输出值,而不是当场修改数据。即方法不应该有副作用。
+
+
+
+### 1.3 Scala的优点
+
+#### 1. 与Java的兼容
+
+Scala 可以与 Java 无缝对接,其在执行时会被编译成 JVM 字节码,这使得其性能与 Java 相当。Scala 可以直接调用 Java 中的方法、访问 Java 中的字段、继承 Java 类、实现 Java 接口。Scala 重度复用并包装了原生的 Java 类型,并支持隐式转换。
+
+#### 2. 精简的语法
+
+Scala 的程序通常比较简洁,相比 Java 而言,代码行数会大大减少,这使得程序员对代码的阅读和理解更快,缺陷也更少。
+
+#### 3. 高级语言的特性
+
+Scala 具有高级语言的特定,对代码进行了高级别的抽象,能够让你更好地控制程序的复杂度,保证开发的效率。
+
+#### 4. 静态类型
+
+Scala 拥有非常先进的静态类型系统,Scala 不仅拥有与 Java 类似的允许嵌套类的类型系统,还支持使用泛型对类型进行参数化,用交集(intersection)来组合类型,以及使用抽象类型来进行隐藏类型的细节。通过这些特性,可以更快地设计出安全易用的程序和接口。
+
+
+
+
+
+## 二、配置IDEA开发环境
+
+### 2.1 前置条件
+
+Scala 的运行依赖于 JDK,Scala 2.12.x 需要 JDK 1.8+。
+
+### 2.2 安装Scala插件
+
+IDEA 默认不支持 Scala 语言的开发,需要通过插件进行扩展。打开 IDEA,依次点击 **File** => **settings**=> **plugins** 选项卡,搜索 Scala 插件 (如下图)。找到插件后进行安装,并重启 IDEA 使得安装生效。
+
+
+
+
+
+### 2.3 创建Scala项目
+
+在 IDEA 中依次点击 **File** => **New** => **Project** 选项卡,然后选择创建 `Scala—IDEA` 工程:
+
+
+
+
+
+### 2.4 下载Scala SDK
+
+#### 1. 方式一
+
+此时看到 `Scala SDK` 为空,依次点击 `Create` => `Download` ,选择所需的版本后,点击 `OK` 按钮进行下载,下载完成点击 `Finish` 进入工程。
+
+
+
+
+
+#### 2. 方式二
+
+方式一是 Scala 官方安装指南里使用的方式,但下载速度通常比较慢,且这种安装下并没有直接提供 Scala 命令行工具。所以个人推荐到官网下载安装包进行安装,下载地址:https://www.scala-lang.org/download/
+
+这里我的系统是 Windows,下载 msi 版本的安装包后,一直点击下一步进行安装,安装完成后会自动配置好环境变量。
+
+
+
+
+
+由于安装时已经自动配置好环境变量,所以 IDEA 会自动选择对应版本的 SDK。
+
+
+
+
+
+### 2.5 创建Hello World
+
+在工程 `src` 目录上右击 **New** => **Scala class** 创建 `Hello.scala`。输入代码如下,完成后点击运行按钮,成功运行则代表搭建成功。
+
+
+
+
+
+
+
+### 2.6 切换Scala版本
+
+在日常的开发中,由于对应软件(如 Spark)的版本切换,可能导致需要切换 Scala 的版本,则可以在 `Project Structures` 中的 `Global Libraries` 选项卡中进行切换。
+
+
+
+
+
+
+
+### 2.7 使用scala命令行
+
+采用 `msi` 方式安装,程序会自动配置好环境变量。此时可以直接使用命令行工具:
+
+
+
+
+
+## 参考资料
+
+1. Martin Odersky(著),高宇翔 (译) . Scala 编程 (第 3 版)[M] . 电子工业出版社 . 2018-1-1
+2. https://www.scala-lang.org/download/
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\347\261\273\345\222\214\345\257\271\350\261\241.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\347\261\273\345\222\214\345\257\271\350\261\241.md"
new file mode 100644
index 0000000..881ebe2
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\347\261\273\345\222\214\345\257\271\350\261\241.md"
@@ -0,0 +1,412 @@
+# 类和对象
+
+
+一、初识类和对象
+二、类
+ 2.1 成员变量可见性
+ 2.2 getter和setter属性
+ 2.3 @BeanProperty
+ 2.4 主构造器
+ 2.5 辅助构造器
+ 2.6 方法传参不可变
+三、对象
+ 3.1 工具类&单例&全局静态常量&拓展特质
+ 3.2 伴生对象
+ 3.3 实现枚举类
+
+
+## 一、初识类和对象
+
+Scala 的类与 Java 的类具有非常多的相似性,示例如下:
+
+```scala
+// 1. 在 scala 中,类不需要用 public 声明,所有的类都具有公共的可见性
+class Person {
+
+ // 2. 声明私有变量,用 var 修饰的变量默认拥有 getter/setter 属性
+ private var age = 0
+
+ // 3.如果声明的变量不需要进行初始赋值,此时 Scala 就无法进行类型推断,所以需要显式指明类型
+ private var name: String = _
+
+
+ // 4. 定义方法,应指明传参类型。返回值类型不是必须的,Scala 可以自动推断出来,但是为了方便调用者,建议指明
+ def growUp(step: Int): Unit = {
+ age += step
+ }
+
+ // 5.对于改值器方法 (即改变对象状态的方法),即使不需要传入参数,也建议在声明中包含 ()
+ def growUpFix(): Unit = {
+ age += 10
+ }
+
+ // 6.对于取值器方法 (即不会改变对象状态的方法),不必在声明中包含 ()
+ def currentAge: Int = {
+ age
+ }
+
+ /**
+ * 7.不建议使用 return 关键字,默认方法中最后一行代码的计算结果为返回值
+ * 如果方法很简短,甚至可以写在同一行中
+ */
+ def getName: String = name
+
+}
+
+
+// 伴生对象
+object Person {
+
+ def main(args: Array[String]): Unit = {
+ // 8.创建类的实例
+ val counter = new Person()
+ // 9.用 var 修饰的变量默认拥有 getter/setter 属性,可以直接对其进行赋值
+ counter.age = 12
+ counter.growUp(8)
+ counter.growUpFix()
+ // 10.用 var 修饰的变量默认拥有 getter/setter 属性,可以直接对其进行取值,输出: 30
+ println(counter.age)
+ // 输出: 30
+ println(counter.currentAge)
+ // 输出: null
+ println(counter.getName)
+ }
+
+}
+```
+
+
+
+## 二、类
+
+### 2.1 成员变量可见性
+
+Scala 中成员变量的可见性默认都是 public,如果想要保证其不被外部干扰,可以声明为 private,并通过 getter 和 setter 方法进行访问。
+
+### 2.2 getter和setter属性
+
+getter 和 setter 属性与声明变量时使用的关键字有关:
+
++ 使用 var 关键字:变量同时拥有 getter 和 setter 属性;
++ 使用 val 关键字:变量只拥有 getter 属性;
++ 使用 private[this]:变量既没有 getter 属性、也没有 setter 属性,只能通过内部的方法访问;
+
+需要特别说明的是:假设变量名为 age,则其对应的 get 和 set 的方法名分别叫做 ` age` 和 `age_=`。
+
+```scala
+class Person {
+
+ private val name = "heibaiying"
+ private var age = 12
+ private[this] var birthday = "2019-08-08"
+ // birthday 只能被内部方法所访问
+ def getBirthday: String = birthday
+}
+
+object Person {
+ def main(args: Array[String]): Unit = {
+ val person = new Person
+ person.age = 30
+ println(person.name)
+ println(person.age)
+ println(person.getBirthday)
+ }
+}
+```
+
+> 解释说明:
+>
+> 示例代码中 `person.age=30` 在执行时内部实际是调用了方法 `person.age_=(30) `,而 `person.age` 内部执行时实际是调用了 `person.age()` 方法。想要证明这一点,可以对代码进行反编译。同时为了说明成员变量可见性的问题,我们对下面这段代码进行反编译:
+>
+> ```scala
+> class Person {
+> var name = ""
+> private var age = ""
+> }
+> ```
+>
+> 依次执行下面编译命令:
+>
+> ```shell
+> > scalac Person.scala
+> > javap -private Person
+> ```
+>
+> 编译结果如下,从编译结果可以看到实际的 get 和 set 的方法名 (因为 JVM 不允许在方法名中出现=,所以它被翻译成$eq),同时也验证了成员变量默认的可见性为 public。
+>
+> ```java
+> Compiled from "Person.scala"
+> public class Person {
+> private java.lang.String name;
+> private java.lang.String age;
+>
+> public java.lang.String name();
+> public void name_$eq(java.lang.String);
+>
+> private java.lang.String age();
+> private void age_$eq(java.lang.String);
+>
+> public Person();
+> }
+> ```
+
+### 2.3 @BeanProperty
+
+在上面的例子中可以看到我们是使用 `.` 来对成员变量进行访问的,如果想要额外生成和 Java 中一样的 getXXX 和 setXXX 方法,则需要使用@BeanProperty 进行注解。
+
+```scala
+class Person {
+ @BeanProperty var name = ""
+}
+
+object Person {
+ def main(args: Array[String]): Unit = {
+ val person = new Person
+ person.setName("heibaiying")
+ println(person.getName)
+ }
+}
+```
+
+
+
+### 2.4 主构造器
+
+和 Java 不同的是,Scala 类的主构造器直接写在类名后面,但注意以下两点:
+
++ 主构造器传入的参数默认就是 val 类型的,即不可变,你没有办法在内部改变传参;
++ 写在主构造器中的代码块会在类初始化的时候被执行,功能类似于 Java 的静态代码块 `static{}`
+
+```scala
+class Person(val name: String, val age: Int) {
+
+ println("功能类似于 Java 的静态代码块 static{}")
+
+ def getDetail: String = {
+ //name="heibai" 无法通过编译
+ name + ":" + age
+ }
+}
+
+object Person {
+ def main(args: Array[String]): Unit = {
+ val person = new Person("heibaiying", 20)
+ println(person.getDetail)
+ }
+}
+
+输出:
+功能类似于 Java 的静态代码块 static{}
+heibaiying:20
+```
+
+
+
+### 2.5 辅助构造器
+
+辅助构造器有两点硬性要求:
+
++ 辅助构造器的名称必须为 this;
++ 每个辅助构造器必须以主构造器或其他的辅助构造器的调用开始。
+
+```scala
+class Person(val name: String, val age: Int) {
+
+ private var birthday = ""
+
+ // 1.辅助构造器的名称必须为 this
+ def this(name: String, age: Int, birthday: String) {
+ // 2.每个辅助构造器必须以主构造器或其他的辅助构造器的调用开始
+ this(name, age)
+ this.birthday = birthday
+ }
+
+ // 3.重写 toString 方法
+ override def toString: String = name + ":" + age + ":" + birthday
+}
+
+object Person {
+ def main(args: Array[String]): Unit = {
+ println(new Person("heibaiying", 20, "2019-02-21"))
+ }
+}
+```
+
+
+
+### 2.6 方法传参不可变
+
+在 Scala 中,方法传参默认是 val 类型,即不可变,这意味着你在方法体内部不能改变传入的参数。这和 Scala 的设计理念有关,Scala 遵循函数式编程理念,强调方法不应该有副作用。
+
+```scala
+class Person() {
+
+ def low(word: String): String = {
+ word="word" // 编译无法通过
+ word.toLowerCase
+ }
+}
+```
+
+
+
+## 三、对象
+
+Scala 中的 object(对象) 主要有以下几个作用:
+
++ 因为 object 中的变量和方法都是静态的,所以可以用于存放工具类;
++ 可以作为单例对象的容器;
++ 可以作为类的伴生对象;
++ 可以拓展类或特质;
++ 可以拓展 Enumeration 来实现枚举。
+
+### 3.1 工具类&单例&全局静态常量&拓展特质
+
+这里我们创建一个对象 `Utils`,代码如下:
+
+```scala
+object Utils {
+
+ /*
+ *1. 相当于 Java 中的静态代码块 static,会在对象初始化时候被执行
+ * 这种方式实现的单例模式是饿汉式单例,即无论你的单例对象是否被用到,
+ * 都在一开始被初始化完成
+ */
+ val person = new Person
+
+ // 2. 全局固定常量 等价于 Java 的 public static final
+ val CONSTANT = "固定常量"
+
+ // 3. 全局静态方法
+ def low(word: String): String = {
+ word.toLowerCase
+ }
+}
+```
+
+其中 Person 类代码如下:
+
+```scala
+class Person() {
+ println("Person 默认构造器被调用")
+}
+```
+
+新建测试类:
+
+```scala
+// 1.ScalaApp 对象扩展自 trait App
+object ScalaApp extends App {
+
+ // 2.验证单例
+ println(Utils.person == Utils.person)
+
+ // 3.获取全局常量
+ println(Utils.CONSTANT)
+
+ // 4.调用工具类
+ println(Utils.low("ABCDEFG"))
+
+}
+
+// 输出如下:
+Person 默认构造器被调用
+true
+固定常量
+abcdefg
+```
+
+### 3.2 伴生对象
+
+在 Java 中,你通常会用到既有实例方法又有静态方法的类,在 Scala 中,可以通过类和与类同名的伴生对象来实现。类和伴生对象必须存在与同一个文件中。
+
+```scala
+class Person() {
+
+ private val name = "HEIBAIYING"
+
+ def getName: String = {
+ // 调用伴生对象的方法和属性
+ Person.toLow(Person.PREFIX + name)
+ }
+}
+
+// 伴生对象
+object Person {
+
+ val PREFIX = "prefix-"
+
+ def toLow(word: String): String = {
+ word.toLowerCase
+ }
+
+ def main(args: Array[String]): Unit = {
+ val person = new Person
+ // 输出 prefix-heibaiying
+ println(person.getName)
+ }
+
+}
+```
+
+
+
+### 3.3 实现枚举类
+
+Scala 中没有直接提供枚举类,需要通过扩展 `Enumeration`,并调用其中的 Value 方法对所有枚举值进行初始化来实现。
+
+```scala
+object Color extends Enumeration {
+
+ // 1.类型别名,建议声明,在 import 时有用
+ type Color = Value
+
+ // 2.调用 Value 方法
+ val GREEN = Value
+ // 3.只传入 id
+ val RED = Value(3)
+ // 4.只传入值
+ val BULE = Value("blue")
+ // 5.传入 id 和值
+ val YELLOW = Value(5, "yellow")
+ // 6. 不传入 id 时,id 为上一个声明变量的 id+1,值默认和变量名相同
+ val PINK = Value
+
+}
+```
+
+使用枚举类:
+
+```scala
+// 1.使用类型别名导入枚举类
+import com.heibaiying.Color.Color
+
+object ScalaApp extends App {
+
+ // 2.使用枚举类型,这种情况下需要导入枚举类
+ def printColor(color: Color): Unit = {
+ println(color.toString)
+ }
+
+ // 3.判断传入值和枚举值是否相等
+ println(Color.YELLOW.toString == "yellow")
+ // 4.遍历枚举类和值
+ for (c <- Color.values) println(c.id + ":" + c.toString)
+}
+
+//输出
+true
+0:GREEN
+3:RED
+4:blue
+5:yellow
+6:PINK
+```
+
+
+
+## 参考资料
+
+1. Martin Odersky . Scala 编程 (第 3 版)[M] . 电子工业出版社 . 2018-1-1
+2. 凯.S.霍斯特曼 . 快学 Scala(第 2 版)[M] . 电子工业出版社 . 2017-7
+
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\347\261\273\345\236\213\345\217\202\346\225\260.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\347\261\273\345\236\213\345\217\202\346\225\260.md"
new file mode 100644
index 0000000..2776d7b
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\347\261\273\345\236\213\345\217\202\346\225\260.md"
@@ -0,0 +1,467 @@
+# 类型参数
+
+
+一、泛型
+ 1.1 泛型类
+ 1.2 泛型方法
+二、类型限定
+ 2.1 类型上界限定
+ 2.2 视图界定
+ 2.3 类型约束
+ 2.4 上下文界定
+ 2.5 ClassTag上下文界定
+ 2.6 类型下界限定
+ 2.7 多重界定
+三、Ordering & Ordered
+ 3.1 Comparable
+ 3.2 Comparator
+ 3.3 上下文界定的优点
+四、通配符
+
+
+
+## 一、泛型
+
+Scala 支持类型参数化,使得我们能够编写泛型程序。
+
+### 1.1 泛型类
+
+Java 中使用 `<>` 符号来包含定义的类型参数,Scala 则使用 `[]`。
+
+```scala
+class Pair[T, S](val first: T, val second: S) {
+ override def toString: String = first + ":" + second
+}
+```
+
+```scala
+object ScalaApp extends App {
+
+ // 使用时候你直接指定参数类型,也可以不指定,由程序自动推断
+ val pair01 = new Pair("heibai01", 22)
+ val pair02 = new Pair[String,Int]("heibai02", 33)
+
+ println(pair01)
+ println(pair02)
+}
+```
+
+### 1.2 泛型方法
+
+函数和方法也支持类型参数。
+
+```scala
+object Utils {
+ def getHalf[T](a: Array[T]): Int = a.length / 2
+}
+```
+
+## 二、类型限定
+
+### 2.1 类型上界限定
+
+Scala 和 Java 一样,对于对象之间进行大小比较,要求被比较的对象实现 `java.lang.Comparable` 接口。所以如果想对泛型进行比较,需要限定类型上界为 `java.lang.Comparable`,语法为 ` S <: T`,代表类型 S 是类型 T 的子类或其本身。示例如下:
+
+```scala
+// 使用 <: 符号,限定 T 必须是 Comparable[T]的子类型
+class Pair[T <: Comparable[T]](val first: T, val second: T) {
+ // 返回较小的值
+ def smaller: T = if (first.compareTo(second) < 0) first else second
+}
+```
+
+```scala
+// 测试代码
+val pair = new Pair("abc", "abcd")
+println(pair.smaller) // 输出 abc
+```
+
+>扩展:如果你想要在 Java 中实现类型变量限定,需要使用关键字 extends 来实现,等价的 Java 代码如下:
+>
+>```java
+>public class Pair> {
+> private T first;
+> private T second;
+> Pair(T first, T second) {
+> this.first = first;
+> this.second = second;
+> }
+> public T smaller() {
+> return first.compareTo(second) < 0 ? first : second;
+> }
+>}
+>```
+
+### 2.2 视图界定
+
+在上面的例子中,如果你使用 Int 类型或者 Double 等类型进行测试,点击运行后,你会发现程序根本无法通过编译:
+
+```scala
+val pair1 = new Pair(10, 12)
+val pair2 = new Pair(10.0, 12.0)
+```
+
+之所以出现这样的问题,是因为 Scala 中的 Int 类并没有实现 Comparable 接口。在 Scala 中直接继承 Comparable 接口的是特质 Ordered,它在继承 compareTo 方法的基础上,额外定义了关系符方法,源码如下:
+
+```scala
+// 除了 compareTo 方法外,还提供了额外的关系符方法
+trait Ordered[A] extends Any with java.lang.Comparable[A] {
+ def compare(that: A): Int
+ def < (that: A): Boolean = (this compare that) < 0
+ def > (that: A): Boolean = (this compare that) > 0
+ def <= (that: A): Boolean = (this compare that) <= 0
+ def >= (that: A): Boolean = (this compare that) >= 0
+ def compareTo(that: A): Int = compare(that)
+}
+```
+
+之所以在日常的编程中之所以你能够执行 `3>2` 这样的判断操作,是因为程序执行了定义在 `Predef` 中的隐式转换方法 `intWrapper(x: Int) `,将 Int 类型转换为 RichInt 类型,而 RichInt 间接混入了 Ordered 特质,所以能够进行比较。
+
+```scala
+// Predef.scala
+@inline implicit def intWrapper(x: Int) = new runtime.RichInt(x)
+```
+
+
+
+要想解决传入数值无法进行比较的问题,可以使用视图界定。语法为 `T <% U`,代表 T 能够通过隐式转换转为 U,即允许 Int 型参数在无法进行比较的时候转换为 RichInt 类型。示例如下:
+
+```scala
+// 视图界定符号 <%
+class Pair[T <% Comparable[T]](val first: T, val second: T) {
+ // 返回较小的值
+ def smaller: T = if (first.compareTo(second) < 0) first else second
+}
+```
+
+> 注:由于直接继承 Java 中 Comparable 接口的是特质 Ordered,所以如下的视图界定和上面是等效的:
+>
+> ```scala
+> // 隐式转换为 Ordered[T]
+> class Pair[T <% Ordered[T]](val first: T, val second: T) {
+> def smaller: T = if (first.compareTo(second) < 0) first else second
+> }
+> ```
+
+### 2.3 类型约束
+
+如果你用的 Scala 是 2.11+,会发现视图界定已被标识为废弃。官方推荐使用类型约束 (type constraint) 来实现同样的功能,其本质是使用隐式参数进行隐式转换,示例如下:
+
+```scala
+ // 1.使用隐式参数隐式转换为 Comparable[T]
+class Pair[T](val first: T, val second: T)(implicit ev: T => Comparable[T])
+ def smaller: T = if (first.compareTo(second) < 0) first else second
+}
+
+// 2.由于直接继承 Java 中 Comparable 接口的是特质 Ordered,所以也可以隐式转换为 Ordered[T]
+class Pair[T](val first: T, val second: T)(implicit ev: T => Ordered[T]) {
+ def smaller: T = if (first.compareTo(second) < 0) first else second
+}
+```
+
+当然,隐式参数转换也可以运用在具体的方法上:
+
+```scala
+object PairUtils{
+ def smaller[T](a: T, b: T)(implicit order: T => Ordered[T]) = if (a < b) a else b
+}
+```
+
+### 2.4 上下文界定
+
+上下文界定的形式为 `T:M`,其中 M 是一个泛型,它要求必须存在一个类型为 M[T]的隐式值,当你声明一个带隐式参数的方法时,需要定义一个隐式默认值。所以上面的程序也可以使用上下文界定进行改写:
+
+```scala
+class Pair[T](val first: T, val second: T) {
+ // 请注意 这个地方用的是 Ordering[T],而上面视图界定和类型约束,用的是 Ordered[T],两者的区别会在后文给出解释
+ def smaller(implicit ord: Ordering[T]): T = if (ord.compare(first, second) < 0) first else second
+}
+
+// 测试
+val pair= new Pair(88, 66)
+println(pair.smaller) //输出:66
+```
+
+在上面的示例中,我们无需手动添加隐式默认值就可以完成转换,这是因为 Scala 自动引入了 Ordering[Int]这个隐式值。为了更好的说明上下文界定,下面给出一个自定义类型的比较示例:
+
+```scala
+// 1.定义一个人员类
+class Person(val name: String, val age: Int) {
+ override def toString: String = name + ":" + age
+}
+
+// 2.继承 Ordering[T],实现自定义比较器,按照自己的规则重写比较方法
+class PersonOrdering extends Ordering[Person] {
+ override def compare(x: Person, y: Person): Int = if (x.age > y.age) 1 else -1
+}
+
+class Pair[T](val first: T, val second: T) {
+ def smaller(implicit ord: Ordering[T]): T = if (ord.compare(first, second) < 0) first else second
+}
+
+
+object ScalaApp extends App {
+
+ val pair = new Pair(new Person("hei", 88), new Person("bai", 66))
+ // 3.定义隐式默认值,如果不定义,则下一行代码无法通过编译
+ implicit val ImpPersonOrdering = new PersonOrdering
+ println(pair.smaller) //输出: bai:66
+}
+```
+
+### 2.5 ClassTag上下文界定
+
+这里先看一个例子:下面这段代码,没有任何语法错误,但是在运行时会抛出异常:`Error: cannot find class tag for element type T`, 这是由于 Scala 和 Java 一样,都存在类型擦除,即**泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉**。对于下面的代码,在运行阶段创建 Array 时,你必须明确指明其类型,但是此时泛型信息已经被擦除,导致出现找不到类型的异常。
+
+```scala
+object ScalaApp extends App {
+ def makePair[T](first: T, second: T) = {
+ // 创建以一个数组 并赋值
+ val r = new Array[T](2); r(0) = first; r(1) = second; r
+ }
+}
+```
+
+Scala 针对这个问题,提供了 ClassTag 上下文界定,即把泛型的信息存储在 ClassTag 中,这样在运行阶段需要时,只需要从 ClassTag 中进行获取即可。其语法为 `T : ClassTag`,示例如下:
+
+```scala
+import scala.reflect._
+object ScalaApp extends App {
+ def makePair[T : ClassTag](first: T, second: T) = {
+ val r = new Array[T](2); r(0) = first; r(1) = second; r
+ }
+}
+```
+
+### 2.6 类型下界限定
+
+2.1 小节介绍了类型上界的限定,Scala 同时也支持下界的限定,语法为:`U >: T`,即 U 必须是类型 T 的超类或本身。
+
+```scala
+// 首席执行官
+class CEO
+
+// 部门经理
+class Manager extends CEO
+
+// 本公司普通员工
+class Employee extends Manager
+
+// 其他公司人员
+class OtherCompany
+
+object ScalaApp extends App {
+
+ // 限定:只有本公司部门经理以上人员才能获取权限
+ def Check[T >: Manager](t: T): T = {
+ println("获得审核权限")
+ t
+ }
+
+ // 错误写法: 省略泛型参数后,以下所有人都能获得权限,显然这是不正确的
+ Check(new CEO)
+ Check(new Manager)
+ Check(new Employee)
+ Check(new OtherCompany)
+
+
+ // 正确写法,传入泛型参数
+ Check[CEO](new CEO)
+ Check[Manager](new Manager)
+ /*
+ * 以下两条语句无法通过编译,异常信息为:
+ * do not conform to method Check's type parameter bounds(不符合方法 Check 的类型参数边界)
+ * 这种情况就完成了下界限制,即只有本公司经理及以上的人员才能获得审核权限
+ */
+ Check[Employee](new Employee)
+ Check[OtherCompany](new OtherCompany)
+}
+```
+
+### 2.7 多重界定
+
++ 类型变量可以同时有上界和下界。 写法为 :`T > : Lower <: Upper`;
+
++ 不能同时有多个上界或多个下界 。但可以要求一个类型实现多个特质,写法为 :
+
+ `T < : Comparable[T] with Serializable with Cloneable`;
+
++ 你可以有多个上下文界定,写法为 `T : Ordering : ClassTag` 。
+
+
+
+## 三、Ordering & Ordered
+
+上文中使用到 Ordering 和 Ordered 特质,它们最主要的区别在于分别继承自不同的 Java 接口:Comparable 和 Comparator:
+
++ **Comparable**:可以理解为内置的比较器,实现此接口的对象可以与自身进行比较;
++ **Comparator**:可以理解为外置的比较器;当对象自身并没有定义比较规则的时候,可以传入外部比较器进行比较。
+
+为什么 Java 中要同时给出这两个比较接口,这是因为你要比较的对象不一定实现了 Comparable 接口,而你又想对其进行比较,这时候当然你可以修改代码实现 Comparable,但是如果这个类你无法修改 (如源码中的类),这时候就可以使用外置的比较器。同样的问题在 Scala 中当然也会出现,所以 Scala 分别使用了 Ordering 和 Ordered 来继承它们。
+
+
+
+
+
+下面分别给出 Java 中 Comparable 和 Comparator 接口的使用示例:
+
+### 3.1 Comparable
+
+```java
+import java.util.Arrays;
+// 实现 Comparable 接口
+public class Person implements Comparable {
+
+ private String name;
+ private int age;
+
+ Person(String name,int age) {this.name=name;this.age=age;}
+ @Override
+ public String toString() { return name+":"+age; }
+
+ // 核心的方法是重写比较规则,按照年龄进行排序
+ @Override
+ public int compareTo(Person person) {
+ return this.age - person.age;
+ }
+
+ public static void main(String[] args) {
+ Person[] peoples= {new Person("hei", 66), new Person("bai", 55), new Person("ying", 77)};
+ Arrays.sort(peoples);
+ Arrays.stream(peoples).forEach(System.out::println);
+ }
+}
+
+输出:
+bai:55
+hei:66
+ying:77
+```
+
+### 3.2 Comparator
+
+```java
+import java.util.Arrays;
+import java.util.Comparator;
+
+public class Person {
+
+ private String name;
+ private int age;
+
+ Person(String name,int age) {this.name=name;this.age=age;}
+ @Override
+ public String toString() { return name+":"+age; }
+
+ public static void main(String[] args) {
+ Person[] peoples= {new Person("hei", 66), new Person("bai", 55), new Person("ying", 77)};
+ // 这里为了直观直接使用匿名内部类,实现 Comparator 接口
+ //如果是 Java8 你也可以写成 Arrays.sort(peoples, Comparator.comparingInt(o -> o.age));
+ Arrays.sort(peoples, new Comparator() {
+ @Override
+ public int compare(Person o1, Person o2) {
+ return o1.age-o2.age;
+ }
+ });
+ Arrays.stream(peoples).forEach(System.out::println);
+ }
+}
+```
+
+使用外置比较器还有一个好处,就是你可以随时定义其排序规则:
+
+```scala
+// 按照年龄大小排序
+Arrays.sort(peoples, Comparator.comparingInt(o -> o.age));
+Arrays.stream(peoples).forEach(System.out::println);
+// 按照名字长度倒序排列
+Arrays.sort(peoples, Comparator.comparingInt(o -> -o.name.length()));
+Arrays.stream(peoples).forEach(System.out::println);
+```
+
+### 3.3 上下文界定的优点
+
+这里再次给出上下文界定中的示例代码作为回顾:
+
+```scala
+// 1.定义一个人员类
+class Person(val name: String, val age: Int) {
+ override def toString: String = name + ":" + age
+}
+
+// 2.继承 Ordering[T],实现自定义比较器,这个比较器就是一个外置比较器
+class PersonOrdering extends Ordering[Person] {
+ override def compare(x: Person, y: Person): Int = if (x.age > y.age) 1 else -1
+}
+
+class Pair[T](val first: T, val second: T) {
+ def smaller(implicit ord: Ordering[T]): T = if (ord.compare(first, second) < 0) first else second
+}
+
+
+object ScalaApp extends App {
+
+ val pair = new Pair(new Person("hei", 88), new Person("bai", 66))
+ // 3.在当前上下文定义隐式默认值,这就相当于传入了外置比较器
+ implicit val ImpPersonOrdering = new PersonOrdering
+ println(pair.smaller) //输出: bai:66
+}
+```
+
+使用上下文界定和 Ordering 带来的好处是:传入 `Pair` 中的参数不一定需要可比较,只要在比较时传入外置比较器即可。
+
+需要注意的是由于隐式默认值二义性的限制,你不能像上面 Java 代码一样,在同一个上下文作用域中传入两个外置比较器,即下面的代码是无法通过编译的。但是你可以在不同的上下文作用域中引入不同的隐式默认值,即使用不同的外置比较器。
+
+```scala
+implicit val ImpPersonOrdering = new PersonOrdering
+println(pair.smaller)
+implicit val ImpPersonOrdering2 = new PersonOrdering
+println(pair.smaller)
+```
+
+
+
+## 四、通配符
+
+在实际编码中,通常需要把泛型限定在某个范围内,比如限定为某个类及其子类。因此 Scala 和 Java 一样引入了通配符这个概念,用于限定泛型的范围。不同的是 Java 使用 `?` 表示通配符,Scala 使用 `_` 表示通配符。
+
+```scala
+class Ceo(val name: String) {
+ override def toString: String = name
+}
+
+class Manager(name: String) extends Ceo(name)
+
+class Employee(name: String) extends Manager(name)
+
+class Pair[T](val first: T, val second: T) {
+ override def toString: String = "first:" + first + ", second: " + second
+}
+
+object ScalaApp extends App {
+ // 限定部门经理及以下的人才可以组队
+ def makePair(p: Pair[_ <: Manager]): Unit = {println(p)}
+ makePair(new Pair(new Employee("heibai"), new Manager("ying")))
+}
+```
+
+目前 Scala 中的通配符在某些复杂情况下还不完善,如下面的语句在 Scala 2.12 中并不能通过编译:
+
+```scala
+def min[T <: Comparable[_ >: T]](p: Pair[T]) ={}
+```
+
+可以使用以下语法代替:
+
+```scala
+type SuperComparable[T] = Comparable[_ >: T]
+def min[T <: SuperComparable[T]](p: Pair[T]) = {}
+```
+
+
+
+## 参考资料
+
+1. Martin Odersky . Scala 编程 (第 3 版)[M] . 电子工业出版社 . 2018-1-1
+2. 凯.S.霍斯特曼 . 快学 Scala(第 2 版)[M] . 电子工业出版社 . 2017-7
+
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\347\273\247\346\211\277\345\222\214\347\211\271\350\264\250.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\347\273\247\346\211\277\345\222\214\347\211\271\350\264\250.md"
new file mode 100644
index 0000000..bcb8b24
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\347\273\247\346\211\277\345\222\214\347\211\271\350\264\250.md"
@@ -0,0 +1,418 @@
+# 继承和特质
+
+
+一、继承
+ 1.1 Scala中的继承结构
+ 1.2 extends & override
+ 1.3 调用超类构造器
+ 1.4 类型检查和转换
+ 1.5 构造顺序和提前定义
+二、抽象类
+三、特质
+ 3.1 trait & with
+ 3.2 特质中的字段
+ 3.3 带有特质的对象
+ 3.4 特质构造顺序
+
+
+
+## 一、继承
+
+### 1.1 Scala中的继承结构
+
+Scala 中继承关系如下图:
+
++ Any 是整个继承关系的根节点;
++ AnyRef 包含 Scala Classes 和 Java Classes,等价于 Java 中的 java.lang.Object;
++ AnyVal 是所有值类型的一个标记;
++ Null 是所有引用类型的子类型,唯一实例是 null,可以将 null 赋值给除了值类型外的所有类型的变量;
++ Nothing 是所有类型的子类型。
+
+
+
+### 1.2 extends & override
+
+Scala 的集成机制和 Java 有很多相似之处,比如都使用 `extends` 关键字表示继承,都使用 `override` 关键字表示重写父类的方法或成员变量。示例如下:
+
+```scala
+//父类
+class Person {
+
+ var name = ""
+ // 1.不加任何修饰词,默认为 public,能被子类和外部访问
+ var age = 0
+ // 2.使用 protected 修饰的变量能子类访问,但是不能被外部访问
+ protected var birthday = ""
+ // 3.使用 private 修饰的变量不能被子类和外部访问
+ private var sex = ""
+
+ def setSex(sex: String): Unit = {
+ this.sex = sex
+ }
+ // 4.重写父类的方法建议使用 override 关键字修饰
+ override def toString: String = name + ":" + age + ":" + birthday + ":" + sex
+
+}
+```
+
+使用 `extends` 关键字实现继承:
+
+```scala
+// 1.使用 extends 关键字实现继承
+class Employee extends Person {
+
+ override def toString: String = "Employee~" + super.toString
+
+ // 2.使用 public 或 protected 关键字修饰的变量能被子类访问
+ def setBirthday(date: String): Unit = {
+ birthday = date
+ }
+
+}
+```
+
+测试继承:
+
+```scala
+
+object ScalaApp extends App {
+
+ val employee = new Employee
+
+ employee.name = "heibaiying"
+ employee.age = 20
+ employee.setBirthday("2019-03-05")
+ employee.setSex("男")
+
+ println(employee)
+}
+
+// 输出: Employee~heibaiying:20:2019-03-05:男
+```
+
+### 1.3 调用超类构造器
+
+在 Scala 的类中,每个辅助构造器都必须首先调用其他构造器或主构造器,这样就导致了子类的辅助构造器永远无法直接调用超类的构造器,只有主构造器才能调用超类的构造器。所以想要调用超类的构造器,代码示例如下:
+
+```scala
+class Employee(name:String,age:Int,salary:Double) extends Person(name:String,age:Int) {
+ .....
+}
+```
+
+### 1.4 类型检查和转换
+
+想要实现类检查可以使用 `isInstanceOf`,判断一个实例是否来源于某个类或者其子类,如果是,则可以使用 `asInstanceOf` 进行强制类型转换。
+
+```scala
+object ScalaApp extends App {
+
+ val employee = new Employee
+ val person = new Person
+
+ // 1. 判断一个实例是否来源于某个类或者其子类 输出 true
+ println(employee.isInstanceOf[Person])
+ println(person.isInstanceOf[Person])
+
+ // 2. 强制类型转换
+ var p: Person = employee.asInstanceOf[Person]
+
+ // 3. 判断一个实例是否来源于某个类 (而不是其子类)
+ println(employee.getClass == classOf[Employee])
+
+}
+```
+
+### 1.5 构造顺序和提前定义
+
+#### **1. 构造顺序**
+
+在 Scala 中还有一个需要注意的问题,如果你在子类中重写父类的 val 变量,并且超类的构造器中使用了该变量,那么可能会产生不可预期的错误。下面给出一个示例:
+
+```scala
+// 父类
+class Person {
+ println("父类的默认构造器")
+ val range: Int = 10
+ val array: Array[Int] = new Array[Int](range)
+}
+
+//子类
+class Employee extends Person {
+ println("子类的默认构造器")
+ override val range = 2
+}
+
+//测试
+object ScalaApp extends App {
+ val employee = new Employee
+ println(employee.array.mkString("(", ",", ")"))
+
+}
+```
+
+这里初始化 array 用到了变量 range,这里你会发现实际上 array 既不会被初始化 Array(10),也不会被初始化为 Array(2),实际的输出应该如下:
+
+```properties
+父类的默认构造器
+子类的默认构造器
+()
+```
+
+可以看到 array 被初始化为 Array(0),主要原因在于父类构造器的执行顺序先于子类构造器,这里给出实际的执行步骤:
+
+1. 父类的构造器被调用,执行 `new Array[Int](range)` 语句;
+2. 这里想要得到 range 的值,会去调用子类 range() 方法,因为 `override val` 重写变量的同时也重写了其 get 方法;
+3. 调用子类的 range() 方法,自然也是返回子类的 range 值,但是由于子类的构造器还没有执行,这也就意味着对 range 赋值的 `range = 2` 语句还没有被执行,所以自然返回 range 的默认值,也就是 0。
+
+这里可能比较疑惑的是为什么 `val range = 2` 没有被执行,却能使用 range 变量,这里因为在虚拟机层面,是先对成员变量先分配存储空间并赋给默认值,之后才赋予给定的值。想要证明这一点其实也比较简单,代码如下:
+
+```scala
+class Person {
+ // val range: Int = 10 正常代码 array 为 Array(10)
+ val array: Array[Int] = new Array[Int](range)
+ val range: Int = 10 //如果把变量的声明放在使用之后,此时数据 array 为 array(0)
+}
+
+object Person {
+ def main(args: Array[String]): Unit = {
+ val person = new Person
+ println(person.array.mkString("(", ",", ")"))
+ }
+}
+```
+
+#### **2. 提前定义**
+
+想要解决上面的问题,有以下几种方法:
+
+(1) . 将变量用 final 修饰,代表不允许被子类重写,即 `final val range: Int = 10 `;
+
+(2) . 将变量使用 lazy 修饰,代表懒加载,即只有当你实际使用到 array 时候,才去进行初始化;
+
+```scala
+lazy val array: Array[Int] = new Array[Int](range)
+```
+
+(3) . 采用提前定义,代码如下,代表 range 的定义优先于超类构造器。
+
+```scala
+class Employee extends {
+ //这里不能定义其他方法
+ override val range = 2
+} with Person {
+ // 定义其他变量或者方法
+ def pr(): Unit = {println("Employee")}
+}
+```
+
+但是这种语法也有其限制:你只能在上面代码块中重写已有的变量,而不能定义新的变量和方法,定义新的变量和方法只能写在下面代码块中。
+
+>**注意事项**:类的继承和下文特质 (trait) 的继承都存在这个问题,也同样可以通过提前定义来解决。虽然如此,但还是建议合理设计以规避该类问题。
+
+
+
+## 二、抽象类
+
+Scala 中允许使用 `abstract` 定义抽象类,并且通过 `extends` 关键字继承它。
+
+定义抽象类:
+
+```scala
+abstract class Person {
+ // 1.定义字段
+ var name: String
+ val age: Int
+
+ // 2.定义抽象方法
+ def geDetail: String
+
+ // 3. scala 的抽象类允许定义具体方法
+ def print(): Unit = {
+ println("抽象类中的默认方法")
+ }
+}
+```
+
+继承抽象类:
+
+```scala
+class Employee extends Person {
+ // 覆盖抽象类中变量
+ override var name: String = "employee"
+ override val age: Int = 12
+
+ // 覆盖抽象方法
+ def geDetail: String = name + ":" + age
+}
+
+```
+
+
+
+## 三、特质
+
+### 3.1 trait & with
+
+Scala 中没有 interface 这个关键字,想要实现类似的功能,可以使用特质 (trait)。trait 等价于 Java 8 中的接口,因为 trait 中既能定义抽象方法,也能定义具体方法,这和 Java 8 中的接口是类似的。
+
+```scala
+// 1.特质使用 trait 关键字修饰
+trait Logger {
+
+ // 2.定义抽象方法
+ def log(msg: String)
+
+ // 3.定义具体方法
+ def logInfo(msg: String): Unit = {
+ println("INFO:" + msg)
+ }
+}
+```
+
+想要使用特质,需要使用 `extends` 关键字,而不是 `implements` 关键字,如果想要添加多个特质,可以使用 `with` 关键字。
+
+```scala
+// 1.使用 extends 关键字,而不是 implements,如果想要添加多个特质,可以使用 with 关键字
+class ConsoleLogger extends Logger with Serializable with Cloneable {
+
+ // 2. 实现特质中的抽象方法
+ def log(msg: String): Unit = {
+ println("CONSOLE:" + msg)
+ }
+}
+```
+
+### 3.2 特质中的字段
+
+和方法一样,特质中的字段可以是抽象的,也可以是具体的:
+
++ 如果是抽象字段,则混入特质的类需要重写覆盖该字段;
++ 如果是具体字段,则混入特质的类获得该字段,但是并非是通过继承关系得到,而是在编译时候,简单将该字段加入到子类。
+
+```scala
+trait Logger {
+ // 抽象字段
+ var LogLevel:String
+ // 具体字段
+ var LogType = "FILE"
+}
+```
+
+覆盖抽象字段:
+
+```scala
+class InfoLogger extends Logger {
+ // 覆盖抽象字段
+ override var LogLevel: String = "INFO"
+}
+```
+
+### 3.3 带有特质的对象
+
+Scala 支持在类定义的时混入 ` 父类 trait`,而在类实例化为具体对象的时候指明其实际使用的 ` 子类 trait`。示例如下:
+
+
+
+trait Logger:
+
+```scala
+// 父类
+trait Logger {
+ // 定义空方法 日志打印
+ def log(msg: String) {}
+}
+```
+
+trait ErrorLogger:
+
+```scala
+// 错误日志打印,继承自 Logger
+trait ErrorLogger extends Logger {
+ // 覆盖空方法
+ override def log(msg: String): Unit = {
+ println("Error:" + msg)
+ }
+}
+```
+
+trait InfoLogger:
+
+```scala
+// 通知日志打印,继承自 Logger
+trait InfoLogger extends Logger {
+
+ // 覆盖空方法
+ override def log(msg: String): Unit = {
+ println("INFO:" + msg)
+ }
+}
+```
+
+具体的使用类:
+
+```scala
+// 混入 trait Logger
+class Person extends Logger {
+ // 调用定义的抽象方法
+ def printDetail(detail: String): Unit = {
+ log(detail)
+ }
+}
+```
+
+这里通过 main 方法来测试:
+
+```scala
+object ScalaApp extends App {
+
+ // 使用 with 指明需要具体使用的 trait
+ val person01 = new Person with InfoLogger
+ val person02 = new Person with ErrorLogger
+ val person03 = new Person with InfoLogger with ErrorLogger
+ person01.log("scala") //输出 INFO:scala
+ person02.log("scala") //输出 Error:scala
+ person03.log("scala") //输出 Error:scala
+
+}
+```
+
+这里前面两个输出比较明显,因为只指明了一个具体的 `trait`,这里需要说明的是第三个输出,**因为 trait 的调用是由右到左开始生效的**,所以这里打印出 `Error:scala`。
+
+### 3.4 特质构造顺序
+
+`trait` 有默认的无参构造器,但是不支持有参构造器。一个类混入多个特质后初始化顺序应该如下:
+
+```scala
+// 示例
+class Employee extends Person with InfoLogger with ErrorLogger {...}
+```
+
+1. 超类首先被构造,即 Person 的构造器首先被执行;
+2. 特质的构造器在超类构造器之前,在类构造器之后;特质由左到右被构造;每个特质中,父特质首先被构造;
+ + Logger 构造器执行(Logger 是 InfoLogger 的父类);
+ + InfoLogger 构造器执行;
+ + ErrorLogger 构造器执行;
+3. 所有超类和特质构造完毕,子类才会被构造。
+
+
+
+## 参考资料
+
+1. Martin Odersky . Scala 编程 (第 3 版)[M] . 电子工业出版社 . 2018-1-1
+2. 凯.S.霍斯特曼 . 快学 Scala(第 2 版)[M] . 电子工业出版社 . 2017-7
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\351\232\220\345\274\217\350\275\254\346\215\242\345\222\214\351\232\220\345\274\217\345\217\202\346\225\260.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\351\232\220\345\274\217\350\275\254\346\215\242\345\222\214\351\232\220\345\274\217\345\217\202\346\225\260.md"
new file mode 100644
index 0000000..d919b63
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\351\232\220\345\274\217\350\275\254\346\215\242\345\222\214\351\232\220\345\274\217\345\217\202\346\225\260.md"
@@ -0,0 +1,356 @@
+# 隐式转换和隐式参数
+
+
+一、隐式转换
+ 1.1 使用隐式转换
+ 1.2 隐式转换规则
+ 1.3 引入隐式转换
+二、隐式参数
+ 2.1 使用隐式参数
+ 2.2 引入隐式参数
+ 2.3 利用隐式参数进行隐式转换
+
+
+
+## 一、隐式转换
+
+### 1.1 使用隐式转换
+
+隐式转换指的是以 `implicit` 关键字声明带有单个参数的转换函数,它将值从一种类型转换为另一种类型,以便使用之前类型所没有的功能。示例如下:
+
+```scala
+// 普通人
+class Person(val name: String)
+
+// 雷神
+class Thor(val name: String) {
+ // 正常情况下只有雷神才能举起雷神之锤
+ def hammer(): Unit = {
+ println(name + "举起雷神之锤")
+ }
+}
+
+object Thor extends App {
+ // 定义隐式转换方法 将普通人转换为雷神 通常建议方法名使用 source2Target,即:被转换对象 To 转换对象
+ implicit def person2Thor(p: Person): Thor = new Thor(p.name)
+ // 这样普通人也能举起雷神之锤
+ new Person("普通人").hammer()
+}
+
+输出: 普通人举起雷神之锤
+```
+
+
+
+### 1.2 隐式转换规则
+
+并不是你使用 `implicit` 转换后,隐式转换就一定会发生,比如上面如果不调用 `hammer()` 方法的时候,普通人就还是普通人。通常程序会在以下情况下尝试执行隐式转换:
+
++ 当对象访问一个不存在的成员时,即调用的方法不存在或者访问的成员变量不存在;
++ 当对象调用某个方法,该方法存在,但是方法的声明参数与传入参数不匹配时。
+
+而在以下三种情况下编译器不会尝试执行隐式转换:
+
++ 如果代码能够在不使用隐式转换的前提下通过编译,则不会使用隐式转换;
++ 编译器不会尝试同时执行多个转换,比如 `convert1(convert2(a))*b`;
++ 转换存在二义性,也不会发生转换。
+
+这里首先解释一下二义性,上面的代码进行如下修改,由于两个隐式转换都是生效的,所以就存在了二义性:
+
+```scala
+//两个隐式转换都是有效的
+implicit def person2Thor(p: Person): Thor = new Thor(p.name)
+implicit def person2Thor2(p: Person): Thor = new Thor(p.name)
+// 此时下面这段语句无法通过编译
+new Person("普通人").hammer()
+```
+
+其次再解释一下多个转换的问题:
+
+```scala
+class ClassA {
+ override def toString = "This is Class A"
+}
+
+class ClassB {
+ override def toString = "This is Class B"
+ def printB(b: ClassB): Unit = println(b)
+}
+
+class ClassC
+
+class ClassD
+
+object ImplicitTest extends App {
+ implicit def A2B(a: ClassA): ClassB = {
+ println("A2B")
+ new ClassB
+ }
+
+ implicit def C2B(c: ClassC): ClassB = {
+ println("C2B")
+ new ClassB
+ }
+
+ implicit def D2C(d: ClassD): ClassC = {
+ println("D2C")
+ new ClassC
+ }
+
+ // 这行代码无法通过编译,因为要调用到 printB 方法,需要执行两次转换 C2B(D2C(ClassD))
+ new ClassD().printB(new ClassA)
+
+ /*
+ * 下面的这一行代码虽然也进行了两次隐式转换,但是两次的转换对象并不是一个对象,所以它是生效的:
+ * 转换流程如下:
+ * 1. ClassC 中并没有 printB 方法,因此隐式转换为 ClassB,然后调用 printB 方法;
+ * 2. 但是 printB 参数类型为 ClassB,然而传入的参数类型是 ClassA,所以需要将参数 ClassA 转换为 ClassB,这是第二次;
+ * 即: C2B(ClassC) -> ClassB.printB(ClassA) -> ClassB.printB(A2B(ClassA)) -> ClassB.printB(ClassB)
+ * 转换过程 1 的对象是 ClassC,而转换过程 2 的转换对象是 ClassA,所以虽然是一行代码两次转换,但是仍然是有效转换
+ */
+ new ClassC().printB(new ClassA)
+}
+
+// 输出:
+C2B
+A2B
+This is Class B
+```
+
+
+
+### 1.3 引入隐式转换
+
+隐式转换的可以定义在以下三个地方:
+
++ 定义在原类型的伴生对象中;
++ 直接定义在执行代码的上下文作用域中;
++ 统一定义在一个文件中,在使用时候导入。
+
+上面我们使用的方法相当于直接定义在执行代码的作用域中,下面分别给出其他两种定义的代码示例:
+
+**定义在原类型的伴生对象中**:
+
+```scala
+class Person(val name: String)
+// 在伴生对象中定义隐式转换函数
+object Person{
+ implicit def person2Thor(p: Person): Thor = new Thor(p.name)
+}
+```
+
+```scala
+class Thor(val name: String) {
+ def hammer(): Unit = {
+ println(name + "举起雷神之锤")
+ }
+}
+```
+
+```scala
+// 使用示例
+object ScalaApp extends App {
+ new Person("普通人").hammer()
+}
+```
+
+**定义在一个公共的对象中**:
+
+```scala
+object Convert {
+ implicit def person2Thor(p: Person): Thor = new Thor(p.name)
+}
+```
+
+```scala
+// 导入 Convert 下所有的隐式转换函数
+import com.heibaiying.Convert._
+
+object ScalaApp extends App {
+ new Person("普通人").hammer()
+}
+```
+
+> 注:Scala 自身的隐式转换函数大部分定义在 `Predef.scala` 中,你可以打开源文件查看,也可以在 Scala 交互式命令行中采用 `:implicit -v` 查看全部隐式转换函数。
+
+
+
+## 二、隐式参数
+
+### 2.1 使用隐式参数
+
+在定义函数或方法时可以使用标记为 `implicit` 的参数,这种情况下,编译器将会查找默认值,提供给函数调用。
+
+```scala
+// 定义分隔符类
+class Delimiters(val left: String, val right: String)
+
+object ScalaApp extends App {
+
+ // 进行格式化输出
+ def formatted(context: String)(implicit deli: Delimiters): Unit = {
+ println(deli.left + context + deli.right)
+ }
+
+ // 定义一个隐式默认值 使用左右中括号作为分隔符
+ implicit val bracket = new Delimiters("(", ")")
+ formatted("this is context") // 输出: (this is context)
+}
+```
+
+关于隐式参数,有两点需要注意:
+
+1.我们上面定义 `formatted` 函数的时候使用了柯里化,如果你不使用柯里化表达式,按照通常习惯只有下面两种写法:
+
+```scala
+// 这种写法没有语法错误,但是无法通过编译
+def formatted(implicit context: String, deli: Delimiters): Unit = {
+ println(deli.left + context + deli.right)
+}
+// 不存在这种写法,IDEA 直接会直接提示语法错误
+def formatted( context: String, implicit deli: Delimiters): Unit = {
+ println(deli.left + context + deli.right)
+}
+```
+
+上面第一种写法编译的时候会出现下面所示 `error` 信息,从中也可以看出 `implicit` 是作用于参数列表中每个参数的,这显然不是我们想要到达的效果,所以上面的写法采用了柯里化。
+
+```
+not enough arguments for method formatted:
+(implicit context: String, implicit deli: com.heibaiying.Delimiters)
+```
+
+2.第二个问题和隐式函数一样,隐式默认值不能存在二义性,否则无法通过编译,示例如下:
+
+```scala
+implicit val bracket = new Delimiters("(", ")")
+implicit val brace = new Delimiters("{", "}")
+formatted("this is context")
+```
+
+上面代码无法通过编译,出现错误提示 `ambiguous implicit values`,即隐式值存在冲突。
+
+
+
+### 2.2 引入隐式参数
+
+引入隐式参数和引入隐式转换函数方法是一样的,有以下三种方式:
+
+- 定义在隐式参数对应类的伴生对象中;
+- 直接定义在执行代码的上下文作用域中;
+- 统一定义在一个文件中,在使用时候导入。
+
+我们上面示例程序相当于直接定义执行代码的上下文作用域中,下面给出其他两种方式的示例:
+
+**定义在隐式参数对应类的伴生对象中**;
+
+```scala
+class Delimiters(val left: String, val right: String)
+
+object Delimiters {
+ implicit val bracket = new Delimiters("(", ")")
+}
+```
+
+```scala
+// 此时执行代码的上下文中不用定义
+object ScalaApp extends App {
+
+ def formatted(context: String)(implicit deli: Delimiters): Unit = {
+ println(deli.left + context + deli.right)
+ }
+ formatted("this is context")
+}
+```
+
+**统一定义在一个文件中,在使用时候导入**:
+
+```scala
+object Convert {
+ implicit val bracket = new Delimiters("(", ")")
+}
+```
+
+```scala
+// 在使用的时候导入
+import com.heibaiying.Convert.bracket
+
+object ScalaApp extends App {
+ def formatted(context: String)(implicit deli: Delimiters): Unit = {
+ println(deli.left + context + deli.right)
+ }
+ formatted("this is context") // 输出: (this is context)
+}
+```
+
+
+
+### 2.3 利用隐式参数进行隐式转换
+
+```scala
+def smaller[T] (a: T, b: T) = if (a < b) a else b
+```
+
+在 Scala 中如果定义了一个如上所示的比较对象大小的泛型方法,你会发现无法通过编译。对于对象之间进行大小比较,Scala 和 Java 一样,都要求被比较的对象需要实现 java.lang.Comparable 接口。在 Scala 中,直接继承 Java 中 Comparable 接口的是特质 Ordered,它在继承 compareTo 方法的基础上,额外定义了关系符方法,源码如下:
+
+```scala
+trait Ordered[A] extends Any with java.lang.Comparable[A] {
+ def compare(that: A): Int
+ def < (that: A): Boolean = (this compare that) < 0
+ def > (that: A): Boolean = (this compare that) > 0
+ def <= (that: A): Boolean = (this compare that) <= 0
+ def >= (that: A): Boolean = (this compare that) >= 0
+ def compareTo(that: A): Int = compare(that)
+}
+```
+
+所以要想在泛型中解决这个问题,有两种方法:
+
+#### 1. 使用视图界定
+
+```scala
+object Pair extends App {
+
+ // 视图界定
+ def smaller[T<% Ordered[T]](a: T, b: T) = if (a < b) a else b
+
+ println(smaller(1,2)) //输出 1
+}
+```
+
+视图限定限制了 T 可以通过隐式转换 `Ordered[T]`,即对象一定可以进行大小比较。在上面的代码中 `smaller(1,2)` 中参数 `1` 和 `2` 实际上是通过定义在 `Predef` 中的隐式转换方法 `intWrapper` 转换为 `RichInt`。
+
+```scala
+// Predef.scala
+@inline implicit def intWrapper(x: Int) = new runtime.RichInt(x)
+```
+
+为什么要这么麻烦执行隐式转换,原因是 Scala 中的 Int 类型并不能直接进行比较,因为其没有实现 `Ordered` 特质,真正实现 `Ordered` 特质的是 `RichInt`。
+
+
+
+
+
+#### 2. 利用隐式参数进行隐式转换
+
+Scala2.11+ 后,视图界定被标识为废弃,官方推荐使用类型限定来解决上面的问题,本质上就是使用隐式参数进行隐式转换。
+
+```scala
+object Pair extends App {
+
+ // order 既是一个隐式参数也是一个隐式转换,即如果 a 不存在 < 方法,则转换为 order(a) Ordered[T]) = if (a < b) a else b
+
+ println(smaller(1,2)) //输出 1
+}
+```
+
+
+
+## 参考资料
+
+1. Martin Odersky . Scala 编程 (第 3 版)[M] . 电子工业出版社 . 2018-1-1
+2. 凯.S.霍斯特曼 . 快学 Scala(第 2 版)[M] . 电子工业出版社 . 2017-7
+
+
+
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\351\233\206\345\220\210\347\261\273\345\236\213.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\351\233\206\345\220\210\347\261\273\345\236\213.md"
new file mode 100644
index 0000000..15313a0
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Scala\351\233\206\345\220\210\347\261\273\345\236\213.md"
@@ -0,0 +1,259 @@
+# 集合
+
+
+一、集合简介
+二、集合结构
+ 3.1 scala.collection
+ 3.2 scala.collection.mutable
+ 3.2 scala.collection.immutable
+三、Trait Traversable
+四、Trait Iterable
+五、修改集合
+
+
+## 一、集合简介
+
+Scala 中拥有多种集合类型,主要分为可变的和不可变的集合两大类:
+
++ **可变集合**: 可以被修改。即可以更改,添加,删除集合中的元素;
+
++ **不可变集合类**:不能被修改。对集合执行更改,添加或删除操作都会返回一个新的集合,而不是修改原来的集合。
+
+## 二、集合结构
+
+Scala 中的大部分集合类都存在三类变体,分别位于 `scala.collection`, `scala.collection.immutable`, `scala.collection.mutable` 包中。还有部分集合类位于 `scala.collection.generic` 包下。
+
+- **scala.collection.immutable** :包是中的集合是不可变的;
+- **scala.collection.mutable** :包中的集合是可变的;
+- **scala.collection** :包中的集合,既可以是可变的,也可以是不可变的。
+
+```scala
+val sortSet = scala.collection.SortedSet(1, 2, 3, 4, 5)
+val mutableSet = collection.mutable.SortedSet(1, 2, 3, 4, 5)
+val immutableSet = collection.immutable.SortedSet(1, 2, 3, 4, 5)
+```
+
+如果你仅写了 `Set` 而没有加任何前缀也没有进行任何 `import`,则 Scala 默认采用不可变集合类。
+
+```scala
+scala> Set(1,2,3,4,5)
+res0: scala.collection.immutable.Set[Int] = Set(5, 1, 2, 3, 4)
+```
+
+### 3.1 scala.collection
+
+scala.collection 包中所有集合如下图:
+
+
+
+### 3.2 scala.collection.mutable
+
+scala.collection.mutable 包中所有集合如下图:
+
+
+
+### 3.2 scala.collection.immutable
+
+scala.collection.immutable 包中所有集合如下图:
+
+
+
+## 三、Trait Traversable
+
+Scala 中所有集合的顶层实现是 `Traversable` 。它唯一的抽象方法是 `foreach`:
+
+```scala
+def foreach[U](f: Elem => U)
+```
+
+实现 `Traversable` 的集合类只需要实现这个抽象方法,其他方法可以从 `Traversable` 继承。`Traversable` 中的所有可用方法如下:
+
+| **方法** | **作用** |
+| ----------------------------------- | ------------------------------------------------------------ |
+| **Abstract Method:** | |
+| `xs foreach f` | 为 xs 的每个元素执行函数 f |
+| **Addition:** | |
+| `xs ++ ys` | 一个包含 xs 和 ys 中所有元素的新的集合。 ys 是一个 Traversable 或 Iterator。 |
+| **Maps:** | |
+| `xs map f` | 对 xs 中每一个元素应用函数 f,并返回一个新的集合 |
+| `xs flatMap f` | 对 xs 中每一个元素应用函数 f,最后将结果合并成一个新的集合 |
+| `xs collect f` | 对 xs 中每一个元素调用偏函数 f,并返回一个新的集合 |
+| **Conversions:** | |
+| `xs.toArray` | 将集合转化为一个 Array |
+| `xs.toList` | 将集合转化为一个 List |
+| `xs.toIterable` | 将集合转化为一个 Iterable |
+| `xs.toSeq` | 将集合转化为一个 Seq |
+| `xs.toIndexedSeq` | 将集合转化为一个 IndexedSeq |
+| `xs.toStream` | 将集合转化为一个延迟计算的流 |
+| `xs.toSet` | 将集合转化为一个 Set |
+| `xs.toMap` | 将一个(key, value)对的集合转化为一个 Map。 如果当前集合的元素类型不是(key, value)对形式, 则报静态类型错误。 |
+| **Copying:** | |
+| `xs copyToBuffer buf` | 拷贝集合中所有元素到缓存 buf |
+| `xs copyToArray(arr,s,n)` | 从索引 s 开始,将集合中最多 n 个元素复制到数组 arr。 最后两个参数是可选的。 |
+| **Size info:** | |
+| `xs.isEmpty` | 判断集合是否为空 |
+| `xs.nonEmpty` | 判断集合是否包含元素 |
+| `xs.size` | 返回集合中元素的个数 |
+| `xs.hasDefiniteSize` | 如果 xs 具有有限大小,则为真。 |
+| **Element Retrieval:** | |
+| `xs.head` | 返回集合中的第一个元素(如果无序,则随机返回) |
+| `xs.headOption` | 以 Option 的方式返回集合中的第一个元素, 如果集合为空则返回 None |
+| `xs.last` | 返回集合中的最后一个元素(如果无序,则随机返回) |
+| `xs.lastOption` | 以 Option 的方式返回集合中的最后一个元素, 如果集合为空则返回 None |
+| `xs find p` | 以 Option 的方式返回满足条件 p 的第一个元素, 如果都不满足则返回 None |
+| **Subcollection:** | |
+| `xs.tail` | 除了第一个元素之外的其他元素组成的集合 |
+| `xs.init` | 除了最后一个元素之外的其他元素组成的集合 |
+| `xs slice (from, to)` | 返回给定索引范围之内的元素组成的集合 (包含 from 位置的元素但不包含 to 位置的元素) |
+| `xs take n` | 返回 xs 的前 n 个元素组成的集合(如果无序,则返回任意 n 个元素) |
+| `xs drop n` | 返回 xs 的后 n 个元素组成的集合(如果无序,则返回任意 n 个元素) |
+| `xs takeWhile p` | 从第一个元素开始查找满足条件 p 的元素, 直到遇到一个不满足条件的元素,返回所有遍历到的值。 |
+| `xs dropWhile p` | 从第一个元素开始查找满足条件 p 的元素, 直到遇到一个不满足条件的元素,返回所有未遍历到的值。 |
+| `xs filter p` | 返回满足条件 p 的所有元素的集合 |
+| `xs withFilter p` | 集合的非严格的过滤器。后续对 xs 调用方法 map、flatMap 以及 withFilter 都只用作于满足条件 p 的元素,而忽略其他元素 |
+| `xs filterNot p` | 返回不满足条件 p 的所有元素组成的集合 |
+| **Subdivisions:** | |
+| `xs splitAt n` | 在给定位置拆分集合,返回一个集合对 (xs take n, xs drop n) |
+| `xs span p` | 根据给定条件拆分集合,返回一个集合对 (xs takeWhile p, xs dropWhile p)。即遍历元素,直到遇到第一个不符合条件的值则结束遍历,将遍历到的值和未遍历到的值分别放入两个集合返回。 |
+| `xs partition p` | 按照筛选条件对元素进行分组 |
+| `xs groupBy f` | 根据鉴别器函数 f 将 xs 划分为集合映射 |
+| **Element Conditions:** | |
+| `xs forall p` | 判断集合中所有的元素是否都满足条件 p |
+| `xs exists p` | 判断集合中是否存在一个元素满足条件 p |
+| `xs count p` | xs 中满足条件 p 的元素的个数 |
+| **Folds:** | |
+| `(z /: xs) (op)` | 以 z 为初始值,从左到右对 xs 中的元素执行操作为 op 的归约操作 |
+| `(xs :\ z) (op)` | 以 z 为初始值,从右到左对 xs 中的元素执行操作为 op 的归约操作 |
+| `xs.foldLeft(z) (op)` | 同 (z /: xs) (op) |
+| `xs.foldRight(z) (op)` | 同 (xs :\ z) (op) |
+| `xs reduceLeft op` | 从左到右对 xs 中的元素执行操作为 op 的归约操作 |
+| `xs reduceRight op` | 从右到左对 xs 中的元素执行操作为 op 的归约操作 |
+| **Specific Folds:** | |
+| `xs.sum` | 累计求和 |
+| `xs.product` | 累计求积 |
+| `xs.min` | xs 中的最小值 |
+| `xs.max` | xs 中的最大值 |
+| **String:** | |
+| `xs addString (b, start, sep, end)` | 向 StringBuilder b 中添加一个字符串, 该字符串包含 xs 的所有元素。start、seq 和 end 都是可选的,seq 为分隔符,start 为开始符号,end 为结束符号。 |
+| `xs mkString (start, seq, end)` | 将集合转化为一个字符串。start、seq 和 end 都是可选的,seq 为分隔符,start 为开始符号,end 为结束符号。 |
+| `xs.stringPrefix` | 返回 xs.toString 字符串开头的集合名称 |
+| **Views:** | |
+| `xs.view` | 生成 xs 的视图 |
+| `xs view (from, to)` | 生成 xs 上指定索引范围内元素的视图 |
+
+
+
+下面为部分方法的使用示例:
+
+```scala
+scala> List(1, 2, 3, 4, 5, 6).collect { case i if i % 2 == 0 => i * 10 }
+res0: List[Int] = List(20, 40, 60)
+
+scala> List(1, 2, 3, 4, 5, 6).withFilter(_ % 2 == 0).map(_ * 10)
+res1: List[Int] = List(20, 40, 60)
+
+scala> (10 /: List(1, 2, 3)) (_ + _)
+res2: Int = 16
+
+scala> List(1, 2, 3, -4, 5) takeWhile (_ > 0)
+res3: List[Int] = List(1, 2, 3)
+
+scala> List(1, 2, 3, -4, 5) span (_ > 0)
+res4: (List[Int], List[Int]) = (List(1, 2, 3),List(-4, 5))
+
+scala> List(1, 2, 3).mkString("[","-","]")
+res5: String = [1-2-3]
+```
+
+
+
+## 四、Trait Iterable
+
+Scala 中所有的集合都直接或者间接实现了 `Iterable` 特质,`Iterable` 拓展自 `Traversable`,并额外定义了部分方法:
+
+| **方法** | **作用** |
+| ---------------------- | ------------------------------------------------------------ |
+| **Abstract Method:** | |
+| `xs.iterator` | 返回一个迭代器,用于遍历 xs 中的元素, 与 foreach 遍历元素的顺序相同。 |
+| **Other Iterators:** | |
+| `xs grouped size` | 返回一个固定大小的迭代器 |
+| `xs sliding size` | 返回一个固定大小的滑动窗口的迭代器 |
+| **Subcollections:** | |
+| `xs takeRigtht n` | 返回 xs 中最后 n 个元素组成的集合(如果无序,则返回任意 n 个元素组成的集合) |
+| `xs dropRight n` | 返回 xs 中除了最后 n 个元素外的部分 |
+| **Zippers:** | |
+| `xs zip ys` | 返回 xs 和 ys 的对应位置上的元素对组成的集合 |
+| `xs zipAll (ys, x, y)` | 返回 xs 和 ys 的对应位置上的元素对组成的集合。其中较短的序列通过附加元素 x 或 y 来扩展以匹配较长的序列。 |
+| `xs.zipWithIndex` | 返回一个由 xs 中元素及其索引所组成的元素对的集合 |
+| **Comparison:** | |
+| `xs sameElements ys` | 测试 xs 和 ys 是否包含相同顺序的相同元素 |
+
+所有方法示例如下:
+
+```scala
+scala> List(1, 2, 3).iterator.reduce(_ * _ * 10)
+res0: Int = 600
+
+scala> List("a","b","c","d","e") grouped 2 foreach println
+List(a, b)
+List(c, d)
+List(e)
+
+scala> List("a","b","c","d","e") sliding 2 foreach println
+List(a, b)
+List(b, c)
+List(c, d)
+List(d, e)
+
+scala> List("a","b","c","d","e").takeRight(3)
+res1: List[String] = List(c, d, e)
+
+scala> List("a","b","c","d","e").dropRight(3)
+res2: List[String] = List(a, b)
+
+scala> List("a","b","c").zip(List(1,2,3))
+res3: List[(String, Int)] = List((a,1), (b,2), (c,3))
+
+scala> List("a","b","c","d").zipAll(List(1,2,3),"",4)
+res4: List[(String, Int)] = List((a,1), (b,2), (c,3), (d,4))
+
+scala> List("a","b","c").zipAll(List(1,2,3,4),"d","")
+res5: List[(String, Any)] = List((a,1), (b,2), (c,3), (d,4))
+
+scala> List("a", "b", "c").zipWithIndex
+res6: List[(String, Int)] = List((a,0), (b,1), (c,2))
+
+scala> List("a", "b") sameElements List("a", "b")
+res7: Boolean = true
+
+scala> List("a", "b") sameElements List("b", "a")
+res8: Boolean = false
+```
+
+
+
+## 五、修改集合
+
+当你想对集合添加或者删除元素,需要根据不同的集合类型选择不同的操作符号:
+
+| 操作符 | 描述 | 集合类型 |
+| ------------------------------------------------------------ | ------------------------------------------------- | --------------------- |
+| coll(k) 即 coll.apply(k) | 获取指定位置的元素 | Seq, Map |
+| coll :+ elem elem +: coll | 向集合末尾或者集合头增加元素 | Seq |
+| coll + elem coll + (e1, e2, ...) | 追加元素 | Seq, Map |
+| coll - elem coll - (e1, e2, ...) | 删除元素 | Set, Map, ArrayBuffer |
+| coll ++ coll2 coll2 ++: coll | 合并集合 | Iterable |
+| coll -- coll2 | 移除 coll 中包含的 coll2 中的元素 | Set, Map, ArrayBuffer |
+| elem :: lst lst2 :: lst | 把指定列表 (lst2) 或者元素 (elem) 添加到列表 (lst) 头部 | List |
+| list ::: list2 | 合并 List | List |
+| set \| set2 set & set2 set &~ set2 | 并集、交集、差集 | Set |
+| coll += elem coll += (e1, e2, ...) coll ++= coll2 coll -= elem coll -= (e1, e2, ...) coll --= coll2 | 添加或者删除元素,并将修改后的结果赋值给集合本身 | 可变集合 |
+| elem +=: coll coll2 ++=: coll | 在集合头部追加元素或集合 | ArrayBuffer |
+
+
+
+## 参考资料
+
+1. https://docs.scala-lang.org/overviews/collections/overview.html
+2. https://docs.scala-lang.org/overviews/collections/trait-traversable.html
+3. https://docs.scala-lang.org/overviews/collections/trait-iterable.html
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/SparkSQL_Dataset\345\222\214DataFrame\347\256\200\344\273\213.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/SparkSQL_Dataset\345\222\214DataFrame\347\256\200\344\273\213.md"
new file mode 100644
index 0000000..ebb1954
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/SparkSQL_Dataset\345\222\214DataFrame\347\256\200\344\273\213.md"
@@ -0,0 +1,147 @@
+# DataFrame和Dataset简介
+
+
+一、Spark SQL简介
+二、DataFrame & DataSet
+ 2.1 DataFrame
+ 2.2 DataFrame 对比 RDDs
+ 2.3 DataSet
+ 2.4 静态类型与运行时类型安全
+ 2.5 Untyped & Typed
+三、DataFrame & DataSet & RDDs 总结
+四、Spark SQL的运行原理
+ 4.1 逻辑计划(Logical Plan)
+ 4.2 物理计划(Physical Plan)
+ 4.3 执行
+
+
+## 一、Spark SQL简介
+
+Spark SQL 是 Spark 中的一个子模块,主要用于操作结构化数据。它具有以下特点:
+
++ 能够将 SQL 查询与 Spark 程序无缝混合,允许您使用 SQL 或 DataFrame API 对结构化数据进行查询;
++ 支持多种开发语言;
++ 支持多达上百种的外部数据源,包括 Hive,Avro,Parquet,ORC,JSON 和 JDBC 等;
++ 支持 HiveQL 语法以及 Hive SerDes 和 UDF,允许你访问现有的 Hive 仓库;
++ 支持标准的 JDBC 和 ODBC 连接;
++ 支持优化器,列式存储和代码生成等特性;
++ 支持扩展并能保证容错。
+
+
+
+## 二、DataFrame & DataSet
+
+### 2.1 DataFrame
+
+为了支持结构化数据的处理,Spark SQL 提供了新的数据结构 DataFrame。DataFrame 是一个由具名列组成的数据集。它在概念上等同于关系数据库中的表或 R/Python 语言中的 `data frame`。 由于 Spark SQL 支持多种语言的开发,所以每种语言都定义了 `DataFrame` 的抽象,主要如下:
+
+| 语言 | 主要抽象 |
+| ------ | -------------------------------------------- |
+| Scala | Dataset[T] & DataFrame (Dataset[Row] 的别名) |
+| Java | Dataset[T] |
+| Python | DataFrame |
+| R | DataFrame |
+
+### 2.2 DataFrame 对比 RDDs
+
+DataFrame 和 RDDs 最主要的区别在于一个面向的是结构化数据,一个面向的是非结构化数据,它们内部的数据结构如下:
+
+
+
+DataFrame 内部的有明确 Scheme 结构,即列名、列字段类型都是已知的,这带来的好处是可以减少数据读取以及更好地优化执行计划,从而保证查询效率。
+
+**DataFrame 和 RDDs 应该如何选择?**
+
++ 如果你想使用函数式编程而不是 DataFrame API,则使用 RDDs;
++ 如果你的数据是非结构化的 (比如流媒体或者字符流),则使用 RDDs,
++ 如果你的数据是结构化的 (如 RDBMS 中的数据) 或者半结构化的 (如日志),出于性能上的考虑,应优先使用 DataFrame。
+
+### 2.3 DataSet
+
+Dataset 也是分布式的数据集合,在 Spark 1.6 版本被引入,它集成了 RDD 和 DataFrame 的优点,具备强类型的特点,同时支持 Lambda 函数,但只能在 Scala 和 Java 语言中使用。在 Spark 2.0 后,为了方便开发者,Spark 将 DataFrame 和 Dataset 的 API 融合到一起,提供了结构化的 API(Structured API),即用户可以通过一套标准的 API 就能完成对两者的操作。
+
+> 这里注意一下:DataFrame 被标记为 Untyped API,而 DataSet 被标记为 Typed API,后文会对两者做出解释。
+
+
+
+
+
+### 2.4 静态类型与运行时类型安全
+
+静态类型 (Static-typing) 与运行时类型安全 (runtime type-safety) 主要表现如下:
+
+在实际使用中,如果你用的是 Spark SQL 的查询语句,则直到运行时你才会发现有语法错误,而如果你用的是 DataFrame 和 Dataset,则在编译时就可以发现错误 (这节省了开发时间和整体代价)。DataFrame 和 Dataset 主要区别在于:
+
+在 DataFrame 中,当你调用了 API 之外的函数,编译器就会报错,但如果你使用了一个不存在的字段名字,编译器依然无法发现。而 Dataset 的 API 都是用 Lambda 函数和 JVM 类型对象表示的,所有不匹配的类型参数在编译时就会被发现。
+
+以上这些最终都被解释成关于类型安全图谱,对应开发中的语法和分析错误。在图谱中,Dataset 最严格,但对于开发者来说效率最高。
+
+
+
+上面的描述可能并没有那么直观,下面的给出一个 IDEA 中代码编译的示例:
+
+
+
+这里一个可能的疑惑是 DataFrame 明明是有确定的 Scheme 结构 (即列名、列字段类型都是已知的),但是为什么还是无法对列名进行推断和错误判断,这是因为 DataFrame 是 Untyped 的。
+
+### 2.5 Untyped & Typed
+
+在上面我们介绍过 DataFrame API 被标记为 `Untyped API`,而 DataSet API 被标记为 `Typed API`。DataFrame 的 `Untyped` 是相对于语言或 API 层面而言,它确实有明确的 Scheme 结构,即列名,列类型都是确定的,但这些信息完全由 Spark 来维护,Spark 只会在运行时检查这些类型和指定类型是否一致。这也就是为什么在 Spark 2.0 之后,官方推荐把 DataFrame 看做是 `DatSet[Row]`,Row 是 Spark 中定义的一个 `trait`,其子类中封装了列字段的信息。
+
+相对而言,DataSet 是 `Typed` 的,即强类型。如下面代码,DataSet 的类型由 Case Class(Scala) 或者 Java Bean(Java) 来明确指定的,在这里即每一行数据代表一个 `Person`,这些信息由 JVM 来保证正确性,所以字段名错误和类型错误在编译的时候就会被 IDE 所发现。
+
+```scala
+case class Person(name: String, age: Long)
+val dataSet: Dataset[Person] = spark.read.json("people.json").as[Person]
+```
+
+
+
+## 三、DataFrame & DataSet & RDDs 总结
+
+这里对三者做一下简单的总结:
+
++ RDDs 适合非结构化数据的处理,而 DataFrame & DataSet 更适合结构化数据和半结构化的处理;
++ DataFrame & DataSet 可以通过统一的 Structured API 进行访问,而 RDDs 则更适合函数式编程的场景;
++ 相比于 DataFrame 而言,DataSet 是强类型的 (Typed),有着更为严格的静态类型检查;
++ DataSets、DataFrames、SQL 的底层都依赖了 RDDs API,并对外提供结构化的访问接口。
+
+
+
+
+
+## 四、Spark SQL的运行原理
+
+DataFrame、DataSet 和 Spark SQL 的实际执行流程都是相同的:
+
+1. 进行 DataFrame/Dataset/SQL 编程;
+2. 如果是有效的代码,即代码没有编译错误,Spark 会将其转换为一个逻辑计划;
+3. Spark 将此逻辑计划转换为物理计划,同时进行代码优化;
+4. Spark 然后在集群上执行这个物理计划 (基于 RDD 操作) 。
+
+### 4.1 逻辑计划(Logical Plan)
+
+执行的第一个阶段是将用户代码转换成一个逻辑计划。它首先将用户代码转换成 `unresolved logical plan`(未解决的逻辑计划),之所以这个计划是未解决的,是因为尽管您的代码在语法上是正确的,但是它引用的表或列可能不存在。 Spark 使用 `analyzer`(分析器) 基于 `catalog`(存储的所有表和 `DataFrames` 的信息) 进行解析。解析失败则拒绝执行,解析成功则将结果传给 `Catalyst` 优化器 (`Catalyst Optimizer`),优化器是一组规则的集合,用于优化逻辑计划,通过谓词下推等方式进行优化,最终输出优化后的逻辑执行计划。
+
+
+
+
+
+### 4.2 物理计划(Physical Plan)
+
+得到优化后的逻辑计划后,Spark 就开始了物理计划过程。 它通过生成不同的物理执行策略,并通过成本模型来比较它们,从而选择一个最优的物理计划在集群上面执行的。物理规划的输出结果是一系列的 RDDs 和转换关系 (transformations)。
+
+
+
+### 4.3 执行
+
+在选择一个物理计划后,Spark 运行其 RDDs 代码,并在运行时执行进一步的优化,生成本地 Java 字节码,最后将运行结果返回给用户。
+
+
+
+## 参考资料
+
+1. Matei Zaharia, Bill Chambers . Spark: The Definitive Guide[M] . 2018-02
+2. [Spark SQL, DataFrames and Datasets Guide](https://spark.apache.org/docs/latest/sql-programming-guide.html)
+3. [且谈 Apache Spark 的 API 三剑客:RDD、DataFrame 和 Dataset(译文)](https://www.infoq.cn/article/three-apache-spark-apis-rdds-dataframes-and-datasets)
+4. [A Tale of Three Apache Spark APIs: RDDs vs DataFrames and Datasets(原文)](https://databricks.com/blog/2016/07/14/a-tale-of-three-apache-spark-apis-rdds-dataframes-and-datasets.html)
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/SparkSQL\345\244\226\351\203\250\346\225\260\346\215\256\346\272\220.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/SparkSQL\345\244\226\351\203\250\346\225\260\346\215\256\346\272\220.md"
new file mode 100644
index 0000000..76ace06
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/SparkSQL\345\244\226\351\203\250\346\225\260\346\215\256\346\272\220.md"
@@ -0,0 +1,499 @@
+# Spark SQL 外部数据源
+
+
+一、简介
+ 1.1 多数据源支持
+ 1.2 读数据格式
+ 1.3 写数据格式
+二、CSV
+ 2.1 读取CSV文件
+ 2.2 写入CSV文件
+ 2.3 可选配置
+三、JSON
+ 3.1 读取JSON文件
+ 3.2 写入JSON文件
+ 3.3 可选配置
+四、Parquet
+ 4.1 读取Parquet文件
+ 2.2 写入Parquet文件
+ 2.3 可选配置
+五、ORC
+ 5.1 读取ORC文件
+ 4.2 写入ORC文件
+六、SQL Databases
+ 6.1 读取数据
+ 6.2 写入数据
+七、Text
+ 7.1 读取Text数据
+ 7.2 写入Text数据
+八、数据读写高级特性
+ 8.1 并行读
+ 8.2 并行写
+ 8.3 分区写入
+ 8.3 分桶写入
+ 8.5 文件大小管理
+九、可选配置附录
+ 9.1 CSV读写可选配置
+ 9.2 JSON读写可选配置
+ 9.3 数据库读写可选配置
+
+
+## 一、简介
+
+### 1.1 多数据源支持
+
+Spark 支持以下六个核心数据源,同时 Spark 社区还提供了多达上百种数据源的读取方式,能够满足绝大部分使用场景。
+
+- CSV
+- JSON
+- Parquet
+- ORC
+- JDBC/ODBC connections
+- Plain-text files
+
+> 注:以下所有测试文件均可从本仓库的[resources](https://github.com/heibaiying/BigData-Notes/tree/master/resources) 目录进行下载
+
+### 1.2 读数据格式
+
+所有读取 API 遵循以下调用格式:
+
+```scala
+// 格式
+DataFrameReader.format(...).option("key", "value").schema(...).load()
+
+// 示例
+spark.read.format("csv")
+.option("mode", "FAILFAST") // 读取模式
+.option("inferSchema", "true") // 是否自动推断 schema
+.option("path", "path/to/file(s)") // 文件路径
+.schema(someSchema) // 使用预定义的 schema
+.load()
+```
+
+读取模式有以下三种可选项:
+
+| 读模式 | 描述 |
+| --------------- | ------------------------------------------------------------ |
+| `permissive` | 当遇到损坏的记录时,将其所有字段设置为 null,并将所有损坏的记录放在名为 _corruption t_record 的字符串列中 |
+| `dropMalformed` | 删除格式不正确的行 |
+| `failFast` | 遇到格式不正确的数据时立即失败 |
+
+### 1.3 写数据格式
+
+```scala
+// 格式
+DataFrameWriter.format(...).option(...).partitionBy(...).bucketBy(...).sortBy(...).save()
+
+//示例
+dataframe.write.format("csv")
+.option("mode", "OVERWRITE") //写模式
+.option("dateFormat", "yyyy-MM-dd") //日期格式
+.option("path", "path/to/file(s)")
+.save()
+```
+
+写数据模式有以下四种可选项:
+
+| Scala/Java | 描述 |
+| :----------------------- | :----------------------------------------------------------- |
+| `SaveMode.ErrorIfExists` | 如果给定的路径已经存在文件,则抛出异常,这是写数据默认的模式 |
+| `SaveMode.Append` | 数据以追加的方式写入 |
+| `SaveMode.Overwrite` | 数据以覆盖的方式写入 |
+| `SaveMode.Ignore` | 如果给定的路径已经存在文件,则不做任何操作 |
+
+
+
+## 二、CSV
+
+CSV 是一种常见的文本文件格式,其中每一行表示一条记录,记录中的每个字段用逗号分隔。
+
+### 2.1 读取CSV文件
+
+自动推断类型读取读取示例:
+
+```scala
+spark.read.format("csv")
+.option("header", "false") // 文件中的第一行是否为列的名称
+.option("mode", "FAILFAST") // 是否快速失败
+.option("inferSchema", "true") // 是否自动推断 schema
+.load("/usr/file/csv/dept.csv")
+.show()
+```
+
+使用预定义类型:
+
+```scala
+import org.apache.spark.sql.types.{StructField, StructType, StringType,LongType}
+//预定义数据格式
+val myManualSchema = new StructType(Array(
+ StructField("deptno", LongType, nullable = false),
+ StructField("dname", StringType,nullable = true),
+ StructField("loc", StringType,nullable = true)
+))
+spark.read.format("csv")
+.option("mode", "FAILFAST")
+.schema(myManualSchema)
+.load("/usr/file/csv/dept.csv")
+.show()
+```
+
+### 2.2 写入CSV文件
+
+```scala
+df.write.format("csv").mode("overwrite").save("/tmp/csv/dept2")
+```
+
+也可以指定具体的分隔符:
+
+```scala
+df.write.format("csv").mode("overwrite").option("sep", "\t").save("/tmp/csv/dept2")
+```
+
+### 2.3 可选配置
+
+为节省主文篇幅,所有读写配置项见文末 9.1 小节。
+
+
+
+## 三、JSON
+
+### 3.1 读取JSON文件
+
+```json
+spark.read.format("json").option("mode", "FAILFAST").load("/usr/file/json/dept.json").show(5)
+```
+
+需要注意的是:默认不支持一条数据记录跨越多行 (如下),可以通过配置 `multiLine` 为 `true` 来进行更改,其默认值为 `false`。
+
+```json
+// 默认支持单行
+{"DEPTNO": 10,"DNAME": "ACCOUNTING","LOC": "NEW YORK"}
+
+//默认不支持多行
+{
+ "DEPTNO": 10,
+ "DNAME": "ACCOUNTING",
+ "LOC": "NEW YORK"
+}
+```
+
+### 3.2 写入JSON文件
+
+```scala
+df.write.format("json").mode("overwrite").save("/tmp/spark/json/dept")
+```
+
+### 3.3 可选配置
+
+为节省主文篇幅,所有读写配置项见文末 9.2 小节。
+
+
+
+## 四、Parquet
+
+ Parquet 是一个开源的面向列的数据存储,它提供了多种存储优化,允许读取单独的列非整个文件,这不仅节省了存储空间而且提升了读取效率,它是 Spark 是默认的文件格式。
+
+### 4.1 读取Parquet文件
+
+```scala
+spark.read.format("parquet").load("/usr/file/parquet/dept.parquet").show(5)
+```
+
+### 2.2 写入Parquet文件
+
+```scala
+df.write.format("parquet").mode("overwrite").save("/tmp/spark/parquet/dept")
+```
+
+### 2.3 可选配置
+
+Parquet 文件有着自己的存储规则,因此其可选配置项比较少,常用的有如下两个:
+
+| 读写操作 | 配置项 | 可选值 | 默认值 | 描述 |
+| -------- | -------------------- | ------------------------------------------------------------ | ------------------------------------------- | ------------------------------------------------------------ |
+| Write | compression or codec | None, uncompressed, bzip2, deflate, gzip, lz4, or snappy | None | 压缩文件格式 |
+| Read | mergeSchema | true, false | 取决于配置项 `spark.sql.parquet.mergeSchema` | 当为真时,Parquet 数据源将所有数据文件收集的 Schema 合并在一起,否则将从摘要文件中选择 Schema,如果没有可用的摘要文件,则从随机数据文件中选择 Schema。 |
+
+> 更多可选配置可以参阅官方文档:https://spark.apache.org/docs/latest/sql-data-sources-parquet.html
+
+
+
+## 五、ORC
+
+ORC 是一种自描述的、类型感知的列文件格式,它针对大型数据的读写进行了优化,也是大数据中常用的文件格式。
+
+### 5.1 读取ORC文件
+
+```scala
+spark.read.format("orc").load("/usr/file/orc/dept.orc").show(5)
+```
+
+### 4.2 写入ORC文件
+
+```scala
+csvFile.write.format("orc").mode("overwrite").save("/tmp/spark/orc/dept")
+```
+
+
+
+## 六、SQL Databases
+
+Spark 同样支持与传统的关系型数据库进行数据读写。但是 Spark 程序默认是没有提供数据库驱动的,所以在使用前需要将对应的数据库驱动上传到安装目录下的 `jars` 目录中。下面示例使用的是 Mysql 数据库,使用前需要将对应的 `mysql-connector-java-x.x.x.jar` 上传到 `jars` 目录下。
+
+### 6.1 读取数据
+
+读取全表数据示例如下,这里的 `help_keyword` 是 mysql 内置的字典表,只有 `help_keyword_id` 和 `name` 两个字段。
+
+```scala
+spark.read
+.format("jdbc")
+.option("driver", "com.mysql.jdbc.Driver") //驱动
+.option("url", "jdbc:mysql://127.0.0.1:3306/mysql") //数据库地址
+.option("dbtable", "help_keyword") //表名
+.option("user", "root").option("password","root").load().show(10)
+```
+
+从查询结果读取数据:
+
+```scala
+val pushDownQuery = """(SELECT * FROM help_keyword WHERE help_keyword_id <20) AS help_keywords"""
+spark.read.format("jdbc")
+.option("url", "jdbc:mysql://127.0.0.1:3306/mysql")
+.option("driver", "com.mysql.jdbc.Driver")
+.option("user", "root").option("password", "root")
+.option("dbtable", pushDownQuery)
+.load().show()
+
+//输出
++---------------+-----------+
+|help_keyword_id| name|
++---------------+-----------+
+| 0| <>|
+| 1| ACTION|
+| 2| ADD|
+| 3|AES_DECRYPT|
+| 4|AES_ENCRYPT|
+| 5| AFTER|
+| 6| AGAINST|
+| 7| AGGREGATE|
+| 8| ALGORITHM|
+| 9| ALL|
+| 10| ALTER|
+| 11| ANALYSE|
+| 12| ANALYZE|
+| 13| AND|
+| 14| ARCHIVE|
+| 15| AREA|
+| 16| AS|
+| 17| ASBINARY|
+| 18| ASC|
+| 19| ASTEXT|
++---------------+-----------+
+```
+
+也可以使用如下的写法进行数据的过滤:
+
+```scala
+val props = new java.util.Properties
+props.setProperty("driver", "com.mysql.jdbc.Driver")
+props.setProperty("user", "root")
+props.setProperty("password", "root")
+val predicates = Array("help_keyword_id < 10 OR name = 'WHEN'") //指定数据过滤条件
+spark.read.jdbc("jdbc:mysql://127.0.0.1:3306/mysql", "help_keyword", predicates, props).show()
+
+//输出:
++---------------+-----------+
+|help_keyword_id| name|
++---------------+-----------+
+| 0| <>|
+| 1| ACTION|
+| 2| ADD|
+| 3|AES_DECRYPT|
+| 4|AES_ENCRYPT|
+| 5| AFTER|
+| 6| AGAINST|
+| 7| AGGREGATE|
+| 8| ALGORITHM|
+| 9| ALL|
+| 604| WHEN|
++---------------+-----------+
+```
+
+可以使用 `numPartitions` 指定读取数据的并行度:
+
+```scala
+option("numPartitions", 10)
+```
+
+在这里,除了可以指定分区外,还可以设置上界和下界,任何小于下界的值都会被分配在第一个分区中,任何大于上界的值都会被分配在最后一个分区中。
+
+```scala
+val colName = "help_keyword_id" //用于判断上下界的列
+val lowerBound = 300L //下界
+val upperBound = 500L //上界
+val numPartitions = 10 //分区综述
+val jdbcDf = spark.read.jdbc("jdbc:mysql://127.0.0.1:3306/mysql","help_keyword",
+ colName,lowerBound,upperBound,numPartitions,props)
+```
+
+想要验证分区内容,可以使用 `mapPartitionsWithIndex` 这个算子,代码如下:
+
+```scala
+jdbcDf.rdd.mapPartitionsWithIndex((index, iterator) => {
+ val buffer = new ListBuffer[String]
+ while (iterator.hasNext) {
+ buffer.append(index + "分区:" + iterator.next())
+ }
+ buffer.toIterator
+}).foreach(println)
+```
+
+执行结果如下:`help_keyword` 这张表只有 600 条左右的数据,本来数据应该均匀分布在 10 个分区,但是 0 分区里面却有 319 条数据,这是因为设置了下限,所有小于 300 的数据都会被限制在第一个分区,即 0 分区。同理所有大于 500 的数据被分配在 9 分区,即最后一个分区。
+
+
+
+### 6.2 写入数据
+
+```scala
+val df = spark.read.format("json").load("/usr/file/json/emp.json")
+df.write
+.format("jdbc")
+.option("url", "jdbc:mysql://127.0.0.1:3306/mysql")
+.option("user", "root").option("password", "root")
+.option("dbtable", "emp")
+.save()
+```
+
+
+
+## 七、Text
+
+Text 文件在读写性能方面并没有任何优势,且不能表达明确的数据结构,所以其使用的比较少,读写操作如下:
+
+### 7.1 读取Text数据
+
+```scala
+spark.read.textFile("/usr/file/txt/dept.txt").show()
+```
+
+### 7.2 写入Text数据
+
+```scala
+df.write.text("/tmp/spark/txt/dept")
+```
+
+
+
+## 八、数据读写高级特性
+
+### 8.1 并行读
+
+多个 Executors 不能同时读取同一个文件,但它们可以同时读取不同的文件。这意味着当您从一个包含多个文件的文件夹中读取数据时,这些文件中的每一个都将成为 DataFrame 中的一个分区,并由可用的 Executors 并行读取。
+
+### 8.2 并行写
+
+写入的文件或数据的数量取决于写入数据时 DataFrame 拥有的分区数量。默认情况下,每个数据分区写一个文件。
+
+### 8.3 分区写入
+
+分区和分桶这两个概念和 Hive 中分区表和分桶表是一致的。都是将数据按照一定规则进行拆分存储。需要注意的是 `partitionBy` 指定的分区和 RDD 中分区不是一个概念:这里的**分区表现为输出目录的子目录**,数据分别存储在对应的子目录中。
+
+```scala
+val df = spark.read.format("json").load("/usr/file/json/emp.json")
+df.write.mode("overwrite").partitionBy("deptno").save("/tmp/spark/partitions")
+```
+
+输出结果如下:可以看到输出被按照部门编号分为三个子目录,子目录中才是对应的输出文件。
+
+
+
+### 8.3 分桶写入
+
+分桶写入就是将数据按照指定的列和桶数进行散列,目前分桶写入只支持保存为表,实际上这就是 Hive 的分桶表。
+
+```scala
+val numberBuckets = 10
+val columnToBucketBy = "empno"
+df.write.format("parquet").mode("overwrite")
+.bucketBy(numberBuckets, columnToBucketBy).saveAsTable("bucketedFiles")
+```
+
+### 8.5 文件大小管理
+
+如果写入产生小文件数量过多,这时会产生大量的元数据开销。Spark 和 HDFS 一样,都不能很好的处理这个问题,这被称为“small file problem”。同时数据文件也不能过大,否则在查询时会有不必要的性能开销,因此要把文件大小控制在一个合理的范围内。
+
+在上文我们已经介绍过可以通过分区数量来控制生成文件的数量,从而间接控制文件大小。Spark 2.2 引入了一种新的方法,以更自动化的方式控制文件大小,这就是 `maxRecordsPerFile` 参数,它允许你通过控制写入文件的记录数来控制文件大小。
+
+```scala
+ // Spark 将确保文件最多包含 5000 条记录
+df.write.option(“maxRecordsPerFile”, 5000)
+```
+
+
+
+## 九、可选配置附录
+
+### 9.1 CSV读写可选配置
+
+| 读\写操作 | 配置项 | 可选值 | 默认值 | 描述 |
+| --------- | --------------------------- | ------------------------------------------------------------ | -------------------------- | ------------------------------------------------------------ |
+| Both | seq | 任意字符 | `,`(逗号) | 分隔符 |
+| Both | header | true, false | false | 文件中的第一行是否为列的名称。 |
+| Read | escape | 任意字符 | \ | 转义字符 |
+| Read | inferSchema | true, false | false | 是否自动推断列类型 |
+| Read | ignoreLeadingWhiteSpace | true, false | false | 是否跳过值前面的空格 |
+| Both | ignoreTrailingWhiteSpace | true, false | false | 是否跳过值后面的空格 |
+| Both | nullValue | 任意字符 | “” | 声明文件中哪个字符表示空值 |
+| Both | nanValue | 任意字符 | NaN | 声明哪个值表示 NaN 或者缺省值 |
+| Both | positiveInf | 任意字符 | Inf | 正无穷 |
+| Both | negativeInf | 任意字符 | -Inf | 负无穷 |
+| Both | compression or codec | None, uncompressed, bzip2, deflate, gzip, lz4, or snappy | none | 文件压缩格式 |
+| Both | dateFormat | 任何能转换为 Java 的 SimpleDataFormat 的字符串 | yyyy-MM-dd | 日期格式 |
+| Both | timestampFormat | 任何能转换为 Java 的 SimpleDataFormat 的字符串 | yyyy-MMdd’T’HH:mm:ss.SSSZZ | 时间戳格式 |
+| Read | maxColumns | 任意整数 | 20480 | 声明文件中的最大列数 |
+| Read | maxCharsPerColumn | 任意整数 | 1000000 | 声明一个列中的最大字符数。 |
+| Read | escapeQuotes | true, false | true | 是否应该转义行中的引号。 |
+| Read | maxMalformedLogPerPartition | 任意整数 | 10 | 声明每个分区中最多允许多少条格式错误的数据,超过这个值后格式错误的数据将不会被读取 |
+| Write | quoteAll | true, false | false | 指定是否应该将所有值都括在引号中,而不只是转义具有引号字符的值。 |
+| Read | multiLine | true, false | false | 是否允许每条完整记录跨域多行 |
+
+### 9.2 JSON读写可选配置
+
+| 读\写操作 | 配置项 | 可选值 | 默认值 |
+| --------- | ---------------------------------- | ------------------------------------------------------------ | -------------------------------- |
+| Both | compression or codec | None, uncompressed, bzip2, deflate, gzip, lz4, or snappy | none |
+| Both | dateFormat | 任何能转换为 Java 的 SimpleDataFormat 的字符串 | yyyy-MM-dd |
+| Both | timestampFormat | 任何能转换为 Java 的 SimpleDataFormat 的字符串 | yyyy-MMdd’T’HH:mm:ss.SSSZZ |
+| Read | primitiveAsString | true, false | false |
+| Read | allowComments | true, false | false |
+| Read | allowUnquotedFieldNames | true, false | false |
+| Read | allowSingleQuotes | true, false | true |
+| Read | allowNumericLeadingZeros | true, false | false |
+| Read | allowBackslashEscapingAnyCharacter | true, false | false |
+| Read | columnNameOfCorruptRecord | true, false | Value of spark.sql.column&NameOf |
+| Read | multiLine | true, false | false |
+
+### 9.3 数据库读写可选配置
+
+| 属性名称 | 含义 |
+| ------------------------------------------ | ------------------------------------------------------------ |
+| url | 数据库地址 |
+| dbtable | 表名称 |
+| driver | 数据库驱动 |
+| partitionColumn, lowerBound, upperBoun | 分区总数,上界,下界 |
+| numPartitions | 可用于表读写并行性的最大分区数。如果要写的分区数量超过这个限制,那么可以调用 coalesce(numpartition) 重置分区数。 |
+| fetchsize | 每次往返要获取多少行数据。此选项仅适用于读取数据。 |
+| batchsize | 每次往返插入多少行数据,这个选项只适用于写入数据。默认值是 1000。 |
+| isolationLevel | 事务隔离级别:可以是 NONE,READ_COMMITTED, READ_UNCOMMITTED,REPEATABLE_READ 或 SERIALIZABLE,即标准事务隔离级别。 默认值是 READ_UNCOMMITTED。这个选项只适用于数据读取。 |
+| createTableOptions | 写入数据时自定义创建表的相关配置 |
+| createTableColumnTypes | 写入数据时自定义创建列的列类型 |
+
+> 数据库读写更多配置可以参阅官方文档:https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html
+
+
+
+## 参考资料
+
+1. Matei Zaharia, Bill Chambers . Spark: The Definitive Guide[M] . 2018-02
+2. https://spark.apache.org/docs/latest/sql-data-sources.html
+
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/SparkSQL\345\270\270\347\224\250\350\201\232\345\220\210\345\207\275\346\225\260.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/SparkSQL\345\270\270\347\224\250\350\201\232\345\220\210\345\207\275\346\225\260.md"
new file mode 100644
index 0000000..0d0ea52
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/SparkSQL\345\270\270\347\224\250\350\201\232\345\220\210\345\207\275\346\225\260.md"
@@ -0,0 +1,339 @@
+# 聚合函数Aggregations
+
+
+一、简单聚合
+ 1.1 数据准备
+ 1.2 count
+ 1.3 countDistinct
+ 1.4 approx_count_distinct
+ 1.5 first & last
+ 1.6 min & max
+ 1.7 sum & sumDistinct
+ 1.8 avg
+ 1.9 数学函数
+ 1.10 聚合数据到集合
+二、分组聚合
+ 2.1 简单分组
+ 2.2 分组聚合
+三、自定义聚合函数
+ 3.1 有类型的自定义函数
+ 3.2 无类型的自定义聚合函数
+
+
+
+## 一、简单聚合
+
+### 1.1 数据准备
+
+```scala
+// 需要导入 spark sql 内置的函数包
+import org.apache.spark.sql.functions._
+
+val spark = SparkSession.builder().appName("aggregations").master("local[2]").getOrCreate()
+val empDF = spark.read.json("/usr/file/json/emp.json")
+// 注册为临时视图,用于后面演示 SQL 查询
+empDF.createOrReplaceTempView("emp")
+empDF.show()
+```
+
+> 注:emp.json 可以从本仓库的[resources](https://github.com/heibaiying/BigData-Notes/tree/master/resources) 目录下载。
+
+### 1.2 count
+
+```scala
+// 计算员工人数
+empDF.select(count("ename")).show()
+```
+
+### 1.3 countDistinct
+
+```scala
+// 计算姓名不重复的员工人数
+empDF.select(countDistinct("deptno")).show()
+```
+
+### 1.4 approx_count_distinct
+
+通常在使用大型数据集时,你可能关注的只是近似值而不是准确值,这时可以使用 approx_count_distinct 函数,并可以使用第二个参数指定最大允许误差。
+
+```scala
+empDF.select(approx_count_distinct ("ename",0.1)).show()
+```
+
+### 1.5 first & last
+
+获取 DataFrame 中指定列的第一个值或者最后一个值。
+
+```scala
+empDF.select(first("ename"),last("job")).show()
+```
+
+### 1.6 min & max
+
+获取 DataFrame 中指定列的最小值或者最大值。
+
+```scala
+empDF.select(min("sal"),max("sal")).show()
+```
+
+### 1.7 sum & sumDistinct
+
+求和以及求指定列所有不相同的值的和。
+
+```scala
+empDF.select(sum("sal")).show()
+empDF.select(sumDistinct("sal")).show()
+```
+
+### 1.8 avg
+
+内置的求平均数的函数。
+
+```scala
+empDF.select(avg("sal")).show()
+```
+
+### 1.9 数学函数
+
+Spark SQL 中还支持多种数学聚合函数,用于通常的数学计算,以下是一些常用的例子:
+
+```scala
+// 1.计算总体方差、均方差、总体标准差、样本标准差
+empDF.select(var_pop("sal"), var_samp("sal"), stddev_pop("sal"), stddev_samp("sal")).show()
+
+// 2.计算偏度和峰度
+empDF.select(skewness("sal"), kurtosis("sal")).show()
+
+// 3. 计算两列的皮尔逊相关系数、样本协方差、总体协方差。(这里只是演示,员工编号和薪资两列实际上并没有什么关联关系)
+empDF.select(corr("empno", "sal"), covar_samp("empno", "sal"),covar_pop("empno", "sal")).show()
+```
+
+### 1.10 聚合数据到集合
+
+```scala
+scala> empDF.agg(collect_set("job"), collect_list("ename")).show()
+
+输出:
++--------------------+--------------------+
+| collect_set(job)| collect_list(ename)|
++--------------------+--------------------+
+|[MANAGER, SALESMA...|[SMITH, ALLEN, WA...|
++--------------------+--------------------+
+```
+
+
+
+## 二、分组聚合
+
+### 2.1 简单分组
+
+```scala
+empDF.groupBy("deptno", "job").count().show()
+//等价 SQL
+spark.sql("SELECT deptno, job, count(*) FROM emp GROUP BY deptno, job").show()
+
+输出:
++------+---------+-----+
+|deptno| job|count|
++------+---------+-----+
+| 10|PRESIDENT| 1|
+| 30| CLERK| 1|
+| 10| MANAGER| 1|
+| 30| MANAGER| 1|
+| 20| CLERK| 2|
+| 30| SALESMAN| 4|
+| 20| ANALYST| 2|
+| 10| CLERK| 1|
+| 20| MANAGER| 1|
++------+---------+-----+
+```
+
+### 2.2 分组聚合
+
+```scala
+empDF.groupBy("deptno").agg(count("ename").alias("人数"), sum("sal").alias("总工资")).show()
+// 等价语法
+empDF.groupBy("deptno").agg("ename"->"count","sal"->"sum").show()
+// 等价 SQL
+spark.sql("SELECT deptno, count(ename) ,sum(sal) FROM emp GROUP BY deptno").show()
+
+输出:
++------+----+------+
+|deptno|人数|总工资|
++------+----+------+
+| 10| 3|8750.0|
+| 30| 6|9400.0|
+| 20| 5|9375.0|
++------+----+------+
+```
+
+
+
+## 三、自定义聚合函数
+
+Scala 提供了两种自定义聚合函数的方法,分别如下:
+
+- 有类型的自定义聚合函数,主要适用于 DataSet;
+- 无类型的自定义聚合函数,主要适用于 DataFrame。
+
+以下分别使用两种方式来自定义一个求平均值的聚合函数,这里以计算员工平均工资为例。两种自定义方式分别如下:
+
+### 3.1 有类型的自定义函数
+
+```scala
+import org.apache.spark.sql.expressions.Aggregator
+import org.apache.spark.sql.{Encoder, Encoders, SparkSession, functions}
+
+// 1.定义员工类,对于可能存在 null 值的字段需要使用 Option 进行包装
+case class Emp(ename: String, comm: scala.Option[Double], deptno: Long, empno: Long,
+ hiredate: String, job: String, mgr: scala.Option[Long], sal: Double)
+
+// 2.定义聚合操作的中间输出类型
+case class SumAndCount(var sum: Double, var count: Long)
+
+/* 3.自定义聚合函数
+ * @IN 聚合操作的输入类型
+ * @BUF reduction 操作输出值的类型
+ * @OUT 聚合操作的输出类型
+ */
+object MyAverage extends Aggregator[Emp, SumAndCount, Double] {
+
+ // 4.用于聚合操作的的初始零值
+ override def zero: SumAndCount = SumAndCount(0, 0)
+
+ // 5.同一分区中的 reduce 操作
+ override def reduce(avg: SumAndCount, emp: Emp): SumAndCount = {
+ avg.sum += emp.sal
+ avg.count += 1
+ avg
+ }
+
+ // 6.不同分区中的 merge 操作
+ override def merge(avg1: SumAndCount, avg2: SumAndCount): SumAndCount = {
+ avg1.sum += avg2.sum
+ avg1.count += avg2.count
+ avg1
+ }
+
+ // 7.定义最终的输出类型
+ override def finish(reduction: SumAndCount): Double = reduction.sum / reduction.count
+
+ // 8.中间类型的编码转换
+ override def bufferEncoder: Encoder[SumAndCount] = Encoders.product
+
+ // 9.输出类型的编码转换
+ override def outputEncoder: Encoder[Double] = Encoders.scalaDouble
+}
+
+object SparkSqlApp {
+
+ // 测试方法
+ def main(args: Array[String]): Unit = {
+
+ val spark = SparkSession.builder().appName("Spark-SQL").master("local[2]").getOrCreate()
+ import spark.implicits._
+ val ds = spark.read.json("file/emp.json").as[Emp]
+
+ // 10.使用内置 avg() 函数和自定义函数分别进行计算,验证自定义函数是否正确
+ val myAvg = ds.select(MyAverage.toColumn.name("average_sal")).first()
+ val avg = ds.select(functions.avg(ds.col("sal"))).first().get(0)
+
+ println("自定义 average 函数 : " + myAvg)
+ println("内置的 average 函数 : " + avg)
+ }
+}
+```
+
+自定义聚合函数需要实现的方法比较多,这里以绘图的方式来演示其执行流程,以及每个方法的作用:
+
+
+
+
+
+关于 `zero`,`reduce`,`merge`,`finish` 方法的作用在上图都有说明,这里解释一下中间类型和输出类型的编码转换,这个写法比较固定,基本上就是两种情况:
+
+- 自定义类型 Case Class 或者元组就使用 `Encoders.product` 方法;
+- 基本类型就使用其对应名称的方法,如 `scalaByte `,`scalaFloat`,`scalaShort` 等,示例如下:
+
+```scala
+override def bufferEncoder: Encoder[SumAndCount] = Encoders.product
+override def outputEncoder: Encoder[Double] = Encoders.scalaDouble
+```
+
+
+
+### 3.2 无类型的自定义聚合函数
+
+理解了有类型的自定义聚合函数后,无类型的定义方式也基本相同,代码如下:
+
+```scala
+import org.apache.spark.sql.expressions.{MutableAggregationBuffer, UserDefinedAggregateFunction}
+import org.apache.spark.sql.types._
+import org.apache.spark.sql.{Row, SparkSession}
+
+object MyAverage extends UserDefinedAggregateFunction {
+ // 1.聚合操作输入参数的类型,字段名称可以自定义
+ def inputSchema: StructType = StructType(StructField("MyInputColumn", LongType) :: Nil)
+
+ // 2.聚合操作中间值的类型,字段名称可以自定义
+ def bufferSchema: StructType = {
+ StructType(StructField("sum", LongType) :: StructField("MyCount", LongType) :: Nil)
+ }
+
+ // 3.聚合操作输出参数的类型
+ def dataType: DataType = DoubleType
+
+ // 4.此函数是否始终在相同输入上返回相同的输出,通常为 true
+ def deterministic: Boolean = true
+
+ // 5.定义零值
+ def initialize(buffer: MutableAggregationBuffer): Unit = {
+ buffer(0) = 0L
+ buffer(1) = 0L
+ }
+
+ // 6.同一分区中的 reduce 操作
+ def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
+ if (!input.isNullAt(0)) {
+ buffer(0) = buffer.getLong(0) + input.getLong(0)
+ buffer(1) = buffer.getLong(1) + 1
+ }
+ }
+
+ // 7.不同分区中的 merge 操作
+ def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
+ buffer1(0) = buffer1.getLong(0) + buffer2.getLong(0)
+ buffer1(1) = buffer1.getLong(1) + buffer2.getLong(1)
+ }
+
+ // 8.计算最终的输出值
+ def evaluate(buffer: Row): Double = buffer.getLong(0).toDouble / buffer.getLong(1)
+}
+
+object SparkSqlApp {
+
+ // 测试方法
+ def main(args: Array[String]): Unit = {
+
+ val spark = SparkSession.builder().appName("Spark-SQL").master("local[2]").getOrCreate()
+ // 9.注册自定义的聚合函数
+ spark.udf.register("myAverage", MyAverage)
+
+ val df = spark.read.json("file/emp.json")
+ df.createOrReplaceTempView("emp")
+
+ // 10.使用自定义函数和内置函数分别进行计算
+ val myAvg = spark.sql("SELECT myAverage(sal) as avg_sal FROM emp").first()
+ val avg = spark.sql("SELECT avg(sal) as avg_sal FROM emp").first()
+
+ println("自定义 average 函数 : " + myAvg)
+ println("内置的 average 函数 : " + avg)
+ }
+}
+```
+
+
+
+## 参考资料
+
+1. Matei Zaharia, Bill Chambers . Spark: The Definitive Guide[M] . 2018-02
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/SparkSQL\350\201\224\347\273\223\346\223\215\344\275\234.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/SparkSQL\350\201\224\347\273\223\346\223\215\344\275\234.md"
new file mode 100644
index 0000000..9eeced7
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/SparkSQL\350\201\224\347\273\223\346\223\215\344\275\234.md"
@@ -0,0 +1,185 @@
+# Spark SQL JOIN
+
+
+一、 数据准备
+二、连接类型
+ 2.1 INNER JOIN
+ 2.2 FULL OUTER JOIN
+ 2.3 LEFT OUTER JOIN
+ 2.4 RIGHT OUTER JOIN
+ 2.5 LEFT SEMI JOIN
+ 2.6 LEFT ANTI JOIN
+ 2.7 CROSS JOIN
+ 2.8 NATURAL JOIN
+三、连接的执行
+
+
+## 一、 数据准备
+
+本文主要介绍 Spark SQL 的多表连接,需要预先准备测试数据。分别创建员工和部门的 Datafame,并注册为临时视图,代码如下:
+
+```scala
+val spark = SparkSession.builder().appName("aggregations").master("local[2]").getOrCreate()
+
+val empDF = spark.read.json("/usr/file/json/emp.json")
+empDF.createOrReplaceTempView("emp")
+
+val deptDF = spark.read.json("/usr/file/json/dept.json")
+deptDF.createOrReplaceTempView("dept")
+```
+
+两表的主要字段如下:
+
+```properties
+emp 员工表
+ |-- ENAME: 员工姓名
+ |-- DEPTNO: 部门编号
+ |-- EMPNO: 员工编号
+ |-- HIREDATE: 入职时间
+ |-- JOB: 职务
+ |-- MGR: 上级编号
+ |-- SAL: 薪资
+ |-- COMM: 奖金
+```
+
+```properties
+dept 部门表
+ |-- DEPTNO: 部门编号
+ |-- DNAME: 部门名称
+ |-- LOC: 部门所在城市
+```
+
+> 注:emp.json,dept.json 可以在本仓库的[resources](https://github.com/heibaiying/BigData-Notes/tree/master/resources) 目录进行下载。
+
+
+
+## 二、连接类型
+
+Spark 中支持多种连接类型:
+
++ **Inner Join** : 内连接;
++ **Full Outer Join** : 全外连接;
++ **Left Outer Join** : 左外连接;
++ **Right Outer Join** : 右外连接;
++ **Left Semi Join** : 左半连接;
++ **Left Anti Join** : 左反连接;
++ **Natural Join** : 自然连接;
++ **Cross (or Cartesian) Join** : 交叉 (或笛卡尔) 连接。
+
+其中内,外连接,笛卡尔积均与普通关系型数据库中的相同,如下图所示:
+
+
+
+这里解释一下左半连接和左反连接,这两个连接等价于关系型数据库中的 `IN` 和 `NOT IN` 字句:
+
+```sql
+-- LEFT SEMI JOIN
+SELECT * FROM emp LEFT SEMI JOIN dept ON emp.deptno = dept.deptno
+-- 等价于如下的 IN 语句
+SELECT * FROM emp WHERE deptno IN (SELECT deptno FROM dept)
+
+-- LEFT ANTI JOIN
+SELECT * FROM emp LEFT ANTI JOIN dept ON emp.deptno = dept.deptno
+-- 等价于如下的 IN 语句
+SELECT * FROM emp WHERE deptno NOT IN (SELECT deptno FROM dept)
+```
+
+所有连接类型的示例代码如下:
+
+### 2.1 INNER JOIN
+
+```scala
+// 1.定义连接表达式
+val joinExpression = empDF.col("deptno") === deptDF.col("deptno")
+// 2.连接查询
+empDF.join(deptDF,joinExpression).select("ename","dname").show()
+
+// 等价 SQL 如下:
+spark.sql("SELECT ename,dname FROM emp JOIN dept ON emp.deptno = dept.deptno").show()
+```
+
+### 2.2 FULL OUTER JOIN
+
+```scala
+empDF.join(deptDF, joinExpression, "outer").show()
+spark.sql("SELECT * FROM emp FULL OUTER JOIN dept ON emp.deptno = dept.deptno").show()
+```
+
+### 2.3 LEFT OUTER JOIN
+
+```scala
+empDF.join(deptDF, joinExpression, "left_outer").show()
+spark.sql("SELECT * FROM emp LEFT OUTER JOIN dept ON emp.deptno = dept.deptno").show()
+```
+
+### 2.4 RIGHT OUTER JOIN
+
+```scala
+empDF.join(deptDF, joinExpression, "right_outer").show()
+spark.sql("SELECT * FROM emp RIGHT OUTER JOIN dept ON emp.deptno = dept.deptno").show()
+```
+
+### 2.5 LEFT SEMI JOIN
+
+```scala
+empDF.join(deptDF, joinExpression, "left_semi").show()
+spark.sql("SELECT * FROM emp LEFT SEMI JOIN dept ON emp.deptno = dept.deptno").show()
+```
+
+### 2.6 LEFT ANTI JOIN
+
+```scala
+empDF.join(deptDF, joinExpression, "left_anti").show()
+spark.sql("SELECT * FROM emp LEFT ANTI JOIN dept ON emp.deptno = dept.deptno").show()
+```
+
+### 2.7 CROSS JOIN
+
+```scala
+empDF.join(deptDF, joinExpression, "cross").show()
+spark.sql("SELECT * FROM emp CROSS JOIN dept ON emp.deptno = dept.deptno").show()
+```
+
+### 2.8 NATURAL JOIN
+
+自然连接是在两张表中寻找那些数据类型和列名都相同的字段,然后自动地将他们连接起来,并返回所有符合条件的结果。
+
+```scala
+spark.sql("SELECT * FROM emp NATURAL JOIN dept").show()
+```
+
+以下是一个自然连接的查询结果,程序自动推断出使用两张表都存在的 dept 列进行连接,其实际等价于:
+
+```sql
+spark.sql("SELECT * FROM emp JOIN dept ON emp.deptno = dept.deptno").show()
+```
+
+
+
+由于自然连接常常会产生不可预期的结果,所以并不推荐使用。
+
+
+
+## 三、连接的执行
+
+在对大表与大表之间进行连接操作时,通常都会触发 `Shuffle Join`,两表的所有分区节点会进行 `All-to-All` 的通讯,这种查询通常比较昂贵,会对网络 IO 会造成比较大的负担。
+
+
+
+
+
+而对于大表和小表的连接操作,Spark 会在一定程度上进行优化,如果小表的数据量小于 Worker Node 的内存空间,Spark 会考虑将小表的数据广播到每一个 Worker Node,在每个工作节点内部执行连接计算,这可以降低网络的 IO,但会加大每个 Worker Node 的 CPU 负担。
+
+
+
+是否采用广播方式进行 `Join` 取决于程序内部对小表的判断,如果想明确使用广播方式进行 `Join`,则可以在 DataFrame API 中使用 `broadcast` 方法指定需要广播的小表:
+
+```scala
+empDF.join(broadcast(deptDF), joinExpression).show()
+```
+
+
+
+## 参考资料
+
+1. Matei Zaharia, Bill Chambers . Spark: The Definitive Guide[M] . 2018-02
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_RDD.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_RDD.md"
new file mode 100644
index 0000000..21e9e90
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_RDD.md"
@@ -0,0 +1,237 @@
+
+
+# 弹性式数据集RDDs
+
+
+一、RDD简介
+二、创建RDD
+ 2.1 由现有集合创建
+ 2.2 引用外部存储系统中的数据集
+ 2.3 textFile & wholeTextFiles
+三、操作RDD
+四、缓存RDD
+ 4.1 缓存级别
+ 4.2 使用缓存
+ 4.3 移除缓存
+五、理解shuffle
+ 5.1 shuffle介绍
+ 5.2 Shuffle的影响
+ 5.3 导致Shuffle的操作
+五、宽依赖和窄依赖
+六、DAG的生成
+
+
+## 一、RDD简介
+
+`RDD` 全称为 Resilient Distributed Datasets,是 Spark 最基本的数据抽象,它是只读的、分区记录的集合,支持并行操作,可以由外部数据集或其他 RDD 转换而来,它具有以下特性:
+
++ 一个 RDD 由一个或者多个分区(Partitions)组成。对于 RDD 来说,每个分区会被一个计算任务所处理,用户可以在创建 RDD 时指定其分区个数,如果没有指定,则默认采用程序所分配到的 CPU 的核心数;
++ RDD 拥有一个用于计算分区的函数 compute;
++ RDD 会保存彼此间的依赖关系,RDD 的每次转换都会生成一个新的依赖关系,这种 RDD 之间的依赖关系就像流水线一样。在部分分区数据丢失后,可以通过这种依赖关系重新计算丢失的分区数据,而不是对 RDD 的所有分区进行重新计算;
++ Key-Value 型的 RDD 还拥有 Partitioner(分区器),用于决定数据被存储在哪个分区中,目前 Spark 中支持 HashPartitioner(按照哈希分区) 和 RangeParationer(按照范围进行分区);
++ 一个优先位置列表 (可选),用于存储每个分区的优先位置 (prefered location)。对于一个 HDFS 文件来说,这个列表保存的就是每个分区所在的块的位置,按照“移动数据不如移动计算“的理念,Spark 在进行任务调度的时候,会尽可能的将计算任务分配到其所要处理数据块的存储位置。
+
+`RDD[T]` 抽象类的部分相关代码如下:
+
+```scala
+// 由子类实现以计算给定分区
+def compute(split: Partition, context: TaskContext): Iterator[T]
+
+// 获取所有分区
+protected def getPartitions: Array[Partition]
+
+// 获取所有依赖关系
+protected def getDependencies: Seq[Dependency[_]] = deps
+
+// 获取优先位置列表
+protected def getPreferredLocations(split: Partition): Seq[String] = Nil
+
+// 分区器 由子类重写以指定它们的分区方式
+@transient val partitioner: Option[Partitioner] = None
+```
+
+
+
+## 二、创建RDD
+
+RDD 有两种创建方式,分别介绍如下:
+
+### 2.1 由现有集合创建
+
+这里使用 `spark-shell` 进行测试,启动命令如下:
+
+```shell
+spark-shell --master local[4]
+```
+
+启动 `spark-shell` 后,程序会自动创建应用上下文,相当于执行了下面的 Scala 语句:
+
+```scala
+val conf = new SparkConf().setAppName("Spark shell").setMaster("local[4]")
+val sc = new SparkContext(conf)
+```
+
+由现有集合创建 RDD,你可以在创建时指定其分区个数,如果没有指定,则采用程序所分配到的 CPU 的核心数:
+
+```scala
+val data = Array(1, 2, 3, 4, 5)
+// 由现有集合创建 RDD,默认分区数为程序所分配到的 CPU 的核心数
+val dataRDD = sc.parallelize(data)
+// 查看分区数
+dataRDD.getNumPartitions
+// 明确指定分区数
+val dataRDD = sc.parallelize(data,2)
+```
+
+执行结果如下:
+
+
+
+### 2.2 引用外部存储系统中的数据集
+
+引用外部存储系统中的数据集,例如本地文件系统,HDFS,HBase 或支持 Hadoop InputFormat 的任何数据源。
+
+```scala
+val fileRDD = sc.textFile("/usr/file/emp.txt")
+// 获取第一行文本
+fileRDD.take(1)
+```
+
+使用外部存储系统时需要注意以下两点:
+
++ 如果在集群环境下从本地文件系统读取数据,则要求该文件必须在集群中所有机器上都存在,且路径相同;
++ 支持目录路径,支持压缩文件,支持使用通配符。
+
+### 2.3 textFile & wholeTextFiles
+
+两者都可以用来读取外部文件,但是返回格式是不同的:
+
++ **textFile**:其返回格式是 `RDD[String]` ,返回的是就是文件内容,RDD 中每一个元素对应一行数据;
++ **wholeTextFiles**:其返回格式是 `RDD[(String, String)]`,元组中第一个参数是文件路径,第二个参数是文件内容;
++ 两者都提供第二个参数来控制最小分区数;
++ 从 HDFS 上读取文件时,Spark 会为每个块创建一个分区。
+
+```scala
+def textFile(path: String,minPartitions: Int = defaultMinPartitions): RDD[String] = withScope {...}
+def wholeTextFiles(path: String,minPartitions: Int = defaultMinPartitions): RDD[(String, String)]={..}
+```
+
+
+
+## 三、操作RDD
+
+RDD 支持两种类型的操作:*transformations*(转换,从现有数据集创建新数据集)和 *actions*(在数据集上运行计算后将值返回到驱动程序)。RDD 中的所有转换操作都是惰性的,它们只是记住这些转换操作,但不会立即执行,只有遇到 *action* 操作后才会真正的进行计算,这类似于函数式编程中的惰性求值。
+
+```scala
+val list = List(1, 2, 3)
+// map 是一个 transformations 操作,而 foreach 是一个 actions 操作
+sc.parallelize(list).map(_ * 10).foreach(println)
+// 输出: 10 20 30
+```
+
+
+
+## 四、缓存RDD
+
+### 4.1 缓存级别
+
+Spark 速度非常快的一个原因是 RDD 支持缓存。成功缓存后,如果之后的操作使用到了该数据集,则直接从缓存中获取。虽然缓存也有丢失的风险,但是由于 RDD 之间的依赖关系,如果某个分区的缓存数据丢失,只需要重新计算该分区即可。
+
+Spark 支持多种缓存级别 :
+
+| Storage Level (存储级别) | Meaning(含义) |
+| ---------------------------------------------- | ------------------------------------------------------------ |
+| `MEMORY_ONLY` | 默认的缓存级别,将 RDD 以反序列化的 Java 对象的形式存储在 JVM 中。如果内存空间不够,则部分分区数据将不再缓存。 |
+| `MEMORY_AND_DISK` | 将 RDD 以反序列化的 Java 对象的形式存储 JVM 中。如果内存空间不够,将未缓存的分区数据存储到磁盘,在需要使用这些分区时从磁盘读取。 |
+| `MEMORY_ONLY_SER` | 将 RDD 以序列化的 Java 对象的形式进行存储(每个分区为一个 byte 数组)。这种方式比反序列化对象节省存储空间,但在读取时会增加 CPU 的计算负担。仅支持 Java 和 Scala 。 |
+| `MEMORY_AND_DISK_SER` | 类似于 `MEMORY_ONLY_SER`,但是溢出的分区数据会存储到磁盘,而不是在用到它们时重新计算。仅支持 Java 和 Scala。 |
+| `DISK_ONLY` | 只在磁盘上缓存 RDD |
+| `MEMORY_ONLY_2`, `MEMORY_AND_DISK_2`, etc | 与上面的对应级别功能相同,但是会为每个分区在集群中的两个节点上建立副本。 |
+| `OFF_HEAP` | 与 `MEMORY_ONLY_SER` 类似,但将数据存储在堆外内存中。这需要启用堆外内存。 |
+
+> 启动堆外内存需要配置两个参数:
+>
+> + **spark.memory.offHeap.enabled** :是否开启堆外内存,默认值为 false,需要设置为 true;
+> + **spark.memory.offHeap.size** : 堆外内存空间的大小,默认值为 0,需要设置为正值。
+
+### 4.2 使用缓存
+
+缓存数据的方法有两个:`persist` 和 `cache` 。`cache` 内部调用的也是 `persist`,它是 `persist` 的特殊化形式,等价于 `persist(StorageLevel.MEMORY_ONLY)`。示例如下:
+
+```scala
+// 所有存储级别均定义在 StorageLevel 对象中
+fileRDD.persist(StorageLevel.MEMORY_AND_DISK)
+fileRDD.cache()
+```
+
+### 4.3 移除缓存
+
+Spark 会自动监视每个节点上的缓存使用情况,并按照最近最少使用(LRU)的规则删除旧数据分区。当然,你也可以使用 `RDD.unpersist()` 方法进行手动删除。
+
+
+
+## 五、理解shuffle
+
+### 5.1 shuffle介绍
+
+在 Spark 中,一个任务对应一个分区,通常不会跨分区操作数据。但如果遇到 `reduceByKey` 等操作,Spark 必须从所有分区读取数据,并查找所有键的所有值,然后汇总在一起以计算每个键的最终结果 ,这称为 `Shuffle`。
+
+
+
+
+
+### 5.2 Shuffle的影响
+
+Shuffle 是一项昂贵的操作,因为它通常会跨节点操作数据,这会涉及磁盘 I/O,网络 I/O,和数据序列化。某些 Shuffle 操作还会消耗大量的堆内存,因为它们使用堆内存来临时存储需要网络传输的数据。Shuffle 还会在磁盘上生成大量中间文件,从 Spark 1.3 开始,这些文件将被保留,直到相应的 RDD 不再使用并进行垃圾回收,这样做是为了避免在计算时重复创建 Shuffle 文件。如果应用程序长期保留对这些 RDD 的引用,则垃圾回收可能在很长一段时间后才会发生,这意味着长时间运行的 Spark 作业可能会占用大量磁盘空间,通常可以使用 `spark.local.dir` 参数来指定这些临时文件的存储目录。
+
+### 5.3 导致Shuffle的操作
+
+由于 Shuffle 操作对性能的影响比较大,所以需要特别注意使用,以下操作都会导致 Shuffle:
+
++ **涉及到重新分区操作**: 如 `repartition` 和 `coalesce`;
++ **所有涉及到 ByKey 的操作**:如 `groupByKey` 和 `reduceByKey`,但 `countByKey` 除外;
++ **联结操作**:如 `cogroup` 和 `join`。
+
+
+
+## 五、宽依赖和窄依赖
+
+RDD 和它的父 RDD(s) 之间的依赖关系分为两种不同的类型:
+
+- **窄依赖 (narrow dependency)**:父 RDDs 的一个分区最多被子 RDDs 一个分区所依赖;
+- **宽依赖 (wide dependency)**:父 RDDs 的一个分区可以被子 RDDs 的多个子分区所依赖。
+
+如下图,每一个方框表示一个 RDD,带有颜色的矩形表示分区:
+
+
+
+
+
+区分这两种依赖是非常有用的:
+
++ 首先,窄依赖允许在一个集群节点上以流水线的方式(pipeline)对父分区数据进行计算,例如先执行 map 操作,然后执行 filter 操作。而宽依赖则需要计算好所有父分区的数据,然后再在节点之间进行 Shuffle,这与 MapReduce 类似。
++ 窄依赖能够更有效地进行数据恢复,因为只需重新对丢失分区的父分区进行计算,且不同节点之间可以并行计算;而对于宽依赖而言,如果数据丢失,则需要对所有父分区数据进行计算并再次 Shuffle。
+
+
+
+## 六、DAG的生成
+
+RDD(s) 及其之间的依赖关系组成了 DAG(有向无环图),DAG 定义了这些 RDD(s) 之间的 Lineage(血统) 关系,通过血统关系,如果一个 RDD 的部分或者全部计算结果丢失了,也可以重新进行计算。那么 Spark 是如何根据 DAG 来生成计算任务呢?主要是根据依赖关系的不同将 DAG 划分为不同的计算阶段 (Stage):
+
++ 对于窄依赖,由于分区的依赖关系是确定的,其转换操作可以在同一个线程执行,所以可以划分到同一个执行阶段;
++ 对于宽依赖,由于 Shuffle 的存在,只能在父 RDD(s) 被 Shuffle 处理完成后,才能开始接下来的计算,因此遇到宽依赖就需要重新划分阶段。
+
+
+
+
+
+
+
+## 参考资料
+
+1. 张安站 . Spark 技术内幕:深入解析 Spark 内核架构设计与实现原理[M] . 机械工业出版社 . 2015-09-01
+2. [RDD Programming Guide](https://spark.apache.org/docs/latest/rdd-programming-guide.html#rdd-programming-guide)
+3. [RDD:基于内存的集群计算容错抽象](http://shiyanjun.cn/archives/744.html)
+
+
+
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_Streaming\344\270\216\346\265\201\345\244\204\347\220\206.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_Streaming\344\270\216\346\265\201\345\244\204\347\220\206.md"
new file mode 100644
index 0000000..6c6dfce
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_Streaming\344\270\216\346\265\201\345\244\204\347\220\206.md"
@@ -0,0 +1,79 @@
+# Spark Streaming与流处理
+
+
+一、流处理
+ 1.1 静态数据处理
+ 1.2 流处理
+二、Spark Streaming
+ 2.1 简介
+ 2.2 DStream
+ 2.3 Spark & Storm & Flink
+
+
+## 一、流处理
+
+### 1.1 静态数据处理
+
+在流处理之前,数据通常存储在数据库,文件系统或其他形式的存储系统中。应用程序根据需要查询数据或计算数据。这就是传统的静态数据处理架构。Hadoop 采用 HDFS 进行数据存储,采用 MapReduce 进行数据查询或分析,这就是典型的静态数据处理架构。
+
+
+
+
+
+### 1.2 流处理
+
+而流处理则是直接对运动中的数据的处理,在接收数据时直接计算数据。
+
+大多数数据都是连续的流:传感器事件,网站上的用户活动,金融交易等等 ,所有这些数据都是随着时间的推移而创建的。
+
+接收和发送数据流并执行应用程序或分析逻辑的系统称为**流处理器**。流处理器的基本职责是确保数据有效流动,同时具备可扩展性和容错能力,Storm 和 Flink 就是其代表性的实现。
+
+
+
+
+
+流处理带来了静态数据处理所不具备的众多优点:
+
+
+
+- **应用程序立即对数据做出反应**:降低了数据的滞后性,使得数据更具有时效性,更能反映对未来的预期;
+- **流处理可以处理更大的数据量**:直接处理数据流,并且只保留数据中有意义的子集,并将其传送到下一个处理单元,逐级过滤数据,降低需要处理的数据量,从而能够承受更大的数据量;
+- **流处理更贴近现实的数据模型**:在实际的环境中,一切数据都是持续变化的,要想能够通过过去的数据推断未来的趋势,必须保证数据的不断输入和模型的不断修正,典型的就是金融市场、股票市场,流处理能更好的应对这些数据的连续性的特征和及时性的需求;
+- **流处理分散和分离基础设施**:流式处理减少了对大型数据库的需求。相反,每个流处理程序通过流处理框架维护了自己的数据和状态,这使得流处理程序更适合微服务架构。
+
+
+
+## 二、Spark Streaming
+
+### 2.1 简介
+
+Spark Streaming 是 Spark 的一个子模块,用于快速构建可扩展,高吞吐量,高容错的流处理程序。具有以下特点:
+
++ 通过高级 API 构建应用程序,简单易用;
++ 支持多种语言,如 Java,Scala 和 Python;
++ 良好的容错性,Spark Streaming 支持快速从失败中恢复丢失的操作状态;
++ 能够和 Spark 其他模块无缝集成,将流处理与批处理完美结合;
++ Spark Streaming 可以从 HDFS,Flume,Kafka,Twitter 和 ZeroMQ 读取数据,也支持自定义数据源。
+
+
+
+### 2.2 DStream
+
+Spark Streaming 提供称为离散流 (DStream) 的高级抽象,用于表示连续的数据流。 DStream 可以从来自 Kafka,Flume 和 Kinesis 等数据源的输入数据流创建,也可以由其他 DStream 转化而来。**在内部,DStream 表示为一系列 RDD**。
+
+
+
+
+
+### 2.3 Spark & Storm & Flink
+
+storm 和 Flink 都是真正意义上的流计算框架,但 Spark Streaming 只是将数据流进行极小粒度的拆分,拆分为多个批处理,使得其能够得到接近于流处理的效果,但其本质上还是批处理(或微批处理)。
+
+
+
+
+
+## 参考资料
+
+1. [Spark Streaming Programming Guide](https://spark.apache.org/docs/latest/streaming-programming-guide.html)
+2. [What is stream processing?](https://www.ververica.com/what-is-stream-processing)
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_Streaming\345\237\272\346\234\254\346\223\215\344\275\234.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_Streaming\345\237\272\346\234\254\346\223\215\344\275\234.md"
new file mode 100644
index 0000000..da78264
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_Streaming\345\237\272\346\234\254\346\223\215\344\275\234.md"
@@ -0,0 +1,335 @@
+# Spark Streaming 基本操作
+
+
+一、案例引入
+ 3.1 StreamingContext
+ 3.2 数据源
+ 3.3 服务的启动与停止
+二、Transformation
+ 2.1 DStream与RDDs
+ 2.2 updateStateByKey
+ 2.3 启动测试
+三、输出操作
+ 3.1 输出API
+ 3.1 foreachRDD
+ 3.3 代码说明
+ 3.4 启动测试
+
+
+## 一、案例引入
+
+这里先引入一个基本的案例来演示流的创建:获取指定端口上的数据并进行词频统计。项目依赖和代码实现如下:
+
+```xml
+
+ org.apache.spark
+ spark-streaming_2.12
+ 2.4.3
+
+```
+
+```scala
+import org.apache.spark.SparkConf
+import org.apache.spark.streaming.{Seconds, StreamingContext}
+
+object NetworkWordCount {
+
+ def main(args: Array[String]) {
+
+ /*指定时间间隔为 5s*/
+ val sparkConf = new SparkConf().setAppName("NetworkWordCount").setMaster("local[2]")
+ val ssc = new StreamingContext(sparkConf, Seconds(5))
+
+ /*创建文本输入流,并进行词频统计*/
+ val lines = ssc.socketTextStream("hadoop001", 9999)
+ lines.flatMap(_.split(" ")).map(x => (x, 1)).reduceByKey(_ + _).print()
+
+ /*启动服务*/
+ ssc.start()
+ /*等待服务结束*/
+ ssc.awaitTermination()
+ }
+}
+```
+
+使用本地模式启动 Spark 程序,然后使用 `nc -lk 9999` 打开端口并输入测试数据:
+
+```shell
+[root@hadoop001 ~]# nc -lk 9999
+hello world hello spark hive hive hadoop
+storm storm flink azkaban
+```
+
+此时控制台输出如下,可以看到已经接收到数据并按行进行了词频统计。
+
+
+
+
+下面针对示例代码进行讲解:
+
+### 3.1 StreamingContext
+
+Spark Streaming 编程的入口类是 StreamingContext,在创建时候需要指明 `sparkConf` 和 `batchDuration`(批次时间),Spark 流处理本质是将流数据拆分为一个个批次,然后进行微批处理,`batchDuration` 就是批次拆分的时间间隔。这个时间可以根据业务需求和服务器性能进行指定,如果业务要求低延迟并且服务器性能也允许,则这个时间可以指定得很短。
+
+这里需要注意的是:示例代码使用的是本地模式,配置为 `local[2]`,这里不能配置为 `local[1]`。这是因为对于流数据的处理,Spark 必须有一个独立的 Executor 来接收数据,然后再由其他的 Executors 来处理,所以为了保证数据能够被处理,至少要有 2 个 Executors。这里我们的程序只有一个数据流,在并行读取多个数据流的时候,也需要保证有足够的 Executors 来接收和处理数据。
+
+### 3.2 数据源
+
+在示例代码中使用的是 `socketTextStream` 来创建基于 Socket 的数据流,实际上 Spark 还支持多种数据源,分为以下两类:
+
++ **基本数据源**:包括文件系统、Socket 连接等;
++ **高级数据源**:包括 Kafka,Flume,Kinesis 等。
+
+在基本数据源中,Spark 支持监听 HDFS 上指定目录,当有新文件加入时,会获取其文件内容作为输入流。创建方式如下:
+
+```scala
+// 对于文本文件,指明监听目录即可
+streamingContext.textFileStream(dataDirectory)
+// 对于其他文件,需要指明目录,以及键的类型、值的类型、和输入格式
+streamingContext.fileStream[KeyClass, ValueClass, InputFormatClass](dataDirectory)
+```
+
+被监听的目录可以是具体目录,如 `hdfs://host:8040/logs/`;也可以使用通配符,如 `hdfs://host:8040/logs/2017/*`。
+
+> 关于高级数据源的整合单独整理至:[Spark Streaming 整合 Flume](https://github.com/heibaiying/BigData-Notes/blob/master/notes/Spark_Streaming整合Flume.md) 和 [Spark Streaming 整合 Kafka](https://github.com/heibaiying/BigData-Notes/blob/master/notes/Spark_Streaming整合Kafka.md)
+
+### 3.3 服务的启动与停止
+
+在示例代码中,使用 `streamingContext.start()` 代表启动服务,此时还要使用 `streamingContext.awaitTermination()` 使服务处于等待和可用的状态,直到发生异常或者手动使用 `streamingContext.stop()` 进行终止。
+
+
+
+## 二、Transformation
+
+### 2.1 DStream与RDDs
+
+DStream 是 Spark Streaming 提供的基本抽象。它表示连续的数据流。在内部,DStream 由一系列连续的 RDD 表示。所以从本质上而言,应用于 DStream 的任何操作都会转换为底层 RDD 上的操作。例如,在示例代码中 flatMap 算子的操作实际上是作用在每个 RDDs 上 (如下图)。因为这个原因,所以 DStream 能够支持 RDD 大部分的*transformation*算子。
+
+
+
+### 2.2 updateStateByKey
+
+除了能够支持 RDD 的算子外,DStream 还有部分独有的*transformation*算子,这当中比较常用的是 `updateStateByKey`。文章开头的词频统计程序,只能统计每一次输入文本中单词出现的数量,想要统计所有历史输入中单词出现的数量,可以使用 `updateStateByKey` 算子。代码如下:
+
+```scala
+object NetworkWordCountV2 {
+
+
+ def main(args: Array[String]) {
+
+ /*
+ * 本地测试时最好指定 hadoop 用户名,否则会默认使用本地电脑的用户名,
+ * 此时在 HDFS 上创建目录时可能会抛出权限不足的异常
+ */
+ System.setProperty("HADOOP_USER_NAME", "root")
+
+ val sparkConf = new SparkConf().setAppName("NetworkWordCountV2").setMaster("local[2]")
+ val ssc = new StreamingContext(sparkConf, Seconds(5))
+ /*必须要设置检查点*/
+ ssc.checkpoint("hdfs://hadoop001:8020/spark-streaming")
+ val lines = ssc.socketTextStream("hadoop001", 9999)
+ lines.flatMap(_.split(" ")).map(x => (x, 1))
+ .updateStateByKey[Int](updateFunction _) //updateStateByKey 算子
+ .print()
+
+ ssc.start()
+ ssc.awaitTermination()
+ }
+
+ /**
+ * 累计求和
+ *
+ * @param currentValues 当前的数据
+ * @param preValues 之前的数据
+ * @return 相加后的数据
+ */
+ def updateFunction(currentValues: Seq[Int], preValues: Option[Int]): Option[Int] = {
+ val current = currentValues.sum
+ val pre = preValues.getOrElse(0)
+ Some(current + pre)
+ }
+}
+```
+
+使用 `updateStateByKey` 算子,你必须使用 `ssc.checkpoint()` 设置检查点,这样当使用 `updateStateByKey` 算子时,它会去检查点中取出上一次保存的信息,并使用自定义的 `updateFunction` 函数将上一次的数据和本次数据进行相加,然后返回。
+
+### 2.3 启动测试
+
+在监听端口输入如下测试数据:
+
+```shell
+[root@hadoop001 ~]# nc -lk 9999
+hello world hello spark hive hive hadoop
+storm storm flink azkaban
+hello world hello spark hive hive hadoop
+storm storm flink azkaban
+```
+
+此时控制台输出如下,所有输入都被进行了词频累计:
+
+
+同时在输出日志中还可以看到检查点操作的相关信息:
+
+```shell
+# 保存检查点信息
+19/05/27 16:21:05 INFO CheckpointWriter: Saving checkpoint for time 1558945265000 ms
+to file 'hdfs://hadoop001:8020/spark-streaming/checkpoint-1558945265000'
+
+# 删除已经无用的检查点信息
+19/05/27 16:21:30 INFO CheckpointWriter:
+Deleting hdfs://hadoop001:8020/spark-streaming/checkpoint-1558945265000
+```
+
+## 三、输出操作
+
+### 3.1 输出API
+
+Spark Streaming 支持以下输出操作:
+
+| Output Operation | Meaning |
+| :------------------------------------------ | :----------------------------------------------------------- |
+| **print**() | 在运行流应用程序的 driver 节点上打印 DStream 中每个批次的前十个元素。用于开发调试。 |
+| **saveAsTextFiles**(*prefix*, [*suffix*]) | 将 DStream 的内容保存为文本文件。每个批处理间隔的文件名基于前缀和后缀生成:“prefix-TIME_IN_MS [.suffix]”。 |
+| **saveAsObjectFiles**(*prefix*, [*suffix*]) | 将 DStream 的内容序列化为 Java 对象,并保存到 SequenceFiles。每个批处理间隔的文件名基于前缀和后缀生成:“prefix-TIME_IN_MS [.suffix]”。 |
+| **saveAsHadoopFiles**(*prefix*, [*suffix*]) | 将 DStream 的内容保存为 Hadoop 文件。每个批处理间隔的文件名基于前缀和后缀生成:“prefix-TIME_IN_MS [.suffix]”。 |
+| **foreachRDD**(*func*) | 最通用的输出方式,它将函数 func 应用于从流生成的每个 RDD。此函数应将每个 RDD 中的数据推送到外部系统,例如将 RDD 保存到文件,或通过网络将其写入数据库。 |
+
+前面的四个 API 都是直接调用即可,下面主要讲解通用的输出方式 `foreachRDD(func)`,通过该 API 你可以将数据保存到任何你需要的数据源。
+
+### 3.1 foreachRDD
+
+这里我们使用 Redis 作为客户端,对文章开头示例程序进行改变,把每一次词频统计的结果写入到 Redis,并利用 Redis 的 `HINCRBY` 命令来进行词频统计。这里需要导入 Jedis 依赖:
+
+```xml
+
+ redis.clients
+ jedis
+ 2.9.0
+
+```
+
+具体实现代码如下:
+
+```scala
+import org.apache.spark.SparkConf
+import org.apache.spark.streaming.dstream.DStream
+import org.apache.spark.streaming.{Seconds, StreamingContext}
+import redis.clients.jedis.Jedis
+
+object NetworkWordCountToRedis {
+
+ def main(args: Array[String]) {
+
+ val sparkConf = new SparkConf().setAppName("NetworkWordCountToRedis").setMaster("local[2]")
+ val ssc = new StreamingContext(sparkConf, Seconds(5))
+
+ /*创建文本输入流,并进行词频统计*/
+ val lines = ssc.socketTextStream("hadoop001", 9999)
+ val pairs: DStream[(String, Int)] = lines.flatMap(_.split(" ")).map(x => (x, 1)).reduceByKey(_ + _)
+ /*保存数据到 Redis*/
+ pairs.foreachRDD { rdd =>
+ rdd.foreachPartition { partitionOfRecords =>
+ var jedis: Jedis = null
+ try {
+ jedis = JedisPoolUtil.getConnection
+ partitionOfRecords.foreach(record => jedis.hincrBy("wordCount", record._1, record._2))
+ } catch {
+ case ex: Exception =>
+ ex.printStackTrace()
+ } finally {
+ if (jedis != null) jedis.close()
+ }
+ }
+ }
+ ssc.start()
+ ssc.awaitTermination()
+ }
+}
+
+```
+
+其中 `JedisPoolUtil` 的代码如下:
+
+```java
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+import redis.clients.jedis.JedisPoolConfig;
+
+public class JedisPoolUtil {
+
+ /* 声明为 volatile 防止指令重排序 */
+ private static volatile JedisPool jedisPool = null;
+ private static final String HOST = "localhost";
+ private static final int PORT = 6379;
+
+ /* 双重检查锁实现懒汉式单例 */
+ public static Jedis getConnection() {
+ if (jedisPool == null) {
+ synchronized (JedisPoolUtil.class) {
+ if (jedisPool == null) {
+ JedisPoolConfig config = new JedisPoolConfig();
+ config.setMaxTotal(30);
+ config.setMaxIdle(10);
+ jedisPool = new JedisPool(config, HOST, PORT);
+ }
+ }
+ }
+ return jedisPool.getResource();
+ }
+}
+```
+
+### 3.3 代码说明
+
+这里将上面保存到 Redis 的代码单独抽取出来,并去除异常判断的部分。精简后的代码如下:
+
+```scala
+pairs.foreachRDD { rdd =>
+ rdd.foreachPartition { partitionOfRecords =>
+ val jedis = JedisPoolUtil.getConnection
+ partitionOfRecords.foreach(record => jedis.hincrBy("wordCount", record._1, record._2))
+ jedis.close()
+ }
+}
+```
+
+这里可以看到一共使用了三次循环,分别是循环 RDD,循环分区,循环每条记录,上面我们的代码是在循环分区的时候获取连接,也就是为每一个分区获取一个连接。但是这里大家可能会有疑问:为什么不在循环 RDD 的时候,为每一个 RDD 获取一个连接,这样所需要的连接数会更少。实际上这是不可行的,如果按照这种情况进行改写,如下:
+
+```scala
+pairs.foreachRDD { rdd =>
+ val jedis = JedisPoolUtil.getConnection
+ rdd.foreachPartition { partitionOfRecords =>
+ partitionOfRecords.foreach(record => jedis.hincrBy("wordCount", record._1, record._2))
+ }
+ jedis.close()
+}
+```
+
+此时在执行时候就会抛出 `Caused by: java.io.NotSerializableException: redis.clients.jedis.Jedis`,这是因为在实际计算时,Spark 会将对 RDD 操作分解为多个 Task,Task 运行在具体的 Worker Node 上。在执行之前,Spark 会对任务进行闭包,之后闭包被序列化并发送给每个 Executor,而 `Jedis` 显然是不能被序列化的,所以会抛出异常。
+
+第二个需要注意的是 ConnectionPool 最好是一个静态,惰性初始化连接池 。这是因为 Spark 的转换操作本身就是惰性的,且没有数据流时不会触发写出操作,所以出于性能考虑,连接池应该是惰性的,因此上面 `JedisPool` 在初始化时采用了懒汉式单例进行惰性初始化。
+
+### 3.4 启动测试
+
+在监听端口输入如下测试数据:
+
+```shell
+[root@hadoop001 ~]# nc -lk 9999
+hello world hello spark hive hive hadoop
+storm storm flink azkaban
+hello world hello spark hive hive hadoop
+storm storm flink azkaban
+```
+
+使用 Redis Manager 查看写入结果 (如下图),可以看到与使用 `updateStateByKey` 算子得到的计算结果相同。
+
+
+
+
+> 本片文章所有源码见本仓库:[spark-streaming-basis](https://github.com/heibaiying/BigData-Notes/tree/master/code/spark/spark-streaming-basis)
+
+
+
+## 参考资料
+
+Spark 官方文档:http://spark.apache.org/docs/latest/streaming-programming-guide.html
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_Streaming\346\225\264\345\220\210Flume.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_Streaming\346\225\264\345\220\210Flume.md"
new file mode 100644
index 0000000..fd40d08
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_Streaming\346\225\264\345\220\210Flume.md"
@@ -0,0 +1,359 @@
+# Spark Streaming 整合 Flume
+
+
+一、简介
+二、推送式方法
+ 2.1 配置日志收集Flume
+ 2.2 项目依赖
+ 2.3 Spark Streaming接收日志数据
+ 2.4 项目打包
+ 2.5 启动服务和提交作业
+ 2.6 测试
+ 2.7 注意事项
+三、拉取式方法
+ 3.1 配置日志收集Flume
+ 2.2 新增依赖
+ 2.3 Spark Streaming接收日志数据
+ 2.4 启动测试
+
+
+
+## 一、简介
+
+Apache Flume 是一个分布式,高可用的数据收集系统,可以从不同的数据源收集数据,经过聚合后发送到分布式计算框架或者存储系统中。Spark Straming 提供了以下两种方式用于 Flume 的整合。
+
+## 二、推送式方法
+
+在推送式方法 (Flume-style Push-based Approach) 中,Spark Streaming 程序需要对某台服务器的某个端口进行监听,Flume 通过 `avro Sink` 将数据源源不断推送到该端口。这里以监听日志文件为例,具体整合方式如下:
+
+### 2.1 配置日志收集Flume
+
+新建配置 `netcat-memory-avro.properties`,使用 `tail` 命令监听文件内容变化,然后将新的文件内容通过 `avro sink` 发送到 hadoop001 这台服务器的 8888 端口:
+
+```properties
+#指定agent的sources,sinks,channels
+a1.sources = s1
+a1.sinks = k1
+a1.channels = c1
+
+#配置sources属性
+a1.sources.s1.type = exec
+a1.sources.s1.command = tail -F /tmp/log.txt
+a1.sources.s1.shell = /bin/bash -c
+a1.sources.s1.channels = c1
+
+#配置sink
+a1.sinks.k1.type = avro
+a1.sinks.k1.hostname = hadoop001
+a1.sinks.k1.port = 8888
+a1.sinks.k1.batch-size = 1
+a1.sinks.k1.channel = c1
+
+#配置channel类型
+a1.channels.c1.type = memory
+a1.channels.c1.capacity = 1000
+a1.channels.c1.transactionCapacity = 100
+```
+
+### 2.2 项目依赖
+
+项目采用 Maven 工程进行构建,主要依赖为 `spark-streaming` 和 `spark-streaming-flume`。
+
+```xml
+
+ 2.11
+ 2.4.0
+
+
+
+
+
+ org.apache.spark
+ spark-streaming_${scala.version}
+ ${spark.version}
+
+
+
+ org.apache.spark
+ spark-streaming-flume_${scala.version}
+ 2.4.3
+
+
+
+```
+
+### 2.3 Spark Streaming接收日志数据
+
+调用 FlumeUtils 工具类的 `createStream` 方法,对 hadoop001 的 8888 端口进行监听,获取到流数据并进行打印:
+
+```scala
+import org.apache.spark.SparkConf
+import org.apache.spark.streaming.{Seconds, StreamingContext}
+import org.apache.spark.streaming.flume.FlumeUtils
+
+object PushBasedWordCount {
+
+ def main(args: Array[String]): Unit = {
+ val sparkConf = new SparkConf()
+ val ssc = new StreamingContext(sparkConf, Seconds(5))
+ // 1.获取输入流
+ val flumeStream = FlumeUtils.createStream(ssc, "hadoop001", 8888)
+ // 2.打印输入流的数据
+ flumeStream.map(line => new String(line.event.getBody.array()).trim).print()
+
+ ssc.start()
+ ssc.awaitTermination()
+ }
+}
+```
+
+### 2.4 项目打包
+
+因为 Spark 安装目录下是不含有 `spark-streaming-flume` 依赖包的,所以在提交到集群运行时候必须提供该依赖包,你可以在提交命令中使用 `--jar` 指定上传到服务器的该依赖包,或者使用 `--packages org.apache.spark:spark-streaming-flume_2.12:2.4.3` 指定依赖包的完整名称,这样程序在启动时会先去中央仓库进行下载。
+
+这里我采用的是第三种方式:使用 `maven-shade-plugin` 插件进行 `ALL IN ONE` 打包,把所有依赖的 Jar 一并打入最终包中。需要注意的是 `spark-streaming` 包在 Spark 安装目录的 `jars` 目录中已经提供,所以不需要打入。插件配置如下:
+
+
+```xml
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 8
+ 8
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+
+ true
+
+
+ *:*
+
+ META-INF/*.SF
+ META-INF/*.sf
+ META-INF/*.DSA
+ META-INF/*.dsa
+ META-INF/*.RSA
+ META-INF/*.rsa
+ META-INF/*.EC
+ META-INF/*.ec
+ META-INF/MSFTSIG.SF
+ META-INF/MSFTSIG.RSA
+
+
+
+
+
+ org.apache.spark:spark-streaming_${scala.version}
+ org.scala-lang:scala-library
+ org.apache.commons:commons-lang3
+
+
+
+
+
+ package
+
+ shade
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.scala-tools
+ maven-scala-plugin
+ 2.15.1
+
+
+ scala-compile
+
+ compile
+
+
+
+ **/*.scala
+
+
+
+
+ scala-test-compile
+
+ testCompile
+
+
+
+
+
+
+```
+> 本项目完整源码见:[spark-streaming-flume](https://github.com/heibaiying/BigData-Notes/tree/master/code/spark/spark-streaming-flume)
+
+使用 `mvn clean package` 命令打包后会生产以下两个 Jar 包,提交 ` 非 original` 开头的 Jar 即可。
+
+
+
+### 2.5 启动服务和提交作业
+
+ 启动 Flume 服务:
+
+```shell
+flume-ng agent \
+--conf conf \
+--conf-file /usr/app/apache-flume-1.6.0-cdh5.15.2-bin/examples/netcat-memory-avro.properties \
+--name a1 -Dflume.root.logger=INFO,console
+```
+
+提交 Spark Streaming 作业:
+
+```shell
+spark-submit \
+--class com.heibaiying.flume.PushBasedWordCount \
+--master local[4] \
+/usr/appjar/spark-streaming-flume-1.0.jar
+```
+
+### 2.6 测试
+
+这里使用 `echo` 命令模拟日志产生的场景,往日志文件中追加数据,然后查看程序的输出:
+
+
+
+Spark Streaming 程序成功接收到数据并打印输出:
+
+
+
+### 2.7 注意事项
+
+#### 1. 启动顺序
+
+这里需要注意的,不论你先启动 Spark 程序还是 Flume 程序,由于两者的启动都需要一定的时间,此时先启动的程序会短暂地抛出端口拒绝连接的异常,此时不需要进行任何操作,等待两个程序都启动完成即可。
+
+
+
+#### 2. 版本一致
+
+最好保证用于本地开发和编译的 Scala 版本和 Spark 的 Scala 版本一致,至少保证大版本一致,如都是 `2.11`。
+
+
+
+## 三、拉取式方法
+
+拉取式方法 (Pull-based Approach using a Custom Sink) 是将数据推送到 `SparkSink` 接收器中,此时数据会保持缓冲状态,Spark Streaming 定时从接收器中拉取数据。这种方式是基于事务的,即只有在 Spark Streaming 接收和复制数据完成后,才会删除缓存的数据。与第一种方式相比,具有更强的可靠性和容错保证。整合步骤如下:
+
+### 3.1 配置日志收集Flume
+
+新建 Flume 配置文件 `netcat-memory-sparkSink.properties`,配置和上面基本一致,只是把 `a1.sinks.k1.type` 的属性修改为 `org.apache.spark.streaming.flume.sink.SparkSink`,即采用 Spark 接收器。
+
+```properties
+#指定agent的sources,sinks,channels
+a1.sources = s1
+a1.sinks = k1
+a1.channels = c1
+
+#配置sources属性
+a1.sources.s1.type = exec
+a1.sources.s1.command = tail -F /tmp/log.txt
+a1.sources.s1.shell = /bin/bash -c
+a1.sources.s1.channels = c1
+
+#配置sink
+a1.sinks.k1.type = org.apache.spark.streaming.flume.sink.SparkSink
+a1.sinks.k1.hostname = hadoop001
+a1.sinks.k1.port = 8888
+a1.sinks.k1.batch-size = 1
+a1.sinks.k1.channel = c1
+
+#配置channel类型
+a1.channels.c1.type = memory
+a1.channels.c1.capacity = 1000
+a1.channels.c1.transactionCapacity = 100
+```
+
+### 2.2 新增依赖
+
+使用拉取式方法需要额外添加以下两个依赖:
+
+```xml
+
+ org.scala-lang
+ scala-library
+ 2.12.8
+
+
+ org.apache.commons
+ commons-lang3
+ 3.5
+
+```
+
+注意:添加这两个依赖只是为了本地测试,Spark 的安装目录下已经提供了这两个依赖,所以在最终打包时需要进行排除。
+
+### 2.3 Spark Streaming接收日志数据
+
+这里和上面推送式方法的代码基本相同,只是将调用方法改为 `createPollingStream`。
+
+```scala
+import org.apache.spark.SparkConf
+import org.apache.spark.streaming.{Seconds, StreamingContext}
+import org.apache.spark.streaming.flume.FlumeUtils
+
+object PullBasedWordCount {
+
+ def main(args: Array[String]): Unit = {
+
+ val sparkConf = new SparkConf()
+ val ssc = new StreamingContext(sparkConf, Seconds(5))
+ // 1.获取输入流
+ val flumeStream = FlumeUtils.createPollingStream(ssc, "hadoop001", 8888)
+ // 2.打印输入流中的数据
+ flumeStream.map(line => new String(line.event.getBody.array()).trim).print()
+ ssc.start()
+ ssc.awaitTermination()
+ }
+}
+```
+
+### 2.4 启动测试
+
+启动和提交作业流程与上面相同,这里给出执行脚本,过程不再赘述。
+
+启动 Flume 进行日志收集:
+
+```shell
+flume-ng agent \
+--conf conf \
+--conf-file /usr/app/apache-flume-1.6.0-cdh5.15.2-bin/examples/netcat-memory-sparkSink.properties \
+--name a1 -Dflume.root.logger=INFO,console
+```
+
+提交 Spark Streaming 作业:
+
+```shel
+spark-submit \
+--class com.heibaiying.flume.PullBasedWordCount \
+--master local[4] \
+/usr/appjar/spark-streaming-flume-1.0.jar
+```
+
+
+
+## 参考资料
+
+- [streaming-flume-integration](https://spark.apache.org/docs/latest/streaming-flume-integration.html)
+- 关于大数据应用常用的打包方式可以参见:[大数据应用常用打包方式](https://github.com/heibaiying/BigData-Notes/blob/master/notes/大数据应用常用打包方式.md)
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_Streaming\346\225\264\345\220\210Kafka.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_Streaming\346\225\264\345\220\210Kafka.md"
new file mode 100644
index 0000000..139a70f
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_Streaming\346\225\264\345\220\210Kafka.md"
@@ -0,0 +1,321 @@
+# Spark Streaming 整合 Kafka
+
+
+一、版本说明
+二、项目依赖
+三、整合Kafka
+ 3.1 ConsumerRecord
+ 3.2 生产者属性
+ 3.3 位置策略
+ 3.4 订阅方式
+ 3.5 提交偏移量
+四、启动测试
+
+
+
+## 一、版本说明
+
+Spark 针对 Kafka 的不同版本,提供了两套整合方案:`spark-streaming-kafka-0-8` 和 `spark-streaming-kafka-0-10`,其主要区别如下:
+
+| | [spark-streaming-kafka-0-8](https://spark.apache.org/docs/latest/streaming-kafka-0-8-integration.html) | [spark-streaming-kafka-0-10](https://spark.apache.org/docs/latest/streaming-kafka-0-10-integration.html) |
+| :-------------------------------------------- | :----------------------------------------------------------- | :----------------------------------------------------------- |
+| Kafka 版本 | 0.8.2.1 or higher | 0.10.0 or higher |
+| AP 状态 | Deprecated 从 Spark 2.3.0 版本开始,Kafka 0.8 支持已被弃用 | Stable(稳定版) |
+| 语言支持 | Scala, Java, Python | Scala, Java |
+| Receiver DStream | Yes | No |
+| Direct DStream | Yes | Yes |
+| SSL / TLS Support | No | Yes |
+| Offset Commit API(偏移量提交) | No | Yes |
+| Dynamic Topic Subscription (动态主题订阅) | No | Yes |
+
+本文使用的 Kafka 版本为 `kafka_2.12-2.2.0`,故采用第二种方式进行整合。
+
+## 二、项目依赖
+
+项目采用 Maven 进行构建,主要依赖如下:
+
+```xml
+
+ 2.12
+
+
+
+
+
+ org.apache.spark
+ spark-streaming_${scala.version}
+ ${spark.version}
+
+
+
+ org.apache.spark
+ spark-streaming-kafka-0-10_${scala.version}
+ 2.4.3
+
+
+```
+
+> 完整源码见本仓库:[spark-streaming-kafka](https://github.com/heibaiying/BigData-Notes/tree/master/code/spark/spark-streaming-kafka)
+
+## 三、整合Kafka
+
+通过调用 `KafkaUtils` 对象的 `createDirectStream` 方法来创建输入流,完整代码如下:
+
+```scala
+import org.apache.kafka.common.serialization.StringDeserializer
+import org.apache.spark.SparkConf
+import org.apache.spark.streaming.kafka010.ConsumerStrategies.Subscribe
+import org.apache.spark.streaming.kafka010.LocationStrategies.PreferConsistent
+import org.apache.spark.streaming.kafka010._
+import org.apache.spark.streaming.{Seconds, StreamingContext}
+
+/**
+ * spark streaming 整合 kafka
+ */
+object KafkaDirectStream {
+
+ def main(args: Array[String]): Unit = {
+
+ val sparkConf = new SparkConf().setAppName("KafkaDirectStream").setMaster("local[2]")
+ val streamingContext = new StreamingContext(sparkConf, Seconds(5))
+
+ val kafkaParams = Map[String, Object](
+ /*
+ * 指定 broker 的地址清单,清单里不需要包含所有的 broker 地址,生产者会从给定的 broker 里查找其他 broker 的信息。
+ * 不过建议至少提供两个 broker 的信息作为容错。
+ */
+ "bootstrap.servers" -> "hadoop001:9092",
+ /*键的序列化器*/
+ "key.deserializer" -> classOf[StringDeserializer],
+ /*值的序列化器*/
+ "value.deserializer" -> classOf[StringDeserializer],
+ /*消费者所在分组的 ID*/
+ "group.id" -> "spark-streaming-group",
+ /*
+ * 该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下该作何处理:
+ * latest: 在偏移量无效的情况下,消费者将从最新的记录开始读取数据(在消费者启动之后生成的记录)
+ * earliest: 在偏移量无效的情况下,消费者将从起始位置读取分区的记录
+ */
+ "auto.offset.reset" -> "latest",
+ /*是否自动提交*/
+ "enable.auto.commit" -> (true: java.lang.Boolean)
+ )
+
+ /*可以同时订阅多个主题*/
+ val topics = Array("spark-streaming-topic")
+ val stream = KafkaUtils.createDirectStream[String, String](
+ streamingContext,
+ /*位置策略*/
+ PreferConsistent,
+ /*订阅主题*/
+ Subscribe[String, String](topics, kafkaParams)
+ )
+
+ /*打印输入流*/
+ stream.map(record => (record.key, record.value)).print()
+
+ streamingContext.start()
+ streamingContext.awaitTermination()
+ }
+}
+```
+
+### 3.1 ConsumerRecord
+
+这里获得的输入流中每一个 Record 实际上是 `ConsumerRecord ` 的实例,其包含了 Record 的所有可用信息,源码如下:
+
+```scala
+public class ConsumerRecord {
+
+ public static final long NO_TIMESTAMP = RecordBatch.NO_TIMESTAMP;
+ public static final int NULL_SIZE = -1;
+ public static final int NULL_CHECKSUM = -1;
+
+ /*主题名称*/
+ private final String topic;
+ /*分区编号*/
+ private final int partition;
+ /*偏移量*/
+ private final long offset;
+ /*时间戳*/
+ private final long timestamp;
+ /*时间戳代表的含义*/
+ private final TimestampType timestampType;
+ /*键序列化器*/
+ private final int serializedKeySize;
+ /*值序列化器*/
+ private final int serializedValueSize;
+ /*值序列化器*/
+ private final Headers headers;
+ /*键*/
+ private final K key;
+ /*值*/
+ private final V value;
+ .....
+}
+```
+
+### 3.2 生产者属性
+
+在示例代码中 `kafkaParams` 封装了 Kafka 消费者的属性,这些属性和 Spark Streaming 无关,是 Kafka 原生 API 中就有定义的。其中服务器地址、键序列化器和值序列化器是必选的,其他配置是可选的。其余可选的配置项如下:
+
+#### 1. fetch.min.byte
+
+消费者从服务器获取记录的最小字节数。如果可用的数据量小于设置值,broker 会等待有足够的可用数据时才会把它返回给消费者。
+
+#### 2. fetch.max.wait.ms
+
+broker 返回给消费者数据的等待时间。
+
+#### 3. max.partition.fetch.bytes
+
+分区返回给消费者的最大字节数。
+
+#### 4. session.timeout.ms
+
+消费者在被认为死亡之前可以与服务器断开连接的时间。
+
+#### 5. auto.offset.reset
+
+该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下该作何处理:
+
+- latest(默认值) :在偏移量无效的情况下,消费者将从其启动之后生成的最新的记录开始读取数据;
+- earliest :在偏移量无效的情况下,消费者将从起始位置读取分区的记录。
+
+#### 6. enable.auto.commit
+
+是否自动提交偏移量,默认值是 true,为了避免出现重复数据和数据丢失,可以把它设置为 false。
+
+#### 7. client.id
+
+客户端 id,服务器用来识别消息的来源。
+
+#### 8. max.poll.records
+
+单次调用 `poll()` 方法能够返回的记录数量。
+
+#### 9. receive.buffer.bytes 和 send.buffer.byte
+
+这两个参数分别指定 TCP socket 接收和发送数据包缓冲区的大小,-1 代表使用操作系统的默认值。
+
+
+
+### 3.3 位置策略
+
+Spark Streaming 中提供了如下三种位置策略,用于指定 Kafka 主题分区与 Spark 执行程序 Executors 之间的分配关系:
+
++ **PreferConsistent** : 它将在所有的 Executors 上均匀分配分区;
+
++ **PreferBrokers** : 当 Spark 的 Executor 与 Kafka Broker 在同一机器上时可以选择该选项,它优先将该 Broker 上的首领分区分配给该机器上的 Executor;
++ **PreferFixed** : 可以指定主题分区与特定主机的映射关系,显示地将分区分配到特定的主机,其构造器如下:
+
+```scala
+@Experimental
+def PreferFixed(hostMap: collection.Map[TopicPartition, String]): LocationStrategy =
+ new PreferFixed(new ju.HashMap[TopicPartition, String](hostMap.asJava))
+
+@Experimental
+def PreferFixed(hostMap: ju.Map[TopicPartition, String]): LocationStrategy =
+ new PreferFixed(hostMap)
+```
+
+
+
+### 3.4 订阅方式
+
+Spark Streaming 提供了两种主题订阅方式,分别为 `Subscribe` 和 `SubscribePattern`。后者可以使用正则匹配订阅主题的名称。其构造器分别如下:
+
+```scala
+/**
+ * @param 需要订阅的主题的集合
+ * @param Kafka 消费者参数
+ * @param offsets(可选): 在初始启动时开始的偏移量。如果没有,则将使用保存的偏移量或 auto.offset.reset 属性的值
+ */
+def Subscribe[K, V](
+ topics: ju.Collection[jl.String],
+ kafkaParams: ju.Map[String, Object],
+ offsets: ju.Map[TopicPartition, jl.Long]): ConsumerStrategy[K, V] = { ... }
+
+/**
+ * @param 需要订阅的正则
+ * @param Kafka 消费者参数
+ * @param offsets(可选): 在初始启动时开始的偏移量。如果没有,则将使用保存的偏移量或 auto.offset.reset 属性的值
+ */
+def SubscribePattern[K, V](
+ pattern: ju.regex.Pattern,
+ kafkaParams: collection.Map[String, Object],
+ offsets: collection.Map[TopicPartition, Long]): ConsumerStrategy[K, V] = { ... }
+```
+
+在示例代码中,我们实际上并没有指定第三个参数 `offsets`,所以程序默认采用的是配置的 `auto.offset.reset` 属性的值 latest,即在偏移量无效的情况下,消费者将从其启动之后生成的最新的记录开始读取数据。
+
+### 3.5 提交偏移量
+
+在示例代码中,我们将 `enable.auto.commit` 设置为 true,代表自动提交。在某些情况下,你可能需要更高的可靠性,如在业务完全处理完成后再提交偏移量,这时候可以使用手动提交。想要进行手动提交,需要调用 Kafka 原生的 API :
+
++ `commitSync`: 用于异步提交;
++ `commitAsync`:用于同步提交。
+
+具体提交方式可以参见:[Kafka 消费者详解](https://github.com/heibaiying/BigData-Notes/blob/master/notes/Kafka 消费者详解.md)
+
+
+
+## 四、启动测试
+
+### 4.1 创建主题
+
+#### 1. 启动Kakfa
+
+Kafka 的运行依赖于 zookeeper,需要预先启动,可以启动 Kafka 内置的 zookeeper,也可以启动自己安装的:
+
+```shell
+# zookeeper启动命令
+bin/zkServer.sh start
+
+# 内置zookeeper启动命令
+bin/zookeeper-server-start.sh config/zookeeper.properties
+```
+
+启动单节点 kafka 用于测试:
+
+```shell
+# bin/kafka-server-start.sh config/server.properties
+```
+
+#### 2. 创建topic
+
+```shell
+# 创建用于测试主题
+bin/kafka-topics.sh --create \
+ --bootstrap-server hadoop001:9092 \
+ --replication-factor 1 \
+ --partitions 1 \
+ --topic spark-streaming-topic
+
+# 查看所有主题
+ bin/kafka-topics.sh --list --bootstrap-server hadoop001:9092
+```
+
+#### 3. 创建生产者
+
+这里创建一个 Kafka 生产者,用于发送测试数据:
+
+```shell
+bin/kafka-console-producer.sh --broker-list hadoop001:9092 --topic spark-streaming-topic
+```
+
+### 4.2 本地模式测试
+
+这里我直接使用本地模式启动 Spark Streaming 程序。启动后使用生产者发送数据,从控制台查看结果。
+
+从控制台输出中可以看到数据流已经被成功接收,由于采用 `kafka-console-producer.sh` 发送的数据默认是没有 key 的,所以 key 值为 null。同时从输出中也可以看到在程序中指定的 `groupId` 和程序自动分配的 `clientId`。
+
+
+
+
+
+
+
+## 参考资料
+
+1. https://spark.apache.org/docs/latest/streaming-kafka-0-10-integration.html
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_Structured_API\347\232\204\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_Structured_API\347\232\204\345\237\272\346\234\254\344\275\277\347\224\250.md"
new file mode 100644
index 0000000..5ba02c4
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_Structured_API\347\232\204\345\237\272\346\234\254\344\275\277\347\224\250.md"
@@ -0,0 +1,244 @@
+# Structured API基本使用
+
+
+一、创建DataFrame和Dataset
+二、Columns列操作
+三、使用Structured API进行基本查询
+四、使用Spark SQL进行基本查询
+
+
+
+## 一、创建DataFrame和Dataset
+
+### 1.1 创建DataFrame
+
+Spark 中所有功能的入口点是 `SparkSession`,可以使用 `SparkSession.builder()` 创建。创建后应用程序就可以从现有 RDD,Hive 表或 Spark 数据源创建 DataFrame。示例如下:
+
+```scala
+val spark = SparkSession.builder().appName("Spark-SQL").master("local[2]").getOrCreate()
+val df = spark.read.json("/usr/file/json/emp.json")
+df.show()
+
+// 建议在进行 spark SQL 编程前导入下面的隐式转换,因为 DataFrames 和 dataSets 中很多操作都依赖了隐式转换
+import spark.implicits._
+```
+
+可以使用 `spark-shell` 进行测试,需要注意的是 `spark-shell` 启动后会自动创建一个名为 `spark` 的 `SparkSession`,在命令行中可以直接引用即可:
+
+
+
+
+
+### 1.2 创建Dataset
+
+Spark 支持由内部数据集和外部数据集来创建 DataSet,其创建方式分别如下:
+
+#### 1. 由外部数据集创建
+
+```scala
+// 1.需要导入隐式转换
+import spark.implicits._
+
+// 2.创建 case class,等价于 Java Bean
+case class Emp(ename: String, comm: Double, deptno: Long, empno: Long,
+ hiredate: String, job: String, mgr: Long, sal: Double)
+
+// 3.由外部数据集创建 Datasets
+val ds = spark.read.json("/usr/file/emp.json").as[Emp]
+ds.show()
+```
+
+#### 2. 由内部数据集创建
+
+```scala
+// 1.需要导入隐式转换
+import spark.implicits._
+
+// 2.创建 case class,等价于 Java Bean
+case class Emp(ename: String, comm: Double, deptno: Long, empno: Long,
+ hiredate: String, job: String, mgr: Long, sal: Double)
+
+// 3.由内部数据集创建 Datasets
+val caseClassDS = Seq(Emp("ALLEN", 300.0, 30, 7499, "1981-02-20 00:00:00", "SALESMAN", 7698, 1600.0),
+ Emp("JONES", 300.0, 30, 7499, "1981-02-20 00:00:00", "SALESMAN", 7698, 1600.0))
+ .toDS()
+caseClassDS.show()
+```
+
+
+
+### 1.3 由RDD创建DataFrame
+
+Spark 支持两种方式把 RDD 转换为 DataFrame,分别是使用反射推断和指定 Schema 转换:
+
+#### 1. 使用反射推断
+
+```scala
+// 1.导入隐式转换
+import spark.implicits._
+
+// 2.创建部门类
+case class Dept(deptno: Long, dname: String, loc: String)
+
+// 3.创建 RDD 并转换为 dataSet
+val rddToDS = spark.sparkContext
+ .textFile("/usr/file/dept.txt")
+ .map(_.split("\t"))
+ .map(line => Dept(line(0).trim.toLong, line(1), line(2)))
+ .toDS() // 如果调用 toDF() 则转换为 dataFrame
+```
+
+#### 2. 以编程方式指定Schema
+
+```scala
+import org.apache.spark.sql.Row
+import org.apache.spark.sql.types._
+
+
+// 1.定义每个列的列类型
+val fields = Array(StructField("deptno", LongType, nullable = true),
+ StructField("dname", StringType, nullable = true),
+ StructField("loc", StringType, nullable = true))
+
+// 2.创建 schema
+val schema = StructType(fields)
+
+// 3.创建 RDD
+val deptRDD = spark.sparkContext.textFile("/usr/file/dept.txt")
+val rowRDD = deptRDD.map(_.split("\t")).map(line => Row(line(0).toLong, line(1), line(2)))
+
+
+// 4.将 RDD 转换为 dataFrame
+val deptDF = spark.createDataFrame(rowRDD, schema)
+deptDF.show()
+```
+
+
+
+### 1.4 DataFrames与Datasets互相转换
+
+Spark 提供了非常简单的转换方法用于 DataFrame 与 Dataset 间的互相转换,示例如下:
+
+```shell
+# DataFrames转Datasets
+scala> df.as[Emp]
+res1: org.apache.spark.sql.Dataset[Emp] = [COMM: double, DEPTNO: bigint ... 6 more fields]
+
+# Datasets转DataFrames
+scala> ds.toDF()
+res2: org.apache.spark.sql.DataFrame = [COMM: double, DEPTNO: bigint ... 6 more fields]
+```
+
+
+
+## 二、Columns列操作
+
+### 2.1 引用列
+
+Spark 支持多种方法来构造和引用列,最简单的是使用 `col() ` 或 `column() ` 函数。
+
+```scala
+col("colName")
+column("colName")
+
+// 对于 Scala 语言而言,还可以使用$"myColumn"和'myColumn 这两种语法糖进行引用。
+df.select($"ename", $"job").show()
+df.select('ename, 'job).show()
+```
+
+### 2.2 新增列
+
+```scala
+// 基于已有列值新增列
+df.withColumn("upSal",$"sal"+1000)
+// 基于固定值新增列
+df.withColumn("intCol",lit(1000))
+```
+
+### 2.3 删除列
+
+```scala
+// 支持删除多个列
+df.drop("comm","job").show()
+```
+
+### 2.4 重命名列
+
+```scala
+df.withColumnRenamed("comm", "common").show()
+```
+
+需要说明的是新增,删除,重命名列都会产生新的 DataFrame,原来的 DataFrame 不会被改变。
+
+
+
+## 三、使用Structured API进行基本查询
+
+```scala
+// 1.查询员工姓名及工作
+df.select($"ename", $"job").show()
+
+// 2.filter 查询工资大于 2000 的员工信息
+df.filter($"sal" > 2000).show()
+
+// 3.orderBy 按照部门编号降序,工资升序进行查询
+df.orderBy(desc("deptno"), asc("sal")).show()
+
+// 4.limit 查询工资最高的 3 名员工的信息
+df.orderBy(desc("sal")).limit(3).show()
+
+// 5.distinct 查询所有部门编号
+df.select("deptno").distinct().show()
+
+// 6.groupBy 分组统计部门人数
+df.groupBy("deptno").count().show()
+```
+
+
+
+## 四、使用Spark SQL进行基本查询
+
+### 4.1 Spark SQL基本使用
+
+```scala
+// 1.首先需要将 DataFrame 注册为临时视图
+df.createOrReplaceTempView("emp")
+
+// 2.查询员工姓名及工作
+spark.sql("SELECT ename,job FROM emp").show()
+
+// 3.查询工资大于 2000 的员工信息
+spark.sql("SELECT * FROM emp where sal > 2000").show()
+
+// 4.orderBy 按照部门编号降序,工资升序进行查询
+spark.sql("SELECT * FROM emp ORDER BY deptno DESC,sal ASC").show()
+
+// 5.limit 查询工资最高的 3 名员工的信息
+spark.sql("SELECT * FROM emp ORDER BY sal DESC LIMIT 3").show()
+
+// 6.distinct 查询所有部门编号
+spark.sql("SELECT DISTINCT(deptno) FROM emp").show()
+
+// 7.分组统计部门人数
+spark.sql("SELECT deptno,count(ename) FROM emp group by deptno").show()
+```
+
+### 4.2 全局临时视图
+
+上面使用 `createOrReplaceTempView` 创建的是会话临时视图,它的生命周期仅限于会话范围,会随会话的结束而结束。
+
+你也可以使用 `createGlobalTempView` 创建全局临时视图,全局临时视图可以在所有会话之间共享,并直到整个 Spark 应用程序终止后才会消失。全局临时视图被定义在内置的 `global_temp` 数据库下,需要使用限定名称进行引用,如 `SELECT * FROM global_temp.view1`。
+
+```scala
+// 注册为全局临时视图
+df.createGlobalTempView("gemp")
+
+// 使用限定名称进行引用
+spark.sql("SELECT ename,job FROM global_temp.gemp").show()
+```
+
+
+
+## 参考资料
+
+[Spark SQL, DataFrames and Datasets Guide > Getting Started](https://spark.apache.org/docs/latest/sql-getting-started.html)
diff --git "a/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_Transformation\345\222\214Action\347\256\227\345\255\220.md" "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_Transformation\345\222\214Action\347\256\227\345\255\220.md"
new file mode 100644
index 0000000..eb5603c
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256\346\241\206\346\236\266\345\255\246\344\271\240/Spark_Transformation\345\222\214Action\347\256\227\345\255\220.md"
@@ -0,0 +1,418 @@
+# Transformation 和 Action 常用算子
+
+
+一、Transformation
+ 1.1 map
+ 1.2 filter
+ 1.3 flatMap
+ 1.4 mapPartitions
+ 1.5 mapPartitionsWithIndex
+ 1.6 sample
+ 1.7 union
+ 1.8 intersection
+ 1.9 distinct
+ 1.10 groupByKey
+ 1.11 reduceByKey
+ 1.12 sortBy & sortByKey
+ 1.13 join
+ 1.14 cogroup
+ 1.15 cartesian
+ 1.16 aggregateByKey
+二、Action
+ 2.1 reduce
+ 2.2 takeOrdered
+ 2.3 countByKey
+ 2.4 saveAsTextFile
+
+
+## 一、Transformation
+
+spark 常用的 Transformation 算子如下表:
+
+| Transformation 算子 | Meaning(含义) |
+| ------------------------------------------------------------ | ------------------------------------------------------------ |
+| **map**(*func*) | 对原 RDD 中每个元素运用 *func* 函数,并生成新的 RDD |
+| **filter**(*func*) | 对原 RDD 中每个元素使用*func* 函数进行过滤,并生成新的 RDD |
+| **flatMap**(*func*) | 与 map 类似,但是每一个输入的 item 被映射成 0 个或多个输出的 items( *func* 返回类型需要为 Seq )。 |
+| **mapPartitions**(*func*) | 与 map 类似,但函数单独在 RDD 的每个分区上运行, *func*函数的类型为 Iterator\ => Iterator\ ,其中 T 是 RDD 的类型,即 RDD[T] |
+| **mapPartitionsWithIndex**(*func*) | 与 mapPartitions 类似,但 *func* 类型为 (Int, Iterator\) => Iterator\ ,其中第一个参数为分区索引 |
+| **sample**(*withReplacement*, *fraction*, *seed*) | 数据采样,有三个可选参数:设置是否放回(withReplacement)、采样的百分比(*fraction*)、随机数生成器的种子(seed); |
+| **union**(*otherDataset*) | 合并两个 RDD |
+| **intersection**(*otherDataset*) | 求两个 RDD 的交集 |
+| **distinct**([*numTasks*])) | 去重 |
+| **groupByKey**([*numTasks*]) | 按照 key 值进行分区,即在一个 (K, V) 对的 dataset 上调用时,返回一个 (K, Iterable\) **Note:** 如果分组是为了在每一个 key 上执行聚合操作(例如,sum 或 average),此时使用 `reduceByKey` 或 `aggregateByKey` 性能会更好 **Note:** 默认情况下,并行度取决于父 RDD 的分区数。可以传入 `numTasks` 参数进行修改。 |
+| **reduceByKey**(*func*, [*numTasks*]) | 按照 key 值进行分组,并对分组后的数据执行归约操作。 |
+| **aggregateByKey**(*zeroValue*,*numPartitions*)(*seqOp*, *combOp*, [*numTasks*]) | 当调用(K,V)对的数据集时,返回(K,U)对的数据集,其中使用给定的组合函数和 zeroValue 聚合每个键的值。与 groupByKey 类似,reduce 任务的数量可通过第二个参数进行配置。 |
+| **sortByKey**([*ascending*], [*numTasks*]) | 按照 key 进行排序,其中的 key 需要实现 Ordered 特质,即可比较 |
+| **join**(*otherDataset*, [*numTasks*]) | 在一个 (K, V) 和 (K, W) 类型的 dataset 上调用时,返回一个 (K, (V, W)) pairs 的 dataset,等价于内连接操作。如果想要执行外连接,可以使用 `leftOuterJoin`, `rightOuterJoin` 和 `fullOuterJoin` 等算子。 |
+| **cogroup**(*otherDataset*, [*numTasks*]) | 在一个 (K, V) 对的 dataset 上调用时,返回一个 (K, (Iterable\, Iterable\)) tuples 的 dataset。 |
+| **cartesian**(*otherDataset*) | 在一个 T 和 U 类型的 dataset 上调用时,返回一个 (T, U) 类型的 dataset(即笛卡尔积)。 |
+| **coalesce**(*numPartitions*) | 将 RDD 中的分区数减少为 numPartitions。 |
+| **repartition**(*numPartitions*) | 随机重新调整 RDD 中的数据以创建更多或更少的分区,并在它们之间进行平衡。 |
+| **repartitionAndSortWithinPartitions**(*partitioner*) | 根据给定的 partitioner(分区器)对 RDD 进行重新分区,并对分区中的数据按照 key 值进行排序。这比调用 `repartition` 然后再 sorting(排序)效率更高,因为它可以将排序过程推送到 shuffle 操作所在的机器。 |
+
+下面分别给出这些算子的基本使用示例:
+
+### 1.1 map
+
+```scala
+val list = List(1,2,3)
+sc.parallelize(list).map(_ * 10).foreach(println)
+
+// 输出结果: 10 20 30 (这里为了节省篇幅去掉了换行,后文亦同)
+```
+
+### 1.2 filter
+
+```scala
+val list = List(3, 6, 9, 10, 12, 21)
+sc.parallelize(list).filter(_ >= 10).foreach(println)
+
+// 输出: 10 12 21
+```
+
+### 1.3 flatMap
+
+`flatMap(func)` 与 `map` 类似,但每一个输入的 item 会被映射成 0 个或多个输出的 items( *func* 返回类型需要为 `Seq`)。
+
+```scala
+val list = List(List(1, 2), List(3), List(), List(4, 5))
+sc.parallelize(list).flatMap(_.toList).map(_ * 10).foreach(println)
+
+// 输出结果 : 10 20 30 40 50
+```
+
+flatMap 这个算子在日志分析中使用概率非常高,这里进行一下演示:拆分输入的每行数据为单个单词,并赋值为 1,代表出现一次,之后按照单词分组并统计其出现总次数,代码如下:
+
+```scala
+val lines = List("spark flume spark",
+ "hadoop flume hive")
+sc.parallelize(lines).flatMap(line => line.split(" ")).
+map(word=>(word,1)).reduceByKey(_+_).foreach(println)
+
+// 输出:
+(spark,2)
+(hive,1)
+(hadoop,1)
+(flume,2)
+```
+
+### 1.4 mapPartitions
+
+与 map 类似,但函数单独在 RDD 的每个分区上运行, *func*函数的类型为 `Iterator |