2016年初刚刚从CSC辞职回老部门没多久,就听说了部门里有个系统有些问题,最主要的就是反应在系统性能上。然后没多久,该系统的两任Tech lead都跳槽了。再然后这个有问题的系统也划到我名义下来兼管了。之后就经历了一段黑暗时期,每两三天系统不是崩溃就是必须重新启动,各种各样的问题,三天两头数据库锁死,内存溢出。最后,上头终于同意拨了一笔专项基金用于该系统的优化。如此,最重要的钱解决了,那么项目也就可以开始进行了。
为了便于理解,系统的结构还是需要略微简单的介绍一下的。
1. IBM Webseal 负责
负载均衡两条不同数据中心的
服务器和粘性会话。
2. IBM WebSphere Application Server: 部署在不同的数据中心,每个数据中心有两个逻辑服务器,一个负责逻辑显示层,一个负责业务逻辑层。然后每一个逻辑服务器下面有两个实例。 这样总共构成了两条线的逻辑服务器,每条线有4个实例。
3. IBM DB2 作为数据库。
4. 整个程序是建立在Java EE上面的。界面是用部门内部开发的WComponent(在GitHub上可以找到)来做的。逻辑层混合了Spring + EJB + MDB,持久层使用的是Hibernate+ 动态SQL +数据库触发器。
当阅读了系统的框架文档后,并且做了系统的压力测试后,发现有五个方面存在一些性能上的问题。
1. 系统结构
2. 数据库
3. 中间件设置
4. 系统程序
5. 业务要求的不合理性
基本上这个列表里面几乎包括了这个系统的所有的东西。这里我来简单介绍一下某些特定的问题和一些解决方案:
1. 系统结构
大概有8个外部系统与这个有问题的系统通过企业服务总线(Enterprise Service Bus or ESB)用JMS进行数据交换。JMS是以soap信息标准来实现的。其中有两个系统是整个部门的核心系统。系统A是一个IBM主机系统,已经有不少的年头了。系统A通过三种方式来传输JMS数据给问题系统:程序本身,服务转换,以及数据库触发器。系统A大概每秒钟传输25条JMS到问题系统。系统B是一个客户记录系统, 大概每秒钟产生15条JMS传输到问题系统。通过数据分析发现以下问题:
· 由于系统A/B的设计问题,大量重复JMS被发送到问题系统进行处理。
· 由于设计原因,系统A/B更新一条主记录会产生多个JMS对应同一条主记录下面的多个子记录,并且几乎在同一时间发送出去。由于这些JMS属于同一个主记录(KEY),当问题系统多个实例下的MDB接受到JMS,需要更新数据的时候经常会遇到该条数据被锁死的情况。导致后面的JMS必须等数据库更新完成后才能继续处理。
由于修改系统A/B的可能性微乎其微(从系统复杂系,改动费用,以及修改系统A/B对其他系统的影响)。第一个问题我们从ESB入手,通过数据分析,将重复的信息直接在ESB就过滤掉了。第二个问题的解决方案是通过两个步骤修改问题系统。第一步,根据主键值将JMS分配到特定的节点,保证同一主键值的JMS被同一个WebSphere服务器的同一个节点处理。第二步,将即时处理系统改成为批处理系统,批处理的间隔时间根据需求设置的比较短一点。综合第一步和第二部,一方面减少了大量的数据库读写操作,另一方面也可以保证收到的JMS可以根据生成的时间顺序来进行处理。而且,数据库数据被锁死的情况也不会再发生了。
2. 数据库
对于数据库优化我想有一定经验的开发人员都会有一点了解了,这里我们就不讲那些最基本的东西了。当然有一些比较复杂优化方案也只有专业的DBA能够全面的了解,而我有幸的遇到了一个非常不错的DBA。问题系统有很多系统生成的SQL以及开发人员特意撰写的SQL,我们这里来分类说一下。
· 储存框架。问题系统使用hibernate作为储存框架。在某一个hibernate mapping中使用了union-subclass 的策略,也就是对于每个具体类,产生一个表。本身这个策略是没有什么问题的,可是当用到特定的情况下,问题就来了。第一,继承的具体类别比较多。第二,当每个具体类有几百万条记录时,因为自动生成的SQL会使用 A union B union C union D … 导致整个系统的读取变得奇慢无比。在项目刚刚开始的时候因为继承具体类比较少,而且数据库记录也比较少,在两三个具体类,每个类上万条至上几十万条数据的时候系统性能没有什么影响。但是随着具体类的增加,数据记录的增加,问题系统的性能慢慢的出现了问题。为了尽量减少系统数据库的改变,最后使用了joined-subclass来解决了这性能问题。通过使用Joined-subclass,生成的SQL采用了join而不是union,运行效率大大提高。(图中B在优化的时候被取消了,因为B的定义不明确,而A可以直接作为B1/B2/B3的超类。)
· SQL优化。SQL优化可以完全写一本书了,在这里我只是略微带一下了。基本上来说,问题系统的很多SQL没有考虑到整个表扫描的耗费。原先数据表上万上十万的数据全表扫描可能没有什么感觉,但是当数据表有了几百万甚至上千万条记录时,一定要避免全表扫描。我们做了一些统计,特别是当有很多数据的几个表结合在一起的时候,当有全表扫描的时候,同样效果的SQL运行速度要比不需要扫描的SQL慢成百上千倍。另外,另外一个经验教训就是千万不要将太多的商业逻辑封装到SQL里面,而问题系统正式犯了这一个错误。问题系统有些动态生成的SQL近四五百行,复杂程度极高,导致几乎整个开发小组没有人愿意优化这些SQL。这时候幸好我前面所提的DBA给了整个开发小组极大的帮助。
3. 中间件设置
问题系统使用IBM WebSphere Application Server8.5作为中间件。在问题系统长时间运行之后,我们发现数据库连接虽然回到了数据库连接池,但是当系统尝试获得一个数据库链接的时候,WAS并没有可用的数据库链接提供给系统。刚开始的时候开发小组以为在系统的某处可能存在异常处理不到位导致数据库连接没有回到连接池。开发组用了很长的时间做了系统源代码分析,可是没有发现任何可疑的异常处理而导致数据库连接丢失。最后通过WAS的数据库分析模块发现数据库连接是被返回到连接池中了,但是该连接处于不可用状态。最后发现罪魁祸首是WAS的StaleConnectionException。而在WAS的数据库设置里面,系统管理员将数据库连接池清除政策设置成FailingConnectionOnly,而不是整个连接池。最后通过将连接池清除策略设置为整个池后,我们解决了这个问题。
4. 系统程序
问题系统本身有一个批处理功能用来处理大量数据,这个批处理功能在一个全局事务(Global transaction)下面。这个全局事务时间已经从默认的120秒提升到了300秒,但是有时还是有超时现象发生。等仔细研究了系统程序以后,发现这个批处理功能在某一时间只在某一节点上运行,而其他另外三个节点是空闲的。通过修改系统程序,把这个批处理功能所需要处理的数据均匀分布到四个节点上同时进行操作后,该批处理功能速度提升近四倍。
5. 业务要求的不合理性
问题系统有一个最严重的问题就是应许终端用户建立自己的批处理命令,并且应许终端用户设置自己想要运行该批处理命令的时间。通过数据分析发现,两年半之内终端用户总共建立了7000多个批处理命令,而大概有3000多个都是设置运行在早上7:00点。然后大概有2000多个是在凌晨0:00,剩下的2000个左右的批处理命令比较均匀的分布在其他不同的22个时段。当技术小组查看当年的用户需求文档的时候,有一条用户需求就是“用户可以自己设置批处理命令的运行时间”。这个用户要求本身没有什么问题如果这个是一个个人使用的单机系统,但是当作为一个多用户系统,系统的资源是共享的,这条业务需要本身需要一个约束。通过与用户代表的沟通,最后根据系统性能,这个约束条件被加入了开发文档,限定了在每个时段最多有1000个批处理命令。总共全天可以有24000个批处理命令可以执行。
总结
当我们做系统优化的时候,我们不能只考虑到系统本身,必须从多方面,多角度来看。当我们通过压力测试知道了某一个系统瓶颈时候,我们必须分析这个瓶颈是如何造成的。有很多时候这个系统瓶颈并不是由于系统自身导致的,而是由于不合理/不清楚的需求,或者系统交互的设计问题,系统集成的架构问题导致的。没有从这些方面着手,系统内部再怎么优化也是有局限性的。
另外一点,在优化系统的过程中,开发人员往往会发现当一个瓶颈被优化好了以后,另一个瓶颈又出现了。这是很正常的一个过程。优化系统就像是一段旅程,每旅游完一个景点后就会有下一个景点在前面等着你。何时这段旅程能够完成就看优化后的系统是否达到了非功能性要求的标准。
作者华杰, 从事IT工作15年,做过程序员,首席软件工程师,架构师,IT技术顾问,现为澳大利亚移民和边境保护局Tech lead.
LinkedIn:http://au.linkedin.com/in/jie-hua-01021118
个人电子邮件:jhua04@outlook.com
当前名称:系统优化实例一则
转载注明:
http://mswzjz.cn/article/iegdpp.html