关闭

C# 系统日志处理-生产者与消费者模式

标签: 日志高并发c#高并发处理c#日志处理
936人阅读 评论(0) 收藏 举报
分类:

目录

引言

高并必的日志处理在网上可以找到很多的解决方案,在做这个Demo之前我也是个菜鸟;当然,做完这个Demo还是一个菜鸟。废话不多说,先看下先辈们的案例:

这篇文章主要讲的是思路,代码还有待优化,使用的是生产者与消费者模式、另外还使用了单例模式;这只是一种做法而已,编程路上的想法与实现想法。

思路

解决问题思路非常重要,有了思路我们才会有目标、方向;
从问题的来源说起吧,因为DMS系统需要记录日志,然而在现有的方式是记录到数据库,这个方法有好也有坏,坏处在于:

  1. 并必量高的时候增加了数据库服务器的压力为一些日常日志、业务日志或者重要的系统日志来增加数据库服务器的压力还是有些不值得的;
  2. 在增加数据库服务器的压力同时还会增加垃圾数据,这些日志别看量很少,单条数据量不大,但对于庞大的并必量时将是不堪;
    基于这些原因老大要我对日志模块进行优化;使用系统现有的日志模块方式写文件无疑是最好的,对线上系统的维护来说是比较好的选择,能快速的反应事实,但还没上线,在本地浏览器里面随便的Enter,浏览器大哥就跟我说IO冲突,想想也是有可能的,对于WEB服务器来说是多线程,IO冲突也是需要注意的事情,这一点小伙伴们要记住了。好了这是为什么有这遍文章的原由(这是一段屁话-_-)。
    有了这个原由后就要开始想怎么解决IO冲突了,这时想到了生产者与消费者模式;这个模式可以说是为了这种问题而生;WEB服务器做生产者,日志模块做消费者,对于系统现状来说这也是最省的方式,因为日志模块也有了;那我们要做的事就是做这个生产者与消费者的中间件了;
    对中间件的设计思路:

  3. 首先要有一条单独的线程向文件写日志;

  4. 需要有一个仓库来存储生产者生产的产物;
  5. 仓库和线程之间的交互,线程最好能自我化的管理;
    UML图如下:
Created with Raphaël 2.1.0用户1~N用户1~NWEB服务器WEB服务器消息队列消息队列消费线程消费线程日志记录模块日志记录模块请求生产消息请求消息返回消费(获取消息)持久化到文件

说明/注释

  1. 为了保证整个过程中只会有一条线程来对日志文件进行操作,这里就会使用单例模式,单例模式能很好的保护和管理这一问题,在任何情况下都能保证不会有另外一条线程来与当前线程产生IO冲突;
  2. 同样也是使用单例模式来管理消息队列(也是就是上面提到的生产者的产物仓库),对于消息队列也是一样,所有的生产者的的产物都放一同一消息队列中,保存消息队列的唯一性;

中间件实现

对于中间件我做了三个类,分别为SingleThread、MessageQueue、Common
三者类关系图:
中间件类关系图
这里使用Common组合SingleThread和MessageQueue;SingleThread提供对线程的支持,MessageQueue提供对消息队列的支持;而Common刚是对他们的一个封装,开放给生产者一个接口(AddMessage),Web服务器不需要知道其它的去作,只需要告诉Common我需要写日志了,我把我需要写的日志信息给你,你去帮我写到文件;WEB服务器做完这些并将它的日志信息交到消息队列后就可以去做他自己的事情(这样做的目录是将WEB服务器与日志进行解耦,WEB服务器不用去管日志是怎么写入到文件的,有没有写入,这样也方便后期的维护,不知道有没有提升WEB服务器的性能,我认为是有提升的,从理论的角度来讲WEB服务器不用去做写日志的事情);好了,说到这里大家应该从宏观的角度能对这个中间件有一些了解了,那些看来看来具体的代码实现。

代码实例

MessageQueue.CS

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Log.MiddleWare.Message
{
    /// <summary>
    /// 文本消息队列处理类
    /// </summary>
    public class MessageQueue
    {
        private Queue<string> _queue = null;
        private static MessageQueue _messageQueue = null;
        private MessageQueue()
        {
        }


        #region 属性
        /// <summary>
        /// 获取消息队列  在实际应用中请使用线程安全的队列或者其它方式
        /// </summary>
        private Queue<string> GetQueue
        {
            get
            {
                if (_queue == null)
                {
                    _queue = new Queue<string>();
                }
                return _queue;
            }
        }

        /// <summary>
        /// 获取消息队列长度
        /// </summary>
        public long Count
        {
            get { return GetQueue.Count; }
        }
        #endregion

        #region 方法

        /// <summary>
        /// 添加消息
        /// </summary>
        /// <param name="message">需要添加的消息文本</param>
        public void AddMessage(string message)
        {
            GetQueue.Enqueue(message);
        }

        /// <summary>
        /// 获取消息
        /// </summary>
        /// <returns></returns>
        public string GetMessage()
        {
            return GetQueue.Dequeue();
        }

        /// <summary>
        /// 获取类对象
        /// </summary>
        /// <returns></returns>
        public static MessageQueue GetMessageQueue()
        {
            return _messageQueue ?? new MessageQueue();
        }

        #endregion
    }
}


SingleThread.CS

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;

namespace Log.MiddleWare.Thread
{
    /// <summary>
    /// 单线程自我管理基类
    /// </summary>
    public class SingleThread
    {
        /// <summary>
        /// 自我管理线程
        /// </summary>
        private object _thread = new Object();
        /// <summary>
        /// 线程初始化状态
        /// </summary>
        public bool flog = false;
        private static SingleThread singleThread = null;

        #region 属性
        /// <summary>
        /// 线程委托方法
        /// </summary>
        public ThreadStart StartFuncation { get; set; }

        /// <summary>
        /// 获取线程
        /// </summary>
        public System.Threading.Thread GetThread
        {
            get
            {
                lock (_thread)
                {
                    if (_thread.GetType()==typeof(System.Threading.Thread)&&(_thread as System.Threading.Thread).ThreadState == ThreadState.Stopped)
                    {
                        (_thread as System.Threading.Thread).DisableComObjectEagerCleanup();
                        flog = false;
                    }
                    if (!flog)
                    {
                        if (StartFuncation == null)
                        {
                            throw new Exception("请先设置线程委托执行方法");
                        }
                        _thread = new System.Threading.Thread(StartFuncation);
                        (_thread as System.Threading.Thread).Name = "singlethread";
                        flog = true;
                    }

                }
                return _thread as System.Threading.Thread;
            }
        }
        /// <summary>
        /// 获取线程当前状态
        /// </summary>
        public ThreadState ThreadState
        {
            get { return GetThread.ThreadState; }
        }

        #endregion

        private SingleThread()
        {
        }

        public static SingleThread GetSingleThread()
        {
            if (singleThread == null)
            {
                singleThread = new SingleThread();
            }
            return singleThread;
        }

        /// <summary>
        /// 开始执行线程
        /// </summary>
        public void StartThread()
        {
            GetThread.Start();
        }

        /// <summary>
        /// 暂停线程执行
        /// </summary>
        public void AbortThread()
        {
            GetThread.Suspend();
        }

    }
}


Common.CS

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading;

namespace Log.MiddleWare
{
    public class Common
    {
        private Thread.SingleThread singleThread = Thread.SingleThread.GetSingleThread();
        private Message.MessageQueue messageQueue = Message.MessageQueue.GetMessageQueue();

        private static readonly Common _Common = new Common();
        private Common()
        {
            singleThread.StartFuncation = new ThreadStart(WriteLog);
            LogHelper.LogHelper.SetConfig();
        }

        #region 方法

        /// <summary>
        /// 将消息从队列中写入到文件
        /// </summary>
        private void WriteLog()
        {
            while (true)
            {
                string message = messageQueue.GetMessage();
                LogHelper.LogHelper.WriteLog(message);
                //这里为了增加线程处理时间添加一些事务处理,正式使用请删除
                #region 增加事务处理以延长线程处理时间
                DataTable dt=new DataTable();
                dt.Columns.Add("a", typeof(string));
                dt.Columns.Add("b", typeof(string));
                dt.Columns.Add("d", typeof(string));
                dt.Columns.Add("e", typeof(string));
                dt.Columns.Add("f", typeof(string));
                dt.Columns.Add("g", typeof(string));
                dt.Columns.Add("h", typeof(string));
                dt.Columns.Add("i", typeof(string));
                dt.Columns.Add("j", typeof(string));
                dt.Columns.Add("k", typeof(string));
                dt.Columns.Add("l", typeof(string));
                dt.Columns.Add("m", typeof(string));
                for (int i = 0; i < 100; i++)
                {
                    DataRow dr = dt.NewRow();
                    dr[0] = "1111";
                    dr[1] = "1111111111111111111111";
                    dr[2] = "1111111111111111111111";
                    dr[3] = "1111111111111111111111";
                    dr[4] = "1111111111111111111111";
                    dr[5] = "1111111111111111111111";
                    dr[6] = "1111111111111111111111";
                    dr[7] = "1111111111111111111111";
                    dr[8] = "1111111111111111111111";
                    dr[9] = "1111111111111111111111";
                    dr[10] = "1111111111111111111111";
                    dr[11] = "1111111111111111111111";
                    dt.Rows.Add(dr);
                }
                var listdr = (from DataRow dr in dt.AsEnumerable()
                    select new
                    {
                        a = Convert.ToDecimal(dr[0]),
                        b = dr[1],
                        c = dr[2]
                    }).Sum(s => s.a);
                #endregion
                if (messageQueue.Count == 0)
                {
                    singleThread.AbortThread();
                }
            }
        }
        /// <summary>
        /// 将日志写入文件
        /// </summary>
        /// <param name="message"></param>
        public void AddLogMessage(string message)
        {

            messageQueue.AddMessage(message+"\t" + messageQueue.Count + "\t" + singleThread.GetThread.ManagedThreadId + "\t" + singleThread.GetThread.Name);
            if (singleThread.ThreadState==ThreadState.Unstarted)
            {
                singleThread.StartThread();
            }
            if (singleThread.ThreadState==ThreadState.Suspended)
            {
                singleThread.GetThread.Resume();
            }
        }
        /// <summary>
        /// 获取Common对象
        /// </summary>
        /// <returns></returns>
        public static Common GetCommon()
        {
            return _Common;
        }

        #endregion

    }
}

结束语

这个中间件只是一个例子,单例线程处理类中使用了委托来动态的定义需要执行的方法,以便对这个中间件的功能扩展;另外这只是一个例子,写这个中间件的目录并不是只为了处理高并发的日志,这个中间如果再进行优化应该是可以用在其它相同案例的地方。

日志模块说明:日志模块使用的是开源的Log4Net插件

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:36797次
    • 积分:551
    • 等级:
    • 排名:千里之外
    • 原创:18篇
    • 转载:2篇
    • 译文:0篇
    • 评论:3条
    文章分类