HTML5技术

日交易额百亿级交易系统的超轻量日志实现 - cyfonly

字号+ 作者:H5之家 来源:H5之家 2016-12-10 13:00 我要评论( )

首先来聊聊往事吧~~两年前就职于一家传统金融软件公司,为某交易所开发一套大型交易系统,交易标的的价格为流式数据,采用价格触发成交方式,T+0交易制度(类似炒股,只是炒的不是股票而是其他标的物,但可以随时开平仓)。鉴于系统需要记录大量价格数据、交

首先来聊聊往事吧~~两年前就职于一家传统金融软件公司,为某交易所开发一套大型交易系统,交易标的的价格为流式数据,采用价格触发成交方式,T+0交易制度(类似炒股,只是炒的不是股票而是其他标的物,但可以随时开平仓)。鉴于系统需要记录大量价格数据、交易信息及订单流水,且系统对性能要求极高(敏感度达毫秒级),因此需要避免日志服务成为系统性能瓶颈。通过对几个通用型日志(如log4j、logback)的性能压测,以及考虑到它们作为通用型日志相对比较臃肿,就决定自个儿写个日志工具以支撑系统功能和性能所需。当时的做法只是简单的将日志的实现作为一个 util 类写在项目中,只有几百行的代码量。

系统上线两个月后日均成交额200亿RMB,最高达440亿RMB,峰值成交4000笔/秒。系统非常庞大,但几百行的代码却完美支撑住了重要的日志服务!

鉴于其优秀的表现,就花了一点点时间把它抽取出来作为一个独立的日志组件,取名叫 FLogger,代码几乎没有改动,现已托管到GitHub(FLogger),有兴趣的童鞋可以clone下来了解并改进,目前它的实现是非常简(纯)单(粹)的。

以上就是 FLogger 的诞生背景。好吧,下面进入正题。

特性

虽然 FLogger 只有几百行的代码,但是麻雀虽小五脏俱全,它可是拥有非常丰富的特性呢:

使用

既然是个超轻量级日志,使用肯定要很简单。为最大程度保持用户的使用习惯,Flogger 提供了与 log4j 几乎一样的日志 API。你只需要先获取一个实例,接下来的使用方式就非常简单了:

//获取单例 FLogger logger = FLogger.getInstance(); //简便api,只需指定内容 logger.info("Here is your message..."); //指定日志级别和内容,文件名自动映射 logger.writeLog(Constant.INFO, "Here is your customized level message..."); //指定日志输出文件名、日志级别和内容 logger.writeLog("error", Constant.ERROR, "Here is your customized log file and level message...");

使用前你需要在项目根路径下创建 log.properties 文件,配置如下:

########## 公共环境配置 ########## # 字符集 CHARSET_NAME = UTF-8 ########## 日志信息配置 ########## # 日志级别 0:调试信息 1:普通信息 2:警告信息 3:错误信息 4:严重错误信息 LOG_LEVEL = 0,1,2,3,4 # 日志文件存放路径 LOG_PATH =./log # 日志写入文件的间隔时间(默认为1000毫秒) WRITE_LOG_INV_TIME = 1000 # 单个日志文件的大小(默认为10M) SINGLE_LOG_FILE_SIZE = 10485760 # 单个日志文件缓存的大小(默认为10KB) SINGLE_LOG_CACHE_SIZE = 10240

当然,为了提供最大程度的便捷性,日志内部针对所有配置项都提供了默认值,你大可不必担心缺少配置文件会抛出异常。

至此,你可能很好奇使用 FLogger 打印出来的日志格式到底是怎样的,会不会杂乱无章无法理解,还是信息不全根本无法判断上下文呢?好吧,你多虑了,FLogger 提供了非常规范且实用的日志格式,能使让你很容易理解且找到相关上下文。

先来看看上面的 demo 代码打印出来的结果:

info.log

[INFO] 2016-12-06 21:07:32:840 [main] Here is your message...

warn.log

[WARN] 2016-12-06 21:07:32:842 [main] Here is your customized level message...

error.log

[ERROR] 2016-12-06 21:07:32:842 [main] Here is your customized log file and level message...

 从上面可以看到,你可以很清楚的分辨出日志的级别、时间和内容等信息。到这其实很明了了,日志由以下几个元素组成:

[日志级别] 精确到毫秒的时间 [当前线程名] 日志内容

 当然,处于便捷性的考虑,FLogger 目前并不支持用户定义日志格式,毕竟它的目的也不是要做成一个通用性或者可定制性非常高的日志来使用。

源码解析

上面这么多都是围绕如何使用进行说明,下面就针对 FLogger 的特性进行实现逻辑的源码解析。

双缓冲队列

FLogger 在内部采用双缓冲队列,那何为双缓冲队列呢?它的作用又是什么呢?

FLogger 为每个日志文件维护了一个内部对象 LogFileItem ,定义如下:

public class LogFileItem { /** 不包括路径,不带扩展名的日志文件名称 如:MsgInner */ public String logFileName = ""; /** 包括路径的完整日志名称 */ public String fullLogFileName = ""; /** 当前日志文件大小 */ public long currLogSize = 0; /** 当前正在使用的日志缓存 */ public char currLogBuff = 'A'; /** 日志缓冲列表A */ public ArrayList<StringBuffer> alLogBufA = new ArrayList<StringBuffer>(); /** 日志缓冲列表B */ public ArrayList<StringBuffer> alLogBufB = new ArrayList<StringBuffer>(); /** 下次日志输出到文件时间 */ public long nextWriteTime = 0 ; /** 上次写入时的日期 */ public String lastPCDate = ""; /** 当前已缓存大小 */ public long currCacheSize = 0; }

在每次写日志时,日志内容作为一个 StringBuffer 添加到当前正在使用的 ArrayList<StringBuffer> 中,另一个则空闲。当内存中的日志输出到磁盘文件时,会将当前使用的 ArrayList<StringBuffer> 与空闲的 ArrayList<StringBuffer> 进行角色交换,交换后之前空闲的 ArrayList<StringBuffer> 将接收日志内容,而之前拥有日志内容的 ArrayList<StringBuffer> 则用来输出日志到磁盘文件。这样就可以避免每次刷盘时影响日志内容的接收(即所谓的 stop-the-world 效应)及多线程问题。流程如下:

关键代码如下:

日志接收代码

//同步单个文件的日志 synchronized(lfi){     if(lfi.currLogBuff == 'A'){         lfi.alLogBufA.add(logMsg);     }else{         lfi.alLogBufB.add(logMsg);     }     lfi.currCacheSize += CommUtil.StringToBytes(logMsg.toString()).length; }

日志刷盘代码:

 

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
网友点评
f