• 日志的可视化---基本实现以及扩展


    现状:原先公司服务器的日志是用来事后调试。日志是根据时间按照键值对存储的本地文件。业务繁忙或日子一长,便会产生大量无效的文件。除此之外,别无他用。

    目标:从日志中提取有用的信息。从软件上可以获取一定时间内的IO性能;客户端请求的统计。从业务上则可以获取更多。

    这是大致的工作流程。

    image

    技能预备:

    python 3.4 文本分析和数据处理 快速开发,快速实现业务
    mongodb 键值对的存储 数据由于业务不同,相关的键值对也不一致,所以采用nosql
    mysql 最终记录的管理 具体业务的最终结果是相对固定的,同时也为了后续客户端的快速访问
    workbench 数据库table的维护 数据库维护工具
    c# && wcf 服务端,给浏览器提供数据支持 构建一个简单的服务端,支持浏览器的post方式获取数据
    Javascript && HTML 客户端界面和操作 html,javascript分离界面和业务,手工绑定数据
    Fiddler 测试wcf请求 测试客户端的请求和返回值,调试封装后的API

    如何安装这些软件,就不在这里详述了。

    测试环境:

    windows 2008 r2 c#,workbench
    ubuntu14.04 mongodb,mysql

    实现过程:

    1. 原始日志文件的输入

    日志基本说明:

    文件按照目录存放,日志内容是有时序。

    每条日志占用一行。

    它的格式如下:2015-11-09 07:37:09 Request sn=0 topic=RealTimeSEHKFeed6881 fields=<F>P1<D>6881<F>P2<D>CGS<F>P3<D>中國銀河<F>P4<D>中国银河<F>P6<D>7.730<F>P15<D>500

    时间 2015-11-09 07:37:09
    关键字 RealTimeSEHKFeed6881
    数据 <F>P1<D>6881<F>P2<D>CGS<F>P3<D>中國銀河<F>P4<D>中国银河<F>P6<D>7.730<F>P15<D>500

    实现文件列表:

    pyCollectMore.py 启动脚本,设置参数,捕获错误
    pyScanner.py 枚举目录的文件,并发pyResolve(由于开发时间有限,暂时做成单线程的方法调用了)
    pyStoreDefine.py 常量定义
    mongoAccesslib.py mongodb api封装
    pyResolve.py 利用正则表达式分析日志行,写入mongo数据库

    开发细节:

    1. 由于日志之间有时间顺序,所以同一个目录的文件需要依次遍历,但不同目录可以并行执行。

    2. 主要是日志topic对象的信息分解。

    原始数据如下:

    2015-11-09 07:37:09 Request sn=0 topic=RealTimeSEHKFeed6881 fields=<F>P1<D>6881<F>P2<D>CGS<F>P3<D>中國銀河<F>P4<D>中国银河<F>P6<D>7.730<F>P15<D>500

    分解1:获取时间,topic名称,原始键值对。

    正则表达式:(dddd-dd-ddsdd:dd:dd)s(?:Request|Update)ssn=0stopic=\([S]+)\([S]+)sfields=(.+)

    分解2:键值对。

    正则表达式:<F>([^<]+)<D>([^<]*)

    整理和合并日志记录:

    对于一个topic而言,包含2部分:命名空间和topic对象名称。

    在之前的示例中“RealTimeSEHKFeed6881”---命名空间:“RealTimeSEHKFeed”;对象是:“6881”。

    所以处理完成全部的日志行后,需要合并相同命名空间下的对象名称。

    2. 中间结果的存储

    在之前任务中,我们可以获取2部分的信息:topic的明细以及topic的清单。

    这时候需要在mongodb中创建2个表。

    tb_topic_list 记录已收集的topic对象名称
    tb_命名空间 根据日志收集后的命名空间,创建对应的表,存放每个topic的全部明细

    这时候使用nosql的好处了,事先不用定义表以及相关字段。

    tb_topic_list字段定义:

    _id 记录id
    tps 新建表名称,后续调度程序使用
    tpn 对象名称
    tpu 是否已经处理过

    tb_命名空间(根据上下文应该是tb_RealTime_SEHKFeed)字段定义:

    _id 记录id
    nss 记录序列号(python维护)
    nstp topic名称
    nsd 时间
    nsn 命名空间
    业务的字段P1,P2 ..

    3. 任务调度和数据分析

    实现文件列表:

    pyTaskMng.py 启动脚本,定时查询待处理的topic,并发执行pyReduce(topic数量很多,所以这里是并发进程)
    pyReduce.py 从mongodb获取已经分类的topic,根据时间顺序,计算总数,最大值,最小值以及记录明细
    pyStoreDefine.py 常量定义
    mongoAccesslib.py mongodb api封装
    mysql.connector mysql官方引用,在reduce之后写入计算的结果

    开发细节:

    1. pyTaskMng运行在不同的机器上,定时检查tb_topic_list。

    如果有记录上, 并发交给进程池,由pyReduce负责获取topic对应的全部记录,数量大的时候,批量获取。

    2. pyReduce分批获取topic的明细。

    根据业务规则:

    P1 code
    P2 名称
    P3 big5名称
    P4 gb名称
    P5 价格
    P6 数量

    由于这个项目属于demo,所以我们假定以下的规则:

    开盘价 每个股票第一笔价格
    收盘价 每个股票最后一笔价格
    最高价 初值等于开盘价,比较当前价后判断是否更改
    最低价 初值等于开盘价,比较当前价后判断是否更改
    变化次数 累计记录数
    成交量 每个记录报价数

    pyReduce并发执行,执行完成后更新tb_topic_list。

    计算的结果存入mysql表。

    4. 最终结果的存储

    鉴于nosql在统计查询上有些限制,所以使用mysql作为统计信息的存储设备。

    secDaily表记录统计信息

    字段 类型 说明
    tID int 主键
    code varchar(45) 股票code
    secName varchar(32) 股票名称
    ns varchar(64) 命名空间
    secTime datetime 日期
    dayNum int 逻辑天
    openPrice double 开盘价
    closePrice double 收盘价
    highPrice double 最高价
    lowPrice double 最低价
    changeTotal int 变化次数
    exQty double 成交量

    secDetail表记录时间戳

    字段 类型 说明
    sdID int 主键
    secDailyID int 主表id
    seq int 序列号
    detailTime datetime 时间戳
    price double 价格
    qty double 数量

    5. 提供数据的服务端

    为了快速显示,这次使用wcf作为服务端。

    提供5个接口:

    获取指定sec列表总数  
      select count(tID) from secDaily where secCode like '%{0}%' or secName like '%{0}%';
      http://localhost:8000/DEMOService/secSum
     

    R:  {"filter" :"1"}   
    S: {"cookie":0,"total":279}

    获取批量sec列表  
     

    select secCode,secName,ns,DATE_FORMAT(secTime,'%Y-%m-%%d %H:%i:%S'),openPrice,closePrice

    ,highPrice,lowPrice,changeTotal,exQty 

    from secDaily

    where secCode like '%{0}%' or secName like '%{0}%' 

    order by  secCode limit {1},{2};

      http://localhost:8000/DEMOService/secbatch
     

    R: {"filter" :"1", "offset": 0,"count":10}                  
    S:{"cookie": 0,
             "items": [
                 {
                     "changeTotal": 21728,
                     "closePrice": 104.1,
                     "exQty": 10864000,
                     "highPrice": 104.1,
                     "lowPrice": 104.1,
                     "ns": "RealTime-SEHKFeed",
                     "openPrice": 104.1,
                     "secCode": "1",
                     "secName": "CKH HOLDINGS",
                     "secTime": "2015-11-09 09:51:11"
                 }
             ]
         }

    获取指定sec明细总数  
     

    select count(sdID) from secDetail 

    inner join secDaily on secDetail.secDailyID = secDaily.tID

    where secDaily.secCode = '{0}';

      http://localhost:8000/DEMOService/detailSum
     

    R:{"code" :"1"}
    S:{"total":21728}

    获取指定sec批量列表(关键是item的时间)    
      select seq,DATE_FORMAT(detailTime,'%Y-%m-%%d %H:%i:%S')
    from secDetail 
    inner join secDaily on secDetail.secDailyID = secDaily.tID
    where secDaily.secCode = '{0}'  order by seq  limit {1},{2};  
      获取指定sec批量列表
     

    R:  {"code" :"1", "offset": 0,"count":2}
    S:{ "cookie": 0,
            "items": [
                {
                    "detailTime": "2015-11-09 09:51:11",
                    "seq": 45
                },
                {
                    "detailTime": "2015-11-09 09:51:11",
                    "seq": 46
                }
            ]
        }

    获取指定时间内统计  
      create procedure getRange(codeRange varchar(45), beginTime varchar(20), endTime varchar(20),rangval int)
    需要构建临时表,填充空记录
      http://localhost:8000/DEMOService/rangeTotal

    客户端通过浏览器使用POSt方式查询数据.所以服务器请求需要特别处理。

    [ServiceContract(Name = "RESTDemoServices")]
    public interface IRESTDemoServices
    {
        [OperationContract]
        [WebInvoke(UriTemplate = "secSum", Method = "*", BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
         SecSumResponse GetSecSum(SecSumRequest req);
    }

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single, IncludeExceptionDetailInFaults = true)]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [JavascriptCallbackBehavior(UrlParameterName = "callback")]
    public class RestDemoServices : BaseEntry.IRESTDemoServices
    {
        public SecSumResponse GetSecSum(SecSumRequest req)
        {
               
                if (WebOperationContext.Current.IncomingRequest.Method == "OPTIONS")
                {
                    WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", "*");
                    WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Methods", "POST");
                    WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Accept");

                    return null;
                }
                WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", "*");
                SecSumResponse resp = new SecSumResponse();
        }
    }

    6. 客户端实现 Javascript, chart.JS

    实现文件列表:

    main.htm 界面布局
    bigDataStyle.css css
    jquery-1.11.2.js jquery
    mainImpl.js 界面实现脚本
    SecDailyApi.js api封装脚本
    table.js 表格脚本
    Chart.JS Chart.JS引用

    效果图

    image

    测试文件:ch1.fxl

    7. 接下来可以做的任务

    这个项目属于demo性质。下一步预备使用java改写python的实现,scan和reduce部分的存储可能考虑hadoop实现存储和分发。

    源代码:https://github.com/febwave/pybig

  • 相关阅读:
    流媒体服务器搭建
    netcore问题总结
    Linux(Debian)网卡设置
    windows服务器让WEB通过防火墙的问题
    经典算法一 --- 过桥问题
    MySQL字段属性介绍
    什么是流程控制
    终于决定了,转行。
    浅析Openflow
    JavaScript 中 this 的详解
  • 原文地址:https://www.cnblogs.com/febwave/p/5036664.html
Copyright © 2020-2023  润新知