01. 聊 啥
世人都说路不齐、别人骑马我骑驴、回头看看推车汉、比上不足下有余!
你骑马来我骑驴,看看眼前我不如;回头一看推车汉,比上不足比下余。
回头总结一下以往填过的坑,感觉自己还真挺牛掰。
今天不闲聊,主题不跑偏,就想送你们一箩筐经验,预防你们再入坑。
吃个核桃,坐稳,扶好,我们开始。
02. 聊 开
经验一:CPU 长期利用率为 100%
问题现象:
多用户并发情况下,CPU 利用率长期为100%,DUMP 线程信息,发现 CPU 利用率高的线程都与 HashMap 操作相关。
原因分析:
-
在并发情况下选择非线程安全的容器是没有保障的,HashMap是非线程安全的;
-
HashMap 在多线程情况下,进行扩容很容易导致死循环,最终导致 CPU 利用率 100%。
解决方法:
-
在并发场景下,避免使用 hashMap;
-
在并发场景下,若一定要使用 HashMap,使用同步锁或者使用 ConcurrentHashMap 替代 HashMap。
经验二:数据库访问相当的慢
问题现象:
数据库请求次数多,响应慢。
原因分析:
在索引具备的前提下,数据库分配连接数少,且缺少缓存。
解决方法:
-
对于需要连接到数据库的应用,要考虑查询的结果是否可以复用;
-
对于查询结果时效性不高而且需要多次调用的请求,做缓存往往可以节省数据库的资源,也使应用本身效率大大提高。
经验三:系统连接数巨多
问题现象:
系统连接数巨多
原因分析:
Tomcat 容器默认的通讯方式为 TCP/IP + BIO,这种模式往往不适合大并发的情况,BIO 模式生成 Socket 会消耗过多的本地资源,Socket 连接的建立一般比较慢,能支持的连接数有限。
一般都采取 accept 获取 Socket 以后采用一个 thread 来处理,one connection one thread,无论连接是否有真正数据请求,都需要独占一个 thread。
如果我们 server 端需要支持大量连接,但这些连接同时发送请求的峰值不会很多,一般建议替换成 NIO 模式。
解决方法:
替换成 TCP/IP+NIO,NIO 模型是内置的,调用很方便,只需要修改配置文件conf/server.xml文件,将配置文件中 protocol 修改成 org.apache.coyote.http11.Http11NioProtocol,重启即可生效。
下面的配置是已经改过了的,默认 protocol 是HTTP/1.1
<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol" connectionTimeout="20000" redirectPort="8443" maxThreads="500" minSpareThreads="20" acceptCount="100" disableUploadTimeout="true" enableLookups="false" URIEncoding="UTF-8" />
经验四:使用HttpClient的版本问题
问题现象:
不是每个请求都能正确返回,可能遇到 The remote server returned an error.(417) Unkown
原因分析:
系统使用的是 4.0.3 版本,该版本发布时 Expect:100-continue 默认为开启状态,即客户端每次与服务器通讯过程,每次先向服务器发送一个请求,看服务器是否能处理这个请求,一般应用在上传一些特殊的文件或者比较大数据量的交互上,有时候会造 time_wait 很多。
该问题在 HttpClient 4.1 版本中解决,默认为关闭该功能。
解决方法:
升级到 4.1 以上版本,默认为关闭该功能。
经验五:使用 log4j 日志性能的问题
问题现象:
日志 IO 占用大量的系统资源,CPU 利用率高,TPS 偏低,热点方法在
org.apache.log4j.Category.callAppenders
ch.qos.logback.core.OutputStreamAppender.subAppend
等与日志相关的方法。
原因分析:
日志量过大,IO 频繁。日志对系统性能的影响程度主要体现在以下几方面
-
日志输出的选项设置,有些选项极慢, 例如 C/class、 F/file 、L/line 、l 、M/method速度极慢,尽量避免使用;
-
日志输出双份,某些应用通常将业务日志同时输出到控制台和另外一个文件或者日志信息在同一份文件中输出两次;
-
日志输出的目的地,输出到控制台的速度比输出到文件系统的速度要慢;
-
日志输出格式不一样对性能也会有影响,如简单输出布局(SimpleLayout)比格式化输出布局(PatternLayout)输出速度要快。可以根据需要尽量采用简单输出布局格式输出日志信息;
-
日志级别越低输出的日志内容就越多,对系统系能影响很大;
-
日志输出方式的不同,对系统系能也是有一定影响的,采用异步输出方式比同步输出方式性能要高;
-
每次接收到日志输出事件就打印一条日志内容比当日志内容达到一定大小时打印性能要低。
解决方法:
-
精简日志输出内容,合理设置日志输出格式,避免使用那些极慢的选项;
-
设置日志缓存,以及缓存大小;
-
将业务日志仅输出到文件系统,且仅输出一份(以log4j为例,对于日志输出多份的情况举例如下):
#同一份日志输出到两个文件的情况 log4j.rootLogger=DEBUG, stdout, system #表示将等级为 DEBUG 的日志信息输出到 stdout 和 system 这两个目的地,此配置将使得日志输出两份,将此行改成 log4j.rootLogger=DEBUG, system; #表示将日志只输出到 system 这一个目的地;
经验六:logback 比 log4j 拥有更好的性能
使用 logback 和 log4j 日志组件进行日志打印。
当日志级别为 debug 时,系统平均 tps 比为 logback:log4j=1.31:1
当日志级别为 info 时,系统平均 tps 比为 logback:log4j=1.03:1
日志输出量越大时,使用 logback 日志组件进行日志打印比 log4j 方式在处理速度方面的优势越为明显,实验过程中 logback 比之 log4j 系统处理能力提升幅度为 3%~30%。
经验七:已经分享一箩筐了,再分享箩筐也装不下,唯恐聪明的你们也消化不动了。(有缘再续)
03. 聊 毕
过往从事研发中的点滴经验,送给你们,预防你们再入坑,另外你们也可以在面试的时候吹吹牛啦(哈哈)。