美祺 的个人资料Eden of Hexagon照片日志列表更多 ![]() | 帮助 |
|
|
12月29日 biztalk中常用调试方法Biztalk中以图形方式取代了长串的代码,也不再有源代码级的单步调试,取而代之的是综合的调试手段。下面详细讨论biztlak中的常用调试手段。 一、 在Biztalk administration console中查看挂起的服务调试biztalk的应用时,如果没有得到预期的结果,比如给了输入的消息,在应该输出消息的位置没有得到预期的消息,肯定是哪个环节出错了,这时首先应该检查的是否有挂起的服务实例,因为biztalk以消息为核心,而消息都是在某个服务实例中被处理的,一旦出错,出错的服务实例连同正在处理的消息就会被biztalk挂起: l 打开Biztalk Server 2006 Administration l 在Administration Console左边的树形结构中选Biztalk Group l Administration Console右边窗口显示Group Overview,按F5刷新状态,在Suspended Items下可以看到目前是否有被挂起的服务,如下图:
Figure 1 Biztalk Administration Console中查看挂起的服务 如果Suspended service instances的计数不为0,就表示有被挂起的服务实例,点击Suspended service instances查看被挂起的具体服务实例。 打开挂起服务实例后,在窗口的右下方显示具体的被挂起的服务实例,双击某个服务实例,即可查看这个挂起服务实例的被挂起的原因:
Figure 2 Biztalk Administration Console中查看挂起服务的挂起原因 在Service Details窗口中,点击Messages标签,查看此服务实例正在处理的那个消息:
Figure 3 Biztalk Administration Console中查看挂起服务的相应消息 二、 查看事件日志一般biztalk中发生的错误都会在事件日志中有反映,在上面的查看挂起的服务实例中能查到的错误,一般在日志中也都会有反映。但是有时由某些错误导致biztalk应用出错,在biztalk中只反映了跟biztalk直接相关的错误,最原始导致错误的原因可能在事件日志中才会有更详细的反映。 所以,如果在biztalk Administration Console中如果没有找到正真的错误原因时,到事件日志中找找可能会有所发现。 l 开始 – 管理工具 – 时间查看器 l 在事件查看器中右边目录,选“应用程序”,来源为“Biztalk Server 2006”的即为biztalk产生的错误消息。
Figure 4 事件日志中查看biztalk应用相关出错信息 三、 使用Orchestration Debugger断点调试OrchestrationBiztalk中orchestration是实现应用功能的主要手段,业务的流程就是它来实现的,对orchestration是调试一个biztalk应用的最主要的部分。 Orchestration是图形化的设计工具,biztalk也设计了个图形化的调试工具,叫做Orchestration Debugger。这个调试器可以对Orchestration进行断点调试,在断点出同样可以查看各个变量、消息、以及端口的状态。 1、 打开Orchestration Debugger要想在Orchestration Debugger中打开某个orchestration,这个orchestration必须已经被执行过,或者正在执行中。
如果第一次开始测试一个orchestration,当消息进入到biztalk,在入站的接收管道内就出错了,接收端口这个服务实例就会被挂起,消息也不会被路由到要处理这个消息的orchestration,这时这个orchestration还未被执行,所以这种情况是无法在Orchestration Debugger中打开这个orchestration,必须在排除了接收管道的错误,消息能够进入到Orchestration后才可能进行Orchestration的调试。 有两种方法打开Orchestration Debugger。 1.1. 在HAT中打开Orchestration Debuggerl 开始 – 所有程序 – Microsoft Biztalk Server 2006 -- Health and Activity Tracking l 在打开的HAT窗口中,选Queries – Most Recent 100 Service Instances l 在列出的最新的100个服务实例中,找到你要调试的那Orchestration,点击鼠标右键,选Orchestration Debugger。 1.2. 在Biztalk Administration Console中打开Orchestration Debugger在Biztalk Administration Console中可以查看到被挂起的服务实例和正在运行中停留在断点的服务实例,如果是Orchestration的服务实例,都可以在此打开Orchestration Debugger 1.2.1 挂起的Orchestration服务实例l 打开Biztalk Server 2006 Administration l 在Administration Console左边的树形结构中选Biztalk Group l Administration Console右边窗口显示Group Overview,按F5刷新状态,在Suspended Items下可以看到被挂起的服务 l 点击Suspended service instances查看被挂起的具体服务实例。 l 打开挂起服务实例后,在窗口的右下方显示具体的被挂起的服务实例,如果有你需要调试的Orchestration被挂起,在这个Orchestration上点击右键,选Orchestration Debugger 1.2.2 在断点的Orchestration服务实例如果一个Orchestration 已经被设置了断点(如何设置断点后面讨论),Orchestration运行到断点处中断,Orchestration服务实例处于运行中状态。查看这样的服务实例同上面步骤一样,只是在第三步是查看Running Service Instances。 2、 设置断点在Orchestration Debugger中打开了一个Orchestration后,就可以对这个Orchestration设置断点了。 在Orchestration Debugger中,一个Orchestration只显示流程的中间部分,Port Surface部分的端口不被显示,可以在流程的各个形状上设置断点:
Figure 5 Orchestration Debugger中设置断点 设置断点的方法是:选择要需要设置断点的形状,按F9。如果要取消断点,再次按F9键。 2.1. 类级别断点上面方法设置的断点是类级别的断点,一旦设置了,对以后的这个Orchestration的服务实例都起作用,直到取消这个断点。 2.2. 实例级别断点当在Orchestration Debugger中附加(Attach)当前在断点的Orchestration服务实例(如何附加下面讨论),即真正在Orchestration Debugger中打开了在断点的那个服务实例后,这时设置的断点只针对当前正在调试的Orchestration服务实例,对其他服务实例不起作用。 3、 断点调试必须在Orchestration Debugger中打开需要被调试的那个Orchestration服务实例才能对这个Orchestration服务实例进行调试。 只有运行中的Orchestration服务实例才能被断点调试。 运行中的Orchestration服务实例一般有两种情况:出错被挂起、在设置的断点处中断。 出错被挂起的Orchestration服务实例在Biztalk Administration Console挂起的服务中找。在断点处中断的服务在Biztalk Administration Console的Running Service Instances中找。 l 在找到的需要调试的Orchestration上点击右键,选Orchestration Debugger l 在打开的Orchestration Debugger窗口中,选Debug – Attach,这样就把当前需要调试的服务实例附加进来。这时的Orchestration Debugger看上去是这样:
Figure 6 Orchestration Debugger中附加服务实例后断点 这时,可以看到,服务实例被附加进来,并在设置的断点处中断,窗口下部多了两个窗格。 Variable List 窗格显示了当前消息、变量和端口。 Variable List 窗格显示的变量的类型格式为: Assembly.Namespace.Name Variable Properties 窗格显示Variable List 窗格选择的变量的值。 Orchestration Debugger不能单步调试,但是可以通过不断的设置断点来实现类似单步调试的效果。 附加服务实例后设置的断点为实例级别的断点,只对当前服务实例有效。 四、 在流程中使用System.Diagnostics.EventLog.WriteEntry方法写入系统日志设计中在orchestration的一些关键点设置了表达式形状,在orchestration运行到这些点的时候,表达式往日志中写入表示已执行到这一点的信息。使用类似下面这样的语句: System.Diagnostics.EventLog.WriteEntry("用户信息插入到Sql之前","Ack_Sample");
12月4日 使用BizTalk Server常见问题处理在开始开发BizTalk项目的时候,一些开发者会碰到许多基础问题,本文对这些常见问题进行罗列,其中有个别问题笔者向微软的BizTalk工程师寻求了问题解决方案,旨在让使用BizTalk的朋友更快的进行开发。文中的内容基于BizTalk Server 2006和Visual Studio 2005。 1. 在多列结果上不支持数据流 2. 缺少根元素 3. 重新部署BizTalk应用程序无法刷新服务器运行实例 4. 在vs.net 2005中无法自动刷新程序集引用 5. 验证架构文件(.xsd)出现错误 BEC2004 错误信息:(VS2005输出的错误信息有点读不通) 6. 通过vs.net部署应用项目提示“无法添加资源” 7. 只能在原子作用域或服务中声明非序列化对象类型 8. 消息引擎无法从数据库检索配置。详细信息:“c0002a1f” 9. 日志项字符串太长。写入事件日志的字符串不能超过 32766 个字符 10. BizTalk Server 2006安装问题 11. 设置MQSeries传输属性的队列定义,队列管理器失败 12. 非法尝试更新消息“Message_XXX”的只读属性值 13. 使用SQL适配器发送数据提示“新事务不能登记到指定的事务处理器中” 14. 分步启动BizTalk Server 2006应用程序出错 添加SQL适配器元数据提示“无法执行SQL语句。请确保提供的语法正确。” 15. 添加SQL适配器元数据提示“无法执行SQL语句。请确保提供的语法正确。新事务不能登记到指定的事务处理器中。” 16. 在基于内容的消息路由中提示“无法路由发布的消息,因为找不到订户” 错误信息: 17. 执行接收管道时出错,没有任何拆装 阶段组件可识别该数据 18. 执行发送管道时出错,原因: 根级别上的数据无效 19. 配置MQSeries适配器的队列定义信息选择本地服务器队列管理器出错 20. 通过MQSeries适配器发送消息提示访问被拒绝 21. 在应用程序日志中出现“文件系统连接器中发生错误” 8月13日 BizTalk Server Tips提高 BizTalk 编程能力的 8 点技巧和窍门and Scott Zimmerman 本文讨论:
本文使用了以下技术: 1. 始终使用“多部分消息类型” 有 一次,Marty 要与一个大客户进行概念证明,就在要开始前半天,他接到一个需要修复的复杂的 BizTalk® 解决方案。 该解决方案的主要组件是一个业务流程,它集成了几个后端系统,要对每一个后端系统执行多次对外调用。 他看到“业务流程设计器”屏幕上几乎全部由黑色线条构成,将几十个“接收”和“发送”形状连接到 40 多个入站和出站端口 — 这种设计几乎不可能对其进行调试。 他的解决办法: 使用“多部分消息类型”和“直接绑定端口”(下面的技巧 1 和技巧 2)重新开始,沿流程路线执行单元测试。 结果如何? 他成功了! Marty 的成功部分归功于 BizTalk Server 的设计 — 它的设计可以应对互连系统编程中固有的特殊难题,在某些情况下甚至不必编写代码。 但是,尽管它有简洁的拖放式流程图,而且代码很少,您也不要高兴过了头 — 事情没这么简单。 BizTalk 是一种使用范围广而且功能强大的产品,熟练掌握它需要几年的时间。 如果您打算成为一名 BizTalk 专家,您会遇到很多需要您基于 Microsoft® .NET Framework 编写代码的情形,以与 BizTalk 中复杂的内置消息处理能力形成补充。 直到最近,BizTalk 新手们都一直很少有机会利用大师的心得和技巧来加快掌握 BizTalk。 (请参阅侧栏上的“学习 BizTalk Server 2006 编程”以查看相关资源。) 我第一次深入接触 BizTalk 是在参加 Marty 主办的为期一周的 BizTalk 培训活动中的一次课程期间,在这一周的时间里,他不断告诫我们要正确设计系统,并在使用之前在实际负载条件下用 PerfMon 对其进行测试。 通过这次活动,我们汲取了大量技巧,这些技巧可以帮助您成为更高效的开发人员。 本文的目的旨在与大家分享这些有用的技巧,下面转入正题。 BizTalk 的目的旨在处理消息,顺便说一下,所谓消息并非只是电子邮件。 文档、InfoPath 窗体、大型二进制文件、SQL 记录、平面文件以及任何 XML 文件都可以作为消息来处理,同时您还可以获得异步通信带来的好处。 一旦您熟悉了该工具,您会发现面向消息的编程与面向对象的编程一样酷,都是非常有用的编程范例。 BizTalk 中的消息是数据,每个消息都必须属于一种选定的消息类型。 BizTalk 中最常见的消息类型是架构,也就是说,此类消息基于一个 .XSD 文件,此文件指定了消息中的记录和字段结构。 请参阅 msdn2.microsoft.com/en-us/library/ms942182.aspx,查看有关 BizTalk 中的架构和映射的详细讨论。 在用 BizTalk 进行编程的过程中,总有一天您会希望更改某个消息所基于的架构。 但问题是,如果您已经为“发送”或“接收”形状选择了此类消息并将其连接到一个业务流程端口,则在您尝试更改“消息类型”时将收到以下错误: Property value is not valid: One or more Send or Receive actions are connected to Ports and are using this Message. Please disconnect the actions before changing the Message Type. 在按错误消息的建议进行操作之前,让我们先想一想它涉及哪些工作。 首先,您必须检查每个“接收”和“发送”形状,以确定它是否使用了与您要更改的架构关联的 Message 变量(已设置了其“消息类型”)。 目前,BizTalk 中没有可提供此类审核或生成依赖关系映射的功能;实际上,若创建的一个业务流程有太多的“接收”/“发送”形状,以至于需要有这样一种功能,只能说明这一流程的创建没有采用良好的做法。 在前面提到的让人发怵的业务流程中,大多数“接收”和“发送”形状都使用不同的 Message 变量,但它们都指向同一架构定义(都是同一消息类型)! 第二,一旦找到所有“接收”/“发送”形状,必须删除它们的端口连接。 第三,必须更改 Message 变量,以便将“消息类型”属性设置为新的架构,然后重新将 Message 变量与每个“接收”/“发送”形状关联起来。 第四(您认为您已经完成了),必须找出与已经从“接收”/“发送”形状断开连接的端口关联的所有“端口类型”,并重新设置其“操作”的“消息类型”属性。 您知道,在您最初将“接收”或“发送”形状与一个端口关联时,BizTalk 会自动将“端口”类型中的“消息类型”属性设置为与此“接收”/“发送”形状的 Message 变量的“消息类型”相符。 在删除“端口”和“接收”/“发送”形状之间的连接时,BizTalk 将丢失该“端口”的“消息类型”。 幸运的是,有一种更好的方法。 计算机科学领域有一条公理指出,添加一个中间环节可以解决许多问题。 本例再次证明了此言不虚;我们需要做的就是使用一个多部分消息类型来包装底层的架构。 最初的工作量可能会稍大一些,但是灵活性会大大提高,并且从长远看可为您节省时间。 下面介绍如何实现这一点。 在您右键单击“业务流程视图”选项卡中的“消息”来创建新消息时,有四种消息类型属性可供选择(参见图 1 中的右下角)。 为消息类型选择一种架构是通常的做法,但是在这里我们所使用的技巧是要尝试一种全然不同的做法。 单击 + 号展开“多部分消息类型”,然后选择“创建新的多部分消息类型”。 为您的多部分消息类型命名,然后单击 + 号展开它,以便您可以查看其 MessagePart_1 成员(这是 BizTalk 为新的多部分消息类型的第一部分建议的名称)。 现在,将“标识符”属性从“MessagePart_1”更改为更好识别的值,如“Body”(但是不要使用小写“body”,因为它是保留字)。 不要忘记将“消息正文部分”属性设置为 True(以便其行为与常规的消息一样)。
需要做的额外工作到此结束。 最后,将新消息部分(如,Body)的“类型”属性设置为常规的基于架构的消息类型所使用的同样的架构。 现在,您的逻辑端口和“接收”/“发送”形状都在使用您的多部分消息类型,将来您可以轻松地更改底层的架构,而无需断开端口与接收形状的连接。 请看一下图 2,该图显示了在业务流程设计器中配置端口时可用的选项。 在使用端口配置向导时,您将从此图的顶部移动到底部。 注意此图中的三个“直接”选项。 虽然 BizTalk UI 提供了“直接”作为端口类型,但是我们这里将使用行话,称它们为“直接绑定”。
要理解 BizTalk 中的端口术语,关键要理解逻辑端口(也称为业务流程端口)和物理端口的概念。 一句话说,两者的区别是,在业务流程设计器中创建的是逻辑端口,使用 BizTalk 浏览器或 BizTalk 管理控制台创建的是物理端口。 开发人员在业务流程设计器中创建“以后指定”端口时,他是在配置一个逻辑端口,将相应的物理端口属性留给 BizTalk 管理员在以后进行配置。 “以后指定”是最常使用的选项,关键原因是为了让管理员能够在生产环境中灵活地配置物理端口。 使用“立即指定”选项,则开发人员完成所有配置(包括逻辑和物理配置),得到的解决方案是硬绑定的。 除了要进行快速试验以外,不建议使用此选项。 使用“动态”端口时,物理端口属性的最终配置同样也是由管理员完成的。 但是,在这种情况下,开发人员还可以编写代码,在运行时设置特定出站消息属性。 这对于 SMTP 或 HTTP 非常有用,在这两种情形下,业务流程逻辑包括带有如下语句的“表达式”形状: DynamicSendPort(Microsoft.XLANGs.BaseTypes.Address)=
“mailto:john@contoso.com”;
虽然图 2 中有许多选项,但此图中显示的仅是业务流程设计器中的选项。 “静态”端口适用于哪些环境? 当您在 BizTalk 浏览器或 BizTalk 管理控制台中创建物理端口时,会遇到这一术语。 如果开发人员在业务流程设计器中创建一个“以后指定”或“动态”端口(逻辑端口),那么管理员在 BizTalk 浏览器或 BizTalk 管理控制台中创建相应的物理端口时,将从图 3 中所示的四个选项中选择。 如果您刚开始学习 BizTalk 编程,您会发现,如果查看 2005 年 11 月份的《MSDN 杂志》上由 Aaron Skonnard 主持的服务站专栏以及 2005 年 12 月专栏,将对您有很大帮助。 在 BizTalk Server 开发中心,您可以找到数十种免费的网络广播。 如果您处于中级水平,请阅读由 Mark Beckner 编著,Apress 出版的 《BizTalk 2006 Recipes: A Problem-Solution Approach》;并阅读 2006 年 12 月份的《MSDN 杂志》中的 Alex Starykh dives into adapter programming topics(针对 BizTalk Adapters)。 如欲获得中级到高级资料,请参阅由 George Dunphy 与 Marty 合著、Apress 出版的《Pro BizTalk 2006》,以及由 Darren Jefford 编著、Wrox 出版的《Professional BizTalk Server 2006》,在您阅读本文时,这两本书应该已经面世了。 不要因为看到标题中的版本号而感到失望;由 Scott Woodgate 编著、Sams 出版的《Microsoft BizTalk Server 2004》也是非常适用的。 这些书中包括几个使用 C# 代码将 BizTalk 带入更高级别的示例。 为增强您在集成系统理论和设计方面的实力,您需要阅读由 Gregor Hohpe 编著、Addison-Wesley 出版的《企业集成模式》一书。 乍一看,您可能会认为这对您学习 BizTalk 不会有什么帮助,但是,了解一下 BizTalk Server 2006 如何隐式实现这几种模式是很有趣的。 如想了解有关技巧 1 的详细信息,以下由 Kurt Guenther 和 Charles Young 撰写的博客是很好的参考资料: 对于技巧 4,请查看以下由 Brian Loesgen 和 Kevin Lam 撰写的专家博客: 如欲了解更多 BizTalk 技巧,请参阅 Alan Smith 撰写的“七个习惯”一文和 Scott Colestock 撰写的有关 BizTalk 命名约定的短文:
请记住,“动态”物理端口对应于“动态”逻辑端口(相当简单),但“静态”物理端口对应于“以后指定”逻辑端口。 另外,要注意“要求响应”和“请求响应”这两个术语之间的不匹配。 就这些。 完成了这些初步准备之后,我们终于可以讨论“直接绑定”端口的优势了。 “直接绑定”端口在将消息从 BizTalk 发送到 BizTalk 方面是很出色的。 顺便说一下,正是由于这一点,BizTalk 没有必要为 Web 端口提供“直接”选项。 Web 端口用于从 BizTalk 到 Web,而不是从 BizTalk 到 BizTalk。 “直接绑定”端口是自我配置的,不会影响灵活性或性能。 “直接绑定”端口的便捷性在于开发人员和管理员都无需在 BizTalk 浏览器或 BizTalk 管理控制台中创建相应的物理端口。 因此,“直接绑定”端口会形成更独立的业务流程,因而可以更好地重复使用,而且更便于独立地重新部署。 有以下三种“直接绑定”端口类型: 消息框路由、自我关联,以及业务流程到业务流程(也称为合作伙伴端口)。 这三个单选按钮在业务流程设计器中的实际文本要冗长得多,但是,您只要略加观察,就能轻松地把我们的这些短名称与 UI 上的名称相对应。 在这三种端口类型中,“消息框”是最常使用的一类。 它允许您的业务流程完全独立于其他业务流程,尽管您可能只是让它与其自身通信。 没错,这确实意味着有可能出现无限循环。 我们马上就会谈到这一问题。 如果为“消息框路由”配置了一个“激活”属性设置为 True 的“接收”形状,则意味着当消息到达符合给定的订阅筛选器的“消息框”时,将启动业务流程的一个新实例,不管消息来自何处,也不管发送给谁。 这可以帮助您构建一个可重复使用的业务流程,如工具库中的泛型函数。 Marty 使用此技巧构建了一个巧妙的基于消息的异常处理系统,此处暂且不论。 假设您想用以下两种方法启动您的业务流程: 第一,操作员在某个文件夹中对单向静态 FILE(物理)端口发出“启动”消息;或者,第二,业务流程向其自身发送一个启动消息以运行另一个实例。 您首先想到的可能是使用一个“接听”形状(如,用于业务流程的 OR 运算符),并使它下面有两个“接收”形状(“以后指定”用于 FILE 端口,“直接”用于消息框)。 但“直接绑定消息框”端口的最妙之处恰恰是 — 它们不关心消息的来源。 因此,您只需要一个“接收”形状就可以启动此业务流程! “自我关联直接绑定”端口通常在您想要以异步方式将消息从一个业务流程发送到下一个业务流程时使用。 假定您有两个业务流程:A 和 B。只需进入业务流程 B,在参数部分下创建一个逻辑“发送端口”,并将其设置为使用在业务流程 A 中创建的“端口类型”。现在,您通过业务流程 B 中的该端口发送的任何消息都将立即路由到您在业务流程 A 中创建的逻辑“接收”端口。基本上,我们利用 BizTalk 的发布/订阅特性来转发消息,此外还通过隐藏的独特的关联令牌为这些消息加盖印记,因此称为“自我关联”。 在这种情况下,由于您在各业务流程之间共用一种端口类型,因此各业务流程都对其他业务流程有所了解。 您可以将“端口类型”上“操作”的“消息类型”设置为 System.Xml.XmlDocument,从一定程度上减小依赖性。这是使业务流程异步运行,同时使某种 broker 能够接收来自其他业务流程的主动确认或被动确认的一种很好的方法。 业务流程到业务流程直接绑定(合作伙伴)端口用于以异步方式发送消息。 您将向订阅中添加与使用“消息框直接绑定”端口时相比更宽泛的限制。 在这种情况下,也就是说您将接受来自合作伙伴业务流程的任何消息。 一个业务流程指定“直接绑定”端口,另一个业务流程(合作伙伴)指定自身。 发送方或接收方都可以成为引用“直接绑定”端口的一方。 这里的关键是,您将把两个已知的解决方案连接起来进行通信,而使用“消息框直接绑定”端口时发送方和接收方之间的耦合更松散。 接下来介绍有意思的部分。 使用“直接绑定”端口(特别是使用“消息框”)时常见的缺陷是会造成无限循环。 假设一个简单的业务流程只包括两个形状,“激活”被设置为 True 的“接收”形状(当然是“直接绑定”)和仅将消息转发给 FILE 端口的“发送”形状。 当此业务流程发出消息后,消息将去往何处? 和其他情况一样,首先进入消息框。 一旦消息达到消息框,BizTalk 就会进行搜索,看有没有匹配的订阅。 此消息如何与最先激活业务流程的消息区分开? 无法区分。因此 BizTalk 会不假思索地触发业务流程的另一个实例来处理它,以此类推,直到耗尽内存。 修复此问题的一种方法是将入站消息复制到一个新构造的出站消息,并更改至少一个提升的属性的值,以便“接收”形状上的订阅筛选器无法与新消息匹配。 运行管理控制台,并转到“组中心”|“新建查询”|“打开查询”。 下一步是打开 BizTalk\SDK\Utilities 文件夹中的 BTSSubscriptionViewer.btq。 您将看到,在您为“接收”形状设置消息类型时,BizTalk 将自动创建一个谓词。 在“业务流程设计器”中,将一个谓词添加到“筛选器表达式”属性中,完整订阅将如下所示: BTS.MessageType == “http://MyInternalSchemas.MyProject#MyRootNode” AND inbound_message(status) != 1 或者,如果您不愿意像上面所说的那样更改架构以添加自定义字段,还可以向“接收”形状上的订阅筛选器中添加一个子句来测试 BTS.Operation 名称与您的业务流程中的名称不同。 我们前面说过,BizTalk 的目的旨在处理消息,还记得吗? 嗯,回想一下。 实际上也就是订阅。 在 BizTalk 中,在发送端口或业务流程上对消息所做的操作无非就是订阅。 您应始终将接收到的消息转换为您自己的规范架构,不管架构的简单程度如何,也不管它的源是谁。 对于少量的处理,一旦(更恰当地说是“当”)发送方更改架构时,此做法将使您获得很大程度的灵活性。 您不希望您的业务流程逻辑依赖于由其他人控制的架构中的任何属性或字段。 在发送方更改其架构时,您只需更改您的映射,而不是业务流程。 这同样运用了中间环节的公理。 通过遵循为映射、业务流程、内部架构和外部架构创建单独的 Visual Studio 项目这一最佳做法,可以更轻松地重新部署您更新的解决方案。 这对于 BizTalk Web 服务发布向导生成的 Web 服务确实至关重要。 如果您想偷懒,那么不创建自己的带有新字段名称的架构也可以。 只要复制为您提供的入站架构,并将所有字段按照原样从源架构中映射出来即可,但至少要将副本保存到您的内部架构项目中,自己给它取个名称。 这样做还可以减少您需要的映射数。 假设您需要将三种类型的入站消息映射到三种类型的出站消息(可能不是现在,而是在将来)。 应用此技巧,您可以为每个入站架构创建一个到您的规范架构的映射,然后创建从您的规范架构到每个出站架构的映射。 这样一来只需构建和维护六个映射,而不是九个。 BizTalk Server 2006 是一个大型应用程序,幸运的是,它通常会为完成一些事情提供几种方法。 但是由于各种各样的原因,一些方法存在陷阱,多数有经验的使用者现在已经知道远远避开它们:
也就是说,您永远不应单击 BizTalk Web 服务发布向导中的第一个单选按钮;而应始终单击第二个按钮(请参见图 4)。 第一个单选按钮用于发布业务流程,第二个单选按钮用于纯消息处理解决方案,然而使用第二个选项发布业务流程也不失为一个好主意。
是的,这样一来就更复杂了,您肯定希望我们给出合适的理由。 理由是:接口。 如果您想要更详细的解释,那就是松散耦合! 这样做使您在更改业务流程时具有更大的自由性,无需中断调用方。 您可以将这一点看作是技巧 3 中概念的特殊化。 接下来我们讨论一下如何实现这一点。 下面是关于此向导要记住的一点: 进入步骤 2 后,您需要右键单击“Web 服务”对话框(请参见图 5)中的几乎每一项。 您将通过这种方式对您创建的 Web 服务及其方法进行命名。
开始在 Web 服务发布向导中执行此步骤之前,您需要在 BizTalk 中编译您的架构项目,以便您能够浏览到程序集。 实际上,正如您将在下面将看到的一样,在运行此向导之前继续部署您的解决方案是很有必要的。 此外,当您将业务流程发布为 Web 服务时,在构建和部署之前,您必须将业务流程端口类型上的“类型修饰符”属性从“内部”更改为“公用”。 如果您在最初创建此端口时没有更改此属性,那就要下相当大的工夫才能找到它(在“业务流程视图”中的“类型”窗格中突出显示此端口类型 — 而不是在显示不同属性集的“端口图面”上单击此端口)。 将架构发布为 Web 服务的另一个好处是您再也不用执行此操作了! 好了,让我们回到此向导,现在来看看图 5。 您需要右键单击“请求”和“响应”节点以选择 Web 消息所基于的架构。 您发布的业务流程通常会具有用于与 Web 服务的调用方通信的一个逻辑“接收”形状和一个“发送”形状。 这些形状将具有关联的消息,而这些消息将有一个架构类型(不需要多部分消息类型,只是建议采用)。 因此,您需要告知此向导使用您的面向外部的架构(记住技巧 3)。 然后,您将使用“接收”端口中的映射(入站和出站映射)将 Web 服务请求和响应架构转换为您的业务流程所期望的内部架构。 当客户端添加对您的 Web 服务的引用时,他将得到一个代理类,其中包括您的面向外部的架构的类型。 客户端将使用该类型创建并填充一个要发送到您的 Web 服务的请求消息。 此外,他将把来自您的 Web 服务的响应添加到一个对象中,此对象基于您在图 5 中指定的外部响应架构。 您还要记住在“接收”位置选择“XML 管道”,否则,BizTalk 将不会创建“BTS 消息上下文”属性 — 这些属性中的其中一个是传入 Web 服务消息的“消息类型”。 BizTalk 了解此“消息类型”(因为它曾通过管道中的“XML 反汇编程序”解析此消息类型并提升该属性),并使用该类型执行映射,以将请求转换为业务流程期望的类型。 真正重要的是,您在此向导中选择的操作名称必须与您在业务流程的逻辑端口中指定的操作的名称完全匹配。 完全接受每个名称的默认值是不可行的,因为业务流程设计器中默认的操作名称为“Operation_1”,而向导中默认的名称为“WebMethod1”(请参见图 5)。 用动词 — 名词格式(如 UpdateInventory)将其设置为具有特定意义的名称是一种不错的做法。 请记住,各种消息(甚至包括传入 Web 服务请求消息)是基于订阅发送到业务流程的。 当您将一个逻辑端口绑定到一个物理端口时(这是在将“请求响应”端口发布为 Web 服务时的一项要求(即使是通过架构发布),BizTalk 将为您创建一个自动订阅。 您可以从 BizTalk 管理控制台中通过以下方法查看订阅:在查询窗口中选择“Subscriptions”作为值。 订阅与通常为其他逻辑到物理端口创建的非“直接绑定”的 SOAP 略有不同。 BizTalk 为业务流程创建的常规订阅将如下所示: http://schemas.microsoft.com/BizTalk/2003/
system-properties.ReceivePortID == {some Guid}
AND
http://schemas.microsoft.com/BizTalk/2003/system-properties.MessageType ==
http://schemas.someschema#Root
AND
http://schemas.microsoft.com/BizTalk/2003/
system-properties.InboundTransportType != SOAP
OR
http://schemas.microsoft.com/BizTalk/2003/
system-properties.ReceivePortID == {some Guid}
AND
http://schemas.microsoft.com/BizTalk/2003/soap-properties.MethodName ==
{Inbound Operation Name on Logical Port}
您会发现,您键入到向导中用作 MethodName 的名称必须与业务流程中该逻辑端口的“操作”名称相匹配,否则,映射的 Web 请求消息将无法发送到业务流程。 当您进入向导中的“Web 服务项目”对话框时,请输入您的 Web 服务的 URL,例如: http://localhost/MyBizTalkWebService。 另外,您还可以让 BizTalk 在应用程序中为您创建接收位置。 因为您已部署了应用程序,所以在此您可以从下拉列表中选择它。 要了解有关以上所有内容的更多详细信息,请务必查看 BizTalk 帮助文件中名为“如何使用 BizTalk Web 服务发布向导将架构发布为 Web 服务”一文。 我记得我们说过 BizTalk 的核心就是订阅,但这并不是我们的最终答案。 通过后两个技巧提示,您会发现它的核心实际上是架构。 每个消息和每个映射都依赖于一个(或两个)架构,所以掌握架构对于熟练使用 BizTalk 至关重要。 如果您的 BizTalk 业务流程被发布为 Web 服务,那么您一定想要调整 BizTalk 使用的一些默认的 ASP.NET 参数。 实际上,这些参数会影响任何使用 CLR 的 BizTalk 项目,包括 XLANG。 BizTalk 会自动将这些建议的值乘以您拥有的 CPU 数。 具体步骤如下: 1. 在修改注册表前,首先对其进行备份。 2. 在记事本中创建一个包含以下代码的文件。 Windows Registry Editor version 5.00 [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\BTSSvc$BTSHOST\CLR Hosting] “MaxIOThreads”=dword:00000064 “MaxWorkerThreads”=dword:00000064 “MinIOThreads”=dword:00000019 “MinWorkerThreads”=dword:00000019 在以此方式创建了文件之后,您可以在其他运行 BizTalk 的计算机上轻松地重复使用它。 DWORD 值是以十六进制形式表示的(注册表脚本不使用标准的 0x 前缀表示十六进制值)。 3. 用您的计算机的 BizTalk 主机名称替代字符串“BTSHOST”。 您可以首先运行 RegEdit 来检查主机上的实际密钥名称。 4. 如果您将文件保存为 .REG 格式,那么双击此文件就能使其在您的 BizTalk 安装上运行。 5. 运行 RegEdit 并导航至 CLR Hosting 项以验证是否已正确添加了这些值。 请参阅 msdn2.microsoft.com/en-us/library/aa561380.aspx,以了解有关此优化过程及其他优化过程的更多详细信息;并参阅 msdn2.microsoft.com/en-us/library/aa475435.aspx,以查看有关针对低延迟消息处理的性能优化方面的优秀文章。 此技巧将帮助您从版本控制中找到解决方案并在另一个主机上构建它。 您的团队中的一些开发人员可能会用不同的驱动器号来设置其环境,将来您也可以更改您的驱动器号。 这确实非常简单,但是您可能还想了解这些点以后的情况。 建议让解决方案的第一个开发人员在 Visual Studio 解决方案文件 (.sln) 所在的文件夹中创建一个强名称密钥文件。 然后,创建该解决方案文件夹的子文件夹,用于按项目类型(如映射、管道、业务流程、内部架构、外部架构,以及您正在编译的任何 .NET 组件)存放每个 Visual Studio 项目。 顺便说一下,不管您曾经多少次听到此建议,都不必将其视为严格的规则。 如果您了解某些项目将来可能要一起修改(如映射及其架构),那么您就应将它们放在同一项目中,以减少必须重新部署的组件的数量。 在开发过程中,我们可以在解决方案中的各项目之间以及在各开发人员之间共享同一密钥文件。 (为运行环境创建安全的密钥文件是另一回事。) 只需将密钥文件与解决方案中的其他项目一起签入到版本控制中即可。 现在指明解决方案中每个程序集的密钥文件的路径。 对于 BizTalk 项目来说指明路径的方法是“..\..\..\Key.snk”,如图 6 中所示。 这一方法之所以可行,是因为 BizTalk 在名为类似 [YourSolution]\[YourProject]\bin\Development 的子文件夹中生成您的二进制文件。 因此,三个父目录跳转使 BizTalk 返回到您的包含密钥文件的解决方案文件夹。
注意,如果您要向您的解决方案中添加多个 .NET Framework 类库项目,而使用与 BizTalk 项目略有不同的目录结构对这些项目进行编译,那么用于设置密钥文件路径的 Visual Studio UI 一定不允许您输入相对路径。 一种解决方案是将 .csproj(针对 C#)或 .vbproj(针对 Visual Basic®)作为文本文件进行编辑。 搜索您在 Visual Studio 项目属性 UI 中浏览到的密钥文件的名称,然后用一个只包含一级父目录的相对路径(如,..\Key.snk)替换它。 在每次生成后,不要忘记将您的 .NET Framework 程序集添加到 GAC 中。 利用 Visual Studio 中的生成后步骤可以自动实现这一点。 它只需要完整路径和用引号引起来的字符串以及空格,格式如下所示(全部在同一行中): “C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe” /i “$(TargetPath)” /F 下面是一个非常有意义的小技巧。 BizTalk 帮助文件收录了 50 多个示例应用程序和脚本(安装在 SDK\Samples 文件夹中),您可以从中学习,对其进行修改和重新使用。 在以下页上您还可以找到 30 多个有用的 BizTalk 应用程序: msdn2.microsoft.com/en-us/biztalk/aa937647.aspx。 最后,BizTalk 博客作者指南(位于 GotDotNet.com)中涵盖了 400 多篇技术文章,其中包括许多有用的代码段和示例。 我将此博客作者指南的快捷方式保存到我的桌面上,这是在进入新的 BizTalk 领域时我首先会去看的地方之一。 我们承认,如果您已经知道了这一点,您可能会认为这个技巧提示没什么稀奇。 但考虑到除此之外很难找到专业水准的 BizTalk 示例代码,我们认为需要提醒您还是应该将它放在手边。 问题是,这些示例不光在您开始了解 BizTalk 时非常有用,它们还是可供反复查阅的宝贵参考资料。 顺便说一下,不要忘记经常回到 MSDN 查看 BizTalk 帮助文件,它是定期更新的。 您可以以 PDF 或压缩的 .chm 格式下载它,也可以在线浏览。 这可能是 Visual Studio 中最鲜为人知的功能了,它不需要安装 BizTalk。 XSLT 调试器使您能够在其运行时查看转换的结果、设置断点、检查局部变量和调用堆栈。 您甚至可以从 C# 或 Visual Basic 单步执行您的 XSLT 脚本。 图 7 显示了一个正在运行的转换示例。 我刚刚创建了一个空白的解决方案,并加载了以下这个将转换为 HTML 格式的很小的 XML 数据文件:
<?xml version=”1.0” encoding=”utf-8”?> <node attr=”debug” />然后,我进入 XSLT 文件(您可在图 7 的左窗格中看到)。它将创建一个 Hello World 文档,而如果节点元素有一个 attr 属性等于“debug”,它也会启动调试。 现在,将光标放在 XSLT 文件中(这一点很重要),将“输入”属性设置为您将要转换的 XML 数据文件的路径。 注意,在“解决方案资源管理器”窗口中右键单击 XSLT 文件名将显示一组不同的属性,这是“文件”属性,不是“文档”属性。 我希望上述过程更加直观,但是现在只要一学就能掌握。 如果说专业级人员与水平一般的人员有何区别的话,那就是探索精神。 您还可以设置“输出”属性,或体验中间窗格中所示的转换过程。 当 Visual Studio 中的活动文档为 XSLT 文件时,下拉 XML 菜单(位于主菜单栏上)包括 Debug XSLT 命令。 只需单击此命令即可查看转换代码,甚至深入查看子例程。 图 7 显示了程序执行完 xsl:if 测试后“监视”窗口中 @attr 的值。 正如您可从中间窗口的输出看到的那样,测试结果为 true,并且 <h1> 节点为输出。 考虑到当前公司正在开发的应用程序数量如此之大,使用较好的工具和技术尽快开发出可行的解决方案至关重要。 BizTalk 将帮助您高效地完成从概念到工作原型的过程,同时我们希望这些技巧提示将在您的工作中为您节省一些时间。 Marty Wasznicky(MCSE、MCSD、MCDBA、MCTS)是在 Microsoft Connected System 部门中负责 BizTalk Server 的地区项目经理。 他帮助客户和 Microsoft 合作伙伴采用和实施这一领域的 Microsoft 解决方案。 目前,他致力于研究面向软件的体系结构和企业服务总线技术。 他和妻子一起生活在洛杉矶。 Scott Zimmerman(MCSD、MCTS)是 Microsoft 的一名资深应用程序开发顾问,专门提供 BizTalk 和 .NET 方面的咨询。 他曾两次荣获 Web 服务设计奖,八次在世界飞碟锦标赛中夺冠。 他和妻子 Vera 一起生活在华盛顿特区。 8月7日 biztalk中使用.net class类型的消息(一)六. Biztalk如何处理.net class类型的消息... 5 七. Orchestration中从零开始新建一个xml类型的消息... 5 2. 在消息构造形状中构造新.net class类型消息... 5 3.2. 写一个从嵌入资源中读取xml文件内容的方法... 6 3.3. 在消息赋值形状中调用读取xml文件的方法... 7 一般来讲,在biztalk项目中使用的消息都是基于xsd的xml消息,因为这是biztalk最擅长处理的消息。Biztalk整个体系架构都是建构在xml基础之上,在biztalk内部用xml来处理消息,在message box的存储形式也是xml。 但是这并不表示biztalk只能处理xml消息,事实上,biztalk可以处理消息类型可以是平面文件、xml类型、可序列化的.net class或者其他任意的二进制数据流。 这里要讨论的是在biztalk中,尤其是在orchestration中如何使用.net class类型消息。 二. 消息的结构BizTalk中每条消息都可视为多部分消息,此消息可以由零个或多个部分组成。具有一个或多个部分的消息都具有一个标识为正文部分的部分(但是只能有个一部分标识为正文)。每个部分均由可表示 XML 文档、平面文件、序列化的 .NET 类或其他二进制数据流的二进制数据块组成。 一般的开发biztalk项目体验中,大多是新建一个消息,然后给这个消息指定一个xsd作为这个消息的类型。感觉上一个消息就只能指定一个xsd类型(也可以指定为一个序列化的.net class类型),跟一个xsd相关。其实这样通过这样方式建立的消息也是多部分消息,只是这个消息只有一个正文部分而已。 如果要建立真正有多个部分的消息类型,可以在orchestration view面板中的Types窗格中,展开Types,在Multi-part Message Types下新建一个多部分消息类型,在新建的多部分消息类型中可以建立多个部分,每个部分分别指定类型,还要指定其中的一个部分为正文部分。以后新建消息把消息类型指定为这个多部分消息类型,这个新建消息就是一个具有多个部分的消息了。
任何消息在biztalk的表现都是一个多部分的消息,不过一般的消息只有一个正文部分。 三. 消息的类型从上面的叙述可以得知,每个消息其实都是多部分消息,一般来讲每个消息至少有一个部分,也可能有多个部分,不管是只有一个部分还是有多个部分,其中必须有一个部分被指定为正文部分,整个消息的类型就由正文部分的消息类型决定。如果正文部分分别为以下情况时,如何确定消息的类型: 1. 平面文件平面文件进入到biztalk都会在接收管道中都会被平面文件拆装器拆解为xml的格式,有一个经过平面文件扩展后的xsd架构跟其对应,所以平面文件可以被认为也是xml的消息类型。参看下面的xml文档部分。 2. Xml文档Xml消息的消息类型由xml消息的命名空间(后接 # 符号)和xml消息根节点的名称组成。比如,一个xml消息的根结点的名称空间是“http://tempuri.org/samples/MessageType”,xml消息的根结点的名称是Message,那么这个消息的类型就是:http://tempuri.org/samples/MessageType#Message 3. .net class.net class类型的消息同样是由名称空间加上根元素名称组成。 .net class类型的消息的名称空间由class类的System.Xml.Serialization.XmlRootAttribute属性指定,在定义.net class类时可以在类名前加上相关attribute来指定这个类的一些类级别的attribute(请参考第五部分“.net class类型消息”中的详细定义一个.net class的完整代码),System.Xml.Serialization.XmlRootAttribute中用Namespace参数指定此类的名称空间。 定义.net class的类名就相当于xml类型的根元素。 “名称空间#类名”就是.net class消息的消息类型,跟一般xml消息一样,.net class消息类型也可以作为订阅消息的条件。 4. 二进制数据二进制数据流的消息类型需要由定制的接收管道把消息类型升级到消息的上下文,一般的二进制数据流的消息类型使用全限定的类型名。 这种情况这里不加讨论,如有必要,以后用专文来探讨二进制数据流消息的情况。 四. .net class类型的序列化Biztalk中的消息必须是可以序列化的,原因很简单,biztalk中消息不管是从外部进来,还是由orchestration产生的消息,统统都要被保存在Message Box数据库中,要能被保存在数据库中持久保存,消息就必须能被序列化。 还有一点,在长时间运行的orchestration中,运行的周期可能是一天、几天、十几天,甚至可能几个月。在这么长的时间中,一个orchestration实例由始至终都运行在内存中是及其不明智的做法,将耗费大量的资源。所以biztalk设计了一个dehydrat(分解)和rehydrat(重组)的机制,在orchestration实例空闲的时候,把orchestration实例和相关的状态和消息序列化保存到数据库,一旦有消息激活这个orchestration实例时,再把这个orchestration实例和相关状态消息反序列化到内存继续运行。 对.net class序列化Dotnet框架支持两种序列化方式,可以把类序列化为xml格式,也可以把类序列化为二进制格式。 二进制序列化是把对象转换成一串二进制的数据流,二进制序列化保持类型保真度,可以最真实的保存一个对象的全部状态。 Xml序列化是把对象转换成一个xml文档,XML 序列化仅序列化公共属性和字段,且不保持类型保真度。但对于biztalk应用中使用的.net class一般就是使用了类的公共属性和字段。 Biztalk内建直接支持.net class序列化,就是说在biztalk中使用.net class的消息不用开发者自己考虑.net class消息如何如何序列化和反序列化。Biztalk内建.net class消息序列化使用的是xml序列化方式。 二进制序列化方式不是biztalk内建支持的序列化方式,可以选择对.net class消息进行二进制的序列化,但是这需要开发者做更多的工作,需要写代码对消息进行二进制的序列化和反序列化,并定制自己的拆装器和组装器对消息进行序列化和反序列化的工作。 本文只讨论xml序列化的方式,对于二进制序列化的方式,如有必要以后再用专文讨论。 五. .net class类型的消息Biztalk中使用.net class需要考虑作为消息的特点,跟一般的类有些不同,一般要注意以下几点: 1. 类要设置为可序列化在定义类前加上表示可以序列化的attribute:[Serializable()] 2. 类中只包含公共属性和字段一般类中的公共属性和字段会被转换为xml文档中的元素,私有属性没什么意义,方法对转成xml也没什么意义。 3. 设置名称空间名称空间是构成消息类型的一部分,.net class转成xml也可以带有名称空间,看下面的代码,名称空间由定义类前的“[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://myURL")]”设置,表示这个类转成xml的Target Namespace是"http://myURL"。 另外类的名称就相当于xml的根元素名称,跟上面的名称空间一起组合成消息的类型。 如果没有设置名称空间,则此.net class消息的类型就是类名称。 4. 设置可分辨字段和升级属性在.net class类型中同样可以设置可分辨字段和升级字段。 4.1. 设置可分辨字段设置可分辨字段很简单,在需要设置的公共属性或字段前加上“[DistinguishedField]” attribute即可,表示这个公共属性或字段已被设置为可分辨字段。 4.2. 设置升级属性设置升级属性同xsd中设置升级属性类似,首先也先有一个属性架构,然后在需要升级为属性的公共属性或字段前用“Property”attribute指定关联到哪个属性。如下面的代码中的: [Property(typeof(netClassMessage_PromoteXSD.PropertyName))] public string PropertyName 这表示要把公共属性PropertyName升级,并关联到属性架构netClassMessage_PromoteXSD中的PropertyName属性。 5. 一个.net class消息类型的完整代码 1 六. Biztalk如何处理.net class类型的消息.net class类型的消息从外部进入biztalk,.net class类型消息在进入到biztalk时的存在形式必定是被序列化后的xml形式(这里只讨论xml序列化的情况)。 所以,对入站的.net class类型消息需要使用xml拆装器,.net class类型消息序列化后同样有名称空间和根元素,并且也跟一般的xml消息类型一样,名称空间和根元素也决定了这个.net class消息的消息类型。 同样,.net class消息根一般xml消息一样根据消息类型被路由到订阅此消息的orchestration。 .net class消息在orchestration中的存在形式还是xml形式,只有在消息构造形状中对.net class消息进行构造或赋值的时候,biztalk引擎会对消息做相关的序列化和反序列化的工作,以便对.net class消息进行相关的处理。在消息构造形状以外,.net class消息的存在形式就是xml。 七. Orchestration中从零开始新建一个xml类型的消息在orchestration,一个消息一旦被构造完成,这个消息就不再被允许有任何改动,就是不能被修改了。如果要修改这个消息,必须重新在消息构造形状中构造一个新消息,然后修改新的消息,新消息一旦出了构造形状就算构造完成。 构造xml消息和构造.net class类型消息有所不同,下面分别列出 1. 在消息构造形状中构造新xml消息l 通过map映射,map映射把源架构的消息映射到目标架构的消息,在转换的同时把消息值赋给了新的目标架构的消息,构造了一个新的消息。 l 通过消息赋值语句,赋值语句把同类型的消息赋给一个新的消息,构造了新消息。 l 通过把XmlDocument赋值给消息,xml消息的本质就是一个xml,所以可以把一个XmlDocument类型的变量直接赋给xml消息,当然,这个XmlDocument要是跟这个消息兼容的,XmlDocument要符合这个xml消息的架构。 2. 在消息构造形状中构造新.net class类型消息只能通过new操作符构造新.net class类型消息,.net class类型消息首先它是一个.net的类,可以通过new操作符来新建一个此类的对象。 3. 从零开始构造新xml类型消息如果一个.net class类型消息从外部进入到orchestration,在orchestration中对这个.net class类型消息进行处理转换,无外乎会有两种情况:一种是把这个消息赋值给同类的或者另一个.net class的消息,然后对新消息进行处理。更多的情况是,要把这个.net class消息转换成一个xml的消息,然后处理这个xml消息。 对于第一种情况简单,只要用new操作符新建一个.net class类型的消息,然后进行赋值或者修改新消息的属性等等即可进行相关处理。 但是对于第二种情况就比较麻烦,用map吧,map只支持从xml消息到xml消息的转换,不支持.net class消息的转换。用赋值语句吧,xml类型和.net class类型不是同类型,不能直接赋值。如果xml消息类型可以用new操作符新建一个新消息多好,可是,目前还不行,不能跟一般的.net类一样通过new操作符来新建一个对象。 但是这里又必须要先新建一个xml类型的消息,有了这个xml消息,才能把.net class类型的消息的相关属性转移到这个xml类型的消息。 从零新建一个xml类型消息(from scratch)在biztalk中是个比较麻烦的事,上面提到的通过把XmlDocument赋值给xml消息好像是目前的唯一方法。 这种情况大致需要做以下的事情: 在orchestration中新建一个XmlDocument类型的变量,比如变量名为xmlDocVar,要构造的xml消息为xmlMsg,然后在消息赋值形状表达式中 xmlDocVar.LoadXml(“xml字符串”); xmlMsg = xmlDocVar; 其中的“xml字符串”是一个跟xmlMsg消息的架构相容的一个xml样例的文本。赋值以后,就可以对xmlMsg进行各种操作了,把可分辨字段或升级属性修改为需要的值。 把xml样例文本直接放在硬代码里面不太方便,也不灵活,比较好的做法是把xml文本放在一个xml文件中,从文件中读取xml文本。 上面的例子可以变成这样: xmlDocVar.LoadXml(“xml文件名”); xmlMsg = xmlDocVar; 其中的“xml文件名”是xml样例文件的完整路径,这样如果要修改xml样例文本直接修改这个文件就行了,不用去改硬代码。 但是这样会给部署带来问题,项目部署以后这个xml文件往哪放,biztalk项目其他的资源都是dll,被部署到GAC中,就这么个xml文件孤零零的要找个地方放,虽说也没什么问题,但总觉得不很舒服,至少这个办法不够优雅。 其实还有一个更舒服的办法,就是把这个xml文件作为一个嵌入资源,把它嵌入到项目生成的dll中,一起被部署到GAC中,读取的时候也到这个dll中读取。这需要做些额外的编码工作,步骤如下: 3.1. 设置xml文件属性在解决方案资源管理其中,选中这个xml文件,查看它的属性,把“生成操作”这一项,改成“嵌入的资源”,这样这个xml文件就会被嵌入到编后的dll文件中。 3.2. 写一个从嵌入资源中读取xml文件内容的方法在跟这个xml文件同一个项目中,新建一个类,这个类只有一个静态方法,这个方法根据提供的xml文件名,在assembly中找到嵌入的xml文件,返回这个xml文件对应的XmlDocument的对象。 这个方法的完整代码如下: 1 3.3. 在消息赋值形状中调用读取xml文件的方法在orchestration的消息赋值形状中,用类似下面的语句给新xml消息赋值: XmlMsg = netClassMessage_netClass.EmbeddedResourseProcessor.GetXmlDocResource("xml文件名"); 参考资料: l Using .NET Classes for Orchestration Message Types l BizTalk messages based on .NET types instead of Xsd schemas l Constructing BizTalk 2004 XML Messages (In an Orchestration) biztalk中使用信封(Envelope)消息一. 信封(envelope)的作用一般的传送消息就传送一个单个的消息,这个消息架构由一个消息schema定义。但是有时候可能需要同时传送多个消息到同一个接收点,如果一个个的传送就显得效率比较低,biztalk支持信封消息功能。所谓信封消息是将多个消息打包成一个交换(interchange),就是用信封(envelope)将多个消息封装起来,形成一个貌似单个xml消息的功能。 Biztalk收到信封消息,将信封拆开,分别取出其中的消息正文,然后单独的处理每个消息正文。 信封消息还可以在信封部分定义升级属性,信封部分的升级属性可以被带入到所有的消息正文的上下文中。 二. 信封(envelope)的设置要使用信封,首先要定义信封的架构schema,并指示哪些部分是属于信封部分,哪些部分是要被装入的消息正文的位置。 定义信封架构形式上跟定义一般的schema没什么区别,只是需要设定一些特殊的属性。 要把一个schema设置为信封架构,可以在这个schema的<schema>标签的“Envelope”属性设为“Yes”,表示这个架构是个信封架构。 <schema>标签下可以包含多个根元素,表示可以有多个信封的架构,但是使用时只能使用其中一种信封架构。一般就定义一个根元素。 根元素有个“Boby Xpath”属性,用来指示这个信封架构中的哪个位置是消息正文的位置。 看下面这个信封架构,根元素Envelope的“Boby Xpath”属性设置为“Envelope/Boby”,表示消息正文在Boby元素里面的位置,除此之外,别的部分都属于信封部分。
<any>元素相关属性说明: 1. Process Contents此属性指示消息正文验证的级别,有四个可能的的值: Lax ―― 宽松的验证级别,这种设置,只有在收到的消息正文对应的schema在信封schema中已经被import的情况下才会验证此消息正文,否则不验证。 Skip ―― 不验证收到的任何消息正文。 Strict ―― 严格的验证级别,这种设置,所有可能收到的消息正文对应的schema都必须在信封schema中预先导入,对每个消息都要进行验证。 Default ―― 缺省设置等同于“Strict” 如果<any>元素的Process Contents设置为“Strict”或者“Default”(缺省设置为Strict),按文档的描述,在消息实例进入管道后,XML拆装器会寻找每个跟对应<any>位置的元素的名称空间相对应的schema验证这个元素,如果在信封schema中不到相应的schema被import,则这个元素会验证失败。但是实际测试中发现<any>元素Process Contents的“Strict”设置并没有起作用,在信封schema中没有import输入的消息实例名称空间对应的schema,消息依然传送成功。原因不明。 2. Namespace表示可以接受的消息正文的名称空间,如果指定了名称空间,则只有属于这个名称空间的消息正文可以被接受,其他的不被接受。如果不设置名称空间,表示接受任何名称空间的消息正文。 3. Max Occurs、Min Occurs表示消息正文可以出现的最小次数和最大次数,正文可以再次重复出现。但是这两个属性好像对<any>元素不起作用。 在实际测试中,设置<any>元素的这两个属性都为1,这个信封消息应该只能包含一条正文消息,但是测试信封消息中包含两条消息正文,依然被接受,被正常拆封,正常的把两条消息正文路由到订阅的服务。 三. 信封(envelope)的拆装信封的拆装是在pipeline的第二阶段拆装阶段(Disassemble)。 接收位置的接收pipeline要选XmlReceive(其中包含XML Disassembler),或者自建pipeline并使用XML Disassembler,因为只有XML Disassembler 才具有拆装信封消息的功能。 看一下XML Disassembler是如何处理信封消息的,消息进入到消息管道,经过第一阶段的解码后到达拆装阶段,XML Disassembler对消息的处理步骤如下: 1. 删除信封首先根据消息类型在已部署的schema查找,看这个消息是否能跟哪个信封架构匹配,如果匹配不到信封架构,则继续匹配一般架构。如果匹配到信封架构,则根据信封架构的定义把所有的消息正文提取出来,去掉信封。 2. 拆装消息正文和升级属性分别处理每一个消息正文,根据每个消息正文的消息类型(名称空间#根元素名)在已部署的schema中匹配,匹配到后,给这个消息建立上下文,先把信封部分升级的属性加入到消息上下文,然后根据消息架构升级这个消息本身的属性。 每条消息都如此处理,直到都处理完毕。如果一切正常,每个消息都匹配到合适的schema,则biztalk把所有的消息正文作为独立的消息发送到MessageBox等待路由。 如果匹配消息中出现问题,比如某一条消息没有匹配到schema,那么会有两种情况出现,跟接XML收管道的设置有关。 XmlReceive管道有个RecoverableInterchangeProcessing属性(在biztalk管理器中,查看相应的接收位置的属性,在接收管道的右边有个省略号按钮,点击可以配置一些接收管道的属性,RecoverableInterchangeProcessing属性就在这里),这个属性缺省为false,这时如果消息匹配中有某一条消息出现没有匹配到schema的情况,则所有的消息正文都不被处理,整个消息被挂起。如果这个属性设置为true,则只有没有匹配到schema的那个消息正文被挂起,其他的消息正文被发送到MessageBox等到路由。 biztalk的例子一. 随机文档的tutorial在文档中的位置:Help > Getting Started > BizTalk Server 2006 Tutorials 这套教程一共分五个教程,从简单到复杂,循序渐进,逐步的把biztalk涉及的技术呈现出来。步骤十分详细,很容易上手,十分适合刚开始学习biztalk的人跟着做,作完后就会对biztalk有个比较完整的认识。建议初学者从这套教程开始。 二. 随机文档的sample in the SDK在文档中的位置:Help > Development > Samples in the SDK SDK中提供了超过30个例子,包含在下面这些类别中,每个类别中可能有几个例子。 Adapter Samples - Development Adapter Samples - Usage Admin (BizTalk Server Samples Folder) Application Deployment (BizTalk Server Samples Folder) Business Activity Monitoring (BizTalk Server Samples Folder) Business Rules (BizTalk Server Samples Folder) HWS (BizTalk Server Samples Folder) Messaging (BizTalk Server Samples Folder) Orchestrations (BizTalk Server Samples Folder) Pipelines (BizTalk Server Samples Folder) SSO (BizTalk Server Samples Folder) XML Tools (BizTalk Server Samples Folder) 三. 随机文档的walkthrough文章中就某些特殊的特性给出的一些step by step的例子,叫做演练。 在文档中的位置:分散在文档的各处,可以用搜索功能搜索“Walkthrough”来找出这些例子。这些例子包括: Adapters Walkthrough: Creating a BizTalk Application That Uses the MQSeries Adapter Walkthrough: Creating a BizTalk Application That Uses the POP3 Adapter Walkthrough: Module 1 - Sending and Receiving Messages with the Windows SharePoint Services Adapter Walkthrough: Module 2 - Integrating Office with the Windows SharePoint Services Adapter Walkthrough: Module 3 - Accessing SharePoint Properties from an Orchestration Walkthrough: Using Multiple SQL Adapters in an Orchestration Walkthrough: Disassembling Result Sets Using the SQL Adapter Walkthrough: Using the SQL Adapter with a Stored Procedure in an Orchestration Walkthrough: Using the SQL Adapter with an Updategram in an Orchestration Application Deployment Walkthrough: Deploying a Basic BizTalk Application Business Activity Monitoring (BAM) Walkthrough: Consuming BAM Data Walkthrough: Creating a BAM Activity with ODBA Walkthrough: Deploying the BAM Observation Model Walkthrough: Creating a BAM Observation Model with the BAM Add-In for Excel Walkthrough: Creating a Tracking Profile Pipelines Walkthrough: Using XML Envelopes (Basic) Walkthrough: Flat File Disassembly Using a Header and Trailer Schemas Walkthrough: Creating a Flat File Schema From a Document Instance 四. 随机文档中的Scenarios for Business SolutionsBiztalk SDK中除了提供上面那些简单例子外,还提供了三个重量级的例子,应该说是三个完整的商业应用解决方案。 在文档中的位置:Help > Planning and Architecture > Scenarios for Business Solutions 这三个方案分别是: 1. Service Oriented Solution面向服务的解决方案2. Business Process Management Solution业务流程管理 (BPM)解决方案3. Business-to-Business Solution企业对企业(B2B)解决方案五. MSDN提供的例子微软biztalk产品开发团队应biztalk开发者的要求开发了一些例子,并根据开发者的反馈和要求不断推出新的例子,你也可以写信给他们告诉他们你想要看到什么例子。 BizTalk Server Code Samples 网址:(http://msdn2.microsoft.com/en-us/biztalk/aa937647.aspx) 目前已有的例子如下(不断更新中): Publishing and Consuming Web Services with SOAP Headers Consuming Web Services with Array Parameters Extending the BizTalk Server Administration Console Inserting XML Nodes from Business Rules Using Enterprise Library 2.0 with BizTalk Server Using Long-Running Transactions in Orchestrations Mapping to a Repeating Structure Recoverable Interchange Processing Using Pipelines Using the Table Looping Functoid Using the Value Mapping and Value Mapping (Flattening) Functoids Direct Binding to an Orchestration Direct Binding to the MessageBox Database in Orchestrations Using a Custom .NET Type for a Message in Orchestrations Writing Orchestration Information as XML Using the ExplorerOM API Correlating Messages with Orchestration Instances Atomic Transactions with COM+ Serviced Components in Orchestrations Exception Handling in Orchestrations Implementing Scatter and Gather Pattern Using the SQL Adapter with Atomic Transactions in Orchestrations 六. CodeProject网站CodeProject是一个Visual Studio .NET开发者社区,在这里你可以得到别人优秀的源代码,也可以把你自己的得意文章或源代码跟大家分享。 在CodeProject有biztalk的目录,在里面有很多优秀的文章和例子。 网址:http://www.codeproject.com/biztalk/ 目前有的文章和例子有: How to invoke a BizTalk Orchestration using Office InfoPath 2003 How to create a self-routing message using Dynamic Ports in BizTalk Server 2004 Asynchronous call to web services in Biztalk orchestrations Using a SQL Adapter in BizTalk Server 2004 UnzipDisassembler - A custom pipeline component for BizTalk Server 2004 How to split an XML message in BizTalk 2004 using Document and Envelope Schemas Debatching large messages and extending Flatfile pipeline disassembler component in Biztalk 2006 A Quick guide to handling exceptions in BizTalk Orchestrations HOW TO Auto-generate schemas using Well-formed XML documents and DTD documents in Biztalk How To Troubleshoot Schemas in BizTalk Server 2006 Building BizTalk Server 2006 Currency Converter Custom Functoid HOW To Compensate a Transaction in a BizTalk Orchestration Creating an Automated Purchase Order Workflow using BizTalk Server 2004 Using the MSI installer wizard for deploying applications created in BizTalk Server 2006 Keep The Orchestration Simple (KTOS) - A BizTalk Pattern Creating Flat File schemas using the BizTalk Server 2006 Flat File Schema Wizard Calling a Web Service with Custom Parameters from an Orchestration in BizTalk Server 2004 Using the %SourceFileName% macro to create a custom send file name in BizTalk 2004 biztalk项目设置注意事项适用版本:biztalk 2006 适用环境:开发测试环境 在开发过程中,在开发环境中,一定会是一个对项目不断的修改、编译、部署、测试,查看测试结果,发现有问题,然后回到开发环境再修改、编译、部署、测试的反复过程。所以开发环境的项目设置一定要适合这种反复的重新部署的特点,可以方便的修改,编译,重新部署,修改的内容立即能够生效,然后马上测试。 下面的一些设置注意事项可以帮助biztalk项目在开发环境中达到以上目的 一、 设置强名称程序集的密钥文件 在项目属性页中,在左边的树行结构中选择 “通用属性”-“Assembly”,然后在右面的内容面板中找到“Strong name”-“Assembly Key File”项,在这里点击后面的省略号按钮找到密钥文件(密钥文件怎么生成见biztalk文档)。因为biztalk项目生成的程序集必须安装到GAC中,所以程序集必须是强名称的。 二、 设置项目的部署属性 在项目属性页中,在左边的树行结构中选择 “配置属性”-“Deployment”,然后看右面的内容面板中,各个属性的含义: Server ―― 把项目部署到哪个服务器,开发环境中,选择本地机器(local) Configuration database ―― biztalk配置数据库,选BizTalkMgmtDb Application name ―― 这个项目部署到biztalk中所属的应用程序名,所有属于同一应用的项目(一般就是一个解决方案中的项目)都应该部署到一个应用程序中。 Redeploy ―― 本项目是否可以重新部署。指示如果在GAC中已经有了本项目的程序时是否先删掉已经存在的程序集,然后部署新的程序集。开发环境中选“true”,以便可以反复的重新部署程序集。 Install to Global Assembly Cache ―― 是否将本项目生成的程序集安装到GAC中,当然要,选true Restart Host Instance ―― 是否重启主机实例。这一个选项特别重要,下面详细解释这个选项。 在dotnet托管环境中,应用程序域是代码隔离的边界,就是说每个应用程序域之间是相互隔离,不能直接通讯,也不会相互干扰的。一般一个应用程序域需要由一个运行库宿主创建,通常是一个exe的文件,这个运行库宿主创建应用程序域,在运行中使用到其他程序集会被加载到这个应用程序域内,用到多少程序集就会加载多少,但是这些被加载的程序集在使用完后不会马上从内存中卸载,必须等整个应用程序域运行完毕后,一起从内存中卸载。 通常Biztalk有两个主机实例:进程内主机实例BizTalkServerApplication,独立进程主机实例BizTalkServerIsolatedHost。BizTalkServerIsolatedHost主机实例是给web services等进程外的服务使用的。一般biztalk项目生成的程序集都会被部署到BizTalkServerApplication实例中。 BizTalkServerApplication主机实例就是一个BTSNTSvc.exe运行库宿主,所以一旦有biztalk项目部署运行后,相关的程序集都会运行在BTSNTSvc.exe创建的一个应用程序域内,并且如果BTSNTSvc.exe服务不停这些程序集就不会被卸载。 将Restart Host Instance属性设置为true后,每次重新部署biztalk项目后,biztalk会重新启动BizTalkServerApplication主机实例,原先已经运行的那些程序集也会被卸载。 刚开始学习biztalk的朋友,大多都会碰到一些问题,就是第一次部署biztalk项目后比较顺利,测试运行,发现问题修改后重新部署就会出现几种问题: 1、 部署不成功。因为上面提到的那个“Redeploy”属性没有设置为true。 2、 重新部署后结果还是老样子,好像修改没有起作用。一般都是因为修改前的程序集已经在内存中运行,没被卸载,实际运行的还是老版本的程序集,当然结果也就是老样子。这时重启一下BizTalkServerApplication主机实例即可。或者把上面谈到的Restart Host Instance属性设置为true,让每次重新部署都自动重启一下BizTalkServerApplication主机实例,把原来的程序集从内存中卸载。 三、 配置管理 一般biztalk的解决方案都是有几个项目组成,比如biztalk文档中tutoial 1“Enterprise Application Integration”这个例子,就是由两个项目组成:EAIOrchestrations和EAISchemas。大些的项目还可能包括更多的项目,这时可能需要配置一下,哪些项目是需要生成的,哪些是不用生成的;哪些项目是要部署的,哪些项目是不用部署的。 在解决方案上点击右键,选“配置管理器”,弹出如下窗口:
对于非biztalk的项目,上图中的“ClassLibray1”是一般的类库项目,可以被biztalk项目引用。非biztalk的项目不能被设置为可部署。 四、 非biztalk项目的部署 biztalk项目有的地方可以写代码,代码中可以引用一般托管类的代码,所以biztalk解决方案中一般会包含非biztalk项目的类库项目,这些类库项目中的类被biztalk项目中的代码调用。因为biztalk项目的程序集是强名称的,被部署在GAC中,所以被引用的类库的程序集也必须是强名称的并部署到GAC中。 非biztalk项目不能跟biztalk项目一样通过简单设置就能被部署到GAC中,但是可以通过一定的方法做到。以下是Snega (水滴石穿)提供的方案 1、 在类库项目中->属性->生成事件->预生成命令 PATH = C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin GacUtil /u $(TargetName) 这一设置在生成类库项目前先把原来已经在GAC的本项目的程序集卸载,以便新的程序集装载进GAC。 2、 在类库项目中->属性->生成事件->生成后命令 PATH = C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin GacUtil /i $(TargetPath) 这一个设置把生成好的类库程序集装载到GAC中。 或者: 五、 总结 安装以上所述设置biztalk的项目,修改biztalk项目,重新部署会很方便。 但是有一点要注意,再重新部署一个解决方案时,这个解决方案在biztalk所在的那个应用不要有挂起的实例,有的话把它终止掉然后再进行重新部署。 一般在开发环境中进行修改,然后编译解决方案,重新部署解决方案,就可以测试新的修改的效果了。不用手工去做停止应用、删除应用,重启服务等等工作,这些上面的设置都已经给解决了。 biztalk消息引用和引用计数一. 消息引用概述Biztalk完全基于消息和消息订阅的机制,由发布服务器发布消息,消息代理根据订阅服务器的订阅条件判断发布的消息是否跟某个或某几个订阅匹配到,如果有匹配到消息代理机会把消息路由到所有的订阅了此消息的服务实例。如果没有匹配到就会抛出一个异常,告知这个消息没有订阅者订阅,无法路由。 可以看出消息跟订阅消息的服务实例是成对出现的,姑且称之为服务实例消息对。一条消息如果只有一个订阅者,那这个消息跟这个订阅的服务实例就是一对。如果一条消息有多个订阅者订阅,那么这个消息要同时发送给所有的订阅者,就会出现多对服务实例消息对。 同一个消息在biztalk的messagebox数据库中只保存一个副本,多个服务实例订阅同一消息时,各个实例跟这个消息之间是引用关系,不会各自创立一个消息副本。 消息保存在messagebox数据库的spool表和parts表中。SPOOL 表的内容是消息的总体性描述和消息的上下文属性,一条消息在此表中为一条记录。PARTS 表存放多部分消息的各个部分,一个部分在此表中占一条记录。哪一个是正文部分由spool表中的uidBodyPartID标识。 消息进入到biztalk系统后,路由到某个服务实例,就跟这个服务实例有了引用关系,在这个服务实例运行期间消息不能从数据库中删除。一旦这个服务实例运行完毕,这个消息也就不再使用,可以删除。如果有多个实例同时引用这个消息,则必须引用此消息的实例都完成后才能删除消息。 为了反应消息跟实例之间的引用关系,判断一个消息是否还在被某个实例引用,biztalk有着一套消息引用计数功能。 Biztalk采用消息引用计数表来记录消息的引用数,分为两种情况:如果消息只被一个订阅服务订阅,采用本地引用计数机制,如果消息被多个订阅服务订阅,采用全局引用计数机制,下面分别分析这两种情况下的消息引用计数功能的实现。 二. 本地引用计数如果消息只有一个服务订阅,消息代理调用bts_FindSubscriptions存储过程时发现一个订阅,这个存储过程返回一条记录。 这种情况下,消息代理会在本地引用计数表BizTalkServerApplication _MessageRefCountLog中记入实例对消息的引用计数。 BizTalkServerApplication_MessageRefCountLog表有三个字段: uidInstanceID ―― 服务实例的guid uidMessageID ―― 消息的guid nRefCount ―― 引用计数(值为1,表示引用了1次) 一旦服务实例运行完成,执行如下两个动作: l 把BizTalkServerApplication_MessageRefCountLog相关记录删除 l 将此消息的guid写入MessageZeroSum表中,MessageZeroSum表中保存不再被引用的消息的guid,表示这些消息没有服务实例引用了,可以删除。MessageBox_Message_Cleanup_BizTalkMsgBoxDb作业最终就是根据这个表来清理无用的消息。 三. 全局引用计数如果消息有多个服务订阅,消息代理调用bts_FindSubscriptions存储过程时发现多个订阅,这个存储过程返回N条记录。 这种情况下,消息代理会把消息引用计数记入到全局消息引用计数表MessageRefCountLog1或者MessageRefCountLog2中,这两个表结构一样,只是一个表是活动的表,另一个作为备用表。至于当时哪个表处于活动状态有ActiveRefCountLog活动引用计数表的相关字段指示。 MessageRefCountLog表的结构: uidMessageID ―― 消息guid uidInstanceID ―― 服务实例guid snRefCount ―― 引用计数。此字段可以为正负,正表示被引用,负表示被放弃引用。 ActiveRefCountLog表的结构: 当N个服务订阅了同一个消息,消息代理记入一条引用记录:uidMessageID为这条消息的guid;uidInstanceID为空,因为有多个服务订阅;snRefCount设为N,表示这个消息被引用N次。 每个服务各自运行,一个服务完成后就会在此表中增加一条记录:uidMessageID为这条消息的guid;uidInstanceID为空;snRefCount设为-1,表示减少一次引用。如果所有服务都完成后,将会增加N条引用计数为-1的记录,正好跟正的N计数对消。 四. 总结Biztalk中消息如果不特别的进行清理的话,将会一直堆积下去,不管这个消息是否已经不再有用。所以一般需要定期对messagebox数据库中的消息进行清理,把一些过期的无用的消息清理掉,以防大量无用的消息占用很多硬盘空间,也给系统的性能带来负面影响。 Biztalk对消息的引用有完整的记录,可以从这些记录中获知哪些消息已经不再使用,系统可以根据这些信息来清理消息。 关于清理消息biztalk本身提供了几个相关的作业。MessageBox_Message_ManageRefCountLog_BizTalkMsgBoxDb和MessageBox_Message_Cleanup_BizTalkMsgBoxD,它们就是根据这些引用计数的统计来完成清理消息工作的。关于这两个作业另外写文章介绍。 biztalk消息以及消息订阅发布路由机制(四)-消息的轮询和执行一. 消息的轮询和执行1. 轮询机制消息路由到MessageBox数据库中,只是在数据库中写入了相关记录,表示哪个消息需要由哪个服务实例去执行,并没有付诸实施,还需要在进程中实实在在的去实例化这个服务对象,运行服务实例对象,并把消息交给这个实例对象处理。 轮询主机队列是由订阅服务器的那些类先实例化为对象后,由服务实例去查询主机队列中的消息队列,找到是自己订阅的消息就拿过来处理,否则继续轮询下去。 服务实例轮询的时间间隔和同时运行的线程数由在BizTalkMgmtDb管理数据库中的adm_ServiceClass表中设置,此表中有两个字段: MaxReceiveInterval ―― 设置此类服务连续轮询数据库的时间间隔 MaxDequeueThread ―― 最大出列线程的数目 adm_ServiceClass表中目前有四条记录,表示有四类可用的订阅服务类别。前面章节已经有介绍,分别是: XLANG/s – 业务流程(orchestration) Messaging InProcess – 表示一般的发送端口、Solicit- Response发送端口 MSMQT – MS消息队列 Messaging Isolated –表示请求/响应(Request-Response)接收端口,目前基本上就是指HTTP和SOAP的Request-Response双向接收端口。 每个服务相应的MaxReceiveInterval字段表示此类服务轮询主机队列的时间间隔(单位毫秒,默认轮询间隔都是500毫秒),就是说每隔这么多时间就要新开一个线程实例化一个这个服务类,这个实例去查询主机队列。 MaxDequeueThread字段表示同时处理本类服务的线程数,就是可以同时生成多少个本类服务的实例,去查询主机队列(默认都是5个)。这些实例去查询主机队列时有锁定机制进行保障,保证具体某一个时刻只能有一个实例对主机队列就行操作。 2. 轮询过程2.1. 产生轮询的服务实例一类服务的轮询包括两种情况,一种是激活订阅的情况,一种是实例订阅的情况。 激活订阅的情况,Biztalk先按照adm_ServiceClass表中规定的某一服务类的轮询间隔时间一次性生成MaxDequeueThread字段规定的多少个本类服务实例,就是新建多个线程,每个线程把某个服务类实例化为对象,由这些对象去查询主机队列。 这类实例是用来查询激活订阅的,因为激活订阅需要有新的实例来运行接收消息。 实例订阅的情况,原先“请求/响应接收端口”服务实例在接收消息发并发布出去后,本身会产生一个对返回消息的订阅,这就是实例订阅的情况。这类服务实例也会对主机队列进行轮询,查看是返回消息是否已经路由过来了。 2.2. 查询主机队列中的消息2.2.1. 激活订阅的实例每个新生成的服务实例都是由一个线程实例化并在其中运行,每个线程都有一个线程的guid来标识这个线程,就是说每个服务实例都可以对应到一个由线程guid标识的线程。运行中的服务实例到主机队列中进行查询,查看是否有可以处理的消息,主机队列要符合一个条件: l 主机队列中订阅消息的服务类型跟本服务实例类型一致 运行的服务实例找到符合条件的,转入下一步,处理消息。 2.2.2. 实例订阅的实例实例订阅的时候,同样在线程中运行的服务实例去查询主机队列,同样查看是否有路由到自己这的消息,主机队列要符合两个条件: l 主机队列中订阅消息的服务类型跟本服务实例类型一致 l 主机队列中服务实例uidInstanceID字段值跟本服务实例的uidInstanceID一致。 如果有符合条件的消息,转入下一步,处理消息。 2.3. 处理消息轮询的服务实例一旦在主机队列中匹配到待处理的消息,开始处理消息,大致过程如下: l 把线程的guid写入到实例表的uidProcessID字段,表示这个实例由本线程进行处理。 l 将实例表的nState字段置为2,表示实例在运行中 l 根据主机队列表指示的消息guid,到spool表和parts表中获得消息具体内容进行处理 l 删除主机队列中的本条记录,表示此消息已经开始处理。 l 在服务实例运行过程中,原先在主机队列表中服务实例跟消息实例是有关联的,在删除主机队列中的本条记录后,服务实例跟正在处理的消息就失去了关联,故此,biztalk在删除主机队列记录的同时,把跟本实例的关联的消息guid记入到InstanceStateMessageReferences_BizTalkServerApplication实例与消息引用表,这个表主要字段: 2.4. 消息处理完毕后服务实例处理一个消息,处理完毕后要做的工作: l 如果是“请求/响应接收端口”服务请求部分订阅产生的激活订阅服务实例完成后,服务实例在实例表中不删除,依然保留,以便这个实例继续处理返回的消息。 l 如果是一般的激活实例或“请求/响应接收端口”服务响应部分订阅产生的实例订阅服务实例完成后,会把这个服务实例的记录从实例表中删除。 l 删除InstanceStateMessageReferences_BizTalkServerApplication实例与消息引用表中跟此实例相关记录。 biztalk消息以及消息订阅发布路由机制(三)-消息发布和路由一. 消息发布和路由消息的发布有几种情况,上面讲述发布服务器时说过的三种发布服务都可以发布消息,他们的发布原理基本一致,这里以适配器发布消息为主进行描述消息发布和路由的过程。 上面的消息流程章节中描述了消息从接收端口接收进入biztalk后的处理过程,但是消息如何进入到MessageBox,如何路由到订阅此消息的服务的过程还没详细说明,这里将详细描述消息的发布和路由过程。 消息代理处理整个消息发布和路由的过程。 消息在经过了接收管道处理后,相关的属性已经升级到消息上下文(使用了默认接收管道XMLReceive或自建的管道中使用了xml拆装器的情况下biztalk会根据schema升级属性,使用默认接收管道PassThruTransmit不会升级属性)。 消息代理获得消息后,先把消息的属性写入到MessageBox数据库的消息属性表MessageProps表,包括了此消息的系统属性和从消息本身升级的属性。 MessageProps表的结构如下: uidBatchID -- 消息批次id。此字段还不是很清楚,可能是一次可能处理多个消息,同一批进行处理的作为一个批次。 uidMessageID -- 消息的uid uidPropID -- 这个消息的某一个属性,属性都用guid来表示。 vtPropValue -- 这个属性的值 1. 调用bts_FindSubscriptions过程消息代理调用bts_FindSubscriptions存储过程来查找订阅了消息的相关订阅。这个存储过程把订阅表和各个订阅谓词表定义的订阅跟消息的属性表中的属性比较,找出所有所有符合此消息条件的订阅,返回到一个结果集中。这个结果集中最主要包含字段有:订阅guid(uidSubID)、此订阅所属主机实例名(nvcApplicationName)、消息guid(uidMessageID),服务的guid(uidServiceID)、服务实例的guid(uidInstanceID),结果集表示订阅表中的哪些订阅匹配到了这个消息,每个订阅了此消息的订阅为一条记录。如果一个消息被两个服务订阅了,这个结果集中将会有两条记录。 消息代理根据这个结果集,对每条记录调用存储过程bts_InsertMessage。以下的步骤,结果集中的每条记录都要执行一遍。 2. 第一次调用bts_InsertMessage过程这次调用bts_InsertMessage过程主要任务是调用int_EvaluateSubscription过程。int_EvaluateSubscription过程根据当前处理的订阅所属的应用主机实例名,再调用对应此主机实例的存储过程进一步处理,调用的存储过程名称是“int_InsertIntoQueue_主机实例名”这样的命名形式,比如服务属于BizTalkServerApplication主机,则调用int_InsertIntoQueue_BizTalkServerApplication。 存储过程“int_InsertIntoQueue_主机实例名”的作用: 2.1. 激活订阅时,新建实例guid如果是订阅类型是激活订阅,即uidInstanceID为空,说明需要新建一个处理订阅消息的服务实例,这一步骤新建一个guid,作为新建实例的实例guid。 之后要新建实例记录,即在实例表中插入一条实例记录表示要处理这条消息的实例。 Instances实例表主要字段: 主要字段 含义 uidAppOwnerID 所属主机实例的guid uidInstanceID 此服务实例的guid uidServiceID 此服务的guid uidClassID 此服务的类型guid uidProcessID 运行这个实例的线程guid nState 实例状态: 2.1.1. 订阅状态为活动时(状态码为1)如果订阅的状态是处于正常的活动状态,则在Instances实例表中插入一条记录,只记入四个字段:uidAppOwnerID, uidInstanceID, uidServiceID, uidClassID, nState。uidAppOwnerID为此主机实例的guid,uidInstanceID为新建的服务实例的guid,uidServiceID为订阅消息的服务guid,uidClassID为订阅服务的类型guid,nState实例状态为256,表示已计划要运行。 2.1.2. 订阅状态为停用时(状态码为2)如果订阅的状态是处于停用状态,在插入Instances实例表(插入的状态字段nState为4)的同时还会在InstancesSuspended挂起实例表中都插入一条记录,挂起实例表中除了上面上面提到的四个字段,还包括一些错误代码和错误描述字段,可能是描述挂起原因的一些内容。此时nState实例状态为4,表示这个实例是可恢复的挂起。 2.1.3. 订阅状态码为8时我没看出来这个状态码的含义,有知道的朋友请告知 如果是实例订阅,由于订阅的主体服务实例已经存在(在实例表中有这个服务的一条记录),不必再新建一个订阅服务的实例。 2.2. 将消息队列插入到相应的主机队列表如果订阅是处于活动状态,这一步骤要将这个订阅内容插入到相应的主机队列表中,主机队列表为“<主机实例名>Q”形式,表示路由到此主机的还未被处理的消息队列。如果相应的主机实例是BizTalkServerApplication,那就将此消息订阅内容插入到BizTalkServerApplicationQ表中。 主机队列表BizTalkServerApplicationQ主要字段: 主要字段 含义 nID 自动标识 uidMessageID 消息guid uidSubscriptionID 订阅guid uidClassID 订阅此消息的服务类别guid uidServiceID 订阅此消息的服务guid uidInstanceID 订阅此消息的服务实例guid uidAppInstanceID 主机实例guid 如果订阅处于停用(订阅状态码为2),或者订阅状态码为8(此码含义不清楚)时,订阅内容不被插入到主机队列表中,而是插入到主机队列挂起表中,表示订阅此消息的订阅不处于活动状态,此消息被挂起。挂起表为“<主机实例名>Q_Suspended”形式,此表大部分内容跟主机队列表相同,增加了一些表示为何挂起的字段,比如挂起原因nvcAdditionalInfo字段,挂起是否可恢复nIsResumable字段等。 3. 其后调用bts_InsertMessage过程接下来的过程将把消息的所有内容写入到MessageBox数据库中,一条消息本身内容在数据只有一个副本,所有订阅了此消息的服务都参考引用这一个消息的副本。消息本身是不能修改的,如果哪个服务需要修改消息,必然是需要生成另外一条消息。 3.1. 写入消息上下文和多部分消息的正文部分其后的第一次调用bts_InsertMessage过程将传递消息上下文,然后将该消息上下文以及有关消息的元数据(例如,部分数、正文部分名称和 ID)插入到 SPOOL 表中。SPOOL 表是对一条消息的总体性描述和消息的上下文属性,一条消息在此表中有一条记录。另外,消息正文部分也在这一步使用 int_InsertPart 存储过程插入到 PARTS 表中,一条消息的多部分中只能有一个正文部分。 SPOOL 表主要字段: 主要字段 含义 uidMessageID 消息的guid UserName 用户名,运行主机实例的用户 PublishingServer biztalk所在服务器的名称 OriginatorSID 创建者的安全id,即启动biztalk服务的帐户 OriginatorPID 创建者参与方id,一般是“s-1-5-7” nvcMessageType 消息的类型,由正文部分决定的 nNumParts 消息包含的部分数 uidBodyPartID 正文部分的guid nvcBodyPartName 正文部分名 nCounter 部分计数 imgContext 上下文的内容 MessageParts表是SPOOL消息队列表跟PARTS消息部分表之间的中间关系表,MessageParts. uidMessageID字段跟SPOOL.uidMessageID字段关联,PARTS. uidPartID字段跟MessagePart. uidPartID段关联。 主要字段 含义 uidMessageID 消息guid uidPartID 消息部分的guid nvcPartName 消息部分名 nBodyPart 是否正文 PARTS 表存放多部分消息的各个部分,一个部分在此表中占一条记录。哪一个是正文部分由spool表中的uidBodyPartID标识。 uidPartID 消息部分guid nPartSize 部分的尺寸 imgPart 部分的数据 实例表Instances表跟消息队列SPOOL表之间关系,就是哪个服务实例订阅了哪个消息是靠主机队列BizTalkServerApplicationQ表的关联起来,Instances. uidInstanceID字段跟BizTalkServerApplicationQ. uidInstanceID字段相关,BizTalkServerApplicationQ. uidMessageID字段跟SPOOL.uidMessageID字段相关。 这一步的最后还有做一件事就是把消息代理写入到MessageBox属性表MessageProps中这个批次的所有属性记录都删除,因为它们已经不再有用。 3.2. 依次写入多部分消息的其他部分随后,除了消息正文部分为每个剩余的消息部分调用 bts_InsertMessage 存储过程,把这些消息部分一一写入到PARTS 表中。 4. 消息发布和路由总结一个消息有从适配器进来,到消息代理把消息路由到MessageBox数据口中相应的表中,这个过程就是消息的发布和路由过程。 路由完毕后,在主机队列表中存在待处理的消息队列,这个表可以简单的理解为哪个消息将要由哪个服务实例处理。 实例表Instances表存放要处理这些消息的服务实例记录,一个待处理的消息对应一个实例,实例表每条记录可以简单的理解为这个实例是哪个服务类型的服务,服务目前是什么状态。其实Instances表中的实例还需要由具体的服务线程来运行它,这时才是真正的服务实例化,这点下一章“消息的轮询和执行”中会有详细讲解。 消息的具体内容存放在SPOOL 表和PARTS 表中,Spool表存放消息的总体性描述和消息的上下文属性,PARTS 表存放消息的各个部分。 8月4日 biztalk消息以及消息订阅发布路由机制(一)-消息概述——转载一. 消息概述BizTalk Server 基于消息发布/订阅的基本机制,在BizTalk Server内部分为发布服务器和订阅服务器,所谓发布服务器就是产生消息并发布到MessageBox中,所谓订阅服务器就是可以消费消息的服务器,根据订阅的消息的条件,只对自己需要的信息进行处理。 一般的情况是从外部收到的信息经过接收端口后都会被处理转换包装成biztalk的消息,消息中包含了相关属性(消息上下文),比如接收的端口、接收的适配器信息,还有预设的从消息内部提取出来的可以用来路由消息的特征值(从schema升级的属性)。把然后存放到消息数据库MessageBox中。 消息代理根据订阅表查找订阅了此消息的服务,并将订阅了此消息的所有订阅分别存到的相应的服务器实例对应的消息队列表中,系统不断的轮询此队列,发现有未处理的消息,就会把消息发送到订阅此消息的服务,同时把此条订阅消息从消息队列表去除。 订阅消息的服务(这个服务可能是业务流程,也可能是输出端口等等)收到消息后对消息就行处理,处理完后,如果服务是输出端口,此消息就经过适配器发送到系统外部。如果服务是业务流程,可能会产生新的消息,这种情况的处理同从接收端口进来的消息做类似的处理,发送消息到MessageBox,并查找订阅此消息的服务,把消息发送到订阅消息队列中继续流转。 1. 发布和订阅服务器发布服务器指的是可以送出消息发布到MessageBox的服务,包括: 接收端口 ―― 从外部接收信息,处理后发布到MessageBox 业务流程 ―― 业务流程启动后可能生成消息发送到发送端口或启动别的业务流程,生成的消息也是发布到MessageBox 要求/响应(Solicit-Response)发送端口 ―― 即双向的要求/响应发送端口。 订阅服务器是可以订阅并消费消息的服务,可以作为订阅服务器的服务类型目前有四类: XLANG/s – 业务流程(orchestration) Messaging InProcess – 表示发送端口,Solicit- Response发送端口 MSMQT – MS消息队列 Messaging Isolated – 表示请求/响应(Request-Response)接收端口,目前基本上就是指HTTP和SOAP的Request-Response双向接收端口。 Biztalk中的发布和订阅都是相对MeassagBox而言的,导致消息进入到MeassagBox的就叫发布,根据条件从MeassagBox中取得消息的叫订阅。 2. 消息构成BizTalk Server 2006 本质上是一个消息处理引擎。每条消息都可视为多部分消息,此消息可以由零个或多个部分组成。具有一个或多个部分的消息都具有一个标识为正文部分的部分(只能有个一部分标识为正文)。每个部分均由可表示 XML 文档、平面文件、序列化的 .NET 类或其他二进制数据流的二进制数据块组成。消息的正文部分的类型决定了整个消息的类型,这个消息类型可用为路由消息的条件。 每条消息都附带有一个属性集,称为消息上下文,这些属性的值是从消息本身提取或不来自消息本身但与消息本身相关的值。属性分为两种类型,升级属性和写入属性。这两种属性的区别是升级的属性可以用来作为路由消息的条件,写入的属性不能。 2.1.1. 升级属性要把一个消息架构中的某个元素升级到上下文属性,首先要有一个属性架构来对应各个被升级的属性。这个属性架构是简化的架构,只能包含简单数据类型的元素,每个元素对应一个属性,并分配给这个属性一个GUID,用来标识这个属性。看一下升级属性架构中的一个属性: <xs:element name=”Property1” type=”xs:string”> <xs:annotation> <xs:appinfo> <b:fieldInfo propertyGuid=”4bfe2aee-9c25-436f-979d-862ed812a523” /> </xs:appinfo> </xs:annotation> </xs:element> 属性名是Property1,属性GUID是4bfe2aee-9c25-436f-979d-862ed812a523。升级消息架构中的属性,先要在消息架构中指定升级架构,然后把消息架构中的某个元素对应到升级架构中的某个同数据类型的属性。消息架构中的元素被升级后,消息架构会保存此升级属性在架构中的位置信息的xpath,以便可以通过xpath提取元素的值。 如果接收管道中使用了xml拆装器,则消息经过接收端口的管道时,xml拆装器会自动把入站的消息跟已部署的消息架构匹配,匹配到合适的消息架构后,管道根据消息架构中升级属性的xpath提取相应的值保存到上下文属性,然后消息代理把这些属性的GUID和值保存到BizTalkMsgBoxDb数据库MessageProps 表中,就可以根据订阅的条件对照这些属性来路由消息。 比如某个服务订阅了消息类型为订单、收货单位为xxxx的消息,这里消息类型和收货单位都是升级到上下文属性的属性,消息代理发现这个条件跟收到的消息一致就会把消息发送到这个订阅的服务。 升级的属性除了从消息本身升级来的外,还有不是消息本身的信息但跟此消息相关的系统属性,比如接到此消息的端口适配器类型,接收此消息的端口id等等,这些系统属性也都具有自己的guid,会同其他的升级属性一同写入到MessageProps 表中作为路由消息的依据。 关于详细的消息订阅和消息发布路由的机制下面有专门章节论述。 2.1.2. 写入属性写入属性就是把消息架构中的某个元素设置为可分辨字段(Distinguished),消息架构会保存此写入属性在架构中位置信息的xpath,以便可以通过xpath提取元素的值。 写入的属性不必有属性架构的支持,也就没有相应的GUID,属性会被提取出来保存到上下文属性中,但是写入的属性不能用来作为路由消息的条件。 写入属性一般是为了在业务流程中方便的访问消息中的某个元素,可以直接从消息上下文属性获得此属性值,省去开发人员手工解析提取这个元素的值麻烦。 2.2. 多部分biztalk中的消息又叫做多部分消息,意为一个消息可以包含多个部分,每个部分都可能为XML 文档、平面文件、序列化的 .NET 类或其他二进制数据流的二进制数据块。一个消息可以是简单的只包含一个部分的消息,也可以有多个部分,但是其中必须并且只能有一个部分为正文本分,正文部分的类型就是整个消息的消息类型。 3. 消息流程先看一下消息在biztalk中的流转过程的图,下面详细描述一个从接收端口进入到biztalk后一直到消息发送出biztalk的整个过程。 一个接收端口可以包含多个接收位置,每个接收位置就是一个对外接收信息的实际物理接口。接收位置接收到的信息经过处理,如果设计有映射,经过消息映射转化后再由消息代理根据消息订阅情况分发到各个订阅消息的服务。 接收位置: 接收位置主要由接收适配器和接收管道组成,这两个组件再终结点管理器的管理下协调工作。 接收适配器: Biztalk作为EAI和B2B的平台软件,需要跟各种系统通讯联系,所以信息接口应该覆盖面尽可能的广,能够接收各种协议的各种类型的信息,biztalk使用适配器来解决这个问题,通过适配器来把多种多样的信息接收进来并转换成biztalk内部使用的xml的消息。 Biztalk本身包含了许多一般常用的适配器SOAP、http、ftp、Smtp、平面文件、msmq、sql等等,基本能满足大多数需求。如果biztalk本身带的适配器不能满足需求,还可以自己开发需要的适配器,另外有很多第三方的公司也提供各种自己开发的适配器给需要者使用。 接收适配器读取数据流创建消息(Microsoft.BizTalk.Message.Interop.IbaseMessage 接口的实现)、向该消息添加部分(Microsoft.BizTalk.Message.Interop.IbasePart 接口的实现)、然后将数据流作为该部分内容进行提供。 接收消息后,适配器将接收位置、适配器类型以及其他内容(与适配器相关)升级到消息的上下文属性中, 以便根据接收位置或适配器类型订阅消息的服务能接收到订阅的消息。 接收管道: 从适配器出来的消息在终结点管理器的控制下送到接收管道中进行处理。
Xml拆装器可以指定消息的架构,xml拆装器就会按照这个消息的架构对消息就行处理,并可以校验消息是否符合架构。同时如果消息架构中有升级属性,这一步会根据升级属性的xpath把属性值从消息中提取出来放到消息的上下文属性中。指定了消息架构的xml拆装器还会把消息的类型升级到消息的上下文属性中,消息的类型为命名空间(后接 # 符号)和根节点的名称组成。比如:http://tempuri.org/samples/MessageType#Message 平面文件拆装器需要指定架构,并且架构是做过平面文件扩展的,就是架构中包含如果分解平面文件数据到xml的信息,平面文件拆装器根据这些信息解析平面文件,并生成xml。 Biztalk的默认接收管道PassThruTransmit是个直通的管道,它不处理xml文档,所以如果要接收xml消息并升级属性此管道不适合。 Biztalk的默认接收管道XMLReceive,这个管道专门用来拆装xml消息,管道中的xml拆装器根据架构的命名空间和根节点名称在已经部署的所有架构逐个进行匹配,一旦匹配成功,就使用这个架构,然后根据schema中升级属性的xpath提取属性值放入上下文。如果一个都没匹配成功,则根据是否路由不可识别消息的设置确定是否继续路由,还是生成错误信息。 消息代理: 接收管道出来的消息又回到终结点管理器,如果有映射器就把消息送到映射器就行消息的架构转换,如果没有把消息直接送到消息代理,由消息代理把消息发布到MessageBox中,并处理消息的路由。 消息的发布和路由在下面章节详述。 3.2. MessageBoxMicrosoft BizTalk Server 中发布/订阅引擎的核心是 MessageBox 数据库。接收到的消息存放在MessageBox中,各个服务消息订阅也是存放在MessageBox中,发送到订阅服务的消息队列也是在MessageBox中。MessageBox是biztalk的信息核心。 3.3. 业务流程业务流程可以参与消息的处理过程,但业务流程不是处理消息过程的必须过程,一个消息流程完全可以直接从接收端口到MessageBox,然后直接路由到一个输出端口,不需要业务流程的参与。 业务流程中也有端口概念,是业务流程内部跟外部进行消息交换的接口,业务流程的端口称作逻辑端口,用户从biztalk外部接收数据、发送数据的端口称作物理端口,是不同的概念。 业务流程可以作为发布服务器也可以作为订阅服务器,作为订阅服务器时可以订阅从物理接收端口进入biztalk的消息,作为发布服务器,业务流程从逻辑输出端口输出的消息也可以被别的订阅服务器订阅。 业务流程内部的各种功能不在本文的讨论范围,可以自行参考biztalk的随即文档的业务流程相关的部分。 3.4. 发送端口在消息准备从 BizTalk Server 发送时,它将在发送端口中经历一个互补的过程。映射将在发送管道执行前应用于消息,从而,消息在由管道处理并通过适配器发送前转换为特定于客户或应用程序的格式。在发送管道中,属性将从上下文降级到消息中,而非升级到消息上下文中。 biztalk消息以及消息订阅发布路由机制(二)-消息订阅 (转载)一. 消息订阅订阅消息的主体叫订阅服务器,订阅服务器是可以订阅并消费消息的服务,可以作为订阅服务器的服务类型目前有四类,在BizTalkMgmtDb管理数据库中的adm_ServiceClass的Name字段列出了所有可以作为订阅服务器的服务类型,包括: XLANG/s – 业务流程(orchestration) Messaging InProcess – 表示一般的发送端口、Solicit- Response发送端口 MSMQT – MS消息队列 Messaging Isolated –表示请求/响应(Request-Response)接收端口,目前基本上就是指HTTP和SOAP的Request-Response双向接收端口。 每种服务都有自己的id,为16字节GUID的形式(biztalk中对象的标识id都采用GUID的形式,biztalk数据表中大量的使用到这种16字节的统一标识数据,sql server中的uniqueidentifier 数据类型,就是用来存放GUID类型的数据) 1. 消息订阅主体消息订阅最后体现在BizTalkMsgBoxDb数据库的反应订阅主体的Subscription表(表示是哪个服务产生的这个订阅)和反应订阅条件的一组谓词表中(表示这个订阅的具体条件,用来判断哪些消息是符合这个订阅的)。 先来看一下Subscription表的主要字段含义,这个表指示了订阅消息的具体是哪一个服务: nvcName ―― 此订阅的名称 uidSubID ―― Guid类型,此订阅的uid nvcApplicationName ―― 订阅服务器所属主机实例名,路由消息时不同的主机实例将调用不同的存储过程处理。 uidClassID ―― 产生订阅的服务类型,是adm_ServiceClass 服务表UniqueId字段的外键 uidServiceID ―― 产生此订阅的具体服务,根据uidClassID服务类型的不同,对应到不同类别的服务.如果uidClassID指示的是XLANG/s,则对应的是业务流程,此字段对应到bts_Orchestration表中的uidGUID字段。为Messaging In-Proc时,表示由发送端口订阅消息,对应到bts_SendPort表中的uidGUID 。MSMQt时有点混乱,但大多数情况下也在bts_SendPort table表中体现。实例订阅时,服务实例是请求/响应端口时的这个字段并不是接收端口的guid也不是接收位置的guid,不清楚这个guid跟什么对应。哪位知道的朋友能不能告知一下。 uidInstanceID ―― 实例订阅时的订阅消息的服务实例的guid。激活订阅时此字节为空。 Biztalk中存在两类订阅:激活订阅和实例订阅。激活订阅收到执行订阅的消息后,该消息创建新的订阅服务器实例来处理这个消息,一般的订阅都属于这类订阅。实例订阅将指示执行订阅的消息应路由到已在运行的订阅服务器实例。有一种服务会产生实例订阅的情况即“Messaging Isolated ”,这类服务的典型就是“请求/响应接收端口”,这类服务其实可以产生两个订阅。先是“请求/响应接收端口”的接收部分,这部分相当于消息分布服务器,可以由其他订阅服务器来订阅这个消息,比如一般是业务流程的一个接收形状绑定这个端口,实际上就是订阅了这个端口接收到的消息。这个接收端口服务实例建立后,把收到的消息发送到MessageBox。然后“请求/响应接收端口”的响应部分,这部分相当于订阅服务器,如果这部分绑定到业务流程的一个发送形状,这个接收端口服务实例会生成一个实例订阅,这个字段就用来存放这个接收端口服务实例的guid,表示还是由这个服务实例来处理返回的消息。这样,就完成了一个从接收到响应的一个往返过程。实例订阅是由服务实例生成的订阅,所以在产生实例订阅收到消息被放入到消息队列后,会在订阅表中把这个实例订阅删除。 还有一种情况会形成实例订阅,orchestration中使用相关集时,一个orchestration实例的一个端口发送消息出去,返回的消息仍然由这个orchestration实例的一个接收形状接收。这里也有一个orchestration实例的订阅发出去的消息的实例订阅。 uidPortID ―― 订阅服务类型为XLANG/s时,此字段表示业务流程中接收消息的端口形状,对应bts_orchestration_port表中的uidGUID字段。对于 Messaging或MSMQt,这个字段指示使用发送端口的主通道还是辅助通道。先根据uidServiceID在bts_SendPort表中找到相应的端口,然后用bts_SendPort表的nID 跟bts_sendport_transport表的nSendPortID 关联。uidPortID跟bts_sendport_transport表的uidGUID 关联,就能确定使用哪个发送端口的通道。 fEnabled ―― 此订阅的状态,0 ―― 禁用;1 ―― 活动;2 ―― 停用 uidPredicateGroupID ―― 订阅谓词组id。 2. 消息订阅条件(谓词)一个订阅的主体存在Subscription表中,相应的订阅条件则分别存在一组谓词表中,不同的类型的条件放在不同的表中,比如等于的条件放在EqualsPredicates中,小于的条件放在LessThenPredicates中,一共有以下这么多的谓词表: l BitwiseANDPredicates l EqualsPredicates l EqualsPredicates2ndPass l ExistsPredicates l FirstPassPredicates l GreaterThanOrEqualsPredicates l GreaterThanPredicates l LessThenOrEqualsPredicates l LessThenPredicates l NotEqualsPredicates 这些表的基本结构都是一样的,都是按照什么属性(属性的GUID)+谓词(等于、大于、小于。。。)+属性的值的形式出现。看一下这些表的数据结构: uidPropID ―― 属性的guid。所有属性都有自己的guid,包括类似接收端口、接收适配器类型等系统属性,或者从schema升级的属性。 vtValue ―― 属性的值 uidPredicateGroupID ―― 一组谓词的id,一个订阅可以有多个条件谓词,这个字段表示同一组的条件,条件也可分为或条件和与条件,条件的组合通过PredicateGroup表在Subscription表和各个谓词表之间进行组合。 看一下PredicateGroup表的结构: uidPredicateORGroupID -- 订阅谓词,对应到Subscription的uidPredicateGroupID字段表示是与这个订阅相关的条件。此字段相同的记录表示相应的订阅有多组条件用或运算组成。 uidPredicateANDGroupID -- 订阅谓词与运算一组的谓词id,每个或运算条件都能由多条与运算的条件组成,最终由这个字段跟各个谓词表去关联。 nNumFirstPassPredicates -- 这个或条件组中包含几个与运算条件。
3. 消息订阅过程下面的图是涉及到订阅部分数据库表的主要结构和相互关系。各字段的含义上面两节基本都有描述了。
3.1. 激活订阅一般情况下,业务流程、发送端口和MSMQ这三类服务产生的订阅是激活订阅。这种订阅是被订阅的消息根据订阅条件新建订阅消息的服务主体,即新建一个服务实例,然后由这个服务实例处理订阅的消息,服务实例将消息处理完毕后,这个实例就算完成任务。 业务流程订阅一般表现形式是业务流程的接收端口跟物理端口绑定。其实它的订阅主体是这个业务流程的这个接收端口。条件是绑定的发送端口发送来的消息。 发送端口订阅一般可以是发送端口绑定到业务流程的一个发送端口。也可以是发送端口直接订阅由接收位置接收的消息,在发送端口的筛选器中可以根据消息的各种属性(包括系统属性和从schema升级的属性)来定义订阅的条件,比如某个消息的哪个升级属性大于某个值时。 以业务流程绑定一个物理接收端口为例说明: 业务流程跟一个物理接收端口绑定后,实际进行的操作是产生一个订阅。就是在Subscription表中生成一条记录表示订阅的主体,然后在一组谓词表中生成相应的订阅条件。 订阅主体就是这个业务流程的绑定的端口。在Subscription表中生成记录,字段uidClassID是XLANG/s业务流程服务的guid,表示订阅主体的服务类型是业务流程。uidServiceID字段是订阅的具体业务流程的guid(对应bts_Orchestration表)。uidPortID字段是这个业务流程中接收端口的guid(对应bts_orchestration_port表),uidPredicateGroupID字段是订阅谓词组id,表示跟这个订阅相关的谓词表中的记录。 订阅主体在Subscription表生成记录后,然后把订阅的条件要记入到相应的谓词表中。本例中端口绑定的情况,会产生两个条件,一个是接收端口id等于绑定的那个接收端口,形式为:http://schemas.microsoft.com/BizTalk/2003/system-properties. ReceivePortID == {9DB16A5F-ADD7-4CF2-9B9A-D7924426DD18};另一个是消息类型是业务流程接收端口指定的消息类型,形式为:http://schemas.microsoft.com/BizTalk/2003/system-properties.MessageType == http://EAISchemas.Request#Request。 这两个条件中都含有“system-properties”,表示是系统属性,这两个又都是等于条件,所以都记入EqualsPredicates谓词表中。所有的属性都有guid,系统属性有预设的guid,升级属性前面章节已经讲到,每个升级的属性都会生成一个guid(这就是为什么升级的字段可以用来路由消息,而可分辨字段不可以用来路由消息的原因,可分辨字段没有guid)。所以这两个条件在谓词表中的表现是,两属性guid代表的属性分别等于什么值。 3.2. 实例订阅这种订阅由服务实例产生,并放入到订阅表中。当消息路由到消息队列后,会将订阅表中的实例订阅记录删除。 一般来讲Messaging Isolated(表示物理请求/响应类型的接收端口)这类服务会用到实例订阅,下面就这个为例说明: 请求/响应类型的接收端口,一般是由业务流程订阅这个端口的请求部分的消息,经过处理后返回消息,这个端口的的响应部分再订阅这个返回的消息,处理后返回给请求端。 请求部分的订阅是激活订阅,不再赘述。 响应部分是实例订阅,是由请求部分收到消息后新建Messaging Isolated服务实例后,这个服务实例在订阅表中建立的一个订阅,这个订阅其他部分跟激活订阅一致,就是在Subscription表中的uidInstanceID字段填入这个服务实例自身的guid,表示这是实例订阅,订阅主体就是这个服务实例。 当消息路由到这个实例订阅的消息队列后,这个实例订阅的使命就完成了,被从订阅表中删除,这个过程在下面消息的发布和路由中还有叙述。 4. 端口绑定的本质端口绑定本质就是生成订阅,订阅服务器订阅发布服务器的消息,但是不表示这个发布服务器的消息只能给一个订阅服务器消费,一个消息可以同时被多个服务订阅,只要这个消息符合订阅条件,消息代理会把订阅把消息发送到所有订阅这个消息的服务实例。 7月29日 Hibernate 对视图的操作及配置也是这几天,在弄Hibernate的时候,发现对于视图的mapping往往做的很不够,如果这个视图里没有主键的话,那是无法搜出要的数据类的。呵呵,这个也就是平时说得给hibernate配视图:)不过,后面的myeclipse3.2平台上已经统一解决了这类问题,通过id类。这个针对的是过去eclipse3.1的平台而言,同时也让自己明白了hibernate的一些小脾气:) hibernate对视图的操作 // Fields // Property accessors public void setId(AllTablePbId id) { public class AllTablePbId implements java.io.Serializable { // Fields private String owner; private String tableName; private String columnName; private String dataType; private String pbcCnam; private String pbcCmnt; // Property accessors public String getOwner() { public void setOwner(String owner) { public String getTableName() { public void setTableName(String tableName) { public String getColumnName() { public void setColumnName(String columnName) { public String getDataType() { public void setDataType(String dataType) { public String getPbcCnam() { public void setPbcCnam(String pbcCnam) { public String getPbcCmnt() { public void setPbcCmnt(String pbcCmnt) { } 配置文件: 通过这样对视图的操作,同样也可以用到对于那些没有定义主键的表,操作方法是一样的。 Java中Double的高精度问题及bigdecimal解决方式 最近有空写了点老的J2EE的代码,发现有一个十分有意思的问题,当用Hibernate从数据库里把浮点数读取出来的时候做一些比如累加的工作,例如 summary 或者递减之类的,就会发现在最后的结果中会出现些许问题。
如:3.41+5.2+56.2+23.3+... (这类两位小数的价钱),结果会出现103.00000000000001这种结果,但是人算的话反而会得出正常的数据。看样子double,float这类数据精度上来了还会有这类问题。
于是,翻了点资料,在有的编程语言中提供了专门的货币类型来处理这种情况,但是Java没有。
四舍五入
我们的第一个反应是做四舍五入。Math类中的round方法不能设置保留几位小数,我们只能
象这样(保留两位): public double round(double value){
return Math.round(value*100)/100.0;
}
非常不幸,上面的代码并不能正常工作,给这个方法传入4.015它将返回4.01而不是4.02,
如我们在上面看到的 4.015*100=401.49999999999994
因此如果我们要做到精确的四舍五入,不能利用简单类型做任何运算
java.text.DecimalFormat也不能解决这个问题:
System.out.println(new java.text.DecimalFormat("0.00").format(4.025));
输出是4.02
BigDecimal 在《Effective Java》这本书中也提到这个原则,float和double只能用来做科学计算或者 是工程计算,在商业计算中我们要用 java.math.BigDecimal。BigDecimal一共有4个够造方 法,我们不关心用BigInteger来够造的那两个,那么还有两个,它们是: BigDecimal(double val)
Translates a double into a BigDecimal.
BigDecimal(String val)
Translates the String repre sentation of a BigDecimal into a
BigDecimal. 上面的API简要描述相当的明确,而且通常情况下,上面的那一个使用起来要方便一些。我
们可能想都不想就用上了,会有什么问题呢?等到出了问题的时候,才发现上面哪个够造方 法的详细说明中有这么一段: Note: the results of this constructor can be somewhat unpredictable. One might
assume that new BigDecimal(.1) is exactly equal to .1, but it is actually equal to .1000000000000000055511151231257827021181583404541015625. This is so because .1 cannot be represented exactly as a double (or, for that matter, as a binary fraction of any finite length). Thus, the long value that is being passed in to the constructor is not exactly equal to .1, appearances nonwithstanding. The (String) constructor, on the other hand, is perfectly predictable: new
BigDecimal(".1") is exactly equal to .1, as one would expect. Therefore, it is generally recommended that the (String) constructor be used in preference to this one. 原来我们如果需要精确计算,非要用String来够造BigDecimal不可!在《Effective Java》
一书中的例子是用String来够造BigDecimal的,但是书上却没有强调这一点,这也许是一个 小小的失误吧。 解决方案
现在我们已经可以解决这个问题了,原则是使用BigDecimal并且一定要用String来够造。
但是想像一下吧,如果我们要做一个加法运算,需要先将两个浮点数转为String,然后够造
成BigDecimal,在其中一个上调用add方法,传入另一个作为参数,然后把运算的结果( BigDecimal)再转换为浮点数。你能够忍受这么烦琐的过程吗?下面我们提供一个工具类 Arith来简化操作。它提供以下静态方法,包括加减乘除和四舍五入: public static double add(double v1,double v2)
public static double sub(double v1,double v2)
public static double mul(double v1,double v2)
public static double div(double v1,double v2)
public static double div(double v1,double v2,int scale)
public static double round(double v,int scale)
附录
源文件Arith.java:
package com.common.util;
import java.math.BigDecimal; /** * 由于Java的简单类型不能够精确的对浮点数进行运算, * 这个工具类提供精确的浮点数运算,包括加减乘除和四舍五入。 */ public final class Arith { // 默认除法运算精度 private static final int DEF_DIV_SCALE = 2; // 这个类不能实例化 private Arith() { } /** * 提供精确的加法运算。 * * @param v1 被加数 * @param v2 加数 * @return 两个参数的和 */ public static double add(double v1, double v2) { BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.add(b2).doubleValue(); } /** * 提供精确的减法运算。 * * @param v1 被减数 * @param v2 减数 * @return 两个参数的差 */ public static double sub(double v1, double v2) { BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.subtract(b2).doubleValue(); } /** * 提供精确的乘法运算。 * * @param v1 被乘数 * @param v2 乘数 * @return 两个参数的积 */ public static double mul(double v1, double v2) { BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.multiply(b2).doubleValue(); } /** * 提供(相对)精确的除法运算,当发生除不尽的情况时, * 精确到小数点以后10位,以后的数字四舍五入。 * * @param v1 被除数 * @param v2 除数 * @return 两个参数的商 */ public static double div(double v1, double v2) { return div(v1, v2, DEF_DIV_SCALE); } /** * 提供(相对)精确的除法运算。 * 当发生除不尽的情况时,由scale参数指定精度,以后的数字四舍五入。 * * @param v1 被除数 * @param v2 除数 * @param scale 表示表示需要精确到小数点以后几位。 * @return 两个参数的商 */ public static double div(double v1, double v2, int scale) { if (scale < 0) { throw new IllegalArgumentException( "The scale must be a positive integer or zero"); } BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue(); } /** * 提供精确的小数位四舍五入处理。 * * @param v 需要四舍五入的数字 * @param scale 小数点后保留几位 * @return 四舍五入后的结果 */ public static double round(double v, int scale) { if (scale < 0) { throw new IllegalArgumentException( "The scale must be a positive integer or zero"); } BigDecimal b = new BigDecimal(Double.toString(v)); BigDecimal one = new BigDecimal("1"); return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue(); } } 呵呵,float和double只能用来做科学计算或者是工程计算,在商业计算中我们要用 java.math.BigDecimal。这个才是解决的方式,我也试过了发现确实不错。不过话又说回来了,在.net里似乎没有发现有这类问题呀,能说他好么?我看未必,有时候封装的太好,功能太多,往往让人忘记了这些背后本来的面目:) 7月21日 FTP协议——相关参数最近弄了一点FTP的client端操作例程,由于在老的平台.net1.1上进行开发,只好使用最基本的socket通信去和Ftp服务通信,因此不得不调用这些FTP的命令,同时参照返回code码。可喜的是,.net2.0后的FLC里给FTP的操作专门提供了一系列方便的类和接口,呵呵,ftpclient好像是这个,省去了好多麻烦呢:) 不过socket的协议接触下也不是什么坏事,至少还知道了通信这个层面的操作规范,算是有所小小收获吧。
FTP命令命令 描述
7月7日 有趣的两道汇编题——Don't U Break Me down Today 最近有位朋友正好要考到一些和汇编有关的题,所以打来这里要个解决方案。呵呵,我也是一下子来了兴趣,毕竟过去对汇编还是沉迷了一段时间,而且也在上机调ASM的时候成功的把一台PC搞歇了——乱调系统地址造成的(事后知道他们给拿去重装了)。Ok,题目也很简单,就两道,我写的时候加了比较多的注释,大概也是职业使然吧:)
1。从strin单元开始有一串字符,,以* 为结束标志,要求字符串的长度,存入count单元,要求段说明和必要的伪指令。
解答如下:
name length_of_string
data segment ;数据段定义
string db 'dontubreakmedowntoday',2ah ;读取的字符串,2ah是*的ascii码且跟在string后 count db ? ;定义count star equ 2ah ;定义star为*,方便后面比较 data ends stack segment para stack'stack' ;堆栈段的定义(不是很重要,死记)
db 100 dup(?) stack ends code segment ;代码段定义
assume cs:code,ds:data,es:data,ss:stack ;定义各个段的用处 start: proc far begin: push ds mov ax,0 push ax mov ax,data mov ds,ax mov es,ax ;以上的是一个固定的初始化模式,照背就可以了 lea di,string ;设置string串的地址指针 mov dl,0 ;设置串的长度初始长度为0 mov al,star ;设置串的结束标记到al中 again: scasb ;搜索串(逐字节地) je done ;找到结束标记,然后跳转到停止段 inc dl ;计数器加1 jmp again ;循环 done: lea bx,count ;把count的地址入bx寄存器 mov [bx],dl ;把dl的赋给count ret start endp code ends end begin ;game1 over 2。从键盘上输入一行字符,要求第一个为space,否则end,如果是space,则开始接受键入字符,并保存在首地址为buffer的缓冲区,直到接受第二个space为止。(调用中断)
解答如下:
name input_string_store
data segment ;数据段定义 buffer db 100 dup(?) ;预存的缓冲区(100个大小,超过机器肯定崩) space equ 20h ;定义空格,方便比较 data ends stack segment para stack'stack' ;堆栈段的定义(不是很重要,死记)
db 100 dup(?) stack ends code segment ;代码段定义 assume cs:code,ds:data,es:data,ss:stack ;定义各个段的用处 start: proc far begin: push ds mov ax,0 push ax mov ax,data mov ds,ax mov es,ax ;以上的是一个固定的初始化模式,照背就可以了 mov ah,1 ;将ah置1 ;8086汇编中断21H系统功能调用全部子功能 ;AH-01 键盘输入并回显 AL=输入字符 int 21h ;调用中断,这两步是读取输入的步骤 mov bl,al ;al中读取的字符放入bl中 and bl,space ;比较bl和space字符 ;如果相同,则进位标志C清0 jc done ;如果不同C=1,则跳转到结束, ;即第一个输入不是space,结束 ;第一个输入是space,则继续 ;特别注意jc是根据c=1跳转的 lea cx,buffer ;将buffer的首地址入cx寄存器 again: mov ah,1 int 21h ;调用中断,获得输入的字符 mov bl,al ;al中读取的字符放入bl中 and bl,space ;比较bl和space字符 ;如果相同,则进位标志C清0 ;如果不同C=1 jnc done ;当第二次输入space时C=0跳转到结束 ;否则继续 ;特别注意jnc是根据c=0跳转的 mov [cx],bl ;将bl中的字符入cx当前指针 ;指向的地址(即缓冲区) inc cx ;cx的地址自增,指向buffer的下一个存放位置 jmp again ;循环 done: ret start endp code ends end begin ;game2 over 5月8日 强名称工具(SN.exe).NET Framework 工具
强名称工具 (Sn.exe)
强名称工具有助于使用强名称对程序集进行签名。Sn.exe 提供用于密钥管理、签名生成和签名验证的选项。 sn [-quiet][option [parameter(s)]]
参数
示例下面的命令创建一个新的随机密钥对并将其存储在 keyPair.snk 中。 sn -k keyPair.snk 下面的命令将 keyPair.snk 中的密钥存储在强名称 CSP 中的容器 MyContainer 中。 sn -i keyPair.snk MyContainer 下面的命令从 keyPair.snk 中提取公钥并将其存储在 publicKey.snk 中。 sn -p keyPair.snk publicKey.snk 下面的命令显示公钥和 publicKey.snk 包含的公钥的标记。 sn -tp publicKey.snk 下面的命令验证程序集 MyAsm.dll。 sn -v MyAsm.dll 下面的命令从默认 CSP 中删除 MyContainer。 sn -d MyContainer 5月3日 程序集链接器(AL.exe).NET Framework 工具
程序集链接器 (Al.exe)
“程序集链接器”从一个或多个文件(可以是模块,也可以是资源文件)生成一个带有程序集清单的文件。模块是不含程序集清单的“Microsoft 中间语言”(MSIL) 文件。 al sources options
参数您可以指定以下一个或多个 sources。
您可以指定以下“选项”;请注意,必须指定 /out。
备注所有 Visual Studio 编译器都产生程序集。但是,如果您有一个或多个模块(没有清单的元数据),则可使用 Al.exe 在单独的文件中创建带清单的程序集。 要在缓存中安装程序集,从缓存中删除程序集,或列出缓存内容,请使用全局程序集缓存工具 (Gacutil.exe)。 示例以下命令使用 t2.netmodule 模块中的程序集创建可执行文件 t2a.exe。入口点是 MyClass 中的 Main 方法。 al t2.netmodule /target:exe /out:t2a.exe /main:MyClass.Main 4月28日 本机映像生成器(Ngen.exe).NET Framework 工具
本机映像生成器 (Ngen.exe)
本机映像生成器 (Ngen.exe) 是一个提高托管应用程序性能的工具。Ngen.exe 创建本机映像(包含经编译的特定于处理器的机器代码的文件),并将它们安装到本地计算机上的本机映像缓存中。运行库可从缓存中使用本机映像,而不是使用实时 (JIT) 编译器编译原始程序集。 在 .NET Framework 2.0 版中,Ngen.exe 有了很大变化:
有关如何使用 Ngen.exe 和本机映像服务的其他信息,请参见本机映像服务。
ngen <action> [options] ngen /? | /help 操作下表说明了每个操作的语法。有关 actionArguments 各部分的说明,请参见参数、方案和配置表。选项表描述了 options 和帮助开关。
参数
选项
备注若要运行 Ngen.exe,您必须具有管理特权。 Ngen.exe 为指定程序集及其所有依赖项生成本机映像。依赖项是根据程序集清单中的引用来确定的。仅当应用程序使用反射(例如通过调用 System.Reflection.Assembly.Load 方法)来加载依赖项的情况下才需要单独安装依赖项。
Ngen.exe 维护着一个与依赖项有关的计数。例如,假设本机映像缓存中同时安装了 MyAssembly.exe 和 YourAssembly.exe,而且它们都具有对 OurDependency.dll 的引用。如果卸载了 MyAssembly.exe,则不会卸载 OurDependency.dll。只有当 YourAssembly.exe 也被卸载时才会将其移除。 如果为全局程序集缓存中的程序集生成本机映像,请指定其显示名称。请参见 System.Reflection.Assembly.FullName。 Ngen.exe 生成的本机映像可以在应用程序域之间共享。这意味着,在要求在应用程序域之间共享程序集的应用程序方案中可以使用 Ngen.exe。若要指定域非特定性:
将同一个程序集加载到多个应用程序域中时,总是使用非特定于域的代码。如果本机映像在已加载到共享域之后又被加载到非共享的应用程序域中,则该映像将无法使用。
为不同的方案生成映像在您生成一个程序集的本机映像后,每当运行库运行该程序集时,都会自动尝试找到并使用该本机映像。根据使用方案的不同,可生成多个映像。 例如,如果您在调试或分析方案中运行程序集,则运行库将查找利用 /Debug 或 /Profile 选项生成的本机映像。如果运行库无法找到匹配的本机映像,它将恢复为标准的 JIT 编译。调试本机映像的唯一方式是使用 /Debug 选项创建本机映像。 uninstall 操作也能识别方案,因此您可以卸载所有方案或只卸载选择的方案。 确定何时使用本机映像本机映像可从两方面提高性能:改善内存使用情况和减少启动时间。
改善内存使用情况当代码在进程间共享时,本机映像可显著改善内存使用情况。本机映像为 Windows PE 文件,因此一个 .dll 文件的单个副本可由多个进程共享;而 JIT 编译器生成的本机代码存储在私有内存中,并且不可共享。 运行于终端服务下的应用程序也可从共享代码页中获益。 此外,不加载 JIT 编译器会为每个应用程序实例节省固定量的内存。 更快的应用程序启动速度使用 Ngen.exe 预编译程序集可减少某些应用程序的启动时间。通常,如果应用程序共享组件程序集,则可从中获益,因为在第一个应用程序启动之后,共享组件即已加载,可供后续应用程序使用。而冷启动(应用程序中的所有程序集必须从硬盘上加载)则不会从本机映像中获得相同的益处,因为硬盘访问时间占了很大比重。 硬绑定可影响启动时间,因为硬绑定至主应用程序程序集的所有映像必须同时加载。
程序集基址的重要性因为本机映像为 Windows PE 文件,所以它们和其他可执行文件一样有着相同的重定基址问题。如果采用硬绑定,则重定位的性能开销甚至更显著。 若要设置本机映像的基址,请使用编译器的相应选项设置程序集的基址。Ngen.exe 对本机映像使用此基址。
可使用 dumpbin.exe 之类的工具查看本机映像的首选基址。 使用注意事项摘要下面的常规注意事项和应用程序注意事项可能有助于您决定是否对应用程序本机映像进行评估:
除了这些常规注意事项之外,在确定本机映像是否可提供性能益处时,必须考虑应用程序的性质:
硬绑定硬绑定增加吞吐量并减少本机映像的工作集大小。硬绑定的缺点是硬绑定到程序集的所有映像在加载程序集时必须都加载。对于大型应用程序,这会大大增加启动时间。 硬绑定适合于在所有应用程序性能关键的方案中加载的依赖项。与本机映像使用情况的任何方面一样,仔细测量性能是确定硬绑定是否可改善应用程序性能的唯一方式。 DependencyAttribute 和 DefaultDependencyAttribute 属性可使您向 Ngen.exe 提供硬绑定提示。
为依赖项指定绑定提示将 DependencyAttribute 应用于程序集可指示加载指定依赖项的可能性。System.Runtime.CompilerServices.LoadHint.Always 指示适合进行硬绑定,Default 指示应使用依赖项的默认提示,而 Sometimes 则指示不适合使用硬绑定。 下面的代码显示有两个依赖项的程序集的属性。第一个依赖项 (Assembly1) 适合于进行硬绑定,而第二个 (Assembly2) 不适合进行硬绑定。 Visual Basic
Imports System.Runtime.CompilerServices <Assembly:DependencyAttribute("Assembly1", LoadHint.Always)> <Assembly:DependencyAttribute("Assembly2", LoadHint.Sometimes)> C#
using System.Runtime.CompilerServices;
[assembly:DependencyAttribute("Assembly1", LoadHint.Always)]
[assembly:DependencyAttribute("Assembly2", LoadHint.Sometimes)]
C++
using namespace System::Runtime::CompilerServices; [assembly:DependencyAttribute("Assembly1", LoadHint.Always)]; [assembly:DependencyAttribute("Assembly2", LoadHint.Sometimes)]; 程序集名称不包括文件扩展名。可使用显示名称。 为程序集指定默认绑定提示只有某些程序集需要默认绑定提示:这些程序集将由依赖于它们的任何应用程序直接并经常使用。以 System.Runtime.CompilerServices.LoadHint.Always 将 DefaultDependencyAttribute 应用于这样的程序集可指定应使用硬绑定。
Microsoft 使用 DefaultDependencyAttribute 指定硬绑定为 .NET Framework 中极少数程序集(如 mscorlib.dll)的默认绑定。 疑难解答若要确认应用程序正在使用本机映像,可使用程序集绑定日志查看器 (Fuslogvw.exe)。在绑定日志查看器窗口上,选择“日志类别”框中的“本机映像”。Fuslogvw.exe 提供了有关为什么拒绝本机映像的信息。 可使用 JitCompilationStart 托管调试助手 (MDA) 确定 JIT 编译器何时开始编译函数。 推迟处理超大型应用程序的本机映像生成过程可能需要相当长的时间。同样,更改共享组件或更改计算机设置可能需要更新很多本机映像。install 和 update 操作有一个 /queue 选项,该选项将该操作排入队列,以由本机映像服务推迟执行。此外,Ngen.exe 具有 queue 和 executeQueuedItems 操作,这些操作提供了对本机映像服务的某些控制。有关更多信息,请参见本机映像服务。 本机映像和 JIT 编译如果 Ngen.exe 在程序集中遇到它无法生成的任何方法,则它会将这些方法从本机映像中排除。当运行库执行此程序集时,对于那些不包括在本机映像中的方法,它将恢复为 JIT 编译。 此外,如果程序集已更新,或者本机映像出于任何原因已失效,则不会使用本机映像。 无效映像当您使用 Ngen.exe 创建程序集的本机映像时,输出取决于您指定的命令行选项以及计算机上的某些设置。这些设置包括:
Ngen.exe 在生成本机映像时记录这些信息。当您执行程序集时,运行库将查找用匹配计算机的当前环境的选项和设置生成的本机映像。如果运行库没有找到匹配的本机映像,它将恢复为程序集的 JIT 编译。对计算机的设置和环境进行以下更改会导致本机映像失效:
示例下面的命令为当前目录中的 ClientApp.exe 生成本机映像,并在本机映像缓存中安装该映像。如果该程序集存在配置文件,Ngen.exe 将使用它。此外,还会为 ClientApp.exe 所引用的任何 .dll 文件生成本机映像。 ngen install ClientApp.exe 使用 Ngen.exe 安装的映像也称为根。根可以为应用程序或共享组件。 下面的命令生成具有指定路径的 MyAssembly.exe 的本机映像。 ngen install c:\myfiles\MyAssembly.exe 当查找程序集及其依赖项时,Ngen.exe 使用与公共语言运行库所使用的相同的探查逻辑。默认情况下,包含 ClientApp.exe 的目录用作应用程序基目录,所有程序集的探查均从此目录开始。使用 /AppBase 选项可重写此行为。
程序集可以具有不带引用的依赖项(例如,它使用 System.Reflection.Assembly.Load 方法加载 .dll 文件)。您可以使用 /ExeConfig,使用应用程序程序集的配置信息来为这样的 .dll 文件创建本机映像。下面的命令使用 MyApp.exe 的配置信息为 MyLib.dll, 生成一个本机映像。 ngen install c:\myfiles\MyLib.dll /ExeConfig:c:\myapps\MyApp.exe 在移除应用程序时将不移除以此方式安装的程序集。 若要卸载依赖项,请使用与安装时相同的命令行选项。下面的命令卸载上面示例中的 MyLib.dll。 ngen uninstall c:\myfiles\MyLib.dll /ExeConfig:c:\myapps\MyApp.exe 若要在全局程序集缓存中为程序集创建本机映像,请使用程序集的显示名称。例如: ngen install "ClientApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3c7ba247adcd2081, processorArchitecture=MSIL" NGen.exe 会为您安装的每个方案生成一个单独的映像集。例如,下面的命令为正常操作生成一个完整的本机映像集,为调试生成另一个完整的映像集,并为探测生成第三个映像集: ngen install MyApp.exe ngen install MyApp.exe /debug ngen install MyApp.exe /profile 显示本机映像缓存在缓存中安装本机映像后,就可使用 Ngen.exe 显示这些映像。下面的命令显示本机映像缓存中的所有本机映像。 ngen display display 操作首先列出所有的根程序集,然后列出计算机上的所有本机映像。 使用程序集的简单名称仅显示该程序集的信息。下面的命令显示本机映像缓存中与部分名称 MyAssembly 匹配的所有本机映像、其依赖项以及所有依赖 MyAssembly 的根: ngen display MyAssembly 了解哪些根依赖于共享组件程序集对于在共享组件升级后确定 update 操作的影响非常有用。 如果指定了程序集的文件扩展名,则必须指定路径,或从包含该程序集的目录执行 Ngen.exe: ngen display c:\myApps\MyAssembly.exe 下面的命令显示本机映像缓存中名为 MyAssembly 、版本为 1.0.0.0 的所有本机映像。 ngen display "myAssembly, version=1.0.0.0" 更新映像映像通常是在共享组件更新之后进行更新的。若要更新本身发生更改或者其依赖项发生了更改的所有本机映像,请不带任何参数使用 update 操作。 ngen update 更新所有映像可能会耗费很长时间。使用 /queue 选项可对更新操作进行排队以等候本机映像服务执行。有关 /queue 选项和安装优先级的更多信息,请参见本机映像服务。 ngen update /queue 卸载映像Ngen.exe 维护依赖项的列表,所以,只有当依赖于这些共享组件的所有程序集都被移除后,才会移除这些共享组件。此外,已安装为根的共享组件不会被移除。 下面的命令卸载根 ClientApp.exe 的所有方案: ngen uninstall ClientApp uninstall 操作可用于移除特定方案。下面的命令卸载 ClientApp.exe 的所有调试方案: ngen uninstall ClientApp /debug
下面的命令卸载特定版本的 ClientApp.exe 的所有方案: ngen uninstall "ClientApp, Version=1.0.0.0" 下面的命令卸载 "ClientApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3c7ba247adcd2081, processorArchitecture=MSIL", 的所有方案,或者只卸载该程序集的调试方案: ngen uninstall "ClientApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3c7ba247adcd2081, processorArchitecture=MSIL" ngen uninstall "ClientApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3c7ba247adcd2081, processorArchitecture=MSIL" /debug 与 install 操作一样,如果提供了扩展名,则需要从包含该程序集的目录执行 Ngen.exe,或者指定完整路径。 有关本机映像服务的示例,请参见本机映像服务。 3月21日 .net 中通用的formatstring格式符整理——转载
下表描述了标准数字格式字符串。请注意,这些格式说明符产生的输出字符串受“区域选项”控制面板中的设置的影响。使用不同设置的计算机会生成不同的输出字符串。
如果标准数字格式说明符未提供所需的格式化类型,可以使用自定义格式字符串进一步增强字符串输出。标准格式字符串包含一个字母字符,后面可能会跟有数字序列(形成一个 0 到 99 的值);而所有其他格式字符串都是自定义格式字符串。 请注意,对于固定点格式字符串(不包含“E0”、“E+0”、“E-0”、“e0”、“e+0”或“e-0”的字符串),数字被舍入为与小数点右边的数字占位符数目相同的小数位数。如果格式字符串不包含小数点,数字被舍入为最接近的整数。如果数字位数多于小数点左边数字占位符的个数,多余的数字被复制到输出字符串中紧挨着第一个数字占位符的前面。 |
|
|