记录一下运维平台的一次优化经历

余梦

运维平台每天都有人在用,但是用户量不大,高峰期也没啥高并发的场景,也没啥大的表,最大的表记录也就200万的样子,目前情况下完全没啥性能瓶颈。正是如此,为啥要考虑对其进行一次性能优化呢?这还得从一封邮件说起。

为什么考虑要优化

    话说有一天老大突然告诉我,运维平台CPU负载进了高峰期(11-12点)TOP10了。我顿时感觉很诧异,以我对平台的了解和技术认知,这不应该啊,满脸狐疑的打开zabbix监控看了下,我去,那个时间段有次CPU飚的老高了。当时第一反应是问运维,为啥以前平台没发现此类问题,是不是做了什么操作。运维告知说最近刚把平台的监控给加上了。好吧,到了这里,我第一感觉是以前肯定就有类似的情况,只是那会没加监控所以没发现罢了(毕竟我接手后做了不少优化呢)。于是我当天观察了下,发现有几次CPU的飙升是因为FGC引起的,这时我去看了下运维配置的JVM参数,发现运维平台JVM配置和公司服务化后很多应用的模板一样,这肯定不行的,运维平台功能模块多、逻辑复杂、代码量也不小了,项目工程规模相为服务化后的应用不知道要大多少。于是考虑对JVM进行下调优,考虑到运维平台不属于吞吐量优先的系统,属于响应时间优先的系统,我将原来的内存为2个G的设置为4个G,指定CMS配置为-XX:ThreadStackSize=512 -XX:+UseConcMarkSweepGC -XX:CMSMaxAbortablePrecleanTime=5000 -XX:+CMSClassUnloadingEnabled -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnl 配置后观察了一天,FGC次数已经比较少了,感觉问题告一段落了也就没再关注这个,就去弄其它东西了。

问题再现,开始定位问题

    就在我更改JVM配置后没多久,运维平台又进TOP10了,这个时候我看了下,那个时间段的JVM内存占用并没特别之处,对比了dapoint,zabbix的数据,发现也没啥异常,后来看了下全天的CPU,内存占用情况图,也没发现啥问题,只是系统的CPU平均负载相对有点高而已,和系统CPU飙升到一两百也没啥大的关系。后来发现原来是系统重启的时候,平台cpu占用相对是很高,剧我观察,两台机器启动的时候CPU飙升差异还很大,一个100多,一个400多。。。。。(这点zabbix监控统计邮件能否过滤下,这种不应该统计进去的。)还有FGC的时候,CPU有点高,这也正常。以前也没留意平台的CPU负载问题,这次发现一些时间段平台CPU平均负载不正常(以我的了解不应该这么高,有的都70-80%了)。其它很多时间段都没啥特别之处。回到前面这个问题,系统性能问题,不管有多少种可能,最终脱离不了三个原因,基本都可以归属于这三类,CPU、IO、网络。造成CPU高的原因可能很多,比如系统里面有死循环、线程太多上下文切换频繁、并发太高、FGC等。在我进行JVM调优后,FGC次数明显减少,但是一天也还是很多次。我们知道CMS在JVM内存达到阈值时才会触发FGC,但是我们不要忘了,CMS收集器无法处理浮动垃圾,并且对对CPU资源非常敏感,可能出现“Concurrent Mode Failure”失败而导致另一次FGC的产生,同时也会导致内存碎片问题而降低内存使用率提前触发FGC。于是我将CMS改成G1后,到目前为止,似乎都没FGC过。

55555 说到那个上午11-12点高峰段运维平台CPU高的问题,其实除了偶尔是FGC外,基本都是那个点平台在发布。这个有点坑啊,因为运维平台单系统规模相对不小,类文件数目也不少,在系统的时候CPU占用相对很高,为什么会高很多呢?我们知道,JVM在对类文件进行加载、验证、准备、解析、初始化的时候是需要消耗不少CPU、内存资源的。class文件越多,响应的验证、准备、解析、初始化阶段消耗的CPU资源就更高(正常情况下)。后来和老大说了下原因,平台基本不再高峰期发布了(以前考虑那个点发布是因为到了饭店、而且是公司业务高分期,平台用户较少)。既然做到这步了,就索性把平台平均CPU占用有点高的问题一并解决吧。

再次定位问题

为什么在用户量不大的一个应用中,平均CPU占用这么高呢?刚开始我也是很奇怪的,在此之前因为平台没接入监控,也就没留意到这个问题。后来我开始着手观察起来了。 我会时不时去平台看下此时使用的人是否多,如果大家打包发布等操作多起来了(比如发布窗口高峰期),我就去盯着zabbix 的CPU监控指标看,一看CPU还是升起来马上去平台jstack下线程CPU占用TOP信息。这过程也很烦人,毕竟我也没时间一直盯着系统和服务器,有时候我看CPU高了,马上去top,jstack,dump的,结果命令刚敲完CPU就下去了,dump下来的内存快照,用visualVM分析后也无异常。高峰期偶尔jstack下来看到如下信息,问题比较明显,修改优化代码即可。 image image image 在定位哪些代码消耗CPU资源较高的问题中,用jstack很方便,但是对于偶发性、或者CPU负载高但是持续时间较短的情况,非常不方便。我是后来嫌太费事,我就去找运维,我要装大杀器阿尔萨斯,经过运维统一后,开撸。这下爽了,简单一个命令,直接耗资源TOP10的线程和代码都出来了,无处遁形。 如果执行一次没发现异常,可以多执行几次,如果TOP10里面经常出现同一段线程栈情况,这个时候就要引起重视了,重点关注对象。 image

对于一些耗时较高的方法,如果想及时的监控到耗时信息(你也可以通过其他很多方式,比如你可以打点记录耗时但是你可能某个方法没打点记录,也没接dapoint) 找到这些TOP10里面和系统相关的代码,进行代码走读分析,然后进行对应优化和调整。借助于dapoint,你可以很容易的知道系统中方法调用的耗时及调用链路情况。 image

基本上,结合Dapoint、Zabbix、Arthas、三者,你能很容易的就发现和定位分析一些系统中的性能问题。这些工具没用过或者不大清楚的,强烈推荐下,还是挺好用的,尝试着去用用。


运维平台性能优化的一点总结

    在总结之前,先说下运维平台里面影响程序性能最多的几个点。

  1. 为了复用代码而复用代码
    比如几个查询有点类似的操作,返回的数据字段多少有差异,或者是返回的数据一个接口包含了另外一个接口的数据的情况,这个时候为了所谓的复用,共用一个业务处理逻辑,导致你的处理逻辑杂糅到一起,做了不必要的查询和操作,返回了不必要的信息。

  2. 处理业务时,很多原本不必要的查询操作,或者是在循环里面狂查询。

  3. 没有针对业务场景对定时器轮询接口频率进行适当控制,定时器轮询操作频率设置得太高.
  4. 很多可以避免的bean拷贝。
  5. 返回过多的字段
  6. 平台前端有些轮询接口在操作结束后依然在继续轮询,定时器没关闭等。
  7. 有些性能问题可以通过换种方式解决而不是程序上无法再优化的方式去做。

针对上面的问题,做个总结:

  1. 关于代码复用,很多时候为了所谓的代码复用导致多个接口调用同一块业务查询逻辑,或者共用同一个入参出参封装对象,原本只需要返回个状态的地方,或者个别字段的地方,乱七八糟不需要的数据都给返回了,我们知道数据序列化和反序列化、传输过程中都是要消耗资源的。所以代码接口、业务逻辑实现上,尽量做到隔离、职责清晰,防止一个接口或者实现承担过多的任务。这样也利于拓展业务、维护接口
  2. 关于尽可能的减少查询(数据库连接资源是很宝贵的)
    利用缓存、数据不大的情况下关联查询(相对分开多次去查)等,对不需要的数据不要返回等,或者一次性拉取进行操作,比如运维平台有个操作是要查询所有服务器和网卡信息,服务器和网卡是一对多关系,用SQL语句一次拉回常规方式不行,这个时候如果你首先查询出2000多台服务器,然后循环通过服务器去查找网卡列表,你就知道哪怕如此简单的操作这个接口有多慢。此时完全可以把网卡、服务器信息全部拉出来(需要的字段)再去处理。(服务器2000多台,网卡3000左右)。 3.对于页面上的一些轮询操作,可以根据业务场景对时间的敏感性、信息获取的时间周期等情况进行设置,而不是全部采取一套策略。
    4.对于很多接口,特别是调用量比较高的查询接口,业务处理内部,尽量少用基于反射的对象属性拷贝。现在的接口经常涉及到DTO和Mapping Entity的转换,对于一些对象结构很复杂的系统,打破固有的“标准”也不失为一种方法(尽量少用),其次,可以使用MapStruct,都不需要你手动去设置属性,简单注释下就ok。
    5.针对第五个问题,特别是查询频率高、时间敏感的接口,需要几个字段返回几个字段。在数据量小的时候可能感觉不到什么,在数据量大的时候,你就会发现这样做的好处。
    6.有些时候查询的数据非常复杂,包含太多表的信息,这种情况,基本上只有用缓存(或者数据异构),问题是很多时候这些数据还是热点数据,你也没法提前做数据异构,缓存同步也是个大问题,这个时候,或许能改变一下操作或者调整下需求(最终能达到大家都接受的情况)或许问题就解决了。
    7.有些性能问题可以通过换种方式解决,特别是当技术优化边际效应很低的情况下。
    以上就是前阵子对运维平台的一些优化,以及做的一个小结,有些观点可能具有当前环境的局限性未必完全正确,请大家包含O(∩_∩)O哈哈~