中后台前端的 Serverless 还能这么玩

下期预告

7-18,是第十二期 – 可视化专场 :point_right: 报名地址

另外,每一期都提供了全年的联票,可以看所有场次的录播视频,包括下半年到年底的所有直播视频

前端早早聊大会目标成为用得上、听得懂、抄得走的技术大会,计划 2020 年办 >= 15 期,由前端早早聊与掘金联合举办,前端早早聊大会行程动态、录播视频/PPT/讲稿资料下载请关注 「前端早早聊」 公众号跟进。

你的支持,是早早聊办下去的唯一动力!

还想听哪方面的分享,直接加 Scott 微信:codingdreamer 提需求吧!注意申请备注 「参加前端早早聊」

正文

本文 是前端早早聊的第 31 位讲师 ,也是第六届 – Serverless 专场,来自 1688 团队的光毅的分享 – 讲稿简要整理版(完整版含演示请看录播视频和 PPT):

一、前言

关于我,我们团队具体的名称是阿里巴巴 CBU 技术部-体验技术-营销&工程团队,整个团队负责我们整个 CBU BU 里面前端的工程基建和 DevOps,以及整个前台营销导购场景的开发。

个人 16 年毕业校招加入这个团队,在这几年期间主要做页面搭建系统,偏向于无线搭建,然后做一些导购场景开发和前端的工程基建,也就是接下来要和大家分享 Serverless 相关的一些东西。

二、背景

接下主要想和大家分享以下几个方面的内容:首先我们在什么场景下会引入 Serverless 架构,引入后我们做了哪些配套的周边建设,最后是在现有的基础上我们正在做的面向未来的事情。

我们面临的中后台现状

1688 网站是阿里电商体系下偏向于 B 类的电商业务,所以我们这边是大量支撑运营的中后台系统,具体说是大量的基于 Egg 框架的应用。这一类中后台应用从应用结构看,它里面混杂了 Web MVC 分层,定时任务,RPC 服务等各种形态的结构,不太好维护。

另一个问题就是这一类应用都是很典型的长尾应用,流量呈现脉冲式分布的特点;为了安全的考虑,避免单点风险,应用还必须配备至少三台机器,导致的结果就是机器的资源利用率非常低。

在开发的过程中,开发者还需要掌握大量运维相关的知识才能让应用顺利的服务于线上。全栈兴起的时代,大家逐渐开始使用 Node,但同时还要 承担大量运维琐碎的成本 ,这对大家的开发其实是一个非常大的限制。在这种场景下,业界逐渐兴起了 Serverless 这种理念。

Serverless 简介

Serverless 这种理念之前赵兵老师也概括过其实分成 FaaS 跟 BaaS 两种基本的结构,然后从 产品的架构的层面 来看,可以概括成 4 种特点:

  • 产品上看 ,它 倡导的是资源按需分配,这样能充分提升资源的利用率

  • 架构来看 ,它倡导的是你整个在 Serverless 里面放的无状态计算容器,比如说 FaaS 这一层面,就 FaaS 这个函数它倡导的是一种无状态的,因为这更便于它去做扩容跟缩容。这样它水平扩跟缩的时候,不会因为里面的存在应用内大量的状态,而导致它扩跟缩的过程中影响函数的可用性。

  • 人的角度来看 ,从整个全栈发展的趋势,大家逐渐去用到 Node 做服务端开发的时候,其实会随着一个大势,从之前大家去写 CLI 的工具,到各种 Web Framework 开始兴起的时候,会逐渐去做 Web 开发。

  • 云厂商的角度看 ,一直到现在,各大云主流的云厂商也开始推出他们 Serverless 的解决方案,就比如说 AWS、谷歌、微软这些 Top 的厂商,他们都会去做他们自己的 Serverless 解决方案。

我们在内部做了一系列调研,最后确定了一个叫 Ginkgo 的解决方案更适用于我们,上图可以看到 Ginkgo 简单的架构图,它利用的是一种 Sidecar 的模式 ,把所有的流量都通过 RSocket Broker 来代理,无论是它的流量从接入层导入到函数内部,还是函数内部要去调用到第三方服务到外部,整个流量在函数之间的流动,都是通过这种 Broker 的模式来实现的。

Gingko 的解决方案提供的能力非常偏底层,主要专注于去解决整个容器的分配跟调度,以及它的扩缩容类似的问题。在接入层,就提供了不同的 RPC,HTTP,MQ 三类 Trigger。也支持了消息,缓存,RPC 三类中间件,但是缺少原生 DB 读写的能力。而且接入层的能力偏底层,类似于统一的登录鉴权,自定义域名这样在中后台常见的诉求支持得不完整,所以我们的建设就是围绕解决这些问题而展开。

三、方案介绍

细分

如图所示,整个解决方案的重点分成三部分:

  • 第一个是会根据我们的需求去建设一个独立的网关;

  • 第二个是建立一个在线统一的研发流程平台;

  • 第三个是补足它们现有我们需要的一些 BaaS 的能力。

在这种解决方案下面,它的两类主要的用户,他在里面的流量流动大致是怎么样的呢?

从一个开发者进来,它需要去根据自己的需求初始化函数,创建迭代,然后快速开发,最后部署函数上线对外服务。当函数部署到容器环境后,一个用户去访问它,会经过整个统一的接入层的网关,在网关里面会做的,比如说我们对它定义不同的路由,不同域名的时候,以及当函数在某些 SLA 要求比较高的场景下,要对它做一些容错,这个时候在统一的网关层做这个事情是很方便的。然后登录鉴权,不需要在函数内部大量的去维持的一种状态,在整个接入层做掉也是一个非常合适的选择。整个流量通过网关再 Trigger 到容器环境的接入层的时候,通过内部流量的流转,函数里面计算完了再返回,这就是整个流量流动的过程。

具体下来,看我们在这中间具体做了哪些事情。

第一个在整个函数中后台的场景的时候,我们需要去提供哪些高频使用的函数模板,这样初始化的时候会更方便。我们这边分析大概一个常见的中后台的场景,可能里面存在的有哪些高频的模块在里面,我们这边 Egg 用的比较多,所以它要让他写的习惯能更贴合的话,整个需要 Egg 的 FrameWork 来支撑这种语义。

第二个就是向他提供常见的外部的 server 的这种能力。

第三个比如说它的定时跟消息的这种能力。

对应到函数这样的形态,其实就对应到整个 Ginkgo 上支持不同函数的类型上,HTTP 这种类型以及消息中间件触发的这种类型,在这个之上可以在 HTTP 上面做改造,让它支持应用 Framework 这种模式。然后再基于这种分析,我们最终为整个中后台的场景制定了这样 4 种合适的函数模型。整个函数模型在整个 Serverless 里面,把它看成一个容器里面不同的分层的话,首先流量是会进入到整个容器入口的 Proxy 这一层,然后到容器里面会有一个统一的 Runtime 来适配上面函数以及下面容器对它的这种通信,对上提供一个干净统一的一层,在这个之上可以给他引入不同的 Framework,在这种 Framework 之上,就可以把用户的整个代码放到这个里面来运行。

整个研发流程有了完整的应用模型之后,怎么样来去定义它整个研发流程里面需要哪些东西呢?

总览

首先如果只是传统的那种 CLI 模式的话,整个流程也大致是这个样子,初始化开发调试的时候它需要哪些能力,然后部署构建,以及它部署到容器环境之后,一个服务端应用跑到线上之后,必备的就是日志调试跟监控这样的能力,以及要让这个函数被访问到的时候,需要有一个统一的接入层。整个研发流程平台就是把整个能力逐渐的线上化,在线上化的过程中,能够让它有一个统一的一致的一种模式。

在初始化基于函数模板的时候,整个初始化的过程就是一个很自然而然的过程,主要在调试开发,开发的时候需要更多的是能够让他 Debug,在部署构建的时候,如果是在自己本地来做这种 CLI 的构建的话,大概率可能会出现大家的依赖环境不一致的情况。那我们更倾向于是一种在线,在线通过 Docker 的方式,能够有一个统一的一致的环境,能够保持环境的一致,这样也会比较安全。到最后整个接入层的时候,前面介绍整个接入层需要的那些,能够通用抽象出来的那种层,能够把它替代掉那种很原始的,直接通过 Nginx 反向去代理这种很难维护的模式,这样能够让他更加高效的能够去配置它。

至于后面整个流程具体的产品是什么样子,后面会有一些示意图,接下来会介绍一些在构建研发流程时我们做的一些小小的创新,比如说:

一个函数发布过程中可能会需要做灰度肯定会有多版本共存,我们如何确定当前版本是否生效呢?我们设计了一种基于 CommitId 的机制,当这个函数需要迭代的时候,肯定是会对它代码有更改,整个代码在 GitLab 上肯定会是有一个唯一的 CommitID,这个是唯一标识了它当前这一次的迭代,是可以成为一个类似于血缘一样的东西来标志当前版本。

然后在外面如何能感知到这个版本?我们采取一种方式,在整个 Framework 里面内置定义了一个默认的路由,读取一个约定好的路径上的文件,这个文件中会包含整个版本的信息。接下来最关键一步就是把两者关联起来。整个构建的过程跟整个研发的过程是串在一起的,就可以在整个构建的过程中把需要部署的版本的代码拉取下来,通过一些手段把它的 Commit 信息获取到之后塞到对应的地方去。在部署上去的时候,通过外面公网能够访问到的 URL,可以很方便的知道当前这个版本是不是我们预期的版本。

另一个很常见的需求就是本地开发支持远程调试。JS 开发很幸运的,在 Chrome 里面提供了非常好的支持断点的模式,NodeJS 也提供了底层的能力让开发者也能享受这种便利。我们如何让开发者在函数开发时也能享受这种便利?回顾上文提到的函数的分层模型,函数基于 Runtime,再往上是个 App Framework,一个可扩展的 Framework 可以方便我们做很多事情。

为了支持这种远程调试我们在 Framework 内置一个简单的 Webserver,Webserver 主要是用来对外提供一些 Open API,Open API 通过 Node 本身提供的 inspect 协议来操作整个当前容器里面起的整个 Node 的应用,在它里面开启一个远程调试,已经跟 Chrome 配合得很好的一个调试的协议。

右边的图,就是整个在开发的时候已经拼装好的一个很完整地址的示意图。这个地址在 Chrome 打开的时候可以很方便的看到整个调试的面板,可以非常自然的像前端的浏览器端的 JS 那样去调试整个 JS 的代码。

再看整个接入层,接入层的时候它 在中后台我们需要哪些能力 ?第一个,它需要知道我们是通过哪些自定义的域名来访问的,以及给它提供一些默认的域名的时候,它怎么样才能去知道我访问这个路径是需要路由的,我指定的函数上面去。

所以这中间更重要的是定下一个协议。定下一个协议的过程,我们集团内部,其实各个团队大家都在做类似的事情,大家讨论出了一个基于 Swagger OAS 的 合作共建标准 ,在这个基础之上做了一些扩展。在基于这个协议的时候,在整个发布的过程中的时候就部署完了。当它把这个函数部署到容器里面去了之后,这个时候其实是没有办法直接通过接入层来访问到它,因为这个时候函数没有办法跟接入层之间的请求上下文之间建立联系,这个时候就 需要把这些请求的上下文同步到整个接入层里面去

整个接入层感知到信息之后,当流量请求过来的时候,它在整个接入层就可以通过这种预先制定好的协议来感知到是哪个函数去服务于哪些请求的地址,以及到下面它子路径,这个就是 整个接入层最核心的逻辑

整个接入层比较核心的问题解决了之后,其实剩下就是 BaaS 层的能力,整个 BaaS 层提供了 4 种主流的中间件里面的三种:缓存、消息跟 RPC。但是剩下一个没有解决得很好,也是他们整个团队可能有他们自己的规划,但是我们在落地的时候,又想需要一种能力的时候,我们采取了一种比较实用主义的做法,整个功能没有设计得非常复杂。因为在考虑到我们本身面临的中后台的场景里面,历史的有 Node,有 Java 的应用,它们都是带很大量存量的场景,然后他们之前已经有老的数据库,已经在那跑得很好了,大概率是申请新的数据库可能对他们来说成本可能更高,因为数据迁移的成本更高。

第二个在我们考虑中后台的场景里面,它的流量其实并没有那么大,所以他们整个架构可能并不需要考虑太多的,比如说高可用以及那种极致的性能这方面的考虑,主要是解决我们当时场景的问题。所以在这个基础之上,我们其实是借用了它已经成熟的,比如说 RPC 的能力,在整个 RPC 的基础之上再去构建了一个函数,函数可以通过 RPC 访问整个数据库的网关,然后整个数据库的网关,比如在 Node 上,最基础的能力封装得非常薄,它就是提供了远程你通过 RPC 去执行一段 SQL 的能力,然后在这个基础之上就封装了更多可能 ORM ,以及不同类型的 ORM 这种查询的 SDK,SDK 把之前比如说我们需要统一去处理登录信息以及调用不同的中间件,把这些能力都封装到一个 SDK 然后内置到整个函数的模板里面去,这样大家使用起来就会非常的统一跟方便。

接下来看一下我们使用时候的一个截图,比如说创建我们整个在线的流程,它会帮你创建整个仓库,以及在创建的仓库之后,它会去选择不同的函数模板,基于这个模板来做初始化,初始化完了之后是通过 GitLab 跟它分支关联的那种模式,这样比较容易实现代码的分离,以及做回滚。

然后具体到开发过程中,整个开发的流程部署完了之后,可以非常方便的看到,需要在不同地方去看它运维的信息以及怎么去调用它。

四、后续规划

有了这一套的模式之后,开发体验相比之前确实有很大的提升,因为这种模式之下,其实你看了文档之后,因为整个流程都是非常简单,没有任何运维的成本,整个开发过程可能从之前要天级到现在变成可能在小时这个级别就能完成开发,对整个开发的过程其实是非常方便。在这种模式下进一步去考虑,因为整个函数它只是我们把它用在了中后台场景,其实整个模式我们再回顾下来,其实是一个非常通用的计算模式,对吧?它整个对容器对函数的这种模型的定义,其实可以在我们更上层的业务场景去尝试更多。

但是当我们逐渐去把它往电商里面常见的那种导购的场景,我们去照搬之前那套模式的时候,就会发现可能就有些行不通了,这行不通为什么呢?首先我们可以对比一下,假如我们这边常见的在中后台场景以及在那种导购场景里面,它函数里面的构成大概是什么样?在中后台场景里面就像之前说的,它其实耦合的东西非常的多,需要去处理不同的分层,需要去处理不同的计算的模型,以及在里面它可能会去定义自己的数据模型,这种领域模型。

但是到导购场景的时候,比如说一个电商的导购场景,我在一个导购的页面上,我要展示一列的商品,我要展示一列的商家以及整个活动它的优惠信息,这些其实是非常模式化,它有非常固定,非常成熟的这种模型,这种概念在里面,我们在导购场景里面去用它的时候就会发现整个函数代码的复杂性相比于中后台,它的复杂性是降低了,它的降低就在于它整个领域模型不需要它函数本身来维护,它在里面主要就是来做数据裁剪这个事情,然后拼出它在整个 View 这一层需要的数据,这个其实就在导购场景 BFF 非常常见的一种模式。

在这种模式下,我们进一步的考虑,之前在 中后台的场景 可能就是我们 首先要不同的逻辑再去定义出这样的领域模型 ,整个过程可能是这样,但是在 导购场景 这种模式下,我们思考的这种方向可能就是反过来的,因为首先它 已经有这样的模型 ,我们 知道需要哪些模型 ,然后这种模型我们可能 需要知道有哪些字段 ,然后在这个字段之上,我们 再通过函数这种模式来组合出它在前台的 View 这种场景它需要哪些?所以它这种在 BFF 的场景可能更多倾向的是一种 从模型生产函数 的模式,可能是会比中后台能够让整个 BFF 开发的过程能够更快的一种模式。

后续规划也是我们现在正在做的一种模式。整个我们会 建立一个统一的领域模型的市场也好或者集市 也好,然后整个领域模型我们会给它 定义非常完整的像这种文档以及整个调用的方式这种信息 ,能够让整个 BFF 的开发者能够非常快的感知到我需要的模型在哪个地方,这些模型它里面到底有哪些东西,这样的话才能更方便的去开发它。整个 BFF 的函数就是构建在它之上。

然后比如说模型,它的模型它的分层,DDD 里面倡导一种模型的分层,以及我们选取的是一种什么样的模式,从最底下的整个基础设施,在之前已经做过了整个函数的整个接入层整个研发的过程,统统都可以归到整个基础设施的层面,解决的就是整个函数在技术层面的开发的问题。在这个之上,整个基础的就是到了领域模型 域 的范畴了,首先就是在不同的比如电商里面常见的这种域, 在域之上可能就会定义不同的模型 ,在这个 模型之上 ,我们需要去 能够让外界使用这种模型 的时候,可能就是 两种不同的思路 ,第一个是 提供一个二方包 一样的模式,一种 SDK 让使用的地方去依赖这种 SDK。还有一种模式,可以通过 统一的调用方式 ,一种 RPC 的形式 ,然后能够通过统一的一种方式去调,而不用非常强的依赖一种 SDK 的这种模式。在这整个调用的方式之上,就是整个函数运行的一个环境。这个右边的图是我们之前设想出来的一种通过模型生成函数的一个事例。

更具体一点,我们可能想做到的是这样一种模式,就是整个模型它的分层,整个领域模型,它整个模型的市场大致是以这样一种分层,从最里面的最原始的数据字典一直到最外面,通过 GUI 来看到这些模型的信息,以及通过 Open API 能够被不同的服务方去集成,整个我们会通过一个统一的调用入口, 能够让整个模型能够被访问到。

这个模型它到底在这个里面它有哪些呢?首先是非常纯粹的模型,比如说像商品商家这种非常纯粹的模型,它里面不需要太多的业务逻辑,它就需要非常清晰的定义出每个字段及字段的信息,在这个字段之上一些非常通用的方法,这种纯粹的领域模型,然后还有一种在这个模型之上,我们在不同 View 的场景下需要去对这个模型做裁剪吗?但是裁剪的过程,它可能能够被二次复用, 这也是一种非常常见的模式。

这种模式其实就是把函数当它运行之后,给它配上一个 Scheme 的协议,然后来描述整个函数它的原信息,有了这样的原信息的时候,可以把这样一种经过加工之后的高于纯粹的那种领域模型那一层的这样一种函数作为一种模型,再回流到整个领域模型的市场里面去。想要做到这样的事情,需要的是要对整个模型 定义一个非常完整的协议 ,这个协议里面至少会包含这个模型它的原信息,比如说它的入参,它的返回类型这样的一种字段,以及它的调用,它支持的一种调用的形式。还有一种就是当它是通过模型来编排这种函数的形式,回流到里面的一种模型的时候,这种模型可能更需要清楚的定义出它是从哪些更上游的模型派生出来的。

因为 有了这些信息 我们至少 可以做到两个事情第一个 就是 可以确定整个模型的血缘关系 ,甚至可以更精确地做到整个字段的血缘关系。这样的话在使用这个模型,是可以看到整个字段它的血缘关系的流向,更方便于我们日常在一个非常长分布式的链路里面去排查问题。 第二个 就是有这个模型,可以更进一步做到, 基于这样一种统一的协议是可以来生成代码 。什么意思?就是当我们能够清楚的知道我们函数这个 BFF 的脚本,它需要依赖哪些上游的模型的时候,可以通过在它创建的时候就可以非常清楚的知道,这些模型它到底是一种什么样的调用逻辑,这些调用的逻辑,以及怎么写这个代码,是可以通过自动化生成的方式就可以做到。

假如我们可以做到这一点,可以想象一下,甚至当我们整个开发的过程就可以在它开发的时候就勾选上它需要哪些模型,以及这些模型里面到底需要哪些方法字段,有这些东西我们就可以生成大致 60% 甚至 70% 可以用的这种代码,然后在这个基础上再去做开发的时候,整个过程就会非常的方便。

团队介绍

接下来打一下广告,可以再介绍一下我们团队是体验技术部,CBU 体验技术部其实在整个 CBU 的前端都是我们这个部门,我们部门平时外出的活动也挺多的。

在对内的分享其实可能比外出的活动更多,我们也会邀请业内集团内一些大佬来做分享,来扩充我们的视野。

这个可能是大家非常喜欢的霸天小姐姐,也是我们团队的。我们在内部其实是非常活跃的,像霸天小姐姐这样的人,这样活跃的同学其实有更多。

这个是我们在出去团建之前的。

这个是日常的活动,我们在 1024 程序员节的时候也会做一些活动。

这个是我们内部在 1024 的时候一个机器车的 AI 的编程大赛,这个是在比赛的时候的情况。

日常大家过生日的时候也会组织很多活动。

如果熟悉二次元的可能我们团队也有很多像这种玩二次元玩的很溜的同学。

如果对我们整个团队做的事情感兴趣,或者对我刚才讲的这些东西有哪些质疑或者有疑问的地方,大家可以加我的联系方式,我们可以共同讨论。因为整个建设过程我们也可以看到,我们其实是在一个持续建设的过程跟摸索的过程,然后这个是我们团队对外的一个公众号,公众号里面我们团队也会定期的去抛一些内部觉得比较好的一些技术分享,以及业界的一些动态。

我的分享就到这里!

近两年 Scott 观察到前端行业已经完全进入竞争的深水区,各大小公司的前端 TL 刚刚上任,初带团队,针对前端工程师这个群体,应该怎么管人理事,搭台拿结果,帮带有成长,就成立了这个前端技术主管学习交流群,在人的选用育留上互相学习成长,入群的门槛是你有实线或者虚线在带团队,请加 Scott 微信: codingdreamer 邀请入群,注意申请备注 「进管理群」