性能秒杀log4net的NLogger日志组件(附测试代码与NLogger源码)
NLogger特点(200行代码的日志组件):
一:不依赖于第三方插件和支持.net2.0
二:支持多线程高并发
三:支持读写双缓冲对列
四:自定义日志缓冲区大小
五:支持即时触发刷盘机制
六:先按日期再按文件大小RollingFile日志
七:支持日志存储位置,日志文件前缀的个性化定义
一:为什么要特别强调不依赖于第三方插件和支持.net2.0
NLogger包括名称空间也未超过200行代码,可见日志是相当轻量级的,如果是依赖于第三方软件的支持,有失轻量级的定义。
NLogger的第一个版本是基本于.net4.0开发,但是发现在实际应用的时候很难降级到.net2.0的项目,因为第一个版本用到了很多.net4.0的特性,主要表现在:
1,多线程处理是用的Task
2,内存数据存储是用的Tuple<>
3,集合并发处理是用的 ConcurrentQueue
4,用了Linq语法
如此多的.net4特性降级到.net2.0,确实花费了很多时间来重构这个代码,举个例子:
.net2.0与.net4.0在数据集合上的运用,表现的最为不同的就是:
.net2.0 不支持集合的并发处理,如果是多线程操纵集合,必须要借助于lock来锁定对象,然而lock后的集合就从多线程变为单线程的处理了,如此的性能很让人沮丧。
.net4.0引入的Concurrent系列的并发集合,让很多不会多线程编程的伙伴都把多线程玩的溜溜转,性能还如此的高。
二:支持多线程高并发
良好的日志应用组件,支持多线程高并发是必不可少的特性。先标记一下测试机的硬件环境
,不同的硬件环境对测试结果是有较大影响的。
测试代码:
开启10个线程,每个线程写入10W数据,确认一下代码中数值0的个数是否正确:
for (int count = ; count < ; count++)
{
Thread writeThread = new Thread(new ParameterizedThreadStart((para) =>
{
Console.WriteLine(string.Format("开启线程{0}", para));
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = ; i < ; i++)
{
NLogger.WriteLog("test_", string.Format("日志测试数据,序号:{0}", i.ToString()));
}
sw.Stop();
Console.WriteLine(string.Format("线程{0}写入日志结束,共用时{1}毫秒", para, sw.ElapsedMilliseconds));
}));
writeThread.IsBackground = true;
writeThread.Start(count);
}
测试结果:
11秒的时间就把100W条数据刷到了缓存里。在4G内存的笔记本里,处理速度能达到10W条/秒。
三:为什么要用读写双缓冲队列
在操作第二步的时候,业务程序写入的100W条数据,绝大多数还在缓存对列里,还没有持久化到硬盘上,可以通过如下代码监视读写缓存和已持久化到硬盘上的数据。
Thread watchThread = new Thread(new ParameterizedThreadStart((para) =>
{
DateTime startDT = DateTime.Now; while (NLogger.totalCount < )
{
DateTime sectionDt = DateTime.Now;
TimeSpan ts = sectionDt - startDT;
Console.WriteLine(string.Format("已用时{0}秒 已写入{1}条 写缓存{2}条 读缓存{3}条", (int)ts.TotalSeconds, NLogger.totalCount, NLogger.writeQueue.Count, NLogger.readQueue.Count));
Thread.Sleep();
}
}));
watchThread.IsBackground = true;
watchThread.Start("");
可以看到在第10秒的时候,100W条数据已经被业务程序全部处理完成。其中有近83W条数据在缓存里,有13W条数据已经刷到了硬盘里,因是为按秒监控,有4W条数据的误差。持续了50秒,才把100W条数据全部刷到硬盘里。
四:自定义日志缓冲区大小
大量的日志存储在缓冲队列里,在刷新硬盘的时候,不可能一条一条的刷新数据,虽然现在的固态硬盘已经在市场上流行了很多年,还是没有完全普及,在很多计算机上IO还是瓶颈。如果能做到一次能刷新多条数据,就会提高刷盘的速度。每次刷多少数据到硬盘才能达到最优值?本机是设置的64KB,不同的计算机可能这个值有所不同。
具体的实现代码如下:
while (true)
{
if (readQueue.Count > )
{
string[] qItem = readQueue.Dequeue() as string[];
totalCount = totalCount + ;
string[] tempItem = tempQueue.Find(d => d[] == qItem[] && d[] == qItem[]);
if (tempItem == null)
{
tempQueue.Add(qItem);
}
else
{
tempItem[] = string.Concat(tempItem[], Environment.NewLine, qItem[]);
if (tempItem[].Length > * ) //(1 * 1024 * 1024 = 1M);
{
break;
}
}
}
else
{
break;
}
}
五:支持即时触发刷盘机制
在多线程的世界里, AutoResetEvent, ManualResetEvent这两个类是十分重要的。它们的区别,网上是到处都有介绍的,本篇博客就只做一个入门级的介绍。
这两个类称为信号量类,主要包括如下三个方法:
WaitOne()
Set()
Reset()
如果把线程比作水管,WaitOne() 就是水阀,Set()就是通知打开水阀,Reset()就是通知关闭水阀。介绍信号量的博客都喜欢举这个例子,照葫芦画瓢引用了这个例子。
在日志类里开启一个刷盘的线程,在闲时是不运行,也不会占用很多的系统资源,因为WaitOne()水阀状态是关闭着的。一旦别的线程往缓冲队列里写日志数据,就用Set()信号量通知打开水阀,日志刷盘完毕后,用Reset()信号量通知把水阀关起来。
这样就用信号量的原理实现了即时刷盘的机制。
六:RollingFile日志
在特别的场景下,比如即时消息系统,WEB应用系统,日志量都很大,一天可以达到G级别量的日志,一般情况下,记录本文件超过10M,打开速度就比较慢,而且不便于定位和查看。这时就需要对日志文件进行分割,分割日志文件常用的手法一般从两个维度进行,一个是按时间维度,一个是按日志的体积。NLogger采用了时间维度和日志维度叠加的方法来进行日志的分割。
分割日志是一件很容易的事情,难的是对新的日志文件的命名,比如现在的日志文件名是test_20170212(7).log,怎么能计算出下一个日志文件应该是test_20170212(8).log呢?用正则表达式的方式,先匹配出文件名的数字,再计算出下一个文件的名字。这儿的正则式就有含量了,抗干扰性要强,容错性要高,才能匹配出更精准的数字。
七:支持日志存储位置,日志文件前缀的个性化定义
并不是每个程序的日志信息都存储在自身程序目录里,有可能定义到其它盘符,或者其它服务器上的共享目录,支持日志存储位置自定义是有必要的。
不同的应用场景,为了区分日志的分类,有必要在日志文件名前加一个前缀,很幸运NLoger支持了这一功能。
八:与标杆日志组件log4net一试高低
Log4net的普及度是很高的,这儿不做详细介绍如何配置使用了。把测试的代码贴出来,熟手一看就明白了。
这儿做一个Log4net的标配文件:
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/>
</configSections>
<log4net>
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender" >
<Encoding value="UTF-8" />
<file value="Logs/" />
<!--记录日志写入文件时,不锁定文本文件,防止多线程时不能写Log,官方说线程非安全-->
<lockingModel type="log4net.Appender.FileAppender+MinimalLock"/>
<!--按照何种方式产生多个日志文件(日期[Date],文件大小[Size],混合[Composite])-->
<rollingStyle value="Composite" />
<!--按照日期格式输出文件路径-->
<datePattern value="yyyyMMdd'.txt'"/>
<!--是否只写到一个文件中-->
<staticLogFileName value="false"/>
<!--每个文件的大小-->
<maximumFileSize value="1MB"/>
<!--最多产生的日志文件数,超过则只保留最新的n个。设定值value="-1"为不限文件数-->
<maxSizeRollBackups value="100"/>
<!--程序启动后,是否追加到文件-->
<appendToFile value="true" />
<!--日志缓存大小-->
<bufferSize value="128" />
<!--日志格式-->
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %message %newline" />
</layout>
</appender>
<root>
<appender-ref ref="RollingFileAppender" />
<level value="DEBUG" />
</root>
</log4net>
</configuration>
为了测试的公平性,测试代码的逻辑与测试数据和NLogger都是相同的:
for (int count = ; count < ; count++)
{
Thread writeThread = new Thread(new ParameterizedThreadStart((para) =>
{
log4net.ILog log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
Console.WriteLine(string.Format("开启线程{0}", para));
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = ; i < ; i++)
{
log.Info(string.Format("日志测试数据,序号:{0}", i.ToString()));
}
sw.Stop();
Console.WriteLine(string.Format("线程{0}写入日志结束,共用时{1}毫秒", para, sw.ElapsedMilliseconds));
}));
writeThread.IsBackground = true;
writeThread.Start(count);
}
log4net写日志,测试开启10个线程,共写100W条数据到硬盘,吃了个午饭回来还没有执行结束。NLogger 10秒刷100W条数据到缓存,50秒刷100W条数据到硬盘,这样的对比,意义已经不大了。
想了解更多,请翻阅以前的博客,在网页右下角点击推荐。
下篇将NLogger .net4.0版贴出来,性能是优于.net2.0版的。
性能秒杀log4net的NLogger日志组件(附测试代码与NLogger源码)的更多相关文章
- Node 进阶:express 默认日志组件 morgan 从入门使用到源码剖析
本文摘录自个人总结<Nodejs学习笔记>,更多章节及更新,请访问 github主页地址.欢迎加群交流,群号 197339705. 章节概览 morgan是express默认的日志中间件, ...
- 如何实现全屏遮罩(附Vue.extend和el-message源码学习)
[Vue]如何实现全屏遮罩(附Vue.extend和el-message源码学习) 在做个人项目的时候需要做一个类似于电子相册浏览的控件,实现过程中首先要实现全局遮罩,结合自己的思路并阅读了(饿了么) ...
- 如何快速为团队打造自己的组件库(上)—— Element 源码架构
文章已收录到 github,欢迎 Watch 和 Star. 简介 详细讲解了 ElementUI 的源码架构,为下一步基于 ElementUI 打造团队自己的组件库打好坚实的基础. 如何快速为团队打 ...
- 修改testng源码,添加beforeMethod和afterMethod中的日志到test中(可以不改源码,废弃)
在使用testng生成报告的时候,只会记录test方法中的日志,但是一般会在beforeMethod.beforeTest.afterMethod.afterTest中做一下数据的处理,这里面的日志没 ...
- Django drf:认证及组件、token、局部钩子源码分析
一.drf认证功能 二.token讲解 三.局部钩子源码分析 一.drf认证功能 1.认证简介: 只有认证通过的用户才能访问指定的url地址,比如:查询课程信息,需要登录之后才能查看,没有登录则不能查 ...
- 仿酷狗音乐播放器开发日志二十四 选项设置窗体的实现(附328行xml布局源码)
转载请说明原出处,谢谢~~ 花了两天时间把仿酷狗的选项设置窗体做出来了,当然了只是做了外观.现在开学了,写代码的时间减少,所以整个仿酷狗的工程开发速度减慢了.今天把仿酷狗的选项设置窗体的布局代码分享出 ...
- Flume组件source,channel,sink源码分析
LifeCycleState: IDLE, START, STOP, ERROR [Source]: org.apache.flume.Source 继承LifeCycleAware{stop() + ...
- 王爽<汇编语言>实验十一 (附测试代码)
;名称: letterc ;功能: 将以0为结尾的字符串中的小写字母转变成大写字母 ;参数: ds:si指向字符串首地址 assume cs:code data segment db data end ...
- Android 仿 窗帘效果 和 登录界面拖动效果 (Scroller类的应用) 附 2个DEMO及源码
在android学习中,动作交互是软件中重要的一部分,其中的Scroller就是提供了拖动效果的类,在网上,比如说一些Launcher实现滑屏都可以通过这个类去实现.下面要说的就是上次Scroller ...
随机推荐
- webservice-概念性学习(一)
以下是本人原创,如若转载和使用请注明转载地址.本博客信息切勿用于商业,可以个人使用,若喜欢我的博客,请关注我,谢谢!博客地址 学习webservice之前呢,我想说我们先学习以下的知识,对你以后的学习 ...
- JQuery实现超链接鼠标提示效果
一.第一种方法用Jquery<p><a href="http://www.nowamagic.net/" class="tooltip" ti ...
- iOS 简易环形进度条
demo下载地址:https://github.com/haozheMa/LoopProgressDemo/tree/master ViewController中的代码 #import "V ...
- Tomcat热部署:Maven项目一键部署到Tomcat服务器 - 支持多环境
参考:Eclipse中的Maven项目一键部署到Tomcat服务器 - 支持多环境部署 命令 debug模式设置关联源码 eclipse --> 项目右键 --> Debug As --& ...
- laravel memcached使用
当第一次使用cache时,想用 memcached 的方式,但是它直接报错: 说明你的php没安装 memcached 这个扩展,在ubuntu下直接 sudo apt-get install mem ...
- FZU 2101 大三的美好时光
DP+离散化. 首先需要把时间离散化,剩下的就是简单DP. 还要判断哪些选修课与必修课时间有重合,我用了前缀和来处理. 注意:这题时间端点也不能重合. #include<cstdio> # ...
- random 函数
Random()在Delphi中,有一随机函数,是这样定义的:function Random [ ( Range: Integer) ]; 其中,参数Range为一整数,该函数返回值也为整数,其范围为 ...
- Lua C Api lua_gettable 、lua_settable 、lua_next 使用详解
之前一直没理清lua_gettable和lua_settable的使用,今天理清了,顺便就做下笔记了.1.lua_gettable void lua_gettable (lua_State *L, i ...
- css3快速复习
选择器边框.阴影 border-radius: 50%; 设置正圆形背景的改变CSS3重要的新东西: ● transition 过度,让一个元素从一个样式,变为另一个样式,不再是干蹦了,而是有动画,均 ...
- SVN简明课程
Reference: http://www.cnblogs.com/wangkangluo1/archive/2011/08/11/2135312.html 1. 版本控制介绍 1.1. 什么是版本控 ...