这几天认真翻了一下Inside Dynamics AX4.0,第八章以后的几章内容都很吸引人,其中第十七章给我留下的印象最深刻,因为平时写程序的时候往往更重视功能的实现,性能问题经常被忽略,也没有看到指导如何在AX如何写高效率代码的指导文件,这是第一次看到类似的文章。本来想写写读书笔记,在看了几遍后发现,作者的文笔已经够精简的了,任何的缩减都可能违背作者的原意,加上这章写的内容实在太喜欢了,于是决定认真逐字翻译一下。
由于不太确定在blog上翻译原文是否会侵犯原作者的版权,所以看到这篇翻译文字的哥们,请不要转载(当然应该也没人有兴趣转载,不过为了免责还是有模有样地声明一下,呵呵),如果有哥们觉得我侵犯了版权,我将用最快的速度撤下这篇文章。
另外由于只能下班时间翻译,时间有限加上俺对AX的理解很浅和鸟语水平实在抱歉,如果翻译的有所纰漏,还请看到这篇文章的哥们一起讨论,能得到您的点拨,不胜感激。
本章内容:
简介 427
客户端/服务器性能 428
事务性能 432
(译注:原文是Transaction Performance,我觉得无论翻译成交易还是事务都没有办法概括文中的内容,实际的内容是服务器如何更好地跟数据库服务器交互,如何利用缓存提高性能等,这里姑且这么翻译)
Dynamics AX 监测工具 457
本章总结 461
本章目标:
1.描述在三层环境下如何控制逻辑的执行和在设计和实现X++代码时需要注意的地方.
2.解释如何优化数据库性能,如何通过批操作和缓存,限制锁的应用和优化select语句来减少数据库交互.
3.介绍在Dynamics AX4.0开发环境下用于监测客户端/服务器调用,数据库调用和代码执行的工具.
简介
性能往往是在事后才会被考虑.很多开发团队在开发进入后期之前很少注意性能,更有甚者,在用户在正式环境中报告性能问题后才注意.在一个功能实现后,做哪怕一点点的性能提升都是一件很困难的事.但是如果知道如何利用AX中的性能优化特性,就可以做出在AX开发和运行环境中具有良好性能的设计.
在本章中,将介绍开发在三层环境下运行的功能需要注意的地方,在三层环境下X++代码既可以运行在服务器端也可以运行在客户端.也会介绍性能增强的其他特性,比如用于数据库交互的批量更新,缓存,可以在元数据中设定也可以通过X++代码设定,以及通过乐观并发控制来限制数据库的锁定.最后通过介绍几个DynamicsAX的性能监测工具结束本章,这些工具为监测客户端/服务器调用,数据库活动和X++代码的执行提供了一个可靠的基础(译注:foundation在这里翻译成基础有些别扭......).
客户端/服务器性能
正如在第一章"架构概览"中描述的那样,DynamicsAX是一个三层客户端/服务器应用.DynamicsAX应用运行时支持应用运行逻辑执行在客户端或者服务器上.
注释 DynamicsAX应用运行时不支持应用逻辑执行在数据库层(译注:实际上有很多系统把业务层完全用存储过程实现,当然这样业务逻辑的执行都放在了数据库层)
默认情况下,所有的X++代码被定义为"called from",意思是方法运行在调用发生的地方.类的实例方法将会运行在对象被实例化的层.
对于很依赖于数据库(译注:原文是database-intensive)的应用逻辑应该尽可能地运行在距离数据库近的地方,当然就是服务器层了。对于要跟用户发生交互的应用逻辑应该尽可能地运行在距离用户近的地方,那就是客户端层了。这降低了客户端与服务器的往返,这些往返涉及到在两个层之间发送数据包,这通常发生在有一定带宽和延时的网络中,影响性能。同时,客户端和服务器的通信是同步的,这意味着客户端要等待服务器执行完客户端对服务器的调用(译注:好像有些别扭,意思就是客户端请求服务器执行某段逻辑,由于是同步的,客户端必须等待服务器执行完这段代码),反之亦然。
注释 对于那些不涉及与用户和数据库交互但强烈依赖于CPU(译注:原文是CPU-intensive)的应用逻辑是允许它们运行在客户端还是永远交给服务器去做是颇费争议的意见事情。客户端在通常情况下只为一个用户和他(或者她)的应用服务,服务器端通常要处理多个用户的请求。但是服务器比客户端的性能好很多,所以在开发阶段很难决定在运行的时候哪一层有最多的资源运行逻辑。在最新的DynamicsAX版本,降低客户端/服务器的通信量是开发的目标,但是把强烈依赖于CPU的应用逻辑的执行移到特定的层并为作为一个目标去实现。
控制逻辑的执行
DynamicsAX应用运行时通过设置元数据或者用X++代码指定方法签名的方式控制应用逻辑执行的地方,client和server方法修饰符可以应用在表的实例和静态方法和类的静态方法。当应用client修饰符时,方法将会永远在客户端执行,当应用server修饰符时,方法将会永远运行在服务器上。如果调用方和被调用方法(译注:原文是the calling method,字面意思应该是调用方法,不过按我的理解应该是调用方和被调用方法,或许是我的理解有偏差,暂时这么翻译)不在同一层,应用逻辑将会在被调用方法(译注:同前)所制定的层上运行,调用层等待被调用方法(译注:同前)退出或者被调用层调用一个方法使其强行返回。
类实例方法的运行层由类的Runon属性控制.该属性可以被设为called from,server,或者client.如果该属性被设为called from,该类型的对象在实例化层(译注:就是实例化动作发生的地方,换句话说就是new方法在哪个层被调用,哪个层就是实例化层)被实例化,所有的实例方法也执行在该层。如果该属性被指定为server或者client,这个对象被就在指定的层被实力化(译注:就是不管new方法在哪个层被调用,实例化的动作总是发生在RunOn属性指定的层,比如该类的new方法在client被调用,但RunOn属性指定了server,那么该对象的实例化发生在server,这一点可以通过在new方法体内调用Global::isRunningOnServer这个方法去判断).只有在基类的RunOn属性设为Called from时,派生类RunOn属性才可以修改.如果基类的RunOn属性设为了Client或者Server,派生类继承其设定。
默认情况下,如果没有指定server或者client修饰符表的实例和静态方法按"called from"执行.可以给一个方法同时指定client和server两个修饰符,但是这样做并不影响方法在哪里执行;只是表示这个方法的开发者已经评估过这个方法并且决定这个方法应该按照"Called from"执行而不应该是一定运行在客户端或者服务器上(译注:原文是client bound or server bound).
注释 默认情况下,表的insert,update,和delete 方法总是运行在服务器端,即使在方法定义的时候没有标示.这些方法不能强行运行在客户端,因为client修饰符会被DynamicsAX应用运行时忽略掉(译注:实际上,如果试图在这几个方法添加client修饰符在编译的时候会报错,"错误: 140,不能对数据访问方法使用客户端修饰符")
如果在类的静态方法上没有指定client或者server修饰符,这些方法会在类的RunOn属性指定的层上运行。可以同时在方法上指定client和server修饰符,这样会强制方法以"called from"的方式运行而忽略class上指定的属性值。
注释 可以给类的实例方法和窗体报表的方法制定client和server修饰符,但是应用运行时会忽略它们(译注:实际上如果试图在上述方法中指定client和server修饰符的时候,编译时会报错"错误: 50,Client 和 Server 修饰符只能用于静态方法。").
当类通过菜单项调用时,也可以在菜单项上指定该类运行在服务器端,客户端还是调用层.这是因为菜单项上也有RunOn属性,可以设定的值跟类属性相同(译注:called from,client,server),这相当于在类上设定,当然菜单项不能覆盖类的设定(译注:只有当菜单项的RunOn属性设为Called from的时候才会按照在菜单项上指定的属性运行).
优化 客户端/服务器调用
在开发类和表方法的时候,需要考虑类是在客户端和服务器上都可以实例化还是只能在其中一层实例化.这个决定取决于在方法中实现的应用逻辑.在开发类的时候,也要考虑应用程序接口(API)(译注:即写的类要被其他地方调用,则该类可以称为API),特别是开发一个只运行在服务器端的类且该类被运行在客户端的应用逻辑调用时。
考虑下面的X++例子,服务器端绑定(译注:server-bound,前面都翻译成了只运行在服务器端,这边用服务器端绑定来表示,实际的意思就是只运行在服务器端)的类ServerClass被实例化,然后给该对象传递两个变量,返回结果.这段X++代码至少在客户端和服务器端往返了五次:一次是在实例化类ServerClass的时候,两次发生在传递参数的时候,一次在得到结果的时候,最后一次在ServerClass对象引用超出作用域时。最后对象将在服务器端被回收.
static void CallingServerClass(Args _args)
{
ServerClass serverClass;
int result;
;
serverClass = new ServerClass(); //call to server
serverClass.parmVariable1(20); //call to server
serverClass.parmVariable2(30); //call to server
result = serverClass.result(); //call to server
//Destory object - call to server
}
可以把调用parm方法和调用result方法的操作合并成一个方法从而减少调用的次数,代码如下:{
ServerClass serverClass;
int result;
;
serverClass = new ServerClass(); //call to server
serverClass.parmVariable1(20); //call to server
serverClass.parmVariable2(30); //call to server
result = serverClass.result(); //call to server
//Destory object - call to server
}
static void CallingServerClass(Args _args)
{
ServerClass serverClass;
int result;
;
serverClass = new ServerClass(); //call to server
result = serverClass.result(20,30); //call to server
//Destory object - call to server
}
这种做法减少了两次对服务器的调用,总的调用次数减少为三次.优化的解决方法是不在客户端实例化服务器端绑定类,而是调用该服务器端绑定类的静态方法,代码如下所示:{
ServerClass serverClass;
int result;
;
serverClass = new ServerClass(); //call to server
result = serverClass.result(20,30); //call to server
//Destory object - call to server
}
static void CallingServerClass(Args _args)
{
int result;
;
result = ServerClass::calcResult(20,30); //Single call to server
}
同样的优化可以考虑用在服务器端绑定的表方法上.如果客户端需要顺序调用多个服务器端绑定方法,单个的调用可以用一个服务器端绑定方法封装,然后再在服务器端调用其他方法.{
int result;
;
result = ServerClass::calcResult(20,30); //Single call to server
}
用引用和值传递参数
当客户端有一个指向服务器端对象的引用时,服务器会不时通过调用客户端的方式来查询客户端以断定客户端是否还包含指向该对象的引用.如果没有的话,服务器层将回收该对象.如果服务器层也包含指向客户端对象的引用,客户端会执行同样的查询.当然前面举的那个例子,当一个服务器端绑定的类在客户端实例化时会发生上述情况.如果一个对象在客户端实例化然后作为参数传递给一个服务器端绑定的方法时也会发生上述情况 。在这种情况下,对象的引用在两个层中都存在,这些引用指向同样对象,这导致了额外的客户端/服务器调用以便来决定这些对象是否可以被回收.
可以考虑用传值的方式来取代在层之间通过引用传递对象.就是说值被传递到另一层,然后用这些值实例化一个同样类型的对象.这样两个不同的彼此没有关联的对象存在于两个层,于是就没有必要在层之间追踪对象的引用了.
注释 RunBase框架自动支持按值传递.RunBase框架把变量打包进一个container,把Container传递给另一个层,然后把Container解包,根据Container里的变量实例化一个新的对象.
报表和窗体
DynamicsAX报表可以在客户端或者服务器上执行.在哪里执行是由打开报表的菜单项定义的.如果报表在服务器上执行但是在客户端显示,报表的页面是在服务器上产生然后发送到客户端.如果报表在客户端执行 ,客户端呈现和产生报表页(译注:原文是renders and generates,不过我觉得generates and render会不会好一些?先产生后呈现,不知道是不是俺对这两个单词的理解有误,呵呵).
胖窗体(译注:原文是Rich forms,Rich Client一般翻译成胖客户端,这个胖窗体还真难听......)总是在客户端执行的,这导致在获取和操作记录的时候增加了客户端/服务器的通讯量.另外,display方法会降低窗体的性能,因为窗体应用运行时在刷新窗体控件的时候都会调用display方法,一个服务器端绑定的display方法会造成大量的客户端/服务器调用,特别是如果display方法显示在grid中时,这时多个从display方法的返回值被同时使用.可以通过调用FormDataSource对象的cacheAddmethod方法缓存dispaly方法的值.窗体应用运行时会缓存这些返回值并且仅当记录被修改或者重新读取的时候刷新.