【问题标题】:Scaling out Windows Services横向扩展 Windows 服务
【发布时间】:2013-01-19 15:46:37
【问题描述】:

我正在寻找有关如何扩展我公司当前正在运行的 Windows 服务的意见。我们正在使用 .NET 4.0(可以并且将来会升级到 4.5)并在 Windows Server 2012 上运行它。

关于服务
该服务的工作是查询日志表中的新行(我们正在使用 Oracle 数据库),处理信息,创建和/或更新 5 个其他表中的一堆行(我们称之为跟踪表),更新记录表并重复。

日志记录表包含大量 XML(每行最多 20 MB),需要选择并保存在其他 5 个跟踪表中。新行一直以每小时 500,000 行的最大速率添加。
跟踪表的流量要高得多,从最小的表中的 90,000 行到最大表中的潜在数百万行,每小时不等。更不用说这些表上还有更新操作。

关于正在处理的数据
我觉得这一点对于根据这些对象的分组和处理方式找到解决方案很重要。数据结构如下所示:

public class Report
{
    public long Id { get; set; }
    public DateTime CreateTime { get; set; }
    public Guid MessageId { get; set; }
    public string XmlData { get; set; }
}

public class Message
{
    public Guid Id { get; set; }
}
  • 报告是我需要选择和处理的日志数据
  • 对于每条消息,平均有 5 个报告。在某些情况下,这可以在 1 到数百之间变化。
  • Message 有一堆其他的集合和其他关系,但它们与问题无关。

今天,我们几乎没有 Windows 服务管理 16 核服务器上的负载(我不记得完整的规格,但可以肯定地说这台机器是野兽)。我的任务是找到一种方法来扩展和添加更多机器,这些机器将处理所有这些数据并且不会干扰其他实例。

目前每条消息都有自己的线程并处理相关报告。我们分批处理报告,按其 MessageId 分组,以在处理数据时将数据库查询的数量减少到最低限度。

限制

  • 在这个阶段,我可以使用我认为合适的任何架构从头开始重写此服务。
  • 如果一个实例崩溃,其他实例需要能够从崩溃的一个离开的地方接起。不会丢失任何数据。
  • 从插入数据库的报告开始,此处理需要尽可能接近实时。

我正在寻找有关如何构建这样一个项目的任何意见或建议。我认为服务需要是无状态的,或者有没有办法以某种方式同步所有实例的缓存?我应该如何在所有实例之间进行协调并确保它们不处理相同的数据?如何在它们之间平均分配负载?当然,如何处理实例崩溃而不完成它的工作?

编辑
删除了无关信息

【问题讨论】:

  • 听起来像一个 ETL 过程。您是否考虑过查看 SQL Server Integration Services (SSIS) 之类的东西并编写可以安排运行以定期执行此过程的包?
  • 不幸的是,我们使用Oracle,高层不想听到任何关于SQL Server的消息。
  • 我只考虑它的 SSIS 部分,而不是数据库引擎 :) 替代方案可能是 Pentaho Data Integration (pentaho.com/explore/pentaho-data-integration) 或 Talend etl analytics (talend.com/solutions/etl-analytics)

标签: c# windows-services scalability horizontal-scaling


【解决方案1】:

对于您的工作项,Windows 工作流可能是您重构服务的最快方式。

Windows Workflow Foundation @ MSDN

您将从 WF 中获得的最有用的东西是工作流持久性,如果工作流从保存的最后一点发生任何事情,则设计合理的工作流可以从持久化点恢复。

Workflow Persistence @ MSDN

这包括在处理工作流时任何其他进程崩溃时从另一个进程恢复工作流的能力。如果您使用共享工作流存储,则恢复过程不需要在同一台机器上。请注意,所有可恢复的工作流都需要使用工作流存储。

对于工作分配,您有几个选择。

  1. 通过WorkflowService 类使用 WCF 端点通过工作流调用产生消息并结合基于主机的负载平衡的服务。请注意,您可能希望在此处使用设计模式编辑器来构造入口方法,而不是手动设置 Receive 和相应的 SendReply 处理程序(这些映射到 WCF 方法)。您可能会为每条消息调用该服务,也可能为每条报告调用该服务。请注意,CanCreateInstance 属性在这里很重要。与其相关的每个调用都会创建一个独立运行的运行实例。
    ~
    WorkflowService Class (System.ServiceModel.Activities) @ MSDN
    Receive Class (System.ServiceModel.Activities) @ MSDN
    Receive.CanCreateInstance Property (System.ServiceModel.Activities) @ MSDN
    SendReply Class (System.ServiceModel.Activities) @ MSDN

  2. 使用支持队列的服务总线。至少,您需要一些可能接受来自任意数量的客户端的输入,并且其输出可能被唯一标识并只处理一次的东西。想到的几个是 NServiceBus、MSMQ、RabbitMQ 和 ZeroMQ。在此处提到的项目中,NServiceBus 是专门为 .NET 准备的,开箱即用。在云环境中,您的选择还包括特定于平台的产品,例如 Azure 服务总线和 Amazon SQS。
    ~
    NServiceBus
    MSMQ @ MSDN
    RabbitMQ
    ZeroMQ
    Azure Service Bus @ MSDN
    Amazon SQS @ Amazon AWS
    ~
    请注意,服务总线只是将启动消息的生产者和可以存在于任意数量的机器上以从队列中读取的消费者之间的粘合剂。同样,您可以将此间接用于报告生成。您的消费者将创建工作流实例,然后可以使用工作流持久性。

  3. Windows AppFabric 可用于托管工作流,允许您使用许多适用于 IIS 负载平衡的技术来分配您的工作。我个人对它没有任何经验,所以除了它具有开箱即用的良好监控支持外,我无话可说。
    ~
    How to: Host a Workflow Service with Windows App Fabric @ MSDN

【讨论】:

  • 谢谢!我必须做一些阅读和测试,看看我的公司愿意做什么。
  • 鉴于您对报告解决方案对您的问题的评论,我应该警告您,WF 附带的持久性存储依赖于 MS SQL Server,这可能会破坏您的公司。如果您可以将 MSDE 用作持久性存储以避免必须设置 MSSQL 实例,这可能值得一看。
【解决方案2】:

我通过自己编写所有这些可扩展性和冗余的东西来解决这个问题。如果有人需要,我将解释我做了什么以及我是如何做到的。

我在每个实例中创建了一些进程来跟踪其他进程并了解特定实例可以处理哪些记录。启动时,实例将在数据库中注册(如果还没有的话)在名为Instances 的表中。此表包含以下列:

Id                 Number
MachineName        Varchar2
LastActive         Timestamp
IsMaster           Number(1)

如果未找到实例的MachineName,则在此表中注册并创建一行后,该实例开始每秒在一个单独的线程中ping此表,更新其LastActive列。然后它从该表中选择所有行并确保Master Instance(稍后会详细介绍)仍然存在——这意味着它的LastActive 时间在最后10 秒内。如果主实例停止响应,它将承担控制权并将自己设置为主实例。在下一次迭代中,它将确保只有一个 master(以防另一个实例同时决定同时承担控制权),如果没有,它将让给具有最低 Id 的实例。

什么是主实例?
该服务的工作是扫描日志表并处理该数据,以便人们可以轻松地过滤和阅读它。我没有在我的问题中说明这一点,但在这里可能是相关的。我们有一堆 ESB 服务器根据请求将多条记录写入日志表,而我的服务的工作是近乎实时地跟踪它们。由于他们正在异步写入日志,我可能会在日志中的started processing request A 条目之前获得finished processing request A。所以,我有一些代码可以对这些记录进行排序,并确保我的服务以正确的顺序处理数据。因为我需要扩展这项服务,所以只有一个实例可以执行此逻辑以避免大量不必要的数据库查询和可能的疯狂错误。
这就是Master Instance 的用武之地。只有它执行此排序逻辑并将日志记录ID 临时保存在另一个名为ReportAssignment 的表中。该表的工作是跟踪哪些记录已被处理以及由谁处理。处理完成后,记录将被删除。该表如下所示:

RecordId        Number
InstanceId      Number    Nullable

主实例对日志条目进行排序并在此处插入它们的 ID。我的所有服务实例以 1 秒的间隔检查此表,以查找未被任何人处理或正在由非活动实例处理的新记录,以及 [record's Id] % [number of isnstances] == [index of current instance in a sorted array of all the active instances](在 Ping 过程中获取)。查询看起来有点像这样:

SELECT * FROM ReportAssignment 
WHERE (InstanceId IS NULL OR InstanceId NOT IN (1, 2, 3))   // 1,2,3 are the active instances
AND RecordId % 3 == 0    // 0 is the index of the current instance in the list of active instances

我为什么需要这样做?

  • 其他两个实例将查询RecordId % 3 == 1RecordId % 3 == 2
  • RecordId % [instanceCount] == [indexOfCurrentInstance] 确保记录在所有实例之间均匀分布。
  • InstanceId NOT IN (1,2,3) 允许实例接管崩溃的实例正在处理的记录,而不是在添加新实例时处理已活动实例的记录。

一旦实例查询这些记录,它将执行更新命令,将InstanceId 设置为它自己的,并在日志表中查询具有这些ID 的记录。处理完成后,它会从ReportAssignment 中删除记录。

总的来说,我对此感到非常满意。它可以很好地扩展,确保在实例出现故障时不会丢失任何数据,并且几乎没有对我们现有的代码进行任何更改。

【讨论】:

    猜你喜欢
    • 2011-11-26
    • 2013-11-02
    • 2017-09-28
    • 1970-01-01
    • 1970-01-01
    • 2021-10-13
    • 1970-01-01
    • 2020-11-25
    • 2022-11-28
    相关资源
    最近更新 更多