微软
Search 开发负责人 Larry Jordan、开发人员 Michael Ruggiero 和 Michael Stanton 以及 .NET 框架项目经理 Hari Sekhar 在暗中构建了基于 .NET 技术的 Microsoft Web 站点搜索引擎新版本。迄今为止,只有参加过今年 7 月在奥兰多举行的“专业开发人员讨论会”中的一次特别会议的少数外部开发人员略知一些细节。现在终于可以将实情公诸于众了。
如果您经常访问“内幕新闻”站点,您就会知道,Microsoft Web 组在 2000 年 7 月份召开的“专业开发人员讨论会”之前推出了其 Search 引擎的新型改进版本。您已知道该版本引入了先进的同义词匹配、可返回最为相关的加按语搜索结果的扩展 Best Bets 逻辑,以及对最常用搜索的智能缓存。
然而,有关该版本的内幕消息远比表面上的东西多。
我们当然会兴奋不已,因为该搜索版本的丰富的功能以及经改进的搜索结果明显地能为客户带来更佳的搜索体验(参阅 Search 2.5 技术内幕)。但是,大多数人当时并未意识到,我们同时在幕后将传统的基于 ASP(Active Server Page 活动服务器页面)的 Search 2.5 版移植到新型的 Microsoft .NET 框架。
对搜索组而言,这是最具前沿性的开发。因为我们已经深入到 Internet 服务的未来。而且我们希望如此。下面来谈谈个中缘由。
为何要移植到 .NET?
显而易见,我们正在进入 Internet 的下一个阶段。我们正在跨越通常意义上的 Web 页面,并在开发功能强大的 Web 服务。在这一阶段,使资源和信息有计划地得到利用是极为重要的。这样,我们就可以把这些资源和信息作为服务来利用,而不是让其停留在杂乱无章的数据仓库中。
可扩展标记语言 (XML) 是在超级分布式系统之间实现多数据集传输的一种手段。它同时可以使开发人员以更具价值的新型方式聚集和组合各种来源的数据 – 这样用户就可以直接从中受益。
就 Search 而言,我们为多种自定义和本地化 Search 版本设计了在 microsoft.com 上查找信息的核心功能。我们组在如何使数据访问兼备灵活性和可用性方面面临挑战。在 .NET 出现之前,我们确实无法使客户在不使用安全端口上的 DCOM (分布式组件对象模型)的情况下针对我们的功能设计程序,或者客户只得将我们的多种软件版本安装在其服务器上以便访问代码和 COM。
我们组对即将推出的 .NET 技术进行了研究,并认识到可以通过将代码移植到 .NET 框架来解决所有远程性问题。而且,还有一个意外收获,我们还可以实现 HTTP 和 SOAP 的无处不在的连接。对绝大多数人而言,是否有某个人在 Microsoft 或在世界的某个地方,使用我们的 Web 服务在内部开发用于完全不同用途的应用程序,无关紧要。我们对两种情况均予以支持,同时我们也可以免费获得技术方面的好处。
最新的 Search 2.5 版如今运行在 Site Server 3.0 上,并仍然使用 COM 从搜索目录获得结果。该应用程序的其它各个方面都基于 XML。XML 作为一种将数据(例如,Vocabulary 和 Best Bets)发布到 Web 服务器的手段,使我们能够轻而易举地扩大我们的 Web 空间。
我们同时执行了一项缓存客户请求的最为常用的查询和结果的方案,这是通过将这些查询和结果保留在 Web 服务器上来实现的,并因此增强了可扩展性,进一步提高了性能。由于我们的核心体系结构是基于 XML 的,因而,移植到一个将利用 .NET 框架 Web 服务的模型确实非常简单,而这些 .NET 框架 Web 服务是建立在新型 ASP+ 技术基础之上的(ASP+ 技术被称为活动服务器方法 (ASMX) 页面)。
转换
Search 体系结构由三个组件组成:
Word Parsing and Vocabulary
Best Bets
Search Results
Search 的 .NET 端口的体系结构与基于 ASP 的版本相同(参见图 1)。下面让我们深入了解一下各个组件。
图 1.用户提交查询后,(1) 将查询先提交给解析器 (Parser) 进行词条分割和词汇解析,(2) 将找到的项目的显示术语 (Display Term) 传给 Best Bets,(3) 将找到的项目的首选术语 (Preferred Term) 和剩余项目传给 Search Results,(4) 使用 XSL 样式表编译生成的 XML 文档,(5) 给用户的 Web 浏览器提交 HTML。单击以放大。
Word Parsing and Vocabulary _ 这是一个包含一个 C++ COM 对象的 Windows 脚本组件,它暴露出 Search 中所支持的所有语言的各种词条分割程序。这种设计之所以必要是因为词条分割程序的接口不容易编写成脚本,并且通常需要一种 C++ 可编脚本的封装(尽管这是有办法做到的:以后将对此进行详细解释)。在向 .NET 框架移植的过程中,我们使用了 C++ 对象上的类型列表导出程序 (TLBIMP.EXE),并通过 .NET 中的 Interop 技术对其进行调用,这样您就可以调用现有的 COM 对象了。
Vocabulary Object 运行 Xpath(查询 XML 文档的语言)查询,以便将搜索词条映射到首选术语。它同时去除了干扰词条,并产生一种格式化的数据结构,适合于 Best Bets 和 Search Results 组件进行消耗。一项重要成果是,这个相当复杂的小脚本得以移植到 C#,我们还可以继续从中调用传统对象。下面是 Vocabulary Object 中的一个小代码示例:
// We return an array of VocabularyObjects after parsing the user's search
// text. This ability to create simple typed structures in C# vastly improves
// our code modularity and self-documentation. Here is the definition of
// VocabularyObject:
public struct VocabularyObject {
public string PREFERREDTERM; // structure members
public string DISPLAYTERM;
public bool FOUND;
public string ORIGPHRASE;
public bool MULTITERM;
public bool MULTIWORD;
// Constructor
public VocabularyObject(string preferredterm,bool found,string origphrase,
bool multiterm,bool multiword,string displayterm) {
PREFERREDTERM = preferredterm;
FOUND = found;
ORIGPHRASE = origphrase;
MULTITERM = multiterm;
MULTIWORD = multiword;
DISPLAYTERM=displayterm;
}
}
// Example usage. Because the parameters to the objects constructor are
// typed, we'll get a compiler error message if we passed an integer
// where a string was expected, for example. This is a very nice feature
// over traditional scripting environments!
VocabularyObject vo("Microsoft DirectX",true,"dx",false,false,"DirectX");
.NET 环境的其中一个优点就是,您可以创建用于整个代码的多数据结构。上面最后一行是阐明如何使用这些 Vocabulary Object 的代码结构的语句实例。
Best Bets _ 这是一个小脚本组件,它可提供对本地化 XML 文档的 XPath 查询,并可产生加按语的 URL 链接。XML 文档装载于每个 Search 应用程序实例的应用范围,并既可单独工作,又可与 Vocabulary 对象的方法紧密耦合。移植小脚本是 100% 向 .NET 框架的转换,并可利用 System.IO 和 XML DataNavigator 类 (System.NewXml 命名空间)。
这是最简单的移植组件。它几乎是行对行地从 Jscript 向 C# 的转换。我们仅在某些地方对代码作了一些更改,以便利用新的 XML DataNavigator 类 - 用来查询并更新 XML 文档的 .NET 通用语言运行时间部分。
Search Results - 这一复杂的组件与 Site Server 3.0 相接,从而获得与客户的搜索查询相匹配的实际页面描述和链接。它还包含一种完善的缓存算法。
构建并行解决方案
当时我们遇到的最大挑战是,我们在开发 Search 2.5 的同时,也在将整个 Search 应用程序移植到 .NET 框架的 ASP+ 技术。由于要在 PDC 日期之前推出此应用程序并将其移植到 .NET,周转时间紧,因此我们当时决定同时推出这两种版本,并将它们同时上市。很明显,这是一项艰巨的任务,因为我们必须管理新的版本,了解新型 .NET 框架的所有功能和新的语言隐喻,构建具有各种软件平台服务的服务器,等等。
关于我们是如何搞成这个项目的,还有一段有趣的故事呢。为了确保同时推出两种版本(Search 2.5 和 .NET 框架,我们在项目规划阶段确定了首先把握住哪些组件不变、哪些组件在开发过程中变化最大以及哪些组件适合于哪种技术和语言。
我们还及早确定目标,努力分解此应用程序,并按照客户可能会采用的方式移植。因为我们 microsoft.com 的人总是认真对待客户在进行技术决策和研究投资回报时面临的各种问题,所以,我们将此应用程序移植过程分解成许多部分,每个部分都尽可能与客户可能采取的方法接近。我们希望确保做好每项工作,其中包括最简单的移植(即,小脚本移植到 Jscript 类)直到最大的时间和技术利益投入 – 充分利用 C# 编程语言完全移植到 .NET 框架 (100% 可管理的代码空间)。
下面是我们在应对这个挑战时所采取的一些步骤:
首先,我们将主要的 ASP 页面转换为 ASP+。最初,我们是通过 .NET Reflection 技术调用小脚本,这样我们可以在运行时通过查询类型库来调用典型的 COM 对象。
重要知识:我们从具有 ASP 的编程模型出发(其中,数据、业务逻辑以及表示全都被混合在一起),然后采用 ASP+ 的一种完全面向对象的方法,最后是数据分离、编程以及 UI。
其次,处理最简单的小脚本并将其移植。BestBets 是最简单的组件,并且不依赖于 COM 组件。我们决定使用 System.IO、XML Data Navigator 以及 C# 编程语言将这个组件作为 DLL 移植。我们希望将这个组件完全移植到受控环境,并使其充分利用 XML Data Navigator。
重要知识:我们了解了 NewXml 命名空间。同时,我们在移植组件时去除了 .NET Reflection。这样我们就可以在本地调用这些组件。
然后,我们以同样方式处理 Vocabulary 小脚本。这个组件在复杂性和代码行方面处于此应用程序的中间。它由一个小脚本组成,这个小脚本包含用于 Search 的业务和文本解析规则,并对 C++ 组件进行调用,我们创建该组件的目的是包装 COM 对断字程序的引导调用。这个组件在移向受控空间方面具有最大优点。这个复杂组件被全部移植到 .NET 框架和 C# 编程语言。这需要一些技巧,因为,它包含更为复杂的函数逻辑,并需要利用一个自定义 COM 对象。但这还不算太难。下一步将抛弃 C++ 包装并直接调用这些接口。
重要知识:我们更改了函数和逻辑以便受益于象类型安全这样的 C# 的关键优势。在使用 Jscript 时,开发人员必须铭记每个变量的类型(整数、字符串)。C# 会为您做到这一点。所有变量在声明时确定,并且 C# 会检查您的工作以便确保没有越界。这在处理复杂代码时帮助很大。备注: 在 JScript 的下一个版本中,程序员将可以选择完全确定变量的类型。
移植最终组件:SearchResults。最初,我们通过 .NET Reflection 调用这个组件,而且情况良好。由于这个代码太大并相当复杂,同时由于在我们推出 Search 2.5 版本之前对该版本作了一些根本性的更改,因此移植该代码的工作一直持续到现在。在 .NET 测试版中找不到它,但该项工作已取得重大进展。10 月份晚些时候将发布该版本的更新。
总之,这个体系结构是一个杰作。我们拥有一些真正的 C# .NET 组件,我们拥有所有的 ASMX 页面。而且,我们演示了可以通过 Interop 调用自定义 COM 对象,以及通过 .NET Reflection 调用小脚本。传统的对象(比如,SearchResults)可以消耗由 C# 对象(比如,Vocabulary) 创建的数据结构,这是非常好的事。
在您审查 .NET Search 测试版之前值得一提的是,这个体系结构中没有用户界面。您所看到的是一项 Web 服务的默认情况。我们本来是可以添加一个 UI 的,但是我们之所以保留成现在这样,是想让您看到其本来面目。