我们两个人在吃午饭。你在吃饭的整个过程中,吃了米饭、吃了蔬菜、吃了牛肉。吃米饭、吃蔬菜、吃牛肉这三件事其实就是并发执行的。
对于你来说,整个过程中看似是同时完成的的。但其实你是在吃不同的东西之间来回切换的。
还是我们两个人吃午饭。在吃饭过程中,你吃了米饭、蔬菜、牛肉。我也吃了米饭、蔬菜和牛肉。
并发和并行的区别:
并发的多个任务之间是互相抢占资源的。
并行的多个任务之间是不互相抢占资源的、
只有在多CPU的情况中,才会发生并行。否则,看似同时发生的事情,其实都是并发执行的。
高并发和多线程”总是被一起提起,给人感觉两者好像相等,实则高并发≠多线程(他们没有必然的直接联系)
一个系统的吞吐量通常由qps(tps)、并发数来决定,每个系统对这两个值都有一个相对极限值,只要某一项达到最大值,系统的吞吐量就上不去了。
所谓的系统吞吐量其实就是:系统每秒请求数
QueriesPerSecond,每秒查询数,即是每秒能够响应的查询次数,注意这里的查询是指用户发出请求到服务器做出响应成功的次数,简单理解可以认为查询=请求request。
qps=每秒钟request数量
针对单接口而言,TPS可以认为是等价于QPS的,比如访问一个页面/index.html,是一个TPS,而访问/index.html页面可能请求了3次服务器比如css、js、index接口,产生了3个QPS。tps=每秒钟事务数量
对于RT,客户端和服务端是大不相同的,因为请求从客户端到服务端,需要经过广域网,所以客户端RT往往远大于服务端RT,同时客户端的RT往往决定着用户的真实体验,服务端RT往往是评估我们系统好坏的一个关键因素。
在开发过程中,我们一定面临过很多的线程数量的配置问题,这种问题往往让人摸不到头脑,往往都是拍脑袋给出一个线程池的数量,但这可能恰恰是不靠谱的,过小的话会导致请求RT极具增加,过大也一样RT也会升高。所以对于最佳线程数的评估往往比较麻烦。 并发数:
简而言之,系统能同时处理的请求/事务数量。
计算方式:
QPS=并发数/RT或者并发数=QPS*RT
举个栗子:
QPS=3600/60*601
RT=10*60600秒
并发数=1*600600
这样就意味着如果想达到最好的蹲坑体验,公司需要600个坑位来满足员工需求,否则的话上厕所就要排队等待了。
随着请求数量的增加,带来大量的上下文的切换、gc和锁变化。qps更高,产生对象越多,gc越频繁,cputime和利用率都受到影响,尤其在串行的时候,锁自旋、自适应、偏向等等也成为影响par的因素。 总结,为了提升达到最好的性能,我们需要不断的进行性能测试,调整小城池大小,找到最合适的参数来达到提高性能的目的。三、压测报告瓶颈分析等到服务上线后,在业务压力的冲击下,会发现程序运行非常的慢,或者是宕机,莫名其妙的出现各种问题,只会进行一些无脑的扩容,扩容真的能解决问题吗??
可能能解决问题,但是同时也会带来一些其他的问题,因此在项目上线之前,还必须要有一步性能压力测试的步骤,以便于发现服务的一些问题,提前对服务的问题进行修复,优化等等。
项目打包:可以使用idea直接打包上传,也可以在gitlab服务器直接通过maven进行打包,或者使用jenkins来进行打包,现在我们先使用idea的maven进行打包。
注意:打包服务的时候必须注意服务的配套的ip地址,由于此时服务和mysql,redis都在同一个服务器上,因此连接访问地址设置为localhost即可。
启动命令:java-jarjshop-web-1.0-SNAPSHOT.jar
注意:服务器部署的时候,由于服务器环境的不同,往往都需要额外的修改服务的配置文件,重新编译打包,必须服务器ip地址,本地开发环境的ip和线上的ip是不一样的,部署的时候,每次都需要修改这些配置,非常麻烦。因此服务部署时候应该具有一个外挂配置文件的能力。
#启动命令,注意:配置文件的名称必须是application.yaml,或者application.propertiesjava-jarxxx.jar--spring.config.addition-location=/usr/local/src/application.yaml外挂配置文件使用本地连接地址:
压测目标:
线程梯度:500、1000,1500,2000,2500,3000个线程,即模拟这些数目的用户并发;
循环次数:50次
1)设置压测请求
聚合报告:添加聚合报告
查看结果树:添加查看结果树
服务器性能监控:CPU、内存、IO
注意:对于jmeter的cpu监控来说,只能监控单核cpu,没有太大的参考价值,真实测试可以使用top指令观察cpu使用情况。
我们设置线程数n=5,循环次数a=1000,请求www.google.com,得到聚合报告如图:
依然是n=5,得到S=(T-T/n)=8,也就是说,从第一个线程启动到第8秒的时候,最后一个线程开始启动,若需要在最后一个线程启动的时候第一个线程仍未关闭,则需要满足a·t>S,已知S=8,t=0.2,得到a>40。
我们用一张图来直观的看看每个线程的运行情况
说了这么多,我们的目的到底是什么?无非是如何设置线程数,Ramp-UpPeriod以及循环次数。线程数我就不多说了,看各个项目的测试需求,而刚刚我说了这么多,实质上只是介绍了一些概念和如何合理的设置循环次数,至于Ramp-UpPeriod如何合理这是,请看下面大神的分析。
聚合报告:TPS3039,但是在这里看不见TPS的峰值是多少,需要进行改进
可能存在性能问题:
①平均值在初始阶段跳升,而后逐渐平稳起来
一是系统在初始阶段存在性能缺陷,需要进一步优化,如数据库查询缓慢
二是系统有缓存机制,而性能测试数据在测试期间没有变化,如此一来同样的数据在初始阶段的响应时长肯定较慢;这属于性能测试数据准备的问题,不是性能缺陷,需调整后在测试
②平均值持续增大,图片变得越来越陡峭
一是可能存在内存泄漏,此时可以通过监控系统日志、监控应用服务器状态等常见方法,来定位问题。
③平均值在性能测试期间,突然发生跳变,然后又恢复正常
一是可能存在系统性能缺陷
二是可能由于测试环境不稳定所造成的(检查应用服务器状态【CPU占用、内存占用】或者检查测试环境网络是否存在拥塞)
#server端线程数到245就已经上不去了,导致服务端的性能提升不上去#pstree查询线程数量jps-l#查询所有线程pstree-ppid#统计线程数量pstree-ppid|wc-l#开启压测,再次统计线程数量,查看线程数量是否增大,是否还有增大的空间2、内嵌配置Springboot开发的服务使用内嵌的tomcat服务器来启动服务,那么tomcat配置使用的是默认配置,我们需要对tomcat配置进行一些适当的优化,让tomcat性能得以提升。
Tomcat的maxConnections、maxThreads、acceptCount三大配置,分别表示最大连接数,最大线程数、最大的等待数,可以通过application.yml配置文件来改变这个三个值,一个标准的示例如下:
server:tomcat:uri-encoding:UTF-8#最大工作线程数,默认200,4核8g内存,线程数经验值800#操作系统做线程之间的切换调度是有系统开销的,所以不是越多越好。max-threads:1000#等待队列长度,默认100accept-count:1000max-connections:20000#最小工作空闲线程数,默认10,适当增大一些,以便应对突然增长的访问量min-spare-threads:1001)、accept-count:最大等待数
官方文档的说明为:当所有的请求处理线程都在使用时,所能接收的连接请求的队列的最大长度。当队列已满时,任何的连接请求都将被拒绝。accept-count的默认值为100。详细的来说:当调用HTTP请求数达到tomcat的最大线程数时,还有新的HTTP请求到来,这时tomcat会将该请求放在等待队列中,这个acceptCount就是指能够接受的最大等待数,默认100。如果等待队列也被放满了,这个时候再来新的请求就会被tomcat拒绝(connectionrefused)。
2)、maxThreads:最大线程数
每一次HTTP请求到达Web服务,tomcat都会创建一个线程来处理该请求,那么最大线程数决定了Web服务容器可以同时处理多少个请求。maxThreads默认200,肯定建议增加。但是,增加线程是有成本的,更多的线程,不仅仅会带来更多的线程上下文切换成本,而且意味着带来更多的内存消耗。JVM中默认情况下在创建新线程时会分配大小为1M的线程栈,所以,更多的线程异味着需要更多的内存。线程数的经验值为:1核2g内存为200,线程数经验值200;4核8g内存,线程数经验值800。
3)、maxConnections:最大连接数
官方文档的说明为:
最近在用jmeter做压力测试时,发现一个问题,当线程持续上升到某个值时,报错:java.net.BindException:Addressalreadyinuse:connect,如下图所示:
解决办法(在jmeter所在服务器操作):
1.cmd中输入regedit命令打开注册表;
2.在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters右键Parameters;
3.添加一个新的DWORD,名字为MaxUserPort;
4.然后双击MaxUserPort,输入数值数据为65534,基数选择十进制;
5.完成以上操作,务必重启机器,问题解决,亲测有效;
未优化测试性能对比表:
优化后性能对比表:
1、LISTENING状态FTP服务启动后首先处于侦听(LISTENING)状态。
2、ESTABLISHED状态ESTABLISHED的意思是建立连接。表示两台机器正在通信。
*3、CLOSE_WAIT*
*对方主动关闭连接或者网络异常导致连接中断*,这时我方的状态会变成CLOSE_WAIT此时我方要调用close()来使得连接正确关闭
*4、TIME_WAIT*
***我方主动调用close()断开连接,收到对方确认后状态变为TIME_WAIT*。**TCP协议规定TIME_WAIT状态会一直持续2MSL(即两倍的分段最大生存期),以此来确保旧的连接状态不会对新连接产生影响。处于TIME_WAIT状态的连接占用的资源不会被内核释放,所以作为服务器,在可能的情况下,尽量不要主动断开连接,以减少TIME_WAIT状态造成的资源浪费。
目前有一种避免TIME_WAIT资源浪费的方法,就是关闭socket的LINGER选项。但这种做法是TCP协议不推荐使用的,在某些情况下这个操作可能会带来错误。
socket的状态
状态变迁图:(tcp连接状态变迁图)
JavaNIO的服务端只需启动一个专门的线程来处理所有的IO事件,这种通信模型是怎么实现的呢?呵呵,我们一起来探究它的奥秘吧。javaNIO采用了双向通道(channel)进行数据传输,而不是单向的流(stream),在通道上可以注册我们感兴趣的事件。一共有以下四种事件:
服务端和客户端各自维护一个管理通道的对象,我们称之为selector,该对象能检测一个或多个通道(channel)上的事件。我们以服务端为例,如果服务端的selector上注册了读事件,某时刻客户端给服务端发送了一些数据,阻塞I/O这时会调用read()方法阻塞地读取数据,而NIO的服务端会在selector中添加一个读事件。服务端的处理线程会轮询地访问selector,如果访问selector时发现有感兴趣的事件到达,则处理这些事件,如果没有感兴趣的事件到达,则处理线程会一直阻塞直到感兴趣的事件到达为止。下面是我理解的javaNIO的通信模型示意图:
从一个客户端向服务端发送数据,然后服务端接收数据的过程。客户端发送数据时,必须先将数据存入Buffer中,然后将Buffer中的内容写入通道。服务端这边接收数据必须通过Channel将数据读入到Buffer中,然后再从Buffer中取出数据来处理。
查询springboot内置tomcat使用的io类型,源码简单分析:
由于业务应用bug(本身或引入第三方库)、环境原因、硬件问题等原因,Java线上服务出现故障/问题几乎不可避免。例如,常见的现象包括部分请求超时、用户明显感受到系统发生卡顿等等。
尽快线上问题从系统表象来看非常明显,但排查深究其发生的原因还是比较困难的,因此对开发测试或者是运维的同学产生了许多的困扰。
排查定位线上问题是具有一定技巧或者说是经验规律的,排查者如果对业务系统了解得越深入,那么相对来说定位也会容易一些。
不管怎么说,掌握Java服务线上问题排查思路并能够熟练排查问题常用工具/命令/平台是每一个Java程序猿进阶必须掌握的实战技能。
所有Java服务的线上问题从系统表象来看归结起来总共有四方面:CPU、内存、磁盘、网络。例如CPU使用率峰值突然飚高、内存溢出(泄露)、磁盘满了、网络流量异常、FullGC等等问题。
基于这些现象我们可以将线上问题分成两大类:系统异常、业务服务异常。
常见的系统异常现象包括:CPU占用率过高、CPU上下文切换频率次数较高、磁盘满了、磁盘I/O过于频繁、网络流量异常(连接数过多)、系统可用内存长期处于较低值(导致oomkiller)等等。
这些问题可以通过top(cpu)、free(内存)、df(磁盘)、dstat(网络流量)、pstack、vmstat、strace(底层系统调用)等工具获取系统异常现象数据。
此外,如果对系统以及应用进行排查后,均未发现异常现象的更笨原因,那么也有可能是外部基础设施如IAAS平台本身引发的问题。
常见的业务服务异常现象包括:PV量过高、服务调用耗时异常、线程死锁、多线程并发问题、频繁进行FullGC、异常安全攻击扫描等。
我们一般会采用排除法,从外部排查到内部排查的方式来定位线上服务问题。
问题定位流程,在linux系统中排查问题的方法,流程。
Linux常用的性能分析工具使用包括:top(cpu)、free(内存)、df(磁盘)、dstat(网络流量)、pstack、vmstat、strace(底层系统调用)等。
CPU是系统重要的监控指标,能够分析系统的整体运行状况。监控指标一般包括运行队列、CPU使用率和上下文切换等。
top命令是Linux下常用的CPU性能分析工具,能够实时显示系统中各个进程的资源占用状况,常用于服务端性能分析。
#free查询当前内存使用情况free-m#部分参数说明total内存总数:7821Mused已经使用的内存数:713Mfree空闲的内存数:3107Mshared当前已经废弃不用,总是0buffersBuffer缓存内存数:3999M2.3、磁盘#使用df查看磁盘是否被占满,占用情况df-h#查看具体目录下的磁盘使用情况du-m/path2.4、网络dstat命令集成了vmstat、iostat、netstatlsof等等工具能完成的任务。
1)vmstat指令详解:
#lsof打开的文件可以是:1.普通文件2.目录3.网络文件系统的文件4.字符或设备文件5.(函数)共享库6.管道,命名管道7.符号链接8.网络文件(例如:NFSfile、网络socket,unix域名socket)9.还有其它类型的文件,等等3)dstat-ccpu情况-d磁盘读写-n网络状况-l显示系统负载-m显示形同内存状况-p显示系统进程信息-r显示系统IO情况
4)pstackstrace
jps用于输出当前用户启动的所有进程ID,当线上发现故障或者问题时,能够利用jps快速定位对应的Java进程ID
#jps命令查询jps-l-m-m-l-l参数用于输出主启动类的完整路径ps-ef|greptomcat#我们也能快速获取tomcat服务的进程id。当然,我们也可以使用Linux提供的查询进程状态命令,例如:
10进制:jstackpid|greptid-C30–color
某Java进程CPU占用率高,我们想要定位到其中CPU占用率最高的线程。
2)什么是JMX?
JMX(JavaManagementExtensions,即Java管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。
3)监控远程的tomcat想要监控远程的tomcat,就需要在远程的tomcat进行对JMX配置,方法如下:
保存退出,重启tomcat。
4)使用VisualJVM连接远程tomcat添加远程主机:在一个主机下可能会有很多的jvm需要监控,所以接下来要在该主机上添加需要监控的jvm:
#在tomcat的bin目录下,修改catalina.sh,添加如下的参数JAVA_OPTS="‐Dcom.sun.management.jmxremote‐Dcom.sun.management.jmxremote.port=9999‐Dcom.sun.management.jmxremote.authenticate=false‐Dcom.sun.management.jmxremote.ssl=false"#这几个参数的意思是:#-Djava.rmi.server.hostname#‐Dcom.sun.management.jmxremote:允许使用JMX远程管理#‐Dcom.sun.management.jmxremote.port=9999:JMX远程连接端口#‐Dcom.sun.management.jmxremote.authenticate=false:不进行身份认证,任何用户都可以连接#‐Dcom.sun.management.jmxremote.ssl=false:不使用ssl连接成功。使用方法和前面就一样了,就可以和监控本地jvm进程一样,监控远程的tomcat进程。