在多个系统平台之间分布软件系统的需求在过去的10年里增长迅捷,系统设计师的任务也变得越来越困难了。当年第1次开发客户机/服务器架构系统规划至今还记忆犹新。这类系统主要由运行在局域网内“胖”服务器和“胖”客户机组成。最昂贵的资源是时间,所以在进行分布系统设计时,通常的措施是打开数据库连接,在关闭应用程序之前始终保证连接的存在。这样做可以令系统的性能达到最佳状态,不过在这种情况下你可不能让太多的用户同时使用系统。随着系统用户日益增多,服务器上的负载也会逐渐增加,到最后其增加程度如此之大甚至会让系统失去伸缩性。
当三层分布处理系统成为主流的时候,数据访问方式也就有必要从“一次打开一直保持”转变为“按需多次打开和关闭”。新方法大大增强了系统的伸缩性,但在真实性能和预计性能方面却付出了代价。显然,对微软公司来说,只要它的系统还离不开同步性它就永远都不能把应用程序扩大到Internet所具有的规模。Microsoft
Message Queue Server (MSMQ)是作为附加产品发布的,但它很快就找到了进入操作系统的康庄大道。现在,MSMQ已经成为了.NET战略的关键组成部分。
我们中的大多数人可能还没有意识到我们其实不再生活在一个同步的世界里。在同步世界,你一拨通电话就会有人马上接话。还有,大多数的工作都是通过碰头会而非电子邮件交流或者讨论组等形式来完成的。同步性所产生的问题是你和另外的至少一个人必须同时在同一地(物理的或者电子的形式)协作完成工作任务。在同步方式下只要有工作要做就可能会带来天然的瓶颈问题。
现在让我们从系统的角度来考察一下这个问题(见图A)。假如你正在运行一个在线商务系统,你必须经常进行数据库检索操作,每一份定单和每一份确认通知都由系统的中间层处理并传递给唯一的数据库,这里就有天然的瓶颈问题出现了。或许系统运行得还不错,但是,假如你真的成功了,你却无法拓展你的系统,除非进行硬件升级,这就是你的系统扩张战略?这能叫成功的系统设计吗?
图A
数据库成为系统增长的瓶颈
下面我们再从异步实现的角度审视同一在线电子商务系统示例。在异步技术实现的系统下,当在线定单被网站收到时,定单将被加入到一个队列,然后等待数据库处理周期可用的时候进行处理(参看图B)。由于在队列中放置对象的时间相比数据库处理交易所耗费的时间要小得多,所以系统的平均处理速度会大大加快。而在队列中放置定单的对象则可以即时向用户确认回复,比如“已经收到了你的定单,系统正在处理。”
图B
请求被放在了队列里并在下一个数据库处理时隙可用时接受处理
这种队列式的配置还具有另一显著的优点:你可以同步或者异步这两种方式之一扩展系统后端。你可以从同步设计的角度采取同一扩展战略,只需要加大唯一后端的处理能力即可。但是异步设计却让你有机会对整个系统进行扩充。在异步设计下,不必让唯一后端来处理队列,你可以让多个后端获取队列数据并对其进行处理。
但是,我们也必须指出,这种系统设计存在一个主要的缺点。异步系统采用的是近似实时的数据访问而非真正的实时数据访问,比方说,在线电子商务系统的消费者只能根据系统所掌握的最近的股票信息下定单,而这种情况显然不是我们所希望的实际交易数据,对后者而言,消费者下定单的时候数据就正好在交易点,是最真实的实时数据。实际上,达到如此精确程度的代价之高将促使系统在开发和部署时过于昂贵,所以,我们为什么不充分利用下异步处理的伸缩余地大这一显著优势呢?
排队理论并不是一个复杂的概念,但是,要用Visual Studio 6.0和Visual Basic实现数据排队却有比较大的难度。既然排队已经内置在了.NET框架的系统级别上,那么在这一新的平台下处理排队可就简单多了。不过,当你在打开队列的时候,你该如何确定放入队列的数据的格式呢?在.NET诞生之前,这一决策逻辑是很令人头痛的。你必须编写串行化和去串行化代码把数据放到队列的一头,需要的时候从再从另一头把数据拽出来。现在有了
.NET,你可以采用内含的XML对象完成串行化工作,或者你也可以直接把.NET对象放到队列里来, CLR将会自动地进行数据串行化。.NET从其“灵魂深处”开始就支持异步、离散系统的开发,这是毫无疑义的。
在WiredChurch.com,我们构筑了一个处理引擎,其计算是异步发生的。这样就可以让我们随着交易量的增大扩展系统的处理能力。比如,WorkFlow引擎(实现为一种Windows服务)会扫描事件(Event)数据库检索最近需要交付的事件通知、邀请函和备忘。Communications引擎(也是作为一种Windows服务实现的)从事件队列中拾取消息对象并交付这些对象。在我们的负载测试中,我们可以轻松地突然增加从队列中抽取的对象数量,而手法不过是在另一台主机上启动另一个Communications引擎Windows服务并令其指向事件队列即可。
在以上的特定实现情况下,我们希望能保证队列的交付能力。结果,机器宕机只会意味着消息队列的丧失但却并没有丧失交付能力。通过简单地为Message对象设置Recoverable
属性,我们就可以迫使MSMQ在磁盘上创建队列,这样就令我们获得了有保证的交付能力。如果我们需要队列参与外部控制交易那么我们还可以采用Transactional
队列,但不用在交易中选择封装函数。这一系统以队列作为其核心逻辑结构,由此我们就可以创建具有大规模部署灵活性(节点从1到N)和伸缩性(可达到百万级的交易数量)的系统。