分布式服务器框架之Servers.Core中 实现Log模块设计 写入MongoDB数据库

简介: 游戏服务器中都需要用到Log模块,log模块存在的意义第一个是将log输出到控制台又或者是写入到log文件中,出了BUG方便定位;第二是常用于将用户的数据(例如玩家登录、道具购买量)将这种log统计到数据库中,方便统计用户留存信息、数据分析等。

Log模块设计


1.Log模块介绍


游戏服务器中都需要用到Log模块,log模块存在的意义第一个是将log输出到控制台又或者是写入到log文件中,出了BUG方便定位;第二是常用于将用户的数据(例如玩家登录、道具购买量)将这种log统计到数据库中,方便统计用户留存信息、数据分析等。


2.Log 数据库模型类设计


LoggerDBModel 存储MongoClient连接;主要职责是负责指明存放Log的具体数据库和具体数据库表。


LoggerDBModel.cs实现


using MongoDB.Driver;
using System;
using Servers.Core.YFMongoDB;
namespace Servers.Core.Logger
{
    public class LoggerDBModel : YFMongoDBModelBase<LoggerEntity>
    {
        protected override MongoClient Client => LoggerMgr.CurrClient;
        protected override string DatabaseName => "Logger";
        protected override string CollectionName => string.Format("Log_{0}", DateTime.UtcNow.ToString("yyyy-MM-dd"));
        protected override bool CanLogError => false;
    }
}


3.Log实体类设计


LoggerEntity是一个内存实体,存入数据里的时候会把类结构序列化成BSON格式存储到MongoDB中,当要从MongoDB取一个文档的时候,BSON工具会将二进制数据反序列化成一个LoggerEntity类的实例。


LoggerEntity.cs实现


using Servers.Core.YFMongoDB;
namespace Servers.Core.Logger
{
    public class LoggerEntity : YFMongoEntityBase
    {
        /// <summary>
        /// 日志等级
        /// </summary>
        public LoggerLevel Level;
        /// <summary>
        /// 日志分类
        /// </summary>
        public int Category;
        /// <summary>
        /// 日志内容
        /// </summary>
        public string Message;
    }
}


4.Log写入策略类的实现


Log写入DB的时候根据数量和时间双策略决定什么情况,多久写入一次数据库。这样可以最大程度的减缓数据库的写入压力。


可能会存在许多LOG写入DB的策略。例如有这样三条策略:1.当log>=1000条,立即写入;2.log>=100条,30秒写入;3.日志>=1条,120秒写入。


将这三个策略存到链表中,循环时以缓存的log数量优先,数量>=1000直接写入,否则需要等待一定的时间才会将log写入数据库。


LoggerTactics.cs实现


using System;
using System.Collections.Generic;
using System.Text;
namespace Servers.Core.Logger
{
    /// <summary>
    /// 日志策略
    /// </summary>
    public class LoggerTactics
    {
        /// <summary>
        /// 数量
        /// </summary>
        public int Count;
        /// <summary>
        /// 间隔(秒)
        /// </summary>
        public int Interval;
    }
}


5.Log 唯一ID 数据库模型类设计


这个类的主要作用和3差不多,记录了连接的Mongo客户端;数据库名称;唯一ID表名称。写入DB时使用Model类缓存的MongoClient与MongoServer进行通信读写数据。


UniqueIDLogger.cs实现


using System;
using MongoDB.Driver;
using Servers.Core.YFMongoDB;
namespace Servers.Core.Logger
{
    public class UniqueIDLogger : YFUniqueIDBase
    {
        protected override MongoClient Client => LoggerMgr.CurrClient;
        protected override string DatabaseName => "Logger";
        protected override string CollectionName => "UniqueIDLogger";
    }
}


6.日志管理器


日志管理器的是一个管理者,做的事情会比较多。会为外部用户提供统一的接口,会先将用户的log先缓存下来然后输出到控制台;根据策略决定是否要将log写入数据库;LoggerModel和UniqueIDLogger两个DB模型类的单例实现;停机写入;


using MongoDB.Driver;
using System;
using System.Collections.Generic;
using Servers.Core.Utils;
namespace Servers.Core.Logger
{
    /// <summary>
    /// 日志管理器
    /// </summary>
    public sealed class LoggerMgr
    {
        private static object lock_obj = new object();
        /// <summary>
        /// 日志队列
        /// </summary>
        private static Queue<LoggerEntity> m_LoggerQueue;
        /// <summary>
        /// 日志临时列表
        /// </summary>
        private static List<LoggerEntity> m_LoggerList;
        /// <summary>
        /// 上次保存时间
        /// </summary>
        private static DateTime m_PrevSaveTime;
        /// <summary>
        /// 保存策略
        /// </summary>
        private static LinkedList<LoggerTactics> m_LoggerTactics;
        /// <summary>
        /// 初始化
        /// </summary>
        public static void Init()
        {
            m_PrevSaveTime = DateTime.UtcNow;
            m_LoggerQueue = new Queue<LoggerEntity>();
            m_LoggerList = new List<LoggerEntity>();
            m_LoggerTactics = new LinkedList<LoggerTactics>();
            //队列里>=1000条 立刻写入
            m_LoggerTactics.AddLast(new LoggerTactics() { Count = 1000, Interval = 0 });
            //队列里>=100条 并且 距离上次写入>=60秒 写入
            m_LoggerTactics.AddLast(new LoggerTactics() { Count = 100, Interval = 60 });
            //队列里>=1条 并且 距离上次写入>=300秒 写入
            m_LoggerTactics.AddLast(new LoggerTactics() { Count = 1, Interval = 300 });
            Console.WriteLine("LoggerMgr Init Complete");
        }
        #region CurrClient
        private static MongoClient m_CurrClient = null;
        /// <summary>
        /// 当前的MongoClient
        /// </summary>
        public static MongoClient CurrClient
        {
            get
            {
                if (m_CurrClient == null)
                {
                    lock (lock_obj)
                    {
                        if (m_CurrClient == null)
                        {
                            m_CurrClient = new MongoClient("mongodb://127.0.0.1");
                        }
                    }
                }
                return m_CurrClient;
            }
        }
        #endregion
        #region LoggerDBModel
        private static LoggerDBModel m_LoggerDBModel;
        public static LoggerDBModel LoggerDBModel
        {
            get
            {
                if (m_LoggerDBModel == null)
                {
                    lock (lock_obj)
                    {
                        if (m_LoggerDBModel == null)
                        {
                            m_LoggerDBModel = new LoggerDBModel();
                        }
                    }
                }
                return m_LoggerDBModel;
            }
        }
        #endregion
        #region UniqueIDLogger
        private static UniqueIDLogger m_UniqueIDLogger;
        public static UniqueIDLogger UniqueIDLogger
        {
            get
            {
                if (m_UniqueIDLogger == null)
                {
                    lock (lock_obj)
                    {
                        if (m_UniqueIDLogger == null)
                        {
                            m_UniqueIDLogger = new UniqueIDLogger();
                        }
                    }
                }
                return m_UniqueIDLogger;
            }
        }
        #endregion
        #region AddLog 添加日志
        /// <summary>
        /// 添加日志
        /// </summary>
        /// <param name="level">日志等级</param>
        /// <param name="category">分类</param>
        /// <param name="message">消息体</param>
        /// <param name="args">参数</param>
        public static void AddLog(LoggerLevel level = LoggerLevel.Log, int category = 0, bool async = true, string message = null, params object[] args)
        {
            LoggerEntity loggerEntity = new LoggerEntity();
            loggerEntity.YFId = UniqueIDLogger.GetUniqueID(category);
            loggerEntity.Level = level;
            loggerEntity.Category = category;
            loggerEntity.Message = args.Length == 0 ? message : string.Format(message, args);
            loggerEntity.CreateTime = loggerEntity.UpdateTime = DateTime.UtcNow;
            Console.WriteLine(string.Format("{0} {1} {2}", loggerEntity.Level, loggerEntity.Category, loggerEntity.Message));
            if (async)
            {
                _ = LoggerDBModel.AddAsync(loggerEntity);
            }
            else
            {
                LoggerDBModel.Add(loggerEntity);
            }
        }
        #endregion
        #region Log 记录日志
        /// <summary>
        /// 记录日志
        /// </summary>
        /// <param name="level">日志等级</param>
        /// <param name="category">分类</param>
        /// <param name="message">消息体</param>
        /// <param name="args">参数</param>
        public static void Log(LoggerLevel level = LoggerLevel.Log, int category = 0, string message = null, params object[] args)
        {
            Log(level, category, true, message, args);
        }
        /// <summary>
        /// 记录日志
        /// </summary>
        /// <param name="level">日志等级</param>
        /// <param name="category">分类</param>
        /// <param name="async">是否异步</param>
        /// <param name="message">消息体</param>
        /// <param name="args">参数</param>
        public static void Log(LoggerLevel level = LoggerLevel.Log, int category = 0, bool async = true, string message = null, params object[] args)
        {
            LoggerEntity loggerEntity = new LoggerEntity();
            loggerEntity.YFId = UniqueIDLogger.GetUniqueID(category);
            loggerEntity.Level = level;
            loggerEntity.Category = category;
            loggerEntity.Message = args.Length == 0 ? message : string.Format(message, args);
            loggerEntity.CreateTime = loggerEntity.UpdateTime = DateTime.UtcNow;
            Console.WriteLine(string.Format("{0} {1} {2}", loggerEntity.Level, loggerEntity.Category, loggerEntity.Message));
            //加入队列
            m_LoggerQueue.Enqueue(loggerEntity);
            lock (lock_obj)
            {
                //检查是否可以写入DB
                if (CheckCanSave())
                {
                    m_LoggerList.Clear();
                    while (m_LoggerQueue.Count > 0)
                    {
                        //循环加入临时列表
                        m_LoggerList.Add(m_LoggerQueue.Dequeue());
                    }
                    if (async)
                    {
                        _ = LoggerDBModel.AddManyAsync(m_LoggerList);
                    }
                    else
                    {
                        LoggerDBModel.AddMany(m_LoggerList);
                    }
                    m_PrevSaveTime = DateTime.UtcNow;
                    Console.WriteLine("Logger写入DB完毕");
                }
            }
        }
        #endregion
        #region SaveDBWithStopServer 停服的时候写入DB
        /// <summary>
        /// 停服的时候写入DB
        /// </summary>
        public static void SaveDBWithStopServer()
        {
            m_LoggerList.Clear();
            while (m_LoggerQueue.Count > 0)
            {
                //循环加入临时列表
                m_LoggerList.Add(m_LoggerQueue.Dequeue());
            }
            LoggerDBModel.AddMany(m_LoggerList);
            m_PrevSaveTime = DateTime.UtcNow;
            Console.WriteLine("Logger 停服写入DB完毕");
        }
        #endregion
        #region CheckCanSave 检查是否可以写入DB
        /// <summary>
        /// 检查是否可以写入DB
        /// </summary>
        /// <returns></returns>
        private static bool CheckCanSave()
        {
            LinkedListNode<LoggerTactics> curr = m_LoggerTactics.First;
            while (curr != null)
            {
                LoggerTactics loggerTactics = curr.Value;
                long invertal = YFDateTimeUtil.GetTimestamp(DateTime.UtcNow) - YFDateTimeUtil.GetTimestamp(m_PrevSaveTime); //毫秒
                if (m_LoggerQueue.Count >= loggerTactics.Count && invertal >= loggerTactics.Interval * 1000)
                {
                    return true;
                }
                curr = curr.Next;
            }
            return false;
        }
        #endregion
    }
}


7.测试


代码写好了之后需要验证是否可以正常的写入数据,确保实现的功能是没有BUG存在的。测试中测试了两个策略:1.写1000条日志,测试是否立即存入数据里中;2.写100条,测试60秒后是否,测试结果能正常写入。


TestLogger.cs实现


using System;
using System.Collections.Generic;
using System.Text;
using Servers.Core;
using Servers.Common;
namespace Servers.HotFix
{
    public class TestLogger : Singleton<TestLogger>
    {
        public void TestAddLog()
        {
            LoggerMgr.AddLog(LoggerLevel.Log, 0, true, "这一AddLog测试");
            Console.WriteLine(" TestAddLog execute complete");
        }
        public void Test100Log()
        {
            for (int i = 0; i < 100; ++i)
            {
                LoggerMgr.Log(LoggerLevel.LogWarning, 0, "TestLog测试log:" + i);
            }
            ServerTimer timer = new ServerTimer(ServerTimerRunType.FixedInterval,
                ondoaction:()=> 
                {
                    LoggerMgr.Log(LoggerLevel.LogWarning, 0, "TestLog测试log:xx");
                },
                interval:60);
            TimerManager.RegisterServerTime(timer);
            Console.WriteLine("测试写入100条log成功,等待60秒后写入");
        }
        public void Test1000Log()
        {
            for (int i = 0; i < 1000; ++i)
            {
                LoggerMgr.Log(LoggerLevel.LogWarning, 0, "TestLog测试log:" + i);
            }
            Console.WriteLine("测试写入1000条log成功,下一帧立即写入");
        }
        public void Test()
        {
            //TestAddLog();
            Test100Log();
        }
    }
}


8.总结


日志模块总共划分了5个类文件实现了Log功能。在实现的过程中,眼戳遇到了一个双重检验的第二层写成了属性了导致发生StackOverflow的情况,后来查了一会修好了。

相关文章
|
存储 关系型数据库 数据库
【赵渝强老师】PostgreSQL的服务器日志文件
本文介绍了PostgreSQL数据库的物理存储结构,重点讨论了服务器日志文件。通过`pg_ctl`命令启动PostgreSQL实例时,使用`-l`参数指定日志文件位置,记录数据库启动、运行及关闭过程中的关键信息。附有相关视频讲解和日志文件示例。
506 0
|
10月前
|
运维 监控 安全
EventLog Analyzer:高效的Web服务器日志监控与审计解决方案
ManageEngine EventLog Analyzer是一款企业级Web服务器日志监控与审计工具,支持Apache、IIS、Nginx等主流服务器,实现日志集中管理、实时威胁检测、合规报表生成及可视化分析,助力企业应对安全攻击与合规挑战,提升运维效率。
479 1
|
存储 监控 API
【Azure App Service】分享使用Python Code获取App Service的服务器日志记录管理配置信息
本文介绍了如何通过Python代码获取App Service中“Web服务器日志记录”的配置状态。借助`azure-mgmt-web` SDK,可通过初始化`WebSiteManagementClient`对象、调用`get_configuration`方法来查看`http_logging_enabled`的值,从而判断日志记录是否启用及存储方式(关闭、存储或文件系统)。示例代码详细展示了实现步骤,并附有执行结果与官方文档参考链接,帮助开发者快速定位和解决问题。
398 22
|
数据库
【YashanDB数据库】YAS-02079 archive log mode must be enabled when database is in replication mode
YAS-02079 archive log mode must be enabled when database is in replication mode
|
运维 数据挖掘 索引
服务器数据恢复—Lustre分布式文件系统服务器数据恢复案例
5台节点服务器,每台节点服务器上有一组RAID5阵列。每组RAID5阵列上有6块硬盘(其中1块硬盘设置为热备盘,其他5块硬盘为数据盘)。上层系统环境为Lustre分布式文件系统。 机房天花板漏水导致这5台节点服务器进水,每台服务器都有至少2块硬盘出现故障。每台服务器中的RAID5阵列短时间内同时掉线2块或以上数量的硬盘,导致RAID崩溃,服务器中数据无法正常读取。
|
缓存 NoSQL Ubuntu
大数据-39 Redis 高并发分布式缓存 Ubuntu源码编译安装 云服务器 启动并测试 redis-server redis-cli
大数据-39 Redis 高并发分布式缓存 Ubuntu源码编译安装 云服务器 启动并测试 redis-server redis-cli
323 3
|
Python
Flask学习笔记(二):基于Flask框架上传图片到服务器端并原名保存
关于如何使用Flask框架上传图片到服务器端并以其原名保存的教程。
748 1
|
分布式计算 Hadoop
Hadoop-27 ZooKeeper集群 集群配置启动 3台云服务器 myid集群 zoo.cfg多节点配置 分布式协调框架 Leader Follower Observer
Hadoop-27 ZooKeeper集群 集群配置启动 3台云服务器 myid集群 zoo.cfg多节点配置 分布式协调框架 Leader Follower Observer
384 1
|
Python
Flask学习笔记(三):基于Flask框架上传特征值(相关数据)到服务器端并保存为txt文件
这篇博客文章是关于如何使用Flask框架上传特征值数据到服务器端,并将其保存为txt文件的教程。
302 0
Flask学习笔记(三):基于Flask框架上传特征值(相关数据)到服务器端并保存为txt文件
|
存储 SQL 消息中间件
Hadoop-26 ZooKeeper集群 3台云服务器 基础概念简介与环境的配置使用 架构组成 分布式协调框架 Leader Follower Observer
Hadoop-26 ZooKeeper集群 3台云服务器 基础概念简介与环境的配置使用 架构组成 分布式协调框架 Leader Follower Observer
350 0