文档章节

从代码层面优化系统性能应该怎么做?

九州暮云
 九州暮云
发布于 2017/09/07 10:58
字数 2218
阅读 7
收藏 0
点赞 0
评论 0

服务器环境

  • 服务器配置:4 核 CPU,8G 内存,共 4 台
  • MQ:RabbitMQ
  • 数据库:DB2
  • SOA 框架:公司内部封装的 Dubbo
  • 缓存框架:Redis、Memcached
  • 统一配置管理系统:公司内部开发的系统

问题描述

  1. 单台 40TPS,加到 4 台服务器能到 60TPS,扩展性几乎没有。
  2. 在实际生产环境中,经常出现数据库死锁导致整个服务中断不可用。
  3. 数据库事务乱用,导致事务占用时间太长。
  4. 在实际生产环境中,服务器经常出现内存溢出和 CPU 时间被占满。
  5. 程序开发的过程中,考虑不全面,容错很差,经常因为一个小 bug 而导致服务不可用。
  6. 程序中没有打印关键日志,或者打印了日志,信息却是无用信息没有任何参考价值。
  7. 配置信息和变动不大的信息依然会从数据库中频繁读取,导致数据库 IO 很大。
  8. 项目拆分不彻底,一个 Tomcat 中会布署多个项目 WAR 包。
  9. 因为基础平台的 bug,或者功能缺陷导致程序可用性降低。
  10. 程序接口中没有限流策略,导致很多 VIP 商户直接拿我们的生产环境进行压测,直接影响真正的服务可用性。
  11. 没有故障降级策略,项目出了问题后解决的时间较长,或者直接粗暴的回滚项目,但是不一定能解决问题。
  12. 没有合适的监控系统,不能准实时或者提前发现项目瓶颈。

优化解决方案

数据库死锁优化解决

我们从第二条开始分析,先看一个基本例子展示数据库死锁的发生:

输入图片说明

注:在上述事例中,会话 B 会抛出死锁异常,死锁的原因就是 A 和 B 二个会话互相等待。

分析:出现这种问题就是我们在项目中混杂了大量的事务 +for update 语句,针对数据库锁来说有下面三种基本锁:

  • Record Lock:单个行记录上的锁
  • Gap Lock:间隙锁,锁定一个范围,但不包含记录本身
  • Next-Key Lock:Gap Lock + Record Lock,锁定一个范围,并且锁定记录本身

当 for update 语句和 gap lock 和 next-key lock 锁相混合使用,又没有注意用法的时候,就非常容易出现死锁的情况。

那我们用大量的锁的目的是什么,经过业务分析发现,其实就是为了防重,同一时刻有可能会有多笔支付单发到相应系统中,而防重措施是通过在某条记录上加锁的方式来进行。

针对以上问题完全没有必要使用悲观锁的方式来进行防重,不仅对数据库本身造成极大的压力,同时也会把对于项目扩展性来说也是很大的扩展瓶颈,我们采用了三种方法来解决以上问题:

  • 使用 Redis 来做分布式锁,Redis 采用多个来进行分片,其中一个 Redis 挂了也没关系,重新争抢就可以了。
  • 使用主键防重方法,在方法的入口处使用防重表,能够拦截所有重复的订单,当重复插入时数据库会报一个重复错,程序直接返回。
  • 使用版本号的机制来防重。

以上三种方式都必须要有过期时间,当锁定某一资源超时的时候,能够释放资源让竞争重新开始。

数据库事务占用时间过长

伪代码示例:

输入图片说明

项目中类似这样的程序有很多,经常把类似 httpClient,或者有可能会造成长时间超时的操作混在事务代码中,不仅会造成事务执行时间超长,而且也会严重降低并发能力。

那么我们在用事务的时候,遵循的原则是快进快出,事务代码要尽量小。针对以上伪代码,我们要用 httpClient 这一行拆分出来,避免同事务性的代码混在一起,这不是一个好习惯。

CPU 时间被占满分析

下面以我之前分析的一个案例作为问题的起始点,首先看下面的图:

输入图片说明

项目在压测的过程中,CPU 一直居高不下,那么通过分析得出如下分析:

数据库连接池影响

我们针对线上的环境进行模拟,尽量真实的在测试环境中再现,采用数据库连接池为咱们默认的 C3P0。 那么当压测到二万批,100 个用户同时访问的时候,并发量突然降为零!报错如下:

com.yeepay.g3.utils.common.exception.YeepayRuntimeException: Could not get JDBC Connection; nested exception is java.sql.SQLException: An attempt by a client to checkout a Connection has timed out.

那么针对以上错误跟踪 C3P0 源码,以及在网上搜索资料发现 C3P0 在大并发下表现的性能不佳。

线程池使用不当引起

输入图片说明

以上代码的场景是每一次并发请求过来,都会创建一个线程,将 DUMP 日志导出进行分析发现,项目中启动了一万多个线程,而且每个线程都极为忙碌,彻底将资源耗尽。

那么问题到底在哪里呢???就在这一行!

private static final ExecutorService executorService = Executors.newCachedThreadPool();

在并发的情况下,无限制的申请线程资源造成性能严重下降,在图表中显抛物线形状的元凶就是它!!!那么采用这种方式最大可以产生多少个线程呢??答案是:Integer 的最大值!看如下源码:

输入图片说明

那么尝试修改成如下代码:

private static final ExecutorService executorService = Executors.newFixedThreadPool(50);

修改完成以后,并发量重新上升到 100 以上 TPS,但是当并发量非常大的时候,项目 GC(垃圾回收能力下降),分析原因还是因为 Executors.newFixedThreadPool(50) 这一行,虽然解决了产生无限线程的问题,但是当并发量非常大的时候,采用 newFixedThreadPool 这种方式,会造成大量对象堆积到队列中无法及时消费,看源码如下:

输入图片说明

可以看到采用的是无界队列,也就是说队列是可以无限的存放可执行的线程,造成大量对象无法释放和回收。

最终线程池技术方案

方案一

输入图片说明

注:因为服务器的 CPU 只有 4 核,有的服务器甚至只有 2 核,所以在应用程序中大量使用线程的话,反而会造成性能影响,针对这样的问题,我们将所有异步任务全部拆出应用项目,以任务的方式发送到专门的任务处理器处理,处理完成回调应用程序器。后端定时任务会定时扫描任务表,定时将超时未处理的异步任务再次发送到任务处理器进行处理。

方案二

使用 AKKA 技术框架,下面是我以前写的一个简单的压测情况:

http://www.jianshu.com/p/6d62256e3327

日志打印问题

先看下面这段日志打印程序:

输入图片说明

像这样的代码是严格不符合规范的,虽然每个公司都有自己的打印要求。

首先日志的打印必须是以 logger.error 或者 logger.warn 的方式打印出来。

日志打印格式:[系统来源] 错误描述 [关键信息],日志信息要能打印出能看懂的信息,有前因和后果。甚至有些方法的入参和出参也要考虑打印出来。

在输入错误信息的时候,Exception 不要以 e.getMessage 的方式打印出来。

合理的日志格式是:

输入图片说明

我们在程序中大量的打印日志,虽然能够打印很多有用信息帮助我们排查问题,但是更多是日志量太多不仅影响磁盘 IO,更多会造成线程阻塞对程序的性能造成较大影响。

在使用 Log4j1.2.14 版本的时候,使用如下格式:

%d %-5p %c:%L [%t] - %m%n

那么在压测的时候会出现下面大量的线程阻塞,如下图:

输入图片说明

再看压测图如下:

输入图片说明

输入图片说明

原因可以根据 log4j 源码分析如下:

输入图片说明

注:Log4j 源码里用了 synchronized 锁,然后又通过打印堆栈来获取行号,在高并发下可能就会出现上面的情况。

于是修改 Log4j 配置文件为:

%d %-5p %c [%t] - %m%n

上面问题解决,线程阻塞的情况很少出现,极大的提高了程序的并发能力,如下图所示:

输入图片说明

转自:从代码层面优化系统性能应该怎么做?

本文转载自:

共有 人打赏支持
九州暮云
粉丝 47
博文 128
码字总数 65499
作品 0
海淀
高级程序员
深入浅出“JVM”性能优化

深入浅出“JVM”性能优化 理解性能优化 性能基准 性能优化到底是什么 衡量维度 JVM调优 知其然,知其所以然 详解什么是JVM运行时数据区 详解什么是JVM内存模型JMM 详解GC可达 详解各垃圾回收...

Java高级架构 ⋅ 2017/12/18 ⋅ 0

C++ 服务端 性能优化

编写正常运行的程序很容易,但一旦数据量大起来,对代码的性能就需要认真考虑了。对于服务器来说,如果客户端一次访问,就需要话费几百毫秒,那么一旦每秒的访问次数多起来,后续的请求就会造...

lxfeng ⋅ 2016/05/22 ⋅ 0

说走就走的性能优化之旅

雅虎规则 熟悉网站优化的开发者应该都知道,只要提到网页性能优化,就绕不开雅虎军规。优化规则&&原文,仔细阅读这些规则,可以总结到3个方面: Http层面上的优化 减少http请求数,http请求c...

乖小鬼YQ ⋅ 2017/11/29 ⋅ 0

[高并发Java 九] 锁的优化和注意事项

锁优化的思路和方法 在[高并发Java 一] 前言中有提到并发的级别。 一旦用到锁,就说明这是阻塞式的,所以在并发度上一般来说都会比无锁的情况低一点。 这里提到的锁优化,是指在阻塞式的情况...

Hosee ⋅ 2016/02/16 ⋅ 0

秒杀系统架构优化思路

转载 2016-04-06 58沈剑 架构师之路 本文曾在“架构师之路”上发布过,近期支援Qcon-AS大会,在微信群里分享了该话题,故对原文进行重新整理与发布。 一、秒杀业务为什么难做 1)im系统,例如...

xiaolyuh ⋅ 2017/09/18 ⋅ 0

[转]秒杀业务架构优化之路

原文地址:http://www.infoq.com/cn/articles/flash-deal-architecture-optimization 一、秒杀业务为什么难做 IM系统,例如QQ或者微博,每个人都读自己的数据(好友列表、群列表、个人信息)...

风间影月 ⋅ 2016/03/29 ⋅ 0

秒杀系统架构优化思路

一、秒杀业务为什么难做 1)im系统,例如qq或者微博,每个人都读自己的数据(好友列表、群列表、个人信息); 2)微博系统,每个人读你关注的人的数据,一个人读多个人的数据; 3)秒杀系统,...

Candy_Desire ⋅ 2016/04/07 ⋅ 0

架构之路(二):性能

我们在上一篇博客中设定了架构的目标,只有一个,就是可维护性。完全没有提性能,这是故意的。 似乎程序员都是急性子,或许是被windows冗长的开机时间折磨够了,有可能是因为提升性能的效果是...

-鹏 ⋅ 2015/09/21 ⋅ 3

大雨倾城热情难灭 APMCon2016正式召开

  【IT168 资讯】8月18日,由听云、极客邦和InfoQ联合主办的中国应用性能管理行业盛宴――2016中国应用性能管理大会(简称APMCon 2016)在北京新云南皇冠假日酒店召开。作为国内APM领域的技术...

it168网站 ⋅ 2016/08/19 ⋅ 0

架构之路(二):性能

我们在上一篇博客中设定了架构的目标,只有一个,就是可维护性。完全没有提性能,这是故意的。 似乎程序员都是急性子,或许是被windows冗长的开机时间折磨够了,有可能是因为提升性能的效果是...

17bang自由飞 ⋅ 2015/09/07 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

聊聊spring cloud gateway的LoadBalancerClientFilter

序 本文主要研究一下spring cloud gateway的LoadBalancerClientFilter GatewayLoadBalancerClientAutoConfiguration spring-cloud-gateway-core-2.0.0.RELEASE-sources.jar!/org/springfram......

go4it ⋅ 15分钟前 ⋅ 0

详解:Nginx反代实现Kibana登录认证功能

Kibana 5.5 版后,已不支持认证功能,也就是说,直接打开页面就能管理,想想都不安全,不过官方提供了 X-Pack 认证,但有时间限制。毕竟X-Pack是商业版。 下面我将操作如何使用Nginx反向代理...

问题终结者 ⋅ 21分钟前 ⋅ 0

002、nginx配置虚拟主机

一、nginx配置虚拟主机可分为三种方式,分别为: 1、基于域名的虚拟主机,通过域名来区分虚拟主机——应用:外部网站 2、基于端口的虚拟主机,通过端口来区分虚拟主机——应用:公司内部网站...

北岩 ⋅ 25分钟前 ⋅ 0

shell脚本之死循环写法

最近在学习写shell脚本,在练习if while等流程控制时,突然它们的死循环写法是怎么样的?经过百度与亲测记录如下: for死循环 #! /bin/bashfor ((;;));do date sleep 1d...

hensemlee ⋅ 27分钟前 ⋅ 0

苹果的ARKit2.0有多可怕,看了就知道

序言 ARKit主要由三部分组成: 跟踪(Tracking) 跟踪是ARKit的核心组件之一,其提供了设备在物理世界中的位置与方向信息,并对物体进行跟踪,如人脸。 2.场景理解(Scene Understanding) 场...

_小迷糊 ⋅ 28分钟前 ⋅ 0

5.1 vim介绍 5.2 vim移动光标 5.3 ,5.4vim一般模式下移动光标,复制粘贴

vim命令 vim是vi的一个升级版;vim可以显示文字的颜色 安装vim这一个包vim-enhanced 如果不知道安装包,可以使用 命令下面命令来查看vim命令是那个包安装的。 [root@linux-128 ~]# yum prov...

Linux_老吴 ⋅ 31分钟前 ⋅ 0

vim一般模式

vim 是什么 vim是什么 ? 在之前接触Linux,编辑网卡配置文件的时候我们用过了vi ,vim简单说就是vi的升级版,它跟vi一样是Linux系统中的一个文本编辑工具。 如果系统中没有vim ,需要安装一...

李超小牛子 ⋅ 39分钟前 ⋅ 0

docker实战

构建企业级Docker虚拟化平台实战 重点剖析虚拟化和云计算概念; 分析Docker虚拟化的概念和原理; 从0开始实战Docker虚拟化平台; 基于Docker构建Nginx WEB服务器和CentOS虚拟机; 基于开源监...

寰宇01 ⋅ 49分钟前 ⋅ 0

vim介绍、vim颜色显示和移动光标、vim一般模式下移动光标、一般模式下复制粘贴剪切

VIM Vim 是 UNIX 文本编辑器 Vi 的加强版本,加入了更多特性来帮助编辑源代码。Vim 的部分增强功能包括文件比较(vimdiff),语法高亮,全面的帮助系统,本地脚本(Vimscript),和便于选择的...

蛋黄Yolks ⋅ 今天 ⋅ 0

springboot+mockito测试controller层遇到的问题

使用MockitoJUnitRunner测试的一个例子,原来报错无法找到bean, 类似的异常如下:createBeanError..... 原因:是因为@Runwith使用了SpringRunner,应该修改为MockitoJUnitRunner 代码如下: ...

writeademo ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部