65.9K
CodeProject 正在变化。 阅读更多。
Home

用于无人值守处理执行的通用服务引擎

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (53投票s)

2015 年 2 月 27 日

CPOL

31分钟阅读

viewsIcon

70465

downloadIcon

1125

使用 .NET Windows 服务作为专用自定义插件执行引擎

下载完整源代码 - 57.4 KB

引言

当需要开发任何类型的无人值守处理并希望使用 .NET 时,最自然的选择是创建一个 Windows 服务,使其保持运行并持续运行。如果您需要为不同目的多次执行此操作,您会很容易发现需要采用一些标准,并拥有一些已开发一次的通用功能(包括命令、配置、日志记录)。此外,您可能还需要一种简单的方法来调试和设置/安装您正在开发的服务。
在本文中,我将介绍一个软件(我称之为“PEprocessorEngine”),它正是源于这些需求。其目标是提供一个统一的系统,旨在支持通用无人值守处理执行。

该想法是创建一个独特的“引擎”(以单个 Windows 服务的形式,一次性安装),能够动态加载和运行不同的多个“模块”,这些模块是自定义的专用代码片段(以 .NET 程序集的形式),负责执行您希望以无人值守方式运行的特定任务。

这样,您就拥有了一个强大的环境,您的模块可以在其中运行,利用引擎提供的通用功能(如日志记录、监控等),并且能够非常轻松地启动、停止甚至添加新模块,而无需进一步安装。例如,添加一个新模块将通过将其开发为程序集、将其(及其自己的配置信息)复制到目标计算机,然后告知引擎(它已经运行并支持其他模块)加载并运行全新的模块来完成。

架构

我提出的 PEprocessorEngine 系统完全基于 Microsoft .NET Framework 3.5/4.0 开发(使用 VB.NET 语言),并由以下部分组成:

  • 一个名为 **PEprocessorEngine** 的原生 .NET Windows 服务,它负责管理处理模块的启动/停止,以及它们的动态加载/卸载,并提供日志记录/监控功能;
  • 一组可变且可配置的处理 **模块**(后称为“模块”或“处理器”),它们可以被选择性地加载/启动/停止,并进行独立配置;
  • 一个名为 **PEprocessorLib** 的类库程序集,包含实用函数和声明,PEprocessorEngine 和每个模块都引用它;
  • 一个专用的 SQL Server 数据库(称为 PE **支持数据库**),可以运行在 SQL Server 2005 及更高版本(包括 SQL Express)的任何版本上,用作引擎的支持存储、日志记录功能以及支持与用于监控和控制系统的自定义应用程序可能进行的交互。

PEprocessorEngine 是一个以 PEprocessorEngine.exe 可执行文件实现的本地 .NET Windows 服务。它直接引用 PEprocessorLib.DLL 类库,并配备了一个 XML 配置文件(App.config 文件,运行时名为 PEprocessorEngine.exe.config),其中包含引擎的配置设置以及各种已实现模块的通用配置设置(每个模块都有自己的配置文件,包含其特定设置)。PEprocessorEngine.exe.config 不包含单个 PE 处理器模块的特定设置,只包含引擎的设置。
如前所述,PEprocessorEngine 配备了一个支持 SQL Server 数据库,主要用于以下目的:

  1. 维护 PEprocessorEngine 服务和每个模块执行的操作的**集中日志**;
  2. 维护一个“**命令队列**”,用于通过数据库表向引擎提交命令;
  3. **控制**处理器正在运行/停止的**状态**。

PEprocessorEngine 行为

如前所述,PEprocessorEngine 被实现为一个 Windows 服务。启动时,引擎会从其自己的配置文件加载配置。然后,它会立即查找标识要自动启动的模块的配置键;这些键的名称为“PEprocessor”,例如:

<add key="PEprocessor" value="MyModuleA" />

<add key="PEprocessor" value="MyModuleB" />

引擎会尝试启动所有列出的模块,每个模块都在一个独立的线程中,通过加载、实例化和运行相应的程序集。要启动的模块的程序集在引擎运行的同一文件夹中搜索;每个模块必须在一个单独的程序集 DLL 中实现,其名称与模块本身相同(有关详细信息,请参阅“开发 PE 处理器模块”)。
每个模块都可以通过实现接口 PEprocessorLib.IPEprocessor 暴露的无参数方法 **MainTask** 来动态调用。在内存中加载模块的程序集并实例化模块的主类后,引擎只需在一个专用线程上调用该类上的 MainTask 方法。MainTask 模块的入口点仅被引擎调用一次;这可能发生在以下情况之一:

  • 引擎本身启动,并且模块配置为在引擎启动时自动运行;
  • 引擎收到 START 命令时(稍后查看)。

MainTask 方法的实现通常基于受控的无限轮询循环,并在“开发 PE 处理器模块”段中介绍。
当所有“自动启动处理器”都已启动后,引擎开始监听其命令队列,为一些**入站命令**提供支持,这些命令可用于控制引擎本身和处理器,这在“命令 PEprocessorEngine”段中有所描述。

PEprocessorEngine 配置

PEprocessorEngine 的配置存储在文件:**PEprocessorEngine.exe.config** 中。
此“主”配置文件中包含的设置和键在以下段落中进行了描述。

**PEinstanceName。** PEprocessorEngine 特定实例的实例名称。

**PEconnectionString。** 连接到 PE 支持数据库的连接字符串。

**CommandProcessorPollingInterval。** 此键确定发送到引擎的命令(即下文的 CommandQueue 表中的命令)的处理循环的轮询间隔(以毫秒为单位)。此键确定引擎对 CommandQueue 上发送的命令的“响应速度”。

**STOPcommandTimeout。** 此键确定 STOP 命令(下文查看)在终止相应线程之前必须等待模块终止的秒数。此键与 PollingSubIntervalsDuration 键相关:其值应始终大于 PollingSubIntervalsDuration 的值(下文查看)。

**PollingSubIntervalsDuration。** 此键确定 PEprocessorLib 的 PollingSleep 函数的量化子间隔的持续时间(以秒为单位)。此键影响 StoppingProcessors 控制表的轮询查询频率,如下文所述。

**CheckRestartIntervals。** 此键确定“检查和重启”PE 模块之间发生的命令处理器轮询间隔的数量。“检查和重启”功能保证自动重启任何意外停止的模块。如果 CommandProcessorPollingInterval 为 5 秒,则值为 720 等于 1 小时。

**PEprocessor**(多个键)。“PEprocessor”键表示在引擎启动时自动加载和启动的 PE 处理器模块;任何与字符串“PEprocessor”完全匹配的键都将被考虑。
这些键的值必须与模块本身实现的程序集文件名匹配。

PE 支持数据库

PE 数据库用于支持 PEprocessorEngine 的内部操作以及与外部应用程序进行交互。它包含三个表:

  • **CommandQueue** 表:支持与控制应用程序的通信,允许它们向引擎发送命令并从中检索响应;
  • **ProcessorLog** 表:支持引擎的内部日志记录功能,并允许维护自定义 PE 模块的集中日志;
  • **StoppingProcessors** 表:支持引擎和模块之间的 STOP 命令的内部信号。

以下段落将详细描述这些数据库表。

CommandQueue 表

CommandQueue 表用于向 PEprocessorEngine 发出命令并从其检索响应(如“命令 PEprocessorEngine”段中所述),其结构如下:

字段名

字段描述

IDcommand

标识,自动递增的键值。

HostName

目标引擎运行所在的主机名称。运行在不同计算机上的 PEprocessorEngine 的多个实例可能使用同一个数据库作为支持数据库,因此,也使用相同的命令队列和处理器日志。

CommandText

已发出命令的文本。

CommandIssued

发出命令的日期和时间(即:插入到 CommandQueue 表中的时间)。

ResponseText

服务在执行已发出命令后存储的详细文本响应。命令引擎的外部应用程序负责在发出相应命令并等待其实际执行后读取此响应。如果此字段包含 NULL,则该条目对应一个仍在等待处理的命令(并且 StatusQueue 字段等于 1)。

ResponseIssued

在 ResponseText 字段中存储命令响应的日期和时间。如果此字段包含 NULL,则该条目对应一个仍在等待处理的命令(并且 StatusQueue 字段等于 1)。

StatusQueue

命令条目的当前状态

1 = 待处理命令(尚未被引擎处理)

2 = 已执行命令(ResponseText 和 ResponseIssued 字段已相应填充)

ProcessorLog 表

ProcessorLog 表用于跟踪 PEprocessorEngine 和每个模块执行的操作,以及报告无人值守执行的警告和错误。

以下是表结构的描述。

字段名

字段描述

IDprocessorLog

标识,自动递增的键值。

LogDate

表示写入日志条目的日期和时间。它由 GETDATE() T-SQL 函数设置,因此引用的是 DB 服务器时钟。

LogType

根据此枚举指示日志条目的类型

1 (PEactivity):引擎在其操作期间生成的日志条目

2 (PEcommand):引擎在处理入站命令时生成的日志条目

3 (procLowImportance):由处理器模块生成的低重要性日志条目

4 (procHighImportance):由处理器模块生成的高重要性日志条目

LogSource

包含生成条目的模块名称。此字段还可以包含“PEprocessorEngine”的值,用于由引擎本身生成的条目。

ErrorCode

包含一个代码,指示已记录消息的类型。当前采用的代码是:

1 (noError):非错误消息(确认、ok 等)

2 (opError):操作错误消息

3 (warning):警告消息

ErrorDescription

包含记录的消息文本本身。由于数据类型是 varchar(MAX),此字段可以包含任何需要与错误本身一起携带的附加信息。

引擎内部发生的事件和错误会自动记录并存储在 ProcessorLog 表中。

由于每个模块可能需要不同类型的日志记录,具有不同的严重级别和/或详细程度,因此每个模块负责实现和操作自己的日志记录功能。

虽然模块可以通过自己的机制在其自己的存储上实现跟踪和日志记录,但 PEprocessorLib 库提供了一个 API,用于将模块相关的日志条目写入 ProcessorLog 表。这允许拥有一个集中式日志存储,收集引擎相关和模块相关的日志信息(有关详细信息,请参阅“模块内的日志记录”)。

StoppingProcessors 表

PE 引擎使用此表向模块发出操作正常停止的信号。StoppingProcessors 表的使用方式如下:

  • 在正常操作期间,它是空的;
  • 当通过 CommandQueue 向引擎发出 STOP 命令时,指定的模块将被列入 StoppingProcessors 表,指明托管引擎的机器(“HostName”字段)和停止请求的日期/时间(“Inserted”字段);
  • 在其生命周期中,每个处理器模块会定期检查 StoppingProcessors 表(通过调用 **Util.IsProcessorStopping** 方法)以检测指向自己的条目,表明需要结束其自身处理;
  • 如果检测到此类条目,模块必须正常停止其当前工作(这意味着:退出其主轮询循环);
  • 在停止其当前工作后,模块执行的最后一步是通过调用 **Util.SetStoppingProcessor** 方法从 StoppingProcessors 表中删除相关的条目,以信号通知其正常停止。

请注意,如“PE 模块结构”段中所述,**Util.PollingSleep** 方法(应在处理器的主要轮询循环中使用,以便在下一次迭代之前暂停)内部调用 **Util.IsProcessorStopping** 方法,以检查在模块等待下一次迭代时是否已发出 STOP 命令。

安装 PEprocessorEngine

由于 PEprocessorEngine 是一个标准的 .NET Windows 服务,因此可以通过标准的 `installutil` .NET 工具轻松安装。

安装 PEprocessorEngine 的文件夹必须包含:

  • PEprocessorEngine.exe
  • PEprocessorEngine.exe.config
  • PEprocessorLib.dll
  • 每个已安装的自定义 PE 模块的 DLL 程序集(和相应的 CONFIG)
  • 自定义 PE 模块可能引用的任何其他辅助程序集

请注意,PE 安装文件夹的内容在运行时会发生变化:因为 PEprocessorEngine 在备用 AppDomain 上执行处理器的程序集加载,所以这些程序集被复制到一个名为 LoadedAssembliesCache 的子文件夹中,该文件夹在 PE 安装文件夹中自动创建。

命令 PEprocessorEngine

引擎可以在数据库命令队列(在支持数据库的 **CommandQueue** 表中实现)上接收和处理一些“命令”。

引擎会定期轮询命令队列。当检测到待处理的命令需要执行时,引擎会处理该命令,并将已执行命令的结果/结果存储在命令条目本身的专用字段中。然后,命令应用程序负责在命令执行后通过读取命令条目来检索命令结果/结果。

引擎目前能够处理此处列出的命令(有关详细描述,请参阅以下段落):

命令

目的

START *processor*

启动特定的 PE 处理器模块。指定的模块必须预先正确安装和配置。

STOP *processor*

以正常方式停止特定的 PE 处理器模块。

QUERYSTATUS *[processor]*

查询模块的状态。

LISTPROC

列出所有已安装的 PE 处理器模块(即,实现 IPEprocessor 接口的 DLL 程序集)。

QUIT

停止所有正在运行的处理器模块并退出引擎。

监控 PEprocessorEngine

当您想监控 PEprocessorEngine 的执行时,信息的主要来源是 **ProcessorLog** 表中的日志记录。事实上,不仅引擎执行的主要操作会在此处自动跟踪,通常自定义 PE 模块也通过 PEprocessorLib 日志记录 API 在该表中记录其数据。

任何自定义应用程序都可以轻松地提交类似这样的查询来检查最近的 PEprocessorEngine 操作:

SELECT TOP 50 * FROM ProcessorLog WITH (NOLOCK) ORDER BY LogDate DESC

同样,如果您想检查引擎接收的命令及其执行情况,您可以轻松地提供类似这样的查询:

SELECT TOP 10 * FROM CommandQueue WITH (NOLOCK) ORDER BY CommandIssued DESC

“热更新” PE 处理器模块

引擎提供的功能使得对处理器模块进行所谓的“热更新”成为可能。“热更新”是指对特定模块进行更新(即用升级后的版本替换实现它的 DLL),**而不停止整个引擎**,因此不会影响该引擎实例上当前运行的所有其他处理器模块。

特定处理器模块的“热更新”可以通过以下步骤简单完成:

  • 停止特定模块(通过 STOP 命令);
  • 替换实现它的程序集为升级后的版本;
  • 重新启动模块(通过 START 命令)。

理论上,如果所有对处理器模块的更新都始终作为“热更新”进行,那么停止整个引擎(通过 Windows 服务控制管理器操作或向引擎本身发出 QUIT 命令)的需求仅限于引擎本身(或 PEprocessorLib)需要升级的情况。

开发 PE 处理器

以下是典型的 PE 处理器模块结构和特性的摘要描述:

  • 它是一个程序集 DLL,包含一个主类,该类实现 PE 处理器模块本身的逻辑;
  • PE 处理器名称必须匹配:
    • 主**类**的名称;
    • 包含主类的**命名空间**的名称;
    • **程序集**的名称(name.DLL);
    • XML **配置文件**的名称(name.CONFIG);
  • 它引用 **PEprocessorLib.DLL** 库(以及相应的 PEprocessorLib 命名空间);
  • 主类必须实现 **IPEprocessor** 接口(在 PEprocessorLib 中定义);
  • 包含 PE 处理器的最终程序集 DLL 必须(与其对应的 XML 配置文件一起)位于 PEprocessorEngine.EXE 的**同一文件夹**中,以便引擎本身能够正确找到、加载和执行它。

PEprocessorLib.IPEprocessor 接口公开这些属性和方法:

**Sub MainTask()** - 此方法将包含 PE 处理器的业务逻辑(请参阅下面的描述)。此方法在引擎需要启动特定模块时调用一次;它通常包含一个由 IsProcessorStopping 方法调用控制的无限循环。

**ReadOnly Property Name() As String** - 此属性返回实现 PE 处理器的类名(即,PE 处理器本身的名称);它用于标识 PE 处理器的实例和标记日志条目。

**Property ServiceInstanceName() As String** - 此属性由引擎在模块启动时设置,以便其知道启动它的引擎实例的名称。

MainTask() 方法的骨架通常包含以下部分:

  • 初始设置代码:主设置从处理器的“私有”XML 配置文件读取(使用 PEprocessorLib 库公开的 ReadSetting 实用函数);通常,为了使 PE 处理器配置的任何修改都生效,需要停止并重新启动特定的 PE 处理器模块。
  • 主轮询循环:一个“无限”循环(由调用 IsProcessorStopping 方法控制),例如,由以下一般步骤组成:
    • 检索要处理的项目;
    • 对于每个检测到的项目,启动一个处理任务,可能会管理出现的任何错误;
    • 归档或将已处理的项目设置为“已处理”;
    • 暂停一段时间(取决于模块特定的轮询间隔),然后再进行下一次迭代。暂停周期不应实现为简单的 Thread.Sleep() 调用,而应实现为调用 Util.PollingSleep()。这个 PEprocessorLib 方法执行一个“量化”的 Sleep(由 PollingSubIntervalsDuration 设置定义的子间隔组成),以便频繁监听来自引擎的可能 STOP 命令;基本上,IsProcessorStopping 方法在内部被调用,以检查在模块等待下一次迭代时是否已发出 STOP 命令:如果发生这种情况,暂停将提前终止。这确保了“正常”模块关机尽可能快地得到遵守。
  • 退出主轮询循环时执行的最终操作:当引擎请求正常停止时,执行将退出主轮询循环;在停止当前工作后(即退出主轮询循环并在完全终止 MainTask 方法执行之前),处理器执行的最后一步是通过调用 Util.SetStoppingProcessor 方法向引擎发送信号,表明其正常终止(此方法只是从 StoppingProcessors 表中删除与该模块相关的条目)。

例如,一个实际实现与 FTP 输入或文件系统馈送相关的处理任务的处理器模块,其轮询循环步骤将包括以下内容:

  • 检查受监控的文件夹(即:FTP 上传馈送的输入文件夹);
  • 对于每个检测到的文件,启动处理任务,最终管理出现的任何错误;
  • 将已处理的文件移出“输入馈送文件夹”(馈送可以根据处理的成功/失败和某些归档规则分组到专用文件夹中);
  • 暂停一段时间,然后再进行下一次迭代。

您可以在下载的代码中找到 PE 模块骨架的两个示例(C# 和 VB.NET)(在 Visual Studio 解决方案的“PEskeletonProcessorCS”和“PEskeletonProcessorVB”项目中)。

PE 模块配置

每个 PE 处理器模块都有自己的一组配置设置,存储在其“私有”配置文件 XML 中。每个 PE 处理器配置文件名的名称必须**与其模块名称相同**(例如:**MyProcessor.config** 将存储实现该模块的 **MyProcessor.DLL** 模块的配置设置,该模块在 MyProcessor 命名空间中也名为 MyProcessor 类)。

PE 模块的配置文件使用标准的 .NET “AppSettings”部分,包含键值对,如下例所示:

<configuration>

  <appSettings>

    <add key="PollingInterval" value="123" />

    <add key="ConnectionString" value="server=..." />

  </appSettings>

</configuration>

当然,如果您在不同的环境(如开发、集成、生产)中运行相同的处理器,每个环境可能都有自己的配置集,因此在将处理器的文件从一个环境部署到另一个环境时需要小心。

模块的配置设置

“私有”配置文件中包含的设置集对于每个 PE 处理器模块来说显然可能有所不同,具体取决于它实现的业务逻辑。它通常包括:

  • 一个配置键(传统上和约定上称为“**PollingInterval**”),它定义了处理器主轮询循环迭代之间的暂停时长;
  • 用于访问网络共享、数据库、Web 服务、FTP 服务器、SMTP 服务器的配置键;
  • 用于在测试/调试模式和正常/生产模式之间切换的配置键;
  • 用于调整日志记录和警告消息详细程度的配置键;
  • 用于在特殊情况下强制执行特定行为的配置键;
  • ...

读取模块配置

通过代码读取模块的配置设置非常简单:只需使用 **Util.ReadSetting** 方法,指定设置的名称和配置文件名。请记住,ReadSetting 始终返回字符串类型:

string ConfFile = "ProcessorName.config";

string ConnectionString;

ConnectionString = Util.ReadSetting("ConnectionString", ConfFile);

int PollingInterval;

PollingInterval = int.Parse(Util.ReadSetting("PollingInterval", ConfFile));

如前所述,ReadSetting 方法通常在 MainTask 方法的非常开头使用,在主轮询循环之前和之外使用。因此,它们只读取一次,并在模块执行的整个生命周期中保持“活动”(直到,比如说,发出 STOP 命令)。然后,如果您需要更改这些设置,您将不得不重新启动处理器以强制其读取新值。

根据具体的处理器逻辑,某些设置可能需要“即时”读取,而无需重新启动模块,从而提供“**热配置更改**”功能。为此,您只需将负责读取设置的代码(即,针对您需要“即时”可更改的键的 ReadSetting 调用)移到处理器的主轮询循环中。这样,模块将在每次轮询循环迭代时检查并读取配置文件。

模块内的日志记录

目前,要向 ProcessorLog 表写入行,可以使用 PELog 类。它公开以下方法:

  1. Public Sub LogMsg(ByVal LogCategory As LogEntryCategory, ByVal LogSource As String, ByVal LogType As LogEntryType, ByVal ErrorDescription As String)
  2. Public Sub LogMsg(ByVal LogSource As String, ByVal LogType As LogEntryType, ByVal ErrorDescription As String)

**LogCategory** 参数用于指定记录的操作类型:

  • **ProcessorActivity** 表示 PE 模块执行的活动
  • **EngineActivity** 表示 PE 引擎的内部活动或与引擎严格相关的处理器活动(例如,模块在收到引擎的 STOP 命令后发出的“正常停止”消息)
  • **EngineCommand** 表示 PE 引擎处理命令的内部过程(请勿在处理器代码中使用)

**LogSource** 参数应设置为调用上下文的名称(通常是当前正在写入日志条目的 PE 处理器模块的名称)。

**LogType** 参数用于指定记录错误的严重性:

  • **InfoMessage**:信息消息
  • **WarningMessage**:警告消息
  • **ErrorMessage**:操作错误

**ErrorDescription** 参数可用于记录有助于详细说明错误性质和来源的任何信息。它甚至可以用描述与报告错误相关的所有详细信息的结构化 XML 文档来填充。

第二个重载具有默认的 LogCategory = ProcessorActivity:在开发自定义处理器模块代码时,您应该在大多数情况下使用它。

在某些情况下,您可能希望将错误日志写入 Windows 事件日志而不是数据库。当发生某些关键错误或 PE 数据库不可达时,就会发生这种情况。Util 静态类公开了用于执行此操作的 API:

Public Shared Sub LogMsgEventLog(ByVal PEmoduleName As String, ByVal errorMesssage As String, ByVal errorSource As String, ByVal StackTrace As String)

Public Shared Sub LogMsgEventLog(ByVal PEmoduleName As String, ByVal errorMessage As String)

通过 PEtraceListener 进行日志记录

有时,PE 处理器模块使用的业务逻辑必须实现为封装在外部程序集中的外部类,并且该类(出于各种原因)必须尽可能独立于实现 IPEprocessor 接口的类和 PEprocessorLib 库。如果需要这种**解耦**,那么该类中的日志记录可以通过 PEtraceListener 进行,PEtraceListener 是一个包装 PELog 类的跟踪监听器。

要使用此替代日志记录方法,请执行以下操作:

  • 在实现 IPEprocessor 接口的类内部
    • (照常)实例化 PELog 类;
    • 将 PEtraceListener 注册为一个新的活动跟踪监听器,提供刚刚创建的 PELog 实例

PELog MyLog;

...

MyLog = new PELog(mServiceInstanceName);

System.Diagnostics.Trace.Listeners.Add(new PEtraceListener(MyLog, mProcessorModuleInstanceName));

  • 在需要执行解耦日志记录的引用类中,以如下方式发出信息消息、警告消息和错误消息的日志条目:

System.Diagnostics.Trace.TraceInformation("Info message");

System.Diagnostics.Trace.TraceWarning("Warning message");

System.Diagnostics.Trace.TraceError("Error message");

模块开发指南

PE 模块开发的一些通用指南:

  • 确保您的模块不会因意外的未处理**异常**而突然结束执行。最简单的方法是保护最高级别的代码执行(MainTask 入口点)放在 try/catch 块中。不要忘记在此 try/catch 块中记录意外的异常。
  • 不要将 **PollingSleep** 方法调用放在 finally 块中。这可能会阻止模块正确终止(即使在 STOP 命令超时的情况下)。
  • 提供各种级别的**日志记录**,具有不同的可配置详细程度。
  • 推广“**热配置更改**”支持以最小化模块重启。
  • 记录所有已处理的异常以**改进故障排除**。
  • 仔细调整模块的**PollingInterval** 和相关的引擎设置(CommandProcessorPollingInterval、STOPcommandTimeout 等)。

非迭代模块

非迭代模块是 PE 处理器模块的一个特例。它们不包含主轮询循环,而只包含在调用 MainTask 时要执行的操作。它们不会无限期地保持活动,但当它们启动时(在引擎启动期间或收到 START 命令后),它们会执行任务然后正常终止。

这种方法可能适用于仅在引擎启动时执行一次的活动,或用于由 START 命令触发的活动,该命令可能由与 CommandQueue 表交互的外部应用程序调用。您还可以考虑计划一个 SQL Server 作业,在特定时间表上为这些模块发出 START 命令。

调试服务

PEprocessorEngine 可执行文件(PEprocessorEngine.exe)通常需要作为 Windows 服务安装(使用 .NET 标准工具 `installutil.exe`)才能运行。但它也可以作为标准的命令行可执行文件运行,只需在命令行中指定“`/local`”参数来执行它。这样,您就可以运行它并停止它,而无需将其安装为 Windows 服务。如果您配置引擎加载和运行自定义处理器,那么(通过使用“`/local`”参数)您将能够运行引擎并将 Visual Studio 调试器附加到正在运行的进程,以逐行调试您的处理器代码。

如果您有一个包含引擎和您正在开发的处理器项目(如可下载材料中包含的解决方案)的 Visual Studio 解决方案,调试会话将更加轻松:您只需将“`/local`”参数设置为 PEprocessorEngine Visual Studio 项目属性页的“**启动选项**”中的“命令行参数”。

这使得可以直接在 Visual Studio IDE 中执行引擎(和模块)并进行逐行调试,而无需将调试器附加到正在运行的服务进程。

当调试编译为控制台应用程序的服务时,请记住,您想要调试的模块的 DLL 程序集和配置文件必须(连同 PDB 文件)复制到引擎可执行文件运行的同一目录中。

关注点

在开发此引擎时,我发现最有趣的点在于:

  • 在为不同模块管理独立线程;
  • 模块(=程序集)如何在特定 AppDomains 中加载到内存中,以便轻松卸载以支持“热更新”功能。

可下载材料

可下载的材料由一个 Visual Studio 2012 解决方案组成,包括:

  • PEprocessorEngine 的项目;
  • PEprocessorLib 的项目;
  • SQL 数据库创建脚本;
  • 两个示例项目(一个 C#,一个 VB.NET),概述了一个典型的 PE 处理器模块骨架。

未来增强功能

此段落旨在列出处理器引擎系统中可能进行的改进和增强,这些改进和增强是基于在文章初次发布后收集的讨论和建议。非常感谢所有贡献的同事和朋友:一些建议的新功能肯定会包含在未来的版本中。

自动启动模块的动态配置

在初始版本中,引擎启动时要自动启动的模块列表从 PEprocessorEngine.exe.config 中的“PEprocessor”键检索。如果它们存储在(并从)PE 支持数据库的数据库表中检索,它们可以被更轻松地编辑(即使是通过第三方应用程序),而无需修改引擎主配置文件。

内存中的停止命令

在初始版本中,引擎和要停止的模块之间的“消息传递”(作为对收到的 STOP 命令的结果)是基于在 StoppingProcessors 控制表中写入/检索记录。引擎可以改为实现一个内存通信机制,能够跨线程和跨 AppDomain 工作。这将消除对 StoppingProcessors 表的轮询。

处理器版本信息

引擎的初始版本在执行 LISTPROC 等命令时,以检测到的程序集版本形式报告已安装模块的版本。提供一个选项来报告文件版本**或**程序集版本可能会很有用。

last errors track

跟踪引擎和处理器在执行期间发生的 last errors 会很有用。当然,这些错误也记录在 ProcessorLog 表中;但是——由于 ProcessorLog 表被所有处理器模块使用,并且是错误、警告和信息消息的中心存储库——要特别关注特定处理器模块生成的特定严重性错误,尤其是在 ProcessorLog 表被较低严重性消息和/或由其他处理器生成的消息快速填充的情况下,并不总是那么容易。在这些情况下,“track last errors”功能会派上用场。

心跳功能

如果引擎能够产生一种心跳,那将很有用,也就是说:对其自身运行状态的周期性信号。这可以实现为一个存储在 PE 数据库中并以可配置频率定期更新的简单时间戳。这将允许任何第三方监控工具检查引擎的运行状态(以及可能每个已加载模块的状态),并在发生故障时发送适当的警报。

控制面板

提供一个引擎的控制面板会很方便,可以作为 Windows 窗体桌面应用程序或 ASP.NET Web 应用程序,让用户能够:

  • 检查和监控 PE 支持数据库中托管的日志表条目;
  • 检查 PE 模块的运行/停止状态;
  • 向 PE 模块发出启动/停止命令。

控制引擎的粒度

如果开发了一个外部应用程序来为用户提供一些控制引擎的功能(向其发送命令、启动/停止模块、检查日志),目前没有办法区分和精细控制用户操作特定 PE 处理器的能力。例如:一些用户只能访问某些处理器的日志条目,其他用户则只能启动/停止特定模块。能够做到这一点将是很好的。

模块的计划激活

能够创建处理器模块,其执行不只是基于轮询机制,而是可以由“时间点”执行预订触发,该预订可以是预先确定的,也可以由外部应用程序(可能是另一个处理器)设置。
在这些场景中,您可能需要一个能够“预订”目标处理器执行的外部应用程序:

  1. 在日历中定义了一个未来的事件,并且必须在事件日期/时间之前或之后预定的时间距离内发生某些自动操作;
  2. 在日历中定义了一个未来的事件,并且必须在与事件日期/时间相关的时段内执行某些自动处理;
  3. 处理器的工作执行必须按预定计划进行(每小时、每天、每周、每月);
  4. 处理器的工作执行必须按预定计划进行,并且该计划必须由操作员控制。

如果外部应用程序可以通过在数据库表中追加一行来“预订”模块的执行,指示目标处理器和执行日期/时间或周期,那将非常方便。应该提供一个简单的 API 来让目标处理器正确响应这些外部“预订”并据此执行其操作。
该 API 应包含一些灵活性,以管理可能错过计划的情况。

计划激活的 GUI

如果按照上一段的描述实现了模块的计划激活(在支持数据库中有一个新的表用于托管计划的执行),那么 SQL Server Agent 可以用作调度引擎来预订模块的执行。在这种情况下,SQLjobSchedulerGUI 可以用作 GUI 来控制这些计划,而无需使用 SQL Server Management Studio(有关详细信息,请参阅https://codeproject.org.cn/Articles/376731/A-scheduler-GUI-for-SQL-Server-Agent)。

将 PEprocessorEngine 移植到 Microsoft Azure

在 Microsoft Azure 上将 PEprocessorEngine 作为云服务运行将非常棒。这将带来多重好处。事实上,典型的 Azure Worker Role 被设计为只执行一项任务:如果您需要执行多项任务并希望将它们打包到一个 Worker Role 中,您需要将这些任务组合成一个代码块,完全失去每个任务的独立性和代码的模块化。这样,如果您只需要修改其中一个任务,您将被迫停止所有任务(它们实现为单个 Worker Role)。
在 Azure 上拥有类似 PEprocessorEngine 的东西将使您能够创建独立的模块化代码块,每个代码块实现一个特定任务,并在同一个 Azure Worker Role 的边界内独立加载和运行它们。
此外,让实现各种任务的代码以 PE 处理器模块的形式运行在 Azure 托管的 PEprocessorEngine 中:

  • 我们将获得部署和配置活动的更多敏捷性;
  • 我们可以获得(建议的)PEprocessorEngine 调度功能的优势;
  • 我们可以利用 Azure Worker Role 的横向扩展特性,这在重负载场景下很有用。

Azure Worker Role Engine

我决定遵循创建针对 Microsoft Azure 的 PEprocessorEngine 版本的想法,以便在 Microsoft 云中获得上述好处,包括:

  • 能够在同一个 Azure Worker Role 上独立更新、加载和运行**模块化和独立任务**;
  • 部署和配置活动的敏捷性(包括**热更新**);
  • **成本降低**,因为减少了对 Worker Role 实例的需求。

此解决方案使用 SQL Azure 作为支持数据库,并利用 Azure BLOB 容器作为用户放置处理器程序集并由引擎加载的地方。
我将这个新的 Azure 版本的 PEprocessorEngine 命名为“Azure Worker Role Engine”,目前这是一个个人开发项目(如果您有兴趣了解更多详细信息,请随时与我联系,尽管我很快将在网上发布有关“AWRE - Azure Worker Role Engine”的更多信息)。

 

历史

2015 年 2 月 27 日:文章创建,包含引擎的第一个版本
2015 年 3 月 2 日:添加了“未来增强功能”部分
2015 年 3 月 23 日:更新了未来增强功能
2015 年 4 月 20 日:更新了未来增强功能
2015 年 7 月 20 日:更新了未来增强功能
2015 年 8 月 11 日:添加了 Azure Worker Role Engine 段落
2015 年 9 月 8 日:关于 AWRE 开发的新闻

 

 

© . All rights reserved.