Showing preview only (1,748K chars total). Download the full file or copy to clipboard to get everything.
Repository: wangzhiwubigdata/God-Of-BigData
Branch: master
Commit: 4b83d906ac7b
Files: 250
Total size: 1.6 MB
Directory structure:
gitextract_brrx73xl/
├── Flink/
│ ├── 10-Flink集群的高可用(搭建篇补充).md
│ ├── 11-时间戳和水印.md
│ ├── 12-Broadcast广播变量.md
│ ├── 13-Flink-Kafka-Connector.md
│ ├── 14-Flink-Table-&-SQL.md
│ ├── 15-Flink实战项目之实时热销排行.md
│ ├── 16-Flink-Redis-Sink.md
│ ├── 17-Flink消费Kafka写入Mysql.md
│ ├── 6-Flink重启策略.md
│ ├── 7-Flink的分布式缓存.md
│ ├── 8-Flink中的窗口.md
│ ├── 9-Flink中的Time.md
│ ├── Flink从入门到放弃(入门篇1)-Flink是什么?.md
│ ├── Flink从入门到放弃(入门篇2)-本地环境搭建&构建第一个Flink应用.md
│ ├── Flink从入门到放弃(入门篇3)-DataSetAPI.md
│ ├── Flink从入门到放弃(入门篇4)-DataStreamAPI.md
│ └── Flink集群部署.md
├── Flink漫谈系列/
│ ├── Apache-Flink-漫谈系列(02)-Watermark.md
│ ├── Apache-Flink-漫谈系列(03)-State.md
│ ├── Apache-Flink漫谈系列(1)-概述.md
│ └── 我的Markdown笔记/
│ └── Apache-Flink-漫谈系列(03)-State.md
├── Hadoop/
│ ├── Hadoop极简入门.md
│ └── MapReduce编程模型和计算框架架构原理.md
├── JVM/
│ ├── HotSpot垃圾收集器.md
│ ├── HotSpot虚拟机对象探秘.md
│ ├── JVM 性能调优.md
│ ├── JVM内存结构.md
│ ├── jvm系列(一)java类的加载机制.md
│ ├── jvm系列(三)GC算法 垃圾收集器.md
│ ├── jvm系列(二)JVM内存结构.md
│ ├── jvm系列(五)Java GC 分析.md
│ ├── jvm系列(四)jvm调优-命令大全(jps jstat jmap jhat jstack jinfo).md
│ ├── 内存分配与回收策略.md
│ ├── 垃圾收集策略与算法.md
│ ├── 我的Markdown笔记/
│ │ └── jvm系列(五)Java GC 分析.md
│ ├── 类加载器.md
│ ├── 类加载的时机.md
│ ├── 类加载的过程.md
│ └── 类文件结构.md
├── Java高级特性增强/
│ ├── Java NIO之Buffer(缓冲区).md
│ ├── Java NIO之Channel(通道).md
│ ├── Java NIO之Selector(选择器).md
│ ├── Java NIO之拥抱Path和Files.md
│ ├── NIO概览.md
│ ├── 大数据成神之路-Java高级特性增强(HashMap).md
│ ├── 大数据成神之路-Java高级特性增强(HashSet).md
│ ├── 大数据成神之路-Java高级特性增强(LinkedHashMap).md
│ ├── 大数据成神之路-Java高级特性增强(Synchronized关键字).md
│ ├── 大数据成神之路-Java高级特性增强(volatile关键字).md
│ ├── 大数据成神之路-Java高级特性增强(多线程).md
│ ├── 大数据成神之路-Java高级特性增强(锁).md
│ ├── 大数据成神之路-Java高级特性增强(集合框架).md
│ ├── 大数据成神之路-Java高级特性增强-NIO.md
│ └── 大数据成神之路-Java高级特性增强.md
├── Kafka/
│ ├── Apache-Kafka安装和使用.md
│ ├── Apache-Kafka核心概念.md
│ ├── Apache-Kafka核心组件和流程(副本管理器).md
│ ├── Apache-Kafka核心组件和流程-协调器.md
│ ├── Apache-Kafka核心组件和流程-控制器.md
│ ├── Apache-Kafka核心组件和流程-日志管理器.md
│ ├── Apache-Kafka简介.md
│ └── Apache-Kafka编程实战.md
├── Linux基础/
│ └── Linux基础和命令.md
├── NIO/
│ ├── Java NIO之Buffer(缓冲区).md
│ ├── Java NIO之Channel(通道).md
│ ├── Java NIO之Selector(选择器).md
│ ├── Java NIO之拥抱Path和Files.md
│ └── NIO概览.md
├── Netty/
│ ├── Netty源码解析-概述篇.md
│ ├── Netty源码解析1-Buffer.md
│ ├── Netty源码解析2-Reactor.md
│ ├── Netty源码解析3-Pipeline.md
│ ├── Netty源码解析4-Handler综述.md
│ ├── Netty源码解析5-ChannelHandler.md
│ ├── Netty源码解析6-ChannelHandler实例之LoggingHandler.md
│ ├── Netty源码解析7-ChannelHandler实例之TimeoutHandler.md
│ ├── Netty源码解析8-ChannelHandler实例之CodecHandler.md
│ ├── Netty源码解析9-ChannelHandler实例之MessageToByteEncoder.md
│ └── 关于Netty我们都需要知道什么.md
├── README.md
├── RPC/
│ ├── RPC的原理和框架.md
│ ├── RPC简单介绍.md
│ └── 手把手教你实现一个简单的RPC.md
├── WechatArticles/
│ └── flink.md
├── pictures/
│ ├── bigdata-notes-icon.psd
│ └── 大数据技术栈思维导图.xmind
├── zookeeeper/
│ ├── ZooKeeper应用程序.md
│ ├── zk安装和运行.md
│ ├── zk开发实例.md
│ └── zk服务.md
├── 分布式理论/
│ ├── 分布式ID生成器解决方案.md
│ ├── 分布式事务的解决方案.md
│ ├── 分布式系统理论基础一: 一致性、2PC和3PC.md
│ ├── 分布式系统理论基础三-时间、时钟和事件顺序.md
│ ├── 分布式系统理论基础二-CAP.md
│ ├── 分布式系统理论进阶 - Paxos.md
│ ├── 分布式系统理论进阶 - Raft、Zab.md
│ ├── 分布式系统理论进阶:选举、多数派和租约.md
│ ├── 分布式系统的一些基本概念.md
│ ├── 分布式锁的解决方案(二).md
│ └── 分布式锁的解决方案.md
├── 大数据框架学习/
│ ├── Azkaban_Flow_1.0_的使用.md
│ ├── Azkaban_Flow_2.0_的使用.md
│ ├── Azkaban简介.md
│ ├── Flink_Data_Sink.md
│ ├── Flink_Data_Source.md
│ ├── Flink_Data_Transformation.md
│ ├── Flink_Windows.md
│ ├── Flink开发环境搭建.md
│ ├── Flink核心概念综述.md
│ ├── Flink状态管理与检查点机制.md
│ ├── Flume整合Kafka.md
│ ├── Flume简介及基本使用.md
│ ├── HDFS-Java-API.md
│ ├── HDFS常用Shell命令.md
│ ├── Hadoop-HDFS.md
│ ├── Hadoop-MapReduce.md
│ ├── Hadoop-YARN.md
│ ├── Hbase_Java_API.md
│ ├── Hbase_Shell.md
│ ├── Hbase协处理器详解.md
│ ├── Hbase容灾与备份.md
│ ├── Hbase的SQL中间层_Phoenix.md
│ ├── Hbase简介.md
│ ├── Hbase系统架构及数据结构.md
│ ├── Hbase过滤器详解.md
│ ├── HiveCLI和Beeline命令行的基本使用.md
│ ├── Hive分区表和分桶表.md
│ ├── Hive常用DDL操作.md
│ ├── Hive常用DML操作.md
│ ├── Hive数据查询详解.md
│ ├── Hive简介及核心概念.md
│ ├── Hive视图和索引.md
│ ├── Kafka消费者详解.md
│ ├── Kafka深入理解分区副本机制.md
│ ├── Kafka生产者详解.md
│ ├── Kafka简介.md
│ ├── Scala函数和闭包.md
│ ├── Scala列表和集.md
│ ├── Scala基本数据类型和运算符.md
│ ├── Scala数组.md
│ ├── Scala映射和元组.md
│ ├── Scala模式匹配.md
│ ├── Scala流程控制语句.md
│ ├── Scala简介及开发环境配置.md
│ ├── Scala类和对象.md
│ ├── Scala类型参数.md
│ ├── Scala继承和特质.md
│ ├── Scala隐式转换和隐式参数.md
│ ├── Scala集合类型.md
│ ├── SparkSQL_Dataset和DataFrame简介.md
│ ├── SparkSQL外部数据源.md
│ ├── SparkSQL常用聚合函数.md
│ ├── SparkSQL联结操作.md
│ ├── Spark_RDD.md
│ ├── Spark_Streaming与流处理.md
│ ├── Spark_Streaming基本操作.md
│ ├── Spark_Streaming整合Flume.md
│ ├── Spark_Streaming整合Kafka.md
│ ├── Spark_Structured_API的基本使用.md
│ ├── Spark_Transformation和Action算子.md
│ ├── Spark简介.md
│ ├── Spark累加器与广播变量.md
│ ├── Spark部署模式与作业提交.md
│ ├── Spring+Mybtais+Phoenix整合.md
│ ├── Sqoop基本使用.md
│ ├── Sqoop简介与安装.md
│ ├── Storm三种打包方式对比分析.md
│ ├── Storm和流处理简介.md
│ ├── Storm核心概念详解.md
│ ├── Storm编程模型详解.md
│ ├── Storm集成HBase和HDFS.md
│ ├── Storm集成Kakfa.md
│ ├── Storm集成Redis详解.md
│ ├── Zookeeper_ACL权限控制.md
│ ├── Zookeeper_Java客户端Curator.md
│ ├── Zookeeper常用Shell命令.md
│ ├── Zookeeper简介及核心概念.md
│ ├── installation/
│ │ ├── Azkaban_3.x_编译及部署.md
│ │ ├── Flink_Standalone_Cluster.md
│ │ ├── HBase单机环境搭建.md
│ │ ├── HBase集群环境搭建.md
│ │ ├── Hadoop单机环境搭建.md
│ │ ├── Hadoop集群环境搭建.md
│ │ ├── Linux下Flume的安装.md
│ │ ├── Linux下JDK安装.md
│ │ ├── Linux下Python安装.md
│ │ ├── Linux环境下Hive的安装部署.md
│ │ ├── Spark开发环境搭建.md
│ │ ├── Spark集群环境搭建.md
│ │ ├── Storm单机环境搭建.md
│ │ ├── Storm集群环境搭建.md
│ │ ├── Zookeeper单机环境和集群环境搭建.md
│ │ ├── 基于Zookeeper搭建Hadoop高可用集群.md
│ │ ├── 基于Zookeeper搭建Kafka高可用集群.md
│ │ └── 虚拟机静态IP及多IP配置.md
│ ├── 大数据学习路线.md
│ ├── 大数据常用软件安装指南.md
│ ├── 大数据应用常用打包方式.md
│ ├── 大数据技术栈思维导图.md
│ └── 资料分享与工具推荐.md
├── 实战系列文章/
│ ├── Flink实战.md
│ ├── Kafka实战.md
│ ├── OLAP.md
│ ├── Spark实战.md
│ ├── 数据仓库.md
│ └── 面试系列.md
├── 并发容器/
│ ├── 大数据成神之路-Java高级特性增强(ArrayBlockingQueue).md
│ ├── 大数据成神之路-Java高级特性增强(ConcurrentHashMap).md
│ ├── 大数据成神之路-Java高级特性增强(ConcurrentLinkedQueue).md
│ ├── 大数据成神之路-Java高级特性增强(ConcurrentSkipListMap).md
│ ├── 大数据成神之路-Java高级特性增强(ConcurrentSkipListSet).md
│ ├── 大数据成神之路-Java高级特性增强(CopyOnWriteArrayList).md
│ ├── 大数据成神之路-Java高级特性增强(CopyOnWriteArraySet).md
│ ├── 大数据成神之路-Java高级特性增强(LinkedBlockingDeque).md
│ ├── 大数据成神之路-Java高级特性增强(LinkedBlockingQueue).md
│ └── 大数据成神之路-Java高级特性增强(并发容器大纲).md
└── 面试系列/
├── Flume面试题整理/
│ └── Flume.md
├── HBase面试题整理/
│ └── HBase.md
├── Hadoop面试题总结/
│ ├── Hadoop面试题总结(一).md
│ ├── Hadoop面试题总结(三)——MapReduce.md
│ ├── Hadoop面试题总结(二)——HDFS.md
│ ├── Hadoop面试题总结(五)——优化问题.md
│ └── Hadoop面试题总结(四)——YARN.md
├── Hive面试题总结/
│ ├── Hive(一).md
│ └── Hive(二).md
├── Kafka面试题整理/
│ ├── Kafka(一).md
│ └── Kafka(二).md
├── Spark面试题整理/
│ ├── Spark调优/
│ │ ├── Shuffle配置调优.md
│ │ ├── 数据倾斜.md
│ │ ├── 程序开发调优.md
│ │ └── 资源调优.md
│ ├── Spark(一).md
│ ├── Spark(三).md
│ ├── Spark(二).md
│ └── Spark(四).md
├── Zookeeper面试题总结/
│ └── Zookeeper.md
└── pics/
├── Flume面试题Pics/
│ └── 1.md
├── HBase面试题Pics/
│ └── 1.md
├── Hadoop面试题Pics/
│ ├── 1.md
│ ├── HDFS文档-Pics/
│ │ └── 1.md
│ ├── MR-Pics/
│ │ └── 1.md
│ └── YARN-Pics/
│ └── 1.md
├── Hive面试题Pics/
│ └── 1.md
├── Kafka面试题Pics/
│ └── 1.md
├── Spark面试题Pics/
│ ├── 1.md
│ ├── 数据倾斜调优/
│ │ └── 1.md
│ ├── 程序开发调优/
│ │ └── 1.md
│ └── 资源调优/
│ └── 1.md
└── ZK面试题Pics/
└── 1.md
================================================
FILE CONTENTS
================================================
================================================
FILE: Flink/10-Flink集群的高可用(搭建篇补充).md
================================================
Flink的HA搭建并不复杂,本质来说就是配置2个jobmanager。
本文作为Flink集群部署的补充篇。
> 这篇文章来自网络,向作者尼小摩致敬,
## 概述
JobManager 协调每个 Flink 部署。它负责调度和资源管理。
默认情况下,每个 Flink 集群只有一个 JobManager 实例。 这会产生单点故障(SPOF):如果 JobManager 崩溃,则无法提交新作业并且导致运行中的作业运行失败。
使用 JobManager 高可用性模式,可以避免这个问题,从而消除 SPOF。您可以为Standalone和 YARN 集群配置高可用性。
## Standalone集群高可用性
针对 Standalone 集群的 JobManager 高可用性的一般概念是,任何时候都有一个 主 JobManager 和多个备 JobManagers,以便在主节点失败时有备 JobManagers 来接管集群。这保证了没有单点故障,一旦备 JobManager 接管集群,作业就可以正常运行。主备 JobManager 实例之间没有明显的区别。每个 JobManager 都可以充当主备节点。
例如,请考虑以下三个 JobManager 实例的设置:
.resources/6278EDED-A65A-4539-A16D-7BCD9FE77864.png)
### 配置
要启用 JobManager 高可用性,您必须将高可用性模式设置为 zookeeper,配置 zookeeper quorum 将所有 JobManager 主机及其 web UI 端口写入配置文件。
Flink利用 ZooKeeper 在所有正在运行的 JobManager 实例之间进行分布式协调。 ZooKeeper 是独立于 Flink 的服务,通过 Leader 选举和轻量级一致状态存储提供高可靠的分布式协调。
### Masters文件 (masters服务器)
要启动HA集群,请在以下位置配置Master文件
* conf/masters:masters文件:masters文件包含启动 jobmanager 的所有主机和 web 用户界面绑定的端口。
```
jobManagerAddress1:webUIPort1
[...]
jobManagerAddressX:webUIPortX
```
默认情况下,job manager选一个随机端口作为进程随机通信端口。您可以通过 high-availability.jobmanager.port 键修改此设置。此配置接受单个端口(例如50010),范围(50000-50025)或两者的组合(50010,50011,50020-50025,50050-50075)。
### 配置文件(flink-conf.yaml)
要启动HA集群,请将以下配置键添加到 conf/flink-conf.yaml:
* 高可用性模式(必需):在 conf/flink-conf.yaml 中,必须将高可用性模式设置为zookeeper,以打开高可用模式。或者将此选项设置为工厂类的 FQN,Flink 通过创建 HighAvailabilityServices 实例使用。
```
high-availability: zookeeper
```
* Zookeeper quorum(必需): ZooKeeper quorum 是 ZooKeeper 服务器的复制组,它提供分布式协调服务。
```
high-availability.zookeeper.quorum:address1:2181[,...],addressX:2181
```
每个 addressX:port 都是一个 ZooKeeper 服务器的ip及其端口,Flink 可以在指定的地址和端口访问zookeeper。
* ZooKeeper root (推荐): ZooKeeper 根节点,在该节点下放置所有集群节点。
```
high-availability.zookeeper.path.root: /flink
```
* ZooKeeper cluster-id(推荐): ZooKeeper的cluster-id节点,在该节点下放置集群的所有相关数据。
```
high-availability.cluster-id: /default_ns # important: customize per cluster
```
**重要:** 在运行 YARN 或其他群集管理器中运行时,不要手动设置此值。在这些情况下,将根据应用程序 ID 自动生成 cluster-id。 手动设置 cluster-id 会覆盖 YARN 中的自动生成的 ID。反过来,使用 -z CLI 选项指定 cluster-id 会覆盖手动配置。如果在裸机上运行多个 Flink HA 集群,则必须为每个集群手动配置单独的 cluster-id。
* 存储目录(必需): JobManager 元数据保存在文件系统 storageDir 中,在 ZooKeeper 中仅保存了指向此状态的指针。
```
high-availability.storageDir: hdfs:///flink/recovery
```
该storageDir 中保存了 JobManager 恢复状态所需的所有元数据。
配置 master 文件和 ZooKeeper quorum 之后,您可以使用提供的集群启动脚本。它们将启动 HA 群集。请注意,启动 Flink HA 集群前,必须启动 Zookeeper 集群,并确保为要启动的每个 HA 群集配置单独的 ZooKeeper 根路径。
**示例:具有2个 JobManager 的 Standalone 集群**
1. 在conf/flink-conf.yaml 中配置高可用模式和 ZooKeeper quorum:
```
high-availability: zookeeper
high-availability.zookeeper.quorum: localhost:2181
high-availability.zookeeper.path.root: /flink
high-availability.cluster-id: /cluster_one
high-availability.storageDir: hdfs:///flink/recovery
```
2. 在 conf/master 中配置 master:
```
localhost:8081
localhost:8082
```
3. 在 conf/zoo.cfg 中配置 ZooKeeper 服务(目前,每台机器只能运行一个 ZooKeeper 进程)
```
server.0=localhost:2888:3888
```
4. 启动 ZooKeeper quorum:
```
$ bin/start-zookeeper-quorum.sh
Starting zookeeper daemon on host localhost.
```
5. 启动 Flink HA 集群:
```
$ bin/start-cluster.sh
Starting HA cluster with 2 masters and 1 peers in ZooKeeper quorum.
Starting jobmanager daemon on host localhost.
Starting jobmanager daemon on host localhost.
Starting taskmanager daemon on host localhost.
```
6. 停止 Zookeeper quorum 和集群:
```
$ bin/stop-cluster.sh
Stopping taskmanager daemon (pid: 7647) on localhost.
Stopping jobmanager daemon (pid: 7495) on host localhost.
Stopping jobmanager daemon (pid: 7349) on host localhost.
$ bin/stop-zookeeper-quorum.sh
Stopping zookeeper daemon (pid: 7101) on host localhost.
```
## YARN 集群的高可用性
在运行高可用性 YARN 集群时,我们不会运行多个 JobManager (ApplicationMaster) 实例,而只运行一个,该JobManager实例失败时,YARN会将其重新启动。Yarn的具体行为取决于您使用的 YARN 版本。
### 配置
Application Master最大重试次数(yarn-site.xml)
在YARN 配置文件 yarn-site.xml 中,需要配置 application master 的最大重试次数:
```
<property>
<name>yarn.resourcemanager.am.max-attempts</name>
<value>4</value>
<description>
The maximum number of application master execution attempts.
</description>
</property>
```
当前 YARN 版本的默认值是2(表示允许单个JobManager失败两次)。
Application Attempts(flink-conf.yaml):
除了HA配置(参考上文)之外,您还必须配置最大重试次数 conf/flink-conf.yaml:
```
yarn.application-attempts: 10
```
这意味着在如果程序启动失败,YARN会再重试9次(9 次重试 + 1次启动)。如果 YARN 操作需要,如果启动10次作业还失败,yarn才会将该任务的状态置为失败。如果抢占,节点硬件故障或重启,NodeManager 重新同步等操作需要,YARN继续尝试启动应用。 这些重启不计入 yarn.application-attempts 个数中。重要的是要注意 yarn.resourcemanager.am.max-attempts 为yarn中程序重启上限。因此, Flink 中设置的程序尝试次数不能超过 YARN 的集群设置。
### 示例:高可用的YARN Session
1.配置 HA 模式和 ZooKeeper 集群在 conf/flink-conf.yaml 中:
```
high-availability: zookeeper
high-availability.zookeeper.quorum: localhost:2181
high-availability.storageDir: hdfs:///flink/recovery
high-availability.zookeeper.path.root: /flink
yarn.application-attempts: 10
```
2. 配置 ZooKeeper 服务在 conf/zoo.cfg 中(目前每台机器只能运行一个 ZooKeeper 进程):
```
server.0=localhost:2888:3888
```
3. 启动 ZooKeeper 集群:
```
$ bin/start-zookeeper-quorum.sh
Starting zookeeper daemon on host localhost.
```
4. 启动 HA 集群:
```
$ bin / yarn-session.sh -n 2
```
### 配置 Zookeeper 安全性
如果 ZooKeeper 使用 Kerberos 以安全模式运行,flink-conf.yaml 根据需要覆盖以下配置:
```
zookeeper.sasl.service-name: zookeeper
# 默认设置是 “zookeeper” 。如果 ZooKeeper 集群配置了
# 不同的服务名称,那么可以在这里提供。
zookeeper.sasl.login-context-name: Client
# 默认设置是 “Client”。该值配置需要匹配
# "security.kerberos.login.contexts"中的其中一个值。
```
有关 Kerberos 安全性的 Flink 配置的更多信息,请参阅 此处。您还可以在 此处 找到关于 Flink 内部如何设置基于 kerberos 的安全性的详细信息。
### Bootstrap ZooKeeper
如果您没有正在运行的ZooKeeper,则可以使用Flink程序附带的脚本。
这是一个 ZooKeeper 配置模板 conf/zoo.cfg。您可以为主机配置为使用 server.X 条目运行 ZooKeeper,其中 X 是每个服务器的唯一IP:
```
server.X=addressX:peerPort:leaderPort
[...]
server.Y=addressY:peerPort:leaderPort
```
该脚本 bin/start-zookeeper-quorum.sh 将在每个配置的主机上启动 ZooKeeper 服务器。 Flink wrapper 会启动 ZooKeeper 服务,该 wraper 从 conf/zoo.cfg 中读取配置,并设置一些必需的配置项。在生产设置中,建议您使用自己安装的 ZooKeeper。
================================================
FILE: Flink/11-时间戳和水印.md
================================================
本文作者为阿里巴巴高级技术专家:金竹,原文发表在云栖社区。
地址为:https://yq.aliyun.com/articles/666056?spm=a2c4e.11155435.0.0.106e1b10snGqMd
## 实际问题(乱序)
在介绍Watermark相关内容之前我们先抛出一个具体的问题,在实际的流式计算中数据到来的顺序对计算结果的正确性有至关重要的影响,比如:某数据源中的某些数据由于某种原因(如:网络原因,外部存储自身原因)会有5秒的延时,也就是在实际时间的第1秒产生的数据有可能在第5秒中产生的数据之后到来(比如到Window处理节点).选具体某个delay的元素来说,假设在一个5秒的Tumble窗口(详见Window介绍章节),有一个EventTime是 11秒的数据,在第16秒时候到来了。图示第11秒的数据,在16秒到来了,如下图:

那么对于一个Count聚合的Tumble(5s)的window,上面的情况如何处理才能window2=4,window3=2 呢?Apache Flink的时间类型
开篇我们描述的问题是一个很常见的TimeWindow中数据乱序的问题,乱序是相对于事件产生时间和到达Apache Flink 实际处理算子的顺序而言的,关于时间在Apache Flink中有如下三种时间类型,如下图:

那么对于一个Count聚合的Tumble(5s)的window,上面的情况如何处理才能window2=4,window3=2 呢?
## Apache Flink的时间类型
开篇我们描述的问题是一个很常见的TimeWindow中数据乱序的问题,乱序是相对于事件产生时间和到达Apache Flink 实际处理算子的顺序而言的,关于时间在Apache Flink中有如下三种时间类型,如下图:

* ProcessingTime
是数据流入到具体某个算子时候相应的系统时间。ProcessingTime 有最好的性能和最低的延迟。但在分布式计算环境中ProcessingTime具有不确定性,相同数据流多次运行有可能产生不同的计算结果。
* IngestionTime
IngestionTime是数据进入Apache Flink框架的时间,是在Source Operator中设置的。与ProcessingTime相比可以提供更可预测的结果,因为IngestionTime的时间戳比较稳定(在源处只记录一次),同一数据在流经不同窗口操作时将使用相同的时间戳,而对于ProcessingTime同一数据在流经不同窗口算子会有不同的处理时间戳。
* EventTime
EventTime是事件在设备上产生时候携带的。在进入Apache Flink框架之前EventTime通常要嵌入到记录中,并且EventTime也可以从记录中提取出来。在实际的网上购物订单等业务场景中,大多会使用EventTime来进行数据计算。
开篇描述的问题和本篇要介绍的Watermark所涉及的时间类型均是指EventTime类型。
## 什么是Watermark
Watermark是Apache Flink为了处理EventTime 窗口计算提出的一种机制,本质上也是一种时间戳,由Apache Flink Source或者自定义的Watermark生成器按照需求Punctuated或者Periodic两种方式生成的一种系统Event,与普通数据流Event一样流转到对应的下游算子,接收到Watermark Event的算子以此不断调整自己管理的EventTime clock。 Apache Flink 框架保证Watermark单调递增,算子接收到一个Watermark时候,框架知道不会再有任何小于该Watermark的时间戳的数据元素到来了,所以Watermark可以看做是告诉Apache Flink框架数据流已经处理到什么位置(时间维度)的方式。 Watermark的产生和Apache Flink内部处理逻辑如下图所示:

## Watermark的产生方式
目前Apache Flink 有两种生产Watermark的方式,如下:
* Punctuated - 数据流中每一个递增的EventTime都会产生一个Watermark。
在实际的生产中Punctuated方式在TPS很高的场景下会产生大量的Watermark在一定程度上对下游算子造成压力,所以只有在实时性要求非常高的场景才会选择Punctuated的方式进行Watermark的生成。
* Periodic - 周期性的(一定时间间隔或者达到一定的记录条数)产生一个Watermark。在实际的生产中Periodic的方式必须结合时间和积累条数两个维度继续周期性产生Watermark,否则在极端情况下会有很大的延时。
所以Watermark的生成方式需要根据业务场景的不同进行不同的选择。
## Watermark的接口定义
对应Apache Flink Watermark两种不同的生成方式,我们了解一下对应的接口定义,如下:
* Periodic Watermarks - AssignerWithPeriodicWatermarks
```
/**
* Returns the current watermark. This method is periodically called by the
* system to retrieve the current watermark. The method may return {@code null} to
* indicate that no new Watermark is available.
*
* <p>The returned watermark will be emitted only if it is non-null and itsTimestamp
* is larger than that of the previously emitted watermark (to preserve the contract of
* ascending watermarks). If the current watermark is still
* identical to the previous one, no progress in EventTime has happened since
* the previous call to this method. If a null value is returned, or theTimestamp
* of the returned watermark is smaller than that of the last emitted one, then no
* new watermark will be generated.
*
* <p>The interval in which this method is called and Watermarks are generated
* depends on {@link ExecutionConfig#getAutoWatermarkInterval()}.
*
* @see org.Apache.flink.streaming.api.watermark.Watermark
* @see ExecutionConfig#getAutoWatermarkInterval()
*
* @return {@code Null}, if no watermark should be emitted, or the next watermark to emit.
*/
@Nullable
Watermark getCurrentWatermark();
```
* Punctuated Watermarks - AssignerWithPunctuatedWatermarks
```
public interface AssignerWithPunctuatedWatermarks<T> extendsTimestampAssigner<T> {
/**
* Asks this implementation if it wants to emit a watermark. This method is called right after
* the {@link #extractTimestamp(Object, long)} method.
*
* <p>The returned watermark will be emitted only if it is non-null and itsTimestamp
* is larger than that of the previously emitted watermark (to preserve the contract of
* ascending watermarks). If a null value is returned, or theTimestamp of the returned
* watermark is smaller than that of the last emitted one, then no new watermark will
* be generated.
*
* <p>For an example how to use this method, see the documentation of
* {@link AssignerWithPunctuatedWatermarks this class}.
*
* @return {@code Null}, if no watermark should be emitted, or the next watermark to emit.
*/
@Nullable
Watermark checkAndGetNextWatermark(T lastElement, long extractedTimestamp);
}
```
AssignerWithPunctuatedWatermarks 继承了TimestampAssigner接口 -TimestampAssigner
```
public interfaceTimestampAssigner<T> extends Function {
/**
* Assigns aTimestamp to an element, in milliseconds since the Epoch.
*
* <p>The method is passed the previously assignedTimestamp of the element.
* That previousTimestamp may have been assigned from a previous assigner,
* by ingestionTime. If the element did not carry aTimestamp before, this value is
* {@code Long.MIN_VALUE}.
*
* @param element The element that theTimestamp is wil be assigned to.
* @param previousElementTimestamp The previous internalTimestamp of the element,
* or a negative value, if noTimestamp has been assigned, yet.
* @return The newTimestamp.
*/
long extractTimestamp(T element, long previousElementTimestamp);
}
```
从接口定义可以看出,Watermark可以在Event(Element)中提取EventTime,进而定义一定的计算逻辑产生Watermark的时间戳。
## Watermark解决如上问题
从上面的Watermark生成接口和Apache Flink内部对Periodic Watermark的实现来看,Watermark的时间戳可以和Event中的EventTime 一致,也可以自己定义任何合理的逻辑使得Watermark的时间戳不等于Event中的EventTime,Event中的EventTime自产生那一刻起就不可以改变了,不受Apache Flink框架控制,而Watermark的产生是在Apache Flink的Source节点或实现的Watermark生成器计算产生(如上Apache Flink内置的 Periodic Watermark实现), Apache Flink内部对单流或多流的场景有统一的Watermark处理。
回过头来我们在看看Watermark机制如何解决上面的问题,上面的问题在于如何将迟来的EventTime 位11的元素正确处理。要解决这个问题我们还需要先了解一下EventTime window是如何触发的? EventTime window 计算条件是当Window计算的Timer时间戳 小于等于 当前系统的Watermak的时间戳时候进行计算。
* 当Watermark的时间戳等于Event中携带的EventTime时候,上面场景(Watermark=EventTime)的计算结果如下:

上面对应的DDL(Alibaba 企业版的Flink分支)定义如下:
```
CREATE TABLE source(
...,
Event_timeTimeStamp,
WATERMARK wk1 FOR Event_time as withOffset(Event_time, 0)
) with (
...
);
```
* 如果想正确处理迟来的数据可以定义Watermark生成策略为 Watermark = EventTime -5s, 如下:

上面对应的DDL(Alibaba 内部的DDL语法,目前正在和社区讨论)定义如下:
```
CREATE TABLE source(
...,
Event_timeTimeStamp,
WATERMARK wk1 FOR Event_time as withOffset(Event_time, 5000)
) with (
...
);
```
上面正确处理的根源是我们采取了 延迟触发 window 计算 的方式正确处理了 Late Event. 与此同时,我们发现window的延时触发计算,也导致了下游的LATENCY变大,本例子中下游得到window的结果就延迟了5s.
## 多流的Watermark处理
在实际的流计算中往往一个job中会处理多个Source的数据,对Source的数据进行GroupBy分组,那么来自不同Source的相同key值会shuffle到同一个处理节点,并携带各自的Watermark,Apache Flink内部要保证Watermark要保持单调递增,多个Source的Watermark汇聚到一起时候可能不是单调自增的,这样的情况Apache Flink内部是如何处理的呢?如下图所示:

Apache Flink内部实现每一个边上只能有一个递增的Watermark, 当出现多流携带Eventtime汇聚到一起(GroupBy or Union)时候,Apache Flink会选择所有流入的Eventtime中最小的一个向下游流出。从而保证watermark的单调递增和保证数据的完整性.如下图:

本节以一个流计算常见的乱序问题介绍了Apache Flink如何利用Watermark机制来处理乱序问题. 本篇内容在一定程度上也体现了EventTime Window中的Trigger机制依赖了Watermark(后续Window篇章会介绍)。Watermark机制是流计算中处理乱序,正确处理Late Event的核心手段。
================================================
FILE: Flink/12-Broadcast广播变量.md
================================================
## 广播变量简介
在Flink中,同一个算子可能存在若干个不同的并行实例,计算过程可能不在同一个Slot中进行,不同算子之间更是如此,因此不同算子的计算数据之间不能像Java数组之间一样互相访问,而广播变量Broadcast便是解决这种情况的。
我们可以把广播变量理解为是一个公共的共享变量,我们可以把一个dataset 数据集广播出去,然后不同的task在节点上都能够获取到,这个数据在每个节点上只会存在一份
## 用法
```
1:初始化数据
DataSet<Integer> num = env.fromElements(1, 2, 3)
2:广播数据
.withBroadcastSet(toBroadcast, "num");
3:获取数据
Collection<Integer> broadcastSet = getRuntimeContext().getBroadcastVariable("num");
注意:
1:广播出去的变量存在于每个节点的内存中,所以这个数据集不能太大。因为广播出去的数据,会常驻内存,除非程序执行结束
2:广播变量在初始化广播出去以后不支持修改,这样才能保证每个节点的数据都是一致的。
```
## 注意事项
### 使用广播状态,task 之间不会相互通信
只有广播的一边可以修改广播状态的内容。用户必须保证所有 operator 并发实例上对广播状态的 修改行为都是一致的。或者说,如果不同的并发实例拥有不同的广播状态内容,将导致不一致的结果。
### 广播状态中事件的顺序在各个并发实例中可能不尽相同
广播流的元素保证了将所有元素(最终)都发给下游所有的并发实例,但是元素的到达的顺序可能在并发实例之间并不相同。因此,对广播状态的修改不能依赖于输入数据的顺序。
### 所有operator task都会快照下他们的广播状态
在checkpoint时,所有的 task 都会 checkpoint 下他们的广播状态,随着并发度的增加,checkpoint 的大小也会随之增加
### 广播变量存在内存中
广播出去的变量存在于每个节点的内存中,所以这个数据集不能太大,百兆左右可以接受,Gb不能接受
## 案例
```
public class BroadCastTest {
public static void main(String[] args) throws Exception{
ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
//1.封装一个DataSet
DataSet<Integer> broadcast = env.fromElements(1, 2, 3);
DataSet<String> data = env.fromElements("a", "b");
data.map(new RichMapFunction<String, String>() {
private List list = new ArrayList();
@Override
public void open(Configuration parameters) throws Exception {
// 3. 获取广播的DataSet数据 作为一个Collection
Collection<Integer> broadcastSet = getRuntimeContext().getBroadcastVariable("number");
list.addAll(broadcastSet);
}
@Override
public String map(String value) throws Exception {
return value + ": "+ list;
}
}).withBroadcastSet(broadcast, "number")
// 2. 广播的broadcast
.printToErr();//打印到err方便查看
}
}
```
输出结果:
```
a: [1, 2, 3]
b: [1, 2, 3]
```
================================================
FILE: Flink/13-Flink-Kafka-Connector.md
================================================
## 简介
Flink-kafka-connector用来做什么?
Kafka中的partition机制和Flink的并行度机制结合,实现数据恢复
Kafka可以作为Flink的source和sink
任务失败,通过设置kafka的offset来恢复应用
### kafka简单介绍
关于kafka,我们会有专题文章介绍,这里简单介绍几个必须知道的概念。
**1.生产者(Producer)**
顾名思义,生产者就是生产消息的组件,它的主要工作就是源源不断地生产出消息,然后发送给消息队列。生产者可以向消息队列发送各种类型的消息,如狭义的字符串消息,也可以发送二进制消息。生产者是消息队列的数据源,只有通过生产者持续不断地向消息队列发送消息,消息队列才能不断处理消息。
**2.消费者(Consumer)**
所谓消费者,指的是不断消费(获取)消息的组件,它获取消息的来源就是消息队列(即Kafka本身)。换句话说,生产者不断向消息队列发送消息,而消费者则不断从消息队列中获取消息。
**3.主题(Topic)**
主题是Kafka中一个极为重要的概念。首先,主题是一个逻辑上的概念,它用于从逻辑上来归类与存储消息本身。多个生产者可以向一个Topic发送消息,同时也可以有多个消费者消费一个Topic中的消息。Topic还有分区和副本的概念。Topic与消息这两个概念之间密切相关,Kafka中的每一条消息都归属于某一个Topic,而一个Topic下面可以有任意数量的消息。
### kafka简单操作
启动zk:nohup bin/zookeeper-server-start.sh config/zookeeper.properties &
启动server: nohup bin/kafka-server-start.sh config/server.properties &
创建一个topic:bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test
查看topic:bin/kafka-topics.sh --list --zookeeper localhost:2181
发送数据:bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test
启动一个消费者:bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning
删除topic: bin/kafka-topics.sh --delete --zookeeper localhost:2181 --topic topn
## Flink消费Kafka注意事项
* setStartFromGroupOffsets()【默认消费策略】
默认读取上次保存的offset信息
如果是应用第一次启动,读取不到上次的offset信息,则会根据这个参数auto.offset.reset的值来进行消费数据
* setStartFromEarliest()
从最早的数据开始进行消费,忽略存储的offset信息
* setStartFromLatest()
从最新的数据进行消费,忽略存储的offset信息
* setStartFromSpecificOffsets(Map<KafkaTopicPartition, Long>)
从指定位置进行消费
* 当checkpoint机制开启的时候,KafkaConsumer会定期把kafka的offset信息还有其他operator的状态信息一块保存起来。当job失败重启的时候,Flink会从最近一次的checkpoint中进行恢复数据,重新消费kafka中的数据。
* 为了能够使用支持容错的kafka Consumer,需要开启checkpoint
env.enableCheckpointing(5000); // 每5s checkpoint一次
## 搭建Kafka单机环境
我本地安装了一个kafka_2.11-2.1.0版本的kafka

启动Zookeeper和kafka server:
```
启动zk:nohup bin/zookeeper-server-start.sh config/zookeeper.properties &
启动server: nohup bin/kafka-server-start.sh config/server.properties &
```
创建一个topic:
```
bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test
```

## 实战案例
> 所有代码,我放在了我的公众号,回复**Flink**可以下载
* 海量【**java和大数据的面试题+视频资料**】整理在公众号,关注后可以下载~
* 更多大数据技术欢迎和作者一起探讨~

### Kafka作为Flink Sink
首先pom依赖:
```
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-kafka_2.11</artifactId>
<version>1.7.0</version>
</dependency>
```
向kafka写入数据:
```
public class KafkaProducer {
public static void main(String[] args) throws Exception{
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStreamSource<String> text = env.addSource(new MyNoParalleSource()).setParallelism(1);
Properties properties = new Properties();
properties.setProperty("bootstrap.servers", "localhost:9092");
//new FlinkKafkaProducer("topn",new KeyedSerializationSchemaWrapper(new SimpleStringSchema()),properties,FlinkKafkaProducer.Semantic.EXACTLY_ONCE);
FlinkKafkaProducer<String> producer = new FlinkKafkaProducer("test",new SimpleStringSchema(),properties);
/*
//event-timestamp事件的发生时间
producer.setWriteTimestampToKafka(true);
*/
text.addSink(producer);
env.execute();
}
}//
```
大家这里特别注意,我们实现了一个并行度为1的`MyNoParalleSource`来生产数据,代码如下:
```
//使用并行度为1的source
public class MyNoParalleSource implements SourceFunction<String> {//1
//private long count = 1L;
private boolean isRunning = true;
/**
* 主要的方法
* 启动一个source
* 大部分情况下,都需要在这个run方法中实现一个循环,这样就可以循环产生数据了
*
* @param ctx
* @throws Exception
*/
@Override
public void run(SourceContext<String> ctx) throws Exception {
while(isRunning){
//图书的排行榜
List<String> books = new ArrayList<>();
books.add("Pyhton从入门到放弃");//10
books.add("Java从入门到放弃");//8
books.add("Php从入门到放弃");//5
books.add("C++从入门到放弃");//3
books.add("Scala从入门到放弃");//0-4
int i = new Random().nextInt(5);
ctx.collect(books.get(i));
//每2秒产生一条数据
Thread.sleep(2000);
}
}
//取消一个cancel的时候会调用的方法
@Override
public void cancel() {
isRunning = false;
}
}
```
代码实现了一个发送器,来发送书名<Pyhton从入门到放弃><Java从入门到放弃>等...
然后右键运行我们的程序,控制台输出如下:

开始源源不断的生产数据了。
然后我们用命令去查看一下 kafka `test`这个topic:
```
bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning
```
输出如下:

### Kafka作为Flink Source
直接上代码:
```
public class KafkaConsumer {
public static void main(String[] args) throws Exception{
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
Properties properties = new Properties();
properties.setProperty("bootstrap.servers", "localhost:9092");
FlinkKafkaConsumer<String> consumer = new FlinkKafkaConsumer<>("test", new SimpleStringSchema(), properties);
//从最早开始消费
consumer.setStartFromEarliest();
DataStream<String> stream = env
.addSource(consumer);
stream.print();
//stream.map();
env.execute();
}
}//
```
控制台输出如下:

将我们之前发往kafka的消息全部打印出来了。
================================================
FILE: Flink/14-Flink-Table-&-SQL.md
================================================
## 简介
Apache Flink具有两个关系API - 表API和SQL - 用于统一流和批处理。Table API是Scala和Java的语言集成查询API,允许以非常直观的方式组合来自关系运算符的查询,Table API和SQL接口彼此紧密集成,以及Flink的DataStream和DataSet API。您可以轻松地在基于API构建的所有API和库之间切换。例如,您可以使用CEP库从DataStream中提取模式,然后使用Table API分析模式,或者可以在预处理上运行Gelly图算法之前使用SQL查询扫描,过滤和聚合批处理表数据。
## Flink SQL的编程模型
### 创建一个TableEnvironment
TableEnvironment是Table API和SQL集成的核心概念,它主要负责:
1、在内部目录中注册一个Table
2、注册一个外部目录
3、执行SQL查询
4、注册一个用户自定义函数(标量、表及聚合)
5、将DataStream或者DataSet转换成Table
6、持有ExecutionEnvironment或者StreamExecutionEnvironment的引用
一个Table总是会绑定到一个指定的TableEnvironment中,相同的查询不同的TableEnvironment是无法通过join、union合并在一起。
TableEnvironment有一个在内部通过表名组织起来的表目录,Table API或者SQL查询可以访问注册在目录中的表,并通过名称来引用它们。
### 在目录中注册表
TableEnvironment允许通过各种源来注册一个表:
1、一个已存在的Table对象,通常是Table API或者SQL查询的结果
Table projTable = tableEnv.scan("X").select(...);
2、TableSource,可以访问外部数据如文件、数据库或者消息系统
TableSource csvSource = new CsvTableSource("/path/to/file", ...);
3、DataStream或者DataSet程序中的DataStream或者DataSet
//将DataSet转换为Table
Table table= tableEnv.fromDataSet(tableset);
### 注册TableSink
注册TableSink可用于将 Table API或SQL查询的结果发送到外部存储系统,例如数据库,键值存储,消息队列或文件系统(在不同的编码中,例如,CSV,Apache [Parquet] ,Avro,ORC],......):
```
TableSink csvSink = new CsvTableSink("/path/to/file", ...);
```
```
2、 String[] fieldNames = {"a", "b", "c"};
TypeInformation[] fieldTypes = {Types.INT, Types.STRING, Types.LONG};
tableEnv.registerTableSink("CsvSinkTable", fieldNames, fieldTypes, csvSink);
```
## 实战案例一
基于Flink SQL的WordCount:
```
public class WordCountSQL {
public static void main(String[] args) throws Exception{
ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
BatchTableEnvironment tEnv = TableEnvironment.getTableEnvironment(env);
List list = new ArrayList();
String wordsStr = "Hello Flink Hello TOM";
String[] words = wordsStr.split("\\W+");
for(String word : words){
WC wc = new WC(word, 1);
list.add(wc);
}
DataSet<WC> input = env.fromCollection(list);
tEnv.registerDataSet("WordCount", input, "word, frequency");
Table table = tEnv.sqlQuery(
"SELECT word, SUM(frequency) as frequency FROM WordCount GROUP BY word");
DataSet<WC> result = tEnv.toDataSet(table, WC.class);
result.print();
}//main
public static class WC {
public String word;//hello
public long frequency;//1
// public constructor to make it a Flink POJO
public WC() {}
public WC(String word, long frequency) {
this.word = word;
this.frequency = frequency;
}
@Override
public String toString() {
return "WC " + word + " " + frequency;
}
}
}
```
输出如下:
```
WC TOM 1
WC Hello 2
WC Flink 1
```
## 实战案例二
本例稍微复杂,首先读取一个文件中的内容进行统计,并写入到另外一个文件中:
```
public class SQLTest {
public static void main(String[] args) throws Exception{
ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
BatchTableEnvironment tableEnv = BatchTableEnvironment.getTableEnvironment(env);
env.setParallelism(1);
DataSource<String> input = env.readTextFile("test.txt");
input.print();
//转换成dataset
DataSet<Orders> topInput = input.map(new MapFunction<String, Orders>() {
@Override
public Orders map(String s) throws Exception {
String[] splits = s.split(" ");
return new Orders(Integer.valueOf(splits[0]), String.valueOf(splits[1]),String.valueOf(splits[2]), Double.valueOf(splits[3]));
}
});
//将DataSet转换为Table
Table order = tableEnv.fromDataSet(topInput);
//orders表名
tableEnv.registerTable("Orders",order);
Table tapiResult = tableEnv.scan("Orders").select("name");
tapiResult.printSchema();
Table sqlQuery = tableEnv.sqlQuery("select name, sum(price) as total from Orders group by name order by total desc");
//转换回dataset
DataSet<Result> result = tableEnv.toDataSet(sqlQuery, Result.class);
//将dataset map成tuple输出
/*result.map(new MapFunction<Result, Tuple2<String,Double>>() {
@Override
public Tuple2<String, Double> map(Result result) throws Exception {
String name = result.name;
Double total = result.total;
return Tuple2.of(name,total);
}
}).print();*/
TableSink sink = new CsvTableSink("SQLTEST.txt", "|");
//writeToSink
/*sqlQuery.writeToSink(sink);
env.execute();*/
String[] fieldNames = {"name", "total"};
TypeInformation[] fieldTypes = {Types.STRING, Types.DOUBLE};
tableEnv.registerTableSink("SQLTEST", fieldNames, fieldTypes, sink);
sqlQuery.insertInto("SQLTEST");
env.execute();
}
/**
* 源数据的映射类
*/
public static class Orders {
/**
* 序号,姓名,书名,价格
*/
public Integer id;
public String name;
public String book;
public Double price;
public Orders() {
super();
}
public Orders(Integer id, String name, String book, Double price) {
this.id = id;
this.name = name;
this.book = book;
this.price = price;
}
}
/**
* 统计结果对应的类
*/
public static class Result {
public String name;
public Double total;
public Result() {}
}
}//
```
以上所有代码,大家在公众号回复`Flink`即可下载,可以直接本地运行,方便大家调试
================================================
FILE: Flink/15-Flink实战项目之实时热销排行.md
================================================
## 需求
某个图书网站,希望看到双十一秒杀期间实时的热销排行榜单。我们可以将“实时热门商品”翻译成程序员更好理解的需求:每隔5秒钟输出最近一小时内点击量最多的前 N 个商品/图书.
## 需求分解
将这个需求进行分解我们大概要做这么几件事情:
* 告诉 Flink 框架基于时间做窗口,我们这里用processingTime,不用自带时间戳
* 过滤出图书点击行为数据
* 按一小时的窗口大小,每5秒钟统计一次,做滑动窗口聚合(Sliding Window)
* 聚合,输出窗口中点击量前N名的商品
## 代码实现
### 向Kafka发消息模拟购买事件
```
public class KafkaProducer {
public static void main(String[] args) throws Exception{
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStreamSource<String> text = env.addSource(new MyNoParalleSource()).setParallelism(1);
Properties properties = new Properties();
properties.setProperty("bootstrap.servers", "127.0.0.1:9092");
//new FlinkKafkaProducer("topn",new KeyedSerializationSchemaWrapper(new SimpleStringSchema()),properties,FlinkKafkaProducer.Semantic.EXACTLY_ONCE);
FlinkKafkaProducer<String> producer = new FlinkKafkaProducer("topn",new SimpleStringSchema(),properties);
/*
//event-timestamp事件的发生时间
producer.setWriteTimestampToKafka(true);
*/
text.addSink(producer);
env.execute();
}
}//
```
其中的:`MyNoParalleSource` 是作者自己实现的一个并行度为1的发送器,用来向kafka发送数据:
```
public class MyNoParalleSource implements SourceFunction<String> {//1
//private long count = 1L;
private boolean isRunning = true;
/**
* 主要的方法
* 启动一个source
* 大部分情况下,都需要在这个run方法中实现一个循环,这样就可以循环产生数据了
*
* @param ctx
* @throws Exception
*/
@Override
public void run(SourceContext<String> ctx) throws Exception {
while(isRunning){
//图书的排行榜
List<String> books = new ArrayList<>();
books.add("Pyhton从入门到放弃");//10
books.add("Java从入门到放弃");//8
books.add("Php从入门到放弃");//5
books.add("C++从入门到放弃");//3
books.add("Scala从入门到放弃");//0-4
int i = new Random().nextInt(5);
ctx.collect(books.get(i));
//每1秒产生一条数据
Thread.sleep(1000);
}
}
//取消一个cancel的时候会调用的方法
@Override
public void cancel() {
isRunning = false;
}
}
```
可见,我们每过1秒向Kafka的topn这个topic随机发送一本书的名字用来模拟购买行为。
整体实现代码如下:
```
public class TopN {
public static void main(String[] args) throws Exception{
/**
*
* 书1 书2 书3
* (书1,1) (书2,1) (书3,1)
*
*
*/
//每隔5秒钟 计算过去1小时 的 Top 3 商品
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime); //以processtime作为时间语义
Properties properties = new Properties();
properties.setProperty("bootstrap.servers", "127.0.0.1:9092");
FlinkKafkaConsumer<String> input = new FlinkKafkaConsumer<>("topn", new SimpleStringSchema(), properties);
//从最早开始消费 位点
input.setStartFromEarliest();
DataStream<String> stream = env
.addSource(input);
DataStream<Tuple2<String, Integer>> ds = stream
.flatMap(new LineSplitter()); //将输入语句split成一个一个单词并初始化count值为1的Tuple2<String, Integer>类型
DataStream<Tuple2<String, Integer>> wcount = ds
.keyBy(0)
.window(SlidingProcessingTimeWindows.of(Time.seconds(600),Time.seconds(5)))
//key之后的元素进入一个总时间长度为600s,每5s向后滑动一次的滑动窗口
.sum(1);// 将相同的key的元素第二个count值相加
wcount
.windowAll(TumblingProcessingTimeWindows.of(Time.seconds(5)))//(shu1, xx) (shu2,xx)....
//所有key元素进入一个5s长的窗口(选5秒是因为上游窗口每5s计算一轮数据,topN窗口一次计算只统计一个窗口时间内的变化)
.process(new TopNAllFunction(3))
.print();
//redis sink redis -> 接口
env.execute();
}//
private static final class LineSplitter implements
FlatMapFunction<String, Tuple2<String, Integer>> {
public void flatMap(String value, Collector<Tuple2<String, Integer>> out) {
// normalize and split the line
//String[] tokens = value.toLowerCase().split("\\W+");
// emit the pairs
/*for (String token : tokens) {
if (token.length() > 0) {
out.collect(new Tuple2<String, Integer>(token, 1));
}
}*/
//(书1,1) (书2,1) (书3,1)
out.collect(new Tuple2<String, Integer>(value, 1));
}
}
private static class TopNAllFunction
extends
ProcessAllWindowFunction<Tuple2<String, Integer>, String, TimeWindow> {
private int topSize = 3;
public TopNAllFunction(int topSize) {
this.topSize = topSize;
}
public void process(
ProcessAllWindowFunction<Tuple2<String, Integer>, String, TimeWindow>.Context arg0,
Iterable<Tuple2<String, Integer>> input,
Collector<String> out) throws Exception {
TreeMap<Integer, Tuple2<String, Integer>> treemap = new TreeMap<Integer, Tuple2<String, Integer>>(
new Comparator<Integer>() {
@Override
public int compare(Integer y, Integer x) {
return (x < y) ? -1 : 1;
}
}); //treemap按照key降序排列,相同count值不覆盖
for (Tuple2<String, Integer> element : input) {
treemap.put(element.f1, element);
if (treemap.size() > topSize) { //只保留前面TopN个元素
treemap.pollLastEntry();
}
}
for (Map.Entry<Integer, Tuple2<String, Integer>> entry : treemap
.entrySet()) {
out.collect("=================\n热销图书列表:\n"+ new Timestamp(System.currentTimeMillis()) + treemap.toString() + "\n===============\n");
}
}
}
}//
```
查看输出:
```
=================
热销图书列表:
2019-03-05 22:32:40.004{8=(Java从入门到放弃,8), 7=(C++从入门到放弃,7), 5=(Php从入门到放弃,5)}
===============
=================
热销图书列表:
2019-03-05 22:32:45.004{8=(Java从入门到放弃,8), 7=(C++从入门到放弃,7), 5=(Php从入门到放弃,5)}
===============
```
================================================
FILE: Flink/16-Flink-Redis-Sink.md
================================================
## 简介
流式计算中,我们经常有一些场景是消费Kafka数据,进行处理,然后存储到其他的数据库或者缓存或者重新发送回其他的消息队列中。
本文讲述一个简单的Redis作为Sink的案例。
后续,我们会补充完善,比如落入Hbase,Kafka,Mysql等。
## 关于Redis Sink
Flink提供了封装好的写入Redis的包给我们用,首先我们要新增一个依赖:
```
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-redis_2.10</artifactId>
<version>1.1.5</version>
</dependency>
```
然后我们实现一个自己的RedisSinkExample:
```
//指定Redis set
public static final class RedisSinkExample implements RedisMapper<Tuple2<String,Integer>> {
public RedisCommandDescription getCommandDescription() {
return new RedisCommandDescription(RedisCommand.SET, null);
}
public String getKeyFromData(Tuple2<String, Integer> data) {
return data.f0;
}
public String getValueFromData(Tuple2<String, Integer> data) {
return data.f1.toString();
}
}
```
我们用最简单的单机Redis的SET命令进行演示。
完整的代码如下,实现一个读取Kafka的消息,然后进行WordCount,并把结果更新到redis中:
```
public class RedisSinkTest {
public static void main(String[] args) throws Exception{
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
env.enableCheckpointing(2000);
env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
//连接kafka
Properties properties = new Properties();
properties.setProperty("bootstrap.servers", "127.0.0.1:9092");
FlinkKafkaConsumer<String> consumer = new FlinkKafkaConsumer<>("test", new SimpleStringSchema(), properties);
consumer.setStartFromEarliest();
DataStream<String> stream = env.addSource(consumer);
DataStream<Tuple2<String, Integer>> counts = stream.flatMap(new LineSplitter()).keyBy(0).sum(1);
//实例化FlinkJedisPoolConfig 配置redis
FlinkJedisPoolConfig conf = new FlinkJedisPoolConfig.Builder().setHost("127.0.0.1").setPort("6379").build();
//实例化RedisSink,并通过flink的addSink的方式将flink计算的结果插入到redis
counts.addSink(new RedisSink<>(conf,new RedisSinkExample()));
env.execute("WordCount From Kafka To Redis");
}//
public static final class LineSplitter implements FlatMapFunction<String, Tuple2<String, Integer>> {
@Override
public void flatMap(String value, Collector<Tuple2<String, Integer>> out) {
String[] tokens = value.toLowerCase().split("\\W+");
for (String token : tokens) {
if (token.length() > 0) {
out.collect(new Tuple2<String, Integer>(token, 1));
}
}
}
}
//指定Redis set
public static final class RedisSinkExample implements RedisMapper<Tuple2<String,Integer>> {
public RedisCommandDescription getCommandDescription() {
return new RedisCommandDescription(RedisCommand.SET, null);
}
public String getKeyFromData(Tuple2<String, Integer> data) {
return data.f0;
}
public String getValueFromData(Tuple2<String, Integer> data) {
return data.f1.toString();
}
}
}//
```
预告,后续更新写入Hbase和Mysql案例代码。
================================================
FILE: Flink/17-Flink消费Kafka写入Mysql.md
================================================
本文介绍消费Kafka的消息实时写入Mysql
1. maven新增依赖:
```
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.39</version>
</dependency>
```
2.重写RichSinkFunction,实现一个Mysql Sink
```
public class MysqlSink extends
RichSinkFunction<Tuple3<Integer, String, Integer>> {
private Connection connection;
private PreparedStatement preparedStatement;
String username = "";
String password = "";
String drivername = ""; //配置改成自己的配置
String dburl = "";
@Override
public void invoke(Tuple3<Integer, String, Integer> value) throws Exception {
Class.forName(drivername);
connection = DriverManager.getConnection(dburl, username, password);
String sql = "replace into table(id,num,price) values(?,?,?)"; //假设mysql 有3列 id,num,price
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, value.f0);
preparedStatement.setString(2, value.f1);
preparedStatement.setInt(3, value.f2);
preparedStatement.executeUpdate();
if (preparedStatement != null) {
preparedStatement.close();
}
if (connection != null) {
connection.close();
}
}
}
```
3. Flink主类
```
public class MysqlSinkTest {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
Properties properties = new Properties();
properties.setProperty("bootstrap.servers", "localhost:9092");
// 1,abc,100 类似这样的数据,当然也可以是很复杂的json数据,去做解析
FlinkKafkaConsumer<String> consumer = new FlinkKafkaConsumer<>("test", new SimpleStringSchema(), properties);
env.getConfig().disableSysoutLogging(); //设置此可以屏蔽掉日记打印情况
env.getConfig().setRestartStrategy(
RestartStrategies.fixedDelayRestart(5, 5000));
env.enableCheckpointing(2000);
DataStream<String> stream = env
.addSource(consumer);
DataStream<Tuple3<Integer, String, Integer>> sourceStream = stream.filter((FilterFunction<String>) value -> StringUtils.isNotBlank(value))
.map((MapFunction<String, Tuple3<Integer, String, Integer>>) value -> {
String[] args1 = value.split(",");
return new Tuple3<Integer, String, Integer>(Integer
.valueOf(args1[0]), args1[1],Integer
.valueOf(args1[2]));
});
sourceStream.addSink(new MysqlSink());
env.execute("data to mysql start");
}
}
```
================================================
FILE: Flink/6-Flink重启策略.md
================================================
## 概述
* Flink支持不同的重启策略,以在故障发生时控制作业如何重启
* 集群在启动时会伴随一个默认的重启策略,在没有定义具体重启策略时会使用该默认策略。
* 如果在工作提交时指定了一个重启策略,该策略会覆盖集群的默认策略默认的重启策略可以通过 Flink 的配置文件 flink-conf.yaml 指定。配置参数 restart-strategy 定义了哪个策略被使用。
* 常用的重启:
1.策略固定间隔 (Fixed delay)
2.失败率 (Failure rate)
3.无重启 (No restart)
* 如果没有启用 checkpointing,则使用无重启 (no restart) 策略。如果启用了 checkpointing,但没有配置重启策略,则使用固定间隔 (fixed-delay) 策略
* 重启策略可以在flink-conf.yaml中配置,表示全局的配置。也可以在应用代码中动态指定,会覆盖全局配置
## 固定间隔
第一种:全局配置 flink-conf.yaml
```
restart-strategy: fixed-delay
restart-strategy.fixed-delay.attempts: 3
restart-strategy.fixed-delay.delay: 10 s
```
第二种:应用代码设置:
```
env.setRestartStrategy(RestartStrategies.fixedDelayRestart( 3,// 尝试重启的次数
Time.of(10, TimeUnit.SECONDS) // 间隔 ));
```
## 失败率
* 失败率重启策略在Job失败后会重启,但是超过失败率后,Job会最终被认定失败。在两个连续的重启尝试之间,重启策略会等待一个固定的时间
**下面配置是5分钟内若失败了3次则认为该job失败,重试间隔为10s**
第一种:全局配置 flink-conf.yaml
```
restart-strategy: failure-rate
restart-strategy.failure-rate.max-failures-per-interval: 3
restart-strategy.failure-rate.failure-rate-interval: 5 min
restart-strategy.failure-rate.delay: 10 s
```
第二种:应用代码设置
```
env.setRestartStrategy(RestartStrategies.failureRateRestart( 3,//一个时间段内的最大失败次数
Time.of(5, TimeUnit.MINUTES), // 衡量失败次数的是时间段 Time.of(10, TimeUnit.SECONDS) // 间隔 ));
```
## 无重启策略
第一种:全局配置 flink-conf.yaml
```
restart-strategy: none
```
第二种:应用代码设置
```
ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); env.setRestartStrategy(RestartStrategies.noRestart());
```
## 实际代码演示
```
public class RestartTest {
public static void main(String[] args) {
//获取flink的运行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 每隔1000 ms进行启动一个检查点【设置checkpoint的周期】
env.enableCheckpointing(1000);
// 间隔10秒 重启3次
env.setRestartStrategy(RestartStrategies.fixedDelayRestart(3,Time.seconds(10)));
//5分钟内若失败了3次则认为该job失败,重试间隔为10s
env.setRestartStrategy(RestartStrategies.failureRateRestart(3,Time.of(5,TimeUnit.MINUTES),Time.of(10,TimeUnit.SECONDS)));
//不重试
env.setRestartStrategy(RestartStrategies.noRestart());
}//
}
```
================================================
FILE: Flink/7-Flink的分布式缓存.md
================================================
## 分布式缓存
Flink提供了一个分布式缓存,类似于hadoop,可以使用户在并行函数中很方便的读取本地文件,并把它放在taskmanager节点中,防止task重复拉取。
此缓存的工作机制如下:程序注册一个文件或者目录(本地或者远程文件系统,例如hdfs或者s3),通过ExecutionEnvironment注册缓存文件并为它起一个名称。
当程序执行,Flink自动将文件或者目录复制到所有taskmanager节点的本地文件系统,仅会执行一次。用户可以通过这个指定的名称查找文件或者目录,然后从taskmanager节点的本地文件系统访问它。
## 示例
在ExecutionEnvironment中注册一个文件:
```
//获取运行环境
ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
//1:注册一个文件,可以使用hdfs上的文件 也可以是本地文件进行测试
env.registerCachedFile("/Users/wangzhiwu/WorkSpace/quickstart/text","a.txt");
```
在用户函数中访问缓存文件或者目录(这里是一个map函数)。这个函数必须继承RichFunction,因为它需要使用RuntimeContext读取数据:
```
DataSet<String> result = data.map(new RichMapFunction<String, String>() {
private ArrayList<String> dataList = new ArrayList<String>();
@Override
public void open(Configuration parameters) throws Exception {
super.open(parameters);
//2:使用文件
File myFile = getRuntimeContext().getDistributedCache().getFile("a.txt");
List<String> lines = FileUtils.readLines(myFile);
for (String line : lines) {
this.dataList.add(line);
System.err.println("分布式缓存为:" + line);
}
}
@Override
public String map(String value) throws Exception {
//在这里就可以使用dataList
System.err.println("使用datalist:" + dataList + "------------" +value);
//业务逻辑
return dataList +":" + value;
}
});
result.printToErr();
}
```
完整代码如下,仔细看注释:
```
public class DisCacheTest {
public static void main(String[] args) throws Exception{
//获取运行环境
ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
//1:注册一个文件,可以使用hdfs上的文件 也可以是本地文件进行测试
//text 中有4个单词:hello flink hello FLINK env.registerCachedFile("/Users/wangzhiwu/WorkSpace/quickstart/text","a.txt");
DataSource<String> data = env.fromElements("a", "b", "c", "d");
DataSet<String> result = data.map(new RichMapFunction<String, String>() {
private ArrayList<String> dataList = new ArrayList<String>();
@Override
public void open(Configuration parameters) throws Exception {
super.open(parameters);
//2:使用文件
File myFile = getRuntimeContext().getDistributedCache().getFile("a.txt");
List<String> lines = FileUtils.readLines(myFile);
for (String line : lines) {
this.dataList.add(line);
System.err.println("分布式缓存为:" + line);
}
}
@Override
public String map(String value) throws Exception {
//在这里就可以使用dataList
System.err.println("使用datalist:" + dataList + "------------" +value);
//业务逻辑
return dataList +":" + value;
}
});
result.printToErr();
}
}//
```
输出结果如下:
```
[hello, flink, hello, FLINK]:a
[hello, flink, hello, FLINK]:b
[hello, flink, hello, FLINK]:c
[hello, flink, hello, FLINK]:d
```
================================================
FILE: Flink/8-Flink中的窗口.md
================================================
## 窗口
### 窗口类型
1. flink支持两种划分窗口的方式(time和count) 如果根据时间划分窗口,那么它就是一个time-window 如果根据数据划分窗口,那么它就是一个count-window
2. flink支持窗口的两个重要属性(size和interval)
* 如果size=interval,那么就会形成tumbling-window(无重叠数据)
* 如果size>interval,那么就会形成sliding-window(有重叠数据)
* 如果size<interval,那么这种窗口将会丢失数据。比如每5秒钟,统计过去3秒的通过路口汽车的数据,将会漏掉2秒钟的数据。
3. 通过组合可以得出四种基本窗口:
* `time-tumbling-window` 无重叠数据的时间窗口,设置方式举例:timeWindow(Time.seconds(5))
* `time-sliding-window` 有重叠数据的时间窗口,设置方式举例:timeWindow(Time.seconds(5), Time.seconds(3))
* `count-tumbling-window`无重叠数据的数量窗口,设置方式举例:countWindow(5)
* `count-sliding-window` 有重叠数据的数量窗口,设置方式举例:countWindow(5,3)
4. flink支持在stream上的通过key去区分多个窗口
### 窗口的实现方式
上一张经典图:

* Tumbling Time Window
假如我们需要统计每一分钟中用户购买的商品的总数,需要将用户的行为事件按每一分钟进行切分,这种切分被成为翻滚时间窗口(Tumbling Time Window)。翻滚窗口能将数据流切分成不重叠的窗口,每一个事件只能属于一个窗口。
```
// 用户id和购买数量 stream
val counts: DataStream[(Int, Int)] = ...
val tumblingCnts: DataStream[(Int, Int)] = counts
// 用userId分组
.keyBy(0)
// 1分钟的翻滚窗口宽度
.timeWindow(Time.minutes(1))
// 计算购买数量
.sum(1)
```
* Sliding Time Window
我们可以每30秒计算一次最近一分钟用户购买的商品总数。这种窗口我们称为滑动时间窗口(Sliding Time Window)。在滑窗中,一个元素可以对应多个窗口。通过使用 DataStream API,我们可以这样实现:
```
val slidingCnts: DataStream[(Int, Int)] = buyCnts
.keyBy(0)
.timeWindow(Time.minutes(1), Time.seconds(30))
.sum(1)
```
* Tumbling Count Window
当我们想要每100个用户购买行为事件统计购买总数,那么每当窗口中填满100个元素了,就会对窗口进行计算,这种窗口我们称之为翻滚计数窗口(Tumbling Count Window),上图所示窗口大小为3个。通过使用 DataStream API,我们可以这样实现:
```
// Stream of (userId, buyCnts)
val buyCnts: DataStream[(Int, Int)] = ...
val tumblingCnts: DataStream[(Int, Int)] = buyCnts
// key stream by sensorId
.keyBy(0)
// tumbling count window of 100 elements size
.countWindow(100)
// compute the buyCnt sum
.sum(1)
```
* Session Window
在这种用户交互事件流中,我们首先想到的是将事件聚合到会话窗口中(一段用户持续活跃的周期),由非活跃的间隙分隔开。如上图所示,就是需要计算每个用户在活跃期间总共购买的商品数量,如果用户30秒没有活动则视为会话断开(假设raw data stream是单个用户的购买行为流)。Session Window 的示例代码如下:
```
// Stream of (userId, buyCnts)
val buyCnts: DataStream[(Int, Int)] = ...
val sessionCnts: DataStream[(Int, Int)] = vehicleCnts
.keyBy(0)
// session window based on a 30 seconds session gap interval
.window(ProcessingTimeSessionWindows.withGap(Time.seconds(30)))
.sum(1)
```
一般而言,window 是在无限的流上定义了一个有限的元素集合。这个集合可以是基于时间的,元素个数的,时间和个数结合的,会话间隙的,或者是自定义的。Flink 的 DataStream API 提供了简洁的算子来满足常用的窗口操作,同时提供了通用的窗口机制来允许用户自己定义窗口分配逻辑。
================================================
FILE: Flink/9-Flink中的Time.md
================================================
## 时间
### 时间类型
* Flink中的时间与现实世界中的时间是不一致的,在flink中被划分为**事件时间,摄入时间,处理时间**三种。
* 如果以EventTime为基准来定义时间窗口将形成EventTimeWindow,要求消息本身就应该携带EventTime
* 如果以IngesingtTime为基准来定义时间窗口将形成IngestingTimeWindow,以source的systemTime为准。
* 如果以ProcessingTime基准来定义时间窗口将形成ProcessingTimeWindow,以operator的systemTime为准。

### 时间详解
**Processing Time**
Processing Time 是指事件被处理时机器的系统时间。
当流程序在 Processing Time 上运行时,所有基于时间的操作(如时间窗口)将使用当时机器的系统时间。每小时 Processing Time 窗口将包括在系统时钟指示整个小时之间到达特定操作的所有事件。
例如,如果应用程序在上午 9:15 开始运行,则第一个每小时 Processing Time 窗口将包括在上午 9:15 到上午 10:00 之间处理的事件,下一个窗口将包括在上午 10:00 到 11:00 之间处理的事件。
Processing Time 是最简单的 “Time” 概念,不需要流和机器之间的协调,它提供了最好的性能和最低的延迟。但是,在分布式和异步的环境下,Processing Time 不能提供确定性,因为它容易受到事件到达系统的速度(例如从消息队列)、事件在系统内操作流动的速度以及中断的影响。
**Event Time**
Event Time 是事件发生的时间,一般就是数据本身携带的时间。这个时间通常是在事件到达 Flink 之前就确定的,并且可以从每个事件中获取到事件时间戳。在 Event Time 中,时间取决于数据,而跟其他没什么关系。Event Time 程序必须指定如何生成 Event Time 水印,这是表示 Event Time 进度的机制。
完美的说,无论事件什么时候到达或者其怎么排序,最后处理 Event Time 将产生完全一致和确定的结果。但是,除非事件按照已知顺序(按照事件的时间)到达,否则处理 Event Time 时将会因为要等待一些无序事件而产生一些延迟。由于只能等待一段有限的时间,因此就难以保证处理 Event Time 将产生完全一致和确定的结果。
假设所有数据都已到达, Event Time 操作将按照预期运行,即使在处理无序事件、延迟事件、重新处理历史数据时也会产生正确且一致的结果。 例如,每小时事件时间窗口将包含带有落入该小时的事件时间戳的所有记录,无论它们到达的顺序如何。
请注意,有时当 Event Time 程序实时处理实时数据时,它们将使用一些 Processing Time 操作,以确保它们及时进行。
**Ingestion Time**
Ingestion Time 是事件进入 Flink 的时间。 在源操作处,每个事件将源的当前时间作为时间戳,并且基于时间的操作(如时间窗口)会利用这个时间戳。
Ingestion Time 在概念上位于 Event Time 和 Processing Time 之间。 与 Processing Time 相比,它稍微贵一些,但结果更可预测。因为 Ingestion Time 使用稳定的时间戳(在源处分配一次),所以对事件的不同窗口操作将引用相同的时间戳,而在 Processing Time 中,每个窗口操作符可以将事件分配给不同的窗口(基于机器系统时间和到达延迟)。
与 Event Time 相比,Ingestion Time 程序无法处理任何无序事件或延迟数据,但程序不必指定如何生成水印。
在 Flink 中,Ingestion Time 与 Event Time 非常相似,但 Ingestion Time 具有自动分配时间戳和自动生成水印功能。
================================================
FILE: Flink/Flink从入门到放弃(入门篇1)-Flink是什么?.md
================================================
> 本文是例行介绍,熟悉的直接跳过 - 鲁迅
> 鲁迅: ...
# 大纲
**入门篇:**
-Flink是什么?.resources/1.png)
**放弃篇:**
-Flink是什么?.resources/A44BE2B6-FBC9-4143-9743-F097B9C0FDD6.png)
## Flink是什么
## 一句话概括
Apache Flink是一个面向分布式数据流处理和批量数据处理的开源计算平台,提供支持流处理和批处理两种类型应用的功能。
## 前身
Apache Flink 的前身是柏林理工大学一个研究性项目, 在 2014 被 Apache 孵化器所接受,然后迅速地成为了Apache Software Foundation的顶级项目之一。
## 特点
现有的开源计算方案,会把流处理和批处理作为两种不同的应用类型:流处理一般需要支持低延迟、Exactly-once保证,而批处理需要支持高吞吐、高效处理。
Flink是完全支持流处理,也就是说作为流处理看待时输入数据流是无界的;批处理被作为一种特殊的流处理,只是它的输入数据流被定义为有界的。
## Flink组件栈
-Flink是什么?.resources/6F963775-B91B-447F-959E-38B4029BE56D.png)
### Deployment层
主要涉及了Flink的部署模式,Flink支持多种部署模式:本地、集群(Standalone/YARN)、云(GCE/EC2)
-Flink是什么?.resources/F7406066-68CA-4BE7-9743-7FD65A0D722C.png)
### Runtime层
Runtime层提供了支持Flink计算的全部核心实现,比如:支持分布式Stream处理、JobGraph到ExecutionGraph的映射、调度等等,为上层API层提供基础服务
### API层
API层主要实现了面向无界Stream的流处理和面向Batch的批处理API,其中面向流处理对应DataStream API,面向批处理对应DataSet API
### Libaries层
* 在API层之上构建的满足特定应用的实现计算框架,也分别对应于面向流处理和面向批处理两类
* 面向流处理支持:CEP(复杂事件处理)、基于SQL-like的操作(基于Table的关系操作)
* 面向批处理支持:FlinkML(机器学习库)、Gelly(图处理)
## Flink的优势
* 支持高吞吐、低延迟、高性能的流处理
* 支持高度灵活的窗口(Window)操作
* 支持有状态计算的Exactly-once语义
* 提供DataStream API和DataSet API
-Flink是什么?.resources/3DE5BD22-BFE2-49C4-8DA8-C42EAD1948FB.png)
-Flink是什么?.resources/0E6F6341-5EB0-40FD-9953-70C3F0904043.png)
## Flink基本编程模型
> * Flink程序的基础构建模块是流(streams) 与 转换(transformations)
> * 每一个数据流起始于一个或多个 source,并终止于一个或多个 sink
下面是一个由Flink程序映射为Streaming Dataflow的示意图:
-Flink是什么?.resources/656C0986-42A7-4E76-B3CA-C0372395E451.png)
并行数据流示意图:
-Flink是什么?.resources/E6A4AF88-12D9-413A-A318-06A86ABDC1AF.png)
## Flink基本架构
> * Flink是基于Master-Slave风格的架构
> * Flink集群启动时,会启动一个JobManager进程、至少一个TaskManager进程
-Flink是什么?.resources/866EF50B-A9ED-461A-AC13-78BEBBDCCFC9.png)
### JobManager
* Flink系统的协调者,它负责接收Flink Job,调度组成Job的多个Task的执行
* 收集Job的状态信息,并管理Flink集群中从节点TaskManager
### TaskManager
* 实际负责执行计算的Worker,在其上执行Flink Job的一组Task
* TaskManager负责管理其所在节点上的资源信息,如内存、磁盘、网络,在启动的时候将资源的状态向JobManager汇报
### Client
* 用户提交一个Flink程序时,会首先创建一个Client,该Client首先会对用户提交的Flink程序进行预处理,并提交到Flink集群
* Client会将用户提交的Flink程序组装一个JobGraph, 并且是以JobGraph的形式提交的
## 最后
本文是例行介绍,熟悉的直接跳过。
================================================
FILE: Flink/Flink从入门到放弃(入门篇2)-本地环境搭建&构建第一个Flink应用.md
================================================
## 本地安装单机版本Flink
一般来说,线上都是集群模式,那么单机模式方便我们测试和学习。
### 环境要求
本地机器上需要有 Java 8 和 maven 环境,推荐在linux或者mac上开发Flink应用:
如果有 Java 8 环境,运行下面的命令会输出如下版本信息:
-本地环境搭建&构建第一个Flink应用.resources/34F234C6-C9D6-46AB-A864-652BE177B4CA.png)
如果有 maven 环境,运行下面的命令会输出如下版本信息:
-本地环境搭建&构建第一个Flink应用.resources/1A1D2049-1042-43E1-BE0B-6D9FAA8224BE.png)
开发工具推荐使用 ItelliJ IDEA。
#### 第一种方式
来这里[https://flink.apache.org/](https://flink.apache.org/)
看这里:
-本地环境搭建&构建第一个Flink应用.resources/E0A8FC57-9184-4BE8-8D20-BDD91C3C44FD.png)
>注意:
```
An Apache Hadoop installation is not required to use Apache Flink. For users that use Flink without any Hadoop components, we recommend the release without bundled Hadoop libraries.
```
这是啥意思?
这个意思就是说Flink可以不依赖Hadoop环境,如果说单机玩的话,下载一个`only`版本就行了。
#### 第二种方式(不推荐)
```
git clone https://github.com/apache/flink.git
cd flink
mvn clean package -DskipTests
```
然后进入编译好的Flink中去执行 `bin/start-cluster.sh`
### 其他乱七八糟的安装办法
比如 Mac用户可以用`brew install apache-flink` ,前提是安装过 `brew`这个mac下的工具.
## 启动Flink
我们先到Flink的目录下来:
如下:
```
$ flink-1.7.1 pwd
/Users/wangzhiwu/Downloads/flink-1.7.1
```
-本地环境搭建&构建第一个Flink应用.resources/BE68C066-BD15-4FAF-B649-82D9B26F255D.png)
执行命令:
-本地环境搭建&构建第一个Flink应用.resources/C88AEAF7-42B7-4AD1-A793-3E89EBE751E2.png)
接着就可以进入 web 页面(http://localhost:8081/) 查看
-本地环境搭建&构建第一个Flink应用.resources/DAEECBBB-0FB7-4D4E-B338-B3181C23B6CB.png)
恭喜你,一个单机版的flink就跑起来了。
## 构建一个应用
当然了,我们可以用maven,一顿new,new出来一个过程,这里我们将使用 Flink Maven Archetype 来创建我们的项目结构和一些初始的默认依赖。在你的工作目录下,运行如下命令来创建项目:
```
mvn archetype:generate \
-DarchetypeGroupId=org.apache.flink \
-DarchetypeArtifactId=flink-quickstart-java \
-DarchetypeVersion=1.7.2 \
-DgroupId=flink-project \
-DartifactId=flink-project \
-Dversion=0.1 \
-Dpackage=myflink \
-DinteractiveMode=false
```
这样一个工程就构建好了。
还有一个更加牛逼的办法,看这里:
```
curl https://flink.apache.org/q/quickstart.sh | bash
```
直接在命令行执行上面的命令,结果如下图:
-本地环境搭建&构建第一个Flink应用.resources/A78DC26C-BD00-44A9-9481-FE67B9BAE9CF.png)
同样可以构建一个Flink工程,而且自带一些demo。
原理是什么?点一下它看看就明白了。
[https://flink.apache.org/q/quickstart.sh](https://flink.apache.org/q/quickstart.sh)
## 编写一个入门级的WordCount
```public class WordCount {
//
// Program
//
public static void main(String[] args) throws Exception {
// set up the execution environment
final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
// get input data
DataSet<String> text = env.fromElements(
"To be, or not to be,--that is the question:--",
"Whether 'tis nobler in the mind to suffer",
"The slings and arrows of outrageous fortune",
"Or to take arms against a sea of troubles,"
);
DataSet<Tuple2<String, Integer>> counts =
// split up the lines in pairs (2-tuples) containing: (word,1)
text.flatMap(new LineSplitter())
// group by the tuple field "0" and sum up tuple field "1"
.groupBy(0) //(i,1) (am,1) (chinese,1)
.sum(1);
// execute and print result
counts.print();
}
//
// User Functions
//
/**
* Implements the string tokenizer that splits sentences into words as a user-defined
* FlatMapFunction. The function takes a line (String) and splits it into
* multiple pairs in the form of "(word,1)" (Tuple2<String, Integer>).
*/
public static final class LineSplitter implements FlatMapFunction<String, Tuple2<String, Integer>> {
@Override
public void flatMap(String value, Collector<Tuple2<String, Integer>> out) {
// normalize and split the line
String[] tokens = value.toLowerCase().split("\\W+");
// emit the pairs
for (String token : tokens) {
if (token.length() > 0) {
out.collect(new Tuple2<String, Integer>(token, 1));
}
}
}
}
}
```
类似的例子,官方也有提供的,可以在这里下载:
[WordCount官方推荐](https://github.com/apache/flink/blob/master/flink-examples/flink-examples-batch/src/main/java/org/apache/flink/examples/java/wordcount/WordCount.java)
### 运行
本地右键运行:
-本地环境搭建&构建第一个Flink应用.resources/8F0D8EF2-5C0B-4067-AA87-31D7A0DC16C7.png)
提交到本地单机Flink上
* 进入工程目录,使用以下命令打包
```
mvn clean package -Dmaven.test.skip=true
```
然后,进入 flink 安装目录 bin 下执行以下命令提交程序:
```
flink run -c org.myorg.laowang.WordCount /Users/wangzhiwu/WorkSpace/quickstart/target/quickstart-0.1.jar
```
分别制定main方法和jar包的地址。
在刚才的控制台中,可以看到:
-本地环境搭建&构建第一个Flink应用.resources/EB619900-BBDE-4E32-9089-0DC867FF9220.png)
我们刚才提交过的程序。
flink的log目录下有我们提交过的任务的日志:
-本地环境搭建&构建第一个Flink应用.resources/620369FB-ABCA-4184-AA90-C7FEDB114B07.png)
## 总结
一次简单的flink之旅就完成了。
================================================
FILE: Flink/Flink从入门到放弃(入门篇3)-DataSetAPI.md
================================================
## 编程结构
```
public class SocketTextStreamWordCount {
public static void main(String[] args) throws Exception {
if (args.length != 2){
System.err.println("USAGE:\nSocketTextStreamWordCount <hostname> <port>");
return;
}
String hostName = args[0];
Integer port = Integer.parseInt(args[1]);
final StreamExecutionEnvironment env = StreamExecutionEnvironment
.getExecutionEnvironment();
DataStream<String> text = env.socketTextStream(hostName, port);
DataStream<Tuple2<String, Integer>> counts
text.flatMap(new LineSplitter())
.keyBy(0)
.sum(1);
counts.print();
env.execute("Java WordCount from SocketTextStream Example");
}
```
上面的`SocketTextStreamWordCount`是一个典型的Flink程序,他由一下及格部分构成:
* 获得一个execution environment,
* 加载/创建初始数据,
* 指定此数据的转换,
* 指定放置计算结果的位置,
* 触发程序执行
## DataSet API
分类:
* Source: 数据源创建初始数据集,例如来自文件或Java集合
* Transformation: 数据转换将一个或多个DataSet转换为新的DataSet
* Sink: 将计算结果存储或返回
### DataSet Sources
#### 基于文件的
* `readTextFile(path)/ TextInputFormat`- 按行读取文件并将其作为字符串返回。
* `readTextFileWithValue(path)/ TextValueInputFormat`- 按行读取文件并将它们作为StringValues返回。StringValues是可变字符串。
* `readCsvFile(path)/ CsvInputFormat`- 解析逗号(或其他字符)分隔字段的文件。返回元组或POJO的DataSet。支持基本java类型及其Value对应作为字段类型。
* `readFileOfPrimitives(path, Class)/ PrimitiveInputFormat`- 解析新行(或其他字符序列)分隔的原始数据类型(如String或)的文件Integer。
* `readFileOfPrimitives(path, delimiter, Class)/ PrimitiveInputFormat`- 解析新行(或其他字符序列)分隔的原始数据类型的文件,例如String或Integer使用给定的分隔符。
* `readSequenceFile(Key, Value, path)/ SequenceFileInputFormat`- 创建一个JobConf并从类型为SequenceFileInputFormat,Key class和Value类的指定路径中读取文件,并将它们作为Tuple2 <Key,Value>返回。
#### 基于集合
* `fromCollection(Collection)` - 从Java Java.util.Collection创建数据集。集合中的所有数据元必须属于同一类型。
* `fromCollection(Iterator, Class)` - 从迭代器创建数据集。该类指定迭代器返回的数据元的数据类型。
* `fromElements(T ...)` - 根据给定的对象序列创建数据集。所有对象必须属于同一类型。
* `fromParallelCollection(SplittableIterator, Class) `- 并行地从迭代器创建数据集。该类指定迭代器返回的数据元的数据类型。
* `generateSequence(from, to)` - 并行生成给定间隔中的数字序列。
#### 通用方法
* `readFile(inputFormat, path)/ FileInputFormat`- 接受文件输入格式。
* `createInput(inputFormat)/ InputFormat`- 接受通用输入格式。
#### 代码示例
```
ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
// 从本地文件系统读
DataSet<String> localLines = env.readTextFile("file:///path/to/my/textfile");
// 读取HDFS文件
DataSet<String> hdfsLines = env.readTextFile("hdfs://nnHost:nnPort/path/to/my/textfile");
// 读取CSV文件
DataSet<Tuple3<Integer, String, Double>> csvInput = env.readCsvFile("hdfs:///the/CSV/file").types(Integer.class, String.class, Double.class);
// 读取CSV文件中的部分
DataSet<Tuple2<String, Double>> csvInput = env.readCsvFile("hdfs:///the/CSV/file").includeFields("10010").types(String.class, Double.class);
// 读取CSV映射为一个java类
DataSet<Person>> csvInput = env.readCsvFile("hdfs:///the/CSV/file").pojoType(Person.class, "name", "age", "zipcode");
// 读取一个指定位置序列化好的文件
DataSet<Tuple2<IntWritable, Text>> tuples =
env.readSequenceFile(IntWritable.class, Text.class, "hdfs://nnHost:nnPort/path/to/file");
// 从输入字符创建
DataSet<String> value = env.fromElements("Foo", "bar", "foobar", "fubar");
// 创建一个数字序列
DataSet<Long> numbers = env.generateSequence(1, 10000000);
// 从关系型数据库读取
DataSet<Tuple2<String, Integer> dbData =
env.createInput(JDBCInputFormat.buildJDBCInputFormat() .setDrivername("org.apache.derby.jdbc.EmbeddedDriver") .setDBUrl("jdbc:derby:memory:persons")
.setQuery("select name, age from persons")
.setRowTypeInfo(new RowTypeInfo(BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.INT_TYPE_INFO))
.finish());
```
### DataSet Transformation
> 详细可以参考官网:https://flink.sojb.cn/dev/batch/dataset_transformations.html#filter
* Map
采用一个数据元并生成一个数据元。
```
data.map(new MapFunction<String, Integer>() {
public Integer map(String value) { return Integer.parseInt(value); }
});
```
* FlatMap
采用一个数据元并生成零个,一个或多个数据元。
```
data.flatMap(new FlatMapFunction<String, String>() {
public void flatMap(String value, Collector<String> out) {
for (String s : value.split(" ")) {
out.collect(s);
}
}
});
```
* MapPartition
在单个函数调用中转换并行分区。该函数将分区作为Iterable流来获取,并且可以生成任意数量的结果值。每个分区中的数据元数量取决于并行度和先前的 算子操作。
```
data.mapPartition(new MapPartitionFunction<String, Long>() {
public void mapPartition(Iterable<String> values, Collector<Long> out) {
long c = 0;
for (String s : values) {
c++;
}
out.collect(c);
}
});
```
* Filter
计算每个数据元的布尔函数,并保存函数返回true的数据元。
重要信息:系统假定该函数不会修改应用谓词的数据元。违反此假设可能会导致错误的结果。
```
data.filter(new FilterFunction<Integer>() {
public boolean filter(Integer value) { return value > 1000; }
});
```
* Reduce
通过将两个数据元重复组合成一个数据元,将一组数据元组合成一个数据元。Reduce可以应用于完整数据集或分组数据集。
```
data.reduce(new ReduceFunction<Integer> {
public Integer reduce(Integer a, Integer b) { return a + b; }
});
```
如果将reduce应用于分组数据集,则可以通过提供CombineHintto 来指定运行时执行reduce的组合阶段的方式 setCombineHint。在大多数情况下,基于散列的策略应该更快,特别是如果不同键的数量与输入数据元的数量相比较小(例如1/10)。
* ReduceGroup
将一组数据元组合成一个或多个数据元。ReduceGroup可以应用于完整数据集或分组数据集。
```
data.reduceGroup(new GroupReduceFunction<Integer, Integer> {
public void reduce(Iterable<Integer> values, Collector<Integer> out) {
int prefixSum = 0;
for (Integer i : values) {
prefixSum += i;
out.collect(prefixSum);
}
}
});
```
* Aggregate
将一组值聚合为单个值。聚合函数可以被认为是内置的reduce函数。聚合可以应用于完整数据集或分组数据集。
```
Dataset<Tuple3<Integer, String, Double>> input = // [...]
DataSet<Tuple3<Integer, String, Double>> output = input.aggregate(SUM, 0).and(MIN, 2);
```
您还可以使用简写语法进行最小,最大和总和聚合。
```
Dataset<Tuple3<Integer, String, Double>> input = // [...]
DataSet<Tuple3<Integer, String, Double>> output = input.sum(0).andMin(2);
```
* Distinct
返回数据集的不同数据元。它相对于数据元的所有字段或字段子集从输入DataSet中删除重复条目。
```
data.distinct();
```
使用reduce函数实现Distinct。您可以通过提供CombineHintto 来指定运行时执行reduce的组合阶段的方式 setCombineHint。在大多数情况下,基于散列的策略应该更快,特别是如果不同键的数量与输入数据元的数量相比较小(例如1/10)。
* Join
通过创建在其键上相等的所有数据元对来连接两个数据集。可选地使用JoinFunction将数据元对转换为单个数据元,或使用FlatJoinFunction将数据元对转换为任意多个(包括无)数据元。请参阅键部分以了解如何定义连接键。
```
result = input1.join(input2)
.where(0) // key of the first input (tuple field 0)
.equalTo(1); // key of the second input (tuple field 1)
```
您可以通过Join Hints指定运行时执行连接的方式。提示描述了通过分区或广播进行连接,以及它是使用基于排序还是基于散列的算法。
如果未指定提示,系统将尝试估算输入大小,并根据这些估计选择最佳策略。
```
// This executes a join by broadcasting the first data set
// using a hash table for the broadcast data
result = input1.join(input2, JoinHint.BROADCAST_HASH_FIRST)
.where(0).equalTo(1);
```
请注意,连接转换仅适用于等连接。其他连接类型需要使用OuterJoin或CoGroup表示。
* OuterJoin
在两个数据集上执行左,右或全外连接。外连接类似于常规(内部)连接,并创建在其键上相等的所有数据元对。此外,如果在另一侧没有找到匹配的Keys,则保存“外部”侧(左侧,右侧或两者都满)的记录。匹配数据元对(或一个数据元和null另一个输入的值)被赋予JoinFunction以将数据元对转换为单个数据元,或者转换为FlatJoinFunction以将数据元对转换为任意多个(包括无)数据元。请参阅键部分以了解如何定义连接键。
```
input1.leftOuterJoin(input2) // rightOuterJoin or fullOuterJoin for right or full outer joins
.where(0) // key of the first input (tuple field 0)
.equalTo(1) // key of the second input (tuple field 1)
.with(new JoinFunction<String, String, String>() {
public String join(String v1, String v2) {
// NOTE:
// - v2 might be null for leftOuterJoin
// - v1 might be null for rightOuterJoin
// - v1 OR v2 might be null for fullOuterJoin
}
});
```
* CoGroup
reduce 算子操作的二维变体。将一个或多个字段上的每个输入分组,然后关联组。每对组调用转换函数。
```
data1.coGroup(data2)
.where(0)
.equalTo(1)
.with(new CoGroupFunction<String, String, String>() {
public void coGroup(Iterable<String> in1, Iterable<String> in2, Collector<String> out) {
out.collect(...);
}
});
```
* Cross
构建两个输入的笛卡尔积(交叉乘积),创建所有数据元对。可选择使用CrossFunction将数据元对转换为单个数据元
```
DataSet<Integer> data1 = // [...]
DataSet<String> data2 = // [...]
DataSet<Tuple2<Integer, String>> result = data1.cross(data2);
```
注:交叉是一个潜在的非常计算密集型 算子操作它甚至可以挑战大的计算集群!建议使用crossWithTiny()和crossWithHuge()来提示系统的DataSet大小。
* Union
生成两个数据集的并集。
```
DataSet<String> data1 = // [...]
DataSet<String> data2 = // [...]
DataSet<String> result = data1.union(data2);
```
* Rebalance
均匀地Rebalance 数据集的并行分区以消除数据偏差。只有类似Map的转换可能会遵循Rebalance 转换。
```
DataSet<String> in = // [...]
DataSet<String> result = in.rebalance()
.map(new Mapper());
```
* Hash-Partition
散列分区给定键上的数据集。键可以指定为位置键,表达键和键选择器函数。
```
DataSet<Tuple2<String,Integer>> in = // [...]
DataSet<Integer> result = in.partitionByHash(0)
.mapPartition(new PartitionMapper());
```
* Range-Partition
Range-Partition给定键上的数据集。键可以指定为位置键,表达键和键选择器函数。
```
DataSet<Tuple2<String,Integer>> in = // [...]
DataSet<Integer> result = in.partitionByRange(0)
.mapPartition(new PartitionMapper());
```
* Custom Partitioning
手动指定数据分区。
注意:此方法仅适用于单个字段键。
```
DataSet<Tuple2<String,Integer>> in = // [...]
DataSet<Integer> result = in.partitionCustom(Partitioner<K> partitioner, key)
```
* Sort Partition
本地按指定顺序对指定字段上的数据集的所有分区进行排序。可以将字段指定为元组位置或字段表达式。通过链接sortPartition()调用来完成对多个字段的排序。
```
DataSet<Tuple2<String,Integer>> in = // [...]
DataSet<Integer> result = in.sortPartition(1, Order.ASCENDING)
.mapPartition(new PartitionMapper());
```
* First-n
返回数据集的前n个(任意)数据元。First-n可以应用于常规数据集,分组数据集或分组排序数据集。分组键可以指定为键选择器函数或字段位置键。
```
DataSet<Tuple2<String,Integer>> in = // [...]
// regular data set
DataSet<Tuple2<String,Integer>> result1 = in.first(3);
// grouped data set
DataSet<Tuple2<String,Integer>> result2 = in.groupBy(0) .first(3);
// grouped-sorted data set
DataSet<Tuple2<String,Integer>> result3 = in.groupBy(0) .sortGroup(1, Order.ASCENDING) .first(3);
```
### DataSet Sink
数据接收器使用DataSet用于存储或返回。使用OutputFormat描述数据接收器算子操作 。Flink带有各种内置输出格式,这些格式封装在DataSet上的算子操作中:
* writeAsText()/ TextOutputFormat- 按字符串顺序写入数据元。通过调用每个数据元的toString()方法获得字符串。
* writeAsFormattedText()/ TextOutputFormat- 按字符串顺序写数据元。通过为每个数据元调用用户定义的format()方法来获取字符串。
* writeAsCsv(...)/ CsvOutputFormat- 将元组写为逗号分隔值文件。行和字段分隔符是可配置的。每个字段的值来自对象的toString()方法。
* print()/ printToErr()/ print(String msg)/ printToErr(String msg)- 在标准输出/标准错误流上打印每个数据元的toString()值。可选地,可以提供前缀(msg),其前缀为输出。这有助于区分不同的打印调用。如果并行度大于1,则输出也将与生成输出的任务的标识符一起添加。
* write()/ FileOutputFormat- 自定义文件输出的方法和基类。支持自定义对象到字节的转换。
* output()/ OutputFormat- 大多数通用输出方法,用于非基于文件的数据接收器(例如将结果存储在数据库中)。
可以将DataSet输入到多个 算子操作。程序可以编写或打印数据集,同时对它们执行其他转换。
示例:
```
// text data
DataSet<String> textData = // [...]
// write DataSet to a file on the local file system
textData.writeAsText("file:///my/result/on/localFS");
// write DataSet to a file on a HDFS with a namenode running at nnHost:nnPort
textData.writeAsText("hdfs://nnHost:nnPort/my/result/on/localFS");
// write DataSet to a file and overwrite the file if it exists
textData.writeAsText("file:///my/result/on/localFS", WriteMode.OVERWRITE);
// tuples as lines with pipe as the separator "a|b|c"
DataSet<Tuple3<String, Integer, Double>> values = // [...]
values.writeAsCsv("file:///path/to/the/result/file", "\n", "|");
// this writes tuples in the text formatting "(a, b, c)", rather than as CSV lines
values.writeAsText("file:///path/to/the/result/file");
// this writes values as strings using a user-defined TextFormatter object
values.writeAsFormattedText("file:///path/to/the/result/file",
new TextFormatter<Tuple2<Integer, Integer>>() {
public String format (Tuple2<Integer, Integer> value) {
return value.f1 + " - " + value.f0;
}
});
```
使用自定义输出格式:
```
DataSet<Tuple3<String, Integer, Double>> myResult = [...]
// write Tuple DataSet to a relational database
myResult.output(
// build and configure OutputFormat
JDBCOutputFormat.buildJDBCOutputFormat()
.setDrivername("org.apache.derby.jdbc.EmbeddedDriver")
.setDBUrl("jdbc:derby:memory:persons")
.setQuery("insert into persons (name, age, height) values (?,?,?)")
.finish()
);
```
## 序列化器
* Flink自带了针对诸如int,long,String等标准类型的序列化器
* 针对Flink无法实现序列化的数据类型,我们可以交给Avro和Kryo
* 使用方法:ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
```
使用avro序列化:env.getConfig().enableForceAvro();
使用kryo序列化:env.getConfig().enableForceKryo();
使用自定义序列化:env.getConfig().addDefaultKryoSerializer(Class<?> type, Class<? extends Serializer<?>> serializerClass)
```
## 数据类型
* Java Tuple 和 Scala case class
* Java POJOs:java实体类
* Primitive Types
默认支持java和scala基本数据类型
* General Class Types
默认支持大多数java和scala class
* Hadoop Writables
支持hadoop中实现了org.apache.hadoop.Writable的数据类型
* Special Types
例如scala中的Either Option 和Try
================================================
FILE: Flink/Flink从入门到放弃(入门篇4)-DataStreamAPI.md
================================================
DataStream算子将一个或多个DataStream转换为新DataStream。程序可以将多个转换组合成复杂的数据流拓扑。
DataStreamAPI和DataSetAPI主要的区别在于Transformation部分。
## DataStream Transformation
### map
* DataStream→DataStream
用一个数据元生成一个数据元。一个map函数,它将输入流的值加倍:
```
DataStream<Integer> dataStream = //...
dataStream.map(new MapFunction<Integer, Integer>() {
@Override
public Integer map(Integer value) throws Exception {
return 2 * value;
}
});
```
### FlatMap
* DataStream→DataStream
采用一个数据元并生成零个,一个或多个数据元。将句子分割为单词的flatmap函数:
```
dataStream.flatMap(new FlatMapFunction<String, String>() {
@Override
public void flatMap(String value, Collector<String> out)
throws Exception {
for(String word: value.split(" ")){
out.collect(word);
}
}
});
```
### Filter
* DataStream→DataStream
计算每个数据元的布尔函数,并保存函数返回true的数据元。过滤掉零值的过滤器:
```
dataStream.filter(new FilterFunction<Integer>() {
@Override
public boolean filter(Integer value) throws Exception {
return value != 0;
}
});
```
### KeyBy
* DataStream→KeyedStream
逻辑上将流分区为不相交的分区。具有相同Keys的所有记录都分配给同一分区。在内部,keyBy()是使用散列分区实现的。指定键有不同的方法。
此转换返回KeyedStream,其中包括使用被Keys化状态所需的KeyedStream。
```
dataStream.keyBy("someKey") // Key by field "someKey"
dataStream.keyBy(0) // Key by the first element of a Tuple
```
🌺注意:
如果出现以下情况,则类型不能成为key:
* 它是POJO类型但不覆盖hashCode()方法并依赖于Object.hashCode()实现
* 任何类型的数组
### Reduce
KeyedStream→DataStream
将当前数据元与最后一个Reduce的值组合并发出新值。
例如:reduce函数,用于创建部分和的流:
```
keyedStream.reduce(new ReduceFunction<Integer>() {
@Override
public Integer reduce(Integer value1, Integer value2)
throws Exception {
return value1 + value2;
}
});
```
### Fold
KeyedStream→DataStream
具有初始值的被Keys化数据流上的“滚动”折叠。将当前数据元与最后折叠的值组合并发出新值。
折叠函数,当应用于序列(1,2,3,4,5)时,发出序列“start-1”,“start-1-2”,“start-1-2-3”,. ..
```
DataStream<String> result =
keyedStream.fold("start", new FoldFunction<Integer, String>() {
@Override
public String fold(String current, Integer value) {
return current + "-" + value;
}
});
```
### 聚合
* KeyedStream→DataStream
在被Keys化数据流上滚动聚合。min和minBy之间的差异是min返回最小值,而minBy返回该字段中具有最小值的数据元(max和maxBy相同)。
```
keyedStream.sum(0);
keyedStream.sum("key");
keyedStream.min(0);
keyedStream.min("key");
keyedStream.max(0);
keyedStream.max("key");
keyedStream.minBy(0);
keyedStream.minBy("key");
keyedStream.maxBy(0);
keyedStream.maxBy("key");
```
### Window函数
关于Flink的窗口概念,我们会在后面有详细介绍。
* Window
KeyedStream→WindowedStream
可以在已经分区的KeyedStream上定义Windows。Windows根据某些特征(例如,在最后5秒内到达的数据)对每个Keys中的数据进行分组。
```
dataStream.keyBy(0)
.window(TumblingEventTimeWindows
.of(Time.seconds(5))); // Last 5 seconds of data
```
* Window Apply
WindowedStream→DataStream
AllWindowedStream→DataStream
将一般函数应用于整个窗口。下面是一个手动求和窗口数据元的函数。
注意:如果您正在使用windowAll转换,则需要使用AllWindowFunction。
```
windowedStream.apply (new WindowFunction<Tuple2<String,Integer>, Integer, Tuple, Window>() {
public void apply (Tuple tuple,
Window window,
Iterable<Tuple2<String, Integer>> values,
Collector<Integer> out) throws Exception {
int sum = 0;
for (value t: values) {
sum += t.f1;
}
out.collect (new Integer(sum));
}
});
// applying an AllWindowFunction on non-keyed window stream
allWindowedStream.apply (new AllWindowFunction<Tuple2<String,Integer>, Integer, Window>() {
public void apply (Window window,
Iterable<Tuple2<String, Integer>> values,
Collector<Integer> out) throws Exception {
int sum = 0;
for (value t: values) {
sum += t.f1;
}
out.collect (new Integer(sum));
}
});
```
* Window Reduce
WindowedStream→DataStream
将reduce函数应用于窗口并返回reduce后的值。
```
windowedStream.reduce (new ReduceFunction<Tuple2<String,Integer>>() {
public Tuple2<String, Integer> reduce(Tuple2<String, Integer> value1, Tuple2<String, Integer> value2) throws Exception {
return new Tuple2<String,Integer>(value1.f0, value1.f1 + value2.f1);
}
});
```
* 提取时间戳
>关于Time我们在后面有专门的章节进行介绍
DataStream→DataStream
从记录中提取时间戳,以便使用使用事件时间语义的窗口。
```
stream.assignTimestamps (new TimeStampExtractor() {...});
```
### Partition 分区
* 自定义分区
DataStream→DataStream
使用用户定义的分区程序为每个数据元选择目标任务。
```
dataStream.partitionCustom(partitioner, "someKey");
dataStream.partitionCustom(partitioner, 0);
```
* 随机分区
DataStream→DataStream
根据均匀分布随机分配数据元。
```
dataStream.shuffle();
```
* Rebalance (循环分区)
DataStream→DataStream
分区数据元循环,每个分区创建相等的负载。在存在数据倾斜时用于性能优化。
```
dataStream.rebalance();
```
* rescale
DataStream→DataStream
如果上游 算子操作具有并行性2并且下游算子操作具有并行性6,则一个上游 算子操作将分配元件到三个下游算子操作,而另一个上游算子操作将分配到其他三个下游 算子操作。另一方面,如果下游算子操作具有并行性2而上游 算子操作具有并行性6,则三个上游 算子操作将分配到一个下游算子操作,而其他三个上游算子操作将分配到另一个下游算子操作。
在不同并行度不是彼此的倍数的情况下,一个或多个下游 算子操作将具有来自上游 算子操作的不同数量的输入。
请参阅此图以获取上例中连接模式的可视化:

```
dataStream.rescale();
```
* 广播
DataStream→DataStream
向每个分区广播数据元。
```
dataStream.broadcast();
```
================================================
FILE: Flink/Flink集群部署.md
================================================
## 部署方式
一般来讲有三种方式:
* Local
* Standalone
* Flink On Yarn/Mesos/K8s…
## 单机模式
参考上一篇**Flink从入门到放弃(入门篇2)-本地环境搭建&构建第一个Flink应用**
## Standalone模式部署
我们基于CentOS7虚拟机搭建一个3个节点的集群:
角色分配:
```
Master: 192.168.246.134
Slave: 192.168.246.135
Slave: 192.168.246.136
```
```
192.168.246.134 jobmanager
192.168.246.135 taskmanager
192.168.246.136 taskmanager
```
假设三台机器都存在:
用户root 密码为123
```
192.168.246.134 master
192.168.246.135 slave1
192.168.246.136 slave2
```
三台机器首先要做ssh免登,具体方法很简单,可以百度。
下载一个包到本地:

这里我选择了1.7.2版本+Hadoop2.8+Scala2.11版本
然后,分发
```
scp flink-1.7.2-bin-hadoop28-scala_2.11.tgz root@192.168.246.13X:~
scp jdk-8u11-linux-x64.tar.gz root@192.168.246.13X:~
注意:X代表4、5、6,分发到3台机器
修改解压后目录属主:
Chown -R root:root flink/
Chown -R root:root jdk8/
export JAVA_HOME=/root/jdk8
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH
```
分别修改master和slave的flink-conf.yaml文件
```
Vim flink/conf/flink-conf.yaml
##配置master节点ip
jobmanager.rpc.address: 192.168.1.100
##配置slave节点可用内存,单位MB
taskmanager.heap.mb: 25600
##配置每个节点的可用slot,1 核CPU对应 1 slot
##the number of available CPUs per machine
taskmanager.numberOfTaskSlots: 30
##默认并行度 1 slot资源
parallelism.default: 1
修改slave节点配置文件slaves:
192.168.246.135
192.168.246.136
```
启动集群:
```
##在master节点上执行此脚本,就可以启动集群,前提要保证master节点到slaver节点可以免密登录,
##因为它的启动过程是:先在master节点启动jobmanager进程,然后ssh到各slaver节点启动taskmanager进程
./bin/start-cluster.sh
停止集群:
./bin/stop-cluster.sh
```
## Flink on yarn集群部署
### Yarn的简介:

* ResourceManager
ResourceManager 负责整个集群的资源管理和分配,是一个全局的资源管理系统。 NodeManager 以心跳的方式向 ResourceManager 汇报资源使用情况(目前主要是 CPU 和内存的使用情况)。RM 只接受 NM 的资源回报信息,对于具体的资源处理则交给 NM 自己处理。
* NodeManager
NodeManager 是每个节点上的资源和任务管理器,它是管理这台机器的代理,负责该节点程序的运行,以及该节点资源的管理和监控。YARN 集群每个节点都运行一个NodeManager。
NodeManager 定时向 ResourceManager 汇报本节点资源(CPU、内存)的使用情况和Container 的运行状态。当 ResourceManager 宕机时 NodeManager 自动连接 RM 备用节点。
NodeManager 接收并处理来自 ApplicationMaster 的 Container 启动、停止等各种请求。
* ApplicationMaster
负责与 RM 调度器协商以获取资源(用 Container 表示)。
将得到的任务进一步分配给内部的任务(资源的二次分配)。
与 NM 通信以启动/停止任务。
监控所有任务运行状态,并在任务运行失败时重新为任务申请资源以重启任务
### Flink on yarn 集群启动步骤
* 步骤1 用户向YARN中提交应用程序,其中包括ApplicationMaster程序、启动ApplicationMaster的命令、用户程序等。
* 步骤2 ResourceManager为该应用程序分配第一个Container,并与对应的Node-Manager通信,要求它在这个Container中启动应用程序的ApplicationMaster。
* 步骤3 ApplicationMaster首先向ResourceManager注册,这样用户可以直接通过ResourceManager查看应用程序的运行状态,然后它将为各个任务申请资源,并监控它的运行状态,直到运行结束,即重复步骤4~7。
* 步骤4 ApplicationMaster采用轮询的方式通过RPC协议向ResourceManager申请和领取资源。
* 步骤5 一旦ApplicationMaster申请到资源后,便与对应的NodeManager通信,要求它启动任务。
* 步骤6 NodeManager为任务设置好运行环境(包括环境变量、JAR包、二进制程序等)后,将任务启动命令写到一个脚本中,并通过运行该脚本启动任务。
* 步骤7 各个任务通过某个RPC协议向ApplicationMaster汇报自己的状态和进度,以让ApplicationMaster随时掌握各个任务的运行状态,从而可以在任务失败时重新启动任务。 在应用程序运行过程中,用户可随时通过RPC向ApplicationMaster查询应用程序的当前运行状态。
* 步骤8 应用程序运行完成后,ApplicationMaster向ResourceManager注销并关闭自己
### on yarn 集群部署
设置Hadoop环境变量:
```
[root@hadoop2 flink-1.7.2]# vi /etc/profile
export HADOOP_CONF_DIR=这里是你自己的hadoop路径
```
bin/yarn-session.sh -h 查看使用方法:

在启动的是可以指定TaskManager的个数以及内存(默认是1G),也可以指定JobManager的内存,但是JobManager的个数只能是一个
我们开启动一个YARN session:
```
./bin/yarn-session.sh -n 4 -tm 8192 -s 8
```
上面命令启动了4个TaskManager,每个TaskManager内存为8G且占用了8个核(是每个TaskManager,默认是1个核)。在启动YARN session的时候会加载conf/flink-config.yaml配置文件,我们可以根据自己的需求去修改里面的相关参数.
YARN session启动之后就可以使用bin/flink来启动提交作业:
例如:
```
./bin/flink run -c com.demo.wangzhiwu.WordCount $DEMO_DIR/target/flink-demo-1.0.SNAPSHOT.jar --port 9000
```
flink run的用法如下:
```
用法: run [OPTIONS] <jar-file> <arguments>
"run" 操作参数:
-c,--class <classname> 如果没有在jar包中指定入口类,则需要在这里通过这个参数指定
-m,--jobmanager <host:port> 指定需要连接的jobmanager(主节点)地址
使用这个参数可以指定一个不同于配置文件中的jobmanager
-p,--parallelism <parallelism> 指定程序的并行度。可以覆盖配置文件中的默认值。
```
使用run 命令向yarn集群提交一个job。客户端可以确定jobmanager的地址。当然,你也可以通过-m参数指定jobmanager。jobmanager的地址在yarn控制台上可以看到。
值得注意的是:
上面的YARN session是在Hadoop YARN环境下启动一个Flink cluster集群,里面的资源是可以共享给其他的Flink作业。我们还可以在YARN上启动一个Flink作业。这里我们还是使用./bin/flink,但是不需要事先启动YARN session:
```
./bin/flink run -m yarn-cluster -yn 2 ./examples/batch/WordCount.jar \
--input hdfs://user/hadoop/input.txt \
--output hdfs://user/hadoop/output.txt
```
上面的命令同样会启动一个类似于YARN session启动的页面。其中的-yn是指TaskManager的个数,必须要指定。
#### 后台运行 yarn session
如果你不希望flink yarn client一直运行,也可以启动一个后台运行的yarn session。使用这个参数:-d 或者 --detached
在这种情况下,flink yarn client将会只提交任务到集群然后关闭自己。注意:在这种情况下,无法使用flink停止yarn session。
必须使用yarn工具来停止yarn session
```
yarn application -kill <applicationId>
```
#### flink on yarn的故障恢复
flink 的 yarn 客户端通过下面的配置参数来控制容器的故障恢复。这些参数可以通过conf/flink-conf.yaml 或者在启动yarn session的时候通过-D参数来指定。
* yarn.reallocate-failed:这个参数控制了flink是否应该重新分配失败的taskmanager容器。默认是true。
* yarn.maximum-failed-containers:applicationMaster可以接受的容器最大失败次数,达到这个参数,就会认为yarn session失败。默认这个次数和初始化请求的taskmanager数量相等(-n 参数指定的)。
* yarn.application-attempts:applicationMaster重试的次数。如果这个值被设置为1(默认就是1),当application master失败的时候,yarn session也会失败。设置一个比较大的值的话,yarn会尝试重启applicationMaster。
#### 日志文件查看
在某种情况下,flink yarn session 部署失败是由于它自身的原因,用户必须依赖于yarn的日志来进行分析。最有用的就是yarn log aggregation 。启动它,用户必须在yarn-site.xml文件中设置yarn.log-aggregation-enable 属性为true。一旦启用了,用户可以通过下面的命令来查看一个失败的yarn session的所有详细日志。
```
yarn logs -applicationId <application ID>
```
完。
================================================
FILE: Flink漫谈系列/Apache-Flink-漫谈系列(02)-Watermark.md
================================================
## 实际问题(乱序)
在介绍Watermark相关内容之前我们先抛出一个具体的问题,在实际的流式计算中数据到来的顺序对计算结果的正确性有至关重要的影响,比如:某数据源中的某些数据由于某种原因(如:网络原因,外部存储自身原因)会有5秒的延时,也就是在实际时间的第1秒产生的数据有可能在第5秒中产生的数据之后到来(比如到Window处理节点).选具体某个delay的元素来说,假设在一个5秒的Tumble窗口(详见Window介绍章节),有一个EventTime是 11秒的数据,在第16秒时候到来了。图示第11秒的数据,在16秒到来了,如下图:
-Watermark.resources/E6EAF710-D569-4A11-A65D-687482D7B246.png)
那么对于一个Count聚合的Tumble(5s)的window,上面的情况如何处理才能window2=4,window3=2 呢?
## Apache Flink的时间类型
开篇我们描述的问题是一个很常见的TimeWindow中数据乱序的问题,乱序是相对于事件产生时间和到达Apache Flink 实际处理算子的顺序而言的,关于时间在Apache Flink中有如下三种时间类型,如下图:
-Watermark.resources/B28386CD-F1C2-4DED-AC1C-8983B7BB3C81.png)
**ProcessingTime**
是数据流入到具体某个算子时候相应的系统时间。ProcessingTime 有最好的性能和最低的延迟。但在分布式计算环境中ProcessingTime具有不确定性,相同数据流多次运行有可能产生不同的计算结果。
**IngestionTime**
IngestionTime是数据进入Apache Flink框架的时间,是在Source Operator中设置的。与ProcessingTime相比可以提供更可预测的结果,因为IngestionTime的时间戳比较稳定(在源处只记录一次),同一数据在流经不同窗口操作时将使用相同的时间戳,而对于ProcessingTime同一数据在流经不同窗口算子会有不同的处理时间戳。
**EventTime**
EventTime是事件在设备上产生时候携带的。在进入Apache Flink框架之前EventTime通常要嵌入到记录中,并且EventTime也可以从记录中提取出来。在实际的网上购物订单等业务场景中,大多会使用EventTime来进行数据计算。
开篇描述的问题和本篇要介绍的Watermark所涉及的时间类型均是指EventTime类型。
## 什么是Watermark
Watermark是Apache Flink为了处理EventTime 窗口计算提出的一种机制,本质上也是一种时间戳,由Apache Flink Source或者自定义的Watermark生成器按照需求Punctuated或者Periodic两种方式生成的一种系统Event,与普通数据流Event一样流转到对应的下游算子,接收到Watermark Event的算子以此不断调整自己管理的EventTime clock。 Apache Flink 框架保证Watermark单调递增,算子接收到一个Watermark时候,框架知道不会再有任何小于该Watermark的时间戳的数据元素到来了,所以Watermark可以看做是告诉Apache Flink框架数据流已经处理到什么位置(时间维度)的方式。 Watermark的产生和Apache Flink内部处理逻辑如下图所示:
-Watermark.resources/32C5E28E-7C48-4E69-A180-FF78815EF75E.png)
## Watermark的产生方式
目前Apache Flink 有两种生产Watermark的方式,如下:
* Punctuated - 数据流中每一个递增的EventTime都会产生一个Watermark。
在实际的生产中Punctuated方式在TPS很高的场景下会产生大量的Watermark在一定程度上对下游算子造成压力,所以只有在实时性要求非常高的场景才会选择Punctuated的方式进行Watermark的生成。
* Periodic - 周期性的(一定时间间隔或者达到一定的记录条数)产生一个Watermark。在实际的生产中Periodic的方式必须结合时间和积累条数两个维度继续周期性产生Watermark,否则在极端情况下会有很大的延时。
所以Watermark的生成方式需要根据业务场景的不同进行不同的选择。
## Watermark的接口定义
对应Apache Flink Watermark两种不同的生成方式,我们了解一下对应的接口定义,如下:
* Periodic Watermarks - AssignerWithPeriodicWatermarks
```
/**
* Returns the current watermark. This method is periodically called by the
* system to retrieve the current watermark. The method may return {@code null} to
* indicate that no new Watermark is available.
*
* <p>The returned watermark will be emitted only if it is non-null and itsTimestamp
* is larger than that of the previously emitted watermark (to preserve the contract of
* ascending watermarks). If the current watermark is still
* identical to the previous one, no progress in EventTime has happened since
* the previous call to this method. If a null value is returned, or theTimestamp
* of the returned watermark is smaller than that of the last emitted one, then no
* new watermark will be generated.
*
* <p>The interval in which this method is called and Watermarks are generated
* depends on {@link ExecutionConfig#getAutoWatermarkInterval()}.
*
* @see org.Apache.flink.streaming.api.watermark.Watermark
* @see ExecutionConfig#getAutoWatermarkInterval()
*
* @return {@code Null}, if no watermark should be emitted, or the next watermark to emit.
*/
@Nullable
Watermark getCurrentWatermark();
```
* Punctuated Watermarks -AssignerWithPunctuatedWatermarks
```
public interface AssignerWithPunctuatedWatermarks<T> extendsTimestampAssigner<T> {
/**
* Asks this implementation if it wants to emit a watermark. This method is called right after
* the {@link #extractTimestamp(Object, long)} method.
*
* <p>The returned watermark will be emitted only if it is non-null and itsTimestamp
* is larger than that of the previously emitted watermark (to preserve the contract of
* ascending watermarks). If a null value is returned, or theTimestamp of the returned
* watermark is smaller than that of the last emitted one, then no new watermark will
* be generated.
*
* <p>For an example how to use this method, see the documentation of
* {@link AssignerWithPunctuatedWatermarks this class}.
*
* @return {@code Null}, if no watermark should be emitted, or the next watermark to emit.
*/
@Nullable
Watermark checkAndGetNextWatermark(T lastElement, long extractedTimestamp);
}
```
* AssignerWithPunctuatedWatermarks 继承了TimestampAssigner接口 -TimestampAssigner
```
public interfaceTimestampAssigner<T> extends Function {
/**
* Assigns aTimestamp to an element, in milliseconds since the Epoch.
*
* <p>The method is passed the previously assignedTimestamp of the element.
* That previousTimestamp may have been assigned from a previous assigner,
* by ingestionTime. If the element did not carry aTimestamp before, this value is
* {@code Long.MIN_VALUE}.
*
* @param element The element that theTimestamp is wil be assigned to.
* @param previousElementTimestamp The previous internalTimestamp of the element,
* or a negative value, if noTimestamp has been assigned, yet.
* @return The newTimestamp.
*/
long extractTimestamp(T element, long previousElementTimestamp);
}
```
从接口定义可以看出,Watermark可以在Event(Element)中提取EventTime,进而定义一定的计算逻辑产生Watermark的时间戳。
## Watermark解决如上问题
从上面的Watermark生成接口和Apache Flink内部对Periodic Watermark的实现来看,Watermark的时间戳可以和Event中的EventTime 一致,也可以自己定义任何合理的逻辑使得Watermark的时间戳不等于Event中的EventTime,Event中的EventTime自产生那一刻起就不可以改变了,不受Apache Flink框架控制,而Watermark的产生是在Apache Flink的Source节点或实现的Watermark生成器计算产生(如上Apache Flink内置的 Periodic Watermark实现), Apache Flink内部对单流或多流的场景有统一的Watermark处理。
回过头来我们在看看Watermark机制如何解决上面的问题,上面的问题在于如何将迟来的EventTime 位11的元素正确处理。要解决这个问题我们还需要先了解一下EventTime window是如何触发的? EventTime window 计算条件是当Window计算的Timer时间戳 小于等于 当前系统的Watermak的时间戳时候进行计算。
* 当Watermark的时间戳等于Event中携带的EventTime时候,上面场景(Watermark=EventTime)的计算结果如下:
-Watermark.resources/12F12191-492B-45E4-987E-8744D4235822.png)
上面对应的DDL(Alibaba 企业版的Flink分支)定义如下:
```
CREATE TABLE source(
...,
Event_timeTimeStamp,
WATERMARK wk1 FOR Event_time as withOffset(Event_time, 0)
) with (
...
);
```
* 如果想正确处理迟来的数据可以定义Watermark生成策略为 Watermark = EventTime -5s, 如下:
-Watermark.resources/EB72E25B-15BA-4651-BB26-177F1A71E692.png)
上面对应的DDL(Alibaba 内部的DDL语法,目前正在和社区讨论)定义如下:
```
CREATE TABLE source(
...,
Event_timeTimeStamp,
WATERMARK wk1 FOR Event_time as withOffset(Event_time, 5000)
) with (
...
);
```
上面正确处理的根源是我们采取了 延迟触发 window 计算 的方式正确处理了 Late Event. 与此同时,我们发现window的延时触发计算,也导致了下游的LATENCY变大,本例子中下游得到window的结果就延迟了5s.
## 多流的Watermark处理
在实际的流计算中往往一个job中会处理多个Source的数据,对Source的数据进行GroupBy分组,那么来自不同Source的相同key值会shuffle到同一个处理节点,并携带各自的Watermark,Apache Flink内部要保证Watermark要保持单调递增,多个Source的Watermark汇聚到一起时候可能不是单调自增的,这样的情况Apache Flink内部是如何处理的呢?如下图所示:
-Watermark.resources/C7873B00-89E2-4CE4-BB5E-584A1CF4767A.png)
Apache Flink内部实现每一个边上只能有一个递增的Watermark, 当出现多流携带Eventtime汇聚到一起(GroupBy or Union)时候,Apache Flink会选择所有流入的Eventtime中最小的一个向下游流出。从而保证watermark的单调递增和保证数据的完整性.如下图:
-Watermark.resources/8F75B306-8255-4D8D-BE79-BD2FC98113B8.png)
## 小结
本节以一个流计算常见的乱序问题介绍了Apache Flink如何利用Watermark机制来处理乱序问题. 本篇内容在一定程度上也体现了EventTime Window中的Trigger机制依赖了Watermark(后续Window篇章会介绍)。Watermark机制是流计算中处理乱序,正确处理Late Event的核心手段。
================================================
FILE: Flink漫谈系列/Apache-Flink-漫谈系列(03)-State.md
================================================
## 实际问题
在流计算场景中,数据会源源不断的流入Apache Flink系统,每条数据进入Apache Flink系统都会触发计算。如果我们想进行一个Count聚合计算,那么每次触发计算是将历史上所有流入的数据重新新计算一次,还是每次计算都是在上一次计算结果之上进行增量计算呢?答案是肯定的,Apache Flink是基于上一次的计算结果进行增量计算的。那么问题来了: "上一次的计算结果保存在哪里,保存在内存可以吗?",答案是否定的,如果保存在内存,在由于网络,硬件等原因造成某个计算节点失败的情况下,上一次计算结果会丢失,在节点恢复的时候,就需要将历史上所有数据(可能十几天,上百天的数据)重新计算一次,所以为了避免这种灾难性的问题发生,Apache Flink 会利用State存储计算结果。本篇将会为大家介绍Apache Flink State的相关内容。
## 什么是State
这个问题似乎有些"弱智"?不管问题的答案是否显而易见,但我还是想简单说一下在Apache Flink里面什么是State?State是指流计算过程中计算节点的中间计算结果或元数据属性,比如 在aggregation过程中要在state中记录中间聚合结果,比如 Apache Kafka 作为数据源时候,我们也要记录已经读取记录的offset,这些State数据在计算过程中会进行持久化(插入或更新)。所以Apache Flink中的State就是与时间相关的,Apache Flink任务的内部数据(计算数据和元数据属性)的快照。
## 为什么需要State
与批计算相比,State是流计算特有的,批计算没有failover机制,要么成功,要么重新计算。流计算在 大多数场景 下是增量计算,数据逐条处理(大多数场景),每次计算是在上一次计算结果之上进行处理的,这样的机制势必要将上一次的计算结果进行存储(生产模式要持久化),另外由于 机器,网络,脏数据等原因导致的程序错误,在重启job时候需要从成功的检查点(checkpoint,后面篇章会专门介绍)进行state的恢复。增量计算,Failover这些机制都需要state的支撑。
## State 实现
Apache Flink内部有四种state的存储实现,具体如下:
* 基于内存的HeapStateBackend - 在debug模式使用,不 建议在生产模式下应用;
* 基于HDFS的FsStateBackend - 分布式文件持久化,每次读写都产生网络IO,整体性能不佳;
* 基于RocksDB的RocksDBStateBackend - 本地文件+异步HDFS持久化;
* 还有一个是基于Niagara(Alibaba内部实现)NiagaraStateBackend - 分布式持久化- 在Alibaba生产环境应用;
## State 持久化逻辑
Apache Flink版本选择用RocksDB+HDFS的方式进行State的存储,State存储分两个阶段,首先本地存储到RocksDB,然后异步的同步到远程的HDFS。 这样而设计既消除了HeapStateBackend的局限(内存大小,机器坏掉丢失等),也减少了纯分布式存储的网络IO开销。
-State.resources/11AD7C2A-A1DD-4238-8226-AED47EF6F446.png)
## State 分类
Apache Flink 内部按照算子和数据分组角度将State划分为如下两类:
* KeyedState - 这里面的key是我们在SQL语句中对应的GroupBy/PartitioneBy里面的字段,key的值就是groupby/PartitionBy字段组成的Row的字节数组,每一个key都有一个属于自己的State,key与key之间的State是不可见的;
* OperatorState - Apache Flink内部的Source Connector的实现中就会用OperatorState来记录source数据读取的offset。
## State 扩容重新分配
Apache Flink是一个大规模并行分布式系统,允许大规模的有状态流处理。 为了可伸缩性,Apache Flink作业在逻辑上被分解成operator graph,并且每个operator的执行被物理地分解成多个并行运算符实例。 从概念上讲,Apache Flink中的每个并行运算符实例都是一个独立的任务,可以在自己的机器上调度到网络连接的其他机器运行。
Apache Flink的DAG图中只有边相连的节点🈶网络通信,也就是整个DAG在垂直方向有网络IO,在水平方向如下图的stateful节点之间没有网络通信,这种模型也保证了每个operator实例维护一份自己的state,并且保存在本地磁盘(远程异步同步)。通过这种设计,任务的所有状态数据都是本地的,并且状态访问不需要任务之间的网络通信。 避免这种流量对于像Apache Flink这样的大规模并行分布式系统的可扩展性至关重要。
如上我们知道Apache Flink中State有OperatorState和KeyedState,那么在进行扩容时候(增加并发)State如何分配呢?比如:外部Source有5个partition,在Apache Flink上面由Srouce的1个并发扩容到2个并发,中间Stateful Operation 节点由2个并发并扩容的3个并发,如下图所示:
-State.resources/60A3963C-B15F-456F-9B37-69C022B6491D.png)
在Apache Flink中对不同类型的State有不同的扩容方法,接下来我们分别介绍。
## OperatorState对扩容的处理
我们选取Apache Flink中某个具体Connector实现实例进行介绍,以MetaQ为例,MetaQ以topic方式订阅数据,每个topic会有N>0个分区,以上图为例,加上我们订阅的MetaQ的topic有5个分区,那么当我们source由1个并发调整为2个并发时候,State是怎么恢复的呢?
state 恢复的方式与Source中OperatorState的存储结构有必然关系,我们先看MetaQSource的实现是如何存储State的。首先MetaQSource 实现了ListCheckpointed<T extends Serializable>,其中的T是Tuple2<InputSplit,Long>,我们在看ListCheckpointed接口的内部定义如下:
```
public interface ListCheckpointed<T extends Serializable>; {
List<T> snapshotState(long var1, long var3) throws Exception;
void restoreState(List<T> var1) throws Exception;
}
```
我们发现 snapshotState方法的返回值是一个List<T>,T是Tuple2<InputSplit,Long>,也就是snapshotState方法返回List<Tuple2<InputSplit,Long>>,这个类型说明state的存储是一个包含partiton和offset信息的列表,InputSplit代表一个分区,Long代表当前partition读取的offset。InputSplit有一个方法如下:
```
public interface InputSplit extends Serializable {
int getSplitNumber();
}
```
也就是说,InputSplit我们可以理解为是一个Partition索引,有了这个数据结构我们在看看上面图所示的case是如何工作的?当Source的并行度是1的时候,所有打partition数据都在同一个线程中读取,所有partition的state也在同一个state中维护,State存储信息格式如下:
-State.resources/736E827E-E4C1-4412-8F0C-F51AECB68329.png)
如果我们现在将并发调整为2,那么我们5个分区的State将会在2个独立的任务(线程)中进行维护,在内部实现中我们有如下算法进行分配每个Task所处理和维护partition的State信息,如下:
```
List<Integer> assignedPartitions = new LinkedList<>();
for (int i = 0; i < partitions; i++) {
if (i % consumerCount == consumerIndex) {
assignedPartitions.add(i);
}
}
```
这个求mod的算法,决定了每个并发所处理和维护partition的State信息,针对我们当前的case具体的存储情况如下:
-State.resources/436E7A11-0256-4373-A3D4-ED5A4363B0E3.png)
那么到现在我们发现上面扩容后State得以很好的分配得益于OperatorState采用了List<T>的数据结构的设计。另外大家注意一个问题,相信大家已经发现上面分配partition的算法有一个限制,那就是Source的扩容(并发数)是否可以超过Source物理存储的partition数量呢?答案是否定的,不能。目前Apache Flink的做法是提前报错,即使不报错也是资源的浪费,因为超过partition数量的并发永远分配不到待管理的partition。
## KeyedState对扩容的处理
对于KeyedState最容易想到的是hash(key) mod parallelism(operator) 方式分配state,就和OperatorState一样,这种分配方式大多数情况是恢复的state不是本地已有的state,需要一次网络拷贝,这种效率比较低,OperatorState采用这种简单的方式进行处理是因为OperatorState的state一般都比较小,网络拉取的成本很小,对于KeyedState往往很大,我们会有更好的选择,在Apache Flink中采用的是Key-Groups方式进行分配。
## 什么是Key-Groups
Key-Groups 是Apache Flink中对keyed state按照key进行分组的方式,每个key-group中会包含N>0个key,一个key-group是State分配的原子单位。在Apache Flink中关于Key-Group的对象是 KeyGroupRange, 如下:
```
public class KeyGroupRange implements KeyGroupsList, Serializable {
...
...
private final int startKeyGroup;
private final int endKeyGroup;
...
...
}
```
KeyGroupRange两个重要的属性就是 startKeyGroup和endKeyGroup,定义了startKeyGroup和endKeyGroup属性后Operator上面的Key-Group的个数也就确定了。
## 什么决定Key-Groups的个数
key-group的数量在job启动前必须是确定的且运行中不能改变。由于key-group是state分配的原子单位,而每个operator并行实例至少包含一个key-group,因此operator的最大并行度不能超过设定的key-group的个数,那么在Apache Flink的内部实现上key-group的数量就是最大并行度的值。
GroupRange.of(0, maxParallelism)如何决定key属于哪个Key-Group
确定好GroupRange之后,如何决定每个Key属于哪个Key-Group呢?我们采取的是取mod的方式,在KeyGroupRangeAssignment中的assignToKeyGroup方法会将key划分到指定的key-group中,如下:
```
public static int assignToKeyGroup(Object key, int maxParallelism) {
return computeKeyGroupForKeyHash(key.hashCode(), maxParallelism);
}
public static int computeKeyGroupForKeyHash(int keyHash, int maxParallelism) {
return HashPartitioner.INSTANCE.partition(keyHash, maxParallelism);
}
@Override
public int partition(T key, int numPartitions) {
return MathUtils.murmurHash(Objects.hashCode(key)) % numPartitions;
}
```
如上实现我们了解到分配Key到指定的key-group的逻辑是利用key的hashCode和maxParallelism进行取余操作来分配的。如下图当parallelism=2,maxParallelism=10的情况下流上key与key-group的对应关系如下图所示:
-State.resources/90D10775-4713-43B6-B7FE-F77334D29212.png)
如上图key(a)的hashCode是97,与最大并发10取余后是7,被分配到了KG-7中,流上每个event都会分配到KG-0至KG-9其中一个Key-Group中。
每个Operator实例如何获取Key-Groups
了解了Key-Groups概念和如何分配每个Key到指定的Key-Groups之后,我们看看如何计算每个Operator实例所处理的Key-Groups。 在KeyGroupRangeAssignment的computeKeyGroupRangeForOperatorIndex方法描述了分配算法:
```
public static KeyGroupRange computeKeyGroupRangeForOperatorIndex(
int maxParallelism,
int parallelism,
int operatorIndex) {
GroupRange splitRange = GroupRange.of(0, maxParallelism).getSplitRange(parallelism, operatorIndex);
int startGroup = splitRange.getStartGroup();
int endGroup = splitRange.getEndGroup();
return new KeyGroupRange(startGroup, endGroup - 1);
}
public GroupRange getSplitRange(int numSplits, int splitIndex) {
...
final int numGroupsPerSplit = getNumGroups() / numSplits;
final int numFatSplits = getNumGroups() % numSplits;
int startGroupForThisSplit;
int endGroupForThisSplit;
if (splitIndex < numFatSplits) {
startGroupForThisSplit = getStartGroup() + splitIndex * (numGroupsPerSplit + 1);
endGroupForThisSplit = startGroupForThisSplit + numGroupsPerSplit + 1;
} else {
startGroupForThisSplit = getStartGroup() + splitIndex * numGroupsPerSplit + numFatSplits;
endGroupForThisSplit = startGroupForThisSplit + numGroupsPerSplit;
}
if (startGroupForThisSplit >= endGroupForThisSplit) {
return GroupRange.emptyGroupRange();
} else {
return new GroupRange(startGroupForThisSplit, endGroupForThisSplit);
}
}
```
上面代码的核心逻辑是先计算每个Operator实例至少分配的Key-Group个数,将不能整除的部分N个,平均分给前N个实例。最终每个Operator实例管理的Key-Groups会在GroupRange中表示,本质是一个区间值;下面我们就上图的case,说明一下如何进行分配以及扩容后如何重新分配。
假设上面的Stateful Operation节点的最大并行度maxParallelism的值是10,也就是我们一共有10个Key-Group,当我们并发是2的时候和并发是3的时候分配的情况如下图:
-State.resources/FBDBA73F-4927-4834-8284-4893707EA6FB.png)
如上算法我们发现在进行扩容时候,大部分state还是落到本地的,如Task0只有KG-4被分出去,其他的还是保持在本地。同时我们也发现,一个job如果修改了maxParallelism的值那么会直接影响到Key-Groups的数量和key的分配,也会打乱所有的Key-Group的分配,目前在Apache Flink系统中统一将maxParallelism的默认值调整到4096,最大程度的避免无法扩容的情况发生。
## 小结
本篇简单介绍了Apache Flink中State的概念,并重点介绍了OperatorState和KeyedState在扩容时候的处理方式。Apache Flink State是支撑Apache Flink中failover,增量计算,Window等重要机制和功能的核心设施。后续介绍failover,增量计算,Window等相关篇章中也会涉及State的利用,当涉及到本篇没有覆盖的内容时候再补充介绍。
================================================
FILE: Flink漫谈系列/Apache-Flink漫谈系列(1)-概述.md
================================================
-概述.resources/49D66A77-779B-468F-9BCB-6846609484DA.png)
摘要:Apache Flink 的命脉 "命脉" 即生命与血脉,常喻极为重要的事物。系列的首篇,首篇的首段不聊Apache Flink的历史,不聊Apache Flink的架构,不聊Apache Flink的功能特性,我们用一句话聊聊什么是 Apache Flink 的命脉?我的答案是:Apache Flink 是以"批是流的特例"的认知进行系统设计的。
"命脉" 即生命与血脉,常喻极为重要的事物。系列的首篇,首篇的首段不聊Apache Flink的历史,不聊Apache Flink的架构,不聊Apache Flink的功能特性,我们用一句话聊聊什么是 Apache Flink 的命脉?我的答案是:Apache Flink 是以"批是流的特例"的认知进行系统设计的。
我们经常听说 "天下武功,唯快不破",大概意思是说 "任何一种武功的招数都是有拆招的,唯有速度快,快到对手根本来不及反应,你就将对手KO了,对手没有机会拆招,所以唯快不破"。 那么这与Apache Flink有什么关系呢?Apache Flink是Native Streaming(纯流式)计算引擎,在实时计算场景最关心的就是"快",也就是 "低延时"。
就目前最热的两种流计算引擎Apache Spark和Apache Flink而言,谁最终会成为No1呢?单从 "低延时" 的角度看,Spark是Micro Batching(微批式)模式,最低延迟Spark能达到0.5~2秒左右,Flink是Native Streaming(纯流式)模式,最低延时能达到微秒。很显然是相对较晚出道的 Apache Flink 后来者居上。 那么为什么Apache Flink能做到如此之 "快"呢?根本原因是Apache Flink 设计之初就认为 "批是流的特例",整个系统是Native Streaming设计,每来一条数据都能够触发计算。相对于需要靠时间来积攒数据Micro Batching模式来说,在架构上就已经占据了绝对优势。
那么为什么关于流计算会有两种计算模式呢?归其根本是因为对流计算的认知不同,是"流是批的特例" 和 "批是流的特例" 两种不同认知产物。
Micro Batching 模式
Micro-Batching 计算模式认为 "流是批的特例", 流计算就是将连续不断的批进行持续计算,如果批足够小那么就有足够小的延时,在一定程度上满足了99%的实时计算场景。那么那1%为啥做不到呢?这就是架构的魅力,在Micro-Batching模式的架构实现上就有一个自然流数据流入系统进行攒批的过程,这在一定程度上就增加了延时。具体如下示意图:
-概述.resources/3BF00033-D856-49C2-A301-E6DB65B22EAE.png)
很显然Micro-Batching模式有其天生的低延时瓶颈,但任何事物的存在都有两面性,在大数据计算的发展历史上,最初Hadoop上的MapReduce就是优秀的批模式计算框架,Micro-Batching在设计和实现上可以借鉴很多成熟实践。
Native Streaming 模式
Native Streaming 计算模式认为 ""批是流的特", 这个认知更贴切流的概念,比如一些监控类的消息流,数据库操作的binlog,实时的支付交易信息等等自然流数据都是一条,一条的流入。Native Streaming 计算模式每条数据的到来都进行计算,这种计算模式显得更自然,并且延时性能达到更低。具体如下示意图:
-概述.resources/68B36353-346D-4E32-9AD8-8AE91F3FE461.png)
很明显Native Streaming模式占据了流计算领域 "低延时" 的核心竞争力,当然Native Streaming模式的实现框架是一个历史先河,第一个实现
Native Streaming模式的流计算框架是第一个吃螃蟹的人,需要面临更多的挑战,后续章节我们会慢慢介绍。当然Native Streaming模式的框架实现上面很容易实现Micro-Batching和Batching模式的计算,Apache Flink就是Native Streaming计算模式的流批统一的计算引擎。
Apache Flink 按不同的需求支持Local,Cluster,Cloud三种部署模式,同时Apache Flink在部署上能够与其他成熟的生态产品进行完美集成,如 Cluster模式下可以利用YARN(Yet Another Resource Negotiator)/Mesos集成进行资源管理,在Cloud部署模式下可以与GCE(Google Compute Engine), EC2(Elastic Compute Cloud)进行集成。
Local 模式
该模式下Apache Flink 整体运行在Single JVM中,在开发学习中使用,同时也可以安装到很多端类设备上。参考
Cluster模式
该模式是典型的投产的集群模式,Apache Flink 既可以Standalone的方式进行部署,也可以与其他资源管理系统进行集成部署,比如与YARN进行集成。Standalone Cluster 参考 YARN Cluster 参考
这种部署模式是典型的Master/Slave模式,我们以Standalone Cluster模式为例示意如下:
-概述.resources/79ACC2B0-BF5E-4DC4-99DE-9C4DF6007A3F.png)
其中JM(JobManager)是Master,TM(TaskManager)是Slave,这种Master/Slave模式有一个典型的问题就是SPOF(single point of failure), SPOF如何解决呢?Apache Flink 又提供了HA(High Availability)方案,也就是提供多个Master,在任何时候总有一个JM服役,N(N>=1)个JM候选,进而解决SPOF问题,示意如下:
-概述.resources/5F99E16C-1172-4E31-989B-DA7C0800D476.png)
在实际的生产环境我们都会配置HA方案,目前Alibaba内部使用的也是基于YARN Cluster的HA方案。
Cloud 模式
该模式主要是与成熟的云产品进行集成,Apache Flink官网介绍了Google的GCE 参考,Amazon的EC2 参考,在Alibaba我们也可以将Apache Flink部署到Alibaba的ECS(Elastic Compute Service)。
什么是容错
容错(Fault Tolerance) 是指容忍故障,在故障发生时能够自动检测出来并使系统能够自动回复正常运行。当出现某些指定的网络故障、硬件故障、软件错误时,系统仍能执行规定的一组程序,或者说程序不会因系统中的故障而中止,并且执行结果也不会因系统故障而引起计算差错。
容错的处理模式
在一个分布式系统中由于单个进程或者节点宕机都有可能导致整个Job失败,那么容错机制除了要保证在遇到非预期情况系统能够"运行"外,还要求能"正确运行",也就是数据能按预期的处理方式进行处理,保证计算结果的正确性。计算结果的正确性取决于系统对每一条计算数据处理机制,一般有如下三种处理机制:
At Most Once:最多消费一次,这种处理机制会存在数据丢失的可能。
At Least Once:最少消费一次,这种处理机制数据不会丢失,但是有可能重复消费。
Exactly Once:精确一次,无论何种情况下,数据都只会消费一次,这种机制是对数据准确性的最高要求,在金融支付,银行账务等领域必须采用这种模式。
Apache Flink的容错机制
Apache Flink的Job会涉及到3个部分,外部数据源(External Input), Flink内部数据处理(Flink Data Flow)和外部输出(External Output)。如下示意图:
-概述.resources/C810C9BC-45F5-4ADA-8408-7FF57C0C31DB.png)
目前Apache Flink 支持两种数据容错机制:
* At Least Once
* Exactly Once
其中 Exactly Once 是最严格的容错机制,该模式要求每条数据必须处理且仅处理一次。那么对于这种严格容错机制,一个完整的Flink Job容错要做到 End-to-End 的 容错必须结合三个部分进行联合处理,根据上图我们考虑三个场景:
系统内部容错
Apache Flink利用Checkpointing机制来处理容错,Checkpointing的理论基础 Stephan 在 Lightweight Asynchronous Snapshots for Distributed Dataflows 进行了细节描述,该机制源于有K. MANI CHANDY和LESLIE LAMPORT 发表的 Determining-Global-States-of-a-Distributed-System Paper。Apache Flink 基于Checkpointing机制对Flink Data Flow实现了At Least Once 和 Exactly Once 两种容错处理模式。
Apache Flink Checkpointing的内部实现会利用 Barriers,StateBackend等后续章节会详细介绍的技术来将数据的处理进行Marker。Apache Flink会利用Barrier将整个流进行标记切分,如下示意图:
-概述.resources/72919AF2-F467-4306-BE2B-042FA56E4DD4.png)
这样Apache Flink的每个Operator都会记录当前成功处理的Checkpoint,如果发生错误,就会从上一个成功的Checkpoint开始继续处理后续数据。比如 Soruce Operator会将读取外部数据源的Position实时的记录到Checkpoint中,失败时候会从Checkpoint中读取成功的position继续精准的消费数据。每个算子会在Checkpoint中记录自己恢复时候必须的数据,比如流的原始数据和中间计算结果等信息,在恢复的时候从Checkpoint中读取并持续处理流数据。
外部Source容错
Apache Flink 要做到 End-to-End 的 Exactly Once 需要外部Source的支持,比如上面我们说过 Apache Flink的Checkpointing机制会在Source节点记录读取的Position,那就需要外部数据提供读取的Position和支持根据Position进行数据读取。
外部Sink容错
Apache Flink 要做到 End-to-End 的 Exactly Once 相对比较困难,如上场景三所述,当Sink Operator节点宕机,重新恢复时候根据Apache Flink 内部系统容错 exactly once的保证,系统会回滚到上次成功的Checkpoin继续写入,但是上次成功Checkpoint之后当前Checkpoint未完成之前已经把一部分新数据写入到kafka了. Apache Flink自上次成功的Checkpoint继续写入kafka,就造成了kafka再次接收到一份同样的来自Sink Operator的数据,进而破坏了End-to-End 的 Exactly Once 语义(重复写入就变成了At Least Once了),如果要解决这一问题,Apache Flink 利用Two phase commit(两阶段提交)的方式来进行处理。本质上是Sink Operator 需要感知整体Checkpoint的完成,并在整体Checkpoint完成时候将计算结果写入Kafka。
批与流是两种不同的数据处理模式,如Apache Storm只支持流模式的数据处理,Apache Spark只支持批(Micro Batching)模式的数据处理。那么Apache Flink 是如何做到既支持流处理模式也支持批处理模式呢?
统一的数据传输层
开篇我们就介绍Apache Flink 的 "命脉"是以"批是流的特例"为导向来进行引擎的设计的,系统设计成为 "Native Streaming"的模式进行数据处理。那么Apache FLink将批模式执行的任务看做是流式处理任务的特殊情况,只是在数据上批是有界的(有限数量的元素)。
Apache Flink 在网络传输层面有两种数据传输模式:
* PIPELINED模式 - 即一条数据被处理完成以后,立刻传输到下一个节点进行处理。
* BATCH 模式 - 即一条数据被处理完成后,并不会立刻传输到下一个节点进行处理,而是写入到缓存区,如果缓存写满就持久化到本地硬盘上,最后当所有数据都被处理完成后,才将数据传输到下一个节点进行处理。
对于批任务而言同样可以利用PIPELINED模式,比如我要做count统计,利用PIPELINED模式能拿到更好的执行性能。只有在特殊情况,比如SortMergeJoin,这时候我们需要全局数据排序,才需要BATCH模式。大部分情况流与批可用统一的传输策略,只有特殊情况,才将批看做是流的一个特例继续特殊处理。
统一任务调度层
Apache Flink 在任务调度上流与批共享统一的资源和任务调度机制(后续章节会详细介绍)。
统一的用户API层
Apache Flink 在DataStremAPI和DataSetAPI基础上,为用户提供了流批统一的上层TableAPI和SQL,在语法和语义上流批进行高度统一。(其中DataStremAPI和DataSetAPI对流和批进行了分别抽象,这一点并不优雅,在Alibaba内部对其进行了统一抽象)。
求同存异
Apache Flink 是流批统一的计算引擎,并不意味着流与批的任务都走统一的code path,在对底层的具体算子的实现也是有各自的处理的,在具体功能上面会根据不同的特性区别处理。比如 批没有Checkpoint机制,流上不能做SortMergeJoin。
组件栈
我们上面内容已经介绍了很多Apache Flink的各种组件,下面我们整体概览一下全貌,如下:
-概述.resources/624AD4C0-351D-42C0-ACEA-E30851223B5F.png)
TableAPI和SQL都建立在DataSetAPI和DataStreamAPI的基础之上,那么TableAPI和SQL是如何转换为DataStream和DataSet的呢?
TableAPI&SQL到DataStrem&DataSet的架构
TableAPI&SQL最终会经过Calcite优化之后转换为DataStream和DataSet,具体转换示意如下:
-概述.resources/DC074C98-3D57-4997-AAF6-896BEF272F84.png)
对于流任务最终会转换成DataStream,对于批任务最终会转换成DataSet。
ANSI-SQL的支持
Apache Flink 之所以利用ANSI-SQL作为用户统一的开发语言,是因为SQL有着非常明显的优点,如下:
-概述.resources/7C067E43-57A2-4A83-BE9C-3C769DDFF6C6.png)
Declarative - 用户只需要表达我想要什么,不用关心如何计算。
Optimized - 查询优化器可以为用户的 SQL 生成最优的执行计划,获取最好的查询性能。
Understandable - SQL语言被不同领域的人所熟知,用SQL 作为跨团队的开发语言可以很大地提高效率。
Stable - SQL 是一个拥有几十年历史的语言,是一个非常稳定的语言,很少有变动。
Unify - Apache Flink在引擎上对流与批进行统一,同时又利用ANSI-SQL在语法和语义层面进行统一。
无限扩展的优化机制
Apache Flink 利用Apache Calcite对SQL进行解析和优化,Apache Calcite采用Calcite是开源的一套查询引擎,实现了两套Planner:
HepPlanner - 是RBO(Rule Base Optimize)模式,基于规则的优化。
VolcanoPlanner - 是CBO(Cost Base Optimize)模式,基于成本的优化。
Flink SQL会利用Calcite解析优化之后,最终转换为底层的DataStrem和Dataset。上图中 Batch rules和Stream rules可以根据优化需要无限添加优化规则。
Apache Flink 优秀的架构就像一座摩天大厦的地基一样为Apache Flink 持久的生命力打下了良好的基础,为打造Apache Flink丰富的功能生态留下无限的空间。
类库
CEP - 复杂事件处理类库,核心是一个状态机,广泛应用于事件驱动的监控预警类业务场景。
ML - 机器学习类库,机器学习主要是识别数据中的关系、趋势和模式,一般应用在预测类业务场景。
GELLY - 图计算类库,图计算更多的是考虑边和点的概念,一般被用来解决网状关系的业务场景。
算子
Apache Flink 提供了丰富的功能算子,对于数据流的处理来讲,可以分为单流处理(一个数据源)和多流处理(多个数据源)。
多流操作
如上通过UION和JOIN我们可以将多流最终变成单流,Apache Flink 在单流上提供了更多的操作算子。
单流操作
将多流变成单流之后,我们按数据输入输出的不同归类如下:
类型 输入 输出 Table/SQL算子 DataStream/DataSet算子
Scalar Function 1 1 Built-in & UDF, Map
Table Function 1 N(N>=0) Built-in & UDTF FlatMap
Aggregate Function N(N>=0) 1 Built-in & UDAF Reduce
如上表格对单流上面操作做简单归类,除此之外还可以做 过滤,排序,窗口等操作,我们后续章节会逐一介绍。
存在的问题
Apache Flink 目前的架构还存在很大的优化空间,比如前面提到的DataStreamAPI和DataSetAPI其实是流与批在API层面不统一的体现,同时看具体实现会发现DataStreamAPI会生成Transformation tree然后生成StreamGraph,最后生成JobGraph,底层对应StreamTask,但DataSetAPI会形成Operator tree,flink-optimize模块会对Batch Plan进行优化,形成Optimized Plan 后形成JobGraph,最后形成BatchTask。具体示意如下:
-概述.resources/1DEA43A8-381D-451C-9B99-9106B2B058B5.png)
这种情况其实 DataStreamAPI到Runtime 和 DataSetAPI到Runtime的实现上并没有得到最大程度的统一和复用。在这一点上面Aalibab 企业版的Flink在架构和实现上都进行了进一步优化。
组件栈
Alibaba 对Apache Flink进行了大量的架构优化,如下架构是一直努力的方向,大部分功能还在持续开发中,具体如下:
-概述.resources/928F452D-B175-4740-A139-8186EEDAC99C.png)
如上架构我们发现较大的变化是:
QP/QE/QO - 我们增加了QP/QE/QO层,在这一层进行统一的流和批的查询优化和底层算子的转换。
DAG API - 我们在Runtime层面统一抽象API接口,在API层对流与批进行统一。
TableAPI&SQL到Runtime的架构
Apache Flink执行层是流批统一的设计,在API和算子设计上面我们尽量达到流批的共享,在TableAPI和SQL层无论是流任务还是批任务最终都转换为统一的底层实现。这个层面最核心的变化是批最终也会生成StreamGraph,执行层运行Stream Task,如下:
-概述.resources/61F69F73-CEE1-43F4-BB88-177A115FC62E.png)
本篇概要的介绍了"批是流的特例"这一设计观点是Apache Flink的"命脉",它决定了Apache Flink的运行模式是纯流式的,这在实时计算场景的"低延迟"需求上,相对于Micro Batching模式占据了架构的绝对优势,同时概要的向大家介绍了Apache Flink的部署模式,容错处理,引擎的统一性和Apache Flink的架构,最后和大家分享了Apache Flink的优化架构。
本篇没有对具体技术进行详细展开,大家只要对Apache Flink有初步感知,头脑中知道Alibaba对Apache Flink进行了架构优化,增加了众多功能就可以了,至于Apache Flink的具体技术细节和实现原理,以及Alibaba对Apache Flink做了哪些架构优化和增加了哪些功能后续章节会展开介绍!
================================================
FILE: Flink漫谈系列/我的Markdown笔记/Apache-Flink-漫谈系列(03)-State.md
================================================
## 实际问题
在流计算场景中,数据会源源不断的流入Apache Flink系统,每条数据进入Apache Flink系统都会触发计算。如果我们想进行一个Count聚合计算,那么每次触发计算是将历史上所有流入的数据重新新计算一次,还是每次计算都是在上一次计算结果之上进行增量计算呢?答案是肯定的,Apache Flink是基于上一次的计算结果进行增量计算的。那么问题来了: "上一次的计算结果保存在哪里,保存在内存可以吗?",答案是否定的,如果保存在内存,在由于网络,硬件等原因造成某个计算节点失败的情况下,上一次计算结果会丢失,在节点恢复的时候,就需要将历史上所有数据(可能十几天,上百天的数据)重新计算一次,所以为了避免这种灾难性的问题发生,Apache Flink 会利用State存储计算结果。本篇将会为大家介绍Apache Flink State的相关内容。
## 什么是State
这个问题似乎有些"弱智"?不管问题的答案是否显而易见,但我还是想简单说一下在Apache Flink里面什么是State?State是指流计算过程中计算节点的中间计算结果或元数据属性,比如 在aggregation过程中要在state中记录中间聚合结果,比如 Apache Kafka 作为数据源时候,我们也要记录已经读取记录的offset,这些State数据在计算过程中会进行持久化(插入或更新)。所以Apache Flink中的State就是与时间相关的,Apache Flink任务的内部数据(计算数据和元数据属性)的快照。
## 为什么需要State
与批计算相比,State是流计算特有的,批计算没有failover机制,要么成功,要么重新计算。流计算在 大多数场景 下是增量计算,数据逐条处理(大多数场景),每次计算是在上一次计算结果之上进行处理的,这样的机制势必要将上一次的计算结果进行存储(生产模式要持久化),另外由于 机器,网络,脏数据等原因导致的程序错误,在重启job时候需要从成功的检查点(checkpoint,后面篇章会专门介绍)进行state的恢复。增量计算,Failover这些机制都需要state的支撑。
## State 实现
Apache Flink内部有四种state的存储实现,具体如下:
* 基于内存的HeapStateBackend - 在debug模式使用,不 建议在生产模式下应用;
* 基于HDFS的FsStateBackend - 分布式文件持久化,每次读写都产生网络IO,整体性能不佳;
* 基于RocksDB的RocksDBStateBackend - 本地文件+异步HDFS持久化;
* 还有一个是基于Niagara(Alibaba内部实现)NiagaraStateBackend - 分布式持久化- 在Alibaba生产环境应用;
## State 持久化逻辑
Apache Flink版本选择用RocksDB+HDFS的方式进行State的存储,State存储分两个阶段,首先本地存储到RocksDB,然后异步的同步到远程的HDFS。 这样而设计既消除了HeapStateBackend的局限(内存大小,机器坏掉丢失等),也减少了纯分布式存储的网络IO开销。
-State.resources/11AD7C2A-A1DD-4238-8226-AED47EF6F446.png)
## State 分类
Apache Flink 内部按照算子和数据分组角度将State划分为如下两类:
* KeyedState - 这里面的key是我们在SQL语句中对应的GroupBy/PartitioneBy里面的字段,key的值就是groupby/PartitionBy字段组成的Row的字节数组,每一个key都有一个属于自己的State,key与key之间的State是不可见的;
* OperatorState - Apache Flink内部的Source Connector的实现中就会用OperatorState来记录source数据读取的offset。
## State 扩容重新分配
Apache Flink是一个大规模并行分布式系统,允许大规模的有状态流处理。 为了可伸缩性,Apache Flink作业在逻辑上被分解成operator graph,并且每个operator的执行被物理地分解成多个并行运算符实例。 从概念上讲,Apache Flink中的每个并行运算符实例都是一个独立的任务,可以在自己的机器上调度到网络连接的其他机器运行。
Apache Flink的DAG图中只有边相连的节点🈶网络通信,也就是整个DAG在垂直方向有网络IO,在水平方向如下图的stateful节点之间没有网络通信,这种模型也保证了每个operator实例维护一份自己的state,并且保存在本地磁盘(远程异步同步)。通过这种设计,任务的所有状态数据都是本地的,并且状态访问不需要任务之间的网络通信。 避免这种流量对于像Apache Flink这样的大规模并行分布式系统的可扩展性至关重要。
如上我们知道Apache Flink中State有OperatorState和KeyedState,那么在进行扩容时候(增加并发)State如何分配呢?比如:外部Source有5个partition,在Apache Flink上面由Srouce的1个并发扩容到2个并发,中间Stateful Operation 节点由2个并发并扩容的3个并发,如下图所示:
-State.resources/60A3963C-B15F-456F-9B37-69C022B6491D.png)
在Apache Flink中对不同类型的State有不同的扩容方法,接下来我们分别介绍。
## OperatorState对扩容的处理
我们选取Apache Flink中某个具体Connector实现实例进行介绍,以MetaQ为例,MetaQ以topic方式订阅数据,每个topic会有N>0个分区,以上图为例,加上我们订阅的MetaQ的topic有5个分区,那么当我们source由1个并发调整为2个并发时候,State是怎么恢复的呢?
state 恢复的方式与Source中OperatorState的存储结构有必然关系,我们先看MetaQSource的实现是如何存储State的。首先MetaQSource 实现了ListCheckpointed<T extends Serializable>,其中的T是Tuple2<InputSplit,Long>,我们在看ListCheckpointed接口的内部定义如下:
```
public interface ListCheckpointed<T extends Serializable>; {
List<T> snapshotState(long var1, long var3) throws Exception;
void restoreState(List<T> var1) throws Exception;
}
```
我们发现 snapshotState方法的返回值是一个List<T>,T是Tuple2<InputSplit,Long>,也就是snapshotState方法返回List<Tuple2<InputSplit,Long>>,这个类型说明state的存储是一个包含partiton和offset信息的列表,InputSplit代表一个分区,Long代表当前partition读取的offset。InputSplit有一个方法如下:
```
public interface InputSplit extends Serializable {
int getSplitNumber();
}
```
也就是说,InputSplit我们可以理解为是一个Partition索引,有了这个数据结构我们在看看上面图所示的case是如何工作的?当Source的并行度是1的时候,所有打partition数据都在同一个线程中读取,所有partition的state也在同一个state中维护,State存储信息格式如下:
-State.resources/736E827E-E4C1-4412-8F0C-F51AECB68329.png)
如果我们现在将并发调整为2,那么我们5个分区的State将会在2个独立的任务(线程)中进行维护,在内部实现中我们有如下算法进行分配每个Task所处理和维护partition的State信息,如下:
```
List<Integer> assignedPartitions = new LinkedList<>();
for (int i = 0; i < partitions; i++) {
if (i % consumerCount == consumerIndex) {
assignedPartitions.add(i);
}
}
```
这个求mod的算法,决定了每个并发所处理和维护partition的State信息,针对我们当前的case具体的存储情况如下:
-State.resources/436E7A11-0256-4373-A3D4-ED5A4363B0E3.png)
那么到现在我们发现上面扩容后State得以很好的分配得益于OperatorState采用了List<T>的数据结构的设计。另外大家注意一个问题,相信大家已经发现上面分配partition的算法有一个限制,那就是Source的扩容(并发数)是否可以超过Source物理存储的partition数量呢?答案是否定的,不能。目前Apache Flink的做法是提前报错,即使不报错也是资源的浪费,因为超过partition数量的并发永远分配不到待管理的partition。
## KeyedState对扩容的处理
对于KeyedState最容易想到的是hash(key) mod parallelism(operator) 方式分配state,就和OperatorState一样,这种分配方式大多数情况是恢复的state不是本地已有的state,需要一次网络拷贝,这种效率比较低,OperatorState采用这种简单的方式进行处理是因为OperatorState的state一般都比较小,网络拉取的成本很小,对于KeyedState往往很大,我们会有更好的选择,在Apache Flink中采用的是Key-Groups方式进行分配。
## 什么是Key-Groups
Key-Groups 是Apache Flink中对keyed state按照key进行分组的方式,每个key-group中会包含N>0个key,一个key-group是State分配的原子单位。在Apache Flink中关于Key-Group的对象是 KeyGroupRange, 如下:
```
public class KeyGroupRange implements KeyGroupsList, Serializable {
...
...
private final int startKeyGroup;
private final int endKeyGroup;
...
...
}
```
KeyGroupRange两个重要的属性就是 startKeyGroup和endKeyGroup,定义了startKeyGroup和endKeyGroup属性后Operator上面的Key-Group的个数也就确定了。
## 什么决定Key-Groups的个数
key-group的数量在job启动前必须是确定的且运行中不能改变。由于key-group是state分配的原子单位,而每个operator并行实例至少包含一个key-group,因此operator的最大并行度不能超过设定的key-group的个数,那么在Apache Flink的内部实现上key-group的数量就是最大并行度的值。
GroupRange.of(0, maxParallelism)如何决定key属于哪个Key-Group
确定好GroupRange之后,如何决定每个Key属于哪个Key-Group呢?我们采取的是取mod的方式,在KeyGroupRangeAssignment中的assignToKeyGroup方法会将key划分到指定的key-group中,如下:
```
public static int assignToKeyGroup(Object key, int maxParallelism) {
return computeKeyGroupForKeyHash(key.hashCode(), maxParallelism);
}
public static int computeKeyGroupForKeyHash(int keyHash, int maxParallelism) {
return HashPartitioner.INSTANCE.partition(keyHash, maxParallelism);
}
@Override
public int partition(T key, int numPartitions) {
return MathUtils.murmurHash(Objects.hashCode(key)) % numPartitions;
}
```
如上实现我们了解到分配Key到指定的key-group的逻辑是利用key的hashCode和maxParallelism进行取余操作来分配的。如下图当parallelism=2,maxParallelism=10的情况下流上key与key-group的对应关系如下图所示:
-State.resources/90D10775-4713-43B6-B7FE-F77334D29212.png)
如上图key(a)的hashCode是97,与最大并发10取余后是7,被分配到了KG-7中,流上每个event都会分配到KG-0至KG-9其中一个Key-Group中。
每个Operator实例如何获取Key-Groups
了解了Key-Groups概念和如何分配每个Key到指定的Key-Groups之后,我们看看如何计算每个Operator实例所处理的Key-Groups。 在KeyGroupRangeAssignment的computeKeyGroupRangeForOperatorIndex方法描述了分配算法:
```
public static KeyGroupRange computeKeyGroupRangeForOperatorIndex(
int maxParallelism,
int parallelism,
int operatorIndex) {
GroupRange splitRange = GroupRange.of(0, maxParallelism).getSplitRange(parallelism, operatorIndex);
int startGroup = splitRange.getStartGroup();
int endGroup = splitRange.getEndGroup();
return new KeyGroupRange(startGroup, endGroup - 1);
}
public GroupRange getSplitRange(int numSplits, int splitIndex) {
...
final int numGroupsPerSplit = getNumGroups() / numSplits;
final int numFatSplits = getNumGroups() % numSplits;
int startGroupForThisSplit;
int endGroupForThisSplit;
if (splitIndex < numFatSplits) {
startGroupForThisSplit = getStartGroup() + splitIndex * (numGroupsPerSplit + 1);
endGroupForThisSplit = startGroupForThisSplit + numGroupsPerSplit + 1;
} else {
startGroupForThisSplit = getStartGroup() + splitIndex * numGroupsPerSplit + numFatSplits;
endGroupForThisSplit = startGroupForThisSplit + numGroupsPerSplit;
}
if (startGroupForThisSplit >= endGroupForThisSplit) {
return GroupRange.emptyGroupRange();
} else {
return new GroupRange(startGroupForThisSplit, endGroupForThisSplit);
}
}
```
上面代码的核心逻辑是先计算每个Operator实例至少分配的Key-Group个数,将不能整除的部分N个,平均分给前N个实例。最终每个Operator实例管理的Key-Groups会在GroupRange中表示,本质是一个区间值;下面我们就上图的case,说明一下如何进行分配以及扩容后如何重新分配。
假设上面的Stateful Operation节点的最大并行度maxParallelism的值是10,也就是我们一共有10个Key-Group,当我们并发是2的时候和并发是3的时候分配的情况如下图:
-State.resources/FBDBA73F-4927-4834-8284-4893707EA6FB.png)
如上算法我们发现在进行扩容时候,大部分state还是落到本地的,如Task0只有KG-4被分出去,其他的还是保持在本地。同时我们也发现,一个job如果修改了maxParallelism的值那么会直接影响到Key-Groups的数量和key的分配,也会打乱所有的Key-Group的分配,目前在Apache Flink系统中统一将maxParallelism的默认值调整到4096,最大程度的避免无法扩容的情况发生。
## 小结
本篇简单介绍了Apache Flink中State的概念,并重点介绍了OperatorState和KeyedState在扩容时候的处理方式。Apache Flink State是支撑Apache Flink中failover,增量计算,Window等重要机制和功能的核心设施。后续介绍failover,增量计算,Window等相关篇章中也会涉及State的利用,当涉及到本篇没有覆盖的内容时候再补充介绍。
================================================
FILE: Hadoop/Hadoop极简入门.md
================================================
其实Hadoop诞生至今已经十多年了,网络上也充斥着关于Hadoop相关知识的海量资源。但是,有时还是会使刚刚接触大数据领域的童鞋分不清hadoop、hdfs、Yarn和MapReduce等等技术词汇。
Hadoop是ASF(Apache软件基金会)开源的,根据Google开源的三篇大数据论文设计的,一个能够允许大量数据在计算机集群中,通过使用简单的编程模型进行分布式处理的框架。其设计的规模可从单一的服务器到数千台服务器,每一个均可提供局部运算和存储功能。Hadoop并不依赖昂贵的硬件以支持高可用性。Hadoop可以检测并处理应用层上的错误,并可以把错误转移到其他服务器上(让它错误,我在用别的服务器顶上就可以了),所以Hadoop提供一个基于计算机集群的、高效性的服务。
经过十年的发展,Hadoop这个名词的本身也在不断进化者,目前我们提到Hadoop大多是指大数据的生态圈,这个生态圈包括众多的软件技术(e.g. HBase、Hive和Spark等等)。
有如Spring框架有着最基础的几个模块Context、Bean和Core,其他的模块和项目都是基于这些基础模块构建。Hadoop与之一样,也有最基础的几个模块。
**Common**: 支持其他模块的公用工具包。
**HDFS**: 一个可高吞吐访问应用数据的分布式文件系统。
**YARN**: 一个管理集群服务器资源和任务调度的框架。
**MapReduce**: 基于Yarn对大数据集进行并行计算的系统。
其它的,像HBase、Hive等等不过在这几个基础模块上的高级抽象。另外Hadoop也不是目前大数据的唯一解决方案,像Amazon的大数据技术方案等等。
Common
Common模块是Hadoop最为基础的模块,他为其他模块提供了像操作文件系统、I/O、序列化和远程方法调用等最为基础的实现。如果想深入的了解Hadoop的具体实现,可以阅读一下Common的源码。
## HDFS
HDFS是“Hadoop Distributed File System”的首字母缩写,是一种设计运行在一般硬件条件(不需要一定是服务器级别的设备,但更好的设备能发挥更大的作用)下的分布式文件系统. 他和现有的其他分布式文件系统(e.g. RAID)有很多相似的地方。和其他分布式文件系统的不同之处是HDFS设计为运行在低成本的硬件上(e.g. 普通的PC机),且提供高可靠性的服务器. HDFS设计满足大数据量,大吞吐量的应用情况。
为了更好的理解分布式文件系统,我们先从文件讲起。
### 文件
文件这个词,恐怕只要是现代人都不会陌生。但是在不同行业中,文件有着不同的意义。在计算机科学领域,文件是什么呢?文件是可以在目录中看的见的图标么?当然不是。文件在存储设备时,是个N长的字节序列。而在一个计算机使用者的角度而言,文件是对所有I/O设备的抽象。每个I/O设备都可以视为文件,包括磁盘、键盘和网络等。文件这个简单而精致的概念其内涵是十分丰富的,它向应用程序提供了一个统一的视角,来看待系统中可能含有的各式各样的I/O设备。
### 文件系统
那么一台计算机上肯定不止一个文件,成千上万的文件怎么管理呢?因此需要我们需要一种对文件进行管理的东西,即文件系统。文件系统是一种在计算机上存储和组织数据的方法,它使得对其访问和查找变得容易,文件系统使用文件和树形目录的抽象逻辑概念代替了硬盘和光盘等物理设备使用数据块的概念,用户使用文件系统来保存数据而不必关心数据实际保存在硬盘的地址为多少的数据块上,只需要记住这个文件的所属目录和文件名。在写入新数据之前,用户不必关心硬盘上的那个块地址没有被使用,硬盘上的存储空间管理(分配和释放)功能由文件系统自动完成,用户只需要记住数据被写入到了哪个文件中即可。
### 分布式文件系统
相对于单机的文件系统而言,分布式文件系统(Distributed file system)。是一种允许文件通过网络在多台主机上分享的文件系统,可让多计算机上的多用户分享文件和存储空间。
在这样的文件系统中,客户端并非直接访问底层的数据存储区块和磁盘。而是通过网络,基于单机文件系统并借由特定的通信协议的帮助,来实现对于文件系统的读写。
分布式文件系统需要拥有的最基本的能力是通过畅通网络I/O来实现数据的复制与容错。也就是说,一方面一个文件是分为多个数据块分布在多个设备中。另一方面,数据块有多个副本分布在不同的设备上。即使有一小部分的设备出现离线和宕机等情况,整体来说文件系统仍然可以持续运作而不会有数据损失。
注意:分布式文件系统和分布式数据存储的界线是模糊的,但一般来说,分布式文件系统是被设计用在局域网,比较强调的是传统文件系统概念的延伸,并通过软件方法来达成容错的目的。而分布式数据存储,则是泛指应用分布式运算技术的文件和数据库等提供数据存储服务的系统。
### HDFS
HDFS正是Hadoop中负责分布式文件系统的。HDFS采用master/slave架构。一个HDFS集群是由一个Namenode和一定数目的Datanodes组成。Namenode是一个中心服务器,负责管理文件系统的命名空间以及文件的访问控制。集群中的Datanode一般是一个设备上部署一个,负责管理它所在节点上的存储。HDFS暴露了文件系统的命名空间,用户能够以文件的形式在上面存储数据。实际上,一个文件会被分成一个或多个数据块,这些块存储在一组Datanode上。Namenode执行文件系统的命名空间操作,比如打开、关闭、重命名文件或目录。它也负责确定数据块到具体Datanode设备的映射。Datanode负责处理文件系统客户端的读写请求。在Namenode的统一调度下进行数据块的创建、删除和复制。为了保证文件系统的高可靠,往往需要另一个Standby的Namenode在Actived Namenode出现问题后,立刻接管文件系统。
网络上有很多关于hdfs的安装配置手册,本文就不再复述。只提供一个以前项目中应用过的部署架构仅供大家参考。

这个高可用的HDFS架构是由3台zookeeper设备、2台域名服务(DNS)和时间服务(NTP)设备、2台Namenode设备(如果必要Standby可以更多)、一个共享存储设备(NFS)和N个DataNode组成。
Zookeeper负责接受NameNode的心跳,当Actived namenode不向zookeeper报告心跳时,Standby Namenode的监控进程会收到这个消息,从而激活Standby NameNode并接管Active NameNode的工作。
NFS负责为2个NameNode存储EditLog文件,(NameNode 在执行 HDFS 客户端提交的创建文件或者移动文件这样的写操作时,会首先把这些操作记录在 EditLog 文件之中,然后再更新内存中的文件系统镜像,最终再刷新到磁盘。 EditLog 只是在数据恢复的时候起作用。记录在 EditLog 之中的每一个操作又称为一个事务,每个事务有一个整数形式的事务 id 作为编号。EditLog 会被切割为很多段,每一段称为一个 Segment)当发生NameNode切换的情况时,Standby NameNode接管后,会根据EditLog中把未完成的写操作继续下去并开使向EditLog写入新的写操作记录。(此外,hadoop还提供了另一种QJM的EditLog方案)
DNS&NTP分布负责整个系统的(包括客户端)域名服务和时间服务。这个在集群部署中是非常有必要的两个存在。首先说一下DNS的必要性,一、Hadoop是极力提倡用机器名作为在HDFS环境中的标识。二、当然可以在/etc/hosts文件中去标明机器名和IP的映射关系,可是请想想如果在一个数千台设备的集群中添加一个设备时,负责系统维护的伙伴会不会骂集群的设计者呢?其次是NTP的必要性,在刚刚开始接触Hadoop集群时我遇到的大概90%的问题是由于各个设备时间不一致导致的。各个设备的时间同步是数据一致性和管理一致性的一个基本保障。
### MapReduce
MapReduce是一个使用简单的软件框架,基于它写出来的应用程序能够运行在由上千个商用机器组成的大型集群上,并以一种可靠容错的方式并行处理上T级别的数据集。
一个MapReduce 作业(job)通常会把输入的数据集切分为若干独立的数据块,由 map任务(task)以完全并行的方式处理它们。框架会对map的输出先进行排序, 然后把结果输入给reduce任务。通常作业的输入和输出都会被存储在文件系统中。 整个框架负责任务的调度和监控,以及重新执行已经失败的任务。
通常,MapReduce框架和HDFS是运行在一相同的设备集群上的,也就是说,计算设备和存储设备通常在一起。这种配置允许框架在那些已经存好数据的设备上高效地调度任务,这可以使整个集群的网络带宽被非常高效地利用。
MapReduce框架由一个单独的master JobTracker 和每个集群设备一个slave TaskTracker共同组成。master负责调度构成一个作业的所有任务,这些任务分布在不同的slave上,master监控它们的执行,重新执行已经失败的任务。而slave仅负责执行由master指派的任务。
用户编写的MapReduce应用程序应该指明输入/输出的文件位置(路径),并通过实现合适的接口或抽象类提供map和reduce函数。再加上其他作业的参数,就构成了作业配置(job configuration)。然后,job client提交作业(jar包/可执行程序等)和配置信息给JobTracker,后者负责分发这些软件和配置信息给slave、调度任务并监控它们的执行,同时提供状态和诊断信息给job-client。

在抽象的层面上MapReduce是由两个函数Map和Reduce组成的。简单来说,一个Map函数就是对一些独立元素组成的概念上的列表的每一个元素进行指定的操作。事实上,每个元素都是被独立操作的,而原始列表没有被更改,因为这里创建了一个新的列表来保存操作结果。这就是说,Map操作是可以高度并行的。而Reduce函数指的是对Map函数的结果(中间经过洗牌的过程,会把map的结果进行分组)分组后多个列表的元素进行适当的归并。
注意:虽然Hadoop框架是用JavaTM实现的,但MapReduce应用程序则不一定要用 Java来写 。至少Scala是可以写的哟。
附上Scala实现的计算词频的Scala源码
```
import java.io.IOException
import java.util.StringTokenizer
import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.fs.Path
import org.apache.hadoop.io.{IntWritable, Text}
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat
import org.apache.hadoop.mapreduce.{Job, Mapper, Reducer}
import scala.collection.JavaConversions
object WordCount {
def main(args: Array[String]): Unit = {
val job = new Job(new Configuration(), "WordCount")
job.setJarByClass(classOf[WordMapper]);
job.setMapperClass(classOf[WordMapper]);
job.setCombinerClass(classOf[WordReducer]);
job.setReducerClass(classOf[WordReducer]);
job.setOutputKeyClass(classOf[Text]);
job.setOutputValueClass(classOf[IntWritable]);
job.setNumReduceTasks(1)
FileInputFormat.addInputPath(job, new Path(args(0)));
FileOutputFormat.setOutputPath(job, new Path(args(1)));
System.exit(job.waitForCompletion(true) match { case true => 0
case false => 1
});
}
}
class WordMapper extends Mapper[Object, Text, Text, IntWritable] {
val one = new IntWritable(1)
@throws[IOException]
@throws[InterruptedException]
override def map(key: Object, value: Text, context: Mapper[Object, Text, Text, IntWritable]#Context) = {
val stringTokenizer = new StringTokenizer(value.toString());
while (stringTokenizer.hasMoreTokens()) {
context.write(new Text(stringTokenizer.nextToken()), one);
}
}
}
class WordReducer extends Reducer[Text, IntWritable, Text, IntWritable] {
@throws[IOException]
@throws[InterruptedException]
override def reduce(key: Text, values: java.lang.Iterable[IntWritable], context: Reducer[Text, IntWritable, Text, IntWritable]#Context) = {
import JavaConversions.iterableAsScalaIterable
context.write(key, new IntWritable(values.map(x=>x.get()).reduce(_+_)));
}
}
```
### Yarn
YARN(Yet Another Resource Negotiator)是Hadoop的设备资源管理器,它是一个通用资源管理系统,MapReduce和其他上层应用提供统一的资源管理和调度,它为集群在利用率、资源统一管理和数据共享等方面提供了巨大的帮助。
Yarn由ResourceManager、NodeManager、ApplicationMaster和Containe四个概念构成。

ResourceManager是一个全局的资源管理器,负责整个系统的资源管理和分配。它主要由两个组件构成:调度器(Scheduler)和应用程序管理器(Applications Manager)。调度器根据容量、队列等限制条件,将系统中的资源分配给各个正在运行的MapReduce程序。应用程序管理器负责管理整个系统中所有MapReduce程序,包括提交、与调度器协商资源以启动ApplicationMaster、监控ApplicationMaster运行状态并在失败时重新启动它等。
用户提交的每个MapReduce程序均包含一个ApplicationMaster,主要功能包括:与ResourceManager调度器协商以获取资源(用Container表示);将得到的任务进一步分配给内部的任务(资源的二次分配);与NodeManager通信以启动/停止任务;监控所有任务运行状态,并在任务运行失败时重新为任务申请资源以重启任务。
NodeManager是每个设备上的资源和任务管理器,一方面,它会定时地向ResourceManager汇报本设备上的资源使用情况和各个Container的运行状态;另一方面,它接收并处理来自ApplicationMaster的Container启动/停止等各种请求。
Container是YARN中的资源抽象,它封装了某个设备上的多维度资源,如内存、CPU、磁盘、网络等,当AM向RM申请资源时,RM为AM返回的资源便是用Container表示。
## 结语
本文走马观花的介绍了Hadoop相关内容。文章的主要目的是给大家一个对大数据的分布式解决方案的感官印象,为后面的大数据相关文章提供一个基础的理解。最后要强调的是,思考大数据方向的问题是一定要记住分布式的概念,因为你的数据并不在一个设备中甚至不再一个集群中,而且计算也是分布的。所以在设计大数据应用程序时,要花时间思考程序和算法在单机应用和分布式应用所产生的不同(e.g. 加权平均值)。
================================================
FILE: Hadoop/MapReduce编程模型和计算框架架构原理.md
================================================
Hadoop解决大规模数据分布式计算的方案是MapReduce。MapReduce既是一个编程模型,又是一个计算框架。也就是说,开发人员必须基于MapReduce编程模型进行编程开发,然后将程序通过MapReduce计算框架分发到Hadoop集群中运行。我们先看一下作为编程模型的MapReduce。
### MapReduce编程模型
MapReduce是一种非常简单又非常强大的编程模型。
简单在于其编程模型只包含map和reduce两个过程,map的主要输入是一对<key , value>值,经过map计算后输出一对<key , value>值;然后将相同key合并,形成<key , value集合>;再将这个<key , value集合>输入reduce,经过计算输出零个或多个<key , value>对。
但是MapReduce同时又是非常强大的,不管是关系代数运算(SQL计算),还是矩阵运算(图计算),大数据领域几乎所有的计算需求都可以通过MapReduce编程来实现。
我们以WordCount程序为例。WordCount主要解决文本处理中的词频统计问题,就是统计文本中每一个单词出现的次数。如果只是统计一篇文章的词频,几十K到几M的数据,那么写一个程序,将数据读入内存,建一个Hash表记录每个词出现的次数就可以了,如下图。

但是如果想统计全世界互联网所有网页(数万亿计)的词频数(这正是google这样的搜索引擎典型需求),你不可能写一个程序把全世界的网页都读入内存,这时候就需要用MapReduce编程来解决。
WordCount的MapReduce程序如下。
```
public class WordCount {
public static class TokenizerMapper
extends Mapper<Object, Text, Text, IntWritable>{
private final static IntWritable one = new IntWritable(1);
private Text word = new Text();
public void map(Object key, Text value, Context context
) throws IOException, InterruptedException {
StringTokenizer itr = new StringTokenizer(value.toString());
while (itr.hasMoreTokens()) {
word.set(itr.nextToken());
context.write(word, one);
}
}
}
public static class IntSumReducer
extends Reducer<Text,IntWritable,Text,IntWritable> {
private IntWritable result = new IntWritable();
public void reduce(Text key, Iterable<IntWritable> values,
Context context
) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable val : values) {
sum += val.get();
}
result.set(sum);
context.write(key, result);
}
}
}
```
其核心是一个map函数,一个reduce函数。
map函数的输入主要是一个<key , value>对,在这个例子里,value是要统计的所有文本中的一行数据,key在这里不重要,我们忽略。
```
public void map(Object key, Text value, Context context)
```
map函数的计算过程就是,将这行文本中的单词提取出来,针对每个单词输出一个<word , 1>这样的<key , value>对。
MapReduce计算框架会将这些<word , 1>收集起来,将相同的word放在一起,形成<word , <1,1,1,1,1,1,1.....>>这样的<key , value集合>数据,然后将其输入给reduce函数。
```
public void reduce(Text key, Iterable<IntWritable> values,Context context)
```
这里的reduce的输入参数values就是由很多个1组成的集合,而key就是具体的单词word。
reduce函数的计算过程就是,将这个集合里的1求和,再将单词(word)和这个和(sum)组成一个<key , value>(<word , sum>)输出。每一个输出就是一个单词和它的词频统计总和。
假设有两个block的文本数据需要进行词频统计,MapReduce计算过程如下图。

### MapReduce计算过程
一个map函数可以针对一部分数据进行运算,这样就可以将一个大数据切分成很多块(这也正是HDFS所做的),MapReduce计算框架为每个块分配一个map函数去计算,从而实现大数据的分布式计算。
上面提到MapReduce编程模型将大数据计算过程切分为map和reduce两个阶段,在map阶段为每个数据块分配一个map计算任务,然后将所有map输出的key进行合并,相同的key及其对应的value发送给同一个reduce任务去处理。
这个过程有两个关键问题需要处理
* 如何为每个数据块分配一个map计算任务,代码是如何发送数据块所在服务器的,发送过去是如何启动的,启动以后又如何知道自己需要计算的数据在文件什么位置(数据块id是什么)
* 处于不同服务器的map输出的<key , value> ,如何把相同的key聚合在一起发送给reduce任务
* 这两个关键问题正好对应文章中“MapReduce计算过程”一图中两处“MapReduce框架处理”。

我们先看下MapReduce是如何启动处理一个大数据计算应用作业的。
#### MapReduce作业启动和运行机制
我们以Hadoop1为例,MapReduce运行过程涉及以下几类关键进程:
* 大数据应用进程:启动用户MapReduce程序的主入口,主要指定Map和Reduce类、输入输出文件路径等,并提交作业给Hadoop集群。
* JobTracker进程:根据要处理的输入数据量启动相应数量的map和reduce进程任务,并管理整个作业生命周期的任务调度和监控。JobTracker进程在整个Hadoop集群全局唯一。
* TaskTracker进程:负责启动和管理map进程以及reduce进程。因为需要每个数据块都有对应的map函数,TaskTracker进程通常和HDFS的DataNode进程启动在同一个服务器,也就是说,Hadoop集群中绝大多数服务器同时运行DataNode进程和TaskTacker进程。
如下图所示。

具体作业启动和计算过程如下:
* 应用进程将用户作业jar包存储在HDFS中,将来这些jar包会分发给Hadoop集群中的服务器执行MapReduce计算。
* 应用程序提交job作业给JobTracker。
* JobTacker根据作业调度策略创建JobInProcess树,每个作业都会有一个自己的JobInProcess树。
* JobInProcess根据输入数据分片数目(通常情况就是数据块的数目)和设置的reduce数目创建相应数量的TaskInProcess。
* TaskTracker进程和JobTracker进程进行定时通信。
* 如果TaskTracker有空闲的计算资源(空闲CPU核),JobTracker就会给他分配任务。分配任务的时候会根据TaskTracker的服务器名字匹配在同一台机器上的数据块计算任务给它,使启动的计算任务正好处理本机上的数据。
* TaskRunner收到任务后根据任务类型(map还是reduce),任务参数(作业jar包路径,输入数据文件路径,要处理的数据在文件中的起始位置和偏移量,数据块多个备份的DataNode主机名等)启动相应的map或者reduce进程。
* map或者reduce程序启动后,检查本地是否有要执行任务的jar包文件,如果没有,就去HDFS上下载,然后加载map或者reduce代码开始执行。
* 如果是map进程,从HDFS读取数据(通常要读取的数据块正好存储在本机)。如果是reduce进程,将结果数据写出到HDFS。
通过以上过程,MapReduce可以将大数据作业计算任务分布在整个Hadoop集群中运行,每个map计算任务要处理的数据通常都能从本地磁盘上读取到。而用户要做的仅仅是编写一个map函数和一个reduce函数就可以了,根本不用关心这两个函数是如何被分布启动到集群上的,数据块又是如何分配给计算任务的。这一切都由MapReduce计算框架完成。
#### MapReduce数据合并与连接机制
在WordCount例子中,要统计相同单词在所有输入数据中出现的次数,而一个map只能处理一部分数据,一个热门单词几乎会出现在所有的map中,这些单词必须要合并到一起进行统计才能得到正确的结果。
事实上,几乎所有的大数据计算场景都需要处理数据关联的问题,简单如WordCount只要对key进行合并就可以了,复杂如数据库的join操作,需要对两种类型(或者更多类型)的数据根据key进行连接。
MapReduce计算框架处理数据合并与连接的操作就在map输出与reduce输入之间,这个过程有个专门的词汇来描述,叫做shuffle。
#### MapReduce shuffle过程
每个map任务的计算结果都会写入到本地文件系统,等map任务快要计算完成的时候,MapReduce计算框架会启动shuffle过程,在map端调用一个Partitioner接口,对map产生的每个<key , value>进行reduce分区选择,然后通过http通信发送给对应的reduce进程。这样不管map位于哪个服务器节点,相同的key一定会被发送给相同的reduce进程。reduce端对收到的<key , value>进行排序和合并,相同的key放在一起,组成一个<key , value集合>传递给reduce执行。
MapReduce框架缺省的Partitioner用key的哈希值对reduce任务数量取模,相同的key一定会落在相同的reduce任务id上,实现上,这样的Partitioner代码只需要一行,如下所示。
```
/** Use {@link Object#hashCode()} to partition. */
public int getPartition(K2 key, V2 value, int numReduceTasks) {
return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
}
```
shuffle是大数据计算过程中发生奇迹的地方,不管是MapReduce还是Spark,只要是大数据批处理计算,一定会有shuffle过程,让数据关联起来,数据的内在关系和价值才会呈现出来。不理解shuffle,就会在map和reduce编程中产生困惑,不知道该如何正确设计map的输出和reduce的输入。shuffle也是整个MapReduce过程中最难最消耗性能的地方,在MapReduce早期代码中,一半代码都是关于shuffle处理的。

================================================
FILE: JVM/HotSpot垃圾收集器.md
================================================
# HotSpot垃圾收集器
HotSpot 虚拟机提供了多种垃圾收集器,每种收集器都有各自的特点,虽然我们要对各个收集器进行比较,但并非为了挑选出一个最好的收集器。我们选择的只是对具体应用最合适的收集器。
## 新生代垃圾收集器
### Serial 垃圾收集器(单线程)
只开启**一条** GC 线程进行垃圾回收,并且在垃圾收集过程中停止一切用户线程\(Stop The World\)。
一般客户端应用所需内存较小,不会创建太多对象,而且堆内存不大,因此垃圾收集器回收时间短,即使在这段时间停止一切用户线程,也不会感觉明显卡顿。因此 Serial 垃圾收集器**适合客户端**使用。
由于 Serial 收集器只使用一条 GC 线程,避免了线程切换的开销,从而简单高效。

### ParNew 垃圾收集器(多线程)
ParNew 是 Serial 的多线程版本。由多条 GC 线程并行地进行垃圾清理。但清理过程依然需要 Stop The World。
ParNew 追求“**低停顿时间**”,与 Serial 唯一区别就是使用了多线程进行垃圾收集,在多 CPU 环境下性能比 Serial 会有一定程度的提升;但**线程切换需要额外的开销**,因此在单 CPU 环境中表现不如 Serial。

### Parallel Scavenge 垃圾收集器(多线程)
Parallel Scavenge 和 ParNew 一样,都是多线程、新生代垃圾收集器。但是两者有巨大的不同点:
* Parallel Scavenge:追求 CPU 吞吐量,能够在较短时间内完成指定任务,因此适合没有交互的后台计算。
* ParNew:追求降低用户停顿时间,适合交互式应用。
吞吐量 = 运行用户代码时间 / \(运行用户代码时间 + 垃圾收集时间\)
追求高吞吐量,可以通过减少 GC 执行实际工作的时间,然而,仅仅偶尔运行 GC 意味着每当 GC 运行时将有许多工作要做,因为在此期间积累在堆中的对象数量很高。单个 GC 需要花更多的时间来完成,从而导致更高的暂停时间。而考虑到低暂停时间,最好频繁运行 GC 以便更快速完成,反过来又导致吞吐量下降。
* 通过参数 -XX:GCTimeRadio 设置垃圾回收时间占总 CPU 时间的百分比。
* 通过参数 -XX:MaxGCPauseMillis 设置垃圾处理过程最久停顿时间。
* 通过命令 -XX:+UseAdaptiveSizePolicy 开启自适应策略。我们只要设置好堆的大小和 MaxGCPauseMillis 或 GCTimeRadio,收集器会自动调整新生代的大小、Eden 和 Survivor 的比例、对象进入老年代的年龄,以最大程度上接近我们设置的 MaxGCPauseMillis 或 GCTimeRadio。
## 老年代垃圾收集器
### Serial Old 垃圾收集器(单线程)
Serial Old 收集器是 Serial 的老年代版本,都是单线程收集器,只启用一条 GC 线程,都适合客户端应用。它们唯一的区别就是:Serial Old 工作在老年代,使用“标记-整理”算法;Serial 工作在新生代,使用“复制”算法。
### Parallel Old 垃圾收集器(多线程)
Parallel Old 收集器是 Parallel Scavenge 的老年代版本,追求 CPU 吞吐量。
### CMS 垃圾收集器
CMS\(Concurrent Mark Sweep,并发标记清除\)收集器是以获取最短回收停顿时间为目标的收集器(追求低停顿),它在垃圾收集时使得用户线程和 GC 线程并发执行,因此在垃圾收集过程中用户也不会感到明显的卡顿。
* 初始标记:Stop The World,仅使用一条初始标记线程对所有与 GC Roots 直接关联的对象进行标记。
* 并发标记:使用**多条**标记线程,与用户线程并发执行。此过程进行可达性分析,标记出所有废弃对象。速度很慢。
* 重新标记:Stop The World,使用多条标记线程并发执行,将刚才并发标记过程中新出现的废弃对象标记出来。
* 并发清除:只使用一条 GC 线程,与用户线程并发执行,清除刚才标记的对象。这个过程非常耗时。
并发标记与并发清除过程耗时最长,且可以与用户线程一起工作,因此,**总体上说**,CMS 收集器的内存回收过程是与用户线程**一起并发执行**的。

CMS 的缺点:
* 吞吐量低
* 无法处理浮动垃圾,导致频繁 Full GC
* 使用“标记-清除”算法产生碎片空间
对于产生碎片空间的问题,可以通过开启 -XX:+UseCMSCompactAtFullCollection,在每次 Full GC 完成后都会进行一次内存压缩整理,将零散在各处的对象整理到一块。设置参数 -XX:CMSFullGCsBeforeCompaction告诉 CMS,经过了 N 次 Full GC 之后再进行一次内存整理。
## G1 通用垃圾收集器
G1 是一款面向服务端应用的垃圾收集器,它没有新生代和老年代的概念,而是将堆划分为一块块独立的 Region。当要进行垃圾收集时,首先估计每个 Region 中垃圾的数量,每次都从垃圾回收价值最大的 Region 开始回收,因此可以获得最大的回收效率。
从整体上看, G1 是基于“标记-整理”算法实现的收集器,从局部(两个 Region 之间)上看是基于“复制”算法实现的,这意味着运行期间不会产生内存空间碎片。
这里抛个问题👇
一个对象和它内部所引用的对象可能不在同一个 Region 中,那么当垃圾回收时,是否需要扫描整个堆内存才能完整地进行一次可达性分析?
并不!每个 Region 都有一个 Remembered Set,用于记录本区域中所有对象引用的对象所在的区域,进行可达性分析时,只要在 GC Roots 中再加上 Remembered Set 即可防止对整个堆内存进行遍历。
如果不计算维护 Remembered Set 的操作,G1 收集器的工作过程分为以下几个步骤:
* 初始标记:Stop The World,仅使用一条初始标记线程对所有与 GC Roots 直接关联的对象进行标记。
* 并发标记:使用**一条**标记线程与用户线程并发执行。此过程进行可达性分析,速度很慢。
* 最终标记:Stop The World,使用多条标记线程并发执行。
* 筛选回收:回收废弃对象,此时也要 Stop The World,并使用多条筛选回收线程并发执行。
(完)
================================================
FILE: JVM/HotSpot虚拟机对象探秘.md
================================================
# HotSpot虚拟机对象探秘
## 对象的内存布局
在 HotSpot 虚拟机中,对象的内存布局分为以下 3 块区域:
* 对象头(Header)
* 实例数据(Instance Data)
* 对齐填充(Padding)

### 对象头
对象头记录了对象在运行过程中所需要使用的一些数据:
* 哈希码
* GC 分代年龄
* 锁状态标志
* 线程持有的锁
* 偏向线程 ID
* 偏向时间戳
对象头可能包含类型指针,通过该指针能确定对象属于哪个类。如果对象是一个数组,那么对象头还会包括数组长度。
### 实例数据
实例数据部分就是成员变量的值,其中包括父类成员变量和本类成员变量。
### 对齐填充
用于确保对象的总长度为 8 字节的整数倍。
HotSpot VM 的自动内存管理系统要求对象的大小必须是 8 字节的整数倍。而对象头部分正好是 8 字节的倍数(1 倍或 2 倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
> 对齐填充并不是必然存在,也没有特别的含义,它仅仅起着占位符的作用。
## 对象的创建过程
### 类加载检查
虚拟机在解析`.class`文件时,若遇到一条 new 指令,首先它会去检查常量池中是否有这个类的符号引用,并且检查这个符号引用所代表的类是否已被加载、解析和初始化过。如果没有,那么必须先执行相应的类加载过程。
### 为新生对象分配内存
对象所需内存的大小在类加载完成后便可完全确定,接下来从堆中划分一块对应大小的内存空间给新的对象。分配堆中内存有两种方式:
- **指针碰撞**<br>
如果 Java **堆中内存绝对规整**(说明采用的是“**复制算法**”或“**标记整理法**”),空闲内存和已使用内存中间放着一个指针作为分界点指示器,那么分配内存时只需要把指针向空闲内存挪动一段与对象大小一样的距离,这种分配方式称为“**指针碰撞**”。
- **空闲列表**<br>
如果 Java **堆中内存并不规整**,已使用的内存和空闲内存交错(说明采用的是**标记-清除法**,有碎片),此时没法简单进行指针碰撞, VM 必须维护一个列表,记录其中哪些内存块空闲可用。分配之时从空闲列表中找到一块足够大的内存空间划分给对象实例。这种方式称为“**空闲列表**”。
### 初始化
分配完内存后,为对象中的成员变量赋上初始值,设置对象头信息,调用对象的构造函数方法进行初始化。
至此,整个对象的创建过程就完成了。
## 对象的访问方式
所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配的。也就是说在建立一个对象时两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在堆栈中分配的内存只是一个指向这个堆对象的指针(引用)而已。 那么根据引用存放的地址类型的不同,对象有不同的访问方式。
### 句柄访问方式
堆中需要有一块叫做“句柄池”的内存空间,句柄中包含了对象实例数据与类型数据各自的具体地址信息。
引用类型的变量存放的是该对象的句柄地址(reference)。访问对象时,首先需要通过引用类型的变量找到该对象的句柄,然后根据句柄中对象的地址找到对象。

### 直接指针访问方式
引用类型的变量直接存放对象的地址,从而不需要句柄池,通过引用能够直接访问对象。但对象所在的内存空间需要额外的策略存储对象所属的类信息的地址。

需要说明的是,HotSpot 采用第二种方式,即直接指针方式来访问对象,只需要一次寻址操作,所以在性能上比句柄访问方式快一倍。但像上面所说,它需要**额外的策略**来存储对象在方法区中类信息的地址。
================================================
FILE: JVM/JVM 性能调优.md
================================================
# JVM 性能调优
在高性能硬件上部署程序,目前主要有两种方式:
* 通过 64 位 JDK 来使用大内存;
* 使用若干个 32 位虚拟机建立逻辑集群来利用硬件资源。
## 使用64位JDK管理大内存
堆内存变大后,虽然垃圾收集的频率减少了,但每次垃圾回收的时间变长。 如果堆内存为14 G,那么每次 Full GC 将长达数十秒。如果 Full GC 频繁发生,那么对于一个网站来说是无法忍受的。
对于用户交互性强、对停顿时间敏感的系统,可以给 Java 虚拟机分配超大堆的前提是有把握把应用程序的 Full GC 频率控制得足够低,至少要低到不会影响用户使用。
可能面临的问题:
* 内存回收导致的长时间停顿;
* 现阶段,64位 JDK 的性能普遍比 32 位 JDK 低;
* 需要保证程序足够稳定,因为这种应用要是产生堆溢出几乎就无法产生堆转储快照(因为要产生超过 10GB 的 Dump 文件),哪怕产生了快照也几乎无法进行分析;
* 相同程序在 64 位 JDK 消耗的内存一般比 32 位 JDK 大,这是由于指针膨胀,以及数据类型对齐补白等因素导致的。
## 使用32位JVM建立逻辑集群
在一台物理机器上启动多个应用服务器进程,每个服务器进程分配不同端口, 然后在前端搭建一个负载均衡器,以反向代理的方式来分配访问请求。
考虑到在一台物理机器上建立逻辑集群的目的仅仅是为了尽可能利用硬件资源,并不需要关心状态保留、热转移之类的高可用性能需求, 也不需要保证每个虚拟机进程有绝对的均衡负载,因此使用无 Session 复制的亲合式集群是一个不错的选择。 我们仅仅需要保障集群具备亲合性,也就是均衡器按一定的规则算法(一般根据 SessionID 分配) 将一个固定的用户请求永远分配到固定的一个集群节点进行处理即可。
可能遇到的问题:
* 尽量避免节点竞争全局资源,如磁盘竞争,各个节点如果同时访问某个磁盘文件的话,很可能导致 IO 异常;
* 很难高效利用资源池,如连接池,一般都是在节点建立自己独立的连接池,这样有可能导致一些节点池满了而另外一些节点仍有较多空余;
* 各个节点受到 32 位的内存限制;
* 大量使用本地缓存的应用,在逻辑集群中会造成较大的内存浪费,因为每个逻辑节点都有一份缓存,这时候可以考虑把本地缓存改成集中式缓存。
## 调优案例分析与实战
### 场景描述
一个小型系统,使用 32 位 JDK,4G 内存,测试期间发现服务端不定时抛出内存溢出异常。 加入 -XX:+HeapDumpOnOutOfMemoryError(添加这个参数后,堆内存溢出时就会输出异常日志), 但再次发生内存溢出时,没有生成相关异常日志。
### 分析
在 32 位 JDK 上,1.6G 分配给堆,还有一部分分配给 JVM 的其他内存,直接内存最大也只能在剩余的 0.4G 空间中分出一部分, 如果使用了 NIO,JVM 会在 JVM 内存之外分配内存空间,那么就要小心“直接内存”不足时发生内存溢出异常了。
### 直接内存的回收过程
直接内存虽然不是 JVM 内存空间,但它的垃圾回收也由 JVM 负责。
垃圾收集进行时,虚拟机虽然会对直接内存进行回收, 但是直接内存却不能像新生代、老年代那样,发现空间不足了就通知收集器进行垃圾回收, 它只能等老年代满了后 Full GC,然后“顺便”帮它清理掉内存的废弃对象。 否则只能一直等到抛出内存溢出异常时,先 catch 掉,再在 catch 块里大喊 “System.gc\(\)”。 要是虚拟机还是不听,那就只能眼睁睁看着堆中还有许多空闲内存,自己却不得不抛出内存溢出异常了。
(完)
================================================
FILE: JVM/JVM内存结构.md
================================================
# JVM 内存结构
Java 虚拟机的内存空间分为 5 个部分:
* 程序计数器
* Java 虚拟机栈
* 本地方法栈
* 堆
* 方法区

JDK 1.8 同 JDK 1.7 比,最大的差别就是:元数据区取代了永久代。元空间的本质和永久代类似,都是对 JVM 规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中,而是使用本地内存。
## 程序计数器(PC 寄存器)
### 程序计数器的定义
程序计数器是一块较小的内存空间,是当前线程正在执行的那条字节码指令的地址。若当前线程正在执行的是一个本地方法,那么此时程序计数器为`Undefined`。
### 程序计数器的作用
* 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制。
* 在多线程情况下,程序计数器记录的是当前线程执行的位置,从而当线程切换回来时,就知道上次线程执行到哪了。
### 程序计数器的特点
* 是一块较小的内存空间。
* 线程私有,每条线程都有自己的程序计数器。
* 生命周期:随着线程的创建而创建,随着线程的结束而销毁。
* 是唯一一个不会出现`OutOfMemoryError`的内存区域。
## Java 虚拟机栈(Java 栈)
### Java 虚拟机栈的定义
Java 虚拟机栈是描述 Java 方法运行过程的内存模型。
Java 虚拟机栈会为每一个即将运行的 Java 方法创建一块叫做“栈帧”的区域,用于存放该方法运行过程中的一些信息,如:
* 局部变量表
* 操作数栈
* 动态链接
* 方法出口信息
* ......

### 压栈出栈过程
当方法运行过程中需要创建局部变量时,就将局部变量的值存入栈帧中的局部变量表中。
Java 虚拟机栈的栈顶的栈帧是当前正在执行的活动栈,也就是当前正在执行的方法,PC 寄存器也会指向这个地址。只有这个活动的栈帧的本地变量可以被操作数栈使用,当在这个栈帧中调用另一个方法,与之对应的栈帧又会被创建,新创建的栈帧压入栈顶,变为当前的活动栈帧。
方法结束后,当前栈帧被移出,栈帧的返回值变成新的活动栈帧中操作数栈的一个操作数。如果没有返回值,那么新的活动栈帧中操作数栈的操作数没有变化。
> 由于Java 虚拟机栈是与线程对应的,数据不是线程共享的,因此不用关心数据一致性问题,也不会存在同步锁的问题。
### Java 虚拟机栈的特点
* 局部变量表随着栈帧的创建而创建,它的大小在编译时确定,创建时只需分配事先规定的大小即可。在方法运行过程中,局部变量表的大小不会发生改变。
* Java 虚拟机栈会出现两种异常:StackOverFlowError 和 OutOfMemoryError。
* StackOverFlowError 若 Java 虚拟机栈的大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度时,抛出 StackOverFlowError 异常。
* OutOfMemoryError 若允许动态扩展,那么当线程请求栈时内存用完了,无法再动态扩展时,抛出 OutOfMemoryError 异常。
* Java 虚拟机栈也是线程私有,随着线程创建而创建,随着线程的结束而销毁。
> 出现 StackOverFlowError 时,内存空间可能还有很多。
## 本地方法栈(C 栈)
### 本地方法栈的定义
本地方法栈是为 JVM 运行 Native 方法准备的空间,由于很多 Native 方法都是用 C 语言实现的,所以它通常又叫 C 栈。它与 Java 虚拟机栈实现的功能类似,只不过本地方法栈是描述本地方法运行过程的内存模型。
### 栈帧变化过程
本地方法被执行时,在本地方法栈也会创建一块栈帧,用于存放该方法的局部变量表、操作数栈、动态链接、方法出口信息等。
方法执行结束后,相应的栈帧也会出栈,并释放内存空间。也会抛出 StackOverFlowError 和 OutOfMemoryError 异常。
> 如果 Java 虚拟机本身不支持 Native 方法,或是本身不依赖于传统栈,那么可以不提供本地方法栈。如果支持本地方法栈,那么这个栈一般会在线程创建的时候按线程分配。
## 堆
### 堆的定义
堆是用来存放对象的内存空间,几乎所有的对象都存储在堆中。
### 堆的特点
* 线程共享,整个 Java 虚拟机只有一个堆,所有的线程都访问同一个堆。而程序计数器、Java 虚拟机栈、本地方法栈都是一个线程对应一个。
* 在虚拟机启动时创建。
* 是垃圾回收的主要场所。
* 进一步可分为:新生代\(Eden区 From Survior To Survivor\)、老年代。
不同的区域存放不同生命周期的对象,这样可以根据不同的区域使用不同的垃圾回收算法,更具有针对性。
堆的大小既可以固定也可以扩展,但对于主流的虚拟机,堆的大小是可扩展的,因此当线程请求分配内存,但堆已满,且内存已无法再扩展时,就抛出 OutOfMemoryError 异常。
> Java 堆所使用的内存不需要保证是连续的。而由于堆是被所有线程共享的,所以对它的访问需要注意同步问题,方法和对应的属性都需要保证一致性。
## 方法区
### 方法区的定义
Java 虚拟机规范中定义方法区是堆的一个逻辑部分。方法区存放以下信息:
* 已经被虚拟机加载的类信息
* 常量
* 静态变量
* 即时编译器编译后的代码
### 方法区的特点
* 线程共享。 方法区是堆的一个逻辑部分,因此和堆一样,都是线程共享的。整个虚拟机中只有一个方法区。
* 永久代。 方法区中的信息一般需要长期存在,而且它又是堆的逻辑分区,因此用堆的划分方法,把方法区称为“永久代”。
* 内存回收效率低。 方法区中的信息一般需要长期存在,回收一遍之后可能只有少量信息无效。主要回收目标是:对常量池的回收;对类型的卸载。
* Java 虚拟机规范对方法区的要求比较宽松。 和堆一样,允许固定大小,也允许动态扩展,还允许不实现垃圾回收。
### 运行时常量池
方法区中存放:类信息、常量、静态变量、即时编译器编译后的代码。常量就存放在运行时常量池中。
当类被 Java 虚拟机加载后, .class 文件中的常量就存放在方法区的运行时常量池中。而且在运行期间,可以向常量池中添加新的常量。如 String 类的 intern\(\) 方法就能在运行期间向常量池中添加字符串常量。
## 直接内存(堆外内存)
直接内存是除 Java 虚拟机之外的内存,但也可能被 Java 使用。
### 操作直接内存
在 NIO 中引入了一种基于通道和缓冲的 IO 方式。它可以通过调用本地方法直接分配 Java 虚拟机之外的内存,然后通过一个存储在堆中的`DirectByteBuffer`对象直接操作该内存,而无须先将外部内存中的数据复制到堆中再进行操作,从而提高了数据操作的效率。
直接内存的大小不受 Java 虚拟机控制,但既然是内存,当内存不足时就会抛出 OutOfMemoryError 异常。
### 直接内存与堆内存比较
* 直接内存申请空间耗费更高的性能
* 直接内存读取 IO 的性能要优于普通的堆内存。
* 直接内存作用链: 本地 IO -> 直接内存 -> 本地 IO
* 堆内存作用链:本地 IO -> 直接内存 -> 非直接内存 -> 直接内存 -> 本地 IO
> 服务器管理员在配置虚拟机参数时,会根据实际内存设置`-Xmx`等参数信息,但经常忽略直接内存,使得各个内存区域总和大于物理内存限制,从而导致动态扩展时出现`OutOfMemoryError`异常。
================================================
FILE: JVM/jvm系列(一)java类的加载机制.md
================================================
## 什么是类的加载
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的 Class对象, Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
java类的加载机制.resources/39F977A1-EAA5-4870-9752-0DAD0C9B2955.jpg)
类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误
* 加载.class文件的方式
* 从本地系统中直接加载通过网络下载.class文件
* 从zip,jar等归档文件中加载.class文件
* 从专有数据库中提取.class文件将Java源文件动态编译为.class文件
## 类的生命周期
java类的加载机制.resources/B33959C2-DD9E-4FA5-8BDE-0CFC04DDDB11.jpg)
其中类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定)。另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。
#### 加载
查找并加载类的二进制数据加载时类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情:
* 通过一个类的全限定名来获取其定义的二进制字节流。
* 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
* 在Java堆中生成一个代表这个类的 java.lang.Class对象,作为对方法区中这些数据的访问入口。
相对于类加载的其他阶段而言,加载阶段(准确地说,是加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,因为开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。
加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在Java堆中也创建一个 java.lang.Class类的对象,这样便可以通过该对象访问方法区中的这些数据。
#### 连接
验证:确保被加载的类的正确性
验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致会完成4个阶段的检验动作:
* 文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以 0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。
* 元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了 java.lang.Object之外。
* 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
* 符号引用验证:确保解析动作能正确执行。验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用 -Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
准备:为类的 静态变量分配内存,并将其初始化为默认值
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:
* 1、这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。
* 2、这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。假设一个类变量的定义为: publicstaticintvalue=3;
那么变量value在准备阶段过后的初始值为0,而不是3,因为这时候尚未开始执行任何Java方法,而把value赋值为3的 publicstatic指令是在程序编译后,存放于类构造器 <clinit>()方法之中的,所以把value赋值为3的动作将在初始化阶段才会执行。
这里还需要注意如下几点:
>对基本数据类型来说,对于类变量(static)和全局变量,如果不显式地对其赋值而直接使用,则系统会为其赋予默认的零值,而对于局部变量来说,在使用前必须显式地为其赋值,否则编译时不通过。对于同时被static和final修饰的常量,必须在声明的时候就为其显式地赋值,否则编译时不通过;而只被final修饰的常量则既可以在声明时显式地为其赋值,也可以在类初始化时显式地为其赋值,总之,在使用前必须为其显式地赋值,系统不会为其赋予默认零值。对于引用数据类型reference来说,如数组引用、对象引用等,如果没有对其进行显式地赋值而直接使用,系统都会为其赋予默认的零值,即null。如果在数组初始化时没有对数组中的各元素赋值,那么其中的元素将根据对应的数据类型而被赋予默认的零值。
3、如果类字段的字段属性表中存在 ConstantValue属性,即同时被final和static修饰,那么在准备阶段变量value就会被初始化为ConstValue属性所指定的值。
假设上面的类变量value被定义为: publicstaticfinalintvalue=3;
编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据 ConstantValue的设置将value赋值为3。我们可以理解为static final常量在编译期就将其结果放入了调用它的类的常量池中
#### 解析:把类中的符号引用转换为直接引用
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量。
直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
#### 初始化
初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:
* ①声明类变量是指定初始值
* ②使用静态代码块为类变量指定初始值
JVM初始化步骤
* 1、假如这个类还没有被加载和连接,则程序先加载并连接该类
* 2、假如该类的直接父类还没有被初始化,则先初始化其直接父类
* 3、假如类中有初始化语句,则系统依次执行这些初始化语句
类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:
* 创建类的实例,也就是new的方式
* 访问某个类或接口的静态变量,或者对该静态变量赋值
* 调用类的静态方法
* 反射(如 Class.forName(“com.shengsiyuan.Test”))
* 初始化某个类的子类,则其父类也会被初始化
* Java虚拟机启动时被标明为启动类的类( JavaTest),直接使用 java.exe命令来运行某个主类
#### 结束生命周期
* 执行了 System.exit()方法
* 程序正常执行结束
* 程序在执行过程中遇到了异常或错误而异常终止
* 由于操作系统出现错误而导致Java虚拟机进程终止
## 3、类加载器
寻找类加载器,先来一个小例子
```
package com.neo.classloader;
public class ClassLoaderTest {
public static void main(String[] args) {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
System.out.println(loader);
System.out.println(loader.getParent());
System.out.println(loader.getParent().getParent());
}
}
```
运行后,输出结果:
```
sun.misc.Launcher$AppClassLoader@64fef26a
sun.misc.Launcher$ExtClassLoader@1ddd40f3
null
```
从上面的结果可以看出,并没有获取到ExtClassLoader的父Loader,原因是Bootstrap Loader(引导类加载器)是用C语言实现的,找不到一个确定的返回父Loader的方式,于是就返回null。
这几种类加载器的层次关系如下图所示:
java类的加载机制.resources/660D04B1-E9B5-4609-8CD6-6674BC130BA9.jpg)
注意:这里父类加载器并不是通过继承关系来实现的,而是采用组合实现的。
站在Java虚拟机的角度来讲,只存在两种不同的类加载器:启动类加载器:它使用C++实现(这里仅限于Hotspot,也就是JDK1.5之后默认的虚拟机,有很多其他的虚拟机是用Java语言实现的),是虚拟机自身的一部分;所有其他的类加载器:这些类加载器都由Java语言实现,独立于虚拟机之外,并且全部继承自抽象类java.lang.ClassLoader,这些类加载器需要由启动类加载器加载到内存中之后才能去加载其他的类。
站在Java开发人员的角度来看,类加载器可以大致划分为以下三类:
**启动类加载器:** Bootstrap ClassLoader,负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被Java程序直接引用的。
**扩展类加载器:** Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载DK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。
**应用程序类加载器:** Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
应用程序都是由这三种类加载器互相配合进行加载的,如果有必要,我们还可以加入自定义的类加载器。因为JVM自带的ClassLoader只是懂得从本地文件系统加载标准的java class文件,因此如果编写了自己的ClassLoader,便可以做到如下几点:
1)在执行非置信代码之前,自动验证数字签名。
2)动态地创建符合用户特定需要的定制化构建类。
3)从特定的场所取得java class,例如数据库中和网络中。
**JVM类加载机制**
•全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
•父类委托,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
•缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效
## 4、类的加载
类加载有三种方式:
1、命令行启动应用时候由JVM初始化加载
2、通过Class.forName()方法动态加载
3、通过ClassLoader.loadClass()方法动态加载
例子:
```
package com.neo.classloader;
public class loaderTest {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader loader = HelloWorld.class.getClassLoader();
System.out.println(loader);
//使用ClassLoader.loadClass()来加载类,不会执行初始化块
loader.loadClass("Test2");
//使用Class.forName()来加载类,默认会执行初始化块
// Class.forName("Test2");
//使用Class.forName()来加载类,并指定ClassLoader,初始化时不执行静态块
// Class.forName("Test2", false, loader);
}
}
```
demo 类
```
public class Test2 {
static {
System.out.println("静态初始化块执行了!");
}
}
```
分别切换加载方式,会有不同的输出结果。
Class.forName()和ClassLoader.loadClass()区别
Class.forName():将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块;
ClassLoader.loadClass():只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
注:
Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象。
## 5、双亲委派模型
双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
双亲委派机制:
1、当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
2、当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
3、如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
4、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。
```
public Class<?> loadClass(String name)throws ClassNotFoundException {
return loadClass(name, false);
}
protected synchronized Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {
// 首先判断该类型是否已经被加载
Class c = findLoadedClass(name);
if (c == null) {
//如果没有被加载,就委托给父类加载或者委派给启动类加载器加载
try {
if (parent != null) {
//如果存在父类加载器,就委派给父类加载器加载
c = parent.loadClass(name, false);
} else {
//如果不存在父类加载器,就检查是否是由启动类加载器加载的类,通过调用本地方法native Class findBootstrapClass(String name)
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// 如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
```
双亲委派模型意义:
-系统类防止内存中出现多份同样的字节码
-保证Java程序安全稳定运行
## 6、自定义类加载器
通常情况下,我们都是直接使用系统类加载器。但是,有的时候,我们也需要自定义类加载器。比如应用是通过网络来传输 Java 类的字节码,为保证安全性,这些字节码经过了加密处理,这时系统类加载器就无法对其进行加载,这样则需要自定义类加载器来实现。自定义类加载器一般都是继承自 ClassLoader 类,从上面对 loadClass 方法来分析来看,我们只需要重写 findClass 方法即可。下面我们通过一个示例来演示自定义类加载器的流程:
```
package com.neo.classloader;
import java.io.*;
public class MyClassLoader extends ClassLoader {
private String root;
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = loadClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0, classData.length);
}
}
private byte[] loadClassData(String className) {
String fileName = root + File.separatorChar
+ className.replace('.', File.separatorChar) + ".class";
try {
InputStream ins = new FileInputStream(fileName);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int length = 0;
while ((length = ins.read(buffer)) != -1) {
baos.write(buffer, 0, length);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public String getRoot() {
return root;
}
public void setRoot(String root) {
this.root = root;
}
public static void main(String[] args) {
MyClassLoader classLoader = new MyClassLoader();
classLoader.setRoot("E:\\temp");
Class<?> testClass = null;
try {
testClass = classLoader.loadClass("com.neo.classloader.Test2");
Object object = testClass.newInstance();
System.out.println(object.getClass().getClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
```
自定义类加载器的核心在于对字节码文件的获取,如果是加密的字节码则需要在该类中对文件进行解密。由于这里只是演示,我并未对class文件进行加密,因此没有解密的过程。这里有几点需要注意:
1、这里传递的文件名需要是类的全限定性名称,即com.paddx.test.classloading.Test格式的,因为 defineClass 方法是按这种格式进行处理的。
2、最好不要重写loadClass方法,因为这样容易破坏双亲委托模式。
3、这类Test 类本身可以被 AppClassLoader 类加载,因此我们不能把 com/paddx/test/classloading/Test.class 放在类路径下。否则,由于双亲委托机制的存在,会直接导致该类由 AppClassLoader 加载,而不会通过我们自定义类加载器来加载。
================================================
FILE: JVM/jvm系列(三)GC算法 垃圾收集器.md
================================================
## 概述
垃圾收集 Garbage Collection 通常被称为“GC”,它诞生于1960年 MIT 的 Lisp 语言,经过半个多世纪,目前已经十分成熟了。
jvm 中,程序计数器、虚拟机栈、本地方法栈都是随线程而生随线程而灭,栈帧随着方法的进入和退出做入栈和出栈操作,实现了自动的内存清理,因此,我们的内存垃圾回收主要集中于 java 堆和方法区中,在程序运行期间,这部分内存的分配和使用都是动态的.
## 对象存活判断
判断对象是否存活一般有两种方式:
引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。
可达性分析(Reachability Analysis):从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。不可达对象。
>在Java语言中,GC Roots包括:
虚拟机栈中引用的对象。
方法区中类静态属性实体引用的对象。
方法区中常量引用的对象。
本地方法栈中JNI引用的对象。
## 垃圾收集算法
### 标记-清除算法
“标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。之所以说它是最基础的收集算法,是因为后续的收集算法都是基于这种思路并对其缺点进行改进而得到的。
它的主要缺点有两个:一个是效率问题,标记和清除过程的效率都不高;另外一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
GC算法 垃圾收集器.resources/1082B388-1A45-4177-98E2-30394876769B.png)
### 复制算法
“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
这样使得每次都是对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为原来的一半,持续复制长生存期的对象则导致效率降低。
GC算法 垃圾收集器.resources/02057726-396E-47F9-9339-2DEF5B338ED2.png)
### 标记-压缩算法
复制收集算法在对象存活率较高时就要执行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。
根据老年代的特点,有人提出了另外一种“标记-整理”(Mark-Compact)算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
GC算法 垃圾收集器.resources/7F6F3B11-DDA0-4E80-8162-442B66A71632.png)
### 分代收集算法
GC分代的基本假设:绝大部分对象的生命周期都非常短暂,存活时间短。
“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收。
## 垃圾收集器
如果说收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现
### Serial收集器
串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。新生代、老年代使用串行回收;新生代复制算法、老年代标记-压缩;垃圾收集的过程中会Stop The World(服务暂停)
参数控制:-XX:+UseSerialGC 串行收集器
GC算法 垃圾收集器.resources/BC391A2A-4905-4708-9052-2422C0E9D692.png)
### ParNew收集器
ParNew收集器其实就是Serial收集器的多线程版本。新生代并行,老年代串行;新生代复制算法、老年代标记-压缩
参数控制:-XX:+UseParNewGC ParNew收集器
-XX:ParallelGCThreads 限制线程数量
GC算法 垃圾收集器.resources/B6AC79A9-21A8-48B4-B29E-CF755E0970FC.png)
### Parallel收集器
Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。可以通过参数来打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;也可以通过参数控制GC的时间不大于多少毫秒或者比例;新生代复制算法、老年代标记-压缩
参数控制:-XX:+UseParallelGC 使用Parallel收集器+ 老年代串行
### Parallel Old 收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在JDK 1.6中才开始提供
参数控制: -XX:+UseParallelOldGC 使用Parallel收集器+ 老年代并行
### CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用都集中在互联网站或B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。
从名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于“标记-清除”算法实现的,它的运作过程相对于前面几种收集器来说要更复杂一些,整个过程分为4个步骤,包括:
初始标记(CMS initial mark)
并发标记(CMS concurrent mark)
重新标记(CMS remark)
并发清除(CMS concurrent sweep)
其中初始标记、重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。
由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行。老年代收集器(新生代使用ParNew)
优点:并发收集、低停顿
缺点:产生大量空间碎片、并发阶段会降低吞吐量
参数控制:-XX:+UseConcMarkSweepGC 使用CMS收集器
-XX:+ UseCMSCompactAtFullCollection Full GC后,进行一次碎片整理;整理过程是独占的,会引起停顿时间变长
-XX:+CMSFullGCsBeforeCompaction 设置进行几次Full GC后,进行一次碎片整理
-XX:ParallelCMSThreads 设定CMS的线程数量(一般情况约等于可用CPU数量)
GC算法 垃圾收集器.resources/4F09EDEB-C98A-43BB-BF13-BAE28C01B961.png)
### G1收集器
G1是目前技术发展的最前沿成果之一,HotSpot开发团队赋予它的使命是未来可以替换掉JDK1.5中发布的CMS收集器。与CMS收集器相比G1收集器有以下特点:
1. 空间整合,G1收集器采用标记整理算法,不会产生内存空间碎片。分配大对象时不会因为无法找到连续空间而提前触发下一次GC。
2. 可预测停顿,这是G1的另一大优势,降低停顿时间是G1和CMS的共同关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为N毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。
上面提到的垃圾收集器,收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔阂了,它们都是一部分(可以不连续)Region的集合。
GC算法 垃圾收集器.resources/C3FF8C8A-97F5-4DC1-8E90-54FFAACD3D25.jpg)
G1的新生代收集跟ParNew类似,当新生代占用达到一定比例的时候,开始出发收集。和CMS类似,G1收集器收集老年代对象会有短暂停顿。
收集步骤:
1、标记阶段,首先初始标记(Initial-Mark),这个阶段是停顿的(Stop the World Event),并且会触发一次普通Mintor GC。对应GC log:GC pause (young) (inital-mark)
2、Root Region Scanning,程序运行过程中会回收survivor区(存活到老年代),这一过程必须在young GC之前完成。
3、Concurrent Marking,在整个堆中进行并发标记(和应用程序并发执行),此过程可能被young GC中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那个这个区域会被立即回收(图中打X)。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)。
GC算法 垃圾收集器.resources/E8512714-576B-47AD-928D-72543F14014C.png)
4、Remark, 再标记,会有短暂停顿(STW)。再标记阶段是用来收集 并发标记阶段 产生新的垃圾(并发阶段和应用程序一同运行);G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning (SATB)。
5、Copy/Clean up,多线程清除失活对象,会有STW。G1将回收区域的存活对象拷贝到新区域,清除Remember Sets,并发清空回收区域并把它返回到空闲区域链表中。
GC算法 垃圾收集器.resources/64EEE171-56CA-4292-B8C8-74512B629639.png)
6、复制/清除过程后。回收区域的活性对象已经被集中回收到深蓝色和深绿色区域。
GC算法 垃圾收集器.resources/A426A5C0-99CE-47B1-933C-EE2E0F34D408.png)
常用的收集器组合
GC算法 垃圾收集器.resources/7B29E97B-2070-485D-AACD-B47E5D8B6B65.png)
================================================
FILE: JVM/jvm系列(二)JVM内存结构.md
================================================
所有的Java开发人员可能会遇到这样的困惑?我该为堆内存设置多大空间呢?OutOfMemoryError的异常到底涉及到运行时数据的哪块区域?该怎么解决呢?其实如果你经常解决服务器性能问题,那么这些问题就会变的非常常见,了解JVM内存也是为了服务器出现性能问题的时候可以快速的了解那块的内存区域出现问题,以便于快速的解决生产故障。
先看一张图,这张图能很清晰的说明JVM内存结构布局。
Java的内存结构:
JVM内存结构.resources/476AD977-8808-4422-8349-06754DB023F5.png)
JVM内存结构主要有三大块:堆内存、方法区和栈。堆内存是JVM中最大的一块由年轻代和老年代组成,而年轻代内存又被分成三部分,Eden空间、From Survivor空间、To Survivor空间,默认情况下年轻代按照8:1:1的比例来分配;
方法区存储类信息、常量、静态变量等数据,是线程共享的区域,为与Java堆区分,方法区还有一个别名Non-Heap(非堆);栈又分为java虚拟机栈和本地方法栈主要用于方法的执行。
在通过一张图来了解如何通过参数来控制各区域的内存大小
JVM内存结构.resources/150E44D4-976A-4736-98B8-6E8681BA307B.png)
控制参数
-Xms设置堆的最小空间大小。
-Xmx设置堆的最大空间大小。
-XX:NewSize设置新生代最小空间大小。
-XX:MaxNewSize设置新生代最大空间大小。
-XX:PermSize设置永久代最小空间大小。
-XX:MaxPermSize设置永久代最大空间大小。
-Xss设置每个线程的堆栈大小。
没有直接设置老年代的参数,但是可以设置堆空间大小和新生代空间大小两个参数来间接控制。
**老年代空间大小=堆空间大小-年轻代大空间大小**
从更高的一个维度再次来看JVM和系统调用之间的关系
JVM内存结构.resources/CD206F3C-4EF8-47AC-9DB5-8B67574E8ADB.png)
方法区和对是所有线程共享的内存区域;而java栈、本地方法栈和程序员计数器是运行是线程私有的内存区域。
下面我们详细介绍每个区域的作用
**Java堆(Heap)**
对于大多数应用来说,Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”。如果从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法,所以Java堆中还可以细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。
根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms控制)。
如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
**方法区(Method Area)**
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。
对于习惯在HotSpot虚拟机上开发和部署程序的开发者来说,很多人愿意把方法区称为“永久代”(Permanent Generation),本质上两者并不等价,仅仅是因为HotSpot虚拟机的设计团队选择把GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已。
Java虚拟机规范对这个区域的限制非常宽松,除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入了方法区就如永久代的名字一样“永久”存在了。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说这个区域的回收“成绩”比较难以令人满意,尤其是类型的卸载,条件相当苛刻,但是这部分区域的回收确实是有必要的。
根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。
**程序计数器(Program Counter Register)**
程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Natvie方法,这个计数器值则为空(Undefined)。
此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
**JVM栈(JVM Stacks)**
与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。
其中64位长度的long和double类型的数据会占用2个局部变量空间(Slot),其余的数据类型只占用1个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
在Java虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展(当前大部分的Java虚拟机都可动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。
**本地方法栈(Native Method Stacks)**
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。
**参考:**
http://ifeve.com/under-the-hood-runtime-data-areas-javas-memory-model/
《深入理解Java虚拟机:JVM高级特性与最佳实践_周志明.高清扫描版.pdf》
下载地址:http://download.csdn.net/detail/ityouknow/9557109
================================================
FILE: JVM/jvm系列(五)Java GC 分析.md
================================================
Java GC就是JVM记录仪,书画了JVM各个分区的表演。
## 什么是 Java GC
Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不需要专门编写内存回收和垃圾清理代码,对内存泄露和溢出的问题,也不需要像C程序员那样战战兢兢。这是因为在Java虚拟机中,存在自动内存管理和垃圾清扫机制。概括地说,该机制对JVM(Java Virtual Machine)中的内存进行标记,并确定哪些内存需要回收,根据一定的回收策略,自动的回收内存,永不停息(Nerver Stop)的保证JVM中的内存空间,防止出现内存泄露和溢出问题。
在Java语言出现之前,就有GC机制的存在,如Lisp语言),Java GC机制已经日臻完善,几乎可以自动的为我们做绝大多数的事情。然而,如果我们从事较大型的应用软件开发,曾经出现过内存优化的需求,就必定要研究Java GC机制。
简单总结一下,Java GC就是通过GC收集器回收不在存活的对象,保证JVM更加高效的运转。
## 如何获取 Java GC日志
一般情况可以通过两种方式来获取GC日志,一种是使用命令动态查看,一种是在容器中设置相关参数打印GC日志。
命令动态查看
Java 自动的工具行命令,jstat可以用来动态监控JVM内存的使用,统计垃圾回收的各项信息。
比如常用命令,jstat -gc 统计垃圾回收堆的行为
```
$ jstat -gc 1262
S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT
26112.0 24064.0 6562.5 0.0 564224.0 76274.5 434176.0 388518.3 524288.0 42724.7 320 6.417 1 0.398 6.815
```
也可以设置间隔固定时间来打印:
```
$ jstat -gc 1262 2000 20
```
这个命令意思就是每隔2000ms输出1262的gc情况,一共输出20次
**GC参数**
JVM的GC日志的主要参数包括如下几个:
-XX:+PrintGC 输出GC日志
-XX:+PrintGCDetails 输出GC的详细日志
-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2017-09-04T21:53:59.234+0800)
-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
-Xloggc:../logs/gc.log 日志文件的输出路径
在生产环境中,根据需要配置相应的参数来监控JVM运行情况。
**Tomcat 设置示例**
我们经常在tomcat的启动参数中添加JVM相关参数,这里有一个典型的示例:
```
JAVA_OPTS="-server -Xms2000m -Xmx2000m -Xmn800m -XX:PermSize=64m -XX:MaxPermSize=256m -XX:SurvivorRatio=4
-verbose:gc -Xloggc:$CATALINA_HOME/logs/gc.log
-Djava.awt.headless=true
-XX:+PrintGCTimeStamps -XX:+PrintGCDetails
-Dsun.rmi.dgc.server.gcInterval=600000 -Dsun.rmi.dgc.client.gcInterval=600000
-XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=15"
```
根据上面的参数我们来做一下解析:
-Xms2000m -Xmx2000m -Xmn800m -XX:PermSize=64m -XX:MaxPermSize=256m
Xms,即为jvm启动时得JVM初始堆大小,Xmx为jvm的最大堆大小,xmn为新生代的大小,permsize为永久代的初始大小,MaxPermSize为永久代的最大空间。
-XX:SurvivorRatio=4
SurvivorRatio为新生代空间中的Eden区和救助空间Survivor区的大小比值,默认是8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10。调小这个参数将增大survivor区,让对象尽量在survitor区呆长一点,减少进入年老代的对象。去掉救助空间的想法是让大部分不能马上回收的数据尽快进入年老代,加快年老代的回收频率,减少年老代暴涨的可能性,这个是通过将-XX:SurvivorRatio 设置成比较大的值(比如65536)来做到。
-verbose:gc -Xloggc:$CATALINA_HOME/logs/gc.log
将虚拟机每次垃圾回收的信息写到日志文件中,文件名由file指定,文件格式是平文件,内容和-verbose:gc输出内容相同。
-Djava.awt.headless=true Headless模式是系统的一种配置模式。在该模式下,系统缺少了显示设备、键盘或鼠标。
-XX:+PrintGCTimeStamps -XX:+PrintGCDetails
设置gc日志的格式
-Dsun.rmi.dgc.server.gcInterval=600000 -Dsun.rmi.dgc.client.gcInterval=600000
指定rmi调用时gc的时间间隔
-XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=15 采用并发gc方式,经过15次minor gc 后进入年老代
## 如何分析GC日志
摘录GC日志一部分
Young GC回收日志:
```
2016-07-05T10:43:18.093+0800: 25.395: [GC [PSYoungGen: 274931K->10738K(274944K)] 371093K->147186K(450048K), 0.0668480 secs] [Times: user=0.17 sys=0.08, real=0.07 secs]
```
Full GC回收日志:
```
2016-07-05T10:43:18.160+0800: 25.462: [Full GC [PSYoungGen: 10738K->0K(274944K)] [ParOldGen: 136447K->140379K(302592K)] 147186K->140379K(577536K) [PSPermGen: 85411K->85376K(171008K)], 0.6763541 secs] [Times: user=1.75 sys=0.02, real=0.68 secs]
```
通过上面日志分析得出,PSYoungGen、ParOldGen、PSPermGen属于Parallel收集器。其中PSYoungGen表示gc回收前后年轻代的内存变化;ParOldGen表示gc回收前后老年代的内存变化;PSPermGen表示gc回收前后永久区的内存变化。young gc 主要是针对年轻代进行内存回收比较频繁,耗时短;full gc 会对整个堆内存进行回城,耗时长,因此一般尽量减少full gc的次数
通过两张图非常明显看出gc日志构成:
Young GC日志:
Java%20GC%20%E5%88%86%E6%9E%90.resources/253C4E10-C025-406F-BCEC-360BD0B901AC.png)
Full GC日志:
Java%20GC%20%E5%88%86%E6%9E%90.resources/E41583CF-6306-4B8F-95D7-396A3B91FBB1.png)
## GC分析工具
GChisto
GChisto是一款专业分析gc日志的工具,可以通过gc日志来分析:Minor GC、full gc的时间、频率等等,通过列表、报表、图表等不同的形式来反应gc的情况。虽然界面略显粗糙,但是功能还是不错的。
配置好本地的jdk环境之后,双击GChisto.jar,在弹出的输入框中点击 add 选择gc.log日志
Java%20GC%20%E5%88%86%E6%9E%90.resources/3BC499FA-2D44-4448-9720-AA4734BA2290.jpg)
GC Pause Stats:可以查看GC 的次数、GC的时间、GC的开销、最大GC时间和最小GC时间等,以及相应的柱状图
Java%20GC%20%E5%88%86%E6%9E%90.resources/175F8410-B0BD-4288-A2C2-5C35AF57F933.jpg)
GC Pause Distribution:查看GC停顿的详细分布,x轴表示垃圾收集停顿时间,y轴表示是停顿次数。
GC Timeline:显示整个时间线上的垃圾收集
Java%20GC%20%E5%88%86%E6%9E%90.resources/1550316713780.jpg)
不过这款工具已经不再维护
GC Easy
这是一个web工具,在线使用非常方便.
地址: http://gceasy.io
进入官网,讲打包好的zip或者gz为后缀的压缩包上传,过一会就会拿到分析结果。
Java%20GC%20%E5%88%86%E6%9E%90.resources/1550316679385.jpg)
推荐使用此工具进行gc分析。
Java%20GC%20%E5%88%86%E6%9E%90.resources/1550316713780.jpg)
================================================
FILE: JVM/jvm系列(四)jvm调优-命令大全(jps jstat jmap jhat jstack jinfo).md
================================================
## 简介
运用jvm自带的命令可以方便的在生产监控和打印堆栈的日志信息帮忙我们来定位问题!虽然jvm调优成熟的工具已经有很多:jconsole、大名鼎鼎的VisualVM,IBM的Memory Analyzer等等,但是在生产环境出现问题的时候,一方面工具的使用会有所限制,另一方面喜欢装X的我们,总喜欢在出现问题的时候在终端输入一些命令来解决。所有的工具几乎都是依赖于jdk的接口和底层的这些命令,研究这些命令的使用也让我们更能了解jvm构成和特性。
Sun JDK监控和故障处理命令有jps jstat jmap jhat jstack jinfo下面做一一介绍
**jps**
JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程。
命令格式
jps [options] [hostid]
option参数
-l : 输出主类全名或jar路径
-q : 只输出LVMID
-m : 输出JVM启动时传递给main()的参数
-v : 输出JVM启动时显示指定的JVM参数
其中[option]、[hostid]参数也可以不写。
示例
```
$ jps -l -m
28920 org.apache.catalina.startup.Bootstrap start
11589 org.apache.catalina.startup.Bootstrap start
25816 sun.tools.jps.Jps -l -m
```
**jstat**
jstat(JVM statistics Monitoring)是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。
命令格式
jstat [option] LVMID [interval] [count]
参数
[option] : 操作参数
LVMID : 本地虚拟机进程ID
[interval] : 连续输出的时间间隔
[count] : 连续输出的次数
option 参数总览
jvm调优-命令大全(jps jstat jmap jhat jstack jinfo).resources/3358A9D6-B4C7-4831-B26B-AD24973357EB.png)
option 参数详解
-class
监视类装载、卸载数量、总空间以及耗费的时间
```
$ jstat -class 11589
Loaded Bytes Unloaded Bytes Time
7035 14506.3 0 0.0 3.67
Loaded : 加载class的数量
Bytes : class字节大小
Unloaded : 未加载class的数量
Bytes : 未加载class的字节大小
Time : 加载时间
-compiler
输出JIT编译过的方法数量耗时等
```
$ jstat -compiler 1262
Compiled Failed Invalid Time FailedType FailedMethod
2573 1 0 47.60 1 org/apache/catalina/loader/WebappClassLoader findResourceInternal
Compiled : 编译数量
Failed : 编译失败数量
Invalid : 无效数量
Time : 编译耗时
FailedType : 失败类型
FailedMethod : 失败方法的全限定名
-gc
垃圾回收堆的行为统计,常用命令
```
$ jstat -gc 1262
S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT
26112.0 24064.0 6562.5 0.0 564224.0 76274.5 434176.0 388518.3 524288.0 42724.7 320 6.417 1 0.398 6.815
```
C即Capacity 总容量,U即Used 已使用的容量
S0C : survivor0区的总容量
S1C : survivor1区的总容量
S0U : survivor0区已使用的容量
S1U : survivor1区已使用的容量
EC : Eden区的总容量
EU : Eden区已使用的容量
OC : Old区的总容量
OU : Old区已使用的容量
PC 当前perm的容量 (KB)
PU perm的使用 (KB)
YGC : 新生代垃圾回收次数
YGCT : 新生代垃圾回收时间
FGC : 老年代垃圾回收次数
FGCT : 老年代垃圾回收时间
GCT : 垃圾回收总消耗时间
```
$ jstat -gc 1262 2000 20
```
这个命令意思就是每隔2000ms输出1262的gc情况,一共输出20次
-gccapacity
同-gc,不过还会输出Java堆各区域使用到的最大、最小空间
```
$ jstat -gccapacity 1262
NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC PGCMN PGCMX PGC PC YGC FGC
614400.0 614400.0 614400.0 26112.0 24064.0 564224.0 434176.0 434176.0 434176.0 434176.0 524288.0 1048576.0 524288.0 524288.0 320 1
```
NGCMN : 新生代占用的最小空间
NGCMX : 新生代占用的最大空间
OGCMN : 老年代占用的最小空间
OGCMX : 老年代占用的最大空间
OGC:当前年老代的容量 (KB)
OC:当前年老代的空间 (KB)
PGCMN : perm占用的最小空间
PGCMX : perm占用的最大空间
-gcutil
同-gc,不过输出的是已使用空间占总空间的百分比
```
$ jstat -gcutil 28920
S0 S1 E O P YGC YGCT FGC FGCT GCT
12.45 0.00 33.85 0.00 4.44 4 0.242 0 0.000 0.242
```
-gccause
垃圾收集统计概述(同-gcutil),附加最近两次垃圾回收事件的原因
```
$ jstat -gccause 28920
S0 S1 E O P YGC YGCT FGC FGCT GCT LGCC GCC
12.45 0.00 33.85 0.00 4.44 4 0.242 0 0.000 0.242 Allocation Failure No GC
```
LGCC:最近垃圾回收的原因
GCC:当前垃圾回收的原因
-gcnew
统计新生代的行为
```
$ jstat -gcnew 28920
S0C S1C S0U S1U TT MTT DSS EC EU YGC YGCT
419392.0 419392.0 52231.8 0.0 6 6 209696.0 3355520.0 1172246.0 4 0.242
```
TT:Tenuring threshold(提升阈值)
MTT:最大的tenuring threshold
DSS:survivor区域大小 (KB)
-gcnewcapacity
新生代与其相应的内存空间的统计
```
$ jstat -gcnewcapacity 28920
NGCMN NGCMX NGC S0CMX S0C S1CMX S1C ECMX EC YGC FGC
4194304.0 4194304.0 4194304.0 419392.0 419392.0 419392.0 419392.0 3355520.0 3355520.0 4 0
```
NGC:当前年轻代的容量 (KB)
S0CMX:最大的S0空间 (KB)
S0C:当前S0空间 (KB)
ECMX:最大eden空间 (KB)
EC:当前eden空间 (KB)
-gcold
统计旧生代的行为
```
$ jstat -gcold 28920
PC PU OC OU YGC FGC FGCT GCT
1048576.0 46561.7 6291456.0 0.0 4 0 0.000 0.242
-gcoldcapacity
```
统计旧生代的大小和空间
```
$ jstat -gcoldcapacity 28920
OGCMN OGCMX OGC OC YGC FGC FGCT GCT
6291456.0 6291456.0 6291456.0 6291456.0 4 0 0.000 0.242
```
-gcpermcapacity
永生代行为统计
```
$ jstat -gcpermcapacity 28920
PGCMN PGCMX PGC PC YGC FGC FGCT GCT
1048576.0 2097152.0 1048576.0 1048576.0 4 0 0.000 0.242
```
-printcompilation
hotspot编译方法统计
```
$ jstat -printcompilation 28920
Compiled Size Type Method
1291 78 1 java/util/ArrayList indexOf
```
Compiled:被执行的编译任务的数量
Size:方法字节码的字节数
Type:编译类型
Method:编译方法的类名和方法名。类名使用”/” 代替 “.” 作为空间分隔符. 方法名是给出类的方法名. 格式是一致于HotSpot - XX:+PrintComplation 选项
**jmap**
jmap(JVM Memory Map)命令用于生成heap dump文件,如果不使用这个命令,还阔以使用-XX:+HeapDumpOnOutOfMemoryError参数来让虚拟机出现OOM的时候·自动生成dump文件。 jmap不仅能生成dump文件,还阔以查询finalize执行队列、Java堆和永久代的详细信息,如当前使用率、当前使用的是哪种收集器等。
命令格式
jmap [option] LVMID
option参数
dump : 生成堆转储快照
finalizerinfo : 显示在F-Queue队列等待Finalizer线程执行finalizer方法的对象
heap : 显示Java堆详细信息
histo : 显示堆中对象的统计信息
permstat : to print permanent generation statistics
F : 当-dump没有响应时,强制生成dump快照
示例
-dump
常用格式
```
-dump::live,format=b,file=<filename> pid
```
dump堆到文件,format指定输出格式,live指明是活着的对象,file指定文件名
```
$ jmap -dump:live,format=b,file=dump.hprof 28920
Dumping heap to /home/xxx/dump.hprof ...
Heap dump file created
```
dump.hprof这个后缀是为了后续可以直接用MAT(Memory Anlysis Tool)打开。
-finalizerinfo
打印等待回收对象的信息
```
$ jmap -finalizerinfo 28920
Attaching to process ID 28920, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 24.71-b01
Number of objects pending for finalization: 0
```
可以看到当前F-QUEUE队列中并没有等待Finalizer线程执行finalizer方法的对象。
-heap
打印heap的概要信息,GC使用的算法,heap的配置及wise heap的使用情况,可以用此来判断内存目前的使用情况以及垃圾回收情况
```
$ jmap -heap 28920
Attaching to process ID 28920, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 24.71-b01
using thread-local object allocation.
Parallel GC with 4 thread(s)//GC 方式
Heap Configuration: //堆内存初始化配置
MinHeapFreeRatio = 0 //对应jvm启动参数-XX:MinHeapFreeRatio设置JVM堆最小空闲比率(default 40)
MaxHeapFreeRatio = 100 //对应jvm启动参数 -XX:MaxHeapFreeRatio设置JVM堆最大空闲比率(default 70)
MaxHeapSize = 2082471936 (1986.0MB) //对应jvm启动参数-XX:MaxHeapSize=设置JVM堆的最大大小
NewSize = 1310720 (1.25MB)//对应jvm启动参数-XX:NewSize=设置JVM堆的‘新生代’的默认大小
MaxNewSize = 17592186044415 MB//对应jvm启动参数-XX:MaxNewSize=设置JVM堆的‘新生代’的最大大小
OldSize = 5439488 (5.1875MB)//对应jvm启动参数-XX:OldSize=<value>:设置JVM堆的‘老生代’的大小
NewRatio = 2 //对应jvm启动参数-XX:NewRatio=:‘新生代’和‘老生代’的大小比率
SurvivorRatio = 8 //对应jvm启动参数-XX:SurvivorRatio=设置年轻代中Eden区与Survivor区的大小比值
PermSize = 21757952 (20.75MB) //对应jvm启动参数-XX:PermSize=<value>:设置JVM堆的‘永生代’的初始大小
MaxPermSize = 85983232 (82.0MB)//对应jvm启动参数-XX:MaxPermSize=<value>:设置JVM堆的‘永生代’的最大大小
G1HeapRegionSize = 0 (0.0MB)
Heap Usage://堆内存使用情况
PS Young Generation
Eden Space://Eden区内存分布
capacity = 33030144 (31.5MB)//Eden区总容量
used = 1524040 (1.4534378051757812MB) //Eden区已使用
free = 31506104 (30.04656219482422MB) //Eden区剩余容量
4.614088270399305% used //Eden区使用比率
From Space: //其中一个Survivor区的内存分布
capacity = 5242880 (5.0MB)
used = 0 (0.0MB)
free = 5242880 (5.0MB)
0.0% used
To Space: //另一个Survivor区的内存分布
capacity = 5242880 (5.0MB)
used = 0 (0.0MB)
free = 5242880 (5.0MB)
0.0% used
PS Old Generation //当前的Old区内存分布
capacity = 86507520 (82.5MB)
used = 0 (0.0MB)
free = 86507520 (82.5MB)
0.0% used
PS Perm Generation//当前的 “永生代” 内存分布
capacity = 22020096 (21.0MB)
used = 2496528 (2.3808746337890625MB)
free = 19523568 (18.619125366210938MB)
11.337498256138392% used
670 interned Strings occupying 43720 bytes.
```
可以很清楚的看到Java堆中各个区域目前的情况。
-histo
打印堆的对象统计,包括对象数、内存大小等等 (因为在dump:live前会进行full gc,如果带上live则只统计活对象,因此不加live的堆大小要大于加live堆的大小 )
```
$ jmap -histo:live 28920 | more
num #instances #bytes class name
----------------------------------------------
1: 83613 12012248 <constMethodKlass>
2: 23868 11450280 [B
3: 83613 10716064 <methodKlass>
4: 76287 10412128 [C
5: 8227 9021176 <constantPoolKlass>
6: 8227 5830256 <instanceKlassKlass>
7: 7031 5156480 <constantPoolCacheKlass>
8: 73627 1767048 java.lang.String
9: 2260 1348848 <methodDataKlass>
10: 8856 849296 java.lang.Class
....
```
仅仅打印了前10行
xml class name是对象类型,说明如下:
B byte
C char
D double
F float
I int
J long
Z boolean
[ 数组,如[I表示int[]
[L+类名 其他对象
-permstat
打印Java堆内存的永久保存区域的类加载器的智能统计信息。对于每个类加载器而言,它的名称、活跃度、地址、父类加载器、它所加载的类的数量和大小都会被打印。此外,包含的字符串数量和大小也会被打印。
```
$ jmap -permstat 28920
Attaching to process ID 28920, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 24.71-b01
finding class loader instances ..done.
computing per loader stat ..done.
please wait.. computing liveness.liveness analysis may be inaccurate ...
class_loader classes bytes parent_loader alive? type
<bootstrap> 3111 18154296 null live <internal>
0x0000000600905cf8 1 1888 0x0000000600087f08 dead sun/reflect/DelegatingClassLoader@0x00000007800500a0
0x00000006008fcb48 1 1888 0x0000000600087f08 dead sun/reflect/DelegatingClassLoader@0x00000007800500a0
0x00000006016db798 0 0 0x00000006008d3fc0 dead java/util/ResourceBundle$RBClassLoader@0x0000000780626ec0
0x00000006008d6810 1 3056 null dead sun/reflect/DelegatingClassLoader@0x00000007800500a0
```
-F
强制模式。如果指定的pid没有响应,请使用jmap -dump或jmap -histo选项。此模式下,不支持live子选项。
jhat
jhat(JVM Heap Analysis Tool)命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看。在此要注意,一般不会直接在服务器上进行分析,因为jhat是一个耗时并且耗费硬件资源的过程,一般把服务器生成的dump文件复制到本地或其他机器上进行分析。
命令格式
jhat [dumpfile]
参数
* -stack false|true 关闭对象分配调用栈跟踪(tracking object allocation call stack)。 如果分配位置信息在堆转储中不可用. 则必须将此标志设置为 false. 默认值为 true.>
* -refs false|true 关闭对象引用跟踪(tracking of references to objects)。 默认值为 true. 默认情况下, 返回的指针是指向其他特定对象的对象,如反向链接或输入引用(referrers or incoming references), 会统计/计算堆中的所有对象。>
* -port port-number 设置 jhat HTTP server 的端口号. 默认值 7000.>
* -exclude exclude-file 指定对象查询时需要排除的数据成员列表文件(a file that lists data members that should be excluded from the reachable objects query)。 例如, 如果文件列列出了 java.lang.String.value , 那么当从某个特定对象 Object o 计算可达的对象列表时, 引用路径涉及 java.lang.String.value 的都会被排除。>
* -baseline exclude-file 指定一个基准堆转储(baseline heap dump)。 在两个 heap dumps 中有相同 object ID 的对象会被标记为不是新的(marked as not being new). 其他对象被标记为新的(new). 在比较两个不同的堆转储时很有用.>
* -debug int 设置 debug 级别. 0 表示不输出调试信息。 值越大则表示输出更详细的 debug 信息.>
* -version 启动后只显示版本信息就退出>
* -J< flag > 因为 jhat 命令实际上会启动一个JVM来执行, 通过 -J 可以在启动JVM时传入一些启动参数. 例如, -J-Xmx512m 则指定运行 jhat 的Java虚拟机使用的最大堆内存为 512 MB. 如果需要使用多个JVM启动参数,则传入多个 -Jxxxxxx.
示例
```
$ jhat -J-Xmx512m dump.hprof
eading from dump.hprof...
Dump file created Fri Mar 11 17:13:42 CST 2016
Snapshot read, resolving...
Resolving 271678 objects...
Chasing references, expect 54 dots......................................................
Eliminating duplicate references......................................................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.
```
中间的-J-Xmx512m是在dump快照很大的情况下分配512M内存去启动HTTP服务器,运行完之后就可在浏览器打开Http://localhost:7000进行快照分析 堆快照分析主要在最后面的Heap Histogram里,里面根据class列出了dump的时候所有存活对象。
分析同样一个dump快照,MAT需要的额外内存比jhat要小的多的多,所以建议使用MAT来进行分析,当然也看个人偏好。
分析
打开浏览器Http://localhost:7000,该页面提供了几个查询功能可供使用:
```
All classes including platform
Show all members of the rootset
Show instance counts for all classes (including platform)
Show instance counts for all classes (excluding platform)
Show heap histogram
Show finalizer summary
Execute Object Query Language (OQL) query
```
一般查看堆异常情况主要看这个两个部分: Show instance counts for all classes (excluding platform),平台外的所有对象信息。如下图:
jvm调优-命令大全(jps jstat jmap jhat jstack jinfo).resources/E7DB83E0-2344-4149-8603-C606D78AB943.png)
Show heap histogram 以树状图形式展示堆情况。如下图:jvm调优-命令大全(jps jstat jmap jhat jstack jinfo).resources/8DE752AB-6C96-4A58-8E01-8970BA3E2014.png)
具体排查时需要结合代码,观察是否大量应该被回收的对象在一直被引用或者是否有占用内存特别大的对象无法被回收。一般情况,会down到客户端用工具来分析
**jstack**
jstack用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。 如果java程序崩溃生成core文件,jstack工具可以用来获得core文件的java stack和native stack的信息,从而可以轻松地知道java程序是如何崩溃和在程序何处发生问题。另外,jstack工具还可以附属到正在运行的java程序中,看到当时运行的java程序的java stack和native stack的信息, 如果现在运行的java程序呈现hung的状态,jstack是非常有用的。
命令格式
jstack [option] LVMID
option参数
-F : 当正常输出请求不被响应时,强制输出线程堆栈
-l : 除堆栈外,显示关于锁的附加信息
-m : 如果调用到本地方法的话,可以显示C/C++的堆栈
示例
```
$ jstack -l 11494|more
2016-07-28 13:40:04
Full thread dump Java HotSpot(TM) 64-Bit Server VM (24.71-b01 mixed mode):
"Attach Listener" daemon prio=10 tid=0x00007febb0002000 nid=0x6b6f waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"http-bio-8005-exec-2" daemon prio=10 tid=0x00007feb94028000 nid=0x7b8c waiting on condition [0x00007fea8f56e000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000cae09b80> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2043)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1068)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- None
.....
```
这里有一篇文章解释的很好 [分析打印出的文件内容](http://www.hollischuang.com/archives/110)
jinfo
jinfo(JVM Configuration info)这个命令作用是实时查看和调整虚拟机运行参数。 之前的jps -v口令只能查看到显示指定的参数,如果想要查看未被显示指定的参数的值就要使用jinfo口令
**jinfo**
jinfo(JVM Configuration info)这个命令作用是实时查看和调整虚拟机运行参数。 之前的jps -v口令只能查看到显示指定的参数,如果想要查看未被显示指定的参数的值就要使用jinfo口令
命令格式
jinfo [option] [args] LVMID
option参数
-flag : 输出指定args参数的值
-flags : 不需要args参数,输出所有JVM参数的值
-sysprops : 输出系统属性,等同于System.getProperties()
示例
```
$ jinfo -flag 11494
-XX:CMSInitiatingOccupancyFraction=80
```
================================================
FILE: JVM/内存分配与回收策略.md
================================================
# 内存分配与回收策略
对象的内存分配,就是在堆上分配(也可能经过 JIT 编译后被拆散为标量类型并间接在栈上分配),对象主要分配在新生代的 Eden 区上,少数情况下可能直接分配在老年代,**分配规则不固定**,取决于当前使用的垃圾收集器组合以及相关的参数配置。
以下列举几条最普遍的内存分配规则,供大家学习。
## 对象优先在 Eden 分配
大多数情况下,对象在新生代 Eden 区中分配。当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC。
👇**Minor GC** vs **Major GC**/**Full GC**:
* Minor GC:回收新生代(包括 Eden 和 Survivor 区域),因为 Java 对象大多都具备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快。
* Major GC / Full GC: 回收老年代,出现了 Major GC,经常会伴随至少一次的 Minor GC,但这并非绝对。Major GC 的速度一般会比 Minor GC 慢 10 倍 以上。
> 在 JVM 规范中,Major GC 和 Full GC 都没有一个正式的定义,所以有人也简单地认为 Major GC 清理老年代,而 Full GC 清理整个内存堆。
## 大对象直接进入老年代
大对象是指需要大量连续内存空间的 Java 对象,如很长的字符串或数据。
一个大对象能够存入 Eden 区的概率比较小,发生分配担保的概率比较大,而分配担保需要涉及大量的复制,就会造成效率低下。
虚拟机提供了一个 -XX:PretenureSizeThreshold 参数,令大于这个设置值的对象直接在老年代分配,这样做的目的是避免在 Eden 区及两个 Survivor 区之间发生大量的内存复制。(还记得吗,新生代采用复制算法回收垃圾)
## 长期存活的对象将进入老年代
JVM 给每个对象定义了一个对象年龄计数器。当新生代发生一次 Minor GC 后,存活下来的对象年龄 +1,当年龄超过一定值时,就将超过该值的所有对象转移到老年代中去。
使用 -XXMaxTenuringThreshold 设置新生代的最大年龄,只要超过该参数的新生代对象都会被转移到老年代中去。
## 动态对象年龄判定
如果当前新生代的 Survivor 中,相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄 >= 该年龄的对象就可以直接进入老年代,无须等到 MaxTenuringThreshold 中要求的年龄。
## 空间分配担保
JDK 6 Update 24 之前的规则是这样的:
在发生 Minor GC 之前,虚拟机会先检查**老年代最大可用的连续空间是否大于新生代所有对象总空间**, 如果这个条件成立,Minor GC 可以确保是安全的; 如果不成立,则虚拟机会查看 HandlePromotionFailure 值是否设置为允许担保失败, 如果是,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小, 如果大于,将尝试进行一次 Minor GC,尽管这次 Minor GC 是有风险的; 如果小于,或者 HandlePromotionFailure 设置不允许冒险,那此时也要改为进行一次 Full GC。
JDK 6 Update 24 之后的规则变为:
只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小,就会进行 Minor GC,否则将进行 Full GC。
通过清除老年代中废弃数据来扩大老年代空闲空间,以便给新生代作担保。
这个过程就是分配担保。
---
👇总结一下有哪些情况可能会触发 JVM 进行 Full GC。
1. System.gc() 方法的调用<br>
此方法的调用是建议 JVM 进行 Full GC,注意这**只是建议而非一定**,但在很多情况下它会触发 Full GC,从而增加 Full GC 的频率。通常情况下我们只需要让虚拟机自己去管理内存即可,我们可以通过 -XX:+ DisableExplicitGC 来禁止调用 System.gc()。
2. 老年代空间不足<br>
老年代空间不足会触发 Full GC操作,若进行该操作后空间依然不足,则会抛出如下错误:<br>
` java.lang.OutOfMemoryError: Java heap space `
3. 永久代空间不足<br>
JVM 规范中运行时数据区域中的方法区,在 HotSpot 虚拟机中也称为永久代(Permanet Generation),存放一些类信息、常量、静态变量等数据,当系统要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,会触发 Full GC。如果经过 Full GC 仍然回收不了,那么 JVM 会抛出如下错误信息:<br>
`java.lang.OutOfMemoryError: PermGen space `
4. CMS GC 时出现 promotion failed 和 concurrent mode failure<br>
promotion failed,就是上文所说的担保失败,而 concurrent mode failure 是在执行 CMS GC 的过程中同时有对象要放入老年代,而此时老年代空间不足造成的。
5. 统计得到的Minor GC晋升到旧生代的平均大小大于老年代的剩余空间
(完)
---
👉 [Previous](/docs/04-hotspot-gc.md)<br>
👉 [Next](/docs/06-jvm-performance-tuning.md)<br>
👉 [Back to README](../README.md)
================================================
FILE: JVM/垃圾收集策略与算法.md
================================================
# 垃圾收集策略与算法
程序计数器、虚拟机栈、本地方法栈随线程而生,也随线程而灭;栈帧随着方法的开始而入栈,随着方法的结束而出栈。这几个区域的内存分配和回收都具有确定性,在这几个区域内不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了。
而对于 Java 堆和方法区,我们只有在程序运行期间才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,垃圾收集器所关注的正是这部分内存。
## 判定对象是否存活
若一个对象不被任何对象或变量引用,那么它就是无效对象,需要被回收。
### 引用计数法
在对象头维护着一个 counter 计数器,对象被引用一次则计数器 +1;若引用失效则计数器 -1。当计数器为 0 时,就认为该对象无效了。
引用计数算法的实现简单,判定效率也很高,在大部分情况下它都是一个不错的算法。但是主流的 Java 虚拟机里没有选用引用计数算法来管理内存,主要是因为它很难解决对象之间循环引用的问题。
> 举个栗子👉对象 objA 和 objB 都有字段 instance,令 objA.instance = objB 并且 objB.instance = objA,由于它们互相引用着对方,导致它们的引用计数都不为 0,于是引用计数算法无法通知 GC 收集器回收它们。
### 可达性分析法
所有和 GC Roots 直接或间接关联的对象都是有效对象,和 GC Roots 没有关联的对象就是无效对象。
GC Roots 是指:
* Java 虚拟机栈(栈帧中的本地变量表)中引用的对象
* 本地方法栈中引用的对象
* 方法区中常量引用的对象
* 方法区中类静态属性引用的对象
GC Roots 并不包括堆中对象所引用的对象,这样就不会有循环引用的问题。
## 引用的种类
判定对象是否存活与“引用”有关。在 JDK 1.2 以前,Java 中的引用定义很传统,一个对象只有被引用或者没有被引用两种状态,我们希望能描述这一类对象:当内存空间还足够时,则保留在内存中;如果内存空间在进行垃圾手收集后还是非常紧张,则可以抛弃这些对象。很多系统的缓存功能都符合这样的应用场景。
在 JDK 1.2 之后,Java 对引用的概念进行了扩充,将引用分为了以下四种。不同的引用类型,主要体现的是对象不同的可达性状态`reachable`和垃圾收集的影响。
### 强引用(Strong Reference)
类似 "Object obj = new Object()" 这类的引用,就是强引用,只要强引用存在,垃圾收集器永远不会回收被引用的对象。但是,如果我们**错误地保持了强引用**,比如:赋值给了 static 变量,那么对象在很长一段时间内不会被回收,会产生内存泄漏。
### 软引用(Soft Reference)
软引用是一种相对强引用弱化一些的引用,可以让对象豁免一些垃圾收集,只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象。JVM 会确保在抛出 OutOfMemoryError 之前,清理软引用指向的对象。软引用通常用来**实现内存敏感的缓存**,如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。
### 弱引用(Weak Reference)
弱引用的**强度比软引用更弱**一些。当 JVM 进行垃圾回收时,**无论内存是否充足,都会回收**被软引用关联的对象。
### 虚引用(Phantom Reference)
虚引用也称幽灵引用或者幻影引用,它是**最弱**的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响。它仅仅是提供了一种确保对象被 finalize 以后,做某些事情的机制,比如,通常用来做所谓的 Post-Mortem 清理机制。
## 回收堆中无效对象
对于可达性分析中不可达的对象,也并不是没有存活的可能。
### 判定 finalize() 是否有必要执行
JVM 会判断此对象是否有必要执行 finalize() 方法,如果对象没有覆盖 finalize() 方法,或者 finalize() 方法已经被虚拟机调用过,那么视为“没有必要执行”。那么对象基本上就真的被回收了。
如果对象被判定为有必要执行 finalize() 方法,那么对象会被放入一个 F-Queue 队列中,虚拟机会以较低的优先级执行这些 finalize()方法,但不会确保所有的 finalize() 方法都会执行结束。如果 finalize() 方法出现耗时操作,虚拟机就直接停止指向该方法,将对象清除。
### 对象重生或死亡
如果在执行 finalize() 方法时,将 this 赋给了某一个引用,那么该对象就重生了。如果没有,那么就会被垃圾收集器清除。
> 任何一个对象的 finalize() 方法只会被系统自动调用一次,如果对象面临下一次回收,它的 finalize() 方法不会被再次执行,想继续在 finalize() 中自救就失效了。
## 回收方法区内存
方法区中存放生命周期较长的类信息、常量、静态变量,每次垃圾收集只有少量的垃圾被清除。方法区中主要清除两种垃圾:
* 废弃常量
* 无用的类
### 判定废弃常量
只要常量池中的常量不被任何变量或对象引用,那么这些常量就会被清除掉。比如,一个字符串 "bingo" 进入了常量池,但是当前系统没有任何一个 String 对象引用常量池中的 "bingo" 常量,也没有其它地方引用这个字面量,必要的话,"bingo"常量会被清理出常量池。
### 判定无用的类
判定一个类是否是“无用的类”,条件较为苛刻。
* 该类的所有对象都已经被清除
* 加载该类的 ClassLoader 已经被回收
* 该类的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
> 一个类被虚拟机加载进方法区,那么在堆中就会有一个代表该类的对象:java.lang.Class。这个对象在类被加载进方法区时创建,在方法区该类被删除时清除。
## 垃圾收集算法
学会了如何判定无效对象、无用类、废弃常量之后,剩余工作就是回收这些垃圾。常见的垃圾收集算法有以下几个:
### 标记-清除算法
判断哪些数据需要清除,并对它们进行标记,然后清除被标记的数据。
这种方法有两个**不足**:
* 效率问题:标记和清除两个过程的效率都不高。
* 空间问题:标记清除之后会产生大量不连续的内存碎片,碎片太多可能导致以后需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
### 复制算法(新生代)
为了解决效率问题,“复制”收集算法出现了。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块内存用完,需要进行垃圾收集时,就将存活者的对象复制到另一块上面,然后将第一块内存全部清除。这种算法有优有劣:
* 优点:不会有内存碎片的问题。
* 缺点:内存缩小为原来的一半,浪费空间。
为了解决空间利用率问题,可以将内存分为三块: Eden、From Survivor、To Survivor,比例是 8:1:1,每次使用 Eden 和其中一块 Survivor。回收时,将 Eden 和 Survivor 中还存活的对象一次性复制到另外一块 Survivor 空间上,最后清理掉 Eden 和刚才使用的 Survivor 空间。这样只有 10% 的内存被浪费。
但是我们无法保证每次回收都只有不多于 10% 的对象存活,当 Survivor 空间不够,需要依赖其他内存(指老年代)进行分配担保。
#### 分配担保
为对象分配内存空间时,如果 Eden+Survivor 中空闲区域无法装下该对象,会触发 MinorGC 进行垃圾收集。但如果 Minor GC 过后依然有超过 10% 的对象存活,这样存活的对象直接通过分配担保机制进入老年代,然后再将新对象存入 Eden 区。
### 标记-整理算法(老年代)
在回收垃圾前,首先将废弃对象做上标记,然后将未标记的对象移到一边,最后清空另一边区域即可。
这是一种老年代的垃圾收集算法。老年代的对象一般寿命比较长,因此每次垃圾回收会有大量对象存活,如果采用复制算法,每次需要复制大量存活的对象,效率很低。
### 分代收集算法
根据对象存活周期的不同,将内存划分为几块。一般是把 Java 堆分为新生代和老年代,针对各个年代的特点采用最适当的收集算法。
* 新生代:复制算法
* 老年代:标记-清除算法、标记-整理算法
(完)
================================================
FILE: JVM/我的Markdown笔记/jvm系列(五)Java GC 分析.md
================================================
Java GC就是JVM记录仪,书画了JVM各个分区的表演。
## 什么是 Java GC
Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不需要专门编写内存回收和垃圾清理代码,对内存泄露和溢出的问题,也不需要像C程序员那样战战兢兢。这是因为在Java虚拟机中,存在自动内存管理和垃圾清扫机制。概括地说,该机制对JVM(Java Virtual Machine)中的内存进行标记,并确定哪些内存需要回收,根据一定的回收策略,自动的回收内存,永不停息(Nerver Stop)的保证JVM中的内存空间,防止出现内存泄露和溢出问题。
在Java语言出现之前,就有GC机制的存在,如Lisp语言),Java GC机制已经日臻完善,几乎可以自动的为我们做绝大多数的事情。然而,如果我们从事较大型的应用软件开发,曾经出现过内存优化的需求,就必定要研究Java GC机制。
简单总结一下,Java GC就是通过GC收集器回收不在存活的对象,保证JVM更加高效的运转。
## 如何获取 Java GC日志
一般情况可以通过两种方式来获取GC日志,一种是使用命令动态查看,一种是在容器中设置相关参数打印GC日志。
命令动态查看
Java 自动的工具行命令,jstat可以用来动态监控JVM内存的使用,统计垃圾回收的各项信息。
比如常用命令,jstat -gc 统计垃圾回收堆的行为
```
$ jstat -gc 1262
S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT
26112.0 24064.0 6562.5 0.0 564224.0 76274.5 434176.0 388518.3 524288.0 42724.7 320 6.417 1 0.398 6.815
```
也可以设置间隔固定时间来打印:
```
$ jstat -gc 1262 2000 20
```
这个命令意思就是每隔2000ms输出1262的gc情况,一共输出20次
**GC参数**
JVM的GC日志的主要参数包括如下几个:
-XX:+PrintGC 输出GC日志
-XX:+PrintGCDetails 输出GC的详细日志
-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2017-09-04T21:53:59.234+0800)
-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
-Xloggc:../logs/gc.log 日志文件的输出路径
在生产环境中,根据需要配置相应的参数来监控JVM运行情况。
**Tomcat 设置示例**
我们经常在tomcat的启动参数中添加JVM相关参数,这里有一个典型的示例:
```
JAVA_OPTS="-server -Xms2000m -Xmx2000m -Xmn800m -XX:PermSize=64m -XX:MaxPermSize=256m -XX:SurvivorRatio=4
-verbose:gc -Xloggc:$CATALINA_HOME/logs/gc.log
-Djava.awt.headless=true
-XX:+PrintGCTimeStamps -XX:+PrintGCDetails
-Dsun.rmi.dgc.server.gcInterval=600000 -Dsun.rmi.dgc.client.gcInterval=600000
-XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=15"
```
根据上面的参数我们来做一下解析:
-Xms2000m -Xmx2000m -Xmn800m -XX:PermSize=64m -XX:MaxPermSize=256m
Xms,即为jvm启动时得JVM初始堆大小,Xmx为jvm的最大堆大小,xmn为新生代的大小,permsize为永久代的初始大小,MaxPermSize为永久代的最大空间。
-XX:SurvivorRatio=4
SurvivorRatio为新生代空间中的Eden区和救助空间Survivor区的大小比值,默认是8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10。调小这个参数将增大survivor区,让对象尽量在survitor区呆长一点,减少进入年老代的对象。去掉救助空间的想法是让大部分不能马上回收的数据尽快进入年老代,加快年老代的回收频率,减少年老代暴涨的可能性,这个是通过将-XX:SurvivorRatio 设置成比较大的值(比如65536)来做到。
-verbose:gc -Xloggc:$CATALINA_HOME/logs/gc.log
将虚拟机每次垃圾回收的信息写到日志文件中,文件名由file指定,文件格式是平文件,内容和-verbose:gc输出内容相同。
-Djava.awt.headless=true Headless模式是系统的一种配置模式。在该模式下,系统缺少了显示设备、键盘或鼠标。
-XX:+PrintGCTimeStamps -XX:+PrintGCDetails
设置gc日志的格式
-Dsun.rmi.dgc.server.gcInterval=600000 -Dsun.rmi.dgc.client.gcInterval=600000
指定rmi调用时gc的时间间隔
-XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=15 采用并发gc方式,经过15次minor gc 后进入年老代
## 如何分析GC日志
摘录GC日志一部分
Young GC回收日志:
```
2016-07-05T10:43:18.093+0800: 25.395: [GC [PSYoungGen: 274931K->10738K(274944K)] 371093K->147186K(450048K), 0.0668480 secs] [Times: user=0.17 sys=0.08, real=0.07 secs]
```
Full GC回收日志:
```
2016-07-05T10:43:18.160+0800: 25.462: [Full GC [PSYoungGen: 10738K->0K(274944K)] [ParOldGen: 136447K->140379K(302592K)] 147186K->140379K(577536K) [PSPermGen: 85411K->85376K(171008K)], 0.6763541 secs] [Times: user=1.75 sys=0.02, real=0.68 secs]
```
通过上面日志分析得出,PSYoungGen、ParOldGen、PSPermGen属于Parallel收集器。其中PSYoungGen表示gc回收前后年轻代的内存变化;ParOldGen表示gc回收前后老年代的内存变化;PSPermGen表示gc回收前后永久区的内存变化。young gc 主要是针对年轻代进行内存回收比较频繁,耗时短;full gc 会对整个堆内存进行回城,耗时长,因此一般尽量减少full gc的次数
通过两张图非常明显看出gc日志构成:
Young GC日志:Java GC 分析.resources/253C4E10-C025-406F-BCEC-360BD0B901AC.png)
Full GC日志:Java GC 分析.resources/E41583CF-6306-4B8F-95D7-396A3B91FBB1.png)
## GC分析工具
GChisto
GChisto是一款专业分析gc日志的工具,可以通过gc日志来分析:Minor GC、full gc的时间、频率等等,通过列表、报表、图表等不同的形式来反应gc的情况。虽然界面略显粗糙,但是功能还是不错的。
配置好本地的jdk环境之后,双击GChisto.jar,在弹出的输入框中点击 add 选择gc.log日志
Java GC 分析.resources/3BC499FA-2D44-4448-9720-AA4734BA2290.jpg)
GC Pause Stats:可以查看GC 的次数、GC的时间、GC的开销、最大GC时间和最小GC时间等,以及相应的柱状图Java GC 分析.resources/175F8410-B0BD-4288-A2C2-5C35AF57F933.jpg)
GC Pause Distribution:查看GC停顿的详细分布,x轴表示垃圾收集停顿时间,y轴表示是停顿次数。
GC Timeline:显示整个时间线上的垃圾收集
Java GC 分析.resources/AE9DCFB3-730C-4431-964A-6C54DDE8DCC0.jpg)
不过这款工具已经不再维护
GC Easy
这是一个web工具,在线使用非常方便.
地址: http://gceasy.io
进入官网,讲打包好的zip或者gz为后缀的压缩包上传,过一会就会拿到分析结果。

推荐使用此工具进行gc分析。

================================================
FILE: JVM/类加载器.md
================================================
# 类加载器
## 类与类加载器
### 判断类是否“相等”
任意一个类,都由**加载它的类加载器**和这个**类本身**一同确立其在 Java 虚拟机中的唯一性,每一个类加载器,都有一个独立的类名称空间。
因此,比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个 Class 文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那么这两个类就必定不相等。
这里的“相等”,包括代表类的 Class 对象的 equals\(\) 方法、isInstance\(\) 方法的返回结果,也包括使用 instanceof 关键字做对象所属关系判定等情况。
### 加载器种类
系统提供了 3 种类加载器:
* 启动类加载器(Bootstrap ClassLoader): 负责将存放在 `<JAVA_HOME>\lib` 目录中的,并且能被虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。
* 扩展类加载器(Extension ClassLoader): 负责加载 `<JAVA_HOME>\lib\ext` 目录中的所有类库,开发者可以直接使用扩展类加载器。
* 应用程序类加载器(Application ClassLoader): 由于这个类加载器是 ClassLoader 中的 getSystemClassLoader\(\) 方法的返回值,所以一般也称它为“系统类加载器”。它负责加载用户类路径(classpath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

当然,如果有必要,还可以加入自己定义的类加载器。
## 双亲委派模型
### 什么是双亲委派模型
双亲委派模型是描述类加载器之间的层次关系。它要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。(父子关系一般不会以继承的关系实现,而是以组合关系来复用父加载器的代码)
### 工作过程
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(找不到所需的类)时,子加载器才会尝试自己去加载。
在 java.lang.ClassLoader 中的 loadClass\(\) 方法中实现该过程。
### 为什么使用双亲委派模型
像 java.lang.Object 这些存放在 rt.jar 中的类,无论使用哪个类加载器加载,最终都会委派给最顶端的启动类加载器加载,从而使得不同加载器加载的 Object 类都是同一个。
相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为 java.lang.Object 的类,并放在 classpath 下,那么系统将会出现多个不同的 Object 类,Java 类型体系中最基础的行为也就无法保证。
(完)
================================================
FILE: JVM/类加载的时机.md
================================================
# 类加载的时机
## 类的生命周期
类从被加载到虚拟机内存开始,到卸载出内存为止,它的整个生命周期包括以下 7 个阶段:
* 加载
* 验证
* 准备
* 解析
* 初始化
* 使用
* 卸载
验证、准备、解析 3 个阶段统称为连接。

加载、验证、准备、初始化和卸载这 5 个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始(注意是“开始”,而不是“进行”或“完成”),而解析阶段则不一定:它在某些情况下可以在初始化后再开始,这是为了支持 Java 语言的运行时绑定。
## 类加载过程中“初始化”开始的时机
Java 虚拟机规范没有强制约束类加载过程的第一阶段(即:加载)什么时候开始,但对于“初始化”阶段,有着严格的规定。有且仅有 5 种情况必须立即对类进行“初始化”:
* 在遇到 new、putstatic、getstatic、invokestatic 字节码指令时,如果类尚未初始化,则需要先触发其初始化。
* 对类进行反射调用时,如果类还没有初始化,则需要先触发其初始化。
* 初始化一个类时,如果其父类还没有初始化,则需要先初始化父类。
* 虚拟机启动时,用于需要指定一个包含 main\(\) 方法的主类,虚拟机会先初始化这个主类。
* 当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF\_getStatic、REF\_putStatic、REF\_invokeStatic 的方法句柄,并且这个方法句柄所对应的类还没初始化,则需要先触发其初始化。
这 5 种场景中的行为称为对一个类进行**主动引用**,除此之外,其它所有引用类的方式都不会触发初始化,称为**被动引用**。
## 被动引用演示 Demo
### Demo1
```java
/**
* 被动引用 Demo1:
* 通过子类引用父类的静态字段,不会导致子类初始化。
*
* @author ylb
*
*/
class SuperClass {
static {
System.out.println("SuperClass init!");
}
public static int value = 123;
}
class SubClass extends SuperClass {
static {
System.out.println("SubClass init!");
}
}
public class NotInitialization {
public static void main(String[] args) {
System.out.println(SubClass.value);
// SuperClass init!
}
}
```
对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。
### Demo2
```java
/**
* 被动引用 Demo2:
* 通过数组定义来引用类,不会触发此类的初始化。
*
* @author ylb
*
*/
public class NotInitialization {
public static void main(String[] args) {
SuperClass[] superClasses = new SuperClass[10];
}
}
```
这段代码不会触发父类的初始化,但会触发“\[L 全类名”这个类的初始化,它由虚拟机自动生成,直接继承自 java.lang.Object,创建动作由字节码指令 newarray 触发。
### Demo3
```java
/**
* 被动引用 Demo3:
* 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。
*
* @author ylb
*
*/
class ConstClass {
static {
System.out.println("ConstClass init!");
}
public static final String HELLO_BINGO = "Hello Bingo";
}
public class NotInitialization {
public static void main(String[] args) {
System.out.println(ConstClass.HELLO_BINGO);
}
}
```
编译通过之后,常量存储到 NotInitialization 类的常量池中,NotInitialization 的 Class 文件中并没有 ConstClass 类的符号引用入口,这两个类在编译成 Class 之后就没有任何联系了。
## 接口的加载过程
接口加载过程与类加载过程稍有不同。
当一个类在初始化时,要求其父类全部都已经初始化过了,但是一个接口在初始化时,并不要求其父接口全部都完成了初始化,当真正用到父接口的时候才会初始化。
(完)
================================================
FILE: JVM/类加载的过程.md
================================================
# 类加载的过程
类加载过程包括 5 个阶段:加载、验证、准备、解析和初始化。
## 加载
### 加载的过程
“加载”是“类加载”过程的一个阶段,不能混淆这两个名词。在加载阶段,虚拟机需要完成 3 件事:
* 通过类的全限定名获取该类的二进制字节流。
* 将二进制字节流所代表的静态结构转化为方法区的运行时数据结构。
* 在内存中创建一个代表该类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。
### 获取二进制字节流
对于 Class 文件,虚拟机没有指明要从哪里获取、怎样获取。除了直接从编译好的 .class 文件中读取,还有以下几种方式:
* 从 zip 包中读取,如 jar、war等
* 从网络中获取,如 Applect
* 通过动态代理计数生成代理类的二进制字节流
* 由 JSP 文件生成对应的 Class 类
* 从数据库中读取,如 有些中间件服务器可以选择把程序安装到数据库中来完成程序代码在集群间的分发。
### “非数组类”与“数组类”加载比较
* 非数组类加载阶段可以使用系统提供的引导类加载器,也可以由用户自定义的类加载器完成,开发人员可以通过定义自己的类加载器控制字节流的获取方式(如重写一个类加载器的 loadClass\(\) 方法)
* 数组类本身不通过类加载器创建,它是由 Java 虚拟机直接创建的,再由类加载器创建数组中的元素类。
### 注意事项
* 虚拟机规范未规定 Class 对象的存储位置,对于 HotSpot 虚拟机而言,Class 对象比较特殊,它虽然是对象,但存放在方法区中。
* 加载阶段与连接阶段的部分内容交叉进行,加载阶段尚未完成,连接阶段可能已经开始了。但这两个阶段的开始实践仍然保持着固定的先后顺序。
## 验证
### 验证的重要性
验证阶段确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
### 验证的过程
* 文件格式验证 验证字节流是否符合 Class 文件格式的规范,并且能被当前版本的虚拟机处理,验证点如下:
* 是否以魔数 0XCAFEBABE 开头
* 主次版本号是否在当前虚拟机处理范围内
* 常量池是否有不被支持的常量类型
* 指向常量的索引值是否指向了不存在的常量
* CONSTANT\_Utf8\_info 型的常量是否有不符合 UTF8 编码的数据
* ......
* 元数据验证 对字节码描述信息进行语义分析,确保其符合 Java 语法规范。
* 字节码验证 本阶段是验证过程中最复杂的一个阶段,是对方法体进行语义分析,保证方法在运行时不会出现危害虚拟机的事件。
* 符号引用验证 本阶段发生在解析阶段,确保解析正常执行。
## 准备
准备阶段是正式为类变量(或称“静态成员变量”)分配内存并设置初始值的阶段。这些变量(不包括实例变量)所使用的内存都在方法区中进行分配。
初始值“通常情况下”是数据类型的零值(0, null...),假设一个类变量的定义为:
```java
public static int value = 123;
```
那么变量 value 在准备阶段过后的初始值为 0 而不是 123,因为这时候尚未开始执行任何 Java 方法。
存在“特殊情况”:如果类字段的字段属性表中存在 ConstantValue 属性,那么在准备阶段 value 就会被初始化为 ConstantValue 属性所指定的值,假设上面类变量 value 的定义变为:
```java
public static final int value = 123;
```
那么在准备阶段虚拟机会根据 ConstantValue 的设置将 value 赋值为 123。
## 解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
## 初始化
类初始化阶段是类加载过程的最后一步,是执行类构造器 <clinit>\(\) 方法的过程。
<clinit>\(\) 方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static {} 块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的。
静态语句块中只能访问定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块中可以赋值,但不能访问。如下方代码所示:
```java
public class Test {
static {
i = 0; // 给变量赋值可以正常编译通过
System.out.println(i); // 这句编译器会提示“非法向前引用”
}
static int i = 1;
}
```
<clinit>\(\) 方法不需要显式调用父类构造器,虚拟机会保证在子类的 <clinit>\(\) 方法执行之前,父类的 <clinit>\(\) 方法已经执行完毕。
由于父类的 <clinit>\(\) 方法先执行,意味着父类中定义的静态语句块要优先于子类的变量赋值操作。如下方代码所示:
```java
static class Parent {
public static int A = 1;
static {
A = 2;
}
}
static class Sub extends Parent {
public static int B = A;
}
public static void main(String[] args) {
System.out.println(Sub.B); // 输出 2
}
```
<clinit>\(\) 方法不是必需的,如果一个类没有静态语句块,也没有对类变量的赋值操作,那么编译器可以不为这个类生成 <clinit>\(\) 方法。
接口中不能使用静态代码块,但接口也需要通过 <clinit>\(\) 方法为接口中定义的静态成员变量显式初始化。但接口与类不同,接口的 <clinit>\(\) 方法不需要先执行父类的 <clinit>\(\) 方法,只有当父接口中定义的变量使用时,父接口才会初始化。
虚拟机会保证一个类的 <clinit>\(\) 方法在多线程环境中被正确加锁、同步。如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的 <clinit>\(\) 方法。
(完)
================================================
FILE: JVM/类文件结构.md
================================================
# 类文件结构
## JVM 的“无关性”
谈论 JVM 的无关性,主要有以下两个:
* 平台无关性:任何操作系统都能运行 Java 代码
* 语言无关性: JVM 能运行除 Java 以外的其他代码
Java 源代码首先需要使用 Javac 编译器编译成 .class 文件,然后由 JVM 执行 .class 文件,从而程序开始运行。
JVM 只认识 .class 文件,它不关心是何种语言生成了 .class 文件,只要 .class 文件符合 JVM 的规范就能运行。 目前已经有 JRuby、Jython、Scala 等语言能够在 JVM 上运行。它们有各自的语法规则,不过它们的编译器 都能将各自的源码编译成符合 JVM 规范的 .class 文件,从而能够借助 JVM 运行它们。
> Java 语言中的各种变量、关键字和运算符号的语义最终都是由多条字节码命令组合而成的, 因此字节码命令所能提供的语义描述能力肯定会比 Java 语言本身更加强大。 因此,有一些 Java 语言本身无法有效支持的语言特性,不代表字节码本身无法有效支持。
## Class 文件结构
Class 文件时二进制文件,它的内容具有严格的规范,文件中没有任何空格,全都是连续的 0/1。Class 文件 中的所有内容被分为两种类型:无符号数、表。
* 无符号数 无符号数表示
gitextract_brrx73xl/
├── Flink/
│ ├── 10-Flink集群的高可用(搭建篇补充).md
│ ├── 11-时间戳和水印.md
│ ├── 12-Broadcast广播变量.md
│ ├── 13-Flink-Kafka-Connector.md
│ ├── 14-Flink-Table-&-SQL.md
│ ├── 15-Flink实战项目之实时热销排行.md
│ ├── 16-Flink-Redis-Sink.md
│ ├── 17-Flink消费Kafka写入Mysql.md
│ ├── 6-Flink重启策略.md
│ ├── 7-Flink的分布式缓存.md
│ ├── 8-Flink中的窗口.md
│ ├── 9-Flink中的Time.md
│ ├── Flink从入门到放弃(入门篇1)-Flink是什么?.md
│ ├── Flink从入门到放弃(入门篇2)-本地环境搭建&构建第一个Flink应用.md
│ ├── Flink从入门到放弃(入门篇3)-DataSetAPI.md
│ ├── Flink从入门到放弃(入门篇4)-DataStreamAPI.md
│ └── Flink集群部署.md
├── Flink漫谈系列/
│ ├── Apache-Flink-漫谈系列(02)-Watermark.md
│ ├── Apache-Flink-漫谈系列(03)-State.md
│ ├── Apache-Flink漫谈系列(1)-概述.md
│ └── 我的Markdown笔记/
│ └── Apache-Flink-漫谈系列(03)-State.md
├── Hadoop/
│ ├── Hadoop极简入门.md
│ └── MapReduce编程模型和计算框架架构原理.md
├── JVM/
│ ├── HotSpot垃圾收集器.md
│ ├── HotSpot虚拟机对象探秘.md
│ ├── JVM 性能调优.md
│ ├── JVM内存结构.md
│ ├── jvm系列(一)java类的加载机制.md
│ ├── jvm系列(三)GC算法 垃圾收集器.md
│ ├── jvm系列(二)JVM内存结构.md
│ ├── jvm系列(五)Java GC 分析.md
│ ├── jvm系列(四)jvm调优-命令大全(jps jstat jmap jhat jstack jinfo).md
│ ├── 内存分配与回收策略.md
│ ├── 垃圾收集策略与算法.md
│ ├── 我的Markdown笔记/
│ │ └── jvm系列(五)Java GC 分析.md
│ ├── 类加载器.md
│ ├── 类加载的时机.md
│ ├── 类加载的过程.md
│ └── 类文件结构.md
├── Java高级特性增强/
│ ├── Java NIO之Buffer(缓冲区).md
│ ├── Java NIO之Channel(通道).md
│ ├── Java NIO之Selector(选择器).md
│ ├── Java NIO之拥抱Path和Files.md
│ ├── NIO概览.md
│ ├── 大数据成神之路-Java高级特性增强(HashMap).md
│ ├── 大数据成神之路-Java高级特性增强(HashSet).md
│ ├── 大数据成神之路-Java高级特性增强(LinkedHashMap).md
│ ├── 大数据成神之路-Java高级特性增强(Synchronized关键字).md
│ ├── 大数据成神之路-Java高级特性增强(volatile关键字).md
│ ├── 大数据成神之路-Java高级特性增强(多线程).md
│ ├── 大数据成神之路-Java高级特性增强(锁).md
│ ├── 大数据成神之路-Java高级特性增强(集合框架).md
│ ├── 大数据成神之路-Java高级特性增强-NIO.md
│ └── 大数据成神之路-Java高级特性增强.md
├── Kafka/
│ ├── Apache-Kafka安装和使用.md
│ ├── Apache-Kafka核心概念.md
│ ├── Apache-Kafka核心组件和流程(副本管理器).md
│ ├── Apache-Kafka核心组件和流程-协调器.md
│ ├── Apache-Kafka核心组件和流程-控制器.md
│ ├── Apache-Kafka核心组件和流程-日志管理器.md
│ ├── Apache-Kafka简介.md
│ └── Apache-Kafka编程实战.md
├── Linux基础/
│ └── Linux基础和命令.md
├── NIO/
│ ├── Java NIO之Buffer(缓冲区).md
│ ├── Java NIO之Channel(通道).md
│ ├── Java NIO之Selector(选择器).md
│ ├── Java NIO之拥抱Path和Files.md
│ └── NIO概览.md
├── Netty/
│ ├── Netty源码解析-概述篇.md
│ ├── Netty源码解析1-Buffer.md
│ ├── Netty源码解析2-Reactor.md
│ ├── Netty源码解析3-Pipeline.md
│ ├── Netty源码解析4-Handler综述.md
│ ├── Netty源码解析5-ChannelHandler.md
│ ├── Netty源码解析6-ChannelHandler实例之LoggingHandler.md
│ ├── Netty源码解析7-ChannelHandler实例之TimeoutHandler.md
│ ├── Netty源码解析8-ChannelHandler实例之CodecHandler.md
│ ├── Netty源码解析9-ChannelHandler实例之MessageToByteEncoder.md
│ └── 关于Netty我们都需要知道什么.md
├── README.md
├── RPC/
│ ├── RPC的原理和框架.md
│ ├── RPC简单介绍.md
│ └── 手把手教你实现一个简单的RPC.md
├── WechatArticles/
│ └── flink.md
├── pictures/
│ ├── bigdata-notes-icon.psd
│ └── 大数据技术栈思维导图.xmind
├── zookeeeper/
│ ├── ZooKeeper应用程序.md
│ ├── zk安装和运行.md
│ ├── zk开发实例.md
│ └── zk服务.md
├── 分布式理论/
│ ├── 分布式ID生成器解决方案.md
│ ├── 分布式事务的解决方案.md
│ ├── 分布式系统理论基础一: 一致性、2PC和3PC.md
│ ├── 分布式系统理论基础三-时间、时钟和事件顺序.md
│ ├── 分布式系统理论基础二-CAP.md
│ ├── 分布式系统理论进阶 - Paxos.md
│ ├── 分布式系统理论进阶 - Raft、Zab.md
│ ├── 分布式系统理论进阶:选举、多数派和租约.md
│ ├── 分布式系统的一些基本概念.md
│ ├── 分布式锁的解决方案(二).md
│ └── 分布式锁的解决方案.md
├── 大数据框架学习/
│ ├── Azkaban_Flow_1.0_的使用.md
│ ├── Azkaban_Flow_2.0_的使用.md
│ ├── Azkaban简介.md
│ ├── Flink_Data_Sink.md
│ ├── Flink_Data_Source.md
│ ├── Flink_Data_Transformation.md
│ ├── Flink_Windows.md
│ ├── Flink开发环境搭建.md
│ ├── Flink核心概念综述.md
│ ├── Flink状态管理与检查点机制.md
│ ├── Flume整合Kafka.md
│ ├── Flume简介及基本使用.md
│ ├── HDFS-Java-API.md
│ ├── HDFS常用Shell命令.md
│ ├── Hadoop-HDFS.md
│ ├── Hadoop-MapReduce.md
│ ├── Hadoop-YARN.md
│ ├── Hbase_Java_API.md
│ ├── Hbase_Shell.md
│ ├── Hbase协处理器详解.md
│ ├── Hbase容灾与备份.md
│ ├── Hbase的SQL中间层_Phoenix.md
│ ├── Hbase简介.md
│ ├── Hbase系统架构及数据结构.md
│ ├── Hbase过滤器详解.md
│ ├── HiveCLI和Beeline命令行的基本使用.md
│ ├── Hive分区表和分桶表.md
│ ├── Hive常用DDL操作.md
│ ├── Hive常用DML操作.md
│ ├── Hive数据查询详解.md
│ ├── Hive简介及核心概念.md
│ ├── Hive视图和索引.md
│ ├── Kafka消费者详解.md
│ ├── Kafka深入理解分区副本机制.md
│ ├── Kafka生产者详解.md
│ ├── Kafka简介.md
│ ├── Scala函数和闭包.md
│ ├── Scala列表和集.md
│ ├── Scala基本数据类型和运算符.md
│ ├── Scala数组.md
│ ├── Scala映射和元组.md
│ ├── Scala模式匹配.md
│ ├── Scala流程控制语句.md
│ ├── Scala简介及开发环境配置.md
│ ├── Scala类和对象.md
│ ├── Scala类型参数.md
│ ├── Scala继承和特质.md
│ ├── Scala隐式转换和隐式参数.md
│ ├── Scala集合类型.md
│ ├── SparkSQL_Dataset和DataFrame简介.md
│ ├── SparkSQL外部数据源.md
│ ├── SparkSQL常用聚合函数.md
│ ├── SparkSQL联结操作.md
│ ├── Spark_RDD.md
│ ├── Spark_Streaming与流处理.md
│ ├── Spark_Streaming基本操作.md
│ ├── Spark_Streaming整合Flume.md
│ ├── Spark_Streaming整合Kafka.md
│ ├── Spark_Structured_API的基本使用.md
│ ├── Spark_Transformation和Action算子.md
│ ├── Spark简介.md
│ ├── Spark累加器与广播变量.md
│ ├── Spark部署模式与作业提交.md
│ ├── Spring+Mybtais+Phoenix整合.md
│ ├── Sqoop基本使用.md
│ ├── Sqoop简介与安装.md
│ ├── Storm三种打包方式对比分析.md
│ ├── Storm和流处理简介.md
│ ├── Storm核心概念详解.md
│ ├── Storm编程模型详解.md
│ ├── Storm集成HBase和HDFS.md
│ ├── Storm集成Kakfa.md
│ ├── Storm集成Redis详解.md
│ ├── Zookeeper_ACL权限控制.md
│ ├── Zookeeper_Java客户端Curator.md
│ ├── Zookeeper常用Shell命令.md
│ ├── Zookeeper简介及核心概念.md
│ ├── installation/
│ │ ├── Azkaban_3.x_编译及部署.md
│ │ ├── Flink_Standalone_Cluster.md
│ │ ├── HBase单机环境搭建.md
│ │ ├── HBase集群环境搭建.md
│ │ ├── Hadoop单机环境搭建.md
│ │ ├── Hadoop集群环境搭建.md
│ │ ├── Linux下Flume的安装.md
│ │ ├── Linux下JDK安装.md
│ │ ├── Linux下Python安装.md
│ │ ├── Linux环境下Hive的安装部署.md
│ │ ├── Spark开发环境搭建.md
│ │ ├── Spark集群环境搭建.md
│ │ ├── Storm单机环境搭建.md
│ │ ├── Storm集群环境搭建.md
│ │ ├── Zookeeper单机环境和集群环境搭建.md
│ │ ├── 基于Zookeeper搭建Hadoop高可用集群.md
│ │ ├── 基于Zookeeper搭建Kafka高可用集群.md
│ │ └── 虚拟机静态IP及多IP配置.md
│ ├── 大数据学习路线.md
│ ├── 大数据常用软件安装指南.md
│ ├── 大数据应用常用打包方式.md
│ ├── 大数据技术栈思维导图.md
│ └── 资料分享与工具推荐.md
├── 实战系列文章/
│ ├── Flink实战.md
│ ├── Kafka实战.md
│ ├── OLAP.md
│ ├── Spark实战.md
│ ├── 数据仓库.md
│ └── 面试系列.md
├── 并发容器/
│ ├── 大数据成神之路-Java高级特性增强(ArrayBlockingQueue).md
│ ├── 大数据成神之路-Java高级特性增强(ConcurrentHashMap).md
│ ├── 大数据成神之路-Java高级特性增强(ConcurrentLinkedQueue).md
│ ├── 大数据成神之路-Java高级特性增强(ConcurrentSkipListMap).md
│ ├── 大数据成神之路-Java高级特性增强(ConcurrentSkipListSet).md
│ ├── 大数据成神之路-Java高级特性增强(CopyOnWriteArrayList).md
│ ├── 大数据成神之路-Java高级特性增强(CopyOnWriteArraySet).md
│ ├── 大数据成神之路-Java高级特性增强(LinkedBlockingDeque).md
│ ├── 大数据成神之路-Java高级特性增强(LinkedBlockingQueue).md
│ └── 大数据成神之路-Java高级特性增强(并发容器大纲).md
└── 面试系列/
├── Flume面试题整理/
│ └── Flume.md
├── HBase面试题整理/
│ └── HBase.md
├── Hadoop面试题总结/
│ ├── Hadoop面试题总结(一).md
│ ├── Hadoop面试题总结(三)——MapReduce.md
│ ├── Hadoop面试题总结(二)——HDFS.md
│ ├── Hadoop面试题总结(五)——优化问题.md
│ └── Hadoop面试题总结(四)——YARN.md
├── Hive面试题总结/
│ ├── Hive(一).md
│ └── Hive(二).md
├── Kafka面试题整理/
│ ├── Kafka(一).md
│ └── Kafka(二).md
├── Spark面试题整理/
│ ├── Spark调优/
│ │ ├── Shuffle配置调优.md
│ │ ├── 数据倾斜.md
│ │ ├── 程序开发调优.md
│ │ └── 资源调优.md
│ ├── Spark(一).md
│ ├── Spark(三).md
│ ├── Spark(二).md
│ └── Spark(四).md
├── Zookeeper面试题总结/
│ └── Zookeeper.md
└── pics/
├── Flume面试题Pics/
│ └── 1.md
├── HBase面试题Pics/
│ └── 1.md
├── Hadoop面试题Pics/
│ ├── 1.md
│ ├── HDFS文档-Pics/
│ │ └── 1.md
│ ├── MR-Pics/
│ │ └── 1.md
│ └── YARN-Pics/
│ └── 1.md
├── Hive面试题Pics/
│ └── 1.md
├── Kafka面试题Pics/
│ └── 1.md
├── Spark面试题Pics/
│ ├── 1.md
│ ├── 数据倾斜调优/
│ │ └── 1.md
│ ├── 程序开发调优/
│ │ └── 1.md
│ └── 资源调优/
│ └── 1.md
└── ZK面试题Pics/
└── 1.md
Condensed preview — 250 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,783K chars).
[
{
"path": "Flink/10-Flink集群的高可用(搭建篇补充).md",
"chars": 6023,
"preview": "Flink的HA搭建并不复杂,本质来说就是配置2个jobmanager。\n本文作为Flink集群部署的补充篇。\n> 这篇文章来自网络,向作者尼小摩致敬,\n\n## 概述\n\nJobManager 协调每个 Flink 部署。它负责调度和资源管理"
},
{
"path": "Flink/11-时间戳和水印.md",
"chars": 7702,
"preview": "本文作者为阿里巴巴高级技术专家:金竹,原文发表在云栖社区。\n\n地址为:https://yq.aliyun.com/articles/666056?spm=a2c4e.11155435.0.0.106e1b10snGqMd\n\n## 实际问题("
},
{
"path": "Flink/12-Broadcast广播变量.md",
"chars": 2022,
"preview": "## 广播变量简介\n\n在Flink中,同一个算子可能存在若干个不同的并行实例,计算过程可能不在同一个Slot中进行,不同算子之间更是如此,因此不同算子的计算数据之间不能像Java数组之间一样互相访问,而广播变量Broadcast便是解决这种"
},
{
"path": "Flink/13-Flink-Kafka-Connector.md",
"chars": 5984,
"preview": "\n## 简介\n\nFlink-kafka-connector用来做什么?\n\nKafka中的partition机制和Flink的并行度机制结合,实现数据恢复\nKafka可以作为Flink的source和sink\n任务失败,通过设置kafka的o"
},
{
"path": "Flink/14-Flink-Table-&-SQL.md",
"chars": 5235,
"preview": "## 简介\n\nApache Flink具有两个关系API - 表API和SQL - 用于统一流和批处理。Table API是Scala和Java的语言集成查询API,允许以非常直观的方式组合来自关系运算符的查询,Table API和SQL接"
},
{
"path": "Flink/15-Flink实战项目之实时热销排行.md",
"chars": 5464,
"preview": "\n## 需求\n某个图书网站,希望看到双十一秒杀期间实时的热销排行榜单。我们可以将“实时热门商品”翻译成程序员更好理解的需求:每隔5秒钟输出最近一小时内点击量最多的前 N 个商品/图书.\n\n\n## 需求分解\n\n将这个需求进行分解我们大概要做这"
},
{
"path": "Flink/16-Flink-Redis-Sink.md",
"chars": 2803,
"preview": "## 简介\n\n流式计算中,我们经常有一些场景是消费Kafka数据,进行处理,然后存储到其他的数据库或者缓存或者重新发送回其他的消息队列中。\n本文讲述一个简单的Redis作为Sink的案例。\n后续,我们会补充完善,比如落入Hbase,Kafk"
},
{
"path": "Flink/17-Flink消费Kafka写入Mysql.md",
"chars": 2405,
"preview": "\n\n本文介绍消费Kafka的消息实时写入Mysql\n\n1. maven新增依赖:\n\n```\n<dependency>\n <groupId>mysql</groupId>\n <artifactId>mysql-connector-"
},
{
"path": "Flink/6-Flink重启策略.md",
"chars": 2201,
"preview": "\n## 概述\n\n* Flink支持不同的重启策略,以在故障发生时控制作业如何重启\n* 集群在启动时会伴随一个默认的重启策略,在没有定义具体重启策略时会使用该默认策略。 \n* 如果在工作提交时指定了一个重启策略,该策略会覆盖集群的默认策略默认"
},
{
"path": "Flink/7-Flink的分布式缓存.md",
"chars": 3196,
"preview": "\n## 分布式缓存\n\nFlink提供了一个分布式缓存,类似于hadoop,可以使用户在并行函数中很方便的读取本地文件,并把它放在taskmanager节点中,防止task重复拉取。\n此缓存的工作机制如下:程序注册一个文件或者目录(本地或者远"
},
{
"path": "Flink/8-Flink中的窗口.md",
"chars": 2495,
"preview": "\n## 窗口\n\n### 窗口类型\n1. flink支持两种划分窗口的方式(time和count) 如果根据时间划分窗口,那么它就是一个time-window 如果根据数据划分窗口,那么它就是一个count-window\n\n2. "
},
{
"path": "Flink/9-Flink中的Time.md",
"chars": 1838,
"preview": "## 时间\n\n### 时间类型\n\n* Flink中的时间与现实世界中的时间是不一致的,在flink中被划分为**事件时间,摄入时间,处理时间**三种。\n\n* 如果以EventTime为基准来定义时间窗口将形成EventTimeWindow,"
},
{
"path": "Flink/Flink从入门到放弃(入门篇1)-Flink是什么?.md",
"chars": 2641,
"preview": "\n\n> 本文是例行介绍,熟悉的直接跳过 - 鲁迅\n\n> 鲁迅: ...\n\n# 大纲\n\n**入门篇:**\n-Flink是什么?.reso"
},
{
"path": "Flink/Flink从入门到放弃(入门篇2)-本地环境搭建&构建第一个Flink应用.md",
"chars": 4986,
"preview": "## 本地安装单机版本Flink\n\n一般来说,线上都是集群模式,那么单机模式方便我们测试和学习。\n\n### 环境要求\n\n本地机器上需要有 Java 8 和 maven 环境,推荐在linux或者mac上开发Flink应用:\n\n如果有 Jav"
},
{
"path": "Flink/Flink从入门到放弃(入门篇3)-DataSetAPI.md",
"chars": 12691,
"preview": "\n## 编程结构\n\n```\npublic class SocketTextStreamWordCount {\n\n\tpublic static void main(String[] args) throws Exception {\n\t\tif "
},
{
"path": "Flink/Flink从入门到放弃(入门篇4)-DataStreamAPI.md",
"chars": 5067,
"preview": "\nDataStream算子将一个或多个DataStream转换为新DataStream。程序可以将多个转换组合成复杂的数据流拓扑。\nDataStreamAPI和DataSetAPI主要的区别在于Transformation部分。\n## Da"
},
{
"path": "Flink/Flink集群部署.md",
"chars": 5616,
"preview": "\n## 部署方式\n\n\n一般来讲有三种方式:\n\n* Local\n* Standalone\n* Flink On Yarn/Mesos/K8s…\n\n## 单机模式\n\n参考上一篇**Flink从入门到放弃(入门篇2)-本地环境搭建&构建第一个Fl"
},
{
"path": "Flink漫谈系列/Apache-Flink-漫谈系列(02)-Watermark.md",
"chars": 7544,
"preview": "## 实际问题(乱序)\n\n在介绍Watermark相关内容之前我们先抛出一个具体的问题,在实际的流式计算中数据到来的顺序对计算结果的正确性有至关重要的影响,比如:某数据源中的某些数据由于某种原因(如:网络原因,外部存储自身原因)会有5秒的延"
},
{
"path": "Flink漫谈系列/Apache-Flink-漫谈系列(03)-State.md",
"chars": 8539,
"preview": "## 实际问题\n\n在流计算场景中,数据会源源不断的流入Apache Flink系统,每条数据进入Apache Flink系统都会触发计算。如果我们想进行一个Count聚合计算,那么每次触发计算是将历史上所有流入的数据重新新计算一次,还是每次"
},
{
"path": "Flink漫谈系列/Apache-Flink漫谈系列(1)-概述.md",
"chars": 9946,
"preview": "-概述.resources/49D66A77-779B-468F-9BCB-6846609484DA.png)\n\n摘要:Apach"
},
{
"path": "Flink漫谈系列/我的Markdown笔记/Apache-Flink-漫谈系列(03)-State.md",
"chars": 8538,
"preview": "## 实际问题\n\n在流计算场景中,数据会源源不断的流入Apache Flink系统,每条数据进入Apache Flink系统都会触发计算。如果我们想进行一个Count聚合计算,那么每次触发计算是将历史上所有流入的数据重新新计算一次,还是每次"
},
{
"path": "Hadoop/Hadoop极简入门.md",
"chars": 7541,
"preview": "\n其实Hadoop诞生至今已经十多年了,网络上也充斥着关于Hadoop相关知识的海量资源。但是,有时还是会使刚刚接触大数据领域的童鞋分不清hadoop、hdfs、Yarn和MapReduce等等技术词汇。\n\nHadoop是ASF(Apach"
},
{
"path": "Hadoop/MapReduce编程模型和计算框架架构原理.md",
"chars": 5552,
"preview": "Hadoop解决大规模数据分布式计算的方案是MapReduce。MapReduce既是一个编程模型,又是一个计算框架。也就是说,开发人员必须基于MapReduce编程模型进行编程开发,然后将程序通过MapReduce计算框架分发到Hadoo"
},
{
"path": "JVM/HotSpot垃圾收集器.md",
"chars": 3090,
"preview": "# HotSpot垃圾收集器\n\nHotSpot 虚拟机提供了多种垃圾收集器,每种收集器都有各自的特点,虽然我们要对各个收集器进行比较,但并非为了挑选出一个最好的收集器。我们选择的只是对具体应用最合适的收集器。\n\n## 新生代垃圾收集器\n\n#"
},
{
"path": "JVM/HotSpot虚拟机对象探秘.md",
"chars": 1857,
"preview": "# HotSpot虚拟机对象探秘\n\n## 对象的内存布局\n\n在 HotSpot 虚拟机中,对象的内存布局分为以下 3 块区域:\n\n* 对象头(Header)\n* 实例数据(Instance Data)\n* 对齐填充(Padding)\n\njava类的加载机制.md",
"chars": 10743,
"preview": "## 什么是类的加载\n类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的 "
},
{
"path": "JVM/jvm系列(三)GC算法 垃圾收集器.md",
"chars": 5458,
"preview": "## 概述\n垃圾收集 Garbage Collection 通常被称为“GC”,它诞生于1960年 MIT 的 Lisp 语言,经过半个多世纪,目前已经十分成熟了。\n\njvm 中,程序计数器、虚拟机栈、本地方法栈都是随线程而生随线程而灭,栈"
},
{
"path": "JVM/jvm系列(二)JVM内存结构.md",
"chars": 3844,
"preview": "所有的Java开发人员可能会遇到这样的困惑?我该为堆内存设置多大空间呢?OutOfMemoryError的异常到底涉及到运行时数据的哪块区域?该怎么解决呢?其实如果你经常解决服务器性能问题,那么这些问题就会变的非常常见,了解JVM内存也是为"
},
{
"path": "JVM/jvm系列(五)Java GC 分析.md",
"chars": 5234,
"preview": "Java GC就是JVM记录仪,书画了JVM各个分区的表演。\n\n## 什么是 Java GC\n\nJava GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不"
},
{
"path": "JVM/jvm系列(四)jvm调优-命令大全(jps jstat jmap jhat jstack jinfo).md",
"chars": 15431,
"preview": "## 简介\n运用jvm自带的命令可以方便的在生产监控和打印堆栈的日志信息帮忙我们来定位问题!虽然jvm调优成熟的工具已经有很多:jconsole、大名鼎鼎的VisualVM,IBM的Memory Analyzer等等,但是在生产环境出现问题"
},
{
"path": "JVM/内存分配与回收策略.md",
"chars": 2452,
"preview": "# 内存分配与回收策略\n对象的内存分配,就是在堆上分配(也可能经过 JIT 编译后被拆散为标量类型并间接在栈上分配),对象主要分配在新生代的 Eden 区上,少数情况下可能直接分配在老年代,**分配规则不固定**,取决于当前使用的垃圾收集器"
},
{
"path": "JVM/垃圾收集策略与算法.md",
"chars": 3640,
"preview": "# 垃圾收集策略与算法\n\n程序计数器、虚拟机栈、本地方法栈随线程而生,也随线程而灭;栈帧随着方法的开始而入栈,随着方法的结束而出栈。这几个区域的内存分配和回收都具有确定性,在这几个区域内不需要过多考虑回收的问题,因为方法结束或者线程结束时,"
},
{
"path": "JVM/我的Markdown笔记/jvm系列(五)Java GC 分析.md",
"chars": 4623,
"preview": "Java GC就是JVM记录仪,书画了JVM各个分区的表演。\n\n## 什么是 Java GC\n\nJava GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不"
},
{
"path": "JVM/类加载器.md",
"chars": 1455,
"preview": "# 类加载器\n\n## 类与类加载器\n\n### 判断类是否“相等”\n\n任意一个类,都由**加载它的类加载器**和这个**类本身**一同确立其在 Java 虚拟机中的唯一性,每一个类加载器,都有一个独立的类名称空间。\n\n因此,比较两个类是否“相"
},
{
"path": "JVM/类加载的时机.md",
"chars": 2483,
"preview": "# 类加载的时机\n\n## 类的生命周期\n\n类从被加载到虚拟机内存开始,到卸载出内存为止,它的整个生命周期包括以下 7 个阶段:\n\n* 加载\n* 验证\n* 准备\n* 解析\n* 初始化\n* 使用\n* 卸载\n\n验证、准备、解析 3 个阶段统称为连"
},
{
"path": "JVM/类加载的过程.md",
"chars": 2819,
"preview": "# 类加载的过程\n类加载过程包括 5 个阶段:加载、验证、准备、解析和初始化。\n## 加载\n\n### 加载的过程\n\n“加载”是“类加载”过程的一个阶段,不能混淆这两个名词。在加载阶段,虚拟机需要完成 3 件事:\n\n* 通过类的全限定名获取该"
},
{
"path": "JVM/类文件结构.md",
"chars": 3931,
"preview": "# 类文件结构\n\n## JVM 的“无关性”\n\n谈论 JVM 的无关性,主要有以下两个: \n\n* 平台无关性:任何操作系统都能运行 Java 代码\n* 语言无关性: JVM 能运行除 Java 以外的其他代码\n\nJava 源代码首先需要使"
},
{
"path": "Java高级特性增强/Java NIO之Buffer(缓冲区).md",
"chars": 8008,
"preview": "### **Java高级特性增强-NIO\n本部分网络上有大量的资源可以参考,在这里做了部分整理并做了部分勘误,感谢前辈的付出,每节文章末尾有引用列表~\n* * *\n**写在所有文字的前面**:作者在此特别推荐Google排名第一的关于NIO"
},
{
"path": "Java高级特性增强/Java NIO之Channel(通道).md",
"chars": 10066,
"preview": "### **Java高级特性增强-NIO\n本部分网络上有大量的资源可以参考,在这里做了部分整理并做了部分勘误,感谢前辈的付出,每节文章末尾有引用列表~\n* * *\n**写在所有文字的前面**:作者在此特别推荐Google排名第一的关于NIO"
},
{
"path": "Java高级特性增强/Java NIO之Selector(选择器).md",
"chars": 10461,
"preview": "### **Java高级特性增强-NIO\n本部分网络上有大量的资源可以参考,在这里做了部分整理并做了部分勘误,感谢前辈的付出,每节文章末尾有引用列表~\n* * *\n**写在所有文字的前面**:作者在此特别推荐Google排名第一的关于NIO"
},
{
"path": "Java高级特性增强/Java NIO之拥抱Path和Files.md",
"chars": 7993,
"preview": "### **Java高级特性增强-NIO\n本部分网络上有大量的资源可以参考,在这里做了部分整理并做了部分勘误,感谢前辈的付出,每节文章末尾有引用列表~\n* * *\n**写在所有文字的前面**:作者在此特别推荐Google排名第一的关于NIO"
},
{
"path": "Java高级特性增强/NIO概览.md",
"chars": 4125,
"preview": "### **Java高级特性增强-NIO\n本部分网络上有大量的资源可以参考,在这里做了部分整理并做了部分勘误,感谢前辈的付出,每节文章末尾有引用列表~\n* * *\n**写在所有文字的前面**:作者在此特别推荐Google排名第一的关于NIO"
},
{
"path": "Java高级特性增强/大数据成神之路-Java高级特性增强(HashMap).md",
"chars": 17619,
"preview": "### **Java高级特性增强-集合框架(HashMap)**\n本部分网络上有大量的资源可以参考,在这里做了部分整理,感谢前辈的付出,每节文章末尾有引用列表,源码推荐看JDK1.8以后的版本,注意甄别~\n####**多线程**\n###**"
},
{
"path": "Java高级特性增强/大数据成神之路-Java高级特性增强(HashSet).md",
"chars": 1466,
"preview": "### **Java高级特性增强-集合框架(HashSet)**\n本部分网络上有大量的资源可以参考,在这里做了部分整理,感谢前辈的付出,每节文章末尾有引用列表,源码推荐看JDK1.8以后的版本,注意甄别~\n####**多线程**\n###**"
},
{
"path": "Java高级特性增强/大数据成神之路-Java高级特性增强(LinkedHashMap).md",
"chars": 7142,
"preview": "### **Java高级特性增强-集合框架(LinkedHashMap)**\n本部分网络上有大量的资源可以参考,在这里做了部分整理,感谢前辈的付出,每节文章末尾有引用列表,源码推荐看JDK1.8以后的版本,注意甄别~\n####**多线程**"
},
{
"path": "Java高级特性增强/大数据成神之路-Java高级特性增强(Synchronized关键字).md",
"chars": 9306,
"preview": "### **Java高级特性增强-Synchronized**\n本部分网络上有大量的资源可以参考,在这里做了部分整理,感谢前辈的付出,每节文章末尾有引用列表,源码推荐看JDK1.8以后的版本,注意甄别~\n####**多线程**\n###**集"
},
{
"path": "Java高级特性增强/大数据成神之路-Java高级特性增强(volatile关键字).md",
"chars": 5594,
"preview": "### **Java高级特性增强-Volatile**\n本部分网络上有大量的资源可以参考,在这里做了部分整理,感谢前辈的付出,每节文章末尾有引用列表,源码推荐看JDK1.8以后的版本,注意甄别~\n\n####**多线程**\n###**集合框架"
},
{
"path": "Java高级特性增强/大数据成神之路-Java高级特性增强(多线程).md",
"chars": 15920,
"preview": "### **Java高级特性增强-多线程**\n本部分网络上有大量的资源可以参考,在这里做了部分整理,感谢前辈的付出,每节文章末尾有引用列表,源码推荐看JDK1.8以后的版本,注意甄别~\n####**多线程**\n###**集合框架**\n###"
},
{
"path": "Java高级特性增强/大数据成神之路-Java高级特性增强(锁).md",
"chars": 15145,
"preview": "### **Java高级特性增强-锁**\n本部分网络上有大量的资源可以参考,在这里做了部分整理,感谢前辈的付出,每节文章末尾有引用列表,源码推荐看JDK1.8以后的版本,注意甄别~\n####**多线程**\n###**集合框架**\n###**"
},
{
"path": "Java高级特性增强/大数据成神之路-Java高级特性增强(集合框架).md",
"chars": 22976,
"preview": "### **Java高级特性增强-集合框架(ArrayList/Vector)**\n本部分网络上有大量的资源可以参考,在这里做了部分整理,感谢前辈的付出,每节文章末尾有引用列表,源码推荐看JDK1.8以后的版本,注意甄别~\n####**多线"
},
{
"path": "Java高级特性增强/大数据成神之路-Java高级特性增强-NIO.md",
"chars": 256,
"preview": "### **Java高级特性增强-NIO**\n本部分网络上有大量的资源可以参考,在这里做了部分整理并做了大量勘误,感谢前辈的付出,每节文章末尾有引用列表~\n\n\n#### **多线程**\n\n\n### **集合框架**\n\n\n### **NIO*"
},
{
"path": "Java高级特性增强/大数据成神之路-Java高级特性增强.md",
"chars": 11407,
"preview": "### **Java高级特性增强-集合框架(LinkedList)**\n本部分网络上有大量的资源可以参考,在这里做了部分整理,感谢前辈的付出,每节文章末尾有引用列表,源码推荐看JDK1.8以后的版本,注意甄别~\n####**多线程**\n##"
},
{
"path": "Kafka/Apache-Kafka安装和使用.md",
"chars": 6014,
"preview": "**Apache Kafka 编程实战您可能感性的文章:**\n\n[Apache-Kafka简介](http://link.zhihu.com/?target=http%3A//mp.weixin.qq.com/s%3F__biz%3DMzU"
},
{
"path": "Kafka/Apache-Kafka核心概念.md",
"chars": 9015,
"preview": "**Apache Kafka 编程实战您可能感性的文章:**\n\n[Apache-Kafka简介](http://link.zhihu.com/?target=http%3A//mp.weixin.qq.com/s%3F__biz%3DMzU"
},
{
"path": "Kafka/Apache-Kafka核心组件和流程(副本管理器).md",
"chars": 3006,
"preview": "**Apache Kafka 编程实战您可能感性的文章:**\n\n[Apache-Kafka简介](http://link.zhihu.com/?target=http%3A//mp.weixin.qq.com/s%3F__biz%3DMzU"
},
{
"path": "Kafka/Apache-Kafka核心组件和流程-协调器.md",
"chars": 7384,
"preview": "**Apache Kafka 编程实战您可能感性的文章:**\n\n[Apache-Kafka简介](http://link.zhihu.com/?target=http%3A//mp.weixin.qq.com/s%3F__biz%3DMzU"
},
{
"path": "Kafka/Apache-Kafka核心组件和流程-控制器.md",
"chars": 7844,
"preview": "**Apache Kafka 编程实战您可能感性的文章:**\n\n[Apache-Kafka简介](http://link.zhihu.com/?target=http%3A//mp.weixin.qq.com/s%3F__biz%3DMzU"
},
{
"path": "Kafka/Apache-Kafka核心组件和流程-日志管理器.md",
"chars": 4236,
"preview": "**Apache Kafka 编程实战您可能感兴趣的文章:**\n\n[Apache-Kafka简介](http://link.zhihu.com/?target=http%3A//mp.weixin.qq.com/s%3F__biz%3DMz"
},
{
"path": "Kafka/Apache-Kafka简介.md",
"chars": 2737,
"preview": "**您可能感兴趣的文章:**\n\n[Apache-Kafka简介](http://link.zhihu.com/?target=http%3A//mp.weixin.qq.com/s%3F__biz%3DMzU3MzgwNTU2Mg%3D%3"
},
{
"path": "Kafka/Apache-Kafka编程实战.md",
"chars": 7750,
"preview": "**Apache Kafka 编程实战您可能感性的文章:**\n\n[Apache-Kafka简介](http://link.zhihu.com/?target=http%3A//mp.weixin.qq.com/s%3F__biz%3DMzU"
},
{
"path": "Linux基础/Linux基础和命令.md",
"chars": 7594,
"preview": "\n## Linux命令\n\n我是小白,我从来没玩过Linux,请点这里:\n```\nhttp://www.runoob.com/linux/Linux-intro.html\n```\n## 推荐的一个Git仓库\n\n我有些基础,推荐一个快速查询命令"
},
{
"path": "NIO/Java NIO之Buffer(缓冲区).md",
"chars": 8008,
"preview": "### **Java高级特性增强-NIO\n本部分网络上有大量的资源可以参考,在这里做了部分整理并做了部分勘误,感谢前辈的付出,每节文章末尾有引用列表~\n* * *\n**写在所有文字的前面**:作者在此特别推荐Google排名第一的关于NIO"
},
{
"path": "NIO/Java NIO之Channel(通道).md",
"chars": 10066,
"preview": "### **Java高级特性增强-NIO\n本部分网络上有大量的资源可以参考,在这里做了部分整理并做了部分勘误,感谢前辈的付出,每节文章末尾有引用列表~\n* * *\n**写在所有文字的前面**:作者在此特别推荐Google排名第一的关于NIO"
},
{
"path": "NIO/Java NIO之Selector(选择器).md",
"chars": 10461,
"preview": "### **Java高级特性增强-NIO\n本部分网络上有大量的资源可以参考,在这里做了部分整理并做了部分勘误,感谢前辈的付出,每节文章末尾有引用列表~\n* * *\n**写在所有文字的前面**:作者在此特别推荐Google排名第一的关于NIO"
},
{
"path": "NIO/Java NIO之拥抱Path和Files.md",
"chars": 7993,
"preview": "### **Java高级特性增强-NIO\n本部分网络上有大量的资源可以参考,在这里做了部分整理并做了部分勘误,感谢前辈的付出,每节文章末尾有引用列表~\n* * *\n**写在所有文字的前面**:作者在此特别推荐Google排名第一的关于NIO"
},
{
"path": "NIO/NIO概览.md",
"chars": 4125,
"preview": "### **Java高级特性增强-NIO\n本部分网络上有大量的资源可以参考,在这里做了部分整理并做了部分勘误,感谢前辈的付出,每节文章末尾有引用列表~\n* * *\n**写在所有文字的前面**:作者在此特别推荐Google排名第一的关于NIO"
},
{
"path": "Netty/Netty源码解析-概述篇.md",
"chars": 4245,
"preview": "本文是由code4craft发表在博客上的,原文基于Netty3.7的版本,源码部分对buffer、Pipeline、Reactor模式等进行了部分讲解,个人又继续新增了后续的几个核心组件的源码解读,新增了具体的案例。\nNetty的源码非常"
},
{
"path": "Netty/Netty源码解析1-Buffer.md",
"chars": 7671,
"preview": "\n上一篇文章我们概要介绍了Netty的原理及结构,下面几篇文章我们开始对Netty的各个模块进行比较详细的分析。Netty的结构最底层是buffer机制,这部分也相对独立,我们就先从buffer讲起。\n\n## What:buffer简介\n\n"
},
{
"path": "Netty/Netty源码解析2-Reactor.md",
"chars": 7344,
"preview": "\n## 一:Netty、NIO、多线程?\n\n理清NIO与Netty的关系之前,我们必须先要来看看Reactor模式。Netty是一个典型的多线程的Reactor模式的使用,理解了这部分,在宏观上理解Netty的NIO及多线程部分就不会有什么"
},
{
"path": "Netty/Netty源码解析3-Pipeline.md",
"chars": 6248,
"preview": "\n## Channel实现概览\n\n在Netty里,`Channel`是通讯的载体,而`ChannelHandler`负责Channel中的逻辑处理。\n\n那么`ChannelPipeline`是什么呢?我觉得可以理解为ChannelHandl"
},
{
"path": "Netty/Netty源码解析4-Handler综述.md",
"chars": 2972,
"preview": "## Netty中的Handler简介\n`Handler`在Netty中,占据着非常重要的地位。`Handler`与Servlet中的filter很像,通过Handler可以完成通讯报文的解码编码、拦截指定的报文、\n\n统一对日志错误进行处理"
},
{
"path": "Netty/Netty源码解析5-ChannelHandler.md",
"chars": 2917,
"preview": "\nChannelHandler并不处理事件,而由其子类代为处理:ChannelInboundHandler拦截和处理入站事件,ChannelOutboundHandler拦截和处理出站事件。ChannelHandler和ChannelHan"
},
{
"path": "Netty/Netty源码解析6-ChannelHandler实例之LoggingHandler.md",
"chars": 2841,
"preview": "## LoggingHandler\n\n日志处理器LoggingHandler是使用Netty进行开发时的好帮手,它可以对入站\\出站事件进行日志记录,从而方便我们进行问题排查。首先看类签名:\n```\n @Sharable\n pub"
},
{
"path": "Netty/Netty源码解析7-ChannelHandler实例之TimeoutHandler.md",
"chars": 5970,
"preview": "## TimeoutHandler\n\n在开发TCP服务时,一个常见的需求便是使用心跳保活客户端。而Netty自带的三个超时处理器IdleStateHandler,ReadTimeoutHandler和WriteTimeoutHandler可"
},
{
"path": "Netty/Netty源码解析8-ChannelHandler实例之CodecHandler.md",
"chars": 21503,
"preview": "编解码处理器作为Netty编程时必备的ChannelHandler,每个应用都必不可少。Netty作为网络应用框架,在网络上的各个应用之间不断进行数据交互。而网络数据交换的基本单位是字节,所以需要将本应用的POJO对象编码为字节数据发送到其"
},
{
"path": "Netty/Netty源码解析9-ChannelHandler实例之MessageToByteEncoder.md",
"chars": 2061,
"preview": "\nMessageToByteEncoder框架可见用户使用POJO对象编码为字节数据存储到ByteBuf。用户只需定义自己的编码方法encode()即可。\n首先看类签名:\n```\n public abstract class Mess"
},
{
"path": "Netty/关于Netty我们都需要知道什么.md",
"chars": 10336,
"preview": "\n## 1.BIO、NIO和AIO的区别?\n* BIO:一个连接一个线程,客户端有连接请求时服务器端就需要启动一个线程进行处理。线程开销大。\n* 伪异步IO:将请求连接放入线程池,一对多,但线程还是很宝贵的资源。\n* NIO:一个请求一个线"
},
{
"path": "README.md",
"chars": 113722,
"preview": "\n<br/>\n<div align=\"center\">\n <a href=\"https://mp.weixin.qq.com/s/0N4XSMFPuD7U_paGsBsblw\" style=\"text-decoration:none\""
},
{
"path": "RPC/RPC的原理和框架.md",
"chars": 22949,
"preview": "本文来自:csdn博客,作者在其中做了一些补充并添加了示例\n\nNelson 的论文中指出实现 RPC 的程序包括 5 个部分:\n\n1. User\n\n2. User-stub\n\n3. RPCRuntime\n\n4. Server-stub\n\n5"
},
{
"path": "RPC/RPC简单介绍.md",
"chars": 935,
"preview": "## RPC\n### 1. RPC是什么\n\nRPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的"
},
{
"path": "RPC/手把手教你实现一个简单的RPC.md",
"chars": 7242,
"preview": "\n## RPC的实现原理\n\n上面2讲我们已经讲过,RPC主要是为了解决的两个问题:\n解决分布式系统中,服务之间的调用问题。\n远程调用时,要能够像本地调用一样方便,让调用者感知不到远程调用的逻辑。\n还是以计算器Calculator为例,如果实"
},
{
"path": "WechatArticles/flink.md",
"chars": 1,
"preview": "\n"
},
{
"path": "zookeeeper/ZooKeeper应用程序.md",
"chars": 11664,
"preview": "\n在对ZooKeeper有了一个深入的了解以后,我们来看一下用ZooKeeper可以实现哪些应用。\n\n## 配置服务 Configuration Service\n\n一个基本的ZooKeeper实现的服务就是“配置服务”,集群中的服务器可以通"
},
{
"path": "zookeeeper/zk安装和运行.md",
"chars": 1379,
"preview": "\n\n> 本文由holynull发表在了gitbook上\n> 大家可以点击这里获取更好的阅读体验: https://holynull.gitbooks.io/zookeeper/content/chapter1.html\n\n> zk目前更新到"
},
{
"path": "zookeeeper/zk开发实例.md",
"chars": 7178,
"preview": "\n## ZooKeeper中的组和成员\n\n我们可以把Zookeeper理解为一个高可用的文件系统。但是它没有文件和文件夹的概念,只有一个叫做znode的节点概念。那么znode即是数据的容器,也是其他节点的容器。(其实znode就可以理解为"
},
{
"path": "zookeeeper/zk服务.md",
"chars": 10788,
"preview": "\n## Zookeeper 服务\n\nZooKeeper 是一个高可用的高性能调度服务。这一节我们将讲述他的模型、操作和接口。\n\n## 数据模型 Data Model\n\nZooKeeper包含一个树形的数据模型,我们叫做znode。一个zno"
},
{
"path": "分布式理论/分布式ID生成器解决方案.md",
"chars": 4757,
"preview": "\n\n本文主要介绍在一个分布式系统中, 怎么样生成全局唯一的 ID\n\n## 一, 问题描述\n在分布式系统存在多个 Shard 的场景中, 同时在各个 Shard 插入数据时, 怎么给这些数据生成全局的 unique ID?\n\n在单机系统中 ("
},
{
"path": "分布式理论/分布式事务的解决方案.md",
"chars": 7691,
"preview": "分布式事务的解决方案有如下几种:\n\n* 全局消息\n* 基于可靠消息服务的分布式事务\n* TCC\n* 最大努力通知\n\n\n## 方案1:全局事务(DTP模型)\n全局事务基于DTP模型实现。DTP是由X/Open组织提出的一种分布式事务模型——X"
},
{
"path": "分布式理论/分布式系统理论基础一: 一致性、2PC和3PC.md",
"chars": 6397,
"preview": "## 引言\n\n狭义的分布式系统指由网络连接的计算机系统,每个节点独立地承担计算或存储任务,节点间通过网络协同工作。广义的分布式系统是一个相对的概念,正如Leslie Lamport所说[1]:\nWhat is a distributed s"
},
{
"path": "分布式理论/分布式系统理论基础三-时间、时钟和事件顺序.md",
"chars": 3947,
"preview": "现实生活中时间是很重要的概念,时间可以记录事情发生的时刻、比较事情发生的先后顺序。分布式系统的一些场景也需要记录和比较不同节点间事件发生的顺序,但不同于日常生活使用物理时钟记录时间,分布式系统使用逻辑时钟记录事件顺序关系,下面我们来看分布式"
},
{
"path": "分布式理论/分布式系统理论基础二-CAP.md",
"chars": 4542,
"preview": "## 引言\n\nCAP是分布式系统、特别是分布式存储领域中被讨论最多的理论,“什么是CAP定理?”在Quora 分布式系统分类下排名 FAQ 的 No.1。CAP在程序员中也有较广的普及,它不仅仅是“C、A、P不能同时满足,最多只能3选2”,"
},
{
"path": "分布式理论/分布式系统理论进阶 - Paxos.md",
"chars": 4174,
"preview": "## 引言\n\n《分布式系统理论基础 - 一致性、2PC和3PC》一文介绍了一致性、达成一致性需要面临的各种问题以及2PC、3PC模型,Paxos协议在节点宕机恢复、消息无序或丢失、网络分化的场景下能保证决议的一致性,是被讨论最广泛的一致性协"
},
{
"path": "分布式理论/分布式系统理论进阶 - Raft、Zab.md",
"chars": 4741,
"preview": "## 引言\n\n《分布式系统理论进阶 - Paxos》介绍了一致性协议Paxos,今天我们来学习另外两个常见的一致性协议——Raft和Zab。通过与Paxos对比,了解Raft和Zab的核心思想、加深对一致性协议的认识。\n\n## Raft\n\n"
},
{
"path": "分布式理论/分布式系统理论进阶:选举、多数派和租约.md",
"chars": 2779,
"preview": "选举(election)是分布式系统实践中常见的问题,通过打破节点间的对等关系,选得的leader(或叫master、coordinator)有助于实现事务原子性、提升决议效率。 多数派(quorum)的思路帮助我们在网络分化的情况下达成决"
},
{
"path": "分布式理论/分布式系统的一些基本概念.md",
"chars": 14014,
"preview": "## 分布式\n**来自csdn,作者:陆小凤\n进阶篇来自:bangerlee\n作者对部分地方做了订正\n目前这系列文章是网络上分布式系统讲的最全最深入的系列,文中参考了大量国外英文文献。**\n\n小明的公司又3个系统:系统A,系统B和系统C,这"
},
{
"path": "分布式理论/分布式锁的解决方案(二).md",
"chars": 6755,
"preview": "目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性("
},
{
"path": "分布式理论/分布式锁的解决方案.md",
"chars": 3786,
"preview": "## 前言\n\n随着互联网技术的不断发展,数据量的不断增加,业务逻辑日趋复杂,在这种背景下,传统的集中式系统已经无法满足我们的业务需求,分布式系统被应用在更多的场景,而在分布式系统中访问共享资源就需要一种互斥机制,来防止彼此之间的互相干扰,以"
},
{
"path": "大数据框架学习/Azkaban_Flow_1.0_的使用.md",
"chars": 4307,
"preview": "# Azkaban Flow 1.0 的使用\n\n<nav>\n<a href=\"#一简介\">一、简介</a><br/>\n<a href=\"#二基本任务调度\">二、基本任务调度</a><br/>\n<a href=\"#三多任务调度\">三、多任务调"
},
{
"path": "大数据框架学习/Azkaban_Flow_2.0_的使用.md",
"chars": 5762,
"preview": "# Azkaban Flow 2.0的使用\n\n<nav>\n<a href=\"#一Flow-20-简介\">一、Flow 2.0 简介</a><br/>\n<a href=\"#二YAML语法\">二、YAML语法</a><br/>\n<a href="
},
{
"path": "大数据框架学习/Azkaban简介.md",
"chars": 1748,
"preview": "# Azkaban简介\n\n\n## 一、Azkaban 介绍\n\n#### 1.1 背景\n\n一个完整的大数据分析系统,必然由很多任务单元 (如数据收集、数据清洗、数据存储、数据分析等) 组成,所有的任务单元及其之间的依赖关系组成了复杂的工作流。"
},
{
"path": "大数据框架学习/Flink_Data_Sink.md",
"chars": 8479,
"preview": "# Flink Sink\n<nav>\n<a href=\"#一Data-Sinks\">一、Data Sinks</a><br/>\n <a href="
},
{
"path": "大数据框架学习/Flink_Data_Source.md",
"chars": 8866,
"preview": "# Flink Data Source\n<nav>\n<a href=\"#一内置-Data-Source\">一、内置 Data Source</a><br/>\n  "
},
{
"path": "大数据框架学习/Flink_Data_Transformation.md",
"chars": 11347,
"preview": "# Flink Transformation\n<nav>\n<a href=\"#一Transformations-分类\">一、Transformations 分类</a><br/>\n<a href=\"#二DataStream-Transfor"
},
{
"path": "大数据框架学习/Flink_Windows.md",
"chars": 3870,
"preview": "# Flink Windows\n<nav>\n<a href=\"#一窗口概念\">一、窗口概念</a><br/>\n<a href=\"#二Time-Windows\">二、Time Windows</a><br/>\n &nbs"
},
{
"path": "大数据框架学习/Flink开发环境搭建.md",
"chars": 8862,
"preview": "# Flink 开发环境搭建\n\n<nav>\n<a href=\"#一安装-Scala-插件\">一、安装 Scala 插件</a><br/>\n<a href=\"#二Flink-项目初始化\">二、Flink 项目初始化</a><br/>\n&nbs"
},
{
"path": "大数据框架学习/Flink核心概念综述.md",
"chars": 7167,
"preview": "# Flink 核心概念综述\n<nav>\n<a href=\"#一Flink-简介\">一、Flink 简介</a><br/>\n<a href=\"#二Flink-核心架构\">二、Flink 核心架构</a><br/>\n &"
},
{
"path": "大数据框架学习/Flink状态管理与检查点机制.md",
"chars": 12186,
"preview": "# Flink 状态管理\n<nav>\n<a href=\"#一状态分类\">一、状态分类</a><br/>\n <a href=\"#21-算子状态\">2"
},
{
"path": "大数据框架学习/Flume整合Kafka.md",
"chars": 2768,
"preview": "# Flume 整合 Kafka\n\n<nav>\n<a href=\"#一背景\">一、背景</a><br/>\n<a href=\"#二整合流程\">二、整合流程</a><br/>\n &nbs"
},
{
"path": "大数据框架学习/Flume简介及基本使用.md",
"chars": 8199,
"preview": "# Flume 简介及基本使用\n\n<nav>\n<a href=\"#一Flume简介\">一、Flume简介</a><br/>\n<a href=\"#二Flume架构和基本概念\">二、Flume架构和基本概念</a><br/>\n &nb"
},
{
"path": "大数据框架学习/HDFS-Java-API.md",
"chars": 9781,
"preview": "# HDFS Java API\n\n<nav>\n<a href=\"#一-简介\">一、 简介</a><br/>\n<a href=\"#二API的使用\">二、API的使用</a><br/>\n  "
},
{
"path": "大数据框架学习/HDFS常用Shell命令.md",
"chars": 2114,
"preview": "# HDFS 常用 shell 命令\n\n**1. 显示当前目录结构**\n\n```shell\n# 显示当前目录结构\nhadoop fs -ls <path>\n# 递归显示当前目录结构\nhadoop fs -ls -R <path>\n# "
},
{
"path": "大数据框架学习/Hadoop-HDFS.md",
"chars": 5240,
"preview": "# Hadoop分布式文件系统——HDFS\n\n<nav>\n<a href=\"#一介绍\">一、介绍</a><br/>\n<a href=\"#二HDFS-设计原理\">二、HDFS 设计原理</a><br/>\n &"
},
{
"path": "大数据框架学习/Hadoop-MapReduce.md",
"chars": 10779,
"preview": "# 分布式计算框架——MapReduce\n\n<nav>\n<a href=\"#一MapReduce概述\">一、MapReduce概述</a><br/>\n<a href=\"#二MapReduce编程模型简述\">二、MapReduce编程模型简述"
},
{
"path": "大数据框架学习/Hadoop-YARN.md",
"chars": 4462,
"preview": "# 集群资源管理器——YARN\n\n<nav>\n<a href=\"#一hadoop-yarn-简介\">一、hadoop yarn 简介</a><br/>\n<a href=\"#二YARN架构\">二、YARN架构</a><br/>\n &"
},
{
"path": "大数据框架学习/Hbase_Java_API.md",
"chars": 23836,
"preview": "# HBase Java API 的基本使用\n\n<nav>\n<a href=\"#一简述\">一、简述</a><br/>\n<a href=\"#二Java-API-1x-基本使用\">二、Java API 1.x 基本使用</a><br/>\n<a "
},
{
"path": "大数据框架学习/Hbase_Shell.md",
"chars": 5979,
"preview": "# Hbase 常用 Shell 命令\n<nav>\n<a href=\"#一基本命令\">一、基本命令</a><br/>\n <a href=\"#11-"
},
{
"path": "大数据框架学习/Hbase协处理器详解.md",
"chars": 13046,
"preview": "# Hbase 协处理器\n\n<nav>\n<a href=\"#一简述\">一、简述</a><br/>\n<a href=\"#二协处理器类型\">二、协处理器类型</a><br/>\n <a href=\"#"
},
{
"path": "大数据框架学习/Hbase容灾与备份.md",
"chars": 4273,
"preview": "# Hbase容灾与备份\n\n<nav>\n<a href=\"#一前言\">一、前言</a><br/>\n<a href=\"#二CopyTable\">二、CopyTable</a><br/>\n <a h"
},
{
"path": "大数据框架学习/Hbase的SQL中间层_Phoenix.md",
"chars": 7455,
"preview": "# Hbase的SQL中间层——Phoenix\n\n<nav>\n<a href=\"#一Phoenix简介\">一、Phoenix简介</a><br/>\n<a href=\"#二Phoenix安装\">二、Phoenix安装</a><br/>\n&nb"
},
{
"path": "大数据框架学习/Hbase简介.md",
"chars": 2572,
"preview": "# HBase简介\n\n<nav>\n<a href=\"#一Hadoop的局限\">一、Hadoop的局限</a><br/>\n<a href=\"#二HBase简介\">二、HBase简介</a><br/>\n<a href=\"#三HBase-Tabl"
},
{
"path": "大数据框架学习/Hbase系统架构及数据结构.md",
"chars": 6264,
"preview": "# Hbase系统架构及数据结构\n\n<nav>\n<a href=\"#一基本概念\">一、基本概念</a><br/>\n <a href=\"#11-Row-Key-行键\">1.1 Row Key (行"
},
{
"path": "大数据框架学习/Hbase过滤器详解.md",
"chars": 13392,
"preview": "# Hbase 过滤器详解\n\n<nav>\n<a href=\"#一HBase过滤器简介\">一、HBase过滤器简介</a><br/>\n<a href=\"#二过滤器基础\">二、过滤器基础</a><br/>\n &"
},
{
"path": "大数据框架学习/HiveCLI和Beeline命令行的基本使用.md",
"chars": 10346,
"preview": "# Hive CLI和Beeline命令行的基本使用\n\n<nav>\n<a href=\"#一Hive-CLI\">一、Hive CLI</a><br/>\n &nb"
},
{
"path": "大数据框架学习/Hive分区表和分桶表.md",
"chars": 4423,
"preview": "# Hive分区表和分桶表\n\n<nav>\n<a href=\"#一分区表\">一、分区表</a><br/>\n<a href=\"#二分桶表\">二、分桶表</a><br/>\n<a href=\"#三分区表和分桶表结合使用\">三、分区表和分桶表结合使用"
},
{
"path": "大数据框架学习/Hive常用DDL操作.md",
"chars": 9965,
"preview": "# Hive常用DDL操作\n\n<nav>\n<a href=\"#一Database\">一、Database</a><br/>\n <a href=\"#"
},
{
"path": "大数据框架学习/Hive常用DML操作.md",
"chars": 8237,
"preview": "# Hive 常用DML操作\n\n<nav>\n<a href=\"#一加载文件数据到表\">一、加载文件数据到表</a><br/>\n<a href=\"#二查询结果插入到表\">二、查询结果插入到表</a><br/>\n<a href=\"#三使用SQL"
},
{
"path": "大数据框架学习/Hive数据查询详解.md",
"chars": 9223,
"preview": "# Hive数据查询详解\n\n<nav>\n<a href=\"#一数据准备\">一、数据准备</a><br/>\n<a href=\"#二单表查询\">二、单表查询</a><br/>\n &nbs"
},
{
"path": "大数据框架学习/Hive简介及核心概念.md",
"chars": 7976,
"preview": "# Hive简介及核心概念\n\n<nav>\n<a href=\"#一简介\">一、简介</a><br/>\n<a href=\"#二Hive的体系架构\">二、Hive的体系架构</a><br/>\n<a href=\"#三数据类型\">三、数据类型</a>"
},
{
"path": "大数据框架学习/Hive视图和索引.md",
"chars": 5334,
"preview": "# Hive 视图和索引\n\n<nav>\n<a href=\"#一视图\">一、视图</a><br/>\n<a href=\"#二索引\">二、索引</a><br/>\n<a href=\"#三索引案例\">三、索引案例</a><br/>\n<a href=\""
},
{
"path": "大数据框架学习/Kafka消费者详解.md",
"chars": 12333,
"preview": "# Kafka消费者详解\n\n<nav>\n<a href=\"#一消费者和消费者群组\">一、消费者和消费者群组</a><br/>\n<a href=\"#二分区再均衡\">二、分区再均衡</a><br/>\n<a href=\"#三创建Kafka消费者\""
},
{
"path": "大数据框架学习/Kafka深入理解分区副本机制.md",
"chars": 6920,
"preview": "# 深入理解Kafka副本机制\n\n<nav>\n<a href=\"#一Kafka集群\">一、Kafka集群</a><br/>\n<a href=\"#二副本机制\">二、副本机制</a><br/>\n &"
},
{
"path": "大数据框架学习/Kafka生产者详解.md",
"chars": 9610,
"preview": "# Kafka生产者详解\n\n<nav>\n<a href=\"#一生产者发送消息的过程\">一、生产者发送消息的过程</a><br/>\n<a href=\"#二创建生产者\">二、创建生产者</a><br/>\n<a href=\"#二发送消息\">二、发"
},
{
"path": "大数据框架学习/Kafka简介.md",
"chars": 2321,
"preview": "# Kafka简介\n\n<nav>\n<a href=\"#一Kafka简介\">一、Kafka简介</a><br/>\n<a href=\"#二Kafka核心概念\">二、Kafka核心概念</a><br/>\n &nb"
},
{
"path": "大数据框架学习/Scala函数和闭包.md",
"chars": 6189,
"preview": "# 函数和闭包\n\n<nav>\n<a href=\"#一函数\">一、函数</a><br/>\n <a href=\"#11-函数与方法\">1.1 函数与方"
},
{
"path": "大数据框架学习/Scala列表和集.md",
"chars": 10429,
"preview": "# List & Set\n\n<nav>\n<a href=\"#一List字面量\">一、List字面量</a><br/>\n<a href=\"#二List类型\">二、List类型</a><br/>\n<a href=\"#三构建List\">三、构建L"
},
{
"path": "大数据框架学习/Scala基本数据类型和运算符.md",
"chars": 5171,
"preview": "# Scala基本数据类型和运算符\n\n<nav>\n<a href=\"#一数据类型\">一、数据类型</a><br/>\n<a href=\"#二字面量\">二、字面量</a><br/>\n<a href=\"#三运算符\">三、运算符</a><br/>\n"
},
{
"path": "大数据框架学习/Scala数组.md",
"chars": 3928,
"preview": "# Scala 数组相关操作\n\n<nav>\n<a href=\"#一定长数组\">一、定长数组</a><br/>\n<a href=\"#二变长数组\">二、变长数组</a><br/>\n<a href=\"#三数组遍历\">三、数组遍历</a><br/>"
},
{
"path": "大数据框架学习/Scala映射和元组.md",
"chars": 5574,
"preview": "# Map & Tuple\n\n<nav>\n<a href=\"#一映射Map\">一、映射(Map)</a><br/>\n <a href=\"#11-构"
},
{
"path": "大数据框架学习/Scala模式匹配.md",
"chars": 4026,
"preview": "# Scala模式匹配\n\n<nav>\n<a href=\"#一模式匹配\">一、模式匹配</a><br/>\n <a href=\"#11-更好的swit"
},
{
"path": "大数据框架学习/Scala流程控制语句.md",
"chars": 3626,
"preview": "# 流程控制语句\n\n<nav>\n<a href=\"#一条件表达式if\">一、条件表达式if</a><br/>\n<a href=\"#二块表达式\">二、块表达式</a><br/>\n<a href=\"#三循环表达式while\">三、循环表达式wh"
},
{
"path": "大数据框架学习/Scala简介及开发环境配置.md",
"chars": 2926,
"preview": "# Scala简介及开发环境配置\n\n<nav>\n<a href=\"#一Scala简介\">一、Scala简介</a><br/>\n<a href=\"#二配置IDEA开发环境\">二、配置IDEA开发环境</a><br/>\n</nav>\n\n\n## "
},
{
"path": "大数据框架学习/Scala类和对象.md",
"chars": 7585,
"preview": "# 类和对象\n\n<nav>\n<a href=\"#一初识类和对象\">一、初识类和对象</a><br/>\n<a href=\"#二类\">二、类</a><br/>\n "
},
{
"path": "大数据框架学习/Scala类型参数.md",
"chars": 12153,
"preview": "# 类型参数\n\n<nav>\n<a href=\"#一泛型\">一、泛型</a><br/>\n <a href=\"#11-泛型类\">1.1 泛型类</a>"
},
{
"path": "大数据框架学习/Scala继承和特质.md",
"chars": 8130,
"preview": "# 继承和特质\n\n<nav>\n<a href=\"#一继承\">一、继承</a><br/>\n <a href=\"#11-Scala中的继承结构\">1."
},
{
"path": "大数据框架学习/Scala隐式转换和隐式参数.md",
"chars": 7530,
"preview": "# 隐式转换和隐式参数\n\n<nav>\n<a href=\"#一隐式转换\">一、隐式转换</a><br/>\n <a href=\"#11-使用隐式转换\""
},
{
"path": "大数据框架学习/Scala集合类型.md",
"chars": 13867,
"preview": "# 集合\n\n<nav>\n<a href=\"#一集合简介\">一、集合简介</a><br/>\n<a href=\"#二集合结构\">二、集合结构</a><br/>\n "
},
{
"path": "大数据框架学习/SparkSQL_Dataset和DataFrame简介.md",
"chars": 6070,
"preview": "# DataFrame和Dataset简介\n\n<nav>\n<a href=\"#一Spark-SQL简介\">一、Spark SQL简介</a><br/>\n<a href=\"#二DataFrame--DataSet\">二、DataFrame &"
},
{
"path": "大数据框架学习/SparkSQL外部数据源.md",
"chars": 20284,
"preview": "# Spark SQL 外部数据源\n\n<nav>\n<a href=\"#一简介\">一、简介</a><br/>\n <a href=\"#11-多数据源支"
},
{
"path": "大数据框架学习/SparkSQL常用聚合函数.md",
"chars": 8953,
"preview": "# 聚合函数Aggregations\n\n<nav>\n<a href=\"#一简单聚合\">一、简单聚合</a><br/>\n <a href=\"#11-"
},
{
"path": "大数据框架学习/SparkSQL联结操作.md",
"chars": 4999,
"preview": "# Spark SQL JOIN\n\n<nav>\n<a href=\"#一-数据准备\">一、 数据准备</a><br/>\n<a href=\"#二连接类型\">二、连接类型</a><br/>\n &nbs"
},
{
"path": "大数据框架学习/Spark_RDD.md",
"chars": 7642,
"preview": "\n\n# 弹性式数据集RDDs\n\n<nav>\n<a href=\"#一RDD简介\">一、RDD简介</a><br/>\n<a href=\"#二创建RDD\">二、创建RDD</a><br/>\n &nbs"
},
{
"path": "大数据框架学习/Spark_Streaming与流处理.md",
"chars": 2514,
"preview": "# Spark Streaming与流处理\n\n<nav>\n<a href=\"#一流处理\">一、流处理</a><br/>\n <a href=\"#11"
},
{
"path": "大数据框架学习/Spark_Streaming基本操作.md",
"chars": 10866,
"preview": "# Spark Streaming 基本操作\n\n<nav>\n<a href=\"#一案例引入\">一、案例引入</a><br/>\n <a href=\""
},
{
"path": "大数据框架学习/Spark_Streaming整合Flume.md",
"chars": 11125,
"preview": "# Spark Streaming 整合 Flume\n\n<nav>\n<a href=\"#一简介\">一、简介</a><br/>\n<a href=\"#二推送式方法\">二、推送式方法</a><br/>\n &nbs"
},
{
"path": "大数据框架学习/Spark_Streaming整合Kafka.md",
"chars": 9847,
"preview": "# Spark Streaming 整合 Kafka\n\n<nav>\n<a href=\"#一版本说明\">一、版本说明</a><br/>\n<a href=\"#二项目依赖\">二、项目依赖</a><br/>\n<a href=\"#三整合Kafka\">"
},
{
"path": "大数据框架学习/Spark_Structured_API的基本使用.md",
"chars": 5411,
"preview": "# Structured API基本使用\n\n<nav>\n<a href=\"#一创建DataFrame和Dataset\">一、创建DataFrame和Dataset</a><br/>\n<a href=\"#二Columns列操作\">二、Colu"
},
{
"path": "大数据框架学习/Spark_Transformation和Action算子.md",
"chars": 14590,
"preview": "# Transformation 和 Action 常用算子\n\n<nav>\n<a href=\"#一Transformation\">一、Transformation</a><br/>\n  "
},
{
"path": "大数据框架学习/Spark简介.md",
"chars": 3264,
"preview": "# Spark简介\n\n<nav>\n<a href=\"#一简介\">一、简介</a><br/>\n<a href=\"#二特点\">二、特点</a><br/>\n<a href=\"#三集群架构\">三、集群架构</a><br/>\n<a href=\"#四核"
},
{
"path": "大数据框架学习/Spark累加器与广播变量.md",
"chars": 2531,
"preview": "# Spark 累加器与广播变量\n\n<nav>\n<a href=\"#一简介\">一、简介</a><br/>\n<a href=\"#二累加器\">二、累加器</a><br/>\n "
},
{
"path": "大数据框架学习/Spark部署模式与作业提交.md",
"chars": 7638,
"preview": "# Spark部署模式与作业提交\n\n<nav>\n<a href=\"#一作业提交\">一、作业提交</a><br/>\n<a href=\"#二Local模式\">二、Local模式</a><br/>\n<a href=\"#三Standalone模式\""
},
{
"path": "大数据框架学习/Spring+Mybtais+Phoenix整合.md",
"chars": 12138,
"preview": "# Spring/Spring Boot 整合 Mybatis + Phoenix\n\n<nav>\n<a href=\"#一前言\">一、前言</a><br/>\n<a href=\"#二Spring-+-Mybatis-+-Phoenix\">二、S"
},
{
"path": "大数据框架学习/Sqoop基本使用.md",
"chars": 9118,
"preview": "# Sqoop基本使用\n\n<nav>\n<a href=\"#一Sqoop-基本命令\">一、Sqoop 基本命令</a><br/>\n<a href=\"#二Sqoop-与-MySQL\">二、Sqoop 与 MySQL</a><br/>\n<a hr"
},
{
"path": "大数据框架学习/Sqoop简介与安装.md",
"chars": 3954,
"preview": "# Sqoop 简介与安装\n\n<nav>\n<a href=\"#一Sqoop-简介\">一、Sqoop 简介</a><br/>\n<a href=\"#二安装\">二、安装</a><br/>\n <a hr"
},
{
"path": "大数据框架学习/Storm三种打包方式对比分析.md",
"chars": 10758,
"preview": "# Storm三种打包方式对比分析\n\n<nav>\n<a href=\"#一简介\">一、简介</a><br/>\n<a href=\"#二mvn-package\">二、mvn package</a><br/>\n<a href=\"#三maven-as"
},
{
"path": "大数据框架学习/Storm和流处理简介.md",
"chars": 3432,
"preview": "# Storm和流处理简介\n\n<nav>\n<a href=\"#一Storm\">一、Storm</a><br/>\n <a href=\"#11-简介\""
},
{
"path": "大数据框架学习/Storm核心概念详解.md",
"chars": 5470,
"preview": "# Storm 核心概念详解\n\n<nav>\n<a href=\"#一storm核心概念\">一、Storm核心概念</a><br/>\n <a href=\"#11--Topologies拓扑\">1.1"
},
{
"path": "大数据框架学习/Storm编程模型详解.md",
"chars": 12840,
"preview": "# Storm 编程模型\n\n<nav>\n<a href=\"#一简介\">一、简介</a><br/>\n<a href=\"#二IComponent接口\">二、IComponent接口</a><br/>\n<a href=\"#三Spout\">三、Sp"
},
{
"path": "大数据框架学习/Storm集成HBase和HDFS.md",
"chars": 13046,
"preview": "# Storm集成HDFS和HBase\n\n<nav>\n<a href=\"#一Storm集成HDFS\">一、Storm集成HDFS</a><br/>\n<a href=\"#二Storm集成HBase\">二、Storm集成HBase</a><br"
},
{
"path": "大数据框架学习/Storm集成Kakfa.md",
"chars": 9977,
"preview": "# Storm集成Kafka\n\n<nav>\n<a href=\"#一整合说明\">一、整合说明</a><br/>\n<a href=\"#二写入数据到Kafka\">二、写入数据到Kafka</a><br/>\n<a href=\"#三从Kafka中读取"
},
{
"path": "大数据框架学习/Storm集成Redis详解.md",
"chars": 19082,
"preview": "# Storm 集成 Redis 详解\n\n<nav>\n<a href=\"#一简介\">一、简介</a><br/>\n<a href=\"#二集成案例\">二、集成案例</a><br/>\n<a href=\"#三storm-redis-实现原理\">三、"
},
{
"path": "大数据框架学习/Zookeeper_ACL权限控制.md",
"chars": 8354,
"preview": "# Zookeeper ACL\n\n<nav>\n<a href=\"#一前言\">一、前言</a><br/>\n<a href=\"#二使用Shell进行权限管理\">二、使用Shell进行权限管理</a><br/>\n  "
},
{
"path": "大数据框架学习/Zookeeper_Java客户端Curator.md",
"chars": 9921,
"preview": "# Zookeeper Java 客户端 ——Apache Curator\n\n<nav>\n<a href=\"#一基本依赖\">一、基本依赖</a><br/>\n<a href=\"#二客户端相关操作\">二、客户端相关操作</a><br/>\n&nb"
},
{
"path": "大数据框架学习/Zookeeper常用Shell命令.md",
"chars": 9257,
"preview": "# Zookeeper常用Shell命令\n\n<nav>\n<a href=\"#一节点增删改查\">一、节点增删改查</a><br/>\n <a href"
},
{
"path": "大数据框架学习/Zookeeper简介及核心概念.md",
"chars": 7223,
"preview": "# Zookeeper简介及核心概念\n\n<nav>\n<a href=\"#一Zookeeper简介\">一、Zookeeper简介</a><br/>\n<a href=\"#二Zookeeper设计目标\">二、Zookeeper设计目标</a><b"
},
{
"path": "大数据框架学习/installation/Azkaban_3.x_编译及部署.md",
"chars": 3200,
"preview": "# Azkaban 3.x 编译及部署\n\n<nav>\n<a href=\"#一Azkaban-源码编译\">一、Azkaban 源码编译</a><br/>\n<a href=\"#二Azkaban-部署模式\">二、Azkaban 部署模式</a><"
},
{
"path": "大数据框架学习/installation/Flink_Standalone_Cluster.md",
"chars": 7720,
"preview": "# Flink Standalone Cluster\n<nav>\n<a href=\"#一部署模式\">一、部署模式</a><br/>\n<a href=\"#二单机模式\">二、单机模式</a><br/>\n &nb"
},
{
"path": "大数据框架学习/installation/HBase单机环境搭建.md",
"chars": 4279,
"preview": "# HBase基本环境搭建\n\n<nav>\n<a href=\"#一安装前置条件说明\">一、安装前置条件说明</a><br/>\n<a href=\"#二Standalone-模式\">二、Standalone 模式</a><br/>\n<a href"
},
{
"path": "大数据框架学习/installation/HBase集群环境搭建.md",
"chars": 5146,
"preview": "# HBase集群环境配置\n\n<nav>\n<a href=\"#一集群规划\">一、集群规划</a><br/>\n<a href=\"#二前置条件\">二、前置条件</a><br/>\n<a href=\"#三集群搭建\">三、集群搭建</a><br/>\n"
},
{
"path": "大数据框架学习/installation/Hadoop单机环境搭建.md",
"chars": 3945,
"preview": "# Hadoop单机版环境搭建\n\n<nav>\n<a href=\"#一前置条件\">一、前置条件</a><br/>\n<a href=\"#二配置-SSH-免密登录\">二、配置 SSH 免密登录</a><br/>\n<a href=\"#三Hadoop"
},
{
"path": "大数据框架学习/installation/Hadoop集群环境搭建.md",
"chars": 4988,
"preview": "# Hadoop集群环境搭建\n\n<nav>\n<a href=\"#一集群规划\">一、集群规划</a><br/>\n<a href=\"#二前置条件\">二、前置条件</a><br/>\n<a href=\"#三配置免密登录\">三、配置免密登录</a><"
},
{
"path": "大数据框架学习/installation/Linux下Flume的安装.md",
"chars": 1049,
"preview": "# Linux下Flume的安装\n\n\n## 一、前置条件\n\nFlume 需要依赖 JDK 1.8+,JDK 安装方式见本仓库:\n\n> [Linux 环境下 JDK 安装](https://github.com/heibaiying/BigD"
},
{
"path": "大数据框架学习/installation/Linux下JDK安装.md",
"chars": 912,
"preview": "# Linux下JDK的安装\n\n>**系统环境**:centos 7.6\n>\n>**JDK 版本**:jdk 1.8.0_20\n\n\n\n### 1. 下载并解压\n\n在[官网](https://www.oracle.com/technetwor"
},
{
"path": "大数据框架学习/installation/Linux下Python安装.md",
"chars": 1116,
"preview": "## Linux下Python安装\n\n>**系统环境**:centos 7.6\n>\n>**Python 版本**:Python-3.6.8\n\n### 1. 环境依赖\n\nPython3.x 的安装需要依赖这四个组件:gcc, zlib,zli"
},
{
"path": "大数据框架学习/installation/Linux环境下Hive的安装部署.md",
"chars": 4686,
"preview": "# Linux环境下Hive的安装\n\n<nav>\n<a href=\"#一安装Hive\">一、安装Hive</a><br/>\n <a href=\"#"
},
{
"path": "大数据框架学习/installation/Spark开发环境搭建.md",
"chars": 3736,
"preview": "# Spark开发环境搭建\n\n<nav>\n<a href=\"#一安装Spark\">一、安装Spark</a><br/>\n<a href=\"#二词频统计案例\">二、词频统计案例</a><br/>\n<a href=\"#三Scala开发环境配置\""
},
{
"path": "大数据框架学习/installation/Spark集群环境搭建.md",
"chars": 4607,
"preview": "# 基于ZooKeeper搭建Spark高可用集群\n\n<nav>\n<a href=\"#一集群规划\">一、集群规划</a><br/>\n<a href=\"#二前置条件\">二、前置条件</a><br/>\n<a href=\"#三Spark集群搭建\""
},
{
"path": "大数据框架学习/installation/Storm单机环境搭建.md",
"chars": 1608,
"preview": "# Storm单机版本环境搭建\n\n### 1. 安装环境要求\n\n> you need to install Storm's dependencies on Nimbus and the worker machines. These are:"
},
{
"path": "大数据框架学习/installation/Storm集群环境搭建.md",
"chars": 3910,
"preview": "# Storm集群环境搭建\n\n<nav>\n<a href=\"#一集群规划\">一、集群规划</a><br/>\n<a href=\"#二前置条件\">二、前置条件</a><br/>\n<a href=\"#三集群搭建\">三、集群搭建</a><br/>\n"
},
{
"path": "大数据框架学习/installation/Zookeeper单机环境和集群环境搭建.md",
"chars": 4491,
"preview": "# Zookeeper单机环境和集群环境搭建\n\n<nav>\n<a href=\"#一单机环境搭建\">一、单机环境搭建</a><br/>\n <a hr"
},
{
"path": "大数据框架学习/installation/基于Zookeeper搭建Hadoop高可用集群.md",
"chars": 13237,
"preview": "# 基于ZooKeeper搭建Hadoop高可用集群\n\n<nav>\n<a href=\"#一高可用简介\">一、高可用简介</a><br/>\n<a href=\"#二集群规划\">二、集群规划</a><br/>\n<a href=\"#三前置条件\">三"
},
{
"path": "大数据框架学习/installation/基于Zookeeper搭建Kafka高可用集群.md",
"chars": 5848,
"preview": "# 基于Zookeeper搭建Kafka高可用集群\n\n<nav>\n<a href=\"#一Zookeeper集群搭建\">一、Zookeeper集群搭建</a><br/>\n "
},
{
"path": "大数据框架学习/installation/虚拟机静态IP及多IP配置.md",
"chars": 2354,
"preview": "# 虚拟机静态IP及多IP配置\n\n<nav>\n<a href=\"#一虚拟机静态IP配置\">一、虚拟机静态IP配置</a><br/>\n <a hre"
},
{
"path": "大数据框架学习/大数据学习路线.md",
"chars": 7273,
"preview": "# 大数据学习路线\n\n<nav>\n<a href=\"#一大数据处理流程\">一、大数据处理流程</a><br/>\n <a href=\"#11-数据收"
},
{
"path": "大数据框架学习/大数据常用软件安装指南.md",
"chars": 1739,
"preview": "## 大数据常用软件安装指南\n\n为方便大家查阅,本仓库所有软件的安装方式单独整理如下:\n\n### 一、基础软件安装\n\n1. [Linux 环境下 JDK 安装](installation/Linux下JDK安装.md)\n2. [Linux "
},
{
"path": "大数据框架学习/大数据应用常用打包方式.md",
"chars": 9527,
"preview": "# 大数据应用常用打包方式\n\n<nav>\n<a href=\"#一简介\">一、简介</a><br/>\n<a href=\"#二mvn-package\">二、mvn package</a><br/>\n<a href=\"#三maven-assemb"
},
{
"path": "大数据框架学习/大数据技术栈思维导图.md",
"chars": 70,
"preview": "<div align=\"center\"> <img src=\"../pictures/大数据技术栈思维导图.png\"/> </div>\n\n"
},
{
"path": "大数据框架学习/资料分享与工具推荐.md",
"chars": 1713,
"preview": "这里分享一些自己学习过程中觉得不错的资料和开发工具。\n\n\n\n## :book: 经典书籍\n\n- [《hadoop 权威指南 (第四版)》](https://book.douban.com/subject/27115351/) 2017 年\n"
},
{
"path": "实战系列文章/Flink实战.md",
"chars": 4226,
"preview": "# Flink实战进阶文章合集\n\n1. [菜鸟供应链实时技术架构演进](https://mp.weixin.qq.com/s/fnx2GnbCWNcaptVPsSp7dw)\n2. [趣头条实战-基于Flink+ClickHouse构建实时数"
}
]
// ... and 50 more files (download for full content)
About this extraction
This page contains the full source code of the wangzhiwubigdata/God-Of-BigData GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 250 files (1.6 MB), approximately 714.6k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.