C++服务主备切换方案,热切换。

前言

最近接了一个项目,甲方要求后台服务需要有主备备切换,所以设计了一个主备备切换的策略,实现程序的热切换。

技术要求

程序需要部署在三台服务器上,运行时要求一主二备,只有主的进行发送和数据入库,两备份监听。如果主服务挂掉,需要在5秒钟内让其中一备份切换为主服务。

流程图

在这里插入图片描述

代码实现

首先定义服务的的构体与状态枚举和协议关键ICD:

//通讯标志位
	enum COMM_FLAG
	{
		E_SERVAER_SEND_COMM_MESS = 2901,	//服务器的消息
		E_CLIENT_SEND_COMM_MESS = 2902,	//客户端的消息
	};
	enum SERVER_PRIMARY
	{
		E_INIT_SERVER = 0, //初始化标志
		E_PRIMARY_SERVER = 1,//主服务器
		E_BACKUP_SERVER = 2, //备份服务器
	};
	//服务器消息标志
	enum SERVER_FLAG
	{
		E_SERVER_HEART_MESS = 400, //服务器心跳,用来两个服务器对发的
		E_SERVER_SEND_PRIMARY_BACKUP = 401,//服务器启动时发送的问询,用来判断是否有主服务器
		E_SERVER_SEND_PRIMARY_BACKUP_ACK = 402,//服务器启动时发送的问询,用来判断是否有主服务器,主服务器回执

	};

	struct stServerItem
	{
		quint8 unServerType;	//表示自己还是别人1自己 2别人
		SERVER_PRIMARY eServerMain;	//主备状态 1主 2备
		quint64 unSatrtTime;	//启动时间
		quint16 usServerDev;	//设备号
		qint32 nHeartbeatCount;	//心跳计数器 超过三次就是超时
		stServerItem()
		{
			unServerType = 2;
			eServerMain = E_INIT_SERVER;
			unSatrtTime = 0;
			usServerDev = 0;
			nHeartbeatCount = 0;
		}

	};

服务通过定时器周期发送上图枚举400消息。服务收到心跳后,重置对应服务心跳。
周期进行状态判定,关键代码段:

		//赋值一下
		SERVER_PRIMARY eServerLastType = m_stServerItem.eServerMain;

		QMutexLocker locker(&m_mutexMapServer);
		if (m_stServerItem.eServerMain == SERVER_PRIMARY::E_PRIMARY_SERVER)//我是主服务
		{
			for (auto It = m_mapServerItem.begin(); It != m_mapServerItem.end(); ++It)
			{
				if (It->eServerMain == SERVER_PRIMARY::E_PRIMARY_SERVER)//对手也是组
				{
					if (It->nHeartbeatCount >= TIMEOUT_COUNT_SERVER)//对手超时了
					{
						if (m_bPingDevResult)//能ping通
						{
							//对手切换为备份
							It->eServerMain = SERVER_PRIMARY::E_BACKUP_SERVER;
							qWarning() << QStringLiteral("对手服务超时,对手从主服务切换为备服务");
						}
						else //不通
						{
							//不能ping通,那么判断我是掉线了,切换为备
							{
								QMutexLocker myServerLocker(&m_mutexMyServer);
								m_stServerItem.eServerMain = SERVER_PRIMARY::E_BACKUP_SERVER;
							}

							qWarning() << QStringLiteral("服务ping %1 超时,我从主服务切换为备服务").arg(m_stPingDevItem.strPingDevtIP);
							break;
						}
					}
					else
					{
						if (m_stServerItem.unSatrtTime > It->unSatrtTime)
						{
							//我的启动时间大于对方,对手切换为备份
							It->eServerMain = SERVER_PRIMARY::E_BACKUP_SERVER;
							qWarning() << QStringLiteral("有两个相同的主服务我比对手先启动,对手从主服务切换为备服务");
						}
						else
						{
							//我的启动时间小于对方,我切换为备
							{
								QMutexLocker myServerLocker(&m_mutexMyServer);
								m_stServerItem.eServerMain = SERVER_PRIMARY::E_BACKUP_SERVER;
							}
							qWarning() << QStringLiteral("有两个相同的主服务我比对手后启动,我从主服务切换为备服务");
							break;
						}
					}
				}
				else /*if (It->eServerMain == SERVER_PRIMARY::E_BACKUP_SERVER)*/
				{
					if (It->nHeartbeatCount >= TIMEOUT_COUNT_SERVER)//对手超时了
					{
						if (m_bPingDevResult)//能ping通
						{
						}
						else
						{
							//不能ping通,那么判断我是掉线了,切换为备
							{
								QMutexLocker myServerLocker(&m_mutexMyServer);
								m_stServerItem.eServerMain = SERVER_PRIMARY::E_BACKUP_SERVER;
							}
							qWarning() << QStringLiteral("服务ping %1 超时,我从主服务切换为备服务").arg(m_stPingDevItem.strPingDevtIP);
							break;
						}
					}
				}
			}
		}
		else if (m_stServerItem.eServerMain == SERVER_PRIMARY::E_BACKUP_SERVER)//我是备份服务
		{
			QList<quint32> listServerDev;//总的 用来查看有没有主服务 == 0为没有主设备
			int nTimeoutCount = 0;//计数超时的主设备
			for (auto cIt = m_mapServerItem.cbegin(); cIt != m_mapServerItem.cend(); ++cIt)
			{
				if (cIt->eServerMain == E_PRIMARY_SERVER)
				{
					listServerDev.append(cIt->usServerDev);

					//对手超时了
					nTimeoutCount += cIt->nHeartbeatCount >= TIMEOUT_COUNT_SERVER ? 1 : 0;
				}
			}

			if (listServerDev.size() != 0)//有主服务
			{
				if (nTimeoutCount == listServerDev.size())//所有的主服务都超时了
				{
					/*
					既然所有的主服务都超时了,
					那么需要找到所有在线的备份服务器,
					并且找到一个启动时间最早的没有超时的备用服务器用来做主服务器。
					*/
					if (m_bPingDevResult)//能ping通
					{
						/*能ping通说明我的网络是正确的*/
						OnSwitchServer();
					}
					else
					{
						//不能ping通,那么判断我是掉线了,什么都不做
						//qWarning() << QStringLiteral("服务ping %1 超时,服务为备服务").arg(m_stPingDevItem.strPingDevtIP);
					}
				}
				else
				{
					/*
					既然还有在线的主服务,
					那么我这个备份服务就不需要切换,
					并且把超时的主服务切换为备服务
					*/
					for (auto It = m_mapServerItem.begin(); It != m_mapServerItem.end(); ++It)
					{
						if (It->eServerMain == E_PRIMARY_SERVER && It->nHeartbeatCount >= TIMEOUT_COUNT_SERVER)
						{
							It->eServerMain = E_BACKUP_SERVER;
						}
					}
				}
			}
			else
			{
				/*
				既然没有主服务,
				那么需要找到所有在线的备份服务器,
				并且找到一个启动时间最早的没有超时的备用服务器用来做主服务器。
				*/
				if (m_bPingDevResult)
				{
					//能ping通说明网络没有问题
					OnSwitchServer();
				}
				else
				{
					//不能ping通,那么判断我是掉线了,什么都不做
					//qWarning() << QStringLiteral("服务ping %1 超时,服务为备服务").arg(m_stPingDevItem.strPingDevtIP);
					//return;//结束判断
				}
			}
		}
		//不等与说明主备发生了变化
		if (m_stServerItem.eServerMain != eServerLastType)
		{
			OnNotifyUserServerChange();
		}
		//所有心跳+1
		OnServerDevHeartAdd();

程序效果图

结果图

下载地址

测试程序下载地址

测试程序下载地址:

源码下载地址

CSDN:
GitHub:
GitLab:

最后

本人才疏学浅,还有很多不足,欢迎各位指出,也请收下留情,勿喷。

转载请注明出处谢谢。


版权声明:本文为luck_anan原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。