目录
一、性能诊断导图
1.0 检查RT
模拟用户发起负载后,采用自顶向下的方式首先分析RT(响应时间)
- 如果RT小,TPS大,说明性能良好
- 如果RT小,TPS小,检查负载机资源占用情况,确定是否需要加大负载
- 如果RT大,TPS小,检查负载机资源占用情况,确保不是负载机的性能原因导致的RT变大
1.1 检查TPS
- 如果TPS大,RT小,说明性能良好
- TPS小,RT大,检查负载机资源占用情况,确保不是负载机的性能原因导致的RT变大
2.0 检查负载机资源消耗
- 检查CPU使用率,CPU负载(load Average)确认是否是用户CPU占用高还是系统CPU占用高?确认测试脚本没有性能问题,不会造成统计的不准确
- 检查内存使用情况,确认并发内存泄漏风险,不会造成结果统计的不准确
- 检查网络占用带宽,建议不超过35%的使用率
可以通过一台并发100用户,另一台运行一个用户,比较两者的响应时间是否在同一个数量级,从而评估负载机对性能的影响
2.1 判断负载机是否有性能问题
排除负载机的性能问题,确保测试结果的可参考性。
3.0 检查web服务器资源消耗
- 检查CPU使用率,确认用户CPU与系统CPU占用情况
- 系统CPU占用多,检查系统调用情况,找出是哪个进程或线程,确认调用是否正常?比如滥写日志,导致系统CPU利用率高
- 用户CPU高,找出进程或线程,定位程序,确认是否调用正常?
- 一般来说web服务程序不涉及到运算的,CPU利用率高要确认是否其他资源等待导致的CPU利用率高
- 比如IO等待会带来CPU利用率高,CPU由于等待IO,而频繁的进行上下文切换
- 比如事务过程较长,需要锁定的资源较多,而请求也较多时,CPU需要不停的中断、上下文切换去处理获取到资源的请求
- 一般来说web服务程序不涉及到运算的,CPU利用率高要确认是否其他资源等待导致的CPU利用率高
- 检查内存使用情况,找出占内存多的进程或线程
- 检查磁盘使用情况,找出IO量大的进程或线程
- 检查占用的带宽,建议不要超过35%
为什么是不超过35%,而不是70%?如果带宽占到70%对性能无大影响,那当然也是可以放宽的。最好先做一下实验,具体以实验结果为准。
- 分析web页面响应的时间组成,确认是什么请求,是否影响了性能
3.1 确认是否web服务器瓶颈
从上一步,3.0监控指标判断是否是web服务器硬件性能瓶颈。
3.2 检查中间配置
- 检查中间件线程池活动连接数,确认是否是此配置问题
- 检查JVM内存,堆(Heap)配置,确认gc的影响
3.3 是否是中间件限制
- 监控线程池活动连接数,确认连接数够用
- 监控线程状态,如果长期是Blocked状态,可能是响应慢,有可能会导致死锁,Dump线程栈找到疑问程序
- 监控JVM,关注GC,评估Heap空间是否够用
4.0 检查APP服务器资源消耗
分析方法同3.0步骤
4.1 确认是否APP服务器瓶颈
从3.0监控指标判断是否APP服务器硬件性能瓶颈。
4.2 检查中间件配置
- 检查中间件线程池
- 检查数据库连接池
- 检查Heap配置
4.3 是否是中间件限制
- 监控线程池活动连接数,确认连接数够用
- 监控线程状态,如果长期是Blocked状态,可能是响应慢,有可能会导致死锁,Dump线程栈找到疑问程序
- 监控与数据库的连接数,确认数据库连接池是否够用
- 监控JVM,关注GC,评估Heap空间是否够用。可以借助JVisualvm、jprifile来分析,也可以使用jdk自带的监控工具
5.0 数据库服务器资源消耗分析
- CPU消耗,CPU负载
- 内存消耗
- IO繁忙程度
- 数据库监控
- 慢查询
- 按CPU使用率排序进程或线程,定位操作(可以找DBA帮忙监控分析)
二、单系统压测与多系统压测
三、全链路压测
如何知道web服务被调用几次?
- 每一笔业务,业务系统都会有业务日志,从日志中分析出来
- 把每个系统之间的调用关系,整理出来。每一笔业务调用哪一个接口,调用多少次进行总结
- 只要业务出了问题,就能拿着总结的流程图,找出这个系统性能以前的风险点在哪,可以马上去那个点定位问题(整理的好处)
四、线程问题定位
docker exec -it jforumweb /bin/bash # 进入容器
通过以下命令进行问题的排查:
- top
- jps # 查Tomcat的信息
- jstat -gcutil 1 2000 # 查GC,每2秒刷新一次
- vmstat 2 # 查内存 【用户程序使用了很多cpu】
找程序,看在做些什么样的操作?
- 看堆信息,堆是放数据的
- 看线程信息,线程是把我们程序的执行过程存储在里面
- tid: java thread id # 线程ID
- nid: native thread id # 操作系统层面能看到的ID
- prio : # 线程优先级
- [0x063bf000] # 线程栈起始地址
线程信息分析:
从下往上看,关注blocked 状态的线程(执行任务,正在执行中,慢。或者执行过程中,等待其他资源到位) 。blocked状态是我们关注的,有问题的进程状态都是blocked状态。但blocked状态不一定就是有问题。
线程的几个状态:
- blocked ---> 线程阻塞【忙,等待资源】
查询一个大的数据结果集,数据库记录很多,查询的过程中,把数据查到,然后给应用程序可能花费1秒钟时间,那么在这1秒的过程中,线程处于blocked状态,在等结果。结果集返回应用程序,还在做数据运算处理,处理的时间有点长,这个时候的线程也是blocked状态【比如DB,下游的程序完成请求返回你】
- waiting ---> 等待资源【不忙、等待别人或自己唤醒】
等待资源的过程中就是waiting状态(依赖别人而等待的状态)。不等待资源,要休息一下,也是waiting状态(线程自己主动sleep)。waiting on condition:等待资源,或等待某个条件的发生,往往waiting on monitor entry 这后伴随着blocked 状态
线程为什么会有waiting 等待资源,主要是对资源的占用(同一时刻,只有一个线程可以获取到该资源),所以才会有等待。
资源被占用,实际上是对象(obj),线程同步怎么处理?
entry set,还没有获取到该资源。没有获取到的线程在entry set 区域里排队获取,waiting for monitor 【比较严重的等待】
比如obj对应的monitor 被其他线程拥有,所以本线程在队列中等待,如果其他线程不释放就产生死锁。
the owner,已经获取到该资源。已经获取到线程就为the owner区域里。
wait set,曾经获取到过该资源,现在把资源释放了。资源释放后,线程就在wait set区域里(wait set 的优先级大于entry set)。in Object.wait()【相较于entry set,严重程度稍微好些】
- running
- runnable 【正在忙,自己瞎忙,占用CPU)】
- init
- closed
线程的这几个状态,分析时最需要的是 blocked 和 waiting 状态。
4.1 进入容器分析线程
jps l # jdk自带的命令,可看出Java的进程为1,同时里面有很多的线程
top # 只是到进程级别
top -Hp 1 # 到线程级别,显示进程1里的所有线程监控
PID在操作系统层面是可见的:
4.2 jstack 线程状态分析
jstack 1 > 1.thread.info # 1为Java的进程号
dump线程信息:
多开几个远程窗口,压测过程中看到cpu高的进程号,记录下来。在另一个窗口执行jstack 1 >1.thread.info 的命令,然后cat 这个文件,对针对执行高的CPU 进程号信息进行分析
文件中的nid就是我们进程的PID,但显示的是十六进制,需要我们进行转换后,在文件中进行搜索对应的报错信息(自习下向上分析,线程在做哪些操作)
可通过反编译工具,定位到具体的源码:
查找压测过程中top5的报错线程信息:
- 将脚本放到被测容器中映射的程序目录中
- 运行被测试的容器
- 执行压测
- topcpu.sh -p 1 -c 5 # 显示前5个占CPU最多的线程信息
#!/bin/bash
# @Function
# TOP CPU Thread INFO.
#
#
#
PROG=`basename $0`
usage() {
cat <<EOF
Usage: ${PROG} [OPTION]...
Java top cpu print the stack of these threads.
Example: ${PROG} -c 10
Options:
-p, --pid java process(use jps find)
-c, --count set the thread count to show, default is 5
-h, --help display this help and exit
EOF
exit $1
}
ARGS=`getopt -n "$PROG" -a -o c:p:h -l count:,pid:,help -- "$@"`
[ $? -ne 0 ] && usage 1
eval set -- "${ARGS}"
while true; do
case "$1" in
-c|--count)
count="$2"
shift 2
;;
-p|--pid)
pid="$2"
shift 2
;;
-h|--help)
usage
;;
--)
shift
break
;;
esac
done
count=${count:-5}
redEcho() {
[ -c /dev/stdout ] && {
# if stdout is console, turn on color output.
echo -ne "\033[1;31m"
echo -n "$@"
echo -e "\033[0m"
} || echo "$@"
}
## Check the existence of jstack command!
if ! which jstack &> /dev/null; then
[ -n "$JAVA_HOME" ] && [ -f "$JAVA_HOME/bin/jstack" ] && [ -x "$JAVA_HOME/bin/jstack" ] && {
export PATH="$JAVA_HOME/bin:$PATH"
} || {
redEcho "Error: jstack not found on PATH and JAVA_HOME!"
exit 1
}
fi
uuid=`date +%s`_${RANDOM}_$$
cleanupWhenExit() {
rm /tmp/${uuid}_* &> /dev/null
}
trap "cleanupWhenExit" EXIT
printStackOfThread(){
while read threadLine ; do
pid=`echo ${threadLine} | awk '{print $1}'`
threadId=`echo ${threadLine} | awk '{print $2}'`
threadId0x=`printf %x ${threadId}`
user=`echo ${threadLine} | awk '{print $3}'`
pcpu=`echo ${threadLine} | awk '{print $5}'`
jstackFile=/tmp/${uuid}_${pid}
[ ! -f "${jstackFile}" ] && {
jstack ${pid} > ${jstackFile} || {
redEcho "Fail to jstack java process ${pid}!"
rm ${jstackFile}
continue
}
}
redEcho "The stack of busy(${pcpu}%) thread(${threadId}/0x${threadId0x}) of java process(${pid}) of user(${user}):"
sed "/nid=0x${threadId0x}/,/^$/p" -n ${jstackFile}
done
}
[ -z "${pid}" ] && {
ps -Leo pid,lwp,user,comm,pcpu --no-headers | awk '$4=="java"{print $0}' |
sort -k5 -r -n | head --lines "${count}" | printStackOfThread
} || {
ps -Leo pid,lwp,user,comm,pcpu --no-headers | awk -v "pid=${pid}" '$1==pid,$4=="java"{print $0}' |
sort -k5 -r -n | head --lines "${count}" | printStackOfThread
}
4.3 线程的各种状态转换
4.4 如何找出CPU利用率高的程序
- 先TOP 命令,查看Java进程,占CPU多得进程
- top -Hp <pid> ,通过top -Hp 查询到的线程PID,与jstack 的nid 进制转换 dump 线程
- jps -l ,列出正在运行的Java程序的进程ID
- top -Hp <pid> 与 jsp -l 同时存在相同的PID值
- dump 线程
- jstack -m <pid> >jvm_deadlocks.txt
- jstack -l <pid> >jvm_listlocks.txt
- dump 线程内存
- jmap –dump:live,format=b,file=heap.bin <pid>
- 对dump下来的线程信息,进行分析和问题定位
常见线程状态需要关注的blocked和waiting状态的线程。分析定位问题,线程被哪个方法死锁了。
4.5 方法总结
- 运行被测试的容器
- 进入容器,找到top cpu 高的Java线程ID (top -Hp <PID>)
- jstack 1 > thread.info
- java 线程ID 转化为16进制
- 在thread.info 中查找 转化为16进制的nid 【线程栈信息中的nid 就是top -Hp <PID>的线程ID】
- 反统计程序,找到存在问题的程序
五、JVM问题定位
5.1 JVM内存结构
5.1.1 空间与区域维度
空间维度:
- heap(堆)空间:年轻代和年老代
- 非堆空间(方法区):持久代 【一直会拥有的】
职能维度:
- 年轻代(young generation),又区分为:from 区、to 区(被引用)、eden区(存放对象)
- 年老代(old generation),又区分为:Tenured区(幸存者区)
- 持久代(Permanent Generation)
持久代是放在方法区中。内容包括:字段、常量 、文本、构造函数等放在该区,如 A.class 放在方法区
线程,不存放在方法区,也不存放在堆空间。而是开辟一片新区域,native区 (占操作系统的内存)
top -Hp <pid> 看到的线程,关联到的内存(res)大于分配的内存,原因就是该线程除了分配的jvm内存,还需要占用部分的操作系统的内存。
5.1.2 线程栈
jvm 线程运行的内存需要占操作系统的内存,线程栈的大小默认是1MB,也可以手动设置为2M 或是 512KB,根据实际去设置。线程栈的大小不建议设置很大,浪费栈的空间。
5.1.3 内存占用的计算
内存 = heap + method + T *1M + SYS (T:线程的数量 sys: 操作系统占用的内存),比如: 100个线程,运行100个线程需要1300M的内存。内存 = 500M(堆空间) + 200M(方法区) + 100M(线程栈) + 1300M(运行线程的内存) = 2100M内存
JVM为什么要做内存回收。如果内存不释放回收,会出现内存泄露,对象太多,无heap空间,导致OOM。
数据和逻辑分离的好处是内存回收,线程存放逻辑,不需要去回收,堆存放数据,可回收。
5.2 JVM内存
5.2.1 内存回收简单示例图(堆空间)
向左、向右移动(from 和 to 可以来回交替位置),默认15次,该对象就会去到年老区(old区)幸存者区。from 、 to 空间也可能随时被淘汰。
内存回收的特点:
- 先标记,并行,堆回收的时候,还可以做任务
- 在回收,并发,并发的进行回收(毫秒级回收)
- 在整理
内存回收,一般都是在from区和to区进行来回的被淘汰,由eden区生成新的对象,如果old区的对象经常频繁的被淘汰,说明存在异常(如内存泄露等)。
对象被引用是不会被回收的,需要去找到他的引用对象的回收节点。可用jdk自带的 jstack或jconsole工具进行JVM内存监控。
edn空间不足,执行 young gc。因为年轻代里的对象都是一些临时(short-lived )对象,执行Minor GC非常快,所以应用不会受到(“Stop the World”)影响。
old空间不足,perm空间不足,调用方法System.gc() ,ygc时的悲观策略,dump live的内存信息时(jmap –dump:live),都会执行full gc。
由于Major GC会检查所有存活的对象,因此会花费更长的时间。应该尽量减少Major GC。因为Major GC会在垃圾回收期间让你的应用反应迟钝,所以如果你有一个需要快速响应的应用发生多次Major GC,你会看到超时错误。
垃圾回收时间取决于垃圾回收策略。这就是为什么有必要去监控垃圾收集和对垃圾收集进行调优。从而避免要求快速响应的应用出现超时错误。
5.2.2 jvm内存的构成
开发人员编写Java 源代码(.java 文件),经过java编译器将源代码编译成字节码(.class 文件)
最后字节码被装入内存。一旦字节码进入虚拟机中,它就会被解释器解释执行,或者是被即时编译器有选择的转换成机器码执行。
类文件(如A.class) 调用,方法区 、 Java堆(程序的数据)、Java栈(线程,程序执行的过程,占操作系统内存)、程序计数器 (底层算法,当前线程所执行的字节码的行号指示器)、本地方法栈,程序运行。
5.2.3 程序计数器
- 可以看做是当前线程所执行的字节码的行号指示器
- 字节码解释器工作时就是通过改变这个计数器的值来选取吓一跳需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成
- 如果运行java程序,将记录正在执行的虚拟机字节码指令地址
5.2.4 栈信息
栈信息就是我们平时dump下来的线程信息。虚拟机栈描述的是Java方法执行的内存模型,每个方法被执行的时候都会同时创建一个栈帧用于存储局部变量表、操作栈、动态链接、方法出口等信息。每个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。栈信息可以查看程序之间调用的逻辑(方法与方法之间的调用)。
栈信息异常案例:
- 如果线程请求的栈深度大于虚拟机所允许的深度,将会抛出stackOverflowError异常。一般是程序自己调用自己(递归调用),程序死循环了。内存自己把自己撑爆,就会出现这个问题。栈溢出后果,线程挂了。解决方案:xss 加大栈空间 或 优化程序(存在很多嵌套的调用)
- 如果虚拟机可以动态扩展(大部分Java虚拟机都可动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时,会抛出OutOfMemoryError异常。
- 没有能力创建新的线程,会抛出OutOfMemoryError:unable to create new native thread异常
5.2.5 堆信息
Java堆其实就是保存数据的信息。比如用户登录信息,只有用户不退出,数据就一直保存在堆内存。Java堆是Java虚拟机所管理的内存最大的一块。java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。Java堆内存区域的唯一目的就是存放对象实例 ,几乎所有的对象实例都在这里分配内存。
堆信息异常案例:
如果在堆中没有内存完成实例分配,并且堆也无法在扩展时(创建够多的对象,撑爆heap空间),就会抛出 OutOfMemoryError:Java heap space 异常。堆溢出的原因:
- 95%的原因是,集合对象太大导致heap不够(溢出 --- 超出临界状态)
- 缓存对象太多,回收不急(回收导致GC频繁),响应变慢 (存在溢出风险 --- 临界状态)
5.2.6 持久代内存溢出
持久代存放在方法区(非堆空间),异常时会抛出 OutOfMemoryError:PermGen space
原因:类动态加载导致的(通常出现在springboot类似的框架中)
jvm 监控,主要就是监控堆内存。
5.2.7 JVM参数
堆空间(年轻代 + 年老代):
- young: -Xmn 512m
- heap空间:-Xms -Xmx
xms 代表初始空间,xmx 最大空间。通常情况,xms 和 xmx 值设置为一样大, 1024m
非堆空间或方法区:
- -XX:PermSize 128m
- -XX:MaxPermSize
- 栈容量设置:-Xss 一般是1M 或512KB 或128KB 足够
可以在jvm配置中,加上dump 字段和写入路径,dump下来堆内存溢出的日志,通过日志,去排查问题,通过jdk自带的工具对日志进行分析与定位
jdk visualVM (dump线程,问题定位步骤):
- 倒叙实例数(大小) ,过滤包名(显示自己测试的程序)
- 进到实例图中显示
- 找到引用对象(显示最近的垃圾回收根节点)
- 通常是集合对象
- 在线程中显示,找到程序
jstat -gcutil 1 # JVM监控命令
5.3 内存分析工具 MAT
- MAT的内存溢出分析报告中,找最大内存的部分进行分析。往往是大对象(集合对象),ArrayList(user)
- 找到当前引用的对象
- Histogram 倒序heap大小,根据包名进行过滤(程序员写的程序)
- Max(Objects) 和 Max(Retained Heap),找到可疑的对象
- 右键 list Objects,选择with incoming reference,点开看到谁引用的当前对象
- 对象树,倒序heap,找到最大的对象,查看里面的对象,定位到问题
一般情况下,当响应时间越来越长,TPS在逐级下降,又或者稳定性测试时,以运行24小时,需要开始监控内存,特别是JVM的内存。
六、中间件
6.1 中间件的作用
中间件是一种独立的系统软件或服务程序,分布式应用软件借助这种软件在不同的技术之间共享资源、中间件位于客户机/服务器的操作系统之上,管理计算机资源和网络通讯。是连接两个独立应用程序或独立系统的软件。相连接的系统,即使它们具有不同的接口,但通过中间件相互之间仍能交换信息。执行中间件的一个关键途径是信息传递。通过中间件,应用程序可以工作与多平台或OS环境。
网络通讯
通过http/https(TLS,SSL)应用层协议进行访问,我们可以通过socket协议来模拟(POP IMAP FTP TELNET SSH SMTP协议) 【tcp/ip 协议镞】
用户一个get/post请求,通过net 传输到中间件(Tomcat),中间件进行收发包,解析包(转化为电脑能识别的二进制代码),分配处理,JVM,servlet等。我们不用管理这些协议,我们只要写程序发布在这上面即可,网络通讯有中间件统一管理。
网络IO也是性能调优的一个点:
- BIO,进入消息队列,前面一个事务没有处理,都会导致后面很慢【单队列风险很大】
- NIO,异步IO,进入消息队列,向服务器发送请求,不用等结果,服务器处理完在进行通知返回数据。特点:多队列(前面一个事务处理慢,后面一个事务也能同时发送请求,服务器处理完,在分别返回结果)
- AIO,操作系统IO
- Netty 异步IO框架 【慢的请求拿出去,不影响整个系统的性能】
6.2 常见的中间件
- Tomcat
- Jetty
- Jboss
- weblogic
- was
6.3 tomcat 中间件
6.3.1 Tomcat目录结构
- bin:启动、停止服务相关的脚本、参数,以及Tomcat启动时需要的类文件
- conf:配置文件
- lib:运行时依赖的库
- logs:日志文件
- temp:临时文件
- webapps:主要的web应用程序的发布目录
- work:Tomcat工作目录,存放jsp编译后产生的class文件(第一次访问网站通常很慢,因为需要编译)
6.3.2 server.xml:连接数相关
<Connector>连接器:它负责接收客户请求,以及向客户返回响应结果
port:监听来自客户端的请求
protocol:设置连接器的网络请求模型,默认值是 HTTP/1.1使用自动切换机制来选择基于Java NIO的连接器或基于APR / native的连接器
- BIO:同步阻塞式I/O操作,Tomcat8之后已经移除
- NIO:同步非阻塞式I/O操作,默认运行模式,主要想解决的是BIO的大并发问题,利用Java的异步IO处理,可以通过少量的线程处理大量的请求。服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
- AIO(NIO2):异步非阻塞式I/O操作,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。AIO方式使用于连接数目多且连接比较长(重操作)的架构,充分调用OS参与并发操作,编程比较复杂
- APR:是Apache HTTP服务器的支持库,利用系统级异步IO,大文件处理性能比较明显
connectionTimeout:等待超时的时间数(以毫秒为单位),默认60秒
maxThreads:设定在监听端口的线程的最大数目,这个值也决定了服务器可以同时响应客户请求的最大数目。默认值为200
minSpareThreads:最小线程数始终保持运行。这包括活动和空闲线程,默认10
maxSpareThreads:允许最大空闲线程数,默认50
acceptCount:当所有可以使用的处理请求的线程都被用光时,可以放到处理队列中的请求数,超过这个数的请求将不予处理,而返回Connection refused错误。
redirectPort:服务器正在处理http请求时收到了一个SSL传输请求后重定向的端口号
七、中间件配置调优
7.1 线程链接(DB连接池)
tomcat DB连接池和连接数在service.xml文件中进行配置
<Executor
name="tomcatThreadPool" # 该线程池的标记
namePrefix ="catalina-exec-" # 线程名字的前缀
maxThreads="150" # 线程池中最大活跃线程数,默认值200
minSpareThreads="4" /> # 线程池中保持的最小线程数,最小值是25
<Connector
executor="tomcatThreadPool"
port="8080"
protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
acceptCount="1000" />
7.2 Heap
配置jvm内存参数,service.xml文件中配置,此处略,参考上文。
7.3 IO通讯方式
通过jdk自带的工具(jvistual)进行线程分析,tomcat连接器的三种方式BIO、NIO(tomcat8以后默认的方式)、APR三种运行方式,简单介绍如下:
BIO
bio是阻塞式IO操作,使用java io技术,即每一个请求都要创建一个线程来进行处理。缺点:并发量高时,线程数较多,占资源
NIO
使用java nio技术,能够通过少量的线程处理大量的请求。nio是基于java中非阻塞IO操作的API实现,比传统的i/o处理方式有更高的并发运行性能
APR(Apache Portable Runtime/Apache可移植运行时库)
apr是从操作系统级别解决异步IO问题,大幅度提高服务器的并发处理性能,也是Tomcat生产环境运行的首选方式。
tomcat8以后若有需要需要手动修改配置:
将service.xml文件中
<Connector port="8091" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
修改成
<Connector port="8091" protocol="org.apache.coyote.http11.Http11AprProtocol" connectionTimeout="20000" redirectPort="8443" />
总结:
- Apr处理请求最快,Nio次之,Bio最慢
- Apr宽带占用最高,Bio最低;但Apr的内存占用最高,而Nio的内存占用最低
- 综合来看Nio适用于一般需求,Apr适用于高并发需求
ab压力测试,可以直接检查中间件的并发能力,ab命令在apache目录bin目录下面,查看中间件的并发能力:
ab -n 20000 -c 40 http://127.0.0.1:8080/examples/jsp # -n:每次并发量,-c:总共发送的数量
ab测试的官方资料文档: https://httpd.apache.org/docs/2.4/programs/ab.html