如何使活动服务具有高可用性?

8
我知道通过使用“网络负载均衡”和“故障转移群集”,我们可以使“被动服务”高度可用。但是对于“主动应用程序”呢?
例如:我的一个应用程序在固定时间间隔内从外部资源检索一些内容。我想到了以下几种方案:
1. 在单台机器上运行。问题:如果此实例崩溃,则无法检索内容。 2. 在群集的每台机器上运行。问题:将多次检索内容。 3. 在群集的每台机器上都安装该应用程序,但是只在其中一台机器上运行。每个实例都必须检查某种公共资源,以确定是否轮到自己执行任务。
当我考虑解决方案 #3 时,我想知道公共资源应该是什么。我想创建一个数据库表,在其中可以使用它来获取全局锁。
这是最好的解决方案吗?人们通常如何做到这一点?
顺便说一下,这是运行在 Windows Server 2008 上的 C# .NET WCF 应用程序。
6个回答

4
针对这类问题,他们发明了消息队列。想象一下这样的情况,当您的群集化应用程序都监听一个消息队列(自身也进行了群集化 :-))时,某个时刻,一个实例会接收到您下载外部资源的初始命令。如果成功,该实例则清空该消息,并在 '运行时间' + '间隔' 等于 '延迟执行时间' 的后面发布另一个消息。但是,如果该实例在处理期间死机,那就没问题了。消息将在队列中回滚(超时后),其他实例可以获取它。有点交易,有点消息队列。
我在Java EE领域,所以可以帮你处理编码细节。

点赞,因为这是一个好的模式要遵循,但我认为你的答案并不完全适用于OP,因为他正在寻找特定于NLB和集群的可用性选项,而不是企业架构。 - Josh E
看一下亚马逊简单队列服务,你可以使用类似的实现(甚至购买他们的服务)。 - dwery

1

我曾经使用你的解决方案#3实现过类似的东西。

创建一个名为resource_lock的表,其中包含一列(例如locking_key),该列将包含一个锁定密钥。

然后在每个间隔期内,您的应用程序的所有实例都会执行以下操作:

  1. 运行查询类似于'update resource_lock set resource_key = 1 where resource_key is null'。 (当然,您也可以插入服务器特定的id,时间戳等)
  2. 如果更新了0行:不做任何事情-另一个应用程序实例已经获取了资源。
  3. 如果更新了1行:获取资源并将locking_key设置回null

这样做有两个优点:

  • 如果其中一个服务器失败,则仍然可以由仍在运行的服务器获取资源。
  • 将锁定留给数据库,这样可以节省您自己实施锁定的时间。

如果在执行过程中出现故障怎么办? - Jader Dias
然后问问自己:当再次尝试获取资源时,是否有望成功获取?如果是:实现某种重试机制。如果不是:跳过并等待下一个间隔。我想这也取决于每次获取资源的重要性。 - Eric Eijkelenboom
我在询问行值。如果将其更新为“1”的进程停止,该值可能会保持不变,没有进程会再次获取该资源。 - Jader Dias
当然,你应该始终有一种finally块来释放资源。但是你是指无法恢复的崩溃吗?在这种情况下,时间戳可能比0和1更好。然后用于检查锁的条件可以是(资源键为空或资源键 < (当前时间 - 2小时))。 - Eric Eijkelenboom

1
从简单性的角度来看,实现您所寻找的最快/最简单的方法是“轮询”您的集群,以便每个请求都选择一台机器(由集群管理服务或类似服务)来处理请求。实际客户端请求不直接发送到处理它的机器;它们指向一个单一的终点,该终点作为代理,根据可用性和负载分配传入请求到机器。引用下面链接中的内容:
网络负载平衡是一种配置机器池的方式,使它们轮流响应请求。它最常见的实现是在服务器农场中:具有相同配置的机器将网站的负载分散开来,或者可能是终端服务器农场。您还可以将其用于防火墙(ISA)农场、VPN访问点,实际上,任何时候当TCP/IP流量对单个机器的负载过大,但您仍希望它出现为访问目的的单个机器时,都可以使用它。
至于您的应用程序是否“活动”,这个要求并不影响这个方程式,因为无论是“活动”还是“被动”,应用程序仍然会向您的服务器发出请求。

商业负载均衡器可用于提供HTTP样式请求的服务,因此可能值得研究一下,但是使用W2k8的负载平衡功能可能是最好的选择。

有关如何在Win2k8中进行配置的更多信息,请参见this文章。

this article更加技术化,重点是使用Exchange与NLB,但原则仍然适用于您的情况。

在这里查看另一个详细的NLB设置和配置步骤。

如果无法解决问题,您可以在ServerFault上搜索/发布问题,因为您的应用程序代码并不(也不应该)严格意识到NLB的存在。

编辑:添加了另一个链接。

编辑(第二次):楼主纠正了我在“主动”和“被动”概念上的错误结论。我的回答与原始回答非常相似,只是“主动”服务(由于您使用WCF,可以轻松成为Windows服务)可以分成两部分:实际处理部分和管理部分。管理部分将在一个服务器上运行,并作为其他执行实际处理的服务器的轮询负载平衡器。它比原始情况稍微复杂一些,但我认为它会提供很大的灵活性,并在处理逻辑和管理逻辑之间提供清晰的分离。


你没有理解我所说的“活跃”。在活跃的情况下,我的服务器不会接收任何请求。相反,它们会生成请求。 - Jader Dias
但是我对你的解决方案有些问题。在我的情况下,“管理部分”已经被分离出来了,而且我对其高可用性表示担忧。我不想在单个服务器上运行它。 - Jader Dias
那么,在这种情况下,您可以编写逻辑来允许一个“主”服务器负责检索和传播更新。主服务器将由集群中的服务器“选举”,如果主服务器未能及时响应传播事件,则会进行新的选举。这类似于某些Windows网络服务的操作方式。 - Josh E

1

有一些要求可能是你知道但在问题中没有描述,这使得给出一个明确的答案具有挑战性。其中一些问题包括:

  • 任务是否必须成功完成?
  • 如果任务完成/未完成,谁需要知道并需要执行什么类型的操作?
  • 如果任务在下次运行时尚未完成,应该运行还是不运行?
  • 作业按指定间隔运行的重要性有多大?如果间隔为每5分钟,它必须每5分钟运行吗,还是可以在5分10秒后运行任务?

第一步是回答定期任务将如何安排运行。一种选择是使用Windows计划任务,但它本身并不高度可用,但可能可以解决这个问题。如果您正在使用SQL Server,则另一种选择是使用SQL Server代理作为调度程序,因为它将作为SQL Server的一部分进行故障转移。

确定的下一步是如何调用WCF应用程序。最简单的选择是通过NLB IP地址触发作业来调用WCF服务。如果数据库服务器(或该区域中的其他服务器)正在调用应用程序区域,则可能会被视为不可行(当然,总会有例外,例如MSDTC)。
另一个选择是使用队列模型。在大多数情况下,这将是最可靠的选择。例如,SQL Server代理可以执行存储过程以在队列表中输入记录。然后,在每个应用程序服务器上,服务都可以轮询查找要处理的排队记录。对队列中的记录的访问将由数据库序列化,以便第一个进入的服务器运行作业(并且该作业仅运行一次)。
根据这个答案中的开放性问题的答案,您可能需要添加一些更多的错误处理。如果外部资源的检索通常相当短,则可以使用“select for update”仅保留队列记录锁定,并在任务完成时更新状态(或删除记录,如果您愿意)。这将阻止其他服务实例在另一个服务器上处理记录时处理记录,并且如果在处理过程中发生崩溃,则应回滚事务并且集群中的另一个服务可以接管记录。(尽管您可以将事务超时增加到您认为需要的时间长。)
如果长时间保持数据库锁定不可行,则可以更改逻辑并向服务添加一些监视。现在,当作业开始处理时,其状态将从排队更改为运行,并且正在处理记录的服务器将在记录上进行更新。可以创建某种服务状态表,并且每个服务实例都会在每次轮询时更新当前时间。这将允许集群中的其他服务重新处理显示为正在运行但它们应该运行的服务在一定时间内没有“签入”的作业。
这种方法也有局限性:如果任务实际上已经完成,但是由于某种原因数据库连接丢失,作业可能会再次运行。当然,我认为将原子数据库操作与其他非事务性资源(例如Web请求、文件系统)结合起来的问题不会很容易地得到解决。我假设您正在编写一个文件或类似的东西——如果外部内容也被放入数据库中,那么单个事务将保证一切都是一致的。

我喜欢SQL Server Agent的建议。我相信许多关系型数据库管理系统都有类似的功能。 - Jader Dias

0
在某些情况下,人们发现让三台机器处理所有请求并在最后比较结果非常有用,以确保结果绝对正确,没有硬件故障导致任何问题。例如,在飞机上就是这样做的。
在其他时候,您可以容忍一个错误的结果和短暂的停机时间来切换到新的服务,但只希望下一个服务正常运行。在这种情况下,使用心跳监视器的解决方案3是一个很好的设置。
还有其他时候,人们只需要通过短信通知他们的服务已经停止,应用程序将只使用一些过时的数据,直到您手动执行某种故障转移。
在您的情况下,我认为后者可能更有用。由于您无法真正依赖另一端的服务可用性,因此仍然必须想出在这种情况下该怎么做的解决方案。返回过时的数据可能对您有益,也可能不是。很抱歉不得不说:这取决于具体情况。

我已确定解决方案3适合我,我不确定的是同步方法。 - Jader Dias
这个问题没有提到正在检索什么类型的内容,但可以假设它随时间变化(例如股票报价),并且可能不能保证在稍微不同的时间进行请求的3个服务器将接收相同的数据。 - Randy Levy
@Tuzo 在我的情况下,数据仅每2分钟更新一次,并且每1分50秒获取一次。 - Jader Dias

0

Zookeeper是一个分布式锁的好用例。Zookeeper有类似于带数据的目录的z节点。

即使Netflix Curator已经有很多配方可以使用,例如:领导选举、分布式锁等等。

我认为我们有C#的Zookeeper客户端,你一定要尝试这些选项。 #Option3


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接