闲鱼前端 Faas 框架与通讯方案|前端搞 Serverless

前端早早聊大会,前端成长的新起点,与掘金联合举办。前端

本文 是前端早早聊的第 45 位讲师 ,也是第六届 - Serverless 专场,来自闲鱼前端团队的丹侠的分享 - 讲稿简要整理版(完整版含演示请看录播视频和 PPT):android

概述

今天给你们分享一下闲鱼如何在现有产品中落地 FaaS,但愿咱们的实践能够给你们带来借鉴和灵感。今天跟我一同分享的,还有个人同事,海潴。他是咱们团队的架构师,FaaS 层的通讯架构就是由他设计并实现。在后续的 PPT 中,有关通讯相关的技术点,会由他来带给你们。面试

以前 5 场,你们已经听到了像 Severless、云计算、云平台等跟 FaaS 关系比较紧密的概念。接下来我和海潴讲的内容里,不会涉及太多的基础概念,咱们的方案不限定具体某个技术栈或者是工具,也没有要求使用特定的平台及环境。咱们会更倾向于解决具体业务问题。主要给你们分享一套设计思路和方法论。讲的是在富交互场景的产品链路里,如何利用 FaaS,给现有的研发模式提供一些便利和想象空间。固然,FaaS 目前在前端领域,产生不少新的研发创新思路的同时,也是有一些不可忽视的问题存在,也是须要你们一块儿不断思考和实践,并逐渐完善。算法

闲鱼的端技术和 FaaS 研发体系

首先让你们了解下闲鱼目前的端技术组成。闲鱼给业内印象比较深的,多是 Flutter 的研发体系。可是闲鱼也有不少基于 Web 的应用场景,这些场景,仍是基于前端比较熟悉的技术栈来实现的,好比 React,Vue,小程序。除了端内有很多 Web 的场景,在端外的投放,基本也都是基于前端 H5 技术来实现的。编程

你们知道,Flutter 官方提供的编程语言是 Dart。因此闲鱼在 FaaS 体系构建之初,是把 Dart 做为 FaaS 层的编程语言,这样客户端就能够实现一体化的研发体验。这跟前端使用 TS 结合 Node 是同样的道理。在 FaaS 的实践过程当中,前端又是跟客户端共享一套 FaaS 技术框架,因此为了让前端同窗也能够实现一体化的编程体验,前端在工程上作了一层语法转化,实现了用 TS 编程,构建时转化成 Dart 的工程能力,这样也就间接实现了一体化的研发体验。json

因此目前闲鱼 FaaS 研发的技术环境就是:客户端和前端共用同一套 FaaS 框架。 咱们的 FaaS 函数,是部署在一个叫盖亚的研发部署平台,这个平台是阿里内部面向函数的研发运维平台,这个对各位同窗来讲并非惟一选择,你们可使用自家公司的,或者本身熟悉的平台来部署 FaaS 函数。redux

传统的 MVVM 的研发模式

先回顾一下传统的前端研发模式,大部分同窗,对 MVVM 应该比较熟悉。像 React、Vue 都是擅长使用这套模式管理前端代码的。小程序

重端侧的研发过程当中,会有三个概念:Model、View、ViewModel,这三个都是在端侧定义并管理的。统一管理的好处是一个工程维护一套业务,研发效率比较高;缺点就是代码管理成本比较高,复杂业务须要借助一些端侧代码框架,好比 Redux,或者像蚂蚁前后推出开发框架 Dvajs、Umijs,都是帮助你们去管理端侧代码。但即便有这些框架的协助,有时对 UI 状态和业务状态的管理也会比较混乱。这跟业务的多变性、开发人员的综合素质以及开发团队对代码管理的严格程度都有关系。后端

重端侧的研发模式,有个问题就是技术栈的适配和迁移成本较高,好比从 Vue 迁移到 React 或者是小程序。不光要适配 UI 层,还须要依赖于以前的逻辑层抽象的比较好,不然这个过程确定是比较痛苦的。这也是近几年小程序比较火的一个缘由之一,各家 App 都支持了小程序,能够作到一次开发,多端投放的效果。可是并非全部的场景都是适合用小程序来实现的。api

重端侧研发还有一个问题是前端对接口的控制能力不足。服务端会针对业务提供定制的接口,复用度较差。数据格式转化到 Model 的过程当中,还须要端侧作一些适配、转化及容错处理。

固然前端也能够经过 BFF 层,Backend For Frontend,顾名思义,服务前端的后端。但这是一层是很是轻量级的服务层,主要作的是数据的重组,字段补全、格式校验、标准化等能力。最终的数据能力仍是没有大的改变。只是把原来服务端不擅长的那部分数据处理工做给揽过来了。

基于 FaaS 的研发模式

基于 FaaS 的研发模式,它给前端的研发模式提供了新的思路。仍是熟悉的 MVVM 研发模式,只是端侧只剩下了View、把 Model 和 ViewModel 都迁移到了 FaaS 侧。大部分更新UI的工做经过事件通讯来实现。

能够看到这种模式下,接口的服务能力也有必定的变化,也就是 FaaS 函数跟 Severless 的配合,服务端会提供领域级别的服务,这些领域服务通常设计成可复用,不跟具体的业务逻辑耦合。对业务的逻辑处理和数据的编排和重组,都是放在 FaaS 层。这样前端的逻辑也放在了 FaaS,FaaS 的能力和职责是被放大了。前端可作的事情和想象空间也变得更大。

固然 FaaS 自己是一种无状态的服务,在逻辑处理过程当中若是须要使用一些状态存储的能力,就要借助于 BaaS 才能完成一些状态保存的能力。

并且业务逻辑和数据编排的能力放在一块儿,可让整个业务逻辑再也不割裂,能够更好地进行抽象设计,甚至编排。

FaaS 在淘系的应用之一(导购)

这里举例一个淘系最先应用 FaaS 的场景:导购。导购的端侧场景特色是以展现为主,通常用户的行为主要是滚动屏幕浏览、点击商品跳转。因此它主要关注的技术点是:

  • 商品个性化推荐算法

  • 商品属性透出(数据补全)

  • 排序规则

  • 数据的分页获取

  • 数据或者权益的投放排期

因此基于这样的业务场景,FaaS 侧要作的主要工做是对服务的编排。

  • 字段映射:FaaS 侧的数据模型跟端侧的 View 模块之间的字段映射。

  • 模板管理:对某个业务模块的实例管理

  • 链接器:链接器是一些  if else 逻辑或者是一些具体的行为(好比数据请求)

模板管理和链接器是可视化编排的重要素材

而数据编排,是下游的服务能力,是给 FaaS 层提供数据的数据中台能力,不包含在 FaaS 应用层。因此咱们以前进行调研的时候,也是考虑导购的 FaaS 业务模型,是否能够套用到咱们闲鱼产品的 FaaS 模型中呢?

接下来我拿闲鱼的回收、寄卖业务来进行例举分析一下。

闲鱼的具体业务分析

以闲鱼的回收寄卖业务为例:这是两条类似的产品链路,他们共用 类目选择、问卷估价 等业务场景,但回收会多出 信用评估 和 代扣签约 这两个业务场景。

同时,回收寄卖涉及的类目也比较多,包括手机数码、你们电、图书、旧衣等。最后又有不一样的服务商对接,也会影响流程页中的渲染和交互方式。并且不一样的类目,对应不一样的业务方和运营同窗,他们的产品策略也会有些差别。因此看似差很少的链路里,包含了不少的差别性因子。若是把这些因子作统一的数据处理和服务编排,可想而知,前端的逻辑会变得很是复杂,而且难以维护。因此现有的 FaaS 框架不足以管理复杂的产品链路。

综合分析下来,是缺乏两个关键的组成:

  1. 缺乏富交互场景的通讯方案。由于复杂交互,用户的行为响应,端侧只剩下 View,没法进行直接处理,是须要频繁跟 FaaS 进行通讯才能实现状态的变动。

  2. 缺乏一套 FaaS 侧的业务框架来抽象和管理整个业务。

产品交互与 FaaS 的通讯模式

因此基于以前的分析和过后的思考,咱们设计出一个模型。这个模型里,最关键的两个概念是:业务框架 FaaS Story 和数据处理框架 Nexus,这两个概念都在 FaaS 侧进行抽象和实现,而后经过一个咱们命名为 Logic Engine 的模块,跟端侧进行通讯。

能够看到图中的整个数据链路:端侧的 page state,管理了组件的属性和事件配置,这里的 Action 并非一个 Function,而是对事件函数的一个配置,端侧的 Logic Engine 会统一解析这个配置,并调用统一的事件函数,组装成统一的数据包,向 FaaS 发起请求。

数据到了 FaaS 侧,仍是由 FaaS 侧的 Logic Engine 进行数据包解析,路由匹配到对应的函数进行处理,函数基于 FaaS Story 这套业务框架进行设计,最终把处理后的信息再次经过 FaaS 侧的 Logic Engine 打包返回给端侧,端侧的 Logic Engine 进行解析处理,最终响应具体的 Action(好比更新页面状态,或者是发起另外一次数据通讯,又或者是调用某个容器的 API 等)。

业务模型 FaaS-Stroy

基于这样的模型,咱们把以前回收寄卖业务映射过来。整个业务,咱们咱们就定义为一个故事(Story),这个故事有多个 Scene 组成。

这里 Scene 的概念并非一个单页的概念,而是根据业务来进行定义的,一个页面可能会承载多个 Scene,后面也会例举多 Scene 的业务场景。

一个 Scene,主要由数据模型 Model、编排逻辑函数 Convertor 以及渲染逻辑函数 Render 组成。Model 映射原始的接口数据(也就是服务端的领域模型),一个场景函数中,可能会获取多个 Model;Converter 处理业务逻辑并输出跟端侧 page state 一致的结构,它的做用也就是 MVVM 结构中的 ViewModel;Render 运行在端侧,最终渲染页面并挂载事件。咱们的 Nexus 框架实现了统一的端侧事件模型,UI 组件的事件函数一样可使用这个事件模型,也就是以前提到的 Action 配置,这样组件从初始化到交互均可以由 FaaS 控制。

Story 的函数管理方式相似于端侧 APP 的概念,能够全局上对这些 FaaS 函数进行管理和编排,好比提供一套统一的配置,来定义路由,以及处理函数之间的流转关系。

一个多场景的页面

以前在介绍闲鱼业务的时候也提到了,咱们有不少的类目,不一样的类目,虽然主链路基本一致,每一个类目对应的品类属性差别仍是比较大的,这会影响页面的渲染和交互。同时不一样类目对应的业务方也不一样,产品策略和营销策略都会有差别。好比有些类目的下单是须要上门取件,有些类目的评估流程中是须要拍照鉴定,有些类目在某个节日要作个特殊的活动等等。

因此咱们从类目的纬度横向分割了 FaaS 函数,不一样类目的个性化逻辑在本身的 FaaS 函数中独立管理,相互之间互不干扰。他们的公共部分被抽象到了公共类库,或者一些工具类库。

Nexus 框架

Nexus 是一种一体化应用开发协议,用于解决 UI / 逻辑 分离下,端 / FaaS 跨系统函数调用的问题。在它上面能够长不少的,基于特定业务场景的框架,好比上面介绍的 Story,还有另一个同事编写的基于 fish-redux-view 结合 Logic Engine 的 Nexus Framework。

首先说一下为何会有这样一个协议:你们能够看上图左边,在端侧长时间的发展过程当中,你们都在致力于解决 UI/逻辑 如何更好得分离的问题。无论是最先的 MVC,仍是 MVP,以及 MVVM,都想要解决这个问题。可是不管端侧如何解决,严格得执行各类框架,被分离的逻辑都只是那部分存在于端侧的业务逻辑。

咱们若是把目光放得更大一些,会发现,端侧的逻辑是分离出去了,可是网关层的呢?实际上大部分端侧请求的接口,无论是下发数据,仍是写入数据,都不是直接面向领域层的调用。而是会通过网关再进行一次逻辑处理。最典型的好比,页面数据请求,几乎不多有页面去直接面向领域层的多个接口直接进行数据请求。那么为何呢?由于领域是面向具体的领域问题进行的设计,而端侧须要是面向UI展现进行设计,这两边的设计自然是后鸿沟的。简单一点说,领域层下发的不少数据,端侧是不要的。而一般一个领域接口没法知足一个页面所须要的全部数据。因此才须要通过网关层进行处理。

因此你们发现没有,无论端侧怎么作分离,总会有一部分逻辑存在于网关层。那么若是网关层把全部领域数据都处理成 VO 给到端侧好很差?固然好了,可是后端的同窗就不乐意了。一来这个 UI 不是我写的,我还得跟你沟通你须要点啥,每次 UI 改动还得我跟着改。二来写这些东西我也没啥成长。因此更多的时候,是端侧一部分处理逻辑,网关层再作一部分。

这就带来了一个完整业务中 UI / 逻辑 实际并不分离的问题。同时也会形成先后端在沟通、协做上的诸多问题。基于以上,咱们思考的是,那否则就不要后端同窗来写网关层了,用 FaaS 让前端的同窗上去写,本身要什么本身最清楚,也少了协同和沟通,提高效率,还能公用一部分的代码。这就是一体化编程模型的来源。也就是左边的图所表达的。

如今咱们打算让 FaaS 来处理全部的业务逻辑。还有两个问题须要解决:

  1. 事实上,业务如何被驱动,都来自于端侧的事件。那么 FaaS 如何感觉到来自事件的驱动

  2. FaaS 的处理结果,最终是要在端侧产生 Effect 才行,而这些 Effect 基本上只能由端侧来执行。

因此咱们必定是须要一个通讯协议,来让端可以调用到 FaaS 上的逻辑函数,也能让 FaaS 可以调用端侧的函数产生 Effect。

整个协议在数据部分基于 NexusBinderAction 体系,将每个 Action 映射到一个逻辑 Handler 上。Action 是一种数据信息载体,它内部的信息实际上体现的是调用一个函数所须要的“函数签名”和“函数入参”两类关键信息,某种意义上来讲,它也是一种“跨系统的函数调用”协议。

可是它与传统的 RPC 协议之间,有什么区别,或者特色呢?这与端侧 UI 编程的特征有关。在端侧编程中,咱们发现,有三类的函数调用是能够被概括的: 一、调用一个后端函数(即执行一段业务逻辑) 二、调用端侧的公共能力函数 三、修改数据并从新渲染。

这三类函数是端侧编程中被大量重复使用的函数,尤为是第三类。UI = F(state)。这些的背后都隐含着一个操做,就是业务逻辑操做。无论是由某一个动做触发的状态改变,仍是网络请求,仍是诸如 dialog,页面跳转,这背后都须要一些逻辑代码来进行判断和修改。

基于对端侧编程中经常使用的函数调用的抽象,咱们获得了一个 Action 的有限集合。以及支撑这个协议的库 Logic Engine。这里的“有限”很是重要,若是开发者在每次开发一个页面的时候都须要重复得定义大量的 Action 和 Handler,那么一来会增长开发者的负担,二来会出现不少重复的代码,也不符合软件开发中 DRY 的原则。

这样开发方式实际又回到了原始的 Req -> Do something -> Effect,这条路上。因此整个 Action 体系中包含的种类必定要很是少,但它却能够支持大部分的端侧编程中对逻辑函数调用的需求。也就是如今 Nexus 协议的样子。

根据上面对 Nexus 协议的抽象,能够很明显得看到,不管运行在什么环境中。有两种 Action 的处理逻辑是大致上不变的:

  1. 对于 remote - req 来讲,它的逻辑就是解析出 Action 中与请求相关的 apiname、apiversion 以及 params 部分,而后调用一个外部注入进来的网络请求函数把这个调用发送到 FaaS 上去。能够看到,调用一个远程的 FaaS 函数的过程大致上是不变的,也就是 remote - req,Handler 的逻辑能够内置在 Engine 中的的基础。

  2. 从一个 state change 的 Action 中提取新的 UI state,并提交给 UI 进行更新。

另外两种内置的通用 handler 并不来自于 nexus 协议的抽象,而是脱胎于平常开发。

  1. state-diff。它的逻辑是把一个 json patch 合并到当前的 state 中,产生一个新的 state 并提交给 UI 去更新。由于整个端调用 FaaS 的过程当中,FaaS 做为无状态服务自己并不存储状态,那么全部计算所须要的状态信息都会由端发送到 FaaS 上。可是 FaaS 在计算完新的 state 以后,并无必要全量得返回状态数据,自己端侧就保存有 state的原始版本。那么咱们考虑,是否是能够经过 diff 的方式让端侧合成一份新的 state,能够有效得减小远程调用的下行流量。为此,咱们根据 RFC6901/6902 中关于 json pointer 和 json patch 的规范,在 dart 上实现了json_patch 库。咱们在闲鱼内的“下单页”作了测试,“修改地址”操做将会涉及“地址信息”、“红包”、“运费”、“最终价格”数据,使用 diff 后大约能够减小 50% 的下行流量。

  2. 在一体化开发中,咱们发现,常常会须要在端侧顺序得执行多个 action。最简单的一个例子,端侧在进行 FaaS调用的时候一般都会 show 一个 progress 来阻塞用户的后续操做,那么当 FaaS 执行完业务逻辑并返回准备好的 state 数据以后,会经过 engine 让页面进行绘制。可是慢着,你们有没有发现哪里不对劲,这个 progress 还 show 在那里,谁来执行 hide progress 的操做呢?因此这种时候 FaaS 会须要下发一个batch类型的 action,顺序得让端侧的 engine 执行 UI update 和 hide progress。

固然开发者能够基于 batch 作更多复杂得 action 编排。因此本质上 batch 类型的 action 提供给开发者一个顺序编排执行逻辑的能力。避免了屡次来回请求的开销,也会让执行逻辑变得更加得清晰。

上一页咱们已经把 nexus 协议想要解决的问题说清楚了,那么做为具体执行协议的 Engine,它所须要提供的功能就很是明显了。首先它必须可以执行一种协议到具体逻辑代码的映射功能,这里面包含了协议的解析、函数的映射、函数的执行。最后咱们还须要给它加上执行上下文的管理功能。上图中的红色部分,是 Engine 对外提供的功能,包括容许外部进行函数注册,以及外部能够调用某个 API 来进行函数调用。

绿色的部分为 Engine 内部须要提供的功能,包括协议的解析、目标函数的匹配和执行上下文的管理。函数注册、执行函数、解析消息、函数匹配这四个功能是比较容易想到的。对于执行上下文管理功能。Engine 实际上除了绑定了 Action 这种协议载体以外,并不绑定任何的框架或者端侧环境,也就是说对 Engine 来讲,你运行在 android native 仍是 flutter 环境,对它都没有影响,它依然能够完成本身对接 Action 协议的使命。这种设计是为了尽量得给 engine 解绑,也能够释放 Engine 的能力以提供业务方进行上层框架的自定义。

最后也是这里把它单独领出来的一块,橘黄色的部分,“内置的通用函数”。也就是上面所说的 remote-req、state-change、state-diff 和 batch。

代码演示

端侧的逻辑代码

端侧的 UI 跟 ViewModel 的数据模型映射

FaaS 侧的逻辑

研发一体化及热部署

问题思考

Q:端侧交互的时延

A:减小通讯次数,不涉及业务逻辑的行为,在端侧完成。好比曝光、点击埋点。

Q:端侧通讯的时序

A:经过,控制端侧的请求顺序,来保证数据的有效性:阻塞交互(Loading)。关于异步时序的问题,咱们内部也有一些讨论,好比能够经过 CAS(Compare and Swap)或者事务的方式来保证通讯数据的有效性。** **

  • Q:会话状态的保存

  1. 借助独立的 BaaS 服务存放须要的数据。须要引入 BaaS 服务,应用成本相对高一些,并且缓存的有效期不太好控制,存储量也比较大。可是稳定性和灵活性较强。

  2. 轻量的数据存放能力能够利用端侧的页面生命周期,咱们在 Nexus 的通讯协议里能够自定义须要页面生命周期内持久化的数据,而且在请求的时候按需传递这些数据。

——————————————————————————————————————————————————————————————————————————————————————————————————————————————————

 

* **技术成长缓慢,遇到职业瓶颈**

前端知识迭代快,但本身前端知识掌握不成体系,没法快速掌握应用,成长缓慢

* **想跳槽大厂,但没方法也没方向**

一直重复初级的工做内容,项目经验缺少,达不到一线大厂的能力要求

 

在这里送你们一波福利


在这里特意讲我本身这两个月整理的相关面试题分享给你们,免费获取哦~

 

 

 

 

 

获取方式:

1、搜索QQ群,前端学习交流群:954854084

2、点击加入,与前端大牛一块儿进步!

3、QQ扫描下方二维码!

相关文章
相关标签/搜索