本次分享主要分为业务背景、BFF核心架构、基于Serverless的BFF改造、总结四部分。业务背景我们的供应链场景中有很多供应商,每个供应商都有物流、资产、仓储等多个领域,我们对这些领域的后端是基于DDD领域模型做微服务的,前端目前正在开发中在面对这些供应商使用的中后台应用时,我们遇到了以下问题:页面展示需要请求多个域:比如商户的详情页可能需要同时请求存储数据和资产数据为了显示一个页面。接口格式不满足前端需求:后端微服务面向多个项目并通用后,其接口格式可能不满足前端需求。前端需要自己做转换,比如单位转换,字段归约。需求变化快:业务快速迭代,需要大量的接口支持,而我们的后端领域是多项目化的,变更成本高,需要更多的测试。这时候,如果有一个中间层,来做这些事情,那么效率会得到更好的提升。部门协作成本高:有些需求需要其他部门后端同学的支持,而其他部门的同学由于自己部门的需求紧张,排得满满当当,导致我们的需求延误。如果此时有中间层,在中间层,请求其他部门提供的领域服务,将数据组合起来,提供给前端。这时候前端就可以自己完成需求开发,不需要其他系的同学参与,部门之间的协作成本会大大降低。基于以上背景,前端侧引入了BFF架构,BFF架构可以做什么:业务编排:从后端域的多个接口获取数据输出到页面。例如商户详情页,既需要存储数据,又需要资产数据。这个时候我们在BFF层请求存储和资产数据,组装到前端。字段转换:字段过滤、数据格式化等。比如资产域中的商户名称字段为businessName,存储域中的商户名称字段为shopName。这时候可以在BFF层进行统一,前端不需要做判断。个性化数据:为前端提供个性化服务,如数据压缩、单位转换等。BFF核心架构上图为BFF核心架构图。前端是中后端应用,后端域是后端服务,右边的工具支撑是公司的一些基础公共服务,中间是BFF核心实现。我们从上往下看:业务:业务编排、字段转换、自定义等业务逻辑可以在这一层完成,同时提供了一个node-auth包,可以用于用户认证。基础框架:这一层基础框架主要封装了npm包node-soa,包括node-log日志工具、node-hook代码规则校验工具、node-zk集群链接工具等。Node框架:选用Koa2对于节点框架。调用链路核心架构说完,我们再来看整个BFF架构的调用链路:调用链路从上到下,我们的中后台应用通过HTTP向Nginx服务器发送请求,Nginx转发到BFF层,BFF层通过RPC调用后端域的微服务完成整个调用过程。这里需要解释两个概念:ZooKeeper:可以简单理解为一个服务注册中心,后端所有微服务统一注册到这个注册中心,然后BFF层作为ZooKeeperClient进行连接到这个注册中心。在注册中心提取各个服务的Host和Port,拿到Host和Port发起RPC调用。RPC:remoteprocedurecall,也就是两台服务器A和B,一个部署在服务器A上的应用需要访问服务器B上一个应用的某个方法,由于不在同一个内存空间,所以需要表达通过网络调用传递的语义和数据可以简单理解为我们的BFF应用部署在A服务器上,我们的微服务部署在B服务器上。RPC通信协议可以基于HTTP或TCP协议。我们使用gRPC,它是一种使用HTTP/2的RPC调用方式。以上介绍了BFF的核心架构和整个调用环节。下面我们来看一下node-soa的具体实现细节。服务初始化通过调用node-soa提供的init方法完成服务初始化,其中dep为各个后端域的微服务。我们在看init的具体实现:先创建一个ZooKeeperClient连接ZooKeeper集群,连接后通过listChildren方法枚举ZooKeeper集群的所有子节点,得到子节点的Host和Port后创建grpcClient节点,然后就可以通过这个grpcClient发起RPC调用了。服务调用服务初始化后,可以发起RPC调用。node-soa提供了request方法,可以通过该方法发起RPC调用,其中service为后端域,method为java端提供的方法。我们看请求中的具体实现:通过服务开始时创建的grpcClient发起RPC调用,解析并返回数据,即完成一次RPC调用,使用Jaeger+OpenTracing在整个RPC调用过程中做调用链路跟踪,使用node-log做请求日志入盘。以上就是我们第一代BFF架构的核心内容。这种架构在当时的业务背景下是一个比较好的解决方案,但是随着业务的快速发展,这种架构也遇到了一些问题:运维成本的增加大:随着BFF应用的增加,需要更多的机器部署BFF应用程序。发布流程长:添加BFF接口需要完整的编译、构建、部署流程,无法实现秒级部署。域名不收敛:每个BFF都有自己独立的域名,增加了内存开销。针对这些痛点,我们引入了SFF(ServerlessForFrontend)架构。通过在Serverless之上构建BFF,我们用云函数取代了传统的基于NodeJS的BFF层。上图是基于ServerlessBFF修改后的BFF架构。相比第一代BFF架构,这里多了两个部分,一个是FaaS层,一个是开发者平台。开发者平台用于在线编写云函数。主要提供功能管理、发布管理等功能。发布的每个功能都将保存在数据库中。FaaS层主要是函数。当请求一个BFF接口时,它首先去数据库中获取对应的函数,然后执行该函数。实施方案选择目前主流的方案主要是基于容器和基于流程。容器方案:基本实现方式是使用K8s+Docker。当每一个云函数执行时,都会启动一个容器来执行。执行后,容器被销毁。整个容器的管理、并发处理、扩缩容都由K8s管理。进程解决方案:每个云函数的执行都会启动一个新的进程执行,进程执行完销毁。对于实施方案的选择,需要考虑以下几个方面:业务场景的复杂性:最好使用高并发的容器方案;并发少,选择的流程更轻,更容易实现。基础设施&运维能力:容器方案对基础设施和运维能力有更高的要求。需要考虑公司的运维是否可以覆盖。团队人力/能力:基于容器的方案技术要求会更高,落地难度也会更大。需要考虑团队成员是否有这方面的经验,团队是否有足够的人手。我们的业务并不复杂,中后台应用几乎没有高并发。目前公司还没有推广使用集装箱,团队也不是很人性化。再加上缺乏容器实践经验,我们最终采用了基于流程的方式。完成。基本实现如下:用户发起HTTP请求,Node主进程去数据库读取请求的函数实现。得到函数实现后,会fork一个子进程来执行函数,函数执行完毕后销毁子进程。这里需要注意的一点是控制子流程的执行时间,防止因为函数执行异常导致子流程无法销毁。我们来看一下执行函数的具体实现:通过VM2模块执行我们的云函数,从而保证子进程和主进程的Context隔离。如果没有隔离,可能会出现这样一种情况,子进程调用了process.exit(),此时我们的Node主进程就会退出。仅仅隔离进程的上下文是不够的。我们可以使用进程池来优化每个fork子进程的时间,使用CGroup来限制子进程的CPU使用率、内存使用率、磁盘IO。CGroup是Linux内核中的一项核心能力。它提供了对不同进程进行分组管理的能力,可以限制不同分组使用的计算资源(CPU、内存、磁盘IO等)。我们可以限制执行函数的子进程可以消耗的最大内存、磁盘、网络带宽,同时控制进程可以使用的最大CPU使用率,保证稳定性系统的。最终实现如下:以上就是基于serverless的BFF改造的核心内容。与第一代BFF架构相比,基于Serverless的BFF改造有以下优势:效率提升:云函数独立,动态规划,秒级部署。降本:应用融合,有效降低运维和机器成本。总结以上就是本次分享的主要内容。总结一下:后端领域微服务之后,需要一个能够提供业务编排、领域转换、个性化定制的机制来保障业务的快速迭代。BFF架构可以有效实现业务编排、领域转换、个性化定制,让前端进入全栈领域。基于Serverless构建的BFF进一步降低了运维和机器成本,提高了人的效率。(本文作者:赵存)本文由哈尔滨技术团队制作。未经许可,不得转载或商用。如非商业目的转载或使用本文内容,请注明“内容转载自你好技术团队”。
