Wednesday, January 7, 2009

MQ配置和编程最佳实践

更多精彩请到 http://www.139ya.com


对于MQ的使用,主要会涉及到MQ系统本身的配置和MQ应用程序的开发两方面的工作。为了帮助大家更好地使用MQ,本文将就MQ配置和编程中的一些注意事项和技巧与大家探讨,并希望与大家分享这方面的一些最佳实践(Best Practice)。

第一部分:有关MQ对象配置的最佳实践

对于MQ系统配置,我们要规划MQ通讯网络,确定系统的拓扑结构,确定各种对象的属性和命名规则并创建所需的各种对象等,首先,我们谈一谈在系统建设之初,如何设计和定义MQ的各种对象。

1、有关队列管理器:

创建队列管理器时,应考虑的因素主要有:

1) 队列管理器的日志类型以及日志文件的大小和个数,要根据用户数据量的大小、各个队列上的消息总容量,来计算日志的总容量,以免在系统运行过程中出现日志写满的情况;

2) 应该为队列管理器指定和建立死信队列;

3)对最多打开句柄数MAXHANDS(缺省为256,如果您需要多于256个应用程序同时连接队列管理器,应增大该值),最大消息长度 MAXMSGL,最多的未提交的消息个数MAXUMSGS属性(缺省为10000,如果您使用了消息分段或分组,某个大消息的分段个数超过了10000,应增大该值)的考虑;

4) 创建完队列管理器之后,应修改队列管理器的配置文件,考虑有关TCP和通道有关的参数的配置,举例如下:



TCP:

KeepAlive=Yes

Channels:

AdoptNewMCA=ALL

PipeLineLength=2

MaxActiveChannels=200



2、有关队列:

对于队列的属性,应该考虑的因素主要有:

1) 永久性和非永久性设置:尤其要注意的是DEFPSIST属性的缺省值为No,若要保证消息的安全可靠,必须将其设置为Yes;

2) 对于本地队列和传输队列,要考虑队列的最大深度MAXDEPTH(缺省为5000,应根据实际情况计算该值),队列中每个消息的最大字节数MAXMSGL的配置。

3、有关通道:

对于通道的属性,应该考虑的因素主要有:

1) 确定通道的运行方式采用长连接的方式还是触发的方式,通常,对于需要频繁启动的通道,不适宜采用触发的方式。若采用触发方式启动通道,触发类型应为FIRST;

2) 对于发送类型的通道,要考虑通道的断开间隔(DISCINT)、短重试次数(SHORTRTY)、短重试间隔(SHORTTMR)、长重试次数(LONGRTY)、长重试间隔(LONGTMR)、批处理大小(BATCHSZ)的配置。
第二部分:有关MQ程序开发的最佳实践

通常大家在使用MQ时,一般在系统设计之初只考虑MQ的系统配置,而很少提前考虑应用程序编写的指导原则,往往是边写边想,边想边写,边写边改,使得开发效率和质量都比较低,为了使大家更快、更好地开发出MQ应用程序,在该部分我们给出与MQ程序开发相关的一些最佳实践,供大家参考。这里我们无法做到面面俱到,并且由于每个用户的具体需求不同,每条原则都不能一概而论,但是从普遍意义上可以作为您的参考。

为了更好地掌握和了解MQ编程的技巧,我们首先要熟悉MQ的消息通讯模式,这将帮助你更好地理解MQ编程的最佳实践。通常,MQ有两种通讯模式,即数据报 (Datagram) 方式和请求/应答(Request/Reply) 方式:其中,Datagram方式通常又被称为"Send And Forget"(发送/忽略),是最简单的通讯模式,应用程序只需在创建完消息之后,利用MQ的API将消息发送到队列中,它充分利用了MQ确保消息传输,并且传一次且仅传一次(once and once only)的优势,发送端应用程序无需关心消息何时被处理。

Request/Reply(请求/应答)方式相对复杂一些,在消息发出之后,你需要等待对方的处理结果,在这种情况下,你通常需要考虑其他一些问题,如:

等待应答的时间是多少?

如果没有收到应答,是否再次发出请求?

应答发出之前是否会有数据库操作或其他交易被执行?

本次请求/应答过程的会话(session)信息是否需要被保留?

通常,我们要根据用户的需求来决定采用何种通讯模式,不同的通讯模式之下对应用程序的考虑将会有所不同,使用的MQ API的参数和选项也将不同。

为了使你的MQ应用能够更加健壮,并且具有更强的可维护性,我们要学会灵活高效地使用MQ的一些特性以及相关的API选项,从而提到应用程序的质量、灵活性、可靠性和性能。这里,我们将给出一些较典型的建议。

1. 在每一个MQ API调用之后,必须检查完成码(completion code)和原因码(reason code),对于非零的返回码,必须进行相应的处理,必要时,最好将返回码记录错误日志,从而在应用程序出现运行故障时便于检查和处理。

在MQ 中,所有的API调用都会得到其完成码和原因码,完成码有MQCC_OK、MQCC_WARNING、MQCC_FAILED等,MQCC_OK表示调用成功,当得到MQCC_WARNING、MQCC_FAILED返回码时,表示API执行不完全成功,你需要进一步检查原因码,通过不同的原因码分析失败原因。如:原因码2033代表队列已空,没有消息可取;2080代表消息的实际长度超过了你在程序代码中设置的缓冲区长度等等。

2. 对MQCONN, MQOPEN,MQCLOSE, MQDISC的使用

由于MQCONN, MQOPEN,MQCLOSE, MQDISC相对于MQGET和MQPUT来说是比较消耗资源的几个函数,在一个应用中,即使你需要对某个队列进行多次读写操作,也不要对每一次读写都调用一次MQCONN, MQOPEN,MQCLOSE, MQDISC函数,正确的做法应该是,调用一次MQCONN, MQOPEN就可以对队列进行多次读写操作,另外,一定别忘记对称地使用MQCLOSE, MQDISC函数将相关资源释放掉。

3. 读取消息时,等待时间间隔的设置

对队列的读取操作,可以有两种方式,即轮巡方式和触发方式(利用MQ的触发功能动态调起应用程序)。在采用轮巡方式读取队列时,在MQGET时,我们需要设置消息读取选项MQGMO_WAIT,同时指定等待时间间隔。不少用户将时间间隔设定为MQWI_UNLIMITED以实现轮巡的目的,这样做的弊端在于在没有消息到达时应用程序陷入无限的等待,无法接收来自外部系统的相关信号,MQCLOSE,MQDISC调用也无法被执行,因此,我们建议避免采用这种方式,推荐的做法是设置特定的等待时间间隔,然后再循环发出MQGET调用。

4. 如果在同步点控制之下使用MQGET,在所有MQGET调用之后,必须检查消息的回滚次数(Backout Count)。

如果某个消息是在同步点控制之下读取的,并且由于某种原因消息被回滚,消息描述符中(Message Descriptor)的BackoutCount字段的值将被加1,你需要判断该数值,如果它大于某个阈值,你需要使用其它手段来处理该消息。否则,在某些情况下会导致读取消息-消息被回滚-再读取消息-消息再被回滚的死循环。

例如:你使用了触发机制设定当队列中消息到达时,触发某个应用程序,该应用程序在MQ XA Resource Manager的控制之下,读取消息,并且利用其数据对数据库进行更新,假设数据库出现问题,无法成功进行数据库操作,消息将被回滚;这时,又满足了触发条件,又会触发起该应用程序,周而复始,陷入死循环。在这种情况下,你必须在程序中加入对BackoutCount的判断。

5. 当处理backout消息时,可以使用队列的BOTHRESH 和 BOQNAME属性。

如4 中所言,如果某个消息是在同步点控制之下读取的,并且由于某种原因消息被回滚,消息描述符中的BackoutCount字段的值将被加1,你需要判断该数值,如果它大于某个阈值,你需要使用其它手段来处理该消息。在处理该消息的应用中,你可以将其与设定的阈值做比较,这时,阈值会被写死在程序中,为了提高其灵活性,你可以使用队列的BOTHRESH 和 BOQNAME属性。这样,你可以在例外处理中,利用MQINQ查询得到阈值的大小,如果超出,可以将消息转发到BOQNAME指定的队列中,继而对该队列进行相应的处理。这种方法大大增强了应用程序的灵活性。

6. 在使用MQOPEN, MQPUT 和 MQGET调用时,要使用FAIL_IF_ QUIESCING的选项。

MQ 系统本身和使用它提供的服务的用户应用程序之间是互相独立的,必要时,我们可能要停止MQ系统,这时,我们不但希望新的应用不能连接,并且希望所有已连接的应用能够立即停止。为了使所有的应用程序能够快速得知MQ系统正在停止的信号,在上述MQ API中,必须设置FAIL_IF_ QUIESCING的选项。

如果不设置FAIL_IF_ QUIESCING的选项,当MQ系统停止时,所有应用将继续运行,这样会影响MQ系统的停止,从而导致MQ停止需要很长时间,同时可能导致我们必须手工杀掉那些没有设置该选项的应用程序。

7. 消息描述符的不同字段的使用方法

在MQ 的消息描述符MQMD中包含了很多字段,这些字段大部分是一些保留字段,例如:MsgID表示消息的唯一标识,如果你不指定,MQ系统会为你自动产生一个,并保证其唯一性;PutAppType, PutAppName, PutDate, PutTime是系统自动产生的,表示哪个应用何时将消息发送到队列中;再如:GroupId, MsgSeqNumber, Offset, MsgFlags是与消息分段和消息分组相关的控制信息;除了这些系统自动产生无法更改的字段和有特殊用途的字段之外,如果您想选择某些字段为己所用,将其设定为自己应用程序中某个有意义的标识,你可以使用CorrelId和Feedback字段,但是,按照惯例,CorrelId常常被用于在请求/应答通讯模式中来表示请求消息和应答消息之间的关联;因此,我们可以灵活使用Feedback字段,利用该字段来进行一些应用程序控制。当我们接收到某个消息之后,我们可以检查Feedback字段,根据Feedback字段值进行相应的处理。

8. 消息永久性属性的确定

永久性消息保证了消息在系统和网络等故障下的安全可靠,但是同时从性能角度来讲会比非永久性消息要差,因此,要从不同的角度进行权衡和分析,然后决定消息的永久性属性。当对性能要求非常高,可靠性要求相对不高时,可以首先考虑采用非永久性消息,在消息决不允许有任何丢失,并且在丢失之后又无法重新发送时,要使用永久性消息。

9. 当指定消息的永久性和非永久性属性时,最好利用应用程序显式地指定,不要使用"defined as queue"的方法来指定。

消息的永久性和非永久性是消息本身的属性,多数情况下,只有消息的原始发出者才了解丢失消息将产生的重大影响,因此,消息的原始发出者应在应用程序中显式地指定消息的永久性,如果将其定义为依赖于队列的该属性,就会比较被动,当队列的永久性属性(DEPSIST)被意外地设为NO时,就会有丢失的风险。

10. 应用程序可以将请求消息的永久性属性为No,即对于请求消息使用非永久性消息。

一般情况下,请求消息丢失对应用系统不会产生严重的影响,如果出现请求消息丢失的情况,我们可以重新发送,因此,不必将请求消息设置为永久性消息。把请求消息设置为非永久性消息的另外一个好处是,系统不需要对非永久性消息记录日志,从而减少I/O操作,提高系统的性能。

11. 在异种操作系统平台上使用MQ传输消息时,将消息格式设置为MQFMT_STRING。

MQ 的一大优势之一,是对built-in(内置的)消息格式,可以实现不同操作系统平台间、不同系统字符集之间的数据转换,如开发平台ASCII码和主机 EBCDIC码之间的转换。为了实现该数据转换,MQ必须获知本身和对方MQ系统的队列管理器的CCSID和Encoding以及消息的格式。一般而言, CCSID和Encoding会被自动设置和处理,不需要应用程序关心,但是,消息的格式必须由应用程序指定,对于MQ内置支持的消息格式,MQ可以自动转换,这些消息格式由MQFMT_*来指定。鉴于应用程序数据都可以用MQFMT_STRING来表示,并且MQFMT_STRING是MQ内置支持可以转换的格式之一,你可以使用它来表示你的消息格式,同时,对于数字型消息,你需要使用atoi, itoa等函数实现数字型和字符型之间的转换。

12. 对大消息的处理

MQ 支持单条消息的最大长度为100M,队列管理器、队列、通道支持的最大消息的缺省值为4M,即使如此,我们却应该根据不同的网络类型和带宽,具体地确定不同情况下单条消息的合适大小。例如:如果在拨号网络或网络带宽较窄的情况下,我们将单条消息的大小设置得太大,就会影响传输效率,在这种情况下,一定要使用MQ的分段功能将消息进行分段处理,确保每一个物理消息的大小适当,MQ会自动维护整个逻辑消息的完整性,并且可以在接收端一次性将其取出。

13. 如何使用MQ的请求/应答通讯模式来处理同步的消息处理模式。

大家知道,MQ的异步处理模式是非常强健的,同时它也支持同步的消息处理,例如MQ的client-server通讯就是一种典型的同步工作模式,对于 server-server通讯,我们也可以实现同步工作模式,这里就涉及到如何巧妙地使用MQ提供的消息生命周期的功能。这时,对于请求消息和应答消息我们最好为其设置生命周期。

典型的应用案例如下:假设系统A向系统B发出请求,调用B上的某个交易,这里,我们首先要设置请求消息的生命周期,并且在消息到期时将消息丢弃,如果在消息发出之前消息过期,它就会在进入通道之前,被MQ系统丢弃;如果当消息到达目的地之后,在被对方应用程序取走之前消息过期,它也将被MQ系统丢弃。系统B上的交易便不会被调起执行。对于应答消息,我们也设置它的生命周期,与请求消息不同的是,我们设置在消息到期时将其转发到另外一个特定的队列中,这时,如果系统B上的交易执行完之后,会产生应答消息,如果由于通讯等原因,该应答消息在到达系统A时应用程序设置的Timeout时间已经超出,应用程序必然认为系统B上的交易没有被执行,也不会处理该应答消息,这样,应答消息便会过期,当它过期时,根据我们的设置,它会自动被MQ系统转发到特定队列中,我们另设专门的应用程序对此进行冲正处理。

14. 对消息类型的设置。

通常情况下,我们不要求用户一定去设置消息的类型,设置消息类型的方法是在消息描述符MQMD的MsgType字段,消息类型有 datagram, request, reply, report等若干种。但是为了更好地对消息进行管理,我们必要时要设定消息类型,从而对不同的消息进行处理。例如,当reply消息和report消息都被发送到同一个应答队列时,我们可以根据消息类型的不同对其进行不同的处理。

15. 对消息转换的设置。

通常,当需要进行消息转换时,我们有两种方法来进行设置:一种方法是在调用MQGET时设置MQGMO_CONVERT的读取消息选项;第二种方法是将发送端通道的convert属性设置为yes。对比二者,我们推荐使用第一种方法,它的优势在于:它避免了消息通道代理程序对消息的转换,从而提高了通道的性能;其次,当一对多传输时,避免了对不同的目标系统使用不同的转换算法,而统一放在接收端进行。

当我们不需要进行格式转换时,使用在MQMD中将消息格式Format字段设置为MQFMT_NONE的方法来表示,MQFMT_NONE表示忽视任何格式转换。

16. 应用程序最好不要在MQGET和MQPUT调用时使用过大的消息缓冲区,从而减少队列管理器对内存的需求。

当应用程序发出MQGET和MQPUT调用时要设定用于装载消息的数据缓冲区大小,MQ系统将据此来分配内存,如果使用过大的消息缓冲区,队列管理器就会分配较大的内存来处理这些调用,从而造成内存浪费,影响性能。如果我们在接收端不知道消息的大小,为了不至于设置一个很大的缓冲区去接收数据,我们可以在真正的MQGET之前先使用Browse方式来浏览一下队列中的消息,根据浏览到的消息的MQMD的OriginalLength字段的数值来确定消息缓冲区的大小,然后再使用MQGET真正的将消息读取出来。

17. 在设定消息的优先级时,不要直接在消息描述符中设置,最好设置队列的优先级,然后使用"defined as queue"来设置消息的优先级。

优先级是消息的属性之一,当应用发生变化时,我们可能需要改变消息的优先级,如果我们将其写死在程序中,就会影响程序的灵活性。如果借助于队列的优先级来设置消息的优先级,系统管理员可以根据需要,通过改变队列的DEFPRTY的属性来更改其优先级,而无需改变应用程序代码,这样可以大大提高系统管理和网络管理的灵活性。

18. 请求型应用在打开请求队列时,最好不要使用MQOO_OUTPUT选项。

请求队列可以是本地队列,也可能是远程队列,本地队列是既可读又可写的,而远程队列是只可写(MQOO_OUTPUT)的。通常,请求型应用一般都是需要将请求发送到请求队列中,对队列的操作都是MQPUT,这时,队列的类型不会有影响,因为本地队列和远程队列都是可写的。但是,如果某个请求型应用需要读取请求队列,则要求请求队列一定是本地队列,这时就不能使用MQOO_OUTPUT选项。因此建议在打开请求队列时,最好不使用 MQOO_OUTPUT选项,可以使得不同的应用之间很容易移植,而且当队列属性改变时,不会对应用程序造成影响。

19. 与数据库交互时,MQPUT 和 MQGET必须使用同步点控制,即使用MQ的两阶段提交功能,来保证数据的一致性和完整性。

为了保证数据库操作和MQ操作同时成功或同时回滚,需要在做MQPUT 和 MQGET调用时,使用MQPMO_SYNCPOINT和MQGMO_SYNCPOINT选项将队列操作和数据库操作作为一个事物来完成。这样,如果数据库出现问题导致操作失败时,消息可以被正确回滚;否则,会导致数据不一致的现象。

20. 与数据库操作相关的队列消息的属性最好设为永久性消息,即消息的persistence属性应设为yes,并且永不过期。

由于与数据库操作相关的消息的重要性很强,它必须被设置为永久性消息,被MQ系统记录日志,从而在队列管理器重新启动或机器重启时不会丢失。另外,在 "Send and Forget"通讯模式之下,由于这是典型的异步通讯模式,消息何时被处理是不确定的,为了防止消息超时,我们应将其生命周期设置为永不过期。

21. 对消息做了修改或者转发的应用,最好传递原始消息的identity context(身份鉴别上下文)信息。

每个MQ的消息都有其特定的鉴别上下文,通过消息描述符(MQMD)的相关字段来表示,它代表了消息是由谁产生的,当消息在MQ网络中传输时,该上下文应该必须被保留。当消息被另外的用户修改或者转发时,需要修改其上下文,将其原始的上下文重新赋进去。这主要是处于安全的考虑,通过上下文,可以获得消息产生者的信息。

22. 保持永久性动态队列名称的唯一性,确保同一个应用多次调用/运行产生的动态队列的名字唯一性。

当应用程序需要动态产生应答队列时,它可以产生临时性动态队列和永久性动态队列,某些情况下,将利用永久性动态队列保留一些可恢复的消息,这意味着该永久性动态队列可以重新被打开,因此要保证它们名称的唯一性。

23. 在使用临时性动态队列来处理应答时,处理请求消息的应用要保证不要将应答消息设置为永久性消息。

临时性动态队列不能存储永久性消息,鉴于此,对请求做出响应的应用程序必须知道接收应答的队列是临时性动态队列还是永久动态队列,请求端应用和响应端应用必须对响应消息的永久性进行协调,以保证只有非永久性消息会被放置到临时动态队列中。

结论:为了更好地使用MQ,我们必须遵循一定的标准和指导原则,使得我们开发出更加可靠、高效的应用程序,当然,这是从普遍意义上而言,在实际应用中需要您灵活掌握,因地制宜地选用更加适合您的配置和编程模式

No comments: