一个小系统架构的后知后觉

由于公司的培养计划,今年8月中旬我rotate到了一个新的团队,新团队的老大找了4个人(包括我)决定做一个辅助trader分析决策的系统,一些指标的图表显示,相似度推荐和预测(涉及到业务,不能多说)。花了一个月进行机器学习的知识储备和需求分析后,开始了短暂的架构之旅。

系统划分

在设计一个完整的系统的时候,需要将其拆分成多层,每层系统独立完成一个功能,之间不会有影响,可以独立进行技术选型和架构设计,各层之间用rpc,restful,或者消息中间件来通信,符合高内聚低耦合的特点。
在进行各层技术选型时,按序考虑如下:

  • 符合公司政策
  • 适用于业务
  • 社区活跃(文档完整)
  • 学习难度与团队成员熟悉程度

我们将系统核心拆分成了4层:数据处理层,模型训练层,模型接口服务提供层,数据服务层。
"图1"

数据处理层

我们系统的数据都是从上游系统来的,什么形式现在还没正式确定(可以和上游系统商议),公司内部最常用的形式是文件feed,。在这里,进行数据转换工作,我们把数据主要分为两部分,一部分是用于指标显示的(股票前10天的均价,客户成交额等等),存于DB。另一部分用于模型训练,这部分数据可能存DB也可能存为文件格式。
技术选型: spring batch vs spark streaming。

  • spring batch: 企业批处理框架,公司内部常用,但不支持实时,可以用来做伪实时,定时(每小时)调度job,不支持分布式,无法支持大量的数据
  • spark streaming: 分布式实时运算框架,支持海量数据,准实时,对一个时间段内的数据收集起来,作为一个RDD,再处理。处于spark生态区,与其类似的是storm,一个纯实时的框架。

因为本系统每日的数据量不到百万,所以暂时用spring batch来做伪实时的数据处理。
"图2"

模型训练层

在系统上线前我们会通过数据预处理->特征提取->模型训练->测试的反复迭代训练好模型,在这里会采用online learning的算法,训练好的模型存为文件(方便以后的还原),并且将选好的特征存到数据库中,这样日常新来的数据就可以直接进行过滤转换了。每隔一段较长时间会重新进行特征的选取。
难点:产品上线后模型的准确度测试方法还没想到
技术选型: sklearn vs tensorflow vs mllib

  • sklearn:上手简单,不支持分布式,传统式机器学习库,文档全,比较适合学习
  • tensorflow: google的深度学习框架,支持分布式,文档全
  • mllib: spark生态圈的,支持分布式,但库很少

我们的历史数据达到亿条记录,并且tensorflow前景个人感觉很好,所以就用它了
"图3"

模型接口服务提供层

这里会用python flask起一个web server去还原tensorflow的模型,并且接受数据服务层的请求来进行模型的调用(预测,相似度判断等)。
这是最尴尬的一层,因为我实在没找到除了用python调用tensorflow api以外其它还原模型的方法。如果是以脚本的形式,比如在node中调用一段python代码去调用模型,那么每一个request过来,都得去还原模型一次,这显然是不行的,换成http server的话,可以将其缓存在内存中,只有当模型更新的时候才会去重新还原一次模型。

数据服务提供层

这里接受gui的请求,从数据库中去拿数据或者调用模型接口服务。因为这层基本没有逻辑和计算操作,大部分为数据库io操作,所以我们采用node。
我们在这层采用微服务的思想(关于微服务的讨论见:https://www.zhihu.com/question/37808426),由于我们的系统主要包含两部分信息client(客户)和security(证券),两部分信息的显示内容以及用户可进行的操作是不同的,所以将security和client各独立成一个service进程,gui的请求会发送到router上,api router根据url的匹配将请求转发给client和security service。以后扩展,可以在router前个ngnix load balancer, 在service上用pm2管理,实现一个master worker的load balance,甚至还可以引入docker。
关于token验证,如果只在gateway router上做权限认证的话,那么便无需登录可以直接访问service api了,所以要在service上做权限认证。这里会涉及到一个token共享的问题,因为每个service是独立的进程,不会共享资源,现在是存数据库,每个service都会维护一个token的list,一个request到了以后,判断是否存在内存,不存在就去数据库比较,数据库有并且这个token是有效的就添加入这个list,以后会调用公司的权限认证服务,它可以判断该token是否仍有效。这样每个service都会维护着同一份token list了。"图4"
"图5.1"
"图5.2"

数据库

数据库我们采用的是mongo,这个系统不需要事务的回滚,而且对数据量的存储以及读取性能有一定要求,mongo本身就支持集群,关于关系型数据库以及非关系数据库之间的比较太多,不赘述。
"图6"
"图7"

GUI

这系统类似一个图表系统,感觉什么框架都行,本来我们想用react的,然后在搭建的时候react出了license的问题,因为公司的policy(系统代码永远不对外公开),就转为angular了,而且我们还有人对angular较熟悉,所以license问题解决了,我们也没改变。

POC阶段

为了快速立项,所以POC阶段追求简单,我们会直接把数据mock进数据库。
"图8"

后续

目前刚把router以及两个service公用的私有npm包写完,以上只是对这个系统的构想,后续在实践中还会不断更正,而且我现在还挺菜的,所以肯定有很多不完善的地方,尤其在机器学习那块,经验几乎为0,就靠一个月的强化学习。肯定有很多不足,但纪念自己的第一次真正有意义架构。

参考资料:

http1.x与http2

http1.x

Http: 文本传输协议(HyperText Transfer Protocol), 位于计算机网络五层中的应用层,基于tcp之上.

http 瓶颈与优化

长连接

在http/1.1中引入keepalive后,不需要在每个request都要进行tcp握手了,请求状况从图1演变成了图2
"图1"
"图2"

http pipeline

持久http允许重用现有的连接,但意味着要遵从FIFO排队顺序:请求调度->等待完整响应->发送下一个请求,http pipeline是把多个HTTP请求放到一个TCP连接中一一发送,而在发送过程中不需要等待服务器对前一个请求的响应。 如图3:
"图3"
但是由于http/1.x协议的限制:不会严格序列化返回的响应,http/1.x不允许来自多个响应的数据在相同的连接上交错(多路复用),从而使每个响应的字节在下一个响应之前被完全返回。如图4:
"图4"

  • html与css请求并发,html先于css
  • 服务器开始处理。html: 40ms, css: 20ms。
  • css请求先完成,但必须等待,知道html响应完成
  • html响应完毕,发送css
    即使客户端发出了两个请求,并且可以首先使用css资源,但服务器必须等待htm响应完成后才能发送css,这被称为线头阻塞(Head of line blocking),如果第一个请求无限挂起,背后的请求都必须等待。
    在工程中,由于缺乏多路复用,http pipeline有很多影响,所以大部分浏览器都是禁用的。

    多个TCP连接

    由于在http/1.x中没有多路复用,所以浏览器使用多个TCP连接(每个主机一般为6个),但是连接的管理带来了额外的内存和CPU开销。

    域分片(Sharding)

    由于http/1.x对客户端tcp连接数目有限制,对于一些大型网站,这是不够用的,所以可以把资源放在多个子域上,由于主机名不同,这样做等于隐式地增加了浏览器的连接限制。但是这样做会带来额外的DNS查找,为每个额外的套接字消耗资源,而且要手动管理资源分配的地点和方式

    减少请求数目

    spriting

    Spriting是一种将很多较小的图片合并成一张大图,再用JavaScript或者CSS将小图重新“切割”出来的技术。网站可以利用这一技巧来达到提速的目的——在HTTP 1.1里,下载一张大图比下载100张小图快得多。

    内联(lnlining)

    Inlining是另外一种防止发送很多小图请求的技巧,它将图片的原始数据嵌入在CSS文件里面的URL里。

    .icon1 {
        background:url(data:image/png;base64,<data>) no-repeat;
    }
    .icon2 {
        background: url(data:image/png;base64,<data>) no-repeat;
    }
    

    拼接(Concatenation)

    大型网站往往会包含大量的JavaScript文件。一些前端工具可以帮助开发人员将这些文件合并为一个大的文件,从而让浏览器能只花费一个请求就将其下载完,而不是发无数请求去分别下载那些琐碎的JavaScript文件。
    这三个技术的缺陷:

  • 需要额外的预处理,部署注意事项和代码
  • 任何一个单独的文件单一更新都会重新下载整个文件,导致高字节开销
  • 只有当传输完成后,javascript和css才被解析和执行,会延迟应用程序的执行速度
  • 对于spriting,由于浏览器必须解码整个图像将其存在内存中,图像会占用大量的内存

    头部开销

    现在,每个http request的header中都一般会占据大量的字节,尤其是cookie,http协议是无状态的,所以一般request里面都会带有cookie,这造成了巨大的开销。

    HTTPS

    HTTPS = HTTP + TLS,HTTPS多了tls握手的消耗。

HTTP2

SPDY

Google开发的一个协议,http2基于基于SPDY/3草案进行一些修改之后发布了http2的draft-00。

二进制分帧层

HTTP2 是一个二进制协议, 所有性能增强的核心在于新的二进制分帧层。HTTP/1.x协议以换行符作为纯文本的分隔符,而HTTP/2将所有传输的信息分割为更小的消息和帧,并采用二进制格式对它们编码。
"图5"

  • 数据流:已建立的连接内的双向字节流,可以承载一条或多条消息。
  • 消息:与逻辑请求或响应消息对应的完整的一系列帧。
  • 帧:HTTP/2 通信的最小单位,每个帧都包含帧头,至少也会标识出当前帧所属的数据流。
  • 所有通信都在一个 TCP 连接上完成,此连接可以承载任意数量的双向数据流。
  • 每个数据流都有一个唯一的标识符和可选的优先级信息,用于承载双向消息。
  • 每条消息都是一条逻辑 HTTP 消息(例如请求或响应),包含一个或多个帧。
  • 帧是最小的通信单位,承载着特定类型的数据,例如HTTP标头、消息负载,等等。来自不同数据流的帧可以交错发送,然后再根据每个帧头的数据流标识符重新组装。

    HTTP2特性

    多路复用

    每个单独的http2连接都可以包含多个并发的流,这些流中交错的包含着来自两端的帧。流既可以被客户端/服务器端单方面的建立和使用,也可以被双方共享,或者被任意一边关闭。在流里面,每一帧发送的顺序非常关键。接收方会按照收到帧的顺序来进行处理。流的多路复用意味着在同一连接中来自各个流的数据包会被混合在一起。所以级联文件、image sprites 和域名分片都不必要使用了。

    数据流优先级和依赖性

    HTTP/2 标准允许每个数据流都有一个关联的权重和依赖关系,,我们可以根据用户互动和其他信号更改依赖关系和重新分配权重。这样当用户滚动一个全是图片的页面的时候,浏览器就能够指定哪个图片拥有更高的优先级。或者是在你切换标签页的时候,浏览器可以提升新切换到页面所包含流的优先级。

    流控制

    每个http2流都拥有自己的公示的流量窗口,它可以限制另一端发送数据。HTTP/2未指定任何特定算法来实现流控制。不过,它提供了简单的构建块并推迟了客户端和服务器实现,可以实现自定义策略来调节资源使用和分配。

    头压缩

    HTTP/2 使用 HPACK 压缩格式压缩请求和响应标头元数据,这种格式采用两种简单但是强大的技术:
  • 这种格式支持通过静态 Huffman 代码对传输的标头字段进行编码,从而减小了各个传输的大小。
  • 这种格式要求客户端和服务器同时维护和更新一个包含之前见过的标头字段的索引列表,此列表随后会用作参考,对之前传输的值进行有效编码。

    服务器推送

    HTTP/2 打破了严格的请求-响应语义,服务器可以对一个客户端请求发送多个响应,服务器可以向客户端推送额外资源,而无需客户端明确地请求。

参考资料:

JS 正则

string对象中关于正则表达式的方法

1. search()方法

该方法用于检索字符串中指定的子字符串,或检索与正 则表达式相匹配的字符串。
基本语法:stringObject.search(regexp);

2. match()方法

该方法用于在字符串内检索指定的值,或找到一个或者多个正则表达式的匹配。该方法类似于indexOf()或者lastIndexOf(); 但是它返回的是指定的值,而不是字符串的位置;
基本语法:stringObject.match(searchValue)或者stringObject.match(regexp)

3. replace()方法

该方法用于在字符串中使用一些字符替换另一些字符,或者替换一个与正则表达式匹配的子字符串;
基本语法:sstringObject.replace(regexp/substr,replacement);

3. split()方法

该方法把一个字符串分割成字符串数组。
基本语法:stringObject.split(separator,howmany);

RegExp对象方法

1. test()方法

该方法用于检测一个字符串是否匹配某个模式;
基本语法:RegExpObject.test(str);

2. exec()方法

该方法用于检索字符串中的正则表达式的匹配。
基本语法:RegExpObject.exec(string)

练习

  • 匹配价格: /^\d*(.\d{0,2})?$/
  • 身份证号码的匹配: /^(\d{14}|\d{17})(\d|[xX])$/
  • 单词的首字母大写:

    let replaceReg = (reg, str) => {
    str = str.toLowerCase();
    return str.replace(reg, m => m.toUpperCase());
    }
    let reg = /\b\w/g
    
  • 验证邮箱的正则表达式:/^ [0-9a-zA-Z]+(@[0-9a-zA-Z])+(.[0-9a-zA-Z_])/
  • 敏感词过滤:

    let str = '我草你妈哈哈背景天胡景涛哪肉涯剪短发欲望';
    let regExp = /草|肉|欲|胡景涛/g;
    let result = str.replace(regExp, function(match) {
    let len = match.length;
    let str;
    switch (len) {
        case 1:
          str = '*';
          break;
      case 2:
          str = "**";
          break;
      case 3:
       str = "***";
          break;
      default:
          str = '****';
      }
    return str;
    });
    console.log(result); //我*你妈哈哈背景天***哪*涯剪短发*望
    
  • 改字符串为驼峰命名:

    const str = 'get-element-by-id';
    function camelCased(str){
        let regExp = /-(\w)/g;
        return str.replace(regExp, (match, p) => p.toUpperCase());
    };
    console.log(camelCased(str));
    
  • 判断连续重复字母:

    let str1 = 'abc3d4e5';
    let str2 = 'aab2c3';
    let regExp = /([a-zA-Z])\1/;
    console.log(regExp.test(str1));//false
    console.log(regExp.test(str2));//true
    
  • 提取链接

    let reg = /(<a[.\s]*href\s*=\s*')(http:\/\/.*)('>\w*<\/a>)/gi;
    let html = "<div><a href='http://www.baidu.com'>222</a><p>344</p></div>";
    html.match(reg);
    console.log(RegExp.$1);
    

本文摘自: [http://www.cnblogs.com/tugenhua0707/p/5037811.html#_labe9][1]

前言

写博客的原因

本来自己暂时不想写博客的,因为觉得自己现在还是很菜,学都学不完,写博客实在是花时间,而且还可能误导别人,所以一直都没有写技术博客。但是最近发生的几件事,让我有了写技术博客的念头:

  • 看博文看了以后经常过两天就遗忘了
  • 在公司经常要和team share一些技术知识点,每次都要准备挺久
  • 由于公司security policy,所以自己做的项目外部公司的人是看不见的,以后面试无法直接地证明自己的能力

所以才决定开始自己的技术博客生涯,顺便锻炼下自己的表达能力以及消遣现在的单身时光

关于博客的几点声明

  • 技术内容都是来源于自己的理解,由于自身水平有限,不能保证100%的准确率,忘批评指正
  • 写博客时肯定会参考其余的技术文章,本人一定会注明出处