4月21日,有赞举办了第一届“有赞技术开发日”的活动,我作为分享讲师,分享了有赞最近一年在 Node 这一块的实践经验。但由于分享时间有限,我也只能把最重要的内容拿出来和大家分享,所以这个周末就花了几个小时时间,结合那次的分享,并完善了其中的一些内容,写了这篇文章,希望可以给大家带来新的启发。
有赞最早的一个比较完整的 Node 项目是公司内部的一个管理系统,这个系统是用 Node 全栈开发的,主要包括一个给 HR 用的员工管理系统和给小伙伴用的 APP。就像大多数公司一样,我们第一个 Node 项目也是直接用 Koa,然后整合一些开源的中间件,这样就快速的把项目搭建起来了。
这个项目做了半年之后,我们把 Node 该踩的坑基本也都踩了一遍,所以我们就开始尝试在对外产品上使用 Node了,我们第一个尝试改造的项目是公司的官网,这是最简单的一个项目,基本没什么大的风险。
第二个项目我们不可能再按照之前的方式,简单用 Koa 加上一堆中间件的方式来搭建项目了,因为已经有了之前的经验,所以我们就整理了下这一套方案,抽离出了一个项目模板,每个新项目只要把这个模板克隆下来,然后改一下配置,就可以快速搭建出一个新的项目来。
项目多了之后,这种方式弊端很快就显现出来了,因为模板代码和业务代码是耦合在一起,如果要改模板生成的代码,只能每个项目手动更新,而随着时间的推移,越来越难保持同步了,每个项目的目录结构和代码风格可能也会变得非常不一样,所以,解耦框架代码和业务代码就非常重要了。所以我们就在脚手架模板的基础上抽离出了一个框架叫 Astroboy(阿童木),这个框架是在 Koa 的基础上封装的,这样,每个项目都基于这个框架开发,如果框架更新了,项目也只需要更改下框架的版本号。
很多项目都开始用 Node 了,新的问题又出现了,因为每个产品的业务场景都不一样,对框架的需求也都不一样。例如某个中间件,产品 A 可能需要,而产品 B 可能根本不需要这个中间件,而这个时候的框架又不支持定制改造。所以对框架来说,又提出了新的挑战,所以在今年年初,对框架做了一次大的重构。
这次重构在阿童木 1.0 的基础上,加入了很多新特性,主要有以下几点:
首先提供基于 Astroboy 定制上层框架的能力,如下图所示,Youzan Base Framework 是在阿童木的基础上定制的一个有赞最基础的 Node Web 框架,这一层主要集成了一些有赞最基础的服务,像:
有了 Youzan Base Framework 后,我们就需要在上面开发业务了,这个分两种业务场景:对于一些简单单一的业务,直接继承 Youzan Base Framework 开发就可以了;而如果是一些复杂的业务,就可以先在 Youzan Base Framework 的基础上,定制出一个业务框架,像我们有赞原先有一个超大的 PHP 项目(我们叫 Iron),那么服务化拆分后,Node 就承担了原先 PHP 的部分,所以我们新先定制了一个业务级的框架叫 Iron Base Framework,然后再按照业务模块(交易、店铺、用户、营销)拆分成多个子项目。
其次是支持插件化,关于这一点,可查看下面关于插件的说明。
以上介绍了有赞 Node 基础框架迭代和演变的过程,下面主要介绍下阿童木2.0 框架的几个核心概念
应用 Application 的概念很好理解,在这里应用就可以理解成一个项目,它是从框架继承下来,并且实例化之后的一个实例,应用也是由一个一个插件构成的。
Astroboy 框架是在 Koa2 的基础上封装的,关于框架的概念,这里就不再做过多的介绍了。
插件化是软件设计中一个很重要的思想,很多软件像 Eclipse 都支持这样的特性,插件化可以让我们的系统解耦,每个模块做到独立开发,而模块之间又不会相互影响,这样的特性对于大型项目来说是非常重要的。
插件化是 Astroboy 框架中最核心的一个实现,它是服务(Service)、中间件(Middleware)和工具函数库(Lib)等的载体,它本质上还是 NPM 包,只不过是在 NPM 包的基础上,做了更深层次的抽象。基于 Astroboy 的应用,就是由一个一个的 Plugin 组成的,Plugin 就是我们手中的积木,通过 Astroboy 的框架引擎把这些积木组织在一起,就形成了系统。
那么插件跟普通的 NPM 包有什么区别呢?
插件约定了目录结构,这样每个插件看起来都是类似的,这对于团队的协作是非常重要,如果每个模块看起来都不一样,那么团队的协作成本就会很高。 应用启动后,插件的代码是自动注入到整个应用的,只需要在插件的配置文件里面开启这个插件即可。
一个插件可以包含哪些信息?
插件的管理
'astroboy-cookie': {
enable: true,
path: path.resolve(__dirname, '../plugins/astroboy-cookie')
}
enable 设置成 true 就可以开启这个插件,path 表示插件的绝对路径,这种一般适合于还在快速迭代中的插件,如果插件已经很稳定了,你就可以把这个插件打包发布成一个 NPM 包,然后通过 package 声明你的插件即可,如下代码所示:
'astroboy-cookie': {
enable: true,
package: 'astroboy-cookie'
}
随着公司业务的发展,网站应用的规模不断扩大,垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键,所以在这个时候,分布式服务架构就势在必行了。
在介绍技术栈选择之前,先讲一下公司的一些技术背景。
在公司成立初期,为了能够快速开发,把产品快速做出来推出市场,所以我们选择用 PHP 语言,我想这也是大多数创业公司的选择。而随着业务的发展,PHP 越来越难处理复杂的业务。
所以等到了一定时候,我们开始做服务化拆分,那么首先考虑的就是底层技术的选择,我们从下面几点考虑:
对于 Node 层,我们的定位是一层很薄的中间层,Node 这一层不会过多地处理业务逻辑,业务逻辑全部都交给 Java 来处理,它只负责下面三件事情:
而对于 Java 这一层,就需要承担业务逻辑以及缓存等复杂的操作,这里就不做过多的介绍了。
那么服务化拆分之后,首先要解决的一个问题是:Node 如何调用 Java 提供的接口。首先,我们想到的就是 HTTP 的方式,这里说明一下,我们公司采用的分布式服务化框架是阿里开源的 Dubbo 框架,而 Dubbo 框架本身是支持通过添加注解的方式生成 Restful API 的,所以在初期,我们就是采用这个现成的方案。
而随着应用数目的增加,这种方式的弊端也逐渐显现出来,主要有下面几点:
所以,我们就调研了下,看其他公司在使用 Dubbo 框架时,Node 是如何调用 Java 的?如下图所示:
首先,Java 应用服务启动的时候,会往服务注册中心注册服务,这里的服务注册中心可能是 ETCD 或者 Zookeeper,然后,Node 应用在启动的时候,会先从服务注册中心拉取服务列表,接着 Node 会跟 Java 服务建立一条TCP长链接,除此之外,Node 还需要负责 Hession 协议解析以及负载均衡等。
不难发现,这种方式 Node 的职责就比较重,而且对 Node 开发的要求会很高。所以,我们对这种方式做了改进,如下图所示:
我们在 Node 和 Java 之间添加了一层中间代理层 Tether,Tether 是用 Go 语言写的一个本地代理,Tether 会对外暴露一个 HTTP 的服务,对 Node 来说,只需要通过 HTTP 方式调用本地的服务即可,其他服务化相关的服务发现、协议解析、负载均衡、长链建立维护都交由 Tether 来处理。这样,Node 这一层就非常轻量了,那么,最终实现出来,Node 是怎么调用 Java 服务的呢?如下代码所示:
const Service = require('../base/BaseService');
class GoodsService extends Service {
/**
* 根据商品 alias 获取商品详情
* @param {String} alias 商品 alias
*/
async getGoodsDetailByAlias(alias) {
const result = this.invoke(
'com.youzan.ic.service.GoodsService',
'getGoodsDetailByAlias',
[alias]
);
return result;
}
}
module.exports = GoodsService;
对 Node 来说,调用 Java 服务它只需要关注三个点:
最后,总结下这种方式都有哪些优点:
那么,看到这里,有人可能又会想,这里 Node 也是通过 HTTP 方式调用 Java 的,性能上是不是也存在问题呢?所以这里我们就做了一些优化,如下代码所示:
const Agent = require('agentkeepalive');
module.exports = new Agent({
maxSockets: 100,
maxFreeSockets: 10,
timeout: 60000,
freeSocketKeepAliveTimeout: 30000,
});
这里,我们引用了一个 agentkeepalive 包,在 HTTP 早期,每个 HTTP 请求都要求打开一个 TCP Socket 连接,并且使用一次之后就断开这个 TCP 连接,使用 keep-alive 可以改善这种状态,即在一次 TCP 连接中可以持续发送多份数据而不会断开连接。所以通过使用 keep-alive 机制,就可以减少 TCP 连接建立次数。
https://github.com/apache/incubator-dubbo
https://github.com/QianmiOpen/dubbo2.js
https://github.com/QianmiOpen/dubbo-node-client
https://github.com/p412726700/node-zookeeper-dubbo
https://zh.wikipedia.org/wiki/HTTP%E6%8C%81%E4%B9%85%E8%BF%9E%E6%8E%A5
欢迎关注我们的公众号
Original url: Access
Created at: 2019-09-26 18:19:54
Category: default
Tags: none
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
最新评论