当前位置:首页 > 短网址资讯 > 正文内容

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

www.ft12.com7年前 (2017-08-16)短网址资讯1926

我们以前看到的很多架构变迁或者演进方面的文章大多都是针对架构方面的介绍,很少有针对代码级别的性能优化介绍。本文将针对一些代码细节方面的东西进行介绍,欢迎大家吐槽以及提建议。
服务器环境
  • 服务器配置: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. 没有合适的监控系统,不能准实时或者提前发现项目瓶颈。

优化解决方案
1、数据库死锁优化解决

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

注:在上述事例中,会话 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 挂了也没关系,重新争抢就可以了。

  • 使用主键防重方法,在方法的入口处使用防重表,能够拦截所有重复的订单,当重复插入时数据库会报一个重复错,程序直接返回。

  • 使用版本号的机制来防重。

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

2、数据库事务占用时间过长

伪代码示例:

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

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

3、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

4、日志打印问题

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

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

首先日志的打印必须是以 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

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

扫描二维码推送至手机访问。

版权声明:本文由短链接发布,如需转载请注明出处。

本文链接:https://www.ft12.com/article_414.html

分享给朋友:

相关文章

文旅IP与住宿业融合:无IP不文旅,有IP则称王

文旅IP与住宿业融合:无IP不文旅,有IP则称王

[ FT12短网址 ] 生命在于有意义和有意思,已经成为很多人的价值观和生存观。他们渴望一个更精彩的生存方式,而不仅仅是填饱肚皮。这就催生了各种文化娱乐产业的诞生,在旅游行业,就是文旅产业。图片来自“123rf.com.cn”大约...

捷豹F-Pace大战沃尔沃XC60,谁更胜一筹?

捷豹F-Pace大战沃尔沃XC60,谁更胜一筹?

文 | ft12.com 编辑 :短网址作为一个SUV大国,路上已经罕有什么SUV会引起笔者的注意了,但每次一辆保时捷Macan从旁驶过的时候都会引起我短暂的注目。可你知道的,Macan的基础来源于奥迪Q5,所以如果你不想和满大街的各类“M...

在线教育企业7成亏损,15%濒临倒闭,你如何突围?

在线教育企业7成亏损,15%濒临倒闭,你如何突围?

[ 亿欧导读 ] 四月暮春,又到了一年一度在线教育公司交作业的时期,无论是自己晒成绩,还是第三方机构出评估,我们看到报表里的在线教育行业都是欣欣向荣、前景喜人,但真实情况如何?在线教育市场规模增幅迅猛,2019年将达2692.6亿...

如何使营销短信效果最大化?FT12「短链接」统计功能来支招

营销短信可以用较低成本换来高转化率,同时可以达到精准营销的目的,因而广泛受到电商平台的追捧。 但短信链接有没有被点击,短信发出去后的效果如何?如何让营销短信变成用户喜爱的有价信息而非垃圾信息?如何评估营销短信转化率?FT12「短链...

可以原谅,但不再信任!

可以原谅,但不再信任!

如果有人伤害了你,可以原谅他,但永远也不要再相信他。原谅是放过你自己,而盲目信任却只会给他再伤害你的机会。我们当然可以相信人会改过,但验证的机会就留给别人吧。那些伤害过你的人,放过他们,路过他们,挥挥手永不再见。即使生活给你一千个伤心的理由...

百度:我们不造车,Apollo只是人工智能的搬运工

百度:我们不造车,Apollo只是人工智能的搬运工

[ FT12短网址 ] 百度在Apollo生态中承担的角色更像是供应商,但是与传统零部件供应商不同的是,百度将不主要依赖于车企采购产品或技术的方式盈利,百度的盈利模式还有进一步的想象空间,而且在百度内部也还没完全考虑成熟...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。