Spark集群架构
1.Worker Node是Spark的工作节点
2.Executor是执行进程,在进程中处理Task任务。此外,包含一块缓存
3.Task,对应的是RDD中的一个分区数据
4.Cluster Manager集群管理器
5.Driver Program用户编写的Spark驱动程序美
6.每个Driver中,都有一个sc对象
总结
SparkContext的职责
1.负责和ICM交互,申请资源
2.负责当前Driver的任务的调度,分配,监控以及任务的失败恢复
Cluster Manager的职责:
负责整个集群的资源管理
类比Yarn来记忆:
①ResourceManager用于资源管理
②每个MRJob会创建一个ApplictionMaster,负责当前Job的任务管理
Spark的使用模式,常见的:
①Local本地章机模式
Standalone Spark集群模式,此模式下,Spark集群的资源管理有Master来负责
③on Yarn Spark集群的资源管理是由Yarn来管理的。补充:只是更改资源管理器,但Spark的任务调度依然是SparkContext
详细说明
1.Driver Program
用户编写的Spark程序称为Driver Program。每个Driver程序包含一个代表集群环境的SparkContext
对象,程序的执行从Driver程序开始,所有操作执行结束后回到Driver程序中,在Driver程序中结
束。如果你是用spark shell ,那么当你启动Spark shell的时候,系统后台自启了一个Spark驱动器程
序,就是在Spark shell 中预加载的一个叫作 sc的SparkContext对象。如果驱动器程序终止,那么Spark应用也就结束了。
2.SparkContext对象
每个Driver Program里都有一个SparkContext对象,职责如下∶
1 ) SparkContext对象联系cluster manager(集群管理器),让cluster manager为Worker Node分配CPU、内存等资源。此外,cluster manager会在 Worker Node 上启动一个执行器(专属于本
驱动程序)。
2 )和Executor进程交互,负责任务的调度分配。
3.cluster manager集群管理器
它对应的是Master进程。集群管理器负责集群的资源调度,比如为Worker Node分配CPU、内存等资
源。并实时监控Worker的资源使用情况。一个Worker Node默认情况下分配一个Executor (进程)。
从图中可以看到sc和Executor之间画了一根线条,这表明程序运行时,sc是直接与Executor进行交互的。
4.Worker Node
Worker节点。集群上的计算节点,对应一台物理机器
5.Worker进程
它对应Worder进程,用于和Master进程交互,向Master注册和汇报自身节点的资源使用情况,并管理和启动Executor进程
6.Executor
负责运行Task计算任务,并将计算结果回传到Driver中。
Spark Shuffle
shuffle 洗牌,即按照某种分组条件,将具有某种共同特征的数据分发到正确的分区(服务器)。这个过程就是洗牌过程
但是shuffle过程底层是比较复杂的,需要考虑如下因素:
1.大量的数据分散到多台服务器,并且产生的中间数据量巨大,如果光靠内存来存储,肯定是不够的。所以势必会发生多次的磁盘溢写
2.当产生Shuffle时,就意味着发生大量的网络数据传输,所以需要考虑如何节省带宽,比如可以采取压缩机制
集群的性能瓶颈:
①磁盘看转速,转速越快,I/O性能越高②cpu看核数
③内存看大小一般是64GB以上
④带宽―一般是千兆,最好是万兆。最容易出现瓶颈的就是带宽
3.当shuffle时,数据通过网络通信,数据需要序列化,所以序列化性能的高低也很关键。
4.Spark的Shuffle,之所以产生中间的临时文件,目的之一也是防止当Shuffle的RDD子分区数据丢失时,导致整个计算链的重新计算。
补充:一个分区对应一个Task,Task分两种:
①MapTask
②ResultTask
综上,针对这么多的细节和难题,Spark为我们提供Shuffle管理器,解蔽了这些底层细节。
Spark的Shuffle Mananger
目前Spark提供了两种Shuffle管理器
1.Hash Based Shuffle Manager
2.Sort Based Shuffle Manager
Hash Shuff1e管理器:
1.产生的临时文件总数=MapTask*ResultTask数
2.每个ResultTask在进行ShuffleRead时,只去获取属于自己分区的数据3.Hash Shuffle管理器,最大特点:Shuffle过程中,没有任何的排序操作,从而进一步提高Shuffle过程中数据处理效率。后来Hadoop3.0中也针对排序问题作出了优化
补充:这种Hash Shuffle管理器的问题在于,可能会产生很多的临时文件,当临时文件数过多,造成的问题:
①同时打开多个临时文件,会耗费很多内存
②这么多文件就意味着会产生大量的磁盘的随机I/O,造成性能下降。所以,目前Spark底层默认的Shuffle管理已经不是Hash Shuffle 了
Hash Shuffle管理的适用场景:产生的临时文件数不是很多(<200个)时,可以使用这种,因为没有任何的排序,所以处理效果很高。
Sort Based Shuffle Manager
Sort Shuffle管理器,优化了之前的Hash Shuffle的临时文件数过多的缺点。每个MapTask就只会生成一个临时文件。下游的
ResultTask通过临时文件的索引去获取属于自己的分区数据。所以通过这种设计,极大的减少了Shuffle临时文件数的产生。Sort Shuffle管理器是Spark1.2之后默认的Shuffle管理器
补充:Sort Shuffle管理器,在Shuffle过程中有排序过程,但是这种排序只是对分区的索引进行排序,不对分区内的数据排序,所以性能也是很高的
如果面试问到SparkShuffle
先说Shuffle定义->Spark的Hash Shuffle管理器->SortShuffle管理器
shuffle 相关参数配置(了解即可)
概述
Shuffle是Spark Core比较复杂的模块,它也是非常影响性能的操作之一。因此,在这里整理了会影响Shuffle性能的各项配置。
1 ) spark.shuffle.manager
Spark 1.2.0官方版本支持两种方式的Shuffle,即Hash Based Shuffle和Sort Based Shuffle。其中在Spark 1.0之前仅支持Hash Based Shuffle。Spark 1.1引入了Sort Based Shuffle。Spark 1.2的默认Shuffle机制从Hash变成了Sort。如果需要Hash Based Shuffle,只需将spark.shuffle.manager设置成“hash”即可。
配置方式:
1.进入spark安装目录的conf目录
2.cp spark-defaults.conf.template spark-defaults.conf
3.spark.shuffle.manager=hash 或者sort(不配就是sort)
2 ) spark.shuffle.spillI
这个参数的默认值是true,用于指定Shuffle过程中如果内存中的数据超过阈值(参考spark.shuffle.memoryFraction的设置)时是否需要将部分数据临时写入外部存储。如果设置为false,那么这个过程就会一直使用内存,会有内存溢出的风险。因此只有在确定内存足够使用时,才可以将这个选项设置为false。
3 ) spark.shuffle.memoryFraction
在启用spark.shuffle.spill的情况下,spark.shuffle.memoryFraction决定了当Shuffle过程中使用的内存达到总内存多少比例的时候开始spill。在Spark 1.2.0里,这个值是0.2。
此参数可以适当调大,可以控制在0.4~0.6。
通过这个参数可以设置Shuffle过程占用内存的大小,它直接影响了写入到外部存储的频率和垃圾回收的频率。可以适当调大此值,可以减少磁盘I/O次数。
4 ) spark.shuffle.blockTransferService
在Spark 1.2.0中这个配置的默认值是netty,而在之前的版本中是nio。它主要是用于在各个Executor之间传输Shuffle数据。netty的实现更加简洁,但实际上用户不用太关心这个选项。除非有特殊需求,否则采用默认配置即可。
5 ) spark.shuffle.consolidateFiles
这个配置的默认值是false。主要是为了解决在Hash Based Shuffle过程中产生过多文件的问题。如果配置选项为true,那么对于同一个Core(核)上运行的Shuffle Map Task不会产生一个新的Shuffle文件而是重用原来的。
6 ) spark.shuffle.compress和spark.shuffle.spill.compress
这两个参数的默认配置都是true。spark.shuffle.compress和
spark.shuffle.spill.compress都是用来设置Shuffle过程中是否对Shuffle数据进行压缩。
其中,前者针对最终写入本地文件系统的输出文件;后者针对在处理过程需要写入到外部存储的中间数据,即针对最终的shuffle输出文件。
1.设置spark.shuffle.compress
需要评估压缩解压时间带来的时间消耗和因为数据压缩带来的时间节省。如果网络成为瓶
颈,比如集群普遍使用的是千兆网络,那么将这个选项设置为true可能更合理;如果计算
是CPU密集型的,那么将这个选项设置为false可能更好。
2.设置spark.shuffle.spill.compress
如果设置为true,代表处理的中间结果在spill到本地硬盘时都会进行压缩,在将中间结果取回进行merge的时候,要进行解压。因此要综合考虑CPU由于引入压缩、解压的消耗时间和Disk IO因为压缩带来的节省时间的比较。在Disk IO成为瓶颈的场景下,设置为true可能比较合适;如果本地硬盘是SSD,那么设置为false可能比较合适。
7 ) spark.reducer.maxMbInFlight
这个参数用于限制一个Result Task向其他的Executor请求Shuffle数据时所占用的最大内存数,默认是64MB。尤其是如果网卡是千兆和千兆以下的网卡时。默认值是设置这个值需要综合考虑网卡带宽和内存。
RDD容错机制
概述
分布式系统通常在一个机器集群上运行,同时运行的几百台机器中某些出问题的概率大大增加,所以容错设计是分布式系统的一个重要能力。
Spark以前的集群容错处理模型,像MapReduce,将计算转换为一个有向无环图(DAG )的任务集合,这样可以通过重复执行DAG里的一部分任务来完成容错恢复。但是由于主要的数据存储在分布式文件系统中,没有提供其他存储的概念,容错过程需要在网络上进行数据复制,从而增加了大量的消耗。所以,分布式编程中经常需要做检查点,即将某个时机的中间数据写到存储(通常是分布式文件系统)中。
RDD也是一个DAG,每一个RDD都会记住创建该数据集需要哪些操作,跟踪记录RDD的继承关系,这个关系在Spark里面叫lineage(血缘关系)。当一个RDD的某个分区丢失时,RDD是有足够的信息记录其如何通过其他RDD进行计算,且只需重新计算该分区,这是Spark的一个创新。
RDD的缓存
默认情况下,RDD只使用一次,用完即扔,这种机制并不会影响程序的正常执行。但是如果在计算中,某个一子分区数据丢失了,则需要重新计算。如果整个计算链很长,则计算代价很大。
所以Spark提供了RDD的缓存机制,即用户可以显式的将指定的RDD持久化到缓存里。避免重新计算。
缓存操作简单
scala> val r1=sc.makeRDD(List(1,2,3,4),2)
r1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[0] at makeRDD at <console>:24
scala> val r2=r1.map(_*2)
r2: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[1] at map at <console>:26
scala> r2.cache
res0: r2.type = MapPartitionsRDD[1] at map at <console>:26
使用缓存,在case之后设置以下缓存操作,报错,清楚case,再设置缓存
scala> import org.apache.spark.storage._
scala> r2.persist(StorageLevel.MEMORY_AND_DISK)
java.lang.UnsupportedOperationException: Cannot change storage level of an RDD after it was already assigned a level
at org.apache.spark.rdd.RDD.persist(RDD.scala:169)
at org.apache.spark.rdd.RDD.persist(RDD.scala:194)
... 54 elided
scala> r2.unpersist()
res8: r2.type = MapPartitionsRDD[1] at map at <console>:26
scala> r2.persist(StorageLevel.MEMORY_AND_DISK)
res9: r2.type = MapPartitionsRDD[1] at map at <console>:26
对RDD进行缓存的万法:
1.cache
2.persist区别:
cache只有一种缓存级别(memory_only)
persist何以指定其他的缓存级别
代码实现
展示
package cn.persist
import org.apache.spark.SparkConf
import org.apache.spark.SparkContext
import org.apache.spark.storage.StorageLevel
object Driver {
def main(args: Array[String]): Unit = {
val conf=new SparkConf().setMaster("local").setAppName("persisit")
val sc=new SparkContext(conf)
val data=sc.textFile("D://未来/spark练习文件/topk/topk.txt",2)
//-般习惯上将数据源RDD持久化到缓存里,避免数据恢复时发生磁盘工/O
data.cache()
val r1=data.flatMap{line=> line.split(" ")}
//--建议在整个计算链中的中间位置的RDD做持久化。
//--此外,如果整个的计算链很长,比如有30个RDD,可以每隔5个或10个做一下持久化
//--持久化的目的是为了避免重新计算,从而提高执行效率
val r2=r1.map { word => (word,1) }
r2.persist(StorageLevel.MEMORY_AND_DISK)
val r3=r2.reduceByKey{_+_}
r3.foreach{println}
//当整个job执行完毕之后,一定要记得释放掉缓存
data.unpersist()
r2.unpersist()
}
}
常见的缓存级别:
1.memory_only (默认的缓存级别)
2.memory_only_ser
3.memory_and_disky
4.memory_and_disk_ser
5.disk_only
缓存类型详细说明:(看一遍。了解即可)
持久化的方法是调用persist()函数,除了持久化至内存中,还可以在persist()中指定storage level参数
使用其他的类型,具体如下∶
1 )MEMORY_ONLY:将RDD 以反序列化的Java对象的形式存储在VM中.如果内存空间不够,部分数据分区将不会被缓存,在每次需要用到这些数据时重新进行计算.这是默认的级别。
cach()方法对应的级别就是MEMORY_ONLY级别
2 ) MEMORY_AND_DISK:将RDD以反序列化的Java对象的形式存储在JVM中。如果内存空间不够,将未缓存的数据分区存储到磁盘,在需要使用这些分区时从磁盘读取。
3 )MEMORY_ONLY_SER:将RDD 以序列化的Java对象的形式进行存储(每个分区为一个byte数组)。这种方式会比反序列化对象的方式节省很多空间,尤其是在使用fast serialize时会节省更多的空间,但是在读取时会使得CPU的read变得更加密集。如果内存空间不够,部分数据分区将不会被缓存,在每次需要用到这些数据时重新进行计算。
4 )MEMORY_AND_DISK_SER:类似于MEMORY_ONLY_SER,但是溢出的分区会存储到磁盘,而不是在用到它们时重新计算。如果内存空间不够,将未缓存的数据分区存储到磁盘,在需要使用这些分区时从磁盘读取。
5 ) DISK_ONLY:只在磁盘上缓存RDD。
6 )MEMORY_ONLY_2,MEMORY_AND_DISK_2, etc.:与上面的级别功能相同,只不过每个分区在集群中两个节点上建立副本。
7 ) OFF_HEAP将数据存储在. off-heap memory中。使用堆外内存,这是Java虚拟机里面的概念,堆外内存意味着把内存对象分配在Java虚拟机的堆以外的内存,这些内存直接受操作系统管理(而不是虚拟机)。使用堆外内存的好处︰可能会利用到更大的内存存储空间。但是对于数据的垃圾回收会有影响,需要程序员来处理
注意,可能带来一些GC回收问题。
Spark 也会自动持久化一些在shuffle操作过程中产生的临时数据(比如reduceByKey ),即便是用户并没有调用持久化的方法。这样做可以避免当shuffle阶段时如果一个节点挂掉了就得重新计算整个
数据的问题。如果用户打算多次重复使用这些数据,我们仍然建议用户自己调用持久化方法对数据进
行持久化。
扩展∶GC回收机制及算法
1.如何判断数据是否是垃圾数据
①引用计数法
当创建一个对象时,为此对象分配一个引用计数器。当有其他对象引用这个对象时,计数器就+1。当引用失效了,计数器-1。
所以当一个对象的引用计数器=O时,此对象就可以被回收。
优点:原理简单,实现方便
缺点:不能解决对象间循环问题,容易造成内存泄露
String a=helloString b=world
a=b
b=a
上面的例子中,这两个对象的引用计数器永不为0,回收不掉,造成内存泄漏。
②可达性分析法
这种算法的思想:判断一个对象和GC Root是否有相连的引用链,如果没有,则可以进行回收。这种算法解决了引用计数法的缺点(对象间循环引用的问题)
在Java语言中,可作为GC Roots的对象包括下面几种:
1)虚拟机栈(栈帧中的本地变量表)中引用的对象。
2)方法区中类静态属性引用的对象。
3 )方法区中常量引用的对象。
2.如何回收垃圾
1.Mark-Sweep标记清除算法
缺点:回收之后,会产生大量不连续的内存碎片,导致内存环境质量下降。影响是:当分配一个较大对象时,如果找不到连续的内存地址空间,则会提前触发另一次GC (Full GC-全堆GC)
2.Copying算法复制算法
为了解决效率问题,一种称为“复制”(Copying )的收集算法出现了,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为了原来的一半,未免太高了一点。
优点:解决了Mark-Sweep的内存碎片问题
缺点:内存利用率较低,只有50%
以在划分内存空间大小时:按Eden(80%)和两块survivor (10%)。每次是使用Eden和其中的一块survivor。所以在这种优化下,新生代的内存利用率达到90%
========================
新生代,老生代,伊甸园区详细说明
现在的商业虚拟机都采用这种收集算法来回收新生代,IBM公司的专门研究表明,新生代中的对象98%是“朝生夕死”的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和
Survivor的大小比例是8:1,也就是每次新生代中可用内存空间为整个新生代容量的90% ( 80%+10%),只有10%的内存会被“浪费”。
=================
3.标记-整理算法
这种算法是基于标记清除算法做的改进。在清除垃圾之后,进行碎片整理。
针对老生代的垃圾数据回收:
①Mark-Sweep
②Mark-Compact
补充的概念:
新生代的揪c称为:Minor GC频率高
老生代的GC称为: Major(Full GC)频率低当发生Full GC时,是全堆Gc,所以GC时间很长。
所以GC调优的目的是:尽量减少Full CC的出现或延迟Full GC的到来
====================================
标记-整理算法详细说明
复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。
根据老年代的特点,有人提出了另外一种“标记-整理”( Mark-Compact )算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,“标记-整理”算法的示意如下图所示。
所以很多教材说,老年代很少发生GC,因为都是常用的对象,但当老年代的空间满了之后,同样会发生GC回收,称为Major GC,也有的叫Full GC,此时底层一般用的算法就是这种标记-整理算法。
新生代的GC: Minor GC
老生代的GC: Major GC ( full GC)
4分代收集算法
当前商业虚拟机的垃圾收集都采用“分代收集”(Generational Collection )算法,这种算法并没有什么新的思想,只是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记─清理”或者“标记—整理”算法来进行回收。
=============================================
GC回收机制详细说明
概述
说起垃圾收集(Garbage Collection, GC),大部分人都把这项技术当做Java语言的伴生产物。事实上,GC的历史比Java久远,1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言。当Lisp还在胚胎时期时,人们就在思考GC需要完成的3件事情∶:
1)哪些内存数据需要回收?
2)什么时候回收?
3 )如何回收?
经过半个多世纪的发展,目前内存的动态分配与内存回收技术已经相当成熟,一切看起来都进入了“自动化”时代,那为什么我们还要去了解GC和内存分配呢?答案很简单∶当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。
回到我们熟悉的Java语言。Java内存运行时区域的各个部分,其中程序计数器、虚拟机栈、本地方法栈3个区域阪线程而生,随线程而灭,因此这几个区域的内存分配和回收都具备确定性,在这几个区域内就不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了。而Java堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,所以垃圾收集器所关注的是java的堆内存
哪些内存数据需要被回收?
在堆里面存放着Java世界中几乎所有的对象实例,垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”(即不可能再被任何途径使用的对象)。
1)引用计数算法
很多教科书判断对象是否存活的算法是这样的∶给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1﹔任何时刻计数器为0的对象就是不可能再被使用的。
2)可达性分析算法
===========================
GC收集器
上图是Java目前所艾持的7种垃圾收集器有连线,表示可以配合使用。比如serial和Serial old可以一起使用。如果没有连线,就不能一起使用
Serial收集器
1.Serial单线程收集器,特点是:垃圾回收时,会暂停所有的工作线程,而且停顿时间较长。这是java最早的收集器。
Serial的使用场景:可以回收桌面系统的垃圾
=====================
详细说明
Serial收集器是最基本、发展历史最悠久的收集器,曾经(在JDK 1.3.1之前)是虚拟机新生代收集的
唯一选择。
这个收集器是一个单线程的收集器,但它的“单线程”的意义并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。“Stop The World”这个名字也许听起来很酷,但这项工作实际上是由虚拟机在后台自动发起和自动完成的,在用户不可见的情况下把用户正常工作的线程全部停掉,这对很多应用来说都是难以接受的。不妨试想一下,要是你的计算机每运行一个小时就会暂停响应5分钟,你会有什么样的心情?下图示意了Serial/Serial Old收集器的运行过程。
==============================
2.ParNew收集器
ParNew,多线程收集器。用于回收新生代的垃圾数据。因为是多线程收垃圾,所以停顿时间比Serial更短。
此外,ParNew还可以和CMS配合使用。CMS收集器停顿时间是最短的
详细说明
================================
ParNew收集器其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参数(例如:-XX:SurvivorRatio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、对象分配规则、回收策略等都与Seria收集器完全一样,在实现上,这两种收集器也共用了相当多的代码。ParNew收集器的工作过程如下图所示。
========================================
3.CMs(Concurrent Mark sweep)
并发低停顿收集器。即回收垃圾时,CMS的间隔最短。
CMS的使用场景:对于服务器响应延记要求低的场景使用,即注重服务器的低延迟响应速度。
比如HBase框架,后台在回收垃圾时,用的就是CMs
CMS将回收垃圾分为4个过程:
1)初始标记有停顿,仅是扫描对象引用链,所以停顿时间非常短
2)并发标记│
3)重新标记有停顿,仅是扫描对象引用链,所以停顿时间非常短;作用是修正并发标记阶段的引用链变化
4)并发清除垃圾清除阶段变为和用户并发处理机制,因为清除垃圾的停顿是最长的,所以这样设计的目的可以极大的降低停顿时间。并且清除垃圾的线程数可以调节。
其中,1)初始标记、2)重新标记这两个步骤仍然需要“Stip The World”。
优点:停顿时间最短,所以也称为开发停顿收集器
缺点:
①CMS会浮动垃圾,这些浮动垃圾只能等到下一次GC时才能收掉
②垃圾收集线程和用户工作线程一起工作,共同抢占CPU时间片所以可能会降低正常工作线程的执行效率
③CMs (Concurrent Mark Sweep)
底层在回收垃圾时,用的是标记-清除算法,虽然快,但是会产生内存碎片。所以需要定期做碎片整理
详细说明
=========================
CMs收集器
CMS ( Concurrent Mark Sweep )收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。
从名字(包含“Mark Sweep”)上就可以看出,CMS收集器是基于“标记—清除”算法实现的,它的运作过程相对于前面几种收集器来说更复杂一些,整个过程分为4个步骤,包括:
1)初始标记有停顿,仅是扫描对象引用链,所以停顿时间非常短
2)并发标记
3)重新标记有停顿,仅是扫描对象引用链,所以停顿时间非常短;作用是修正并发标记阶段的引用链变化
4)并发清除垃圾清除阶段变为和用户并发处理机制,因为清除垃圾的停顿是最长的,所以这样设计的目的可以极大的降低停顿时间。并且清除垃圾的线程数可以调节。
其中,1)初始标记、2)重新标记这两个步骤仍然需要“Stip The World”。
================================
4.Parallel吞吐量优先收集器
这类收集器并不关注回收的停顿长短,而是关注回收的吞吐量。
扩展:纠删码技术()
文件数据的容错技术:
①副本冗余机制
缺点是:集群的磁盘利用率低
比如HDFS的文件块副本是3副本策略,则利用率: 1/3
②纠删码技术
优点是:提高集群磁盘的利用率,理论上最高可以接近100%
上图演示了刻删码技术的基本思想,红色表示原始的文件块
青色表示引入的数据校验块
纠删码技术的总体思想是:通过编码,让原始数据块和数据校验块产生一个关联关系。
然后当原始数据块丢失后,可以进行恢复。
上图中。磁盘的利用率达到9/12,即75%
举例
①x=1
②y=2
③z=3
④x+y+z=6
⑤2x+3y+z=11
⑥x+2y+3z=14
基于纠删码的思想,有一个经典的算法实现,里所码
总的分两个过程:
1.编程过程,将原始数据块和数据校验块产生关联关系
2.解码过程,当数据块丢失后的一个恢复过程
编码过程:
以下黄块:范德蒙矩阵,特点之—是计算矩阵的行列式值的时候,性能很高
如果一个矩阵的行列值=0,则此矩阵的逆是不存在的
解码过程
详细说明
======================
纠删码的应用
对于在分布式环境下数据存储的可靠性保证,有两种策略︰
1)引入副本冗余机制策略
2)利用纠删码技术,相比于副本策略,纠删码技术可以节省更多磁盘的空间。即有更高的磁盘利用率
拿Hadoop的HDFS来说,策略一般是三副本,当某个副本丢失时,可以通过其他副本复制回来。所以在这种情况下,Hadoop集群的磁盘利用率为1/3。而如果使用纠删码技术后,可以提高磁盘的利用率。
====================================
思考:
1.里所码(纠删码)的优点,即可以提高集群磁盘的利用率,理论上可以达到多少
2.里所码(纠删码)的缺点
3.相比于里所码(删码),副本冗余机制的优点是什么
纠删码技术的优点和缺点
优点:能够提高集群磁盘的利用率,利用率=n/(n+m)~100%
缺点:
①和副本冗余机制相比,恢复效率较低,因为有解码计算过程②和副本冗余机制相比,有很大的计算(cpu)开销
③网络带宽占用较大
所以,可以看出副本冗余机制是一种简单,高效的数掘容错机制,例如HDFS,Kafka
纠删码技术的适用场景:适用于历史冷数据(这些数据几乎不变动,而且不需要用多个文件块副本来存储),从而提高磁盘的利用率。所以Hadoop3.0版本中使用了这种技术
G1GC舍弃了传统的GC收集器的特点,不再将整个HEAP分为新生代和老生代,而是在HEAP创建一个一个的区域块(大小可以设置,最小是1MB,最大是32MB)进行处理。每一个区域块内部不进行新旧分区。而是整体被标记为Eden/Survivor/0ld。
优点:
1.内存利用率非常高,利用了整个Heap的内存空间。而之前传统的GC收集器可能出现的情况:新生代内存紧张,老生代内存空余,这就是一种资源浪费的提现
所以对于一个服务器来说,内存越大,G1GC优势越明显
2.对于GC,有两种,分别是Minor GC和Full GC。如果使用G1,触发FullGC的条件是:在整个Heap中找不到全空区域块时才会发生Full GC。
所以,使用G1,发生Full GC的频次更低
3.G1 GC引入了RememberSet的概念,避免在整个堆中扫描引用链,使得每个区域块的GC更快更加独立。RememberSet记录了当前区域块中对象的引用关系。
GC的配置
常见的4种Gc
1.SerialGC
参数-XX:+UseSerialGC
就是Young区和old区都使用serial垃圾回收算法
2.ParallelGC
参数-XX:+UseParallelGC
Young区:使用Parallel scavenge回收算法
Old区:可以使用单线程的或者Parallel垃圾回收算法,由-XX:+UseParallelOldGC来控制
3.CMS
参数-xX:+UseConcMarkSweepGC
Young区:可以使用普通的或者parallel垃圾回收算法,由参数-XX:+UseParNewGC来控制
Old区∶只能使用Concurrent Mark Sweep
4.G1
参数:-XX:+UseG1GC
没有young/old区
—些配置解释
-XX:+UseG1GC使用G1(Garbage First)垃圾收集器
-XX:MaxGCPauseMillis=n设置最大GC停顿时间(GC pause time)指标(target).这是一个软性指标
(soft goal),JVM会尽量去达成这个目标.
-XXInitiatingHeapOccupancyPercent=n启动并发GC周期时的堆内存占用百分比.G1之类的垃圾收集器用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比.值为0则表示"一直执行GC循环".默认值为45.
这个参数用于控制触发Minor GC的内存占用百分比,如果是GC调优∶适当降低此参数,
因为此参数降低后,提高了MinorGC的频次,从而降低了FullGC的频次
-XX:NewRatio=n老生代和新生代(old/new generation)的大小比例(Ratio).默认值为⒉
如果是G1,此参数无效
-xX:SurvivorRatio=n eden/survivor空间大小的比例(Ratio).默认值为8!如果是G1,此参数无效
-XX:MaxTenuringThreshold=n提升老年代的最大临界值(tenuring threshold).默认值为15.
XX:ParallelGCThreads=n设置垃圾收集器在并行阶段使用的线程数,默认值随JVM运行的平台不同而不同.一般设置的数量=服务器的核数
-XX:ConcGCThreads=n
并发垃圾收集器使用的线程数量.默认值随JVM运行的平台不同而不同(调节CMS )
-XX:G1ReservePercent=n设置堆内存保留为假天花板的总量,以降低提升失败的可能性.默认值是10%
比如堆内存总大小是100GB,假天花板是10%,则G1能够使用的内存大小是90GB,还预留出10GB