云盘服务端从nodejs 专项 java 相关复盘

September 24, 2015

说明

这里是对云盘 node.js 转向 java 做的一个客观总结,个人平时也会经常使用 nodejs,go,python,C#,java 进行开发,比较熟悉各个工具链,所以也并没有语言,框架上面的歧视。

云盘 web 前后端,服务端开发是我在5月份接手的,当时所有端是基础终端来负责,服务化的事情是云计算组负责,5月份的时候全部移交到我们负责,在这之前云计算组已经和我们进行过几次碰头,已经使用 python tornado 开发了一些服务端的事情。接收到我们组之后,由于我们希望在 nodejs 上面来进行更多地技术积累,经过几次评审放弃了我当时在维护的 python 版本,转向 nodejs 进行开发。

云盘概览

image.png

服务端功能模块

0FCC7A09-90DA-4C48-965B-AAD0696FEBCB.png

整个架构为:web 前端(自行开发框架进行开发),web 后端(koajs),服务端(koajs,postgresql),以及 pc,mac,android,iphone 等终端

经过两个月的工作,基本开发出来一个可用的 nodejs 版本服务端,在这些日子的开发遇到了下面这些问题:

  1. nodejs 进行事务管理并没有很好办法,必须要在服务层透传一个 transaction 到 dao 层,整个代码写到后期就基本没办法维护,哪怕抽离出来 db 层也只是将问题放 db 层没办法很好解决问题,牵一发动全身,同时基础的 mysql 包事务管理也有问题,高并发下并不能真正的处理好事务,会抛出奇怪的错误,而且无法定位,无法复现
  2. 每次重构必须通过单元测试,功能测试进行覆盖,很多在编译期就能解决的问题必须通过测试来覆盖,前期开发很爽,后期重构火葬场
  3. 整个服务化领域并没有很好的解决方案,很多知名三方服务没有一个稳定包进行开发,比如存储使用 openstack 的时候我就必须自己查看 openstack 接口给三方包打补丁
  4. 和公司服务化大潮流格格不入,公司在今年下半年开启使用 java 进行服务化,很多提供 jar 包,而 nodejs 必须在 wiki 中寻找接口然后自行开发
  5. 云盘服务端会做很多数据处理的工作,属于高 cpu 工作,上线 node 版本之后,会发现 cpu 会经常不正常波动,后期我使用 c++开发一些模块进行处理,这个问题才得到缓解,但相应的每次重构的时候都会很麻烦,丧失了 nodejs 快速开发的乐趣
  6. nodejs 的回调写法是反人类的,使用 koa.js 也仅仅是缓解了这个问题,但是还是会遇到很多 js 本身的坑,当时 node 还是0.12.7这个版本,在大型项目中定位问题也会很麻烦

所以经过实际检测发现目前这个阶段使用nodejs开发服务端的方式基本是不对的,我们需要重新评估整个服务端

快速的调研了国内外知名网盘的架构情况,最后得到两个比较靠谱的方案,一个是使用 golang 进行开发(dropbox,七牛),另一个就是使用 java 进行开发,golang 很快被我们否掉了,因为还是回碰到上面的1,3,4的问题,很多包需要自己打补丁,云盘当时的情况是服务端只有我自己一个人,同时还要兼管 web 端的事情,时间不够,决定使用 java 进行开发,同时我们重新设计了 java 版本服务端架构

架构

架构 1

image.png

架构 2

image.png

整个架构被我们认为的拆分成了各个微服务,通过 kafka 进行一步消息通知,通过 kafka 进行全局事务维护,保证整个系统是最终一致的。

在这里,我们使用了spring mvc,和spring boot进行微服务的处理,几个关键的服务被我们合并到微服务网关里面进行统一的处理,内嵌了jetty作为服务容器。

整个项目大概开发了两周的时间,开发完成上线第一个bate版本,同等功能的代码量同比nodejs版本多了一些,主要在配置文件上,但是可维护性更强,同时模块间相对独立,各层之间相对独立,可以放心进行重构,快速添加新功能,通过spring自带的事务进行事务管理,通过注解的方式在代码里面可以不去管理事务,同时可以很好的利用公司提供的服务比如mafka,tair等。可以说在nodejs当中遇到的问题基本上都解决了,不好的地方大概是资源消耗上,我们多开了一台服务器,同时java通过mvn编译时间太长,不能像nodejs那样所见即所得。

开发方式上的不同

不同的语言有不同的编程哲学,在nodejs和java里面能明显感受出来这样的变化

1,异常处理 js:由于js是弱类型的,所以我们可以使用函数返回值的方式进行错误处理,调用函数去判定是不是存在error对象,存在就返回给终端错误,同时我们还可以抛出异常,然后进行全局异常拦截器中间件进行拦截 java:java是强类型的,无论是业务错误,还是别的错误,通常我们都使用抛出异常的方式进行处理, 2,数据处理 js:js本身是面向对象也是面向函数的,可以大量借鉴函数式编程的优点,在数据处理上借助lodash很容易写出下面的链式函数(熟悉.net的朋友肯定感觉下面的和linq基本一样)

_.chain(arr).filter((item)=>{return item.name.indexOf(dep_name)!==-1}).map(item=>item.count).reduce((total,n)=>{return total + n}).value()

其实上面并不是“纯函数式”编程,而是以对象/容器为中心的串联,有些像 jquery 对象的链式调用,不过也比java得写法好的多,同时不必担心效率问题,我们可以优化这个链上的任何一个函数,从而达到优化速度的目的,而且整个过程是懒加载的,如果真的使用纯函数编程【ramda.js】,会compose fuctor等概念进行一些合并,直接一次算出结果。 java:if,else,while,for慢慢写吧。。。。别忘了这个过程中还要在声明很多中间过渡需要的承载类,真的有点浪费生命,这里scala做的就很好,最开始其实想使用scala +play框架开发的,如果不是人力上的问题。

3,日志管理 js,我发现普遍对于日志并没有什么特殊的做法,大家通常都是通过pm2查看access log,npm上面有个一log4j的库,不过用法没有java灵活,需要自己在需要的地方埋 java,日志这里通常使用log4或者logback之类的,而且不需要自己处理,只需要声明一个全局的Logger就行,这样还可以动态的调整输出类型,也可以调整输出位置

4,部署 js,通过ops系统,配置两个脚本,扔到服务器就可以 java,需要编译而且和不同版本还有关,而且现有 ops 使用 docker 编译会重新下载所有依赖,非常慢,中间也会遇到很多问题,基本需要和 sre 进行沟通解决。不过新版基于 docker 的发布系统已经在研发中了,这个问题应该很快得到解决