分类:
2008-10-28 15:39:53
商业软件的开发,大部分都需要有一些为其它模块提供服务的底层模块。这些底层模块由于实现的是一些通用功能,需要同时为几个高层模块提供功能,因此通常被设计成一种基于消息队列的框架。任何需要访问这些通用功能的高层模块,都可以通过发送消息并接受返回值来得到需要的服务。
这种构架的设计,一般是围绕消息队列来展开的:首先有一个消息队列,并对外暴露发送消息的API;然后实现一个负责维护并调度该消息队列的线程,该线程负责维护消息队列,并分发消息;最后是一系列处理特定消息的功能模块。消息由暴露给外层模块的API发送到消息队列,由调度线程接受消息,并分发给消息处理模块,然后由处理模块对不同消息进行处理,将处理结果返回给高层模块,这就是一个完善的基于消息队列的公用模块。为了实现这种模块的可扩展性,消息处理模块一般采取一种基于注册的设计,允许用户注册特定消息的消息处理函数。
这种构架从结构上看,是非常完善的,模块功能也比较好划分,因此应用广泛。但是根据个人经验,大部分这种模块的实现,在细节上都会有一些细微的瑕疵,给后期代码的维护和扩展带来麻烦,一下是个人遇到过的3中情况。
1. 消息的定义没有做到“原子化”。单一的功能被认为拆分成好几个消息。比如在一个为其它模块提供数据库访问服务的模块,将打开记录集,获取记录集,关闭记录集分成了3个消息,由于这3个消息在逻辑上有着顺序关系,而消息处理并不能很好的保证这种顺序关系,从而导致获取记录集时,记录集已经被关闭等等类似情况的出现(由于记录集可能很大,需要分多次获取,这种设计有时候也是不可避免)。
要处理这种情况,唯一的办法是保证想办法保证消息处理的顺序性,比如使用同步消息。由客户端确保上一条消息已经被处理,结果已经被返回的情况下才发送第二条消息。
2. 主线程接受到退出消息,开始清理内存资源,而消息处理线程仍然在处理消息。这种情况的发生一般会导致软件报非法内存访问错误,而且一般是crash问题。
处理这种情况,必须确保主线程wait所有其他线程已经退出后,才开始做清理工作。要做到这一点有时候相当困难。个人遇到过的一种情况是,消息处理线程由公司其它team的人开发,他们在消息队列创建的时候创建一个线程池,该线程池自动调用我传递过去的一个回调函数处理消息。而且线程池的销毁工作也在该模块中实现,其结果是在我的模块中,收到系统退出消息以后,无法准确探测是否所有消息处理线程已经全部中止,是否已经可以开始做资源清理工作。
实现基于消息队列的架构时,必须在设计初期处理好多个线程的调度关系,以及逻辑顺序。
3. 如果基于消息队列的模块需要访问数据库,那么还有一种更为讨厌的情况。在某些数据库系统中(MS SQL Server),记录集与事务处理之间的嵌套会导致记录集被破坏。考虑一种情况,高层模块有如下需求:
a.) 查询一个数据表。b.) 删除另外一个表中的2条记录。
第一个需求可以查分成3个消息:打开表,获取记录集,关闭表,由于不改变数据库,不必使用事务。
第二个需求可以简单打包成一个消息,由于更改数据库,需要使用事务机制。
由于消息处理不能保证顺序性,如果以上4条消息处理顺序如下:
打开表1---- 删除表2的两条记录----获取表1记录集中的记录----关闭表1
第一步操作和第二步操作没有问题,但是第三步操作,获取表1记录集时,某些数据库将报错。因为第二步操作使用了事务,而事务会销毁同一个数据库连接上以前打开的记录集,具体可以参考微软的文档:
处理这种情况,一种解决方案是使用微软建议的方式,使用client side cursor,不过这会大大降低数据库访问性能。
另一种解决方案是让查询和事务操作使用不同的数据库连接,这需要在设计初期进行明确的定义。