本文涉及的点:
jvm
nginx状态
微服务
spring cloud
keepalived
PV/UV
主从复制,读写分离(同步)
存储(NFS,rsync)
消峰
JVM
Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域
java与语言编译程序将java代码解释成字节码,jvm将字节码解释成及启指令
JRE:java的平台,所有java都要运行在JRE里
JDK:程序开发者用来编译、调试java程序的开发者工具包。JDK的工具也是java程序,也需要JDK才能运行
JVM:是JRE的一部分
JVM的执行程序的过程
- 加载.class文件
- 管理分配内存
- 执行垃圾回收
一、前言
Java的GC(垃圾回收)机制是区别C++的一个重要特征。C++需要开发者在代码中实现垃圾回收逻辑,但在Java中,JVM帮开发者代劳了。我们只有理解了GC机制,才能编写出高性能的应用。要想理解GC,就要先理解JVM内存管理机制。这样才能知道回收哪些对象,什么时候回收以及怎么回收
二、JVM
根据JVM规范,JVM把内存划分成了如下几个区域:
1.方法区(Method Area)
2.堆区(Heap)
3.虚拟机栈(VM Stack)
4.本地方法栈(Native Method Stack)
5.程序计数器(Program Counter Register)
其中,方法区和堆区所有线程共享。
静态变量 + 常量 + 类信息(构造方法/接口定义) + 运行时常量池存放在 方法区 中
实例变量 存放在 堆内存 中
2.1 方法区(Method Area)
方法区存放了要加载的类的信息(如类名、修饰符等)、静态变量、构造函数、final定义的常量、类中的字段和方法等信息。方法区是全局共享的,在一定条件下也会被GC。当方法区超过它允许的大小时,就会抛出OutOfMemory:PermGen Space异常。
在Hotspot虚拟机中,这块区域对应持久代(Permanent Generation),一般来说,方法区上执行GC的情况很少,因此方法区被称为持久代的原因之一,但这并不代表方法区上完全没有GC,其上的GC主要针对常量池的回收和已加载类的卸载。在方法区上进行GC,条件相当苛刻而且困难。
运行时常量池(Runtime Constant Pool)是方法区的一部分,用于存储编译器生成的常量和引用。一般来说,常量的分配在编译时就能确定,但也不全是,也可以存储在运行时期产生的常量。比如String类的intern()方法,作用是String类维护了一个常量池,如果调用的字符"hello"已经在常量池中,则直接返回常量池中的地址,否则新建一个常量加入池中,并返回地址。
2.2 堆区(Heap)
堆区是GC最频繁的,也是理解GC机制最重要的区域。堆区由所有线程共享,在虚拟机启动时创建。堆区主要用于存放对象实例及数组,所有new出来的对象都存储在该区域。
2.3 虚拟机栈(VM Stack)
虚拟机栈占用的是操作系统内存,每个线程对应一个虚拟机栈,它是线程私有的,生命周期和线程一样,每个方法被执行时产生一个栈帧(Statck Frame),栈帧用于存储局部变量表、动态链接、操作数和方法出口等信息,当方法被调用时,栈帧入栈,当方法调用结束时,栈帧出栈。
局部变量表中存储着方法相关的局部变量,包括各种基本数据类型及对象的引用地址等,因此他有个特点:内存空间可以在编译期间就确定,运行时不再改变。
虚拟机栈定义了两种异常类型:StackOverFlowError(栈溢出)和OutOfMemoryError(内存溢出)。如果线程调用的栈深度大于虚拟机允许的最大深度,则抛出StackOverFlowError;不过大多数虚拟机都允许动态扩展虚拟机栈的大小,所以线程可以一直申请栈,直到内存不足时,抛出OutOfMemoryError。
2.4 本地方法栈(Native Method Stack)
本地方法栈用于支持native方法的执行,存储了每个native方法的执行状态。本地方法栈和虚拟机栈他们的运行机制一致,唯一的区别是,虚拟机栈执行Java方法,本地方法栈执行native方法。在很多虚拟机中(如Sun的JDK默认的HotSpot虚拟机),会将虚拟机栈和本地方法栈一起使用。
2.5 程序计数器(Program Counter Register)
程序计数器是一个很小的内存区域,不在RAM上,而是直接划分在CPU上,程序猿无法操作它,它的作用是:JVM在解释字节码(.class)文件时,存储当前线程执行的字节码行号,只是一种概念模型,各种JVM所采用的方式不一样。字节码解释器工作时,就是通过改变程序计数器的值来取下一条要执行的指令,分支、循环、跳转等基础功能都是依赖此技术区完成的。
每个程序计数器只能记录一个线程的行号,因此它是线程私有的。
如果程序当前正在执行的是一个java方法,则程序计数器记录的是正在执行的虚拟机字节码指令地址,如果执行的是native方法,则计数器的值为空,此内存区是唯一不会抛出OutOfMemoryError的区域。
总结:这里,只有方法区和堆区的内存需要回收
三、GC机制
回收的对象:不存在任何引用的对象
3.1如何判断对象是垃圾 ?
经典的引用计数算法,每个对象添加到引用计数器,每被引用一次,计数器+1,失去引用,计数器-1,当计数器在一段时间内为0时,即认为该对象可以被回收了。但是这个算法有个明显的缺陷:当两个对象相互引用,但是二者都已经没有作用时,理应把它们都回收,但是由于它们相互引用,不符合垃圾回收的条件,所以就导致无法处理掉这一块内存区域。因此,Sun的JVM并没有采用这种算法,而是采用一个叫——根搜索算法,如图:
基本思想是:从一个叫GC Roots的根节点出发,向下搜索,如果一个对象不能达到GC Roots的时候,说明该对象不再被引用,可以被回收。如上图中的Object5、Object6、Object7,虽然它们三个依然相互引用,但是它们其实已经没有作用了,这样就解决了引用计数算法的缺陷。
3.2内存分代
新生代 位于堆区
新生代适合生命周期较短,快速创建和销毁的对象
大致分为Eden区和Survivor区,Survivor区又分为大小相同的两部分:FromSpace和ToSpace。
新建的对象都是从新生代分配内存,Eden区不足的时候,会把存活的对象转移到Survivor区。
当新生代进行垃圾回收时会出发Minor GC(也称作Youn GC)
老年代 位于堆区
适合生命周期较长的对象
老年代用于存放新生代多次回收依然存活的对象,如缓存对象。
当老年代满了的时候就需要对老年代进行回收,老年代的垃圾回收称作Major GC(也称作Full GC)
持久代 位于方法区
Sun Hotp ot虚拟机中就是指方法区
结合我们经常使用的一些 jvm 调优参数后,一些参数能影响的各区域内存大小值,示意图如下:
注:jdk8 开始,用 MetaSpace 区取代了 Perm 区(永久代),所以相应的 jvm 参数变成 -XX:MetaspaceSize 及 -XX:MaxMetaspaceSize。
3.3常用GC算法
1)复制算法
复制算法采用的方式为从根集合进行扫描,将存活的对象移动到一块空闲的区域
2)标记-清除算法
该算法采用的方式是从跟集合开始扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,并进行清除
3)标记-压缩算法
该算法与标记-清除算法类似,都是先对存活的对象进行标记,但是在清除后会把活的对象向左端空闲空间移动,然后再更新其引用对象的指针
4)分代收集算法
在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。
老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用标记-整理 或者 标记-清除。
具体过程:新生代(Young)分为Eden区,From区与To区
当系统创建一个对象的时候,总是在Eden区操作,当这个区满了,那么就会触发一次YoungGC,也就是年轻代的垃圾回收。一般来说这时候不是所有的对象都没用了,所以就会把还能用的对象复制到From区。
这样整个Eden区就被清理干净了,可以继续创建新的对象,当Eden区再次被用完,就再触发一次YoungGC,然后呢,注意,这个时候跟刚才稍稍有点区别。这次触发YoungGC后,会将Eden区与From区还在被使用的对象复制到To区,
再下一次YoungGC的时候,则是将Eden区与To区中的还在被使用的对象复制到From区。
经过若干次YoungGC后,有些对象在From与To之间来回游荡,这时候From区与To区亮出了底线(阈值),这些家伙要是到现在还没挂掉,对不起,一起滚到(复制)老年代吧。
老年代经过这么几次折腾,也就扛不住了(空间被用完),好,那就来次集体大扫除(Full GC),也就是全量回收。如果Full GC使用太频繁的话,无疑会对系统性能产生很大的影响。所以要合理设置年轻代与老年代的大小,尽量减少Full GC的操作。
方法区对象回收
永久代指的是虚拟机内存中的方法区,永久代垃圾回收比较少,效率也比较低,但也必须进行垃圾回收,否则永久代内存
不够用时仍然会抛出OutOfMemoryError异常。永久代也使用“标记-清除”或者“标记-整理”算法进行垃圾回收。
四、垃圾回收器
4.1.Serial收集器
串行收集器是最古老,最稳定以及效率高的收集器
可能会产生较长的停顿,只使用一个线程去回收
-XX:+UseSerialGC
新生代、老年代使用串行回收
新生代复制算法
老年代标记-压缩
4.2. 并行收集器
4.2.1 ParNew
-XX:+UseParNewGC(new代表新生代,所以适用于新生代)
新生代并行
老年代串行
Serial收集器新生代的并行版本
在新生代回收时使用复制算法
多线程,需要多核支持
-XX:ParallelGCThreads 限制线程数量
4.2.2 Parallel收集器
类似ParNew
新生代复制算法
老年代标记-压缩
更加关注吞吐量
-XX:+UseParallelGC
使用Parallel收集器+ 老年代串行
-XX:+UseParallelOldGC
使用Parallel收集器+ 老年代并行
4.2.3 其他GC参数
-XX:MaxGCPauseMills
最大停顿时间,单位毫秒
GC尽力保证回收时间不超过设定值
-XX:GCTimeRatio
0-100的取值范围
垃圾收集时间占总时间的比
默认99,即最大允许1%时间做GC
这两个参数是矛盾的。因为停顿时间和吞吐量不可能同时调优
4.3. CMS收集器
Concurrent Mark Sweep 并发标记清除(应用程序线程和GC线程交替执行)
使用标记-清除算法
并发阶段会降低吞吐量(停顿时间减少,吞吐量降低)
老年代收集器(新生代使用ParNew)
-XX:+UseConcMarkSweepGC
CMS运行过程比较复杂,着重实现了标记的过程,可分为
1. 初始标记(会产生全局停顿)
根可以直接关联到的对象
速度快
2. 并发标记(和用户线程一起)
主要标记过程,标记全部对象
3. 重新标记 (会产生全局停顿)
由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正
4. 并发清除(和用户线程一起)
基于标记结果,直接清理对象
这里就能很明显的看出,为什么CMS要使用标记清除而不是标记压缩,如果使用标记压缩,需要多对象的内存位置进行改变,这样程序就很难继续执行。但是标记清除会产生大量内存碎片,不利于内存分配。
4.4. 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的集合。
G1的新生代收集跟ParNew类似,当新生代占用达到一定比例的时候,开始出发收集。
和CMS类似,G1收集器收集老年代对象会有短暂停顿。
五、总结
1、JVM哪些分区需要垃圾回收?
JVM5个分区中:
1.方法区(Method Area)
2.堆区(Heap)
3.虚拟机栈(VM Stack)
4.本地方法栈(Native Method Stack)
5.程序计数器(Program Counter Register)
方法区和堆区是线程共享的,所以这两个区需要回收
2.这两个分区中,哪些对象需要回收(如何判断垃圾)?
两种算法:
1.引用计数算法
2.根搜索算法
3.GC常见算法?
1.复制算法
2.标记--清除算法
3.标记--压缩算法
4.分代回收算法
4.垃圾回收器
1.串行收集
2.并行收集
3.CMS收集器
4.G1收集器
nginx状态
Active connections: 3
server accepts handled requests
8 8 67
Reading: 0 Writing: 1 Waiting: 2
Active connections:表示nginx正在处理的活跃连接数
server:nginx启动到现在共处理了8个请求
accepts:nginx启动到现在共成功创建8次握手
handled requests:共处理了67个请求
Reading:读取到客户端的header信息数
Writing:nginx返回给客户端的header信息数
Waiting:nginx已经处理完正在等候下一次指令的驻留连接(在开启keep-alive的情况下这个值为Active-(READING+Writing))
keepalived
C语言编写。keepalived是以VRRP协议为实现基础的,VRRP是虚拟路由冗余协议
虚拟路由冗余协议,可以认为是实现路由器高可用的协议,即将多个提供相同功能的路由器组成一个路由器组,这个组里面有一个master和多个backup,master上面有一个对外提供服务的vip(该路由器所在局域网内其他机器的默认路由为该vip),master会发组播,当backup收不到vrrp包时就认为master宕掉了,这时就需要根据VRRP的优先级来选举一个backup当master。这样的话就可以保证路由器的高可用了。
keepalived有3个模块:
- core模块为keepalived的核心,负责主进程的启动、维护以及全局配置文件的加载和解析
- check负责健康检查,包括常见的各种检查方式
- vrrp模块是来实现VRRP协议的
keepalived有三种监听模式layer3、4、5,分别工作在TCP/IP、TCP及应用层下
layer3工作时,会定期向服务器群集中发送一个ICMP的数据包(即ping),如果某台服务器没有响应数据包请求时,keepalived则会视为此台服务器不能提供服务,则在服务器群集里把它剔除,运用场景:来判断某台服务器非法关机
layer4方式工作:keepalived会向服务器群集里发送TCP的数据包,主要监听TCP端口来判断是否在正常状态,如果发现该端口无法访问及没有启动(如80),则剔除掉这台服务器
layer5工作方式:layer5比layer3、4要复杂,占用带宽也要多一些,根据用户设定检查程序是否正常,如果与用户设定不符。keepalived则剔除这台机器
脑裂:由于HA的原因,服务器的数量通常大于1台。本来,这些节点的活动应该为协调的,统一的,但是,由于master与node的心跳线的断开,使得双方不能正常的通信,这是会出现两个问题:
1.双方相互争抢资源,导致双方服务都无法正常使用
2.两边服务都起来了,同时提供服务,同时读写存储,导致数据不一致甚至损坏。
脑裂的原因:
- HA服务器之间心跳线故障,导致无法正常通信;
- HA服务器上开启了防火墙,阻挡了心跳线的信息传输;
- HA服务器上心跳网卡配置不正确,导致心跳信息发送失败;
- 其他服务器配置不当的原因。比如心跳方式不同,心跳广播冲突,软件BUG等;
PV/UV
- UV:指的是通过互联网访问、浏览页面的人。访问网站的一台电脑客户端为一个访客,00:00 - 24:00 相同的客户只能计算一次
- IP:独立IP指访问过某站点的IP总数。因此,可能同一个IP,但是UV数不为1
- PV:页面的点击数,用户对同一页面多次访问,PV会累积
- VV: 指的是统计用户1天内访问网站的次数。同一个人一天可以有多次访问行为,访问次数累计
mysql主从复制的同步延迟
mysql的主从复制都是单线程的操作,主库对所有DDL和 DML产生binlog,binlog是顺序写,所以效率很高,slave的Slave_IO_Running线程到主库取日志,效率很比较高,下一步, 问题来了,slave的Slave_SQL_Running线程将主库的DDL和DML操作在slave实施。DML和DDL的IO操作是随即的,不是顺 序的,成本高很多,还可能可slave上的其他查询产生lock争用,由于Slave_SQL_Running也是单线程的,所以一个DDL卡主了,需要 执行10分钟,那么所有之后的DDL会等待这个DDL执行完才会继续执行,这就导致了延时。有朋友会问:“主库上那个相同的DDL也需要执行10分,为什 么slave会延时?”,答案是master可以并发,Slave_SQL_Running线程却不可以。
主从同步延迟的产生:
当主库的TPS并发较高时,产生的DDL数量超过slave一个sql线程所能承受的范围,那么延时就产生了,当然还有就是可能与slave的大型query语句产生了锁等待。
延迟同步的解决发方案:
最简单的减少slave同步延时的方案就是在架构上做优化,尽量让主库的DDL快速执行。还有就是主库是写,对数据安全性较高,比如 sync_binlog=1,innodb_flush_log_at_trx_commit = 1 之类的设置,而slave则不需要这么高的数据安全,完全可以讲sync_binlog设置为0或者关闭binlog,innodb_flushlog也 可以设置为0来提高sql的执行效率。另外就是使用比主库更好的硬件设备作为slave。
NFS(rsync)
NFS是Network File System的缩写及网络文件系统。
主要功能是通过局域网络让不同的主机系统之间可以共享文件或目录
NFS的优点:
1.NFS文件系统简单易用、方便部署、数据可靠、服务稳定、满足中小企业需求。
2.NFS文件系统内存放的数据都在文件系统之上,所有数据都是能看得见。
NFS的缺点:
1.存在单点故障, 如果构建高可用维护麻烦。(web-》nfs()-》backup)
2.NFS数据明文, 并不对数据做任何校验。
3.客户端挂载无需账户密码, 安全性一般(内网使用)
rsync:
rsync命令是一个远程数据同步工具,。rsync使用所谓的“rsync算法”来使本地和远程两个主机之间的文件达到同步,这个算法只传送两个文件的不同部分,而不是每次都整份传送,因此速度相当快
rsync同步:
rsync main.c machineB:/home/userB
若目的没有该文件,那么新创建的文件权限为源文件的权限。若果有目标文件,那目标文件的权限不会发生改变
消峰
在短期内有大量用户进行访问时,回增加数据库的压力,当到达阈值时会出现问题。
服务器的处理资源是恒定的,用或者不用它的处理能力都是一样的,所以出现峰值的话,很容易导致忙到处理不过来,闲的时候却又没有什么要处理。但是由于要保证服务质量,我们的很多处理资源只能按照忙的时候来预估,而这会导致资源的一个浪费。
因此,需要对用户的访问进行过滤,以缓解这种问题。
方案一:使用消息队列,当然消息队列的也会出现阈值
方案二:分层过滤。
假如请求分别经过 CDN、前台读系统(如商品详情系统)、后台系统(如交易系统)和数据库这几层,那么:
- 大部分数据和流量在用户浏览器或者 CDN 上获取,这一层可以拦截大部分数据的读取;
- 经过第二层(即前台系统)时数据(包括强一致性的数据)尽量得走 Cache,过滤一些无效的请求;
- 再到第三层后台系统,主要做数据的二次检验,对系统做好保护和限流,这样数据量和请求就进一步减少;
-
最后在数据层完成数据的强一致性校验。
分层过滤的核心思想是:在不同的层次尽可能地过滤掉无效请求,让“漏斗”最末端的才是有效请求。而要达到这种效果,我们就必须对数据做分层的校验。
分层校验的基本原则是:
- 将动态请求的读数据缓存在 Web 端,过滤掉无效的数据读;
2.对读数据不做强一致性校验,减少因为一致性校验产生瓶颈的问题;
3.对写数据进行基于时间的合理分片,过滤掉过期的失效请求;
4.对写请求做限流保护,将超出系统承载能力的请求过滤掉;
5.对写数据进行强一致性校验,只保留最后有效的数据。
微服务
spring cloud
Spring Cloud,它我们提供了一整套的微服务解决方案,大大的降低了微服务开发的门槛,同时也减少了开发成本。
组件:
- 服务注册与发现:是 Spring Cloud 中最核心的组件之一,整个系统中所有的服务都可以注册到注册中心,然后由注册中心进行统一调度,方便后续的水平扩展以及故障转移等。
- 配置中心:随着服务的不断增多,同时每个服务也会有多个环境(开发环境、测试环境、生产环境等),每个环境的配置文件又会有所不同,但是其中又有许多配置是可以共用的,如果每个服务自己去管理这些配置,会给维护带来很大的麻烦,这时候,我们就需要引入配置中心去统一管理这些配置。Spring Cloud 还提供了 Spring Cloud Bus 组件,来进行服务间的通讯。用来通知服务配置文件的更新
- 服务消费者:既然是微服务架构,那服务间的调用肯定是无法避免的,Spring Cloud 提供了 Ribbon 组件,它可以用来进行服务间的调用,同时,它还支持客户端的负载均衡。但是,直接使用 Ribbon 不是很方便,所以,Spring Cloud 基于 Netflix Feign ,并整合了 Ribbon,这就有了 Spring Cloud OpenFeign,它实现了声明式的服务调用客户端,
- 服务器容错:在微服务架构中,通常会有多个服务之间相互调用的情况,一旦某些基础服务出现故障,则可能造成整个系统的不可用,称之为服务雪崩,为了避免这种情况的发生,我们需要在服务出现故障时实现故障隔离和服务降级。针对这种情况,Spring Cloud 使用了 Hystrix 来实现服务的容错。
- 网关:网关为系统提供了路由、鉴权、监控、负载均衡等功能。Spring Cloud 为此提供了两个解决方案:Zuul 以及 Spring Cloud Gateway。
Eureka:Eureka 由 Netflix 开发,是一种基于REST(Representational State Transfer)的服务,用于定位与发现服务,以实现中间层服务的负载均衡和故障转移。
Eureka的工作原理:
- 由服务提供方将服务注册到 Eureka Server
- 服务消费者通过 Eureka Server 获取服务提供方的真实地址
- 服务消费者通过真实的地址调用服务
在服务提供者注册到 Eureka Server 的过程中,它会将自身的元数据(包括主机、端口等以及其他自定义的信息)都注册到 Eureka Server 中,服务消费者通过 Eureka Server 即可获取这些信息,所以对于 Eureka 来说,它不会限定服务消费者与服务提供者之间的具体该如何通讯,这些都是由服务消费者与服务提供者决定,你可以自行选择任意你喜欢的方式,如 HTTP、RPC、Thrift 等
Eureka的自我保护机制:
默认情况下,Eureka Server 在一定时间内如果没有接收到某个实例的心跳,就会移除该实例。但是可能会有这种情况:当网络分区故障时,服务与 Eureka Server 无法正常通信,但是服务本身是正常的,此时就不该移除该服务,所以 Eureka 就引入了自我保护机制。当 Eureka 进入此机制时,它将不再移除任何服务,哪怕服务不可用。
微服务详解
·