Java Web应用高并发性能优化方案汇总
日期: 2019-02-22 分类: 跨站数据测试 316次阅读
文章目录
背景
公司开发的一个门户系统运行几年了,最近因为客户的组织机构调整,要大幅增加用户数,于是开始了一场对系统并发性能进行调优的艰难之旅。
本文记录一个传统Java Web系统性能调优过程的方方面面,希望将来能成为此类工作的指南和索引,从中跳转到各个零碎的知识点。因为涉及点非常多,这里只能做汇总记录,求全不求精,每个知识点的具体细节,需要自行到网上查找,文中也给出了一些不错的参考链接。写作不易,给出的链接地址都是经过筛选的,有些实在找不到满意的就只好自己写,因此这篇文章如果给大家带来了一些帮助,别忘了点个赞哦!
本文未涉及复杂的架构变化(因为不需要啊!),不适用于大型网站。想了解大型网站架构优化的,瞅瞅这个:大型网站架构演化。
系统现状
系统现状是这样的:
- 服务器:1台应用服务器,1台Oracle数据库。都是Windows系统。
- 应用:有两个,分别是提供单点登录功能的CAS服务和门户系统,各自运行在各自的Tomcat中,但都在一台应用服务器上。系统代码老旧,没考虑过优化。
- 在线用户数:100左右
- 与外部系统接口:作为门户系统,与多个第三方系统有后台接口,抓取这些系统的数据,展现在门户系统首页上。这些接口需要在用户登录后定时刷新显示,
在以上背景下,系统经LoadRunner压力测试,并发访问能力惨不忍睹。客户要求达到1000并发(不是1000在线!),于是开始了艰难的优化之旅。
优化过程
概括说来,为支持高并发的核心优化点是提高后端处理性能和前后端之间的通信效率。提高前端(浏览器)性能的方法是没有帮助的。总的调优方向可以概括为:
- 增加服务器处理资源
- 提高服务器运行效率
- 减少前后端交互次数
- 减少总通讯流量
因系统运行多年,为降低风险,采用了尽量不修改代码、不增加新的独立架构组件的原则。最终,我们沿着以下的路线来开始这场优化之旅:
一、 应用系统调优
二、 使用集群
三、 网络和部署方式调优
一、应用系统调优
准备:调优分析工具
当系统响应缓慢时,如何准确找到瓶颈点在哪?这里隆重介绍JDK自带工具VisuslVM,利用它可以非常容易地找出运行最耗时的地方,精确到函数级别哦!具体用法,可以参考下面这篇文档:性能分析神器VisualVM。在我的实践中,最有用的就是使用其中的Sampler抽样器功能,找出最耗时的函数,做出针对性优化。
另一个分析神器是阿里出品的Druid连接池。是的,你没看错,一个连接池竟然自带了非常完整的系统监控功能,可以在线查看和分析数据库访问、HTTP请求的实时和统计数据,实为居家开发必备之品。参考链接:http://www.cnblogs.com/han-1034683568/p/6730869.html
1. 使用缓存
一个Web系统的典型处理流程是接受HTTP请求、查询数据库、根据数据渲染页面、返回页面给用户。这个过程有两个地方可以运用缓存:
- 数据库缓存:缓存查询数据库的结果;
- 页面缓存:缓存渲染的页面;
(1)数据库缓存
大多数情况下系统高并发的最大瓶颈是数据库。数据库因为其内部的事务、锁等机制,天生难以达到很高的并发处理能力。我们需要使用应用层缓存来降低应用程序与数据库的交互次数。
可以通过Spring框架中的@Cacheable
和@CacheEvict
来操作缓存(其实不仅仅可以用于数据库方面,Service层的接口都可以)。这方面文章很多,这里不介绍了,只提醒一个坑:Spring默认的缓存Key的生成策略比较简单,如果有两个方法使用的参数一致,可能会产生冲突。所以一定要自定义Key生成策略。这篇文章可以参考一下: https://www.jianshu.com/p/2a584aaafad3。
(2)页面缓存
对企业门户类的系统,页面的变动频率并不高(不要拿互联网门户来比啊!),对用户来说也可以接受一定的延时,因此直接缓存最终渲染后的页面,跳过后台所有获取数据、组装页面的过程,能得到极高的响应速度。配置过程很简单,只要设置好缓存参数,再添加一个Web过滤器,不需要改动代码,是不是很美好?具体可参考ehcache实现页面整体缓存和页面局部缓存。
缓存选型上,我们使用了Ehcache这种嵌入式的轻量级缓存,从而不改变系统总体架构。当然也可以使用其他的,只是要注意,如果使用嵌入式的,最好能支持集群。Spring已经对缓存接口做了抽象,只要写好适配器,就可以通过统一的接口来使用。
2. 优化数据库连接
根据并发数量的要求,需要调节数据库连接池的参数。我们在门户系统中使用的是Druid连接池,参数可以参考网上文章,根据需求和监测到的结果来调整。可参考: https://www.jianshu.com/p/e75d73129f51
下面列出了我们的部分参数:
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="100" />
<property name="minIdle" value="100" />
<property name="maxActive" value="500" />
<!-- 是否关闭未关闭的连接 -->
<property name="removeAbandoned" value="true" />
<property name="removeAbandonedTimeout" value="10" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="10000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="30000" />
<!-- 打开PSCache,并且指定每个连接上PSCache的大小。Oracle应打开 -->
<property name="poolPreparedStatements" value="true" />
<property name="maxPoolPreparedStatementPerConnectionSize" value="20" />
<!-- 配置监控统计拦截的filters -->
<property name="filters" value="stat" />
当然,上面只是应用程序层的调参,对数据库本身还需要通过命令来修改最大连接数限制,并且这个限制需要与应用层的参数配套。
这里我说一下调优经验:
(1) 务必开启Druid连接池的监控统计功能:通过Druid的监控页面可以在压测时实时查看数据库连接数、最慢SQL、SQL执行次数、HTTP请求次数、最慢HTTP请求等重要信息,从而为优化程序代码、设置最大连接数上限等行动提供参考。
(2) 应用程序连接池和数据库最大连接数之间要配套:如果有多个应用访问同一个数据库,需要注意他们的连接池上限之和,防止出现每个连接池本身未满,加起来压垮了数据库的情况。一般来说,先根据并发用户数来确定数据库的连接上限,再根据该上限来分配各个连接池的额度。不过,对单次用户访问,各个应用通常不会同时连接数据库,所以连接池上限之和是可以大于数据库上限的。测试时监控数据库连接数的变化情况,会帮助你搞清楚你系统的并发用户数与数据库连接数之间的大致关系。
(3) 压测时根据错误日志判断哪里需要调参:简单说,如果报错堆栈的最上面是连接池的Class信息,则说明是连接池自身报错,需要调连接池参数;如果最上面是JDBC Class的错误,则说明已经到达了实际访问数据库的地方,很大可能是数据库出错,需要调整数据库参数,但也有可能是因为连接池参数不当间接造成的数据库错误。分析时可能需要去看数据库运行日志。
3. 优化日志输出
我们的应用系统有两种类型日志:
- 用户登录等重要事件的操作日志,存在数据库操作日志表中
- 系统运行时通过Logger打印到文件的运行日志
第一种日志在压测时严重影响性能甚至造成系统崩溃。其实不仅仅是日志,因为数据库的锁机制,只要是高并发地往同一张表写入数据,性能都会急剧下降甚至报错。此时需要引入缓存:先把要写入的数据缓存起来,然后用一个后台工作线程定时或定量触发方式把缓存数据通过Batch SQL的方式批量写入数据库。缓存可以起到削峰的作用,缺点是写入滞后、异常情况下丢失数据。对本系统的日志而言,这些问题可以接受。如果需要高实时性、高可靠性,那就考虑使用Redis、ElasticSearch之类的外部组件,优化架构吧。
第二种日志,如果用户一个动作产生2行日志,并发1000就意味着每秒钟要写入2000行文件,说大不大说小不小,毕竟存储设备也是有IOPS上限的。为了榨取最大性能,可以考虑把日志级别调到WARN以上,减少日志输出量。
4. 程序代码优化
应用程序代码优化是个细活、工夫活,这里分几个层面来说。 除特别声明,本站所有文章均为原创,如需转载请以超级链接形式注明出处:SmartCat's Blog
(1)前端优化
先看这个汇总吧:
标签:开发技术
精华推荐