7

我正在寻找有关如何扩展目前在我公司运行的 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 到数百之间变化。
  • 消息有一堆其他集合和其他关系,但它们与问题无关。

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

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

限制

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

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

编辑
删除无关信息

4

2 回答 2

6

For your work items, Windows Workflow is probably your quickest means to refactor your service.

Windows Workflow Foundation @ MSDN

The most useful thing you'll get out of WF is workflow persistence, where a properly designed workflow may resume from a Persist point, should anything happen to the workflow from the last point at which it was saved.

Workflow Persistence @ MSDN

This includes the ability for a workflow to be recovered from another process should any other process crash while processing the workflow. The resuming process doesn't need to be on the same machine if you use the shared workflow store. Note that all recoverable workflows require the use of the workflow store.

For work distribution, you have a couple options.

  1. A service to produce messages combined with host-based load balancing via workflow invocation using WCF endpoints via the WorkflowService class. Note that you'll probably want to use the design-mode editor here to construct entry methods rather than manually setup Receive and corresponding SendReply handlers (these map to WCF methods). You would likely call the service for every Message, and perhaps also call the service for every Report. Note that the CanCreateInstance property is important here. Every invocation tied to it will create a running instance that runs independently.
    ~
    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. Use a service bus that has Queue support. At the minimum, you want something that potentially accepts input from any number of clients, and whose outputs may be uniquely identified and handled exactly once. A few that come to mind are NServiceBus, MSMQ, RabbitMQ, and ZeroMQ. Out of the items mentioned here, NServiceBus is exclusively .NET ready out-of-the-box. In a cloud context, your options also include platform-specific offerings such as Azure Service Bus and Amazon SQS.
    ~
    NServiceBus
    MSMQ @ MSDN
    RabbitMQ
    ZeroMQ
    Azure Service Bus @ MSDN
    Amazon SQS @ Amazon AWS
    ~
    Note that the service bus is just the glue between a producer that will initiate Messages and a consumer that can exist on any number of machines to read from the queue. Similarly, you can use this indirection for Report generation. Your consumer will create workflow instances that may then use workflow persistence.

  3. Windows AppFabric may be used to host workflows, allowing you to use many techniques that apply to IIS load balancing to distribute your work. I don't personally have any experience with it, so there's not much I can say for it other than it has good monitoring support out-of-the-box.
    ~
    How to: Host a Workflow Service with Windows App Fabric @ MSDN
于 2013-02-04T21:55:22.963 回答
2

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

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

Id                 Number
MachineName        Varchar2
LastActive         Timestamp
IsMaster           Number(1)

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

什么是主实例?
该服务的工作是扫描日志表并处理该数据,以便人们可以轻松地过滤和阅读它。我没有在我的问题中说明这一点,但在这里可能是相关的。我们有一堆 ESB 服务器根据请求将多条记录写入日志表,而我的服务的工作是近乎实时地跟踪它们。由于他们正在异步写入日志,因此我可能会在日志中获得一个finished processing request Abeforestarted 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](在 Pinging 过程中获取的)。查询看起来有点像这样:

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

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

于 2013-03-11T18:16:07.237 回答