WF的性能特征(一)
内容
介绍
本文档对在Windows Workflow Foundation基础上开发应用程序时非常重要的关键性能注意事项和建模原则提供了整体讨论,描述了包括WF 中部分关键功能的几个演示场景(scenarios)的性能特性。它还概括出了独立组件的性能考虑,用以指导您决定修改设计或部署配置,以改进性能或优化指定应用。应该注意,您不应该将本文档描述的性能特征视为可以支持所有系统的基准度量,只有在目标系统上实际测试才可以提供准确的度量。
本文不讨论单个功能,组件,或配置在某个具体的部署或方案对整体性能的影响。本文档是一个说明性指南,不会提供优化某个特定 Windows Workflow Foundation 场景的说明信息或建议。
本文假定读者已经熟悉 Windows Workflow Foundation 的基础知识,主要包含四个主要部分:工作流性能的常用注意事项、工作流场景、工作流性能案例研究和工作流组件级性能。
“常见工作流性能注意事项”部分介绍了用于调整和提高基于WF的应用程序的性能方面最重要的注意事项。“工作流场景”部分提供了三个常见的基于工作流的应用程序,介绍了相关的性能特点和设置,并给出了性能测试的结果。“工作流性能个案研究”部分通过图表显示了关键的工作流性能特征。最后,“工作流组件级性能”部分描述了几个WF组件的性能。
主要性能注意事项
本节介绍Windows Workflow Foundation中最重要的性能注意事项。
工作流的主要性能因素
下列因素对使用 WF 构建的应用程序的性能产生最大影响。
工作流实例状态或上下文已完成的活动的持久性
大多数工作流应用程序需要保存某些时候工作流的实例状态。例如,当一个事务作用域完成,或是要卸载一个空闲并正在等待事件的长时运行的工作流实例时,你可能需要保存工作流实例状态。在大多数情况下,工作流实例状态会保存到持久存储区(比如Microsoft SQL Server表),并在稍后继续运行。
工作流实例在运行期间遇到持久点时,工作流运行时将调用持久化服务来完成这项工作。持久化服务会再将工作流状态序列化(serialize)到一个使用BinaryFormatter的流(可以选择压缩),并将其保存到持久存储区。工作流的序列化/反序列化以及流的压缩/解压缩操作都是可能对您的应用程序性能具有很大影响的大量占用CPU的操作。现成的SqlWorkflowPersistenceService 使用 GZipStream 类用于压缩工作流实例。
下面是保存一个工作流状态的几种方法:
- 在下列情况之一时由工作流运行时发起:
- 一个使用PersistOnClose 属性标记的活动。
- 一个工作流实例在等待事件或计时器时超时,并且WorkflowPersistenceService 类的UnloadOnIdle方法返回true。
- 工作流运行时将停止。
- 由工作流宿主程序或自定义服务使用WorkflowInstance 类的tryUnload 或Unload方法。
同样,也有几种方法将工作流实例状态加载到内存中:
- 在工作流运行时启动时由持久化服务发起:
- 工作流运行时在启动过程中会启动持久化服务,例如默认的 SqlWorkflowPersistenceService 类会尝试加载和运行所有未阻塞的(unblocked)工作流实例。
- 工作流计时器超时而此时实例不在内存中。
- 由工作流主机或自定义服务发起:
- 在WorkflowInstance 类中使用任何公共的控件事件,比如当工作流实例不在内存中时加载、恢复、中断、终止或挂起。
- 工作流实例空闲并且不在内存中时通过ExternalDataExchange 服务引发一个事件。
为了降低持久化一个工作流实例状态的性能影响,重点是知道您的工作流中的持久点的数量,并试着在确保容错和场景正确执行的情况下尽可能地减少它们。例如,您可能希望您的工作流仅仅只在逻辑上成组的活动完成后,而不是每个单独活动完成添加持久点。
图 1。工作流中的持久点
下表列出了几个性能计数器,可用于监视特定工作流应用程序的持久性点数。
Performance Counters |
Workflows Persisted/sec and Workflows Persisted |
Workflows Loaded/sec and Workflows Loaded |
Workflows Unloaded/sec and Workflows Unloaded |
工作流实例状态大小
工作流状态大小和持久化开销有直接的关系,复杂的工作流在CPU、内存、磁盘I/O和磁盘空间方面要比简单的工作流占用更多资源,所以有计划地测量工作流实例在内存中运行时和持久化到存储中时的大小是重要的。
下面的图 2 显示了示例实例状态大小的方式取决于工作流模型:
图 2。工作流实例状态大小
下面的SQL查询可用于查询由缺省的SqlWorkflowPersistenceService持久化的工作流实例的大小:
SELECT DATALENGTH(state) FROM [InstanceState] WHERE uidInstanceID = @uidInstanceID
除了内部的工作流和活动状态之外,增加到根和自定义活动的字段都将当工作流实例被持久化时增加负荷。除非用System.NonSerialized 属性标记(在使用BinaryFormatter进行序列化时,这是WorkflowPersistenceService 类中GetDefaultSerializedForm方法的默认行为),工作流和自定义活动中的所有字段都将被序列化。在某些情况下,通过在运行时填充这些字段,以避免代价高昂的序列化是有可能的。
需要持久化服务活动执行上下文的工作流活动
活动实例的状态是由工作流运行时通过活动执行上下文 (AEC) 自动管理,工作流运行时使用 AEC 来维护活动实例状态并能够在需要时执行补偿逻辑(compensation logic)。
当活动需要被重执行(re-executed)时,会使用BinaryFormatter 类创建一个新 AEC,此操作对工作流应用程序可能产生性能影响,尤其是在被克隆的AEC较复杂(例如,有多个嵌套的活动)的情况下。
一些会创建新的执行上下文的缺省活动包括:WhileActivity. ReplicatorActivity. ConditionedActivityGroup. StateActivity 和 EventHandlerActivity。
下图显示了WhileActivity活动在通过每次迭代期间生成新的AEC:
图 3。克隆与 WhileActivity 活动执行上下文
为了提高性能,活动的开发者应该开发比起基本活动库中的活动没那么灵活,但更满足特定业务要求和提供最佳性能的自定义活动。
带事务支持的工作流活动
具有事务的活动,如默认提供的TransactionScopeActivity和CompensatableTransactionScopeActivity,在执行之前会创建工作流实例初始状态的一个副本,此副本保留在内存中用于事务在需要时回滚。活动执行成功之后,已更新的实例状态被持久化,并且事务被提交。
工作流运行时在内部通过.NET的System.Transactions 机制来处理事务,因此在必要时可以透明地将本地事务扩展(escalation)为分布式事务 (MSDTC) 。
如果需要处理长时间周期的事务,从性能角度出发,可以考虑实现IPendingWork和IWorkBatch接口支持事务,而不是使用支持事务的缺省活动(请参阅Windows Workflow Foundation文档中WorkflowCommitWorkBatch服务的例子)。支持事务的缺省活动会从活动的一开始就打开事务直至结束,在某些情况下会降低效率,而IPendingWork 和 IWorkBatch 可以用作为替代方案。
具有补偿支持的工作流活动
具有嵌套的可补偿(compensatable)活动的工作流,其实例状态的序列化可能会出现性能问题。例如,如果一个可补偿活动是在WhileActivity活动内部重执行的,当我们每次循环迭代时工作流实例状态都会增长,增加了实例状态序列化时的开销。
有两个现成的活动支持补偿:第一个是 CompensatableSequenceActivity 活动,仅支持补偿;和 CompensatableTransactionScopeActivity活动,还支持事务,类似于TransactionScopeActivity活动。如果只需要支持补偿,就应避免事务支持有开销,应该使用CompensatableSequenceActivity活动。
工作流活动的执行跟踪
每个工作流跟踪事件的数量会严重影响性能,请查看您的跟踪配置文件并尽可能多地减少事件的跟踪。
工作流活动的复杂性
许多不同的因素会影响活动的执行性能。在通常情况下,从基础活动类派生、较少字段和属性的简单活动提供最佳的性能和序列化时的最小开销。
影响活动执行性能的一些重要因素包括:
- 字段数量
- 访问的属性和依赖属性
- 服务被占用
- 工作流队列正在使用
- 活动执行代码的开销
活动有验证逻辑也是需要考虑的一个重要因素,即使它只影响活动的第一次执行,并且可以禁用(请参阅本节下面的工作流设置)。
复合活动如 SequenceActivity 和 EventHandlingScopeActivity,更增加了序列化的开销,因为它们比非复合活动更复杂。复合活动不得不安排子活动的执行,因此带来了与这些类型的活动相关的额外的性能损失。此外,项目符号列表中列出的和上一段列出的所有性能因素也适用于复合活动。
工作流激活的开销
当一个工作流宿主程序调用WorkflowRuntime.CreateWorkflow方法时,工作流会被激活。此方法被第一次调用时,会创建一个活动树并验证工作流的活动。在第二次执行时,活动树已在内存中,只验证工作流的活动,工作流运行时只会创建准备执行的活动对象,以尽量减少调用此方法的性能开销。
工作流的激活时间随工作流中的活动的数量增加,上一节中所述活动的复杂性也会影响工作流的激活时间。
工作流的ExternalDataExchange数据和工作流参数
通过EventActivity传递到工作流的数据将被序列化并在工作流队列中排队,因此这些操作带来了序列化/反序列化的性能开销,并且性能变化取决于被传递到工作流事件活动的类型的复杂性和参数数量。
工作流输入/输出参数的数量也是要考虑的性能因素,使用较少的工作流参数的性能开销通常也最小。
工作流断言式的规则条件(rule conditions)和policy活动
在通常情况下,代码条件的计算速度要快过规则性的rule conditions条件。rule conditions条件需要从工作流的程序集资源中提取出来并在执行之前反序列化,代码条件则没有这些额外的处理。
在许多情况下,断言式的规则条件提供了足够好的性能,但在某些性能要求很高的应用程序中建议您使用代码条件来避免它们。
如果policy活动或是规则条件的性能成为您的应用程序中的问题,可以考虑编写自定义工作流服务缓存规则定义,然后使用自定义活动从缓存中查询规则定义并执行它。在其他情况下,在代码中生成规则是更好地方法。使用 RuleEngine 类则是执行规则定义的最佳方法。
规则定义的优先级和链接行为应该仔细设置,因为它们可能会对应用程序的性能具有很大影响。
工作流实例动态更新
动态更新是在运行时更新工作流实例的一种机制,为防止争用出现,在主机应用程序编著(authoring)并应用动态更改之前工作流将首先被挂起。
因为实例被挂起、复制、验证并应用更新,动态更新是代价昂贵的操作,在高吞吐量工作流应用程序要应用这一有用的功能时应该铭记它的性能影响并认真考虑。
工作流的依赖属性(dependency properties)
依赖属性是一种功能强大的机制,以帮助您公开活动属性以使它们能在工作流实例中绑定和共享。这一类型的属性带来了小小的性能开销,特别是当多个依赖属性从工作流中被访问时。但在多数情况下,增加的开销是很小的。
当正则属性或字段被使用时应该避免使用依赖属性。
工作流状态机的根活动
因为要进行流动树寻迹(navigation)和AEC持久化服务,在工作流状态机中,迁移进或出深嵌套的状态都是代价高昂的操作,您应尽可能地尝试避免出现深嵌套的状态。
工作流运行时启动的开销
创建一个WorkflowRuntime 对象并调用StartRuntime方法也是一个代价高昂的操作,需要读取运行时的配置参数并启动所有缺省和自定义的工作流服务,比如:持久化服务、跟踪服务或事务处理支持服务。
理想情况下,在每个应用域内您应该只使用一个WorkflowRuntime对象并重用它执行工作流实例,以最大限度地减少创建和启动的性能开销。
工作流运行时服务
Windows Workflow Foundation包括了几个服务为运行时提供重要的功能,本节介绍与这些服务相关的最重要的性能注意事项。
调度(Scheduler)服务
工作流运行时需要一个调度服务以驱动工作流的执行,Windows Workflow Foundation提供了两个不同的调度服务应该足以满足大多数应用的需要,但是也可以编写一个更加适合您需求的调度服务。
DefaultWorkflowSchedulerService
DefaultWorkflowSchedulerService在将工作流运行时提交的工作项移交到托管线程池执行之前,将它们排队到一个本地队列。有一个性能计数器可用于监视服务的当前队列中等待线程的工作项的数量(计数器名:Workflows Pending)。
托管线程池由后台线程与默认最大为每个可用处理器 25 个的工作线程组成,此外,最少的可用空闲线程数量将等于本机上的处理器数。DefaultWorkflowSchedulerService还通过MaxSimultaneousWorkflows属性控制排队进入托管线程池的工作项的最大数量。
所有这些设置对任何应用程序都是重要的性能因素,应该仔细优化,所有这些设置的默认值不一定能为所有应用程序都提供最佳的性能。
在通常情况下,有过多的运行或空闲线程都可能是资源浪费,但在另一些情况下,太少的线程也可能会导致性能问题。寻找到正确的平衡将是一个完全取决于应用程序的性能调整课题。
ManualWorkflowSchedulerService
ManualWorkflowSchedulerService 允许应用程序在由宿主应用程序拥有的线程上来启动或恢复工作流执行。
工作流宿主应用程序调用RunWorkflow方法执行工作流到下一个空闲点或者完成。
ManualWorkflowSchedulerService使您可以在应用程序中完全控制执行工作流实例的线程,因此,更改线程设置如线程优先级、管理的活动线程数等等都是可以的。这是ASP.NET/Web服务应用的首选调度服务。
事务处理服务
工作流运行时包括两个不同的服务用于执行事务操作。
DefaultWorkflowCommitWorkBatchService
这是由工作流运行时为事务支持提供的缺省服务,该服务使用.NET框架的System.Transactions机制来管理事务,因此能提供动态扩展(在事务实际需要时,它可以使用MSDTC)。
SharedConnectionWorkflowCommitWorkBatchService
此服务被创建在应用程序使用SQL Server作为该工作流的持久性数据库时的缺省事务服务,提供优化的性能,只可以与SqlWorkflowPersistenceService服务和 SqlTrackingService服务部署在同一个数据库。这使得 SharedConnectionWorkflowCommitBatchService服务在持久点可以共享SqlConnection以避免事务被动态扩展到SQL Server 2000 DTC上。对于此特定配置,SharedConnectionWorkflowCommitBatchService 服务提供了最佳的性能。
除非不进行SQL跟踪、或是需要使用SQL工作流实例数据存储、或是使用SqlWorkflowPersistenceService服务并运行在 SQL Server 2005,在所有其它场合,都应使用DefaultWorkflowCommitWorkBatchService服务。
持久化服务
持久化服务用于将工作流实例数据保存在一个持久存储,比如SQL数据库,它通常是具有状态管理要求的任何工作流应用程序中性能最关键的部分之一。
SqlWorkflowPersistenceService
Windows Workflow Foundation提供现成的用于存储工作流实例数据到SQL服务器数据库上的持久化服务。
增加SqlWorkflowPersistenceService到工作流运行时,在工作流完成时会带来一些性能开销,用以执行SQL存储过程来从工作流持久性数据库中删除所有与实例相关的数据。在前文中提到,工作流实例如果有多个持久点,将带来序列化/反序列化和压缩开销。此外,在这些持久点上还有调用SQL 存储过程写入或读取的数据的开销。
当SQLWorkflowPersistenceService启动,它从数据库装载所有未阻塞的工作流实例。未阻塞(unblocked)的工作流实例是指那些是忙,以及在OwnershipTimeoutSeconds参数指定时,是由当前宿主程序拥有或不归任何宿主拥有的工作流实例。装载尤其所有实例是代价很高昂的操作,尤其是在数据库中数量较多时。同样,如果工作流运行时停止,所有内存中的活动实例都将被卸载。
在 SQLWorkflowPersistenceService 类中的UnloadOnIdle属性用于确定在它们空闲后是否立即卸载工作流实例。
这可以在出现错误时读取工作流实例状态以允许重试。
SQLWorkflowPersistenceService能支持使用工作流计时器,计时器可配置为在规则基础上按LoadingInterval 属性的值以秒为单位从数据库中定期装载。如果有多个计时器要同时从数据库中读取时,工作注实例时会影响性能。
最后, SQLWorkflowPersistenceService 为工作流实例提供了锁,用来支持多个工作流宿主应用程序访问同一个工作流持久性数据库的情况。在这种情况下,该服务会有一个启动参数OwnershipTimeoutSeconds用来通知宿主程序可以拥有给定工作流实例多长时间,在此期间,其他宿主程序不能访问该实例,因为它已被锁定。
跟踪服务
工作流跟踪服务用于监视工作流和活动在执行时的事件,工作流运行时支持添加和使用多个不同的工作流跟踪服务。
SqlTrackingService
SqlTrackingService 将工作流跟踪事件数据写到SQL数据库。
监视跟踪数据库的增长和磁盘消耗是重要的,您应该定期存档并清理跟踪数据库。SqlTrackingService支持创建多个分区,可以更容易地存档和销毁旧的跟踪数据。
按照下列步骤操作启用SQL跟踪分区、存档或删除分区:
1. 在SqlTrackingService设置PartitionOnCompletion属性为true,或者您可以选择在工作流应用程序中使用 PartitionCompletedWorkflowInstances存储过程来执行分区批处理,这样可以减少执行周期。这个选项对运行时性能有很高的影响,但它使您在需要时能够快速存档或删除跟踪数据。
2. 通过执行下列存储过程来设置分区间隔:
EXEC SetPartitionInterval 'd'
(变量 " d " 代表每天创建一个新分区。)
3. 对跟踪数据库运行备份脚本或运行bcp命令存档所有跟踪表数据。
4. 可选择地,您可以通过调用以下存储过程删除旧的分区:
EXEC DropPartition @PartitionId
注意 执行下述查询你可以查询所有不活动的分区:
SELECT * FROM dbo.TrackingPartitionSetName WHERE EndDateTime IS NOT NULL
SqlTrackingService 使用的默认跟踪配置文件包括以下跟踪点:
- 活动执行状态事件:初始化、执行、补偿、取消、关闭和故障
- 工作流状态事件:创建、完成、运行、挂起和终止
在应用程序中可以跟踪更多的事件,相应的跟踪开销也更大。
默认情况下,SqlTrackingService是在批处理模式下运行(即IsTransactional属性被设置为true),意味着工作流实例的跟踪事件是在一个批处理中作为工作流实例状态被写入到数据库,这通常能为大多数的工作流设计提供最佳性能。但是如果是一个没有持久化而执行许多活动的工作流并且要跟踪所有活动事件时,会产生负面的性能影响。例如,一个迭代多次但中间不会持久化的WhileActivity活动,在最终被持久化时将产生巨大的性能开销。此时,工作流最终被持久化,而跟踪记录也需要刷新到数据库,可能需要一些时间才能完成写入。为避免这种情况,在WhileActivity活动体的某处设计工作流的持久点可能是必需的。
SqlTrackingService 也可以运行在非批处理模式下(即IsTransactional属性设置为 false),此时每当跟踪事件产生就会被刷新到数据库。
修改跟踪配置文件来跟踪工作流中的成员具有一些性能影响:它包括获取对象的反射调用、对象的二进制序列化、最终将对象写入跟踪数据库的额外SQL调用。为完成这些附加工作,最好是在整体上在需要跟踪的数据量和对系统的影响之间进行平衡。
工作流与性能相关的配置设置
所有以下的配置设置都是对性能非常重要的,可供您用来调整工作流应用程序的性能。它们的大多数可以通过类属性、应用程序配置文件或是类的构造函数来设置。
类(名称) |
值 |
描述 |
EnablePerformanceCounters
(WorkflowRuntimeSection) |
true / false |
允许/禁止工作流性能计数器 |
ValidateOnCreate
(WorkflowRuntimeSection) |
true / false |
允许/禁止工作流首次执行时验证活动 |
EnableRetries
(DefaultWorkflowCommitWorkBatchService) (SharedConnectionWorkflowCommitWorkBatchService) |
true / false |
允许事务在持久点发生错误时重试 |
MaxSimultaneousWorkflows
(DefaultWorkflowSchedulerService) |
Integer(4*处理器数量 ) |
设置可被同时执行的工作流的最大数量 |
EnableRetries
(SqlWorkflowPersistenceService)
(SqlTrackingService) |
true / false |
允许SQL查询在错误时重试 |
LoadIntervalSeconds
(SqlWorkflowPersistenceService) |
Integer(秒) (120 ) |
定时装载的频率 |
IsTransactional
(SqlTrackingService) |
true / false |
指定工作流实例的事件跟踪是否采用批处理方式 |
UseDefaultProfile
(SqlTrackingService) |
true / false |
指定要被使用的缺省跟踪配置文件 |
PartitionOnCompletion
(SqlTrackingService) |
true / false |
指定实例在完成时是否要移到分区,始终可以稍后使用PartitionCompletedWorkflows存储过程来移动 |
OwnershipTimeoutSeconds
(SqlWorkflowPersistenceService) |
Integer (秒) (0 ) |
以秒为单位指定宿主程序可以拥有实例的最长周期 |
DisableWorkflowDebugging
(System.Diagnostics - 工作流配置文件中开关) |
true / false |
允许/禁止工作流调试 |
注意 以粗体显示的是这些属性的缺省值。