victor_armin
作者victor_armin·2012-09-20 08:41
其它·BJ-FANUC

[实战]挑战一次性加载5W条数据,如何提高xGrid的性能(一)

字数 9377阅读 2697评论 2赞 4
首先,写最前面,本实战所有优化方案来自于国外一位大神的思路,Victor只将这些思路学习消化后分享给大家,请让我们向原作者致敬。

经过大家之前的投票内容,新的实战内容将从如何提高一个xGrid的数据读取速度来向大家介绍和分析XPages从设计和code反面的性能优化方法,正如在投票帖里大家看到的那样,经过一系列的优化后,我们可以把性能提高近20倍!下面开始我们的实战内容,首先非常推荐大家把之前的实战内容先了解和熟悉一下,否则在还没有明白xGrid是什么的情况下,Victor觉得你很难真正明白需要提升它性能的价值,已经之所以我们的优化方法能够提升性能的原因,OK,传送门在XPages版的置顶帖里有http://www.lotuschina.net/club/thread-3016-1-1.html,而且我以后也会将一些有价值的内容,收集,分类到置顶帖里,不仅仅是Victor自己的发布的内容,也包括XPages版其他朋友发布的有价值内容!

另外,下载xGrid的话,链接在这里:http://www.openntf.org/internal/home.nsf/releases.xsp?action=openDocument&name=xGrid&documentId=7FF63A251F6AFAF486257A0C006B7D38

本实战比较短,我们分为两次,在实战结束后,依然会提供实际的ntf供大家下载,大家再旧的xGrid的基础上刷新一下就可以获得这个性能优化后的xGrid了。

在第一节的内容里,我们将做2次优化,使得性能提升大概15倍,在大数情况下这种优化已经可以满足我们大多的需求,但是如果你想精益求精的话,就请再关注下一节的内容,虽然下一节的内容相对实现方式有一点小难,而且只能再提升2倍的速度而已,但是10*2=30倍,而且如果你追求完美的话,下一节同样不容错过。

现在开始我们本节的实际内容:

首先确认一个事实,我们将改善xContactsSSJS.xsp的读取数据性能,而xContactsSSJS.xsp获取数据的核心代码在xJsonContacts.xsp中,为了便于我们确认是否性能获得了优化,所以一上来我们可以在xJsonContacts.xsp的获取代码部分的开头和结尾各增加用来计算耗时的代码,改造后的代码如下:
  1. try{
  2.         var start = new Date().getTime();
  3.         var externalContext = facesContext.getExternalContext();
  4.         var writer = facesContext.getResponseWriter();
  5.         var response = externalContext.getResponse();

  6.         // Set content type
  7.         response.setContentType("application/json");
  8.         response.setHeader("Cache-Control", "no-cache");

  9.         // Get all Contacts
  10.         var query = 'Form = "Contact"';

  11.         var docs:NotesDocumentCollection = database.search(query);

  12.         json = "";
  13.         var doc = docs.getFirstDocument()

  14.         while (doc != null) {
  15.                 json = json + '{"@unid":"'+ doc.getUniversalID() + '","FirstName":"' + doc.getItemValueString("FirstName") +
  16.                  '","LastName":"' + doc.getItemValueString("FirstName") +  '","State":"' + doc.getItemValueString("State") +
  17.                  '","City":"' + doc.getItemValueString("City") + '"},'                

  18.                 // Get next doc and recycle
  19.                 tempdoc = docs.getNextDocument();
  20.                 doc.recycle();
  21.                 doc = tempdoc;
  22.         }

  23.         json  = "[" + @Left(json, @Length(json) - 1) + "]";        

  24.         writer.write(json);
  25.         writer.endDocument();
  26.         var elapsed = new Date().getTime() - start;
  27.         print("xContactsSSJS.xsp ->" + elapsed +" ms");

  28. } catch(e){
  29.         _dump(e);
  30. }
复制代码



好了,现在我们连续10次打开xContactsSSJS.xsp,并从Server Console中看看耗时,你应该可以看到大概如下图的结果,如果耗时不同的话,证明你的服务器到底有没有Victor的快,这个无关紧要,虽然大家的服务器性能有差异,但是我们的代码最终优化的倍数是相同的。

1.jpg 


这是加载200条数据的速度,做一个简单的换算,如果是加载5W条的话,需要乘以250倍,那以Victor的服务器为参考的话,最理想的状况下需要多长时间?至少需要50000ms,等于是50秒!

那么开始我们的优化吧,首先XPages版的qq同学已经道出了经典的Domino性能优化方式,通过减少视图的列数为一列,并将所需要的数据都浓缩到这一列中,可以大大对我们的性能产生优化结果,不过和Victor不同的地方在于,qq提出在文档保存时计算出一个域值,并在视图列中显示这个域值;而Victor的方式,是在视图列中写入公式,从而获得同样的值,删除所有多余的列,只剩下第一列,然后将第一列的公式改为:
  1. _fld:="FirstName":"LastName":"State":"City";

  2. "{"@unid":""
  3. +@Text(@DocumentUniqueID)+"","
  4. + @Implode (
  5. @Transform (
  6. _fld; "_fn" ; """ + _fn + "":"" + @Text ( @GetField ( _fn) ) + """ ) ; "," ) + "},"
复制代码



根据Victor的经验来说,qq的方法对性能的提示应该更大,因为qq的方法中计算是在文档保存时进行的,视图不会再进行计算过程,一次性能理论上更好,但具体的差异我没有进行实验,但Victor的这个方法也有一定的好处,这就是当列值的公式需要变化时,我们不需要将所有已经存在的文档都重新计算保存一次,而是直接改视图的列公式即可,这方面的选择,完全可以根据大家的需要。

然后我们还需要将xJsonContacts.xsp中的代码改为:
  1. try{
  2.         var start = new Date().getTime();
  3.         var externalContext = facesContext.getExternalContext();
  4.         var writer = facesContext.getResponseWriter();
  5.         var response = externalContext.getResponse();

  6.         // Set content type
  7.         response.setContentType("application/json");
  8.         response.setHeader("Cache-Control", "no-cache");
  9.         json = ""
  10.           var v:NotesView = database.getView("ContactsSingleCol");
  11.           //do not do AutoUpdates
  12.           v.AutoUpdate = false;
  13.           var nav:NotesViewNavigator = v.createViewNav();
  14.           nav.setEntryOptions(
  15.           NotesViewNavigator.VN_ENTRYOPT_NOCOUNTDATA);
  16.           //enable cache for max buffering
  17.           nav.BufferMaxEntries = 400
  18.           var entry:NotesViewEntry = nav.getFirst();

  19.           while (entry != null) {
  20.             json=json + entry.getColumnValues().elementAt(0).toString();
  21.             var tmpentry:NotesViewEntry = nav.getNext(entry);
  22.             entry.recycle();
  23.             entry = tmpentry;
  24.           }

  25.           writer.write('[' + @Left(json, @Length(json) - 1) + ']');
  26.         writer.endDocument();
  27.         var elapsed = new Date().getTime() - start;
  28. print("xContactsSSJSViewNav.xsp ->" + elapsed +" ms");

  29. } catch(e){
  30.         _dump(e);
  31. }
复制代码



然后,同样运行10次,我们看看初步的效果:
2.jpg 


太棒了!是不是?通过这两步简单的改造,我们的性能直接提升了2倍还多!这不改造的关键在于两点:首先优化了视图的设计,将视图列缩减为了一列;其次,使用NotesViewNavigator替代NotesDocumentCollection方式,加速获取数据的速度,获得Document总是比获取NotesViewEntry要慢的,这个到底在XPages的开发中还是适用。

但是,如果还是加载5W条数据的话,还是需要25秒的,用户应该还是接受不了,是不是?那我们就再优化一下,代码如下:
  1. try{
  2.         var start = new Date().getTime();
  3.         var externalContext = facesContext.getExternalContext();
  4.         var writer = facesContext.getResponseWriter();
  5.         var response = externalContext.getResponse();

  6.         // Set content type
  7.         response.setContentType("application/json");
  8.         response.setHeader("Cache-Control", "no-cache");

  9.           var json:java.lang.StringBuilder = new java.lang.StringBuilder();
  10.           var v:NotesView = database.getView("ContactsSingleCol");
  11.           //do not do AutoUpdates
  12.           v.AutoUpdate = false;
  13.           var nav:NotesViewNavigator = v.createViewNav();
  14.           nav.setEntryOptions(
  15.           NotesViewNavigator.VN_ENTRYOPT_NOCOUNTDATA);
  16.           //enable cache for max buffering
  17.           nav.BufferMaxEntries = 400
  18.           var entry:NotesViewEntry = nav.getFirst();

  19.           while (entry != null) {
  20.             json.append( entry.getColumnValues().elementAt(0).toString());
  21.             var tmpentry:NotesViewEntry = nav.getNext(entry);
  22.             entry.recycle();
  23.             entry = tmpentry;
  24.           }

  25.   writer.write('[' + @Left(json.toString(), @Length(json.toString()) - 1) + ']');
  26.         writer.endDocument();
  27.         var elapsed = new Date().getTime() - start;
  28. print("xContactsSSJSViewNavSb.xsp ->" + elapsed +" ms");

  29. } catch(e){
  30.         _dump(e);
  31. }
复制代码



如果说第一步的优化方案,很多Domino的开发人员都知道的话,那这第二步的优化方式应该知道的人数就减少了,事实上,在拼串的时候,我们的代码没进行一次“加”的操作,也就是拼串时候的“+”符号,就意味着一个新的String被创建了,关键在于这种创建是需要时间的,而且随着串越长,这种创建的时间就越长,这真的是一个不好的拼串方式,所以很多人眼中看起来天经地义的拼串方式,其实在XPages的开发中却是一个很错误的方式,那更好的方式是什么呢?正如上面的代码所示,就是利用java的一个特性:java.lang.StringBuilder(),你可能会觉得这种提升应该是不明显的,那我们看看结果:
3.jpg 

是不是完全出乎你的意料?这个看起来不大的改动,确将10次平均的获取速度提升了近5倍,因此千万不要忘记,在XPages中你可以在SSJS里使用Core Java,很多适用于Java的性能优化方式,同样适用于XPages的代码,那么现在我们再算算,是的,真不错,看起来好像加载5W条数据只需要大概5秒左右了,尽管这是在最理想的状态下,但是我相信这种加载速度已经超过了大家对Domino性能的预期认识了,但等等,Victor还有进一步的一个优化方式,尽管它做起来比前两步都更麻烦一些,但是它还能在目前已经很不错的性能基础上再提升一倍的速度,这还是值得的,而且和第二步的优化方式相比,进一步的优化方式可以说知道的人更少,因此如果你想成为这少数派的话,那就继续关注我们的一下节实战吧。
4.jpg

如果觉得我的文章对您有用,请点赞。您的支持将鼓励我继续创作!

4

添加新评论2 条评论

wangxiao12371wangxiao12371软件开发工程师HD
2013-09-05 21:11
收藏了,3Q

2013-01-12 20:13
很棒,收藏了。辛苦了,楼主。
Ctrl+Enter 发表

作者其他文章

相关问题

相关资料

X社区推广