在将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。
当服务处于“运行”状态时,它会在每次迭代时检查其合作伙伴的完整性。
最后,在关闭时,当合作伙伴等待接管时,需要告诉它何时可以接管,通过设置状态“准备接管”。
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提供者仅用于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();
}
}
目前不会详细说明,但就目前而言,WCF提供者是TCP,GUI不仅仅限于在服务所在的机器上运行,而是可以在网络中任何能够访问服务器的机器上启动。