3月 252012
 

固定链接:http://www.gamtin.info/archives/235

这是最近以来非常忙的根源之一。

到现在为止,似乎即将有一个结果了,于是简单整理一下。

项目背景

简单介绍一下现在这个项目。

这是一个基于.NET Framework 2.0的SmartClient项目,当然这样说是为了好听。实际上就是一个C/S的东西,通讯靠的是WebService协议,将传输数据序列化为XML格式传来传去。客户端程序靠微软的ClickOnce来发布,还算方便。后面保存数据的是Oracle 10g。顺带一提,对DB的CRUD操作,基本都是Stored Procedure完成的。

问题

某客户积累了N年的数据,业务表达到了100万条,关联的Master表数据差不多30万条左右吧。某机能的任务就是按照客户要求收集这些数据,并且进行一些出报表或者整体更新的任务。

实际操作中,数据收集(结果量50万左右)会消耗2小时以上的时间,因为设置了2小时的Timeout,所以实际多少时间不详。改到结果20万左右的时候,2小时以内倒是可以结束,但是不时的在Client端发生OutOfMemoryException。为此,客户一直在抱怨,我们的压力很大。

分析/解决

表面现象是,大量数据的情况下性能非常差劲。分析下来,突破点有三:

  1. Orcale方面:优化SQL
  2. 程序方面:优化业务逻辑算法
  3. 传输方面:提高数据传输效率

逐条分别来说。

Oracle方面

在DB表结构已经存在,并且已经运行多年的情况下,如果想改变设计是非常不现实的。而且,现在来看,问题似乎并不是表结构设计有多么的不合理。业务数据表主要就是一个,虽然需要连接的Master表不少,且数据量也不小,但是都是主键之间的连接,这种小事对Oracle来说应该不会有太大的问题。

然后分析SQL文。这时候,做外包的短板就出现了,虽然接触了好多年的Oracle,但是对于性能上的考虑完全没有过。被逼到没办法,赶快上网学习怎样研究分析Oracle的执行计划。传统观念认为(包括网上的不少类似“Oracle性能优化经验XX条”),有子查询会降低性能,但是实际的计划看来,子查询对性能的影响其实是挺小的(注意,这里是有条件的,某些子查询的写法似乎对查询效率影响不大),Oracle引进的CBO优化器的效果果然很牛逼。折腾了2、3天,发现SQL完全没有优化的可能。

实际的执行来看,用本地的有5万条结果的DB,用不到5分钟便可以查询完成并且返回到客户端了,这种性能其实已经不错了。

程序方面

分析业务逻辑算法,这其实是相对最简单的了。

检查发现,客户端表示这些数据的性能非常差。还是上面的5万条数据,处理后,主画面实际上只需要表示1500条左右(其他数据会在几个子画面表示)。但就是这个处理,竟然消耗了20分钟!!

一看代码,才发现,这是个“金矿”啊。当时的程序员,将这数据Copy了2份,然后用3层循环来不停的遍历这些可怜的数据。整个处理下来,每条数据被访问了n^3次。于是果断的改掉,成功的将20分钟的处理降低到1分钟左右。

这个问题,正好可以用来以后做面试用:

假设DataTable中有5列(Columun_1~Column_5),现在想得到这样的结果,你怎么来实现?如果数据量非常大,怎么考虑性能?

在这个DataTable后面增加一列,Count(int),用来保存Column_1,Column_2结果相同的数据的个数。

例:

Column_1 Column_2 Column_3 Column_4 Column_5
1 1 1 1 1
1 1 2 2 2
1 1 3 2 2
1 2 1 1 1
1 2 2 2 2
1 1 4 4 4
2 1 1 1 1
2 1 2 2 2

结果:

Column_1 Column_2 Column_3 Column_4 Column_5 Count
1 1 1 1 1 4
1 1 2 2 2 4
1 1 3 2 2 4
1 2 1 1 1 2
1 2 2 2 2 2
1 1 4 4 4 4
2 1 1 1 1 2
2 1 2 2 2 2

其实是挺简单的算法,但是似乎对一些外包的程序员来说,有点难度的。

实际上,在DataTable中,用循环是性能很差的。如果只要取得Count之类的,还是用Select().Length/View.Count效率来得更好。

传输方面

大量的数据,不仅仅是运算的成本高,传输的成本也不低。实际测试来看,5万条数据的大小近50MB,这些数据在网络上传来传去的效率是不能被无视的。而且还发生过客户端接受数据的时候出现OutOfMemoryException。

于是有了这几种方案:

  1. 多次传送方案之一:多次从DB取得数据,类似分页。
    这个问题,头两天在Twitter上咨询过,因为结果是需要Orderby的,且排序的列包括了没有索引的列,所以性能会更差。一次整体取得4分钟不到,分开取得(每次1000条)的话,会直奔20分钟去了。此方案否决!
  2. 多次传送方案之二:单次DB取得,多次Client和WebService通讯。
    因为是基于WebSercie,就是单纯的请求(Request)/响应(Response)模式,一次请求结束后,该线程便被回收了,无法保存数据。且项目的框架已经固定,没法实现类似轮询功能(让WebService继续保持),再加上时间紧,没法(也不敢)大规模的改造项目框架。便想到,将数据以文件形式保存到Server侧,直到全部发送完毕再删除。虽然增加了些IO操作,但是就现在的硬件性能而言,这不到100MB的文件实在是小菜一碟。
    这个方案的问题也是明显的,这么多次的操作,如果某次出现错误,或者客户端异常了,搞不好这个文件就作为垃圾永远的留在Server上了,这是个无法接受的现象。所以,这个方案也基本上被否决了。
  3. 数据压缩
    现在的序列化方法是都转成Base64格式,经验来看,转成Base64本身就会让数据量增加。本来数据量就不小,还加,压力更大了。于是增加了压缩和解压缩的逻辑。实际测试来看,压缩后的数据量大概是原来的14%,可以达到预期,似乎是个比较完美的解决方案。
    但是 ,问题也是有的,虽然减轻了网络压力,但是增加了服务器/客户端的内存和CPU压力。这时候就是个平衡的问题了,相对于增加网络带宽,似乎换个性能更好的机器的成本更低吧。
    到这里,想到了西乔的那个漫画:猛击这里

最后

到这里,基本就基本结束了。本来想简单介绍了,但也写下了不少。

折腾这段时间下来,感觉做外包的,视野确实窄,接触的东西也太少。

3月 122012
 

固定链接:http://www.gamtin.info/archives/232

转眼间,整整4周了,独自在大阪的生活也基本上满一个月了。本来想的挺好的,每天都写点,但是非常不现实,能保证每周来一下就不错了。

OK,不说没用的,换点别的没用的说。简单总结下这4周,个别内容找时间专门再说。

1、忙。平时周一到周五平均都是夜里10点以后才下班,还有两天赶的终电,连跑带冲的到车站,才避免露宿街头之苦。习惯是挺可怕的,习惯这个时间走了,突然改变会很不习惯。上周某一天8点多跑路,一路上各种负罪感啊。

2、冷。来之前,想着这边暖和啊,于是全部春装就过来了,结果现实无情的鞭笞了我一下。经过了整整一周的上下牙打架之后,周末跑去买了件大衣,算是基本复活了。顺便一提,日本人(特别是女人)的耐寒能力真强,这么冷得天,一个个的还光腿 or 丝袜出门。

3、赶上一次地铁事故,某站着火了,结果全线停运。换地铁别的线,再转JR,好大一圈才到公司,结果迟到50分钟。和同事一说,大家普遍表示好难遇到一次,我中奖了。

4、去了趟京都。不过有点遗憾,似乎冬天还没过去,好多寺院都在装修,闭门不开。于是简单走了一圈就回来了。网上查了下,3月应该有庆典,到时候去凑个热闹。

5、今年似乎普遍偏冷,樱花开发还有一段时间。大阪城公园的梅花倒是开放了。跑去一看,人和花基本一样多……住在非常繁华的心斋桥商业街附近,却非常喜欢大阪城公园附近的这种清闲敢。午后,挽着爱人,牵着狗,无聊上一下午。

6、最近在纠结要不要去趟东京。想去是因为好几个朋友在那边,不想去一个是成本过高,另外就是除了见他们,也没别的事干了。继续纠结吧。

7、买了啥?最大的消费是一PSP,同时有了人生的第一个正版TV Game游戏,全部2万2千日元多。第二大花费是地铁的月票,月给9110日元。第三名是上面提到的大衣,8000多。加班多的好处,就是省钱啊。

8、吃的?大阪有日本厨房之称,美食很多。不过这月来,还真没吃到什么好东西。平时午饭公司附近搞定,晚上全部便利店,偶尔早了,去吃个牛肉饭算是改善伙食了。有特色的,就算是有名的章鱼烧了,为了吃它,真是排了好长的队啊。吃完然后,去旁边继续排队等着吃那一蘭拉面。为了这两样,排队就差不多1小时吧。

这周起,感觉应该不会那么忙了。要让我的生活增添的色彩了。