在traveltao的55天--暑期实习报告

在traveltao的55天–暑期实习报告

logo

实习结束,总是要做个总结的.一来呢,55天的实习生活如果只留下一些写的代码做见证总是显得单调.二来日后写简历编写项目经验实习经历的时候也好做个参考.

这篇文章虽然叫做在traveltao的55天但是我的写作顺序可不是按照日期来安排.本文按照项目编排,主要内容还是技术和项目相关的东西.

下面开始第一个主题吧!

JsParsec

JsParsec是刘鑫老师开启的Parsec系列项目的JavaScript实现版本.

Parsec是Haskell语言的一个函数式信息解析的库.关于Haskell语言,在实习之前我可算是知之甚少,对它的印象还停留在简单的函数式编程,Geek向等等这样一些抽象的概念上.当然我这么说并不是说我现在对Haskell就已经很熟悉了.

事实上,我到现在仍然没有系统的去学习过Haskell语言.但是这并没有给我实现JsParsec带来多大的困难,在经历了头几天的焦头烂额之后,我还是逐渐进入状态,越写越顺手,最后在两周时间内实现了JsParsec.

为什么我们需要Jsparsec

Haskell是一个函数式编程语言,借助函数式编程语言的一些特性,实现Parsec这样一个库可以做的非常简单并且漂亮.如下的一些特性在Haskell中是随手可得的,但是却是我们移植出Parsec的时候主要需要解决的问题.

  • Haskell的Monand语法提供了诸如Bind,Then,Over等的这样一些操作.使得我们在实际编程中可以将各个算子的运算结果非常方便的传来传去.
  • 不同于Python,Java,C/C++这样一些常见的命令式编程语言,在Haskell中,我们写出的语句都是一个个算子,具有Lazy的特性,并不是它一执行你就会得到你想要的结果,而是在你真正需要它的结果的时候,他才会把结果计算给你,这一特性可以和上面提到的Monand很好的结合.对于实现Parsec这样的库来说是一个先天优势.

Haskell中的Parsec到底有多强大呢?

在已有的项目中,使用的Parsec实现的一个典型就是Perl 6 Plugs,这也证明了Parsec完全具有实现一个完整的编译器前端的性能.因此完全不用担心Parsec的实用性.

那么我们直接在必要时使用Haskell写解释器,然后做成动态链接库链接进来不就行了吗?为什么还要费这么多精力给每个语言都去实现一个Parsec呢?

这么做是完全有必要的.主要原因如下

  • Haskell的学习成本非常高,能够熟练使用Haskell语言的工程师已经是具备足够的能力能够出国赚美金的,一般的小公司供应不起这样的人才.
  • 在开发项目时使用项目本身的语言可以降低项目的复杂度,简化开发的流程.试想一下,当你写一个golang的项目时,还要每次编译时去链接一个Haskell语言的库,是一个多么麻烦的事情.这种复杂度不仅仅体现在编译上,还体现在开发难度上,增加这种额外的东西将给项目的模块划分和单元测试添加不必要的烦恼.
  • 使用每一个开发人员都熟悉的语言来写解释器的实现,将大大的降低开发人员的学习成本,而且每个开发人员的能力能够得到最大的一个发挥.

所以我们需要使用各种常见的语言给Parsec做出实现.

Parsec的适用场景

其实实习的第二个项目就是一个具体的golang Parsec的应用,这个项目后面我们会详细说明.

  • 首先,一般程序员平常最有可能用到的功能就是信息流的解析了.在某些情况下,使用正则表达式是不值得的,但是使用一个独立的编译系统又不那么值得.这时候,值需要请出我们的Parsec,简单的写几个算子拼装一下就可以做到各种各样的解析.

  • DSL(领域特定语言).在云游道这样一家主要经营旅游的公司里面,业务模型的变更是家常便饭.时常会有各种打折促销,特惠等活动.那么每次业务模型开始变更的时候,当运营人员决定方案之后.接下来的工作就会落到开发人员头上了.这样的任务可不仅仅是在改几个前端的文字就可以解决的.每一个方案的修改都可能会设计到计价方案的修改,资金流的变更.那么事实上,这样的模式能不能改变呢?
    Parsec就是改变这种模式的探索之一.当我们有了一个非常强大的Parsec库之后,这样的尝试变得可能.一个普通的开发人员经过简单的api学习之后就可以快速的掌握.
    有了这些做基础,企业就可以为运营人员开发一款定制的DSL.而不用每次有一些细小的改动时就需要动用大量的开发力量.可以将开发人员解放出来.

  • 第三点就和我暑假做的项目相关了.当我们需要构建类DSL的自然语言有限交互的时候.Parsec就可以发挥出很强的作用.大家或多或少都见过某些网站的在线客服之类的应用,使用Parsec可以使得这类工作得到简化.

下面给出刘鑫老师的一篇文章,详细的说明了Parsec中涉及的各种算子的实现.具体的代码实现也给出链接,这里就不再赘述了.

这篇文章详细介绍了Parsec的rust版本的具体实现
Ruskell——Haskell Parsec 的 Rust 移植

这里是JsParsec的具体代码实现,使用nodejs环境做测试.
JsParsec–github

MTS消息节点

MTS消息节点是实习的第二个项目,使用golang编写,起源于公司CEO对每天中午吃午饭的流程的不满.这里需要说明,在公司里,每天的午饭都是通过外卖解决的.而每天采用的方式就是到了一个时间点,有一个专门负责统计点餐的同事就会在公司的微信群里面群发一个菜单,然后大家在微信群里面说自己想吃的外卖,最后由负责的同事统计之后发给餐馆.
在一个互联网公司使用这么简陋的方式显然是不合适的,尤其是人逐渐多起来之后,统计逐渐变得困难,聊天记录经常被刷走.在忍受了很长时间之后,我们的CEO强烈要求开发部解决这一问题.

要解决一类问题,而不是解决一个问题!

我向刘鑫老师建议开发一个微信企业号之后,刘鑫老师对我说了上面这句话.公司的官方服务号和企业号的功能,在我去之前仍然没有开发.只有运营部使用的推送号,不具备自动回复以及业务的处理功能.
但是我们只是简简单单的做一个微信号就算完了吗?
微博也会有自动回复的需求,QQ企业号客服也会有类似的需求,甚至以后公司的官网上面也会需要自动回复的客服,以及之后可能出现的林林总总的各种各样的平台.如果我们只是简单的搭建一个微信,就是一种简单的解决一个问题的思路.在这个情景中,我们真正需要的是解决不仅仅是一个微信的问题,而是一整套企业自动客服自动回话机器人的问题.

MTS的微服务架构模型

在MTS节点的搭建中,我们采用一种现在广为创业团队所采用的微服务架构模型.
所谓微服务,就是将一个服务器业务逻辑中的各个模块解耦.这种解耦不是在同一个软件设计中的逻辑上的解耦.而是在整个软件系统上的解耦.对于每个模块,我们可以不限于平台,不限于编程语言的设计每个模块.这种解耦不仅是逻辑的,甚至可能是物理上的解耦.我们可以将每个模块分开在不同的主机进行部署,而在节点中使用一些轻量级的通信方式.在这样的部署方式下,如何有效的统一管理这些分开部署的节点是个问题,但是已经超出本文的讨论范围.在实践中,我们主要是通过docker技术对其进行管理的.关于docker的更多信息,请参见下面这个博客.
快速理解Docker - 容器级虚拟化解决方案

具体到我们的MTS节点,到我实习结束前确定下来的一个架构就是将消息节点分为两个部分.第一个部分是gateway,从名字上面就可以看出来,这个节点的功能主要是用于处理另外一个节点—-insideserver传送过来的数据处理结果进行适应各个平台的格式转换并回送,还有就是对各个平台发过来的数据解析之后按照内部的消息格式重新封包发送,并带上一些附加信息.

面向socket字节流的实时state

上面提到过了,我们在采用这一套微服务架构时,在各个节点之间需要采用一些轻量级的通信方式,Restful是可选的一种方案.但是在这里我们选用更加轻量级的socket连接.在socket连接中,我们直接在socket中使用json文本通信.在这样的一种应用场景下,因为socket协议比较底层,所以很多工作就需要我们自己来做.
主要包括

  • 消息的正确性校验.其他传进来的信息并不总是可靠的.消息的传输过程中可能会出现差错、来源信息的json格式可能序列化错误、来源节点可能被恶意攻击使得json内容中所包含的协议信息出现问题.在一个设计严谨的分层架构系统中,这样的错误显然是应该被隔离在下层的.
  • 消息的分离与解析.在gateway和insideserver的一次通信当中,双方如果在一次连接中只发送一次消息.在服务器压力较大的时候,socket连接可能需要频繁的断开以及重新连接.这会带来很多不必要的压力.所以我们希望在一次会话中,我们只保持一个长连接.因此在一次长连接当中对多个json文本进行切割就显得比较重要了.

特别具体的处理方式甚至代码实现,在这里是不宜贴出来的.下面我会在有限的范围内尽可能的描述清楚.

上面两个问题可以统一用Parsec库来解析.因为服务器是使用golang写的,而golang中已经有刘鑫老师预先实现好的goparsec版本.所以这里可以直接拿来使用.
使用parsec需要一个state数据结构,原版parsec的state结构是基于字符串的.显然不能很好的满足我们的需求.为此,需要实现一个面向字节流的state结构,然而完成这样一件事情并非那么轻松.
首先要解决的就是字符的编码问题,在state中我们需要面向的是字节流.而json统一使用utf-8编码,如果把每个字节当成一个字符来处理的话,在处理边界的时候是可行的(json的边界符例如" {} []都是ascii字符,均小于一个字节),但是可能会带来某些意想不到的后果(某些多个字节的字符的某个字节可能正好是以上几个字符).
为了解决这个问题,在每次调用state的next()方法时我们需要一个四个字节的缓冲区,每次都从socket的一排数据中读取四个字节(utf-8最长的字符就是4个)放入缓冲区并从缓冲区中decode一个rune(golang中的字符结构)出来给state结构使用.由于state需要支持类似于seek这样的操作,所以我们还会需要一个缓冲区,将每次decode出来的字符放到这第二个缓冲区,然后将index+1;
因为parsec中对state的访问都是通过接口,所以我们新实现的state可以直接放到parsec中使用.
写好了state结构,接下来的工作主要就是利用Parsec写一个解析json的算子即可.在实现算子的过程中还碰到了无限递归的问题,采用手动lazy解决.

当消息正确的传送到上一层之后,要经过一个路由被正确的分发到相应的模块.
这是本文的最后一个主题,在向上写就要到达具体的业务逻辑了.

路由

路由的实现参考了beego框架的路由设计.使用一个容器映射了json文本中的meta字段和具体的处理模块.上层使用者调用route函数进行路由的注册.route函数需要两个参数.第一个参数是谓词,谓词有两种形式,第一种是一个函数,函数接收整个json实体并返回一个bool值来代表是否通过验证,谓词函数的原型有具体的类型定义.第二种接收一个meta标签,当用户采用这种方式的时候,route函数会使用闭包生成一个默认的谓词函数,这个函数的功能是将用户传递的meta和json实体的meta遍历的比较.只有一一匹配时才会返回true.

由于两种形式的路由注册最后都是采用统一的谓词函数实现,所以当一个请求到来时,路由的处理就变得非常简单了.
每当一个新的请求到来,我们要做的只是遍历路由容器里面的谓词和处理模块的集合.循环的将json实体丢进谓词里面,直到找到第一个返回为true的谓词,然后把json传给对应的模块拿到处理结果打包送回给gateway即可.

总的来说

总的来说,这次的暑期实习还是收获很大的,不仅写了上面的两个项目,还熟悉了pull request工作流、linux下的服务器部署、docker的基本使用等等.对以后学习和求职之路提供了一个指导作用.
文末感谢刘鑫老师收留我实习,感谢云游道公司提供的优越的实习生活环境。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值