TPS和QPS到底搞明白了么

TPS和QPS到底搞明白了么

TPS

TPS:Transactions Per Second,意思是每秒事务数,具体事务的定义,都是人为的,可以一个接口、多个接口、一个业务流程等等。一个事务是指事务内第一个请求发送到接收到最后一个请求的响应的过程,以此来计算使用的时间和完成的事务个数。

以单接口定义为事务为例,每个事务包括了如下3个过程:

  a.向服务器发请求

  b.服务器自己的内部处理(包含应用服务器、数据库服务器等)

  c.服务器返回结果给客户端

  如果每秒能够完成N次这三个过程,tps就是N;

如果多个接口定义为一个事务,那么,会重复执行abc,完成一次这几个请求,算做一个tps。

QPS

QPS:Queries Per Second,意思是每秒查询率,是一台服务器每秒能够响应的查询次数(数据库中的每秒执行查询sql的次数),显然,这个不够全面,不能描述增删改,所以,不建议用qps来作为系统性能指标。

区别

如果是对一个查询接口(单场景)压测,且这个接口内部不会再去请求其它接口,那么tps=qps,否则,tps≠qps

如果是容量场景,假设n个接口都是查询接口,且这个接口内部不会再去请求其它接口,qps=n*tps

jmeter聚合报告中,Throughput是用来衡量请求的吞吐量,也就是tps,tps=样本数/运行时间
如果没有定义事务,会把每个请求作为一个事务

QPS是Query Per Second,是数据库中的概念,每秒执行条数(查询),被引申到压测中来了,但是不包括插入、更新、删除操作,所以不建议用qps来描述系统整体的性能;

建议用tps,这个t,你可以随意的定义,可以是一个接口,也可以是一个业务流程等等。

线上排查基本操作

GC 参数

  1. -Xms2048m 初始堆大小
  2. -Xmx2048m 最大堆大小
  3. -Xss:1024k 设置每个线程堆栈大小,设置每个线程的堆栈大小。
    JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。
    更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
  4. -XX:NewSize=n 设置年轻代大小
  5. -XX:NewRatio=2 设置年轻代和年老代的比值。
  6. -XX:SurvivorRatio=8 年轻代中Eden区与两个Survivor区的比值Eden:S1:S2 = 8:1:1 Eden:Survivor = 8 : 2
  7. -XX:MaxPermSize=64m 设置持久代大小,持久代一般固定大小为64m
  8. -XX:MaxMetaspaceSize=512m 元空间大小
  9. -XX:MetaspaceSize=512m 最大元空间大小

GC:

  1. -XX:+PrintGC 打印GC
  2. -XX:+PrintGCDetails 打印GC,格式不一样,详细
  3. -XX:+PrintGCTimeStamps PrintGCTimeStamps可与上面两个混合使用
  4. -XX:+PrintHeapAtGC 打印GC前后的详细堆栈信息
  5. -Xloggc:D:/temp/gclog/idea.gc.log 与上面几个配合使用,把相关日志信息记录到文件以便分析

垃圾收集器:

  1. -XX:+UseSerialGC:设置串行收集器
  2. -XX:+UseParallelGC:设置并行收集器
  3. -XX:+UseParalledlOldGC:设置并行年老代收集器
  4. -XX:+UseConcMarkSweepGC:设置并发收集器
  5. -XX:InitialBootClassLoaderMetaspaceSize=64M
  6. -XX:MaxTenuringThreshold=6 设置垃圾最大年龄,如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
  7. -XX:+UseConcMarkSweepGC
  8. -XX:CMSFullGCsBeforeCompaction=5 使用并发收集器时,开启对年老代的压缩。由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。

  9. -XX:+CMSParallelRemarkEnabled

  10. -XX:+CMSClassUnloadingEnabled

  11. -XX:+DisableExplicitGC

  12. -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
  13. -XX:ReservedCodeCacheSize=240m
  14. -XX:SoftRefLRUPolicyMSPerMB=50
    -ea
    -Dsun.io.useCanonCaches=false
    -Djava.net.preferIPv4Stack=true
    -XX:+HeapDumpOnOutOfMemoryError
    -XX:-OmitStackTraceInFastThrow

fastjson enable_autotype

fastjson enable_autotype

https://github.com/alibaba/fastjson/wiki/enable_autotype

打开AutoType功能

在1.2.25之后的版本,以及所有的.sec01后缀版本中,autotype功能是受限的,和之前的版本不同,如果在升级的过程中遇到问题,可以通过以下方法配置。

一、添加autotype白名单

添加白名单有三种方式,三选一,如下:

1. 在代码中配置

1
ParserConfig.getGlobalInstance().addAccept("com.taobao.pac.client.sdk.dataobject.");

如果有多个包名前缀,分多次addAccept

2. 加上JVM启动参数

1
-Dfastjson.parser.autoTypeAccept=com.taobao.pac.client.sdk.dataobject.,com.cainiao.

如果有多个包名前缀,用逗号隔开

3. 通过fastjson.properties文件配置。

在1.2.25/1.2.26版本支持通过类路径的fastjson.properties文件来配置,配置方式如下:

1
fastjson.parser.autoTypeAccept=com.taobao.pac.client.sdk.dataobject.,com.cainiao. // 如果有多个包名前缀,用逗号隔开

二、打开autotype功能

如果通过配置白名单解决不了问题,可以选择继续打开autotype功能,fastjson在新版本中内置了多重防护,但是还是可能会存在一定风险。两种方法打开autotype,二选一,如下:

1、JVM启动参数

1
-Dfastjson.parser.autoTypeSupport=true

2、代码中设置

1
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

如果有使用非全局ParserConfig则用另外调用setAutoTypeSupport(true);

三、配置autoType黑名单

打开AutoType之后,是基于内置黑名单来实现安全的,但黑名单是穷举不完的,如果发现了新的风险类,可以通过以下配置来增加黑名单:

1. 配置JVM启动参数

1
-Dfastjson.parser.deny=xx.xxx
  • 这里的xx.xxx是包名前缀,如果有多个包名前缀,用逗号隔开

2. 通过fastjson.properties来配置

在1.2.25之后的版本支持通过类路径的fastjson.properties文件来配置,配置方式如下:

1
-Dfastjson.parser.deny=xx.xxx

  • 这里的xx.xxx是包名前缀,如果有多个包名前缀,用逗号隔开

3. 代码中配置

1
ParserConfig.getGlobalInstance().addDeny("xx.xxx");
  • 这里的xx.xxx是包名前缀,如果有多个包名前缀,用逗号隔开

tomcat最大线程数的设置

tomcat最大线程数的设置

Tomcat的server.xml中连接器设置如下

1.Tomcat配置

1
2
3
4
5
<Connector port="8080"     
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" redirectPort="8443" acceptCount="100"
debug="0" connectionTimeout="20000"
disableUploadTimeout="true" />

2.如何加大tomcat连接数

在tomcat配置文件server.xml中的配置中,和连接数相关的参数有:

  • minProcessors:最小空闲连接线程数,用于提高系统处理性能,默认值为10
  • maxProcessors:最大连接线程数,即:并发处理的最大请求数,默认值为75
  • acceptCount:允许的最大连接数,应大于等于maxProcessors,默认值为100
  • enableLookups:是否反查域名,取值为:true或false。为了提高处理能力,应设置为false
  • connectionTimeout:网络连接超时,单位:毫秒。设置为0表示永不超时,这样设置有隐患的。通常可设置为30000毫秒。

其中和最大连接数相关的参数为maxProcessors和acceptCount。如果要加大并发连接数,应同时加大这两个参数。
web server允许的最大连接数还受制于操作系统的内核参数设置,通常Windows是2000个左右,Linux是1000个左右。tomcat5中的配置示例:

1
2
3
4
5
<Connector port="8080"
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" redirectPort="8443" acceptCount="100"
debug="0" connectionTimeout="20000"
disableUploadTimeout="true" />

对于其他端口的侦听配置,以此类推。

3.tomcat中如何禁止列目录下的文件

在{tomcat_home}/conf/web.xml中,把listings参数设置成false即可,如下:

1
2
3
4
<init-param>  
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>

4.如何加大tomcat可以使用的内存

tomcat默认可以使用的内存为128MB,在较大型的应用项目中,这点内存是不够的,需要调大。
Unix下,在文件{tomcat_home}/bin/catalina.sh的前面,增加如下设置:
JAVA_OPTS=’-Xms【初始化内存大小】 -Xmx【可以使用的最大内存】’
需要把这个两个参数值调大。例如:
JAVA_OPTS=’-Xms256m -Xmx512m’
表示初始化内存为256MB,可以使用的最大内存为512MB

Java锁膨胀

Java锁膨胀

偏所锁,轻量级锁都是乐观锁,重量级锁是悲观锁。

一个对象刚开始实例化的时候,没有任何线程来访问它的时候。它是可偏向的,意味着,它现在认为只可能有一个线程来访问它,所以当第一个线程来访问它的时候,它会偏向这个线程,此时,对象持有==偏向锁==。偏向第一个线程,这个线程在修改对象头成为偏向锁的时候使用CAS操作,并将对象头中的ThreadID改成自己的ID,之后再次访问这个对象时,只需要对比ID,不需要再使用CAS在进行操作。

一旦有第二个线程访问这个对象,因为偏向锁不会主动释放,所以第二个线程可以看到对象时偏向状态,这时表明在这个对象上已经存在竞争了,检查原来持有该对象锁的线程是否依然存活,如果挂了,则可以将对象变为==无锁状态==,然后重新偏向新的线程,如果原来的线程依然存活,则马上执行那个线程的操作栈,检查该对象的使用情况,如果仍然需要持有偏向锁,则偏向锁升级为==轻量级锁==,(偏向锁就是这个时候升级为轻量级锁的)。如果不存在使用了,则可以将对象回复成无锁状态,然后重新偏向。轻量级锁认为竞争存在,但是竞争的程度很轻,一般两个线程对于同一个锁的操作都会错开,或者说稍微等待一下(自旋),另一个线程就会释放锁。但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。

锁膨胀

线上排查基本操作

线上排查基本操作

1. CPU 飚高

线上 CPU 飚高问题大家应该都遇到过,那么如何定位问题呢?

思路:首先找到 CPU 飚高的那个 Java 进程,因为你的服务器会有多个 JVM 进程。然后找到那个进程中的 “问题线程”,最后根据线程堆栈信息找到问题代码。最后对代码进行排查。

如何操作呢?

通过 top 命令找到 CPU 消耗最高的进程,并记住进程 ID。
再次通过 top -Hp [进程 ID] 找到 CPU 消耗最高的线程 ID,并记住线程 ID.

1
top -Hp [进程 ID]  -- 找到 CPU 消耗最高的线程 ID,并记住线程 ID.

通过 JDK 提供的 jstack 工具 dump 线程堆栈信息到指定文件中。具体命令:

1
2
3
jstack -l [进程 ID] >jstack.log。

由于刚刚的线程 ID 是十进制的,而堆栈信息中的线程 ID 是16进制的,因此我们需要将10进制的转换成16进制的,并用这个线程 ID 在堆栈中查找。使用 printf "%x\n" [十进制数字] ,可以将10进制转换成16进制。

通过刚刚转换的16进制数字从堆栈信息里找到对应的线程堆栈。就可以从该堆栈中看出端倪。
从经验来看,一般是某个业务死循环没有出口,这种情况可以根据业务进行修复。
还有 C2 编译器执行编译时也会抢占 CPU,什么是 C2编译器呢?
当 Java 某一段代码执行次数超过10000次(默认)后,
就会将该段代码从解释执行改为编译执行,也就是编译成机器码以提高速度。
而这个 C2编译器就是做这个的。如何解决呢?项目上线后,可以先通过压测工具进行预热,
这样,等用户真正访问的时候,C2编译器就不会干扰应用程序了。
如果是 GC 线程导致的,那么极有可能是 Full GC ,那么就要进行 GC 的优化。

2. 内存问题排查

说完了 CPU 的问题排查,再说说内存的排查,通常,内存的问题就是 GC 的问题,因为 Java 的内存由 GC 管理。
有2种情况,一种是内存溢出了,一种是内存没有溢出,但 GC 不健康。

内存溢出OOM

内存溢出的情况可以通过加上 -XX:+HeapDumpOnOutOfMemoryError 参数,该参数作用是:在程序内存溢出时输出 dump 文件。

有了 dump 文件,就可以通过 dump 分析工具进行分析了,比如常用的MAT,Jprofile,jvisualvm 等工具都可以分析,这些工具都能够看出到底是哪里溢出,哪里创建了大量的对象等等信息。

GC 的健康问题(第二种情况就比较复杂了)

通常一个健康的 GC 是什么状态呢?

YGC 5秒一次左右,每次不超过50毫秒,FGC 最好没有,CMS GC 一天一次左右。

而 GC 的优化有2个维度:

一是频率,二是时长。

YGC 年轻代GC

首先看频率,如果 YGC 超过5秒一次,甚至更长,说明系统内存过大,应该缩小容量
如果频率很高,说明 Eden 区过小,可以将 Eden 区增大,但整个新生代的容量应该在堆的 30% - 40%之间,
eden,from 和 to 的比例应该在 8:1:1左右,这个比例可根据对象晋升的大小进行调整。

如果YGC时间过长呢?
YGC 有2个过程,一个是扫描,一个是复制,通常扫描速度很快,复制速度相比而言要慢一些,如果每次都有大量对象要复制,
就会将 STW 时间延长,还有一个情况就是 StringTable ,这个数据结构中存储着 String.intern 方法返回的常连池的引用,
YGC 每次都会扫描这个数据结构(HashTable),如果这个数据结构很大,且没有经过 FGC,那么也会拉长 STW 时长,
还有一种情况就是操作系统的虚拟内存,当 GC 时正巧操作系统正在交换内存,也会拉长 STW 时长。

FGC

实际上,FGC 我们只能优化频率,无法优化时长,因为这个时长无法控制。如何优化频率呢?

首先,FGC 的原因有几个:
1.Old 区内存不够
2.元数据区内存不够
3.System.gc()
4.执行jmap 或者 jcmd,
5.是CMS Promotion failed 或者 concurrent mode failure,
6.JVM 基于悲观策略认为这次 YGC 后 Old 区无法容纳晋升的对象,因此取消 YGC,提前 FGC。

通常优化的点是 Old 区内存不够导致 FGC。
如果 FGC 后还有大量对象,说明 Old 区过小,应该扩大 Old 区。
如果 FGC 后效果很好,说明 Old 区存在了大量短命的对象,优化的点应该是让这些对象在新生代就被 YGC 掉,
通常的做法是增大新生代,如果有大而短命的对象,通过参数设置对象的大小,不要让这些对象进入 Old 区,还需要检查晋升年龄是否过小。
如果 YGC 后,有大量对象因为无法进入 Survivor 区从而提前晋升,这时应该增大 Survivor 区,但不宜太大。

上面说的都是优化的思路,我们也需要一些工具知道 GC 的状况。

JDK 提供了很多的工具,比如 jmap ,jcmd 等,oracle 官方推荐使用 jcmd 代替 jmap,因为 jcmd 确实能代替 jmap 很多功能。
jmap 可以打印对象的分布信息,可以 dump 文件,注意,jmap 和 jcmd dump 文件的时候会触发 FGC ,使用的时候注意场景。

还有一个比较常用的工具是 jstat,该工具可以查看GC 的详细信息,比如eden ,from,to,old 等区域的内存使用情况。
还有一个工具是 jinfo,该工具可以查看当前 jvm 使用了哪些参数,并且也可以在不停机的情况下修改参数。

包括我们上面说的一些分析 dump 文件的可视化工具,MAT,Jprofile,jvisualvm 等,这些工具可以分析 jmap dump 下来的文件,看看哪个对象使用的内存较多,通常是能够查出问题的。

还有很重要的一点就是,线上环境一定要带上 GC 日志!!!

JVM课程七

分代收集器

一起七个 3 + 3 + 1

在收集器使用的时候都是:新老搭配的。

新生代收集器:

  • Serial
  • ParNew
  • Parallel Scavenge

老年代收集器:

  • CMS(Concurrent Mark Sweep)
  • Serial Old
  • Parallel Old

新老通吃:
G1: Garbadge First

新生代收集器

Serial GC(串行)

HotSpot运行在client模式下的默认新生代收集器。只用一个CPU/一个收集器线程去完成GC。
“Stop The World” -XX:+UseSerialGC

  1. 启动回收
  2. 暂停其他所有工作线程。新生代回收采用复制算法。
  3. 暂停其他所有工作线程。老年代回收采用标记整理算法。单线程收集,但是简单高效。
    在内存占用不大的应用中,效率很高。

ParNew(并行)

其本质是Serial的多线程版本,目的是缩短垃圾收集的时间,也就是占用工作线程时间的问题。单CPU甚至不如Serial,但是在多核CPU配合多线程效果明显。多核CPU + 超线程技术。
若VM启用老年代使用CMS(concurrent mark sweep)时候,
参数: -XX:+UseConcMarkSweepGC
新生代次收集器默认为ParNew。
多核CPU时候一般配置为CPU核数。-XX:ParallelGCThreads= 参数控制GC线程数。

  1. 启动回收
  2. 暂停其他所有工作线程。新生代回收采用复制算法。 (GC线程多个同时工作,并行执行)
  3. 暂停其他所有工作线程。老年代回收采用标记整理算法

Parallel Scavenge

并行多线程,复制算法。关注点与ParNew不同,关注的是系统吞吐量。

系统吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾回收时间)

停顿时间越短就越适用于用户交互的程序。良好的响应速度提升用户体验。
而高吞吐则适用于后台计算而不需要太多交互的任务,可以最高效率的利用CPU时间,
尽快的完成程序的运算任务。

老年代收集器

Serial Old

  1. 启动回收
  2. 暂停其他所有工作线程。新生代回收采用复制算法。
  3. 暂停其他所有工作线程。老年代回收采用标记整理算法。单线程收集,但是简单高效。
    在内存占用不大的应用中,效率很高。

可以与任意的新生代收集器配置使用

Parallel Old

只能配合Parallel Scavenge收集器一起使用。

CMS (并发)

Concurrent Mark Sweep,可以和用户线程同时工作。是一款并发收集器。
虽然有理论上表现更好的G1收集器,主流的仍然是CMS收集器。
能用于server模式下的JVM优化。能结合新生代的Serial 和 ParNew一起使用。

G1

Garbage-First。可以运用在新生代和老年代的直接分区收集。面向服务端的垃圾收集器。 –XX:+UseG1GC
将Java堆划分为多个相等的独立区域Region,保留新生代和老年代的概念。但是不再是物理隔离。

优化方式:
1.选择JVM版本
2.对于堆区大小的分配
3.垃圾回收的方式

JVM课程八

JVM性能监测工具

jps
查询运行于jvm的线程jps -l

jstat
查看HotSpot VM运行的信息
jstat -gc pid 毫秒数 次数

From Created
From Used
To Created 
To Used
Eden Created
Eden Used
Old Created
Old Used
Permanent Created
Permanent Used
Young GC 次收集器收集次数
Young GC Time 次收集器时间
Full GC  全收集次数
Full GC Time 全收集时间
GC Time 收集总时间
S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT

jvisualvm
可视化工具
jvisualvm

JVM课程六

垃圾收集器

Scavenge(次收集) 和 Full GC(全收集)区别

新生代GC (Scavenge GC): 发生在新生代的GC,因为新生代的Java对象大多是朝生夕死,所以Scavenge GC非常频繁。
回收速度快,当Eden内存空间不足时,会触发Scavenge GC。
一般情况下,当新对象生成时,并且在Eden中申请空间失败,就会触发Scavenge GC,对Eden区进行GC,清除非存活对象,
将尚存活的对象移动至Survivor区,然后整理两个Survivor区,这种方式的GC是对年轻代的Eden区进行,不会影响老年代。

老年代GC (Full GC/Major GC): Full GC是指发生在老年代的GC。出现了Full GC一般至少会伴随一次Minor GC。
老年代的对象大多是Minor GC过程中从新生代进入老年代。比如分配担保失败。Full GC的速度一般会比Minor GC慢十倍以上。
当老年代内存不足时或者显式调用System.gc()时候,会触发Full GC。

次收集:
当年轻代堆空间紧张会被触发,相对于全收集,收集间隔较短。

全收集:
当老年代或者持久代空间满了时候会触发全收集操作。可以使用System.gc()显式调用。
全收集一般会根据堆大小,需要的时间较长。不过全收集时间超过3s-5s,那就太长了。

新生代收集器:
Serial
ParNew
Parallel scavenge

老年代收集器:
CMS(Concurrent Mark Sweep)
Serial Old
Parallel Old

新老通吃:
G1: Garbadge First