6 major server modules (PdkFriendServer/GlobalSever/ServerCore/GameModule/GameNetModule) + game logic (GameFix/GameDAL/ServerData) + network layer (NetWorkMessage) + data layer (ObjectModel) + utilities (MrWu/Core/Config/CloudAPI/dll) + adapters (zyxAdapter/base) .NET 8.0 C# solution, 16 projects, 958 source files
572 lines
18 KiB
C#
572 lines
18 KiB
C#
/*
|
|
* 作者:吴隆健
|
|
* 日期: 2019-04-15
|
|
* 时间: 15:27
|
|
*
|
|
*/
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using Server.DB.Redis;
|
|
using Server.Data.Module;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using MrWu.Debug;
|
|
using System.Threading.Tasks;
|
|
using GameData;
|
|
using Server.Core;
|
|
using Server.MQ;
|
|
using StackExchange.Redis;
|
|
|
|
namespace Server
|
|
{
|
|
//发包就四种情况
|
|
|
|
|
|
/*
|
|
* 1.发所有
|
|
* 2.发某类模块的所有
|
|
* 3.发给指定的
|
|
* 4.发给某类模块的任意一个
|
|
* */
|
|
|
|
//拦截多开与检查断线
|
|
//提供的服务就是判断在不在线,
|
|
/// <summary>
|
|
/// 服务器生命检查
|
|
/// </summary>
|
|
public class ServerHeart : ServerNode
|
|
{
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public static ServerHeart instanece {
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public bool IsSendHeart = false;
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
protected ServerHeart() { }
|
|
|
|
static ServerHeart() {
|
|
instanece = new ServerHeart();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 模块类型
|
|
/// </summary>
|
|
public ModuleType myType {
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 模块名称
|
|
/// </summary>
|
|
public string moduleName {
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 未收到心跳的最长时间,超过这个事件认为是掉线
|
|
/// </summary>
|
|
private const int MaxNoGetHeart = 40;
|
|
|
|
/// <summary>
|
|
/// 1分钟不更新认为自己死亡
|
|
/// </summary>
|
|
private readonly TimeSpan heartTime = new TimeSpan(0, 0, MaxNoGetHeart);
|
|
|
|
/// <summary>
|
|
/// 1分钟不更新认为死亡 单位秒
|
|
/// </summary>
|
|
private const int dieTime = MaxNoGetHeart * 1000;
|
|
|
|
/// <summary>
|
|
/// 所有的服务器->这个数据
|
|
/// </summary>
|
|
private readonly ConcurrentDictionary<int, ClusterInfo> Servers = new ConcurrentDictionary<int, ClusterInfo>();
|
|
|
|
private string m_nodeHeartKey;
|
|
|
|
/// <summary>
|
|
/// 节点信息 key
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private string nodeHeartKey {
|
|
get
|
|
{
|
|
if (m_nodeHeartKey == null)
|
|
m_nodeHeartKey = GetNodeHeartKey(moduleName, nodeNum); // "Game:LockModule:Module." + moduleName + "." + nodeNum;
|
|
return m_nodeHeartKey;
|
|
}
|
|
}
|
|
|
|
public string GetNodeHeartKey(string moduleName,int nodeNum)
|
|
{
|
|
return $"Game:LockModule:Module.{moduleName}.{nodeNum}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// 定时器 _ 线程定时器
|
|
/// </summary>
|
|
private Timer timer;
|
|
|
|
DistributedLock disLock = new DistributedLock(10);
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="type"></param>
|
|
/// <param name="nodeNum"></param>
|
|
/// <returns></returns>
|
|
//启动 -> 因为操作同一个库,所以必须分布式锁 -> 从redis中获取到自己这个服务器的编号
|
|
public bool Start(ModuleType type, int nodeNum) {
|
|
this.myType = type;
|
|
this.moduleName = Enum.GetName(type.GetType(), type);
|
|
this.id = (int)type;
|
|
this.nodeNum = nodeNum;
|
|
//检查redis中的数据
|
|
|
|
bool start = disLock.LockRun(nodeHeartKey,() => {
|
|
if (redisManager.db.KeyExists(nodeHeartKey))
|
|
return false;
|
|
return Heart();
|
|
});
|
|
|
|
AddModule(id,moduleName, nodeNum);
|
|
|
|
if (start) {
|
|
//10秒一次
|
|
timer = new Timer(OnTime, null, 0, 10000);
|
|
}
|
|
|
|
return start;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 正常关闭模块
|
|
/// </summary>
|
|
public void Close() {
|
|
timer.Dispose();
|
|
if (heartTimer != null)
|
|
heartTimer.Dispose();
|
|
disLock.LockRun(nodeHeartKey, () => {
|
|
if (redisManager.db.KeyExists(nodeHeartKey))
|
|
redisManager.db.KeyDelete(nodeHeartKey);
|
|
});
|
|
}
|
|
|
|
private List<NodeInfo> tmpdieNode = new List<NodeInfo>();
|
|
|
|
/// <summary>
|
|
/// 定时器
|
|
/// </summary>
|
|
private void OnTime(Object obj) {
|
|
//心跳
|
|
HeartAsync();
|
|
|
|
tmpdieNode.Clear();
|
|
//每10秒检测一次所有服务器是否掉线
|
|
try {
|
|
foreach (var ser in Servers.Values) {
|
|
foreach (var node in ser.nodes.Values) {
|
|
if (node.type == id && node.nodeNum == nodeNum) continue;
|
|
if (node.ExpireTime - TimeInfo.Instance.FrameTime < 15) //距离过期时间剩下15秒内 才去检查
|
|
{
|
|
if (!tmpdieNode.Contains(node)) {
|
|
tmpdieNode.Add(node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
Debug.Error("D12E5112-817D-40C0-A8FB-CED9AC969945:" + e.ToString());
|
|
}
|
|
|
|
_ = CheckDie();
|
|
}
|
|
|
|
private async Task CheckDie()
|
|
{
|
|
if (tmpdieNode.Count <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
List<NodeInfo> checks = new List<NodeInfo>();
|
|
List<Task<TimeSpan?>> timeSpans = new List<Task<TimeSpan?>>();
|
|
IBatch batch = redisManager.db.CreateBatch();
|
|
for (int i = 0; i < tmpdieNode.Count; i++)
|
|
{
|
|
checks.Add(tmpdieNode[i]);
|
|
timeSpans.Add(batch.KeyTimeToLiveAsync(GetNodeHeartKey(tmpdieNode[i].ModuleName, tmpdieNode[i].nodeNum)));
|
|
}
|
|
batch.Execute();
|
|
await Task.WhenAll(timeSpans);
|
|
|
|
for (int i = 0; i < checks.Count; i++)
|
|
{
|
|
if (timeSpans[i].Result == null)
|
|
{
|
|
Debug.ImportantLog($"模块掉线:{checks[i].ModuleName}-{checks[i].nodeNum}");
|
|
checks[i].isDie = true;
|
|
RemoveDie(checks[i].type, checks[i].nodeNum);
|
|
GlobalDispatcher.Instance.DispatchNextFrame(GlobalMsgId.ModuleNodeCrash,checks[i].ModuleName);
|
|
}
|
|
else
|
|
{
|
|
checks[i].ExpireTime = TimeInfo.Instance.FrameTime + (long)timeSpans[i].Result.Value.TotalMilliseconds;
|
|
//Debug.Info($"过期时间:{checks[i].ModuleName} {checks[i].nodeNum} {checks[i].ExpireTime}");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 添加模块
|
|
/// </summary>
|
|
/// <param name="moduleId"></param>
|
|
/// <param name="nodeNum"></param>
|
|
public void AddModule(int moduleId,string moduleName,int nodeNum)
|
|
{
|
|
try
|
|
{
|
|
ClusterInfo ci = Servers.GetOrAdd(moduleId, new ClusterInfo()
|
|
{
|
|
type = moduleId,
|
|
ModuleName = moduleName
|
|
});
|
|
|
|
NodeInfo node = ci.nodes.GetOrAdd(nodeNum, new NodeInfo()
|
|
{
|
|
type = moduleId,
|
|
ModuleName = moduleName,
|
|
nodeNum = nodeNum,
|
|
isDie = false
|
|
});
|
|
|
|
node.ExpireTime = TimeInfo.Instance.FrameTime + dieTime;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.Error("1AD0CEC8-1ACB-4CEC-AB11-EB86C8A63C75:" + e.ToString());
|
|
}
|
|
}
|
|
|
|
// /// <summary>
|
|
// /// 处理心跳
|
|
// /// </summary>
|
|
// /// <param name="moduleId"></param>
|
|
// /// <param name="nodeNum"></param>
|
|
// public void DoHeart(int moduleId, int nodeNum) {
|
|
// //心跳
|
|
// try
|
|
// {
|
|
// ClusterInfo ci = Servers.GetOrAdd(moduleId, new ClusterInfo()
|
|
// {
|
|
// type = moduleId
|
|
// });
|
|
//
|
|
// NodeInfo node = ci.nodes.GetOrAdd(nodeNum, new NodeInfo()
|
|
// {
|
|
// type = moduleId,
|
|
// nodeNum = nodeNum
|
|
// });
|
|
//
|
|
// node.LastActivityTime = TimeInfo.Instance.FrameTime;
|
|
// } catch (Exception e) {
|
|
// Debug.Error("1AD0CEC8-1ACB-4CEC-AB11-EB86C8A63C75:" + e.ToString());
|
|
// }
|
|
// }
|
|
|
|
// /// <summary>
|
|
// /// 来心跳
|
|
// /// </summary>
|
|
// /// <param name="moduleId"></param>
|
|
// /// <param name="nodeNum"></param>
|
|
// public Task DoHeartAsync(int moduleId, int nodeNum) {
|
|
// return Task.Run(
|
|
// () => DoHeart(moduleId, nodeNum)
|
|
// );
|
|
// }
|
|
|
|
/// <summary>
|
|
/// 某个模块是否死亡
|
|
/// </summary>
|
|
public Task<bool> IsDieAsync(int moduleId, int nodeNum) {
|
|
return Task.Run(() => IsDie(moduleId, nodeNum));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 检查节点死否死亡
|
|
/// </summary>
|
|
/// <param name="moduleId"></param>
|
|
/// <param name="nodeNum"></param>
|
|
/// <returns></returns>
|
|
public bool IsDie(int moduleId, int nodeNum) {
|
|
bool result = true;
|
|
|
|
try {
|
|
if (Servers.TryGetValue(moduleId, out ClusterInfo ci))
|
|
{
|
|
if (ci.nodes.TryGetValue(nodeNum, out NodeInfo node))
|
|
{
|
|
result = node.isDie;
|
|
if (result)
|
|
RemoveDie(moduleId, nodeNum);
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
Debug.Error("FC703541-D8AB-4641-9618-9CB0F090FFE4:" + e.ToString());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 检查该模块是否没有一个在线
|
|
/// </summary>
|
|
/// <param name="moduleId"></param>
|
|
/// <returns></returns>
|
|
public bool IsDie(int moduleId) {
|
|
bool result = true;
|
|
try {
|
|
if (Servers.TryGetValue(moduleId, out ClusterInfo ci)) {
|
|
foreach (var node in ci.nodes.Values) {
|
|
if (!node.isDie) {
|
|
result = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
Debug.Error("78A9B0FC-9EFF-4E53-B921-F5EF328BCF5F:" + e.ToString());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 输出日志
|
|
/// </summary>
|
|
public string Log() {
|
|
string str = string.Empty;
|
|
str += id + "," + nodeNum + ":";
|
|
|
|
str += Environment.NewLine;
|
|
|
|
try {
|
|
foreach (var ser in Servers.Values) {
|
|
foreach (NodeInfo node in ser.nodes.Values) {
|
|
str += "在线的模块:" + node.type + "," + node.nodeNum +"," + Environment.NewLine;
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
Debug.Error("E4BEB6EB-465A-428B-9C5D-85264A108429:" + e.ToString());
|
|
}
|
|
|
|
//str += Environment.NewLine;
|
|
|
|
return str;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 移除死亡的模块
|
|
/// </summary>
|
|
/// <param name="moduleId"></param>
|
|
/// <param name="nodeNum"></param>
|
|
public Task RemoveDie(int moduleId, int nodeNum, bool force = false) {
|
|
|
|
//移除死掉的节点
|
|
return Task.Run(() => {
|
|
bool r = false;
|
|
try {
|
|
ClusterInfo ci = null;
|
|
if (Servers.TryGetValue(moduleId, out ci)) {
|
|
NodeInfo node = null;
|
|
if (ci.nodes.TryGetValue(nodeNum, out node)) {
|
|
if (node.isDie && (moduleId != this.id || nodeNum != this.nodeNum)) {
|
|
r = ci.nodes.TryRemove(nodeNum,out _);
|
|
} else if (force) {
|
|
r = ci.nodes.TryRemove(nodeNum,out _);
|
|
}
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
Debug.Error("3D910EC1-3682-44B2-A767-4FC8517BFD2D:" + e.ToString());
|
|
}
|
|
|
|
if (r) {
|
|
GlobalDispatcher.Instance.DispatchNextFrame(GlobalMsgId.ModuleNodeDie,new ServerNode(moduleId,nodeNum));
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取所有的在线的节点
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public List<ServerNode> GetAllOnline() {
|
|
List<ServerNode> nodes = new List<ServerNode>();
|
|
try
|
|
{
|
|
foreach (var ser in Servers.Values)
|
|
{
|
|
foreach (var node in ser.nodes.Values)
|
|
{
|
|
if (!node.isDie)
|
|
nodes.Add(new ServerNode(node.type, node.nodeNum));
|
|
}
|
|
}
|
|
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.Error("F95EF643-7315-48A6-8D07-1606E16102D3:" + e.ToString());
|
|
}
|
|
|
|
return nodes;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取所有的在线节点
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public List<ServerNode> GetAllOnline(int moduleId) {
|
|
List<ServerNode> nodes = new List<ServerNode>();
|
|
try {
|
|
ClusterInfo ci = null;
|
|
if (Servers.TryGetValue(moduleId, out ci)) {
|
|
foreach (var node in ci.nodes.Values) {
|
|
if (!node.isDie)
|
|
nodes.Add(new ServerNode(node.type, node.nodeNum));
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
Debug.Error("E1C6A85D-8F0C-47A9-8103-5AC3D7ACDF0B:" + e.ToString());
|
|
}
|
|
return nodes;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 心跳
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private Task<bool> HeartAsync() {
|
|
return redisManager.db.StringSetAsync(nodeHeartKey, "online", heartTime);
|
|
}
|
|
|
|
private bool Heart()
|
|
{
|
|
return redisManager.db.StringSet(nodeHeartKey, "online", heartTime);
|
|
}
|
|
|
|
#region 心跳包发送处理
|
|
/// <summary>
|
|
/// 发送心跳包定时器
|
|
/// </summary>
|
|
private Timer heartTimer;
|
|
|
|
/// <summary>
|
|
/// 12秒钟发送一次心跳包
|
|
/// </summary>
|
|
const int constheartTime = 12000;
|
|
|
|
/// <summary>
|
|
/// 心跳包
|
|
/// </summary>
|
|
private string heartPack;
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// 开始发送
|
|
/// </summary>
|
|
/// <param name="heartPack"></param>
|
|
public void BeginHeart(string heartPack) {
|
|
this.heartPack = heartPack;
|
|
heartTimer = new Timer(SendHeart, null, 0, constheartTime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 更改心跳包
|
|
/// </summary>
|
|
public void ChangeHeart(string heartPack) {
|
|
this.heartPack = heartPack;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 发送心跳包
|
|
/// </summary>
|
|
private void SendHeart(object state) {
|
|
if (!IsSendHeart)
|
|
{
|
|
return;
|
|
}
|
|
GlobalMQ.instance.DeliveryAll(GlobalMQ.CreateMessage(heartPack), true);
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
/// <summary>
|
|
/// 集群信息
|
|
/// </summary>
|
|
private class ClusterInfo
|
|
{
|
|
/// <summary>
|
|
/// 模块id
|
|
/// </summary>
|
|
public int type;
|
|
|
|
/// <summary>
|
|
/// 模块名称
|
|
/// </summary>
|
|
public string ModuleName;
|
|
|
|
/// <summary>
|
|
/// 节点id
|
|
/// </summary>
|
|
public ConcurrentDictionary<int, NodeInfo> nodes = new ConcurrentDictionary<int, NodeInfo>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 节点信息
|
|
/// </summary>
|
|
private class NodeInfo
|
|
{
|
|
/// <summary>
|
|
/// 类型
|
|
/// </summary>
|
|
public int type;
|
|
|
|
/// <summary>
|
|
/// 模块名称
|
|
/// </summary>
|
|
public string ModuleName;
|
|
|
|
/// <summary>
|
|
/// 节点
|
|
/// </summary>
|
|
public int nodeNum;
|
|
|
|
/// <summary>
|
|
/// 过期的时间
|
|
/// </summary>
|
|
public long ExpireTime;
|
|
|
|
public bool isDie
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|