一、问题的提出
随着系统业务数据量的不断增长, 使用用户的不断增加, 项目组越来越频繁收到用户特别是中研用户反馈的性能问题,
主要是以下几个方面的问题:
1. 系统使用高峰期比如周一和月初, 用户反馈系统登陆不了, 菜单响应和操作缓慢等问题;
2. 客户查询功能响应缓慢, 一个4000条数据的查询时间超过N分钟;
3. 客户增量保存功能操作失败,有些时候增量保存的线程跑了1个小时还没成功保存,结果是用户数据丢失(后台同步处理);
4. 客户操作后死机
5. 管理员反馈报表查询响应不了甚至报系统错误
经过对系统的监测和分析, 监测到的问题如下:
1. 数据库连接占满, 事务等待超时或死锁
2. 数据库服务器停止, 事务日志占满, 报磁盘空间不足的错误
3. 后台日志显示数据库语句堆, 排序堆不足
4. 后台日志显示缓冲池中无页面可用
5. 通过连续监测发现每周一上午9:30-10:30这个时间段数据库CPU使用率接近100%高居不下
二、解决思路
系统的核心业务数据是百万级别的数据, 以及千万级别的日志数据:
既是一个OLTP系统, 又是一个OLAP系统; 任务流程处理这块都是一些大事务和并发多的事务,
而任务分析这块是实时进行大数据量超复杂的统计运算; 这些地方就是系统的性能瓶颈;
鉴于此, 我们从SQL语句, 应用程序逻辑, 数据库, 数据归档, 部署方式等多个层面进行系统的性能优化,
优化的重点是减少事务并发, 保证稀有资源的合理利用;
2.1. SQL语句优化
a) 系统存在很多树结构数据, 比如项目树, WBS, 对这些数据进行查询的SQL进行优化,
分析发现很多递归SQL在递归层次深,数据量大的情况下, 性能问题突出, 我们采取的办法是:
尽量使用视图;
将查询条件从SQL语句的最外层移到内层子查询, 减少数据查询量;
报表SQL允许脏读, 以防大面积的关键数据表行锁升级为表锁;
b) 系统有很多一对多特别是多对多的表关联查询, 比如任务表和日志填报表;
分析发现很多地方就用一个超大的SQL查询任务和日志数据, 这样查询的结果冗余量大,我们采取的办法是:
拆分成多个SQL进行查询;
c) 很多SQL中包含in函数, 而且in的子查询语句的结果集不确定,
可能上千上万, 这种SQL导致数据库后台报语句堆不足的错误, 我们采取的优化办法是:
用关联查询替代in函数;
用or语句替代in函数;
d)整理出大数据量查询的SQL语句, 找出关联查询的条件, 根据索引原则,
增加数据库索引;
e) 有不少报表统计SQL语句直接处理的大数据量的复杂运算, 导致占用大量的数据库资源,我们采取的优化办法是:
SQL语句用来查询数据, 而运算统计的部分交给应用服务器处理;
采用静态报表, 晚上统计出数据, 白天就可以查询统计后的结果数据;
f) 分析发现有些SQL语句类似 ”select * … ” , 这些地方全部将*号替换成具体的列;
还有些SQL返回的结果集数量是可知的,这些地方使用FETCH FIRST
n ROWS ONLY参数
2.2.应用程序逻辑优化
a) 一个最大的问题就是循环里面取数据库连接, 这种情况很普遍, 所有地方全部进行了改正;
还有些功能点需要多次存取数据, 考虑使用一个数据库连接;
取连接的语句在需要的时候执行, 而不是在方法一开始就执行;
b) 另一个问题是程序中, 有些数据库连接释放的部分代码在try子句里面,
有些在catch子句里面,现在全部改为在finally中执行连接释放的操作, 这也是一个普遍的问题;
c) 程序忽略了异常处理或者对异常处理不够正确, 特别是一些无法测试的异常,
一旦发生这里异常可能导致事务不一致, 重复任务数据的BUG就是这样产生的,我们采取的处理办法是:
这些地方都进行了异常捕捉处理;
发生异常的地方都进行了事务的回滚;
d) 另一个是事务一致性和并发性能的取舍问题, 在关键功能上进行了利弊权衡;
e) 有些大事务功能点, 比如客户端下达, 任务反馈日志填报等程序逻辑上,对数据表的更新顺序不一致,这些地方都调整成一致;
以防并发出现互相锁等待的问题;
f) 及时的释放大对象内存, 比如客户端项目列表,WBS列表等;
g) 有些大事务处理的程序导致DB后台日志满, 磁盘空间不足的情况,
我们除了及时的清理备份事务日志, 还对这些大事务程序进行优化, 拆分成多个小事务进行处理;
h) 递归查询的地方采取了数据校验, 防止递归的父和子的ID一致, 死循环查询,
导致最终数据库临时表空间占满, 内存溢出等问题;
2.3.数据库调优
a) 定制一个runstats计划, 将需要进行runstats的数据进行分类,
哪些是按月,哪些是按周统计 runstats实用程序用于更新系统目录表中的统计信息,以帮助查询优化过程
b) 对一些排序, 分组的字段或者关联查询的字段比如名称, 日期等,
根据索引原则, 建立了索引;
c) 定期进行数据库日志归档和清理;
d) 利用性能测试工具和监控工具, 进行了数据库的参数调优;
主要参数是缓冲池大小,locklist大小, 排序堆阀值和大小, 查询堆语句堆大小等;
LockTimeout的时间需要指定, 但不能太大;
使用snapshot监视缓冲池, 调整bufferpool参数, 保证缓冲池命中率到95%+;
使用snapshot跟踪排序活动, 并发测试大型排序的报表功能, 调整sortheap参数,
同时调整sheapthres参数;。
2.4.数据归档
以项目为单位, 以计划任务数据为依据, 统计出一定时间内没有活动的项目;
经过用户的确认, 将这些项目进行归档处理;
a) 第一步, 按照项目导出这些数据到本地, 包括项目, 计划任务,
日志, 项目成员等数据
b) 第二步, 删除生产环境上的这些项目数据;
c) 第三步, 这些项目数据转移到备份数据库, 用户可以从备份数据库上查询到历史数据
数据归档的时候总结出了一些问题
a) 删除语句和导入语句有先后顺序的;
b) 没有主键的表,导入的时候不能用insert_update操作;
确认数据删除没错的话,可直接用insert取代insert_update操作;
c) 导出的数据量比较大的情况, 建议分段导出
d) 执行export和import命令需要在装有DB2 Server的环境中执行,否则会报程序包未找到的错误;
e) 考虑到删除数据量的问题,采用单个项目单个项目数据迁移的方式;
f) 跑脚本的时候,需要停止其他所有应用,否则很容易引起事务等待;
g) 回滚数据库日志的时候比较消耗内存, 回滚前重启机器
2.5.部署方式
将数据库服务器拆分为执行数据库和报表数据库:
执行数据库主要进行OLTP操作,报表服务器用作各类报表的查询,执行OLAP操作
报表服务器的数据定期从执行数据库中同步;
具体方案有2种:
a) 使用同一个应用服务器
在已有的AppServer上加上报表数据库的数据源,报表查询都调用报表数据源即可
b) 使用2个应用服务器独立部署
用户通过执行服务器登陆界面登陆,当查询报表时,自动跳转到报表服务器
报表切换方案
a) 修改 执行AppServer应用程序的报表管理菜单的链接,
让其重定向到 报表AppServer应用上
b)修改 报表AppServer应用程序,增加redirect.jsp文件。
主要是在报表AppServer上自动重建session,包括:
界面语言,是否统一登陆,用户信息,用户类型,菜单列表,当前菜单;
重建完session后,自动定位到报表管理菜单
c)配置 报表AppServer菜单
禁用报表App除了报表管理相关菜单以外的所有菜单,已防止用户误操作;
比如:用户切换到报表管理菜单后登陆客户端操作,会发生没有权限问题;
d) 报表AppServer一些不相关的业务线程考虑关闭。
报表AppServer的组织切换跳转考虑不跳转
数据同步方案
a) DB2远程SQL复制
参考以上部署图,DB2的SQL复制主要有2个程序,一个是Capture程序,通过数据库日志从源数据库中获取变化,并把数据放到一个中间表中。
另一个是Apply程序,在设定的时间间隔将中间表的数据同步到目标数据库,从而到达DB2数据复制的目的。
(步骤略)
b) ETL工具同步
报表需要同步的表和Mapping方式如下, ETL工具配置步骤略;
总结:
目前系统有10几个节点在用, 这些节点都是独立部署, 独立的应用服务器和数据库服务器,后续用户希望能进行跨体系,跨部门的项目管理,
这就要求我们的系统需要进行多节点的合并处理, 业务数据也需要合并, 这种数据几何级的增长, 必然会对系统有更高的要求,
所以后续工作还需要考虑在集群, 磁盘阵列, OLTP&OLAP分离, 表分区, 树型报表分页,
数据同步等等多个方面进行系统的部署调整 |