具有远程控制的 AlwaysOn Windows 服务
一个 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 包含在下载文件中,它同时展示了两个服务:主服务处于活动状态,从服务正在监听。
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日 - 初版发布。