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

具有远程控制的 AlwaysOn Windows 服务

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (2投票s)

2016年2月24日

CPOL

4分钟阅读

viewsIcon

14079

downloadIcon

769

一个 Windows 服务,在另一个服务器上有一个故障转移服务,以防它运行的服务器崩溃

引言

本文介绍了一种扩展 Windows 服务以实现 AlwaysOn 运行的方法。

背景

当我们决定将 SQL Server 实例迁移到 Always-On 群集时。但仅仅让数据库群集化并没有太大意义,如果依赖系统在主实例崩溃时无法工作。

因此,第一个问题很容易解决,我们将所有现有的数据库连接迁移到了 SQL 侦听器,当某个实例发生故障时,这些侦听器会自动切换到另一个实例。

但是,在当前服务器上,我们还有一个负责处理各种任务的服务,如果我们能将此服务也进行群集化,而不是将其迁移到数据库群集之外的第三台服务器上,那就更好了。

因此,本文将向您展示如何实现这样的概念。

它基于并扩展了我以前的文章:带远程控制 GUI 的多线程 Windows 服务

1. 使用代码

源代码基于 Visual Studio 2013 和 .NET 4.5。对于 GUI,您还需要此库:http://wpfanimatedgif.codeplex.com/

该代码将扩展我的多线程服务,这部分内容在此不再赘述,因此请参考之前的文章作为基础。

几乎所有的更改都已在原始服务实现(请参阅链接的文章)的 ServiceExecution.cs 中完成。

此 GUI 包含在下载文件中,它同时展示了两个服务:主服务处于活动状态,从服务正在监听。

GUI program

2. 状态

初始化 服务正在启动和初始化
监听 服务已就绪,正在检查当前工作的伙伴
运行 服务正在工作
Shutting_Down 服务正在关闭
Preparing_Takeover 服务准备将处理交给伙伴
Waiting_for_Takeover 服务正在等待接管合作伙伴的服务
Ready_For_Takeover 服务将接管处理
Stopped 服务处理已停止

3. 附加属性

partnerService AlwaysOn 伙伴 WCF 接口
tcpFactory TCPBinding 的 ChannelFactory
fallbackTakeoverTime 存储了已初始化接管的时间

必须在 EXE 的配置文件中定义一个 MASTER 主服务。

4. 主循环

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。

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

最后,在伙伴等待接管时关闭服务,我们需要通过设置“Ready_For_Takeover”状态来告知它何时可以接管。

5. 初始化 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,因为服务需要在我们的网络中相互通信。每个服务都有自己的配置,其中维护着相应的伙伴绑定地址。

6. 完整性检查

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 execption which tells you the connection is Faulted
		if (tcpFactory.State == CommunicationState.Opened && ex.ToString().Contains("Faulted"))
		{
			tcpFactory.Abort();
		}
 
		return false;
	}
 
	return true;
}

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

然后尝试通过 WCF 接口获取伙伴的状态。无需检查连接是否存活,因为所有异常都会被捕获。

这里需要提及一个特殊的行为:即使连接的 CommunicationState 为“已打开”,连接也可能是无效的,这一点只能在异常消息中确定,因为连接被声明为“已失效”。如果发生这种情况,请中止 ChannelFactory

7. 与伙伴服务协商

private void NegotiateWithPartner()
{
	if (Properties.Settings.Default.always_on_is_master == true)
	{

服务被标记为群集的 master,所以按照 master 的方式进行。

		if (partnerService.CheckState() == State.Listening)
		{
			currentState = State.Running;
		}

仅作说明,如果 slave 离线,它也会正常运行。

		else if (partnerService.CheckState() == State.Running)
		{
			partnerService.PrepareForTakeover();
		}

如果 slave 当前正在运行,告知它 master 将接管处理。

		else if (partnerService.CheckState() == State.Waiting_for_Takeover)
		{
			partnerService.AbortTakeover();
		}

如果 slave 已经在尝试接管,则告诉它停止,因为 master 再次在线。

		else if (partnerService.CheckState() == State.Ready_For_Takeover)
		{
			partnerService.StartService();
		}

当 slave 完成处理后,告诉它重新启动,这样状态就会变为“监听”。

	}
	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;
			}

如果 master 不可用且 slave 正在监听,则启动接管流程。在 slave 实际可以接管之前,定义了一个延迟时间。

			else if (currentState == State.Waiting_for_Takeover)
			{
				if(fallbackTakeoverTime <= DateTime.Now)
				{
					currentState = State.Running;
				}
			}

延迟结束后,开始处理。

		}
	}
}

8. WCF 服务

在原始服务中,WCF 提供程序仅由 GUI 用于与服务通信,我使用了 NetNamedPipeBinding

现在 GUI 和伙伴服务都使用 TCP 绑定。

8.1 IServiceWCF.cs

[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();
}

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

8.2 WCFProvider.cs

WCF 提供程序现在被定义为 NetTCPBinding

    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();
	}
}

9 GUI

我仍然不会详细介绍,但到目前为止,WCF 提供程序是 TCP,GUI 不仅限于在与服务相同的机器上运行,它可以在网络中任何可以访问服务器的机器上启动。

未来改进

目前无

历史

  • 2016年2月24日 - 初版发布。
© . All rights reserved.