美高梅开户-美高梅官方网站开户

您的位置:美高梅开户 > 美高梅开户 > 日交易额百亿级交易系统的超轻量日志实现

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

发布时间:2019-08-15 04:10编辑:美高梅开户浏览(165)

    日交易额百亿级交易系统的超轻量日志完结,百亿级交易系统

    美高梅开户 1

    率先来聊聊以往的事情吧~~八年前下车于一家守旧金融软件公司,为某交易所开拓一套大型交易系统,交易标的的价钱为流式数据,选拔价格触发成交方式,T 0交易制度(类似炒买炒卖股票,只是炒的不是期货而是别的标的物,但足以每一日开平仓)。鉴于系统供给记录多量价格多少、交易音信及订单流水,且系统对性能须求异常高(敏感度达纳秒级),由此需求幸免日志服务造成系统品质瓶颈。通过对几个通用型日志(如log4j、logback)的习性压测,以及考虑到它们作为通用型日志绝相比较较臃肿,就决定自身写个日志工具以支撑类别机能和总体性所需。当时的做法只是粗略的将日志的达成作为一个util 类写在项目中,独有几百行的代码量。

    系统上线多少个月前几天均成交金额200亿毛润之,最高达440亿毛曾外祖父,峰值成交五千笔/秒。系统十三分巨大,但几百行的代码却周全支撑住了最首要的日记服务!

    由于其天时地利的表现,就花了一小点光阴把它收收取来作为多个单身的日志组件,取名字为FLogger,代码差不离平昔不退换,现已托管到GitHub(FLogger),有意思味的童鞋能够clone下来通晓并改正,如今它的兑现是非常简(纯)单(粹)的。

    如上便是 FLogger 的出世背景。好呢,下边进入正题。

    美高梅开户 2

    特性

    虽说 FLogger 只有几百行的代码,不过麻雀虽小五脏俱全,它只是全部极其丰硕的性状呢:

    • 双缓冲队列
    • 各种刷盘机制,援救时直接触、缓存大小触发、服务关闭强制触发等刷盘形式
    • 多种 RollingFile 机制,辅助文件大小触发、按天触发等 Rolling 格局
    • 多日志品级,协理 debug、info、warn、error和 fatal 等日志等级
    • 热加载,由日志事件触发热加载
    • 超轻量,不借助于任何第三方库
    • 品质保证,成功能于日交易额百亿级交易系统

    首先来聊聊过去的事情吧~~三年前下车于一家守旧经济软件店肆,为某交易所开荒一套大型交易系统,交易标的的价位为流式数据,选用价格触发成交方式,T 0交易制度(类似炒买炒卖股票,只是炒的不是股票(stock)而是其余标的物,但能够随时开平仓)。鉴于系统须要记录大批量价位数据、交易音信及订单流水,且系统对品质供给非常高(敏感度达飞秒级),由此必要幸免日志服务产生系统性格瓶颈。通过对多少个通用型日志(如log4j、logback)的性质压测,以及考虑到它们当做通用型日志相对比较臃肿,就调节本人写个日志工具以支撑系统效率和质量所需。当时的做法只是回顾的将日志的落到实处作为一个util 类写在类型中,独有几百行的代码量。

    使用

    既然是个超轻量级日志,使用一定要很简短。为最大程度保持用户的运用习惯,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 如今并不支持用户定义日志格式,毕竟它的目标亦非要做成多个通用性大概可定制性相当高的日志来使用。

    系统上线多少个月后日均成交金额200亿毛主席,最高达440亿毛主席,峰值成交5000笔/秒。系统拾贰分巨大,但几百行的代码却周到支撑住了严重性的日记服务!

    源码深入分析

    下面这么多都是环绕怎样使用举办表达,上边就对准 FLogger 的性状开始展览落到实处逻辑的源码深入分析。

    出于其精良的显现,就花了一小点日子把它抽取出来作为三个独立的日志组件,取名称叫FLogger,代码大致从不改观,现已托管到GitHub(FLogger),有意思味的童鞋能够clone下来理解并革新,方今它的贯彻是极度简(纯)单(粹)的。

    双缓冲队列

    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; }

    日记刷盘代码:

    //获得需要进行输出的缓存列表
    ArrayList<StringBuffer> alWrtLog = null;
    synchronized(lfi){
        if(lfi.currLogBuff == 'A'){
            alWrtLog = lfi.alLogBufA;
            lfi.currLogBuff = 'B';
        }else{
            alWrtLog = lfi.alLogBufB;
            lfi.currLogBuff = 'A';
        }
        lfi.currCacheSize = 0;
    }
    //创建日志文件
    createLogFile(lfi);
    //输出日志
    int iWriteSize = writeToFile(lfi.fullLogFileName,alWrtLog);
    lfi.currLogSize  = iWriteSize;
    

    以上便是 FLogger 的诞生背景。行吗,下边步向正题。

    多刷盘机制

    FLogger 帮忙各个刷盘机制:

    • 刷盘时间距离触发
    • 内部存款和储蓄器缓冲大小触发
    • 剥离强制触发

    下边就来所有人家深入分析。

    特性

    固然 FLogger 唯有几百行的代码,可是麻雀虽小五脏俱全,它可是全数极其丰硕的性状呢:

    • 双缓冲队列
    • 多种刷盘机制,帮衬时直接触、缓存大小触发、服务关闭强制触发等刷盘格局
    • 三种 RollingFile 机制,帮衬文件大小触发、按天触发等 Rolling 形式
    • 多日志品级,协助 debug、info、warn、error和 fatal 等日志等第
    • 热加载,由日志事件触发热加载
    • 超轻量,不信赖任何第三方库
    • 个性有限协理,成成效于日交易额百亿级交易系统

    刷盘时间距离触发

    布置项如下:

    # 日志写入文件的间隔时间(默认为1000毫秒)
    WRITE_LOG_INV_TIME = 1000
    

    当距上次刷盘时间超越间隔时间,将试行内存日志刷盘。

    使用

    FLogger 已经宣告到 maven 公共仓库(版本更新新闻请点此查看),请增加以下重视(或直接在品种中援引jar 包):

    <dependency>
        <groupId>com.github.cyfonly</groupId>
        <artifactId>flogger</artifactId>
        <version>1.0.2</version>
    </dependency>
    

    既是是个超轻量级日志,使用应当要不会细小略。为最大程度保持用户的施用习贯,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...");
    

    Flogger 使用的布局文件名称叫flogger.properties,内部贯彻了灵活的安排文件加运载飞机制。配置文件加载顺序为:

    • 品种根路线
    • src/main/resources
    • 默许配置

    布局项如下:

    ########## 公共环境配置 ##########
    # 字符集
    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 这两天并不帮忙用户定义日志格式,究竟它的指标亦非要做成八个通用性也许可定制性相当高的日记来选用。

    内部存款和储蓄器缓冲大小触发

    配备项如下:

    # 单个日志文件缓存的大小(默认为10KB)
    SINGLE_LOG_CACHE_SIZE = 10240
    

    当内部存款和储蓄器缓冲队列的深浅抢先配置高低时,将施行内部存款和储蓄器日志刷盘。

    源码深入分析

    地点这么多都是围绕怎么利用进行表达,上面就对准 FLogger 的天性开始展览落到实处逻辑的源码深入分析。

    退出强制触发

    FLogger 内部注册了 JVM 关闭钩子 ShutdownHook ,当 JVM 寻常关闭时,由钩子触发强制刷盘,防止内部存款和储蓄器日志错失。相关代码如下:

    public FLogger(){
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            @Override
            public void run() {
                close();
            }
        }));
    }
    
    当 JVM 异常退出时无法保证内存中的日志全部落盘,但可以通过一种妥协的方式来提高日志刷盘的实时度:设置 SINGLE_LOG_CACHE_SIZE = 0 或者 WRITE_LOG_INV_TIME = 0 。
    

    刷盘代码如下:

    /** 线程方法 */
    public void run(){
        int i = 0 ;
        while(bIsRun){
            try{
                //输出到文件
                flush(false);
                //重新获取日志级别
                if(i   % 100 == 0){
                    Constant.CFG_LOG_LEVEL = CommUtil.getConfigByString("LOG_LEVEL","0,1,2,3,4");
                    i = 1;
                }
            }catch(Exception e){
                System.out.println("开启日志服务错误...");
                e.printStackTrace();
            }
        }
    }
    
    /** 关闭方法 */
    public void close(){
        bIsRun = false;
        try{
            flush(true);
        }catch(Exception e){
            System.out.println("关闭日志服务错误...");
            e.printStackTrace();
        }
    }
    
    /**
    * 输出缓存的日志到文件
    * @param bIsForce 是否强制将缓存中的日志输出到文件
    */ 
    private void flush(boolean bIsForce) throws IOException{
        long currTime = System.currentTimeMillis();
        Iterator<String> iter = logFileMap.keySet().iterator();
        while(iter.hasNext()){
            LogFileItem lfi = logFileMap.get(iter.next());
            if(currTime >= lfi.nextWriteTime || SINGLE_LOG_CACHE_SIZE <= lfi.currCacheSize || bIsForce == true){
                //获得需要进行输出的缓存列表
                ArrayList<StringBuffer> alWrtLog = null;
                synchronized(lfi){
                    if(lfi.currLogBuff == 'A'){
                        alWrtLog = lfi.alLogBufA;
                        lfi.currLogBuff = 'B';
                    }else{
                        alWrtLog = lfi.alLogBufB;
                        lfi.currLogBuff = 'A';
                    }
                    lfi.currCacheSize = 0;
                }
                //创建日志文件
                createLogFile(lfi);
                //输出日志
                int iWriteSize = writeToFile(lfi.fullLogFileName,alWrtLog);
                lfi.currLogSize  = iWriteSize;
            }
        }     
    }
    

    双缓冲队列

    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 效应)及多线程难点。流程如下:

    美高梅开户 3

    关键代码如下:

    日志接收代码

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

    日记刷盘代码:

    //获得需要进行输出的缓存列表
    ArrayList<StringBuffer> alWrtLog = null;
    synchronized(lfi){
        if(lfi.currLogBuff == 'A'){
            alWrtLog = lfi.alLogBufA;
            lfi.currLogBuff = 'B';
        }else{
            alWrtLog = lfi.alLogBufB;
            lfi.currLogBuff = 'A';
        }
        lfi.currCacheSize = 0;
    }
    //创建日志文件
    createLogFile(lfi);
    //输出日志
    int iWriteSize = writeToFile(lfi.fullLogFileName,alWrtLog);
    lfi.currLogSize  = iWriteSize;
    

    本文由美高梅开户发布于美高梅开户,转载请注明出处:日交易额百亿级交易系统的超轻量日志实现

    关键词: 美高梅开户