Windows服务的AlwaysOn集群扩展方法

在将SQL Server实例迁移到Always-On集群的过程中,意识到仅仅对数据库进行集群化是不够的。如果依赖的系统在主实例崩溃时无法正常工作,那么集群化的意义就不大。因此,首先解决了这个问题,将所有现有的服务器数据库连接转移到SQL监听器上,这样当一个实例宕机时,监听器会自动切换到另一个实例。

但是,当前的服务器上还有一个服务,它负责处理各种任务。如果这个服务也能被集群化,而不是将其迁移到数据库集群之外的第三台服务器上,那就更好了。本文将展示如何实现这样的概念。

这篇文章基于并扩展了先前的文章:。

使用代码

源代码基于Visual Studio 2013和.NET 4.5。对于GUI,还需要这个库:。代码将扩展多线程服务,这部分内容不会在这里讨论,所以请参考之前的文章作为基础。几乎所有的更改都是在原始服务实现的ServiceExecution.cs中完成的(参见链接的文章)。

下载中包含的GUI显示了两个服务的运行情况——主服务是活动的,从服务在监听:

状态

初始化:服务正在启动并初始化

监听:服务已准备好,并检查当前正在工作的合作伙伴

运行:服务正在工作

关闭:服务正在关闭

准备接管:服务准备将处理交给合作伙伴

等待接管:服务等待从合作伙伴服务接管处理

准备接管:服务将接管处理

已停止:服务处理已停止

附加属性

partnerService:AlwaysOn合作伙伴WCF接口

tcpFactory:TCP绑定的ChannelFactory

fallbackTakeoverTime:存储接管初始化的时间

必须定义一个服务作为MASTER,这在EXE的配置文件中定义

主循环

public void StartServiceExecution() { try { currentState = State.Initializing; InitializeAlwaysOnCluster(); while (currentState == State.Listening || currentState == State.Waiting_for_Takeover) { Thread.Sleep(1000); NegotiateWithPartner(); } while (currentState == State.Running) { CheckIntegrityOfPartner(); ... } // Here all open threads are closed, this takes as long as the last thread has been broken or has finished while (currentState == State.Shutting_Down || currentState == State.Preparing_Takeover) { using (LockHolder<Dictionary<Guid, ThreadHolder>> lockObj = new LockHolder<Dictionary<Guid, ThreadHolder>>(runningThreads, 1000)) { if (lockObj.LockSuccessful) { ... // If no more threads are left, set the state to stopped if (runningThreads.Count == 0) { currentState = currentState == State.Preparing_Takeover ? State.Ready_For_Takeover : State.Stopped; } } } } } catch (Exception e) { ... } }

在启动主进程时,需要初始化AlwaysOn集群,更多信息请参考3。

当服务处于“运行”状态时,它会在每次迭代时检查其合作伙伴的完整性。

最后,在关闭时,当合作伙伴等待接管时,需要告诉它何时可以接管,通过设置状态“准备接管”。

初始化AlwaysOn集群

private void InitializeAlwaysOnCluster() { for (int retry = 0; retry < 5; retry++) { OpenChannelToPartner(); if (CheckIntegrityOfPartner() == true) { break; } } currentState = CheckIntegrityOfPartner() == false ? State.Running : State.Listening; }

在打开时,设置了重试,以给合作伙伴服务一些启动时间。当合作伙伴响应时,服务开始监听,否则它开始处理。

打开合作伙伴通道

private void OpenChannelToPartner() { tcpFactory = new ChannelFactory<IServiceWCF>(new NetTcpBinding(), new EndpointAddress(Properties.Settings.Default.always_on_partner)); partnerService = tcpFactory.CreateChannel(); }

使用NetTcpBinding,因为服务需要在网络中跨服务器通信。每个服务都有自己的配置,其中维护了相应的合作伙伴绑定地址。

完整性检查

public bool CheckIntegrityOfPartner() { try { if (tcpFactory.State == CommunicationState.Closed) { OpenChannelToPartner(); } partnerService.CheckState(); } catch (Exception ex) { // If the connection is aborted by the other side, it stays open but returns an exception which tells you the connection is Faulted if (tcpFactory.State == CommunicationState.Opened && ex.ToString().Contains("Faulted")) { tcpFactory.Abort(); } return false; } return true; }

如果与合作伙伴的连接关闭了,首先要检查合作伙伴服务是否可用并尝试建立连接。

然后尝试通过WCF接口获取合作伙伴的状态。不需要检查连接是否活跃,因为所有异常都被捕获了。

这里需要特别提到的是:即使连接的CommunicationState为“Opened”,连接也可能已经死亡,只能在异常消息中确定,其中连接被声明为“Faulted”。如果是这样,中止ChannelFactory。

与合作伙伴服务协商

private void NegotiateWithPartner() { if (Properties.Settings.Default.always_on_is_master == true) { // The service is marked as master for the cluster, so take the master's way if (partnerService.CheckState() == State.Listening) { currentState = State.Running; } else if (partnerService.CheckState() == State.Running) { partnerService.PrepareForTakeover(); } else if (partnerService.CheckState() == State.Waiting_for_Takeover) { partnerService.AbortTakeover(); } else if (partnerService.CheckState() == State.Ready_For_Takeover) { partnerService.StartService(); } } else { if (CheckIntegrityOfPartner() == false) { if (currentState == State.Listening) { fallbackTakeoverTime = DateTime.Now.AddSeconds(Properties.Settings.Default.fallback_takeover_delay_in_seconds); currentState = State.Waiting_for_Takeover; } else if (currentState == State.Waiting_for_Takeover) { if (fallbackTakeoverTime <= DateTime.Now) { currentState = State.Running; } } } } }

如果服务被标记为集群的主服务,那么采取主服务的方式。

如果从服务离线,无论如何都会运行。

如果从服务当前正在运行,告诉它主服务将接管处理。

如果从服务当前正在等待接管,告诉它停止,因为主服务再次在线。

如果从服务已完成处理,告诉它重新启动,以便状态变为“监听”。

WCF服务

在原始服务中,WCF提供者仅用于GUI与服务之间的通信,使用了NetNamedPipeBinding。

现在,它既被GUI使用,也被合作伙伴服务与TCP绑定使用。

[ServiceContract] public interface IServiceWCF { [OperationContract(IsOneWay = true)] void StartService(); [OperationContract(IsOneWay = true)] void StopService(); [OperationContract] string GetActiveThreads(); [OperationContract] State CheckState(); [OperationContract] void PrepareForTakeover(); [OperationContract] void AbortTakeover(); [OperationContract] bool CheckIntegrityOfPartner(); }

接口现在公开了一些额外的函数,用于服务之间的交互。

class WCFProvider { readonly ServiceHost serviceProviderTCP; public WCFProvider() { serviceProviderTCP = new ServiceHost(typeof(ServiceWCF), new Uri(Properties.Settings.Default.service_provider_uri)); serviceProviderTCP.AddServiceEndpoint(typeof(IServiceWCF), new NetTcpBinding(), Properties.Settings.Default.service_provider_name); serviceProviderTCP.Open(); } public void StopProvidingService() { serviceProviderTCP.Close(); } }

GUI

目前不会详细说明,但就目前而言,WCF提供者是TCP,GUI不仅仅限于在服务所在的机器上运行,而是可以在网络中任何能够访问服务器的机器上启动。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485