MMO基础双端架构(五):如何O(1)的处理心跳消息

更多代码细节,球球各位观众老爷给鄙人的开源项目点个Star,持续更新中~ Free项目开源地址

5.LRU算法淘汰超时心跳消息

采用双向链表+线程安全哈希字典处理心跳消息的超时和检查机制

仿照了经典算法LRU(也就是最少关注移除算法,当容器内的size大于最大容许size时,最少关注的那个单位就会被移除)

这样的设计可以实现,平均o(1)插入删除,整个链表的长度只与客户端连接的数量有关,每一次查询都会均摊超时连接,压力不会完全给到定时轮询,这里我们可以进行一个 _maxCount的限制,限制单次更新查询的最大访问数量,防止一瞬间断联的数量太多压力过大,_maxCount默认为100,但是我定时轮询的查询是不能限制单次压力的,我必须在那个最后时限彻查所有不合法连接

因为心跳消息是按照时间从客户端接收的,记录当前连接最后一次心跳包的时间,字典缓存R(BaseConnection)对应的时间,用于确认该节点是否需要被移除,_timerCallback是被移除后的回调方法,比如说发现这个客户端最后发送的时间已经超过最大容许时间 _maxAge,那么回调BaseConnection的Close操作,断开连接,心跳包是客户端断线重连的重要策略

为什么要手写链表,其一:这是博主的课设,老师要求我的链表手写;其二:更重要的是,当我的一个节点更新的时候,我需要先移除释放内存然后new一个节点放到最后 吗?不需要,这只会浪费GC开销,链表的节点移动本来就是o(1)的,既然我不用移除那么我直接转换前后指针(引用)即可

using System.Collections.Concurrent;
using Common.Summer.core;
using Serilog;

namespace Common.Summer;

public class LinkedListNode<T>: IPool
{
    public T Value { get; private set; }
    public LinkedListNode<T> Prev { get; set; }
    public LinkedListNode<T> Next { get; set; }

    /// 
    /// 不能直接new
    /// 
    private LinkedListNode() {}
    
    /// 
    /// 对象池管理
    /// 
    public static LinkedListNode<T> Create(T value)
    {
        LinkedListNode<T> node = ObjPoolLockManager.Instance.Pop(()=>new LinkedListNode<T>());
        node.Value = value;
        node.Prev = null;
        node.Next = null;
        return node;
    }

    public void ReturnPool()
    {
        this.MoonObjPushPool();
    }
}

public class LinkedList<T>
{
    private readonly object _lock = new();
    public int Count { get; private set; }

    public LinkedListNode<T> First { get; private set; }

    public LinkedListNode<T> Last { get; private set; }

    // 添加节点到尾部
    public LinkedListNode<T> AddLast(T value)
    {
        var newNode = LinkedListNode<T>.Create(value);
        if (First == null)
        {
            First = newNode;
        }
        else
        {
            newNode.Prev = Last;
            Last.Next = newNode;
        }

        Count++;
        return Last = newNode;
    }

    public LinkedListNode<T> AddLast(LinkedListNode<T> node)
    {
        if (First == null)
        {
            First = node;
        }
        else
        {
            node.Prev = Last;
            Last.Next = node;
        }
        
        Count++;
        return Last = node;
    }

    public LinkedListNode<T> AddFirst(T value)
    {
        var newNode = LinkedListNode<T>.Create(value);
        if (First == null)
        {
            Last = newNode;
        }
        else
        {
            newNode.Next = First;
            First.Prev = newNode;
        }

        Count++;
        return First = newNode;
    }

    public void ChangeNodeToLast(LinkedListNode<T> node)
    {
        if (node.Prev != null)
            node.Prev.Next = node.Next;
        else
            First.Prev = null;

        if (node.Next != null)
            node.Next.Prev = node.Prev;
        else
            Last.Next = null;

        node.Prev = Last;
        node.Next = null;
        Last.Next = node;
        Last = node;
    }

    // 移除节点
    public void Remove(LinkedListNode<T> node)
    {
        if (node.Prev != null)
            node.Prev.Next = node.Next;
        else
            First = node.Next;

        if (node.Next != null)
            node.Next.Prev = node.Prev;
        else
            Last = node.Prev;
        node.MoonObjPushPool(); //放回对象池
        Count--;
    }

    public void Clear()
    {
        First = null;
        Last = null;
    }

    // 获取所有节点值(快照)
    public IEnumerable<T> GetAll()
    {
        var current = First;
        while (current != null)
        {
            yield return current.Value;
            current = current.Next;
        }
    }
}

/// 
///     线程安全
/// 
public class LRUCache<R>
{
    private readonly ConcurrentDictionary<R, LinkedListNode<R>> _nodeCache;
    private readonly LinkedList<R> _RList;
    private readonly ConcurrentDictionary<R, DateTime> _useTime;
    private readonly TimeSpan _maxAge;
    private readonly Action<R> _timerCallback;
    private int _maxCount;

    public LRUCache(TimeSpan maxAge, Action<R> timerCallback, int maxCount = 100)
    {
        _maxAge = maxAge;
        _nodeCache = new ConcurrentDictionary<R, LinkedListNode<R>>();
        _RList = new LinkedList<R>();
        _useTime = new ConcurrentDictionary<R, DateTime>();
        _timerCallback = timerCallback;
        _maxCount = maxCount;
    }

    public void Add(R value)
    {
        if (_nodeCache.TryGetValue(value, out var existingNode))
        {
            UpdateExistNode(value, existingNode);
            ClearOldLru();
            return;
        }

        // 创建新节点前清理旧数据
        ClearOldLru(_maxCount);

        var newNode = LinkedListNode<R>.Create(value);
        lock (_nodeCache) // 与链表操作同步
        {
            _RList.AddLast(newNode);
            if (!_nodeCache.TryAdd(value, newNode) ||
                !_useTime.TryAdd(value, DateTime.Now))
                _RList.Remove(newNode); // 回滚无效添加
        }
    }

    private void Remove(R value)
    {
        if (!_nodeCache.TryGetValue(value, out var node)) return;
        node.MoonObjPushPool();
        lock (_nodeCache)
        {
            _RList.Remove(node);
        }
        _nodeCache.Remove(value, out _);
        _useTime.Remove(value, out _);
    }

    private void UpdateExistNode(R value, LinkedListNode<R> node)
    {
        lock (_nodeCache)
        {
            _RList.ChangeNodeToLast(node);
        }
        _useTime[value] = DateTime.Now;
    }

    public void ClearOldLru(int maxCount = Int32.MaxValue)
    {
        while (true)
        {
            R oldest;
            lock (_nodeCache)
            {
                if (_RList.Count == 0) break;
                oldest = _RList.First.Value;
                if (!EvictOldest() || maxCount <= 0) break;
            }
            Interlocked.Decrement(ref maxCount);
            Task.Run(() => _timerCallback?.Invoke(oldest));
            Remove(oldest);
        }
    }

    private bool EvictOldest()
    {
        if (_RList.Count == 0) return false;
        var oldest = _RList.First.Value;
        if (oldest == null) return false;
        // 使用线程安全访问
        if (!_useTime.TryGetValue(oldest, out var lastUsed))
            return false;
        return DateTime.Now - lastUsed >= _maxAge;
    }
}

你可能感兴趣的:(MMO双端游戏架构,游戏,算法,c#)