性能——看不到的压力

   去年过年之前参与了一个项目,对执行项目有了更加深刻的认识,特此记录。

 

   首先项目的功能大致如下:

  1.   系统每天定时从某个外部系统用FTP 的方式获取电话号码相关文件,从周日到周五是增量数据,周六是全量数据。
  2.   系统每天8 点开始处理获取的数据—— 将所有的数据load 到内存中(增量数据大概2万条,全量数据一千两百万左右)
  3.   这个客户本身有自己的用户数据,接近一百五十万条,系统每个小时都需要将所有数据load 到内存中。
  4.   系统为外部系统提供sockethttp 的查询服务。

    这个项目实际上是一个已经上线5 年的项目,这次的功能改进是涉及到刚才列出的第一个,这部分最早不是使用文件的方式,而是使用数据库的方式。由于是功能改进,所以这次项目的一个基本原则就是必须延续之前的设计。之前的设计考虑到大数据量loadmemory 里面,并且不能影响用户查询的速度,所以:

  • 所有的数据按照电话号码的前四位,分成100 个组(因为前两位是一样的),每个组叫做一个subCache
  • load 数据的时候,按照号码顺序将所有前缀相同的号码加载到一个新的subCache 中,每当一个subcache 加载结束,与原有的subCache 进行切换,原有的subCache 即可释放。
  • subCache 保存的数据,为了方便查询使用了STL 提供的标准的map

    这个项目看起来没有多难,又有之前的代码参考,所以当时安排接手项目的是我们组一个新来没多久的孩子,本科毕业两年多一点,姑且叫他cc 吧。cc 是第一次 做要真正上线的项目,心里很紧张,也自然想借这个项目证明一下自己的能力,一个多月废寝忘食,终于所有功能测试都过了,项目的PM 也很满意,大家很高兴,cc 终于可以轻松一下了!

 

  等到上线的那一天,也是格外的顺利,两台64 位的有8G 内存的机器都上线成功。大家终于松了一口气。 但是,刚刚第三天,噩耗传来,程序使用的内存一直在涨,直到,系统没有内存了,程序挂了。


  Faint Faint ,程序挂了!!两台Active/Active 的机器已经挂了一台,另一个的内存使用量眼瞅着冲着7G 就去了,离挂也不远了。天哪, Memory Leak !!!!一定是memory leak !!!!

 

  项目出了这么大的问题,直接惊动了CTO ,立即召开紧急会议,分析原因并安排对策。项目执行过程中,最大的失误就是—— 没有做性能测试!追究完责任,得解 决问题啊,接下来就要开发人员努力去查找root cause ;系统维护人员在生产环境的机器上运行监控脚本,监控机器的内存使用情况,并每个小时定时发邮件给相关人等。


   cc 和我开始找原因,我们还尝试寻求几个高手的帮助。刚开始所有人都以为,那么大量的memory leak ,应该很容易就找到,可是事实证明,cc 写的code ,完全查不到有内存泄露的地方。我们做了能够做的所有的code review ,使用了purify 和一些开源的工具,也没有查到任何leak 的地方。

 

 

   就这样,两个星期过去了,每天每隔一个小时的production 上内存监控的邮件都让我们压力剧增。从内存的增长看起来,只要一有数据load 操作,内存就会涨,每隔三到四天,有一台机器的程序就必定要重新启动。那段时间最期盼的事情就是,有人突然发现出现了问题的代码,然后劈头盖脸的骂过来: 你们! 居然会犯这么弱智的错误!! 系统基本上有三种操作,一种是load 文件中的数据,一种是load 数据库里面的数据,还有,就是查询操作。在我们自己的测试系统中,单独做哪一个操作,系统的内存都会稳定下来,但是,只要三种操作掺杂在一起,内存就out of control.

   找不到内存泄露的地方,就只能推测(说不好听点,就是用猜的),最后能够想到问题就是STLmap 没有及时的把memory 还给操作系统。可是,可是,全世界有无数的程序使用STLMAP ,为什么别人的程序没有碰到这种问题?!!


   眼看着就山穷水尽了,执着于当下是不够的,不能找到root cause ,那么必须有替代方案,否则,每三天重启一下机器,客户也不能答应啊!经过讨论,选了两条路:

  1. memory pool 的方式,在map 里面保存指针,new 出来的对象都从memory pool 里面获取,这样至少可以控制住new 出来对象的内存;
  2. 使用opensourcememory db

   但是阅读了一下memory DB 的代码,发现这么做程序代码改动太大了,因为已经上线的系统,原则只有一个——尽量少的改动。所以实际上只剩一种选择—— memory pool

  

   既然决定了,那么就开始动手修改吧。cc 开始添加memory pool ,另外还多加了一个修改,因为map 里面的key 使用的电话号码,之前用的是string 类型,为了节省内存,加上电话号码的特殊性,全部改成 long 型。写代码的过程中,memory pool 使用了STL list 。改动做完,在测试环境程序依然会从刚启动的2.1G ,攀升到最高的6.8G6.8G 啊,按照以前的经验,性能一旦开始出问题,就可能急剧恶化,完全失去控制。这时一个比较有经验的同事建议在memory pool 里面使用STL deque 。就这一个改动,程序终于可以运行在6G 以下了!!!神奇啊,STL 真是博大精深。


    

    cc 改完,做了两轮完整的功能测试,以及长达7 天的性能测试之后,终于在过年之前上线了。到目前为止,运行良好。


   就在cc 代码改进的过程中,我们竟然又有想到可以改进的空间。其实现在想来倒也不难,既然我们认为可能是map 的内存回收出现问题,那么有两种选择,第一,不用map ;第二,改进map 的回收算法。所以,就有了下面两种方法。

  1. 使 用map 是为了查找的时候更快。那么有什么可以替换的方式呢?在我们这个应用中,有一个比较特别的点,就是电话号码可以用long 型表示,那么,可以选择 使用一维数组的方式来替换map ,而电话号码的后半部分(除去前四位)可以作为数组的下标。按照号码的长度就能够计算出来每次new subCache 的时候会用到的内存,并且可以自己控制释放。(^_^ ,说说到这个想法,居然是有一天在我下班回家的路上,突然蹦到我的脑袋里面的,我还是有点小得意的。)
  2. 改进map 的回收算法。这个是cc 研究侯捷的《STL 源码剖析》之后找到的想法。既然目前使用起来的对我们的应用而言,看起来不够合理,那么可以重写之。

   新的版本上线之后,cc 也根据上面的两种方法修改了程序,事实证明这两种方式下,内存是可以稳定的。虽然这两个版本可能永远不会上线,但是在这个过程中,我们的思维得到了极大的开拓,心理承受能力也飞速增长。


   事实再一次证明,所有会上线,并且需要7*24 小时运行的程序,就没有简单的,功能之外,性能是非常重要的。为了这个项目真是,衣带渐宽终不悔,为伊消得人憔悴哇。


 

  • 0
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 61
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 61
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值