利用StackExchange.Redis和Log4Net构建日志队列

简介:本文是一个简单的demo用于展示利用StackExchange.Redis和Log4Net构建日志队列,为高并发日志处理提供一些思路。

0、先下载安装Redis服务,然后再服务列表里启动服务(Redis的默认端口是6379,貌似还有一个故事)(https://github.com/MicrosoftArchive/redis/releases)

利用StackExchange.Redis和Log4Net构建日志队列_第1张图片

 

1、nuget中安装Redis:Install-Package StackExchange.Redis -version 1.2.6
2、nuget中安装日志:Install-Package Log4Net -version 2.0.8

3、创建RedisConnectionHelp、RedisHelper类,用于调用Redis。由于是Demo我不打算用完整类,比较完整的可以查阅其他博客(例如:https://www.cnblogs.com/liqingwen/p/6672452.html)

/// 
    /// StackExchange Redis ConnectionMultiplexer对象管理帮助类
    /// 
    public class RedisConnectionHelp
    {
        //系统自定义Key前缀
        public static readonly string SysCustomKey = ConfigurationManager.AppSettings["redisKey"] ?? "";
        private static readonly string RedisConnectionString = ConfigurationManager.AppSettings["seRedis"] ?? "127.0.0.1:6379";

        private static readonly object Locker = new object();
        private static ConnectionMultiplexer _instance;
        private static readonly ConcurrentDictionary<string, ConnectionMultiplexer> ConnectionCache = new ConcurrentDictionary<string, ConnectionMultiplexer>();

        /// 
        /// 单例获取
        /// 
        public static ConnectionMultiplexer Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (Locker)
                    {
                        if (_instance == null || !_instance.IsConnected)
                        {
                            _instance = GetManager();
                        }
                    }
                }
                return _instance;
            }
        }

        /// 
        /// 缓存获取
        /// 
        /// 
        /// 
        public static ConnectionMultiplexer GetConnectionMultiplexer(string connectionString)
        {
            if (!ConnectionCache.ContainsKey(connectionString))
            {
                ConnectionCache[connectionString] = GetManager(connectionString);
            }
            return ConnectionCache[connectionString];
        }

        private static ConnectionMultiplexer GetManager(string connectionString = null)
        {
            connectionString = connectionString ?? RedisConnectionString;
            var connect = ConnectionMultiplexer.Connect(connectionString);       
            return connect;
        }
    }
View Code
public class RedisHelper
    {
        private int DbNum { get; set; }
        private readonly ConnectionMultiplexer _conn;
        public string CustomKey;

        public RedisHelper(int dbNum = 0)
            : this(dbNum, null)
        {
        }

        public RedisHelper(int dbNum, string readWriteHosts)
        {
            DbNum = dbNum;
            _conn =
                string.IsNullOrWhiteSpace(readWriteHosts) ?
                RedisConnectionHelp.Instance :
                RedisConnectionHelp.GetConnectionMultiplexer(readWriteHosts);
        }

       

        private string AddSysCustomKey(string oldKey)
        {
            var prefixKey = CustomKey ?? RedisConnectionHelp.SysCustomKey;
            return prefixKey + oldKey;
        }

        private T Do(Func func)
        {
            var database = _conn.GetDatabase(DbNum);
            return func(database);
        }

        private string ConvertJson(T value)
        {
            string result = value is string ? value.ToString() : JsonConvert.SerializeObject(value);
            return result;
        }

        private T ConvertObj(RedisValue value)
        {
            Type t = typeof(T);
            if (t.Name == "String")
            {                
                return (T)Convert.ChangeType(value, typeof(string));
            }

            return JsonConvert.DeserializeObject(value);
        }

        private List ConvetList(RedisValue[] values)
        {
            List result = new List();
            foreach (var item in values)
            {
                var model = ConvertObj(item);
                result.Add(model);
            }
            return result;
        }

        private RedisKey[] ConvertRedisKeys(List<string> redisKeys)
        {
            return redisKeys.Select(redisKey => (RedisKey)redisKey).ToArray();
        }

      

        /// 
        /// 入队
        /// 
        /// 
        /// 
        public void ListRightPush(string key, T value)
        {
            key = AddSysCustomKey(key);
            Do(db => db.ListRightPush(key, ConvertJson(value)));
        }      
 

        /// 
        /// 出队
        /// 
        /// 
        /// 
        /// 
        public T ListLeftPop(string key)
        {
            key = AddSysCustomKey(key);
            return Do(db =>
            {
                var value = db.ListLeftPop(key);
                return ConvertObj(value);
            });
        }

        /// 
        /// 获取集合中的数量
        /// 
        /// 
        /// 
        public long ListLength(string key)
        {
            key = AddSysCustomKey(key);
            return Do(redis => redis.ListLength(key));
        }


    }
View Code

4、创建log4net的配置文件log4net.config。设置属性为:始终复制、内容。

xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
  configSections>

  <log4net>
    <root>
      
      
      <level value="ALL" />
      
      <appender-ref ref="MyErrorAppender"/>
    root>

    <appender name="MyErrorAppender" type="log4net.Appender.RollingFileAppender">
      
      <param name= "File" value= "Log\\"/>
      
      <param name= "AppendToFile" value= "true"/>
      
      <param name= "RollingStyle" value= "Date"/>
      
      <param name= "DatePattern" value= "yyyyMMdd/Error_yyyy_MM_dd".log""/>
      
      <param name= "StaticLogFileName" value= "false"/>
      
      
      <layout type="log4net.Layout.PatternLayout,log4net">
        
        
        
        
        
        
        
        <param name="ConversionPattern" value="========[Begin]========%n%d [线程%t] %-5p %c 日志正文如下- %n%m%n%n" />
      layout>
      
      
      
      
      
      <param name="lockingModel"  type="log4net.Appender.FileAppender+MinimalLock" />

      
      <filter type="log4net.Filter.LevelMatchFilter">
        <LevelToMatch value="ERROR" />
      filter>
      
      
      <filter type="log4net.Filter.DenyAllFilter" />
    appender>
  log4net>
configuration>
View Code

5、创建日志类LoggerFunc、日志工厂类LoggerFactory

/// 
    /// 日志单例工厂
    /// 
    public class LoggerFactory
    {
        public static string CommonQueueName = "DisSunQueue";
        private static LoggerFunc log;
        private static object logKey = new object();
        public static LoggerFunc CreateLoggerInstance()
        {
            if (log != null)
            {
                return log;
            }

            lock (logKey)
            {
                if (log == null)
                {
                    string log4NetPath = AppDomain.CurrentDomain.BaseDirectory + "Config\\log4net.config";
                    log = new LoggerFunc();
                    log.logCfg = new FileInfo(log4NetPath);
                    log.errorLogger = log4net.LogManager.GetLogger("MyError");
                    log.QueueName = CommonQueueName;//存储在Redis中的键名
                    log4net.Config.XmlConfigurator.ConfigureAndWatch(log.logCfg);    //加载日志配置文件S                
                }
            }

            return log;
        }
    }
View Code
/// 
    /// 日志类实体
    /// 
    public class LoggerFunc
    {
        public FileInfo logCfg;
        public log4net.ILog errorLogger;
        public string QueueName;       

        /// 
        /// 保存错误日志
        /// 
        /// 日志内容
        public void SaveErrorLogTxT(string title)
        {
            RedisHelper redis = new RedisHelper();
            //塞进队列的右边,表示从队列的尾部插入。
            redis.ListRightPush<string>(QueueName, title);           
        }

        /// 
        /// 日志队列是否为空
        /// 
        /// 
        public bool IsEmptyLogQueue()
        { 
            RedisHelper redis = new RedisHelper();
            if (redis.ListLength(QueueName) > 0)
            {
                return false;
            }
            return true;        
        }

    }
View Code

6、创建本章最核心的日志队列设置类LogQueueConfig。

ThreadPool是线程池,通过这种方式可以减少线程的创建与销毁,提高性能。也就是说每次需要用到线程时,线程池都会自动安排一个还没有销毁的空闲线程,不至于每次用完都销毁,或者每次需要都重新创建。但其实我不太明白他的底层运行原理,在内部while,是让这个线程一直不被销毁一直存在么?还是说sleep结束后,可以直接拿到一个线程池提供的新线程。为什么不是在ThreadPool.QueueUserWorkItem之外进行循环调用?了解的童鞋可以给我留下言。

/// 
    /// 日志队列设置类
    /// 
    public class LogQueueConfig
    {
        public static void RegisterLogQueue()
        {
            ThreadPool.QueueUserWorkItem(o =>
            {
                while (true)
                {
                    RedisHelper redis = new RedisHelper();
                    LoggerFunc logFunc = LoggerFactory.CreateLoggerInstance();
                    if (!logFunc.IsEmptyLogQueue())
                    {
                        //从队列的左边弹出,表示从队列头部出队
                        string logMsg = redis.ListLeftPop<string>(logFunc.QueueName);

                        if (!string.IsNullOrWhiteSpace(logMsg))
                        {
                            logFunc.errorLogger.Error(logMsg);
                        }
                    }
                    else
                    {
                        Thread.Sleep(1000); //为避免CPU空转,在队列为空时休息1秒
                    }
                }
            });
        }
    }
View Code

7、在项目的Global.asax文件中,启动队列线程。本demo由于是在winForm中,所以放在form中。
 

        public Form1()
        {
            InitializeComponent();
            RedisLogQueueTest.CommonFunc.LogQueueConfig.RegisterLogQueue();//启动日志队列
        }

8、调用日志类LoggerFunc.SaveErrorLogTxT(),插入日志。

            LoggerFunc log = LoggerFactory.CreateLoggerInstance();
            log.SaveErrorLogTxT("您插入了一条随机数:"+longStr);

9、查看下入效果

利用StackExchange.Redis和Log4Net构建日志队列_第2张图片

 

利用StackExchange.Redis和Log4Net构建日志队列_第3张图片

 

10、完整源码(winForm不懂?差不多的啦,打开项目直接运行就可以看见界面):

https://gitee.com/dissun/RedisLogQueueTest

 

#### 原创:DisSun ##########

#### 时间:2019.03.19 #######

你可能感兴趣的:(利用StackExchange.Redis和Log4Net构建日志队列)