前端学serverless系列——WebApplication迁移实践

导语: 说起当前最火的技术,除了最新的区块链,AI,还有一个不得不提的概念是Serverless。Serverless作为一种新型的互联网架构直接或间接推动了云计算的发展,从AWS Lambda到各厂商争先推出Serverless服务框架,Serverless一路高歌。在这个风口,前端好像都要做点什么?

目录:

一、Serverless简介

二、一个轻量web Application迁移实践

一、Serverless简介

本章简单介绍一下Serverless的演变过程、Serverless是什么,其优缺点以及适合的应用场景。

云计算的发展从IaaS,PaaS,SaaS,到最新的BaaS,FasS,在这个趋势中serverless(去服务器化)越来越明显。

Bare Metal(IDC):

物理机托管

IAAS:

IaaS(Infrastructure as a Service) 基础设施即服务,__服务商提供底层/物理层基础设施资源(服务器,数据中心,环境控制,电源,服务器机房),用户需要通过IaaS提供的服务平台购买虚拟资源,选择操作系统,安装软件,部署程序,监控应用。

目前知名的IaaS平台有AWS,Azure,Google Cloud Plantform,腾讯云服务,阿里云以及开源的OpenStack等。

PAAS:

PaaS(Platform as a Service) 平台即服务,服务商提供基础设施底层服务,提供操作系统(Windows,Linux)、数据库服务器、Web服务器、负载均衡器和其他中间件,相对于IaaS客户仅仅需要自己控制上层的应用程序部署与应用托管的环境。

目前知名的PaaS平台有 Amazon Elastic Beanstalk,Azure,Google App Engine,腾讯容器服务,VMware Cloud Foundry等。

BAAS:

BaaS(Backend as a Service) 后端即服务,服务商为客户(开发者)提供整合云后端的服务,如提供文件存储、数据存储、推送服务、身份验证服务等功能,以帮助开发者快速开发应用。

FAAS:

FaaS(Function as a Service) 函数即服务,服务商提供一个平台,允许客户开发、运行和管理应用程序功能,而无需构建和维护基础架构。 按照此模型构建应用程序是实现“无服务器”体系结构的一种方式,通常在构建微服务应用程序时使用。

虚拟化与隔离

从最早的物理服务器开始,我们都在不断地抽象或者虚拟化服务器。

服务器发展

我们使用 XEN、KVM等虚拟化技术,隔离了硬件以及运行在这之上的操作系统。 我们使用云计算进一步地自动管理这些虚拟化的资源。 我们使用 Docker 等容器技术,隔离了应用的操作系统与服务器的操作。 现在,我们有了 Serverless,我们可以隔离操作系统,乃至更底层的技术细节。

无状态

但也决定了Serverless的无状态特性,因为每次函数执行,可能使用的都是不同的容器,无法进行内存或数据共享。如果要共享数据,则只能通过第三方服务,比如 Redis,COS 等。

无运维

使用 Serverless 我们不需要关心服务器,不需要关心运维。这也是 Serverless 思想的核心。

事件驱动编程Serverless 的运行才计算,便意味着他是事件驱动式计算。

低成本

使用 Serverless 成本很低,因为我们只需要为每次函数的运行付费。函数不运行,则不花钱,也不会浪费服务器资源。

  • 在 Serverless 应用中,开发者只需要专注于业务,剩下的运维等工作都不需要操心
  • Serverless 是真正的按需使用,请求到来时才开始运行
  • Serverless 是按运行时间和内存来算钱的
  • Serverless 应用严重依赖于特定的云平台、第三方服务

Serverless 是一种 “无服务器架构”,让用户无需关心程序运行环境、资源及数量,只要将精力 Focus 到业务逻辑上的技术。

FAAS(函数即服务) + BAAS(后台即服务) 可以称为一个完整的 Serverless 的实现。

Serverless Cloud Function(SCF)架构

目前腾讯云SCF支持的Serverless语言

Python 2.7 & 3.6、Node.js 6.10 & Node.js 8.9、Java 8、Php 5 & Php 7、Go 1.8、C#&C++(规划中)

Serverless 的优势

  • 降低启动成本
    • 减少运营成本
    • 降低开发成本
  • 实现快速上线
    • 更快的部署流水线
    • 更快的开发速度
  • 系统安全性更高
  • 适应微服务架构
  • 自动扩展能力

Serverless 的缺点

  • 不适合有状态的服务
  • 不适合长时间运行应用
  • 完全依赖于第三方服务
  • 冷启动时间较长
  • 缺乏调试和开发工具

Serverless 的适用场景

  • 发送通知
  • WebHook
  • 轻量级 API
  • 物联网
  • 数据统计分析
  • Trigger 及定时任务
  • 精益创业
  • Chat 机器人

虽然目前来说Serverless还是有不少的局限性,Serverless一直在发展完善中,广大开发者和服务提供者都在寻找Serverless的无限可能。

二、一个轻量Web Application迁移实践

本章基于腾讯云函数从架构迁移和开发部署流程去说明一个Web Application的迁移实践。

1、架构迁移

我们先来看看一个一般Web Application在SCF上的的架构。

静态资源

静态资源(JS/CSS/IMG/HTML)放在COS(对象存储),COS可以自定义域名和开启CDN加速(具体请查看腾讯云文档《配置自定义域名支持 HTTPS 访问》),通过URL直接访问,这和原来的Web Application没有什么区别。

如果我们是一个单页面异步应用,也就是我们的页面html也是静态资源,你可以选择放在SCF中去返回,就像一个普通的web server返回静态资源一样,但是纯静态资源用SCF去返回总觉得大材小用,既耗费了运行资源,性能也不够好。还可以放在COS上去存储,并且COS也可以支持自定义域名和开启CDN加速。

但是这样一来主域和动态数据的域名就存在跨域的问题,通过下面的方法就可以轻易解决这个问题。

支持跨域访问:可以通过API网关设置支持CORS或者后端程序设置CORS。

性能优化:在页面中header头中使用preconnect进行动态API的预连接,可以大大减少DNS/TCP/SSL的时间,别小看这个时间,因为目前腾讯云的云函数对应的API网关只支持一地接入,地域比较远的地方,这个时间可以达到几百ms。

API网关+应用逻辑

从原来的nodeServer到云函数的架构变化主要如下:

其中API GATEWAY EVENT的格式

你可以选择在代码中直接去解析使用API GATEWAY EVENT,以及封装HTTP响应体。HTTP基本上使用得到的相关的数据字段,API GATEWAY EVENT中字段都有,只是以不同的数据结构出现。如果我们已经很习惯express的开发框架,而且很依赖一些好用的中间件,如果我们需要重建这部分中间件,这会是不小工作量。–那我们有没有方案兼容原来的写法?

无论是迁移还是新开发的项目其实都可以采用这个架构:

我们可以将API网关事件转换成http请求,通过本地socket和函数起nodeserver进行通信。

那么中间经过了一层服务的转发,性能会不会有所损耗呢?在统计耗时来看,有了这一层转发,总共云函数平均耗时也在20ms之内,那么中间即使有性能损耗也在10ms之内了,而且是通过本地的socket进行通信的,相对于网络耗时来说,就是大巫见小巫了。

中间转发代理层已经有一些可用的框架(serverlessplus, scf-framework),大家可以尝试一下,用法都比较简单。

数据存储

由于Serverless的架构是事件触发,用完即释放,那么你一定要考虑的是,你的本地存储和缓存必须依赖于第三方服务如cos和redis,不过可以通过实例保留或者它本身会有3分钟的释放延迟时间,你依然可以利用本地存储和内存缓存作为你的第一级缓存。

1、DB

和原来的DB使用没有太大的区别。

一般我们选择与公司内网打通的VPC内申请资源,这样安全系数比较高,与外网完全隔离,通过选择相同的子网,才能连通。

资源申请在下文开发部署篇会说到。

也可以选择纯外网的DB资源,然后通过建立虚拟子网,和腾讯云函数设置同一个子网内,云函数就可以通过内网IP进行访问。另外DB也可以设置外网域名地址,通过外网访问,这样本地开发的时候也可以访问到了,一般测试时使用。

数据库实例界面:

2、内存缓存

如上面所说,实例会有一个延迟销毁时间,如果短时间内命中同一个实例,实例中内存变量是可以被缓存住的。需要缓存的内容可以两级缓存,先从内存中读取,读取不到再到Redis中读取。

Redis的使用和DB类似,申请资源,设置子网,通过IP PORT进行连接使用。

可以通过下面封装的npm包进行Redis简单使用。

npm i qcloud-serverless-redis

3、文件存储

Serverless可写的目录是/tmp/, 但是会随着实例释放而释放,所以只能临时放一下。

总共大小只有512M,建议临时文件用完要主动删除。

如果要长期存储的文件,可以使用COS进行存储。

具体COS操作可以参考腾讯云文档 如: Node.js SDK 快速入门

我这里也简单封装了一个cos的npm包,可以快速尝试一下cos的存取功能,具体用法看里面的README

tnpm i @tencent/serverless-cos

登录相关

这个解决方案同数据存储类似,可以通过第三方服务来保存状态或者通过token加解密来进行状态保存。

下面这个方案是通过token的加解密来进行登录状态检查的,登录验证过程是调用了原有的后台服务。

而小程序是没有cookie这个概念的,也就是小程序不会帮你set-cookie,保存cookie,发送cookie,这些都需要你自己模拟来做。现在的通用方案一般是前端接收后端返回的内容,保存在localStorage中,每次请求的时候校验有效期,并将token设置到header中的cookie模块,后端可以正常拿到cookie进行验证。

性能调优

第一章说到Serverless其实不适合对时延有比较高要求的场景,那么实际上性能如何,是否有优化的空间,是否能够满足我们的即时响应的需求呢?

我们先来看一下一个云函数启动的过程,包括哪些步骤。

函数发生调用,调度系统看函数实例是否存在,如果实例存在,那么就可以执行函数,返回结果。这个时间是非常短的,在毫秒级别。

如果不存在,那么需要创建容器,下载部署代码。这些过程耗时百毫秒到几秒不等,称为冷启动。

优化函数的性能,也就需要从函数生命周期的各个阶段去优化。

  • 1、函数实例复用,这是最直接有效的手段。但是保留多长时间合适?3分钟,能解决95%的问题3小时,能解决99%的问题3天,能解决99.9%的问题。这个是一个成本和效果衡量的问题。(保留时间也不一定是固定不变的值,需要分析函数特点和时间段。 )
  • 2、预创建一批不同规格的容器(不含代码)来减少创建容器的耗时。
  • 3、函数平台有一个代码仓库来保存管理函数代码的,在使用的时候才会在下载到容器中。可以对热点代码进行缓存:一级:Node计算节点缓存,二级:机房缓存
  • 4、通过机器学习预测流量,预先启动一些函数实例,这样就可以尽量消除冷启动,保证实例都是热启动的。

这些都是平台做的优化,那么开发者可以做什么呢?

  • 代码精简:缩短代码下载时间
  • 公共剥离:增加缓存效果,将一些公共的,常调用的服务拆分成一个独立的云函数,可以增加缓存的效果。
  • 资源复用:缩短执行时间,这里指的是,函数使用的公共资源可以放到函数外面来定义执行,比如数据库的连接。
  • 保持活跃:避免资源回收,这就保证了请求都是热启动的。

更多可以参考我的另外一篇文章《前端学serverless系列——性能调优》

2、开发部署运维

开发调试

1)云上调试

目前发布到云函数是要包含node_modules文件夹的,就算不需要,也要压缩,然后通过网络传输上去。如果改一行代码,就要上传一次来执行,那不是要崩溃?而且在线的IDE也是只能编辑index.js,但是代码都不是写在入口文件那的。在线IDE目前只能支持单文件入口函数的编辑,升级IDE也在规划开发中的。

2)本地调试1.0

需要提前安装tcf,docker

具体安装请参考 TCF命令行工具

本地执行命令:

tcf local invoke --template template.yaml --event event/apigateway.json

调试起来比第一种方案快了许多,虽然在公司的网络一定要开一个代理,否则会拉取不下来docker镜像。

这个运行方式的原理是,加载一个和云函数环境差不多的一个镜像,然后在docker中去执行。

然而还是会遇到不少问题:

比如连接的数据库一定要是外网地址,因为docker的网络环境和本地并不能连通,与云上的环境也不能连通。

比如我在本地安装的npm包,也不能正常执行,因为我本地是mac系统,而镜像是linux系统。

比如云函数本来内置了一些npm包,我写了个脚本,删除了这部分npm包,在云上可以正常执行,在本地调试的时候发现又缺少npm包,原因是云上的环境和镜像中的环境不安全一致。不过这个问题也已经解决。

3)本地调试2.0

随着开发者的反馈,云函数的同事又推出了TCF升级版本。可以支持native调试,其实就是用本地的环境进行调试。这样至少连接数据库没有连通问题,以及调试的时候没有操作系统差异问题。那么最后一个问题只能是需要一个专门的编译机了。一般情况下,如果依赖的npm库如果不涉及到操作系统差异的话,npm包都是可以通用的。

具体用法可以查看文档: https://github.com/tencentyun/tcfcli

tcf native invoke --template template.yaml --event event/apigateway.json

4)node server调试:

如果你的项目是基于koa或者express之类的框架,可以直接增加一个server的入口,本地调试的时候就直接起一个server,和普通node server一样的进行调试。

发布

可以使用tcf命令进行发布,tcf命令行发布支持两种

  • 通过cos对象存储上传代码
  • 通过本地zip包上传代码(zip大小不能超过50M)

具体可以查看文档: 如何用tcf发布部署代码

前面说到用命令行工具很方便的将代码发布到云函数平台上。

//示意代码

tcf package & tcf deloy

但是只是发布上去就可以了吗?

开发测试和线上环境如何隔离呢,如何回滚呢?

云函数本身有版本功能,云函数详情页面右上角可以发布新版本。

API网关也默认有测试、预发布、发布3个环境,可以指定云函数的版本。

那么我们测试的时候可以指定$LATEST版本,测试通过之后可以发一个云函数的版本,然后配置API网关的预发布环境进行预发布验证,预发布验证之后,再发布到线上环境。

具体操作路径:点击API网关具体的服务进到详情页,在API管理下,针对每个API进行编辑。

选择要对应的版本:

编辑完成之后,API需要发布到对应环境才会生效。

操作回滚也很方便,直接切换到历史到版本就可以。不过要注意的是,写好备注,不要像我一样写“test” ^_^

到这里,看起来就可以将测试和线上的环境区分开。

但是实际上,测试和线上连接的资源是不一样的,比如DB,我们通常是通过读取环境变量来判断连接什么资源,而不是通过改代码,而一个云函数只有一个配置。

那么我们可以利用命名空间来将测试和发布环境隔离,发布可以通过函数复制来完成,可选不复制配置,配置在线上和测试环境设置不一样,代码通过判断环境变量来连接线上和测试的资源。

创建命名空间:

复制函数到其他命名空间:

设置环境变量:

根据环境变量读取不同到配置:

//根据环境变量读取不同的配置
const devConfig = require('./config.dev');
const testConfig = require('./config.test');
const prodConfig = require('./config.prod');

const env = process.env.NODE_ENV;
console.log('process.env.NODE_ENV', process.env.NODE_ENV);
switch (env) {
  case 'prod':
    module.exports = prodConfig;
    break;
  case 'test':
    module.exports = testConfig;
    break;
  default:
    module.exports = devConfig;
}

总结一下:

1)测试和线上放在两个不同到命名空间,隔离测试和线上代码。

2)上线通过复制函数来完成。

3)测试和线上环境通过函数配置设置不同的环境变量来区分。

4)回滚通过设置函数版本来完成。

域名映射

API网关会有一个默认的域名,这让我们不需要自己去申请一个域名才可以使用API网关。但是,一般如果是用户在浏览器中访问的URL,肯定是需要自己/简短点的域名更让人信任。

API网关-自定义域名

如果是支持https的话,需要在腾讯云上上传https证书。

另外,还可以自定义路径映射,比如将发布的路径从 http://yourdomain/releasehttp://yourdomain/ 访问更简短。也可以将/test测试路径改得更复杂一些,避免用户访问到,当然你不发布测试环境更保险。

日志

涉及到后台服务,那么日志的打印是必不可少的,调试,查问题,甚至统计可能都需要用到日志。那么云函数的日志可以怎么来使用呢?

在云函数的界面,我看可以看到一个云函数的日志界面,可以支持实时日志显示,还有选择时间,还有只选择失败的日志。

但是我们看到唯一的检索框,只能根据RequestID来检索,每个请求有一个RequestID。那么requestID去哪里获取,貌似只能从这条日志中去获取,如果你传给了其他服务或者前端,其他服务追查问题的时候,可以追溯到这里。

这明显太不方便了。

日志服务在我用的时候还没有出,在我写这篇文章的时候已经上线了。

如果当前函数已经配置了日志服务,可以[前往日志服务]对日志进行更方便的检索。

在函数配置界面下方,可以配置将日志投递到日志服务。新建日志服务

创建日志集:

日志集上可以创建多个日志主题。

一个日志可以如何消费呢,可以看下下面到这个操作栏:

LogListener是用于有自己到服务器的采集方式,而云函数的采集只需要在函数配置中指定一下要投递的日志集和日志主题即可。

索引配置:可以配置分词符

投递配置:可以将日志投递到COS

实时消费:可以用Ckafka消费

检索示例:

监控

云函数和API网关有一些自带的监控,可以满足查看需求,如果需要更详细的视图配置和告警功能的话,可以使用云监控。

云函数首页总览:

云函数监控信息界面:

API网关监控:

如果需要更加丰富的统计监控告警,可以查阅云监控

其中自定义监控对后台服务来说也是非常需要的,可以上报一些业务类型的指标或者告警。可查看文档:自定义监控

注意事项

1、项目中用到的node_modules还不能在线安装,只能从本地打包上传。

2、很多人本地的开发环境是windows或者是mac,有一些node_module依赖是和操作系统相关的,那么在本地安装的node_modules,在云函数上或镜像中并不能使用。

3、本地调试的docker环境是网络隔离的,那么如果你要连接相关的baas服务,那么你需要支持外网访问的baas服务。如果是node的话,已经可以支持tcf native 来调试,不用docker调试了。

4、服务端设置cookie目前只能设置一个。这个腾讯云也在计划修复中。

等~

最后

我们现在使用HTTP协议的时候,需要通过API GATEWAY中转一层,能不能去掉这一层中转呢?

如果我们的业务应用比较复杂的话,需要拆成多个云函数来承载,对于这样的现有项目能不能0改造迁移呢?

现在部署云函数的时候,需要将lib库也打包上传,我们希望能和代码仓库打通,在git push的时候,能够在线编译,并且自动部署。

这些问题将会在腾讯云serverless2.0中解决,即将发布,不妨期待一下。

尝试的路还在继续,欢迎有兴趣有需求的开发者一起讨论一起探索一起建设。