【AWS】Lambda 初识与服务部署

最近工作内容有关将老板vibe coded的一个Next.js服务做AWS部署(AI牛马都知道这活有多脏,哈哈),团队的基建技术栈是AWS,方向是serverless,于是花了一些时间学习相关知识概念,在本篇做个记录。

概念

1. BFF是什么?

Backend for frontend。

我的理解是它是一个静态React 前端和后端交互的一层gateway,它的主要工作是接收前端请求、调用下游真正的后端 (BE)、整合数据。

如果我们牢记代码工程的一条金律:没有什么是再加一层中间层做不到的,就能意识到对于前端项目,多加一个BFF一定视为了解决单纯React SPA做不好的事情:在微服务架构中,如果让纯前端做数据整合和多接口的调用、重试这些逻辑,React负担太重。那么为了解耦,引入BFF,视为了React 层尽可能只做渲染,而BFF处理与后端的逻辑调用和数据处理。

2. Next.js是什么?

Next.js 是一个基于 React 的全栈 Web 开发框架, Next.js 把 React 从一个"前端 UI 库"升级为可以处理路由、数据请求、服务端逻辑的一站式应用框架。

另一个概念叫Node.js,Node.js是JavaScript的运行时,它可以让JS代码脱离浏览器在服务器上运行。和Next.js的关系就是Next.js实际上试运行在Node.js上的。作为一个老Java人,我简单理解,Next.js相当于Spring Boot, node.js相当于JRE。

Next.js相当于把React和BFF打包了,一般一个Next.js项目就默认为React + BFF,而因为BFF这里因为更强调它服务器的角色,所以用"node.js"层表述。

3. Stateless是什么?

简单理解,state是状态,或者说数据,服务器本身带有对数据的管理和持有,就是stateful,没有就是stateless。比如如果你在服务内存里存了数据逻辑,或在服务器存了本地文件,里面有用户数据,那么服务就是stateful的。在原来的项目中,关系型数据存在SQLite文件中,而文件是在服务器路径下的,那么它就是stateful的。如果所有数据都单独管理在服务器之外,单独存在数据库服务器中,那么server本身就是stateless的。

微服务架构下,用stateless server架构比较多是因为请求和服务器不是持久绑定的,同一个用户的2次请求可能打到不同的服务器上,如果是stateful的,那么就得要求两个请求达到一个服务器上才好管理。

也不是所有服务都会采用stateless架构,我印象中websocket就是stateful的,服务器会维护链接和用户的关系。

4. Serverless是什么?

serverless,无服务器,一个基础架构概念。之前微服务,stateless我觉得更多是工程概念吧,就是代码级别的架构选择。但是这里serverless,到了物理层面,服务器上,就说明它是一个infra概念。它强调工程师可以不去关心服务器本身,而把代码逻辑只考虑为运行函数。

有点抽象,我理解了几天,大概就是常规的server,我们都是在服务器上一直运行,如果有请求来了,服务器处理,请求没来,没有流量,它也一直待机。这种对高并发逻辑,服务器一直运行也没毛病,毕竟服务器荷载率很高。但是如果服务器的QPS比较低,或者不稳定,为了节约成本,让服务器资源更物尽其用,最开始会有根据高流量时段缩容和扩容,比如电商大促就申请更多的服务器,平时就缩小服务器集群规模。

现在serverless把这件事做到极致,就是有请求来,拉起一个服务器处理,处理完了销毁,也就是说如果没有流量,其实没有服务器在空跑,节省资源。当然听起来它会有一个overhead,就是启动服务器的过程会给用户带来延迟,不过这就是tradeoff了。这个过程是云服务厂商管理的,对于开发来说不需要关心,属于责任分离了,separation of concern。

注意,serverless部署的前提是服务本身是stateless的,否则,如果server实例保存了用户状态,被销毁了不就找不着了。

5. AWS Lambda是什么

这个serverless概念做成的云服务产品,AWS上叫Lambda。它让你可以只上传代码,无需管理服务器,代码会在事件触发时自动运行、自动扩容,按实际执行时间计费。

Lambda 是还在发展中的产品,可能一些feature会变,不过它目前是有限制的,它单次执行最长 15 分钟,内存最大 10GB。超过15min没完成,lambda实例会被销毁,所以需要较长计算过程的服务不适合用lambda部署,或者说得想别的办法适配这个运行限制。与之对比的是Fargate这种容器,单进程可以一直运行直至结束。

多说一句这里从直觉上,这个按需拉起是pub-sub模型,也就是说lambda本身是事件驱动的。上游比如gateway,接到URL请求时发送率事件,下游有worker消费事件,完成对Lambda实例的拉起。所以对Lambda 上部署的逻辑来说,需要做的就是把逻辑subscribe到事件源,这样你的逻辑就可以触发事件,完成对Lambda的驱动。

6. AWS Amplify Hosting是什么?

AWS厂商为前端应用,尤其Next.js应用提供的一个部署产品,它底层是基于Lambda的,也就是serverless的。同时Gen 2会进行全链路整合,CICD,数据对接等等,说白了就是打包到一起卖一个产品,这样工程师需要单独管理的东西比较少,这样比较开箱即用。

AWS Amplify Hosting 底层是基于 CloudFront (CDN) 和 Lambda 函数来运行你的 Next.js SSR 逻辑的,也就是说它是网关+serverless的模式,有两层。

决策

把一个纯vibe-coding项目接管,并做云部署,需要根据团队的技术方向做很多决策,它们在技术上不一定绝对正确,但是会是在团队架构下的最优解。

前提:这是一个Next.JS全栈项目,有React写UI模块,node.js BFF,以及SQLite 做存储。对于agent后端(真正的BE),调用的是外部的REST endpoint。

方向:尽量开箱即用,能托管不要自己处理,向serverless方向走,尽量省钱。

1. 对于Streaming response的处理

现在的很多项目都是会连Agent (LLM)后端的,而对于LLM请求,有一个特点就是streaming,它可以持续很久,并且是流式返回,不是一股脑都返回在response里。

那么回到项目本身,这个项目它是会调用多个agent endpoint完成流式返回的。这个逻辑原来在BFF层,也就是说浏览器的请求会经过BFF层转发重新路由去调用真正的Agent BE。

browser -> Stateic React -> Next.js(BFF) -> Streaming Endpoints

如果这个服务部署在Amplify这样的产品上,则是:

bash 复制代码
browser -> Amplify -> Amplify(Lambda Serverless) -> BE ednpoint

这里要注意,从BE上来的每一个中间节点都要支持流式响应,才可以保证用户在浏览器也拿到流式响应,而不是buffered响应。

常见的手段:先用Smoke Test测一下Amplify对流式输出是怎么处理的。这一块交给agent写个脚本,部署到Amplify上,然后请求一下发现并不是流式输出的,是在响应完成的那一刻直接返回的。

这里其实Amplify在部署BFF的时候,里面有两层,一层CloudFront, 一层Lambda,这两层可能都进行了buffered输出,也可能只有CloudFront进行了buffer输出。(因为Lambda最新支持了Lambda Streaming Response)。

我的感受是目前Amplify对streaming的支持其实并不好,那么在这种情况下就要考虑agent endpoint的调用,要剥离开这套双层架构。如果此时去问AI,AI给了我Lambda Function URL的方案。这里我不展开了,因为我采取了另一个方案:agent调用逻辑从BFF层拿开,变为直接从浏览器调用。

2. BFF层的定位

AI的方案让我意识到保留streaming endpoints在BFF里的方案是多加一层Lambda,但是BE已经是流式访问了,联想到Lambda的冷启动问题,我似乎觉得在这里加一个Lambda 层完全没有必要,既然SPA可以直接调用streaming并渲染结果,那么不如把streaming endpoint的控制权上移到React层,这样BFF层没有关于streaming response的处理,用纯净的物理管道保障数据流,就变得轻巧很多。

由于这个项目不是我最开始搭建的,所以最开始没有直接想到这个方案,但一旦考虑剥离streaming endpoint,就其实对BFF层的定位有了更明确的规划。它的逻辑集中处理项目本身的CRUD逻辑,以及其他非streaming HTTP的调用,运行在Lambda上就是合理的。

BFF的定位因此可以总结为:

  1. stateless routes
  2. non-streaming http routes
  3. CRUD routes
bash 复制代码
browser <---> 前端 React <---> Amplify (内置 CloudFront 网关) <---> BFF Lambda (Next.js) <---> BE
												^																													^
												|																													|
												----------------------------------------------------------------

踩坑

  1. CORS问题
    当我们把streaming endpoint上移到React层,失去了BFF层的路由的时候,就会产生跨域问题。
    我也真的这次才明白什么是CORS跨域问题。
    CORS(跨域资源共享)纯粹是"浏览器"的安全限制机制。服务器和服务器之间通信,是不存在 CORS 限制的。也就是说BFF存在的时候,BFF向后端的调用是没有CORS限制的,它们是服务器间通讯。
    但是当streaming endpoint上移到浏览器层的时候(React代码运行在浏览器,当它直接调用后端streaming endpoint的时候,使用原生FetchAPI执行的),这时浏览器调用BFF可能是域名A,而直接调用BE可能是域名B,A和B不是一个域名,因此出现了跨域问题。

它的机制是这样的:

  1. 浏览器会先把你真正的请求扣下来。
  2. 浏览器代替你,向 BE的ALB 发送一个探测请求(HTTP OPTIONS 方法,俗称 Preflight 预检请求),问 BE:"嘿,有个域名A 想拿你的数据,你允许吗?"
  3. 如果此时BE的 ALB(或者后面的容器代码)没有明确回答:"我允许(返回 Access-Control-Allow-Origin: 域名A)",浏览器就会直接报错拦截,把连接掐断。这就是你看到的 CORS 报错。

看懂这个机制的话,破解CORS也很明白,就是从BE那里把域名A允许放行就可以了。具体来说在BE的ALB层或者容器代码层应该都可以。问AI就好了。

TBD

另外关于数据库的迁移这篇没有处理,因为我还没有干到那......,做完再总结吧。

至于数据库为什么会迁移?回顾一下,Amplify底层对BFF的部署是serverless的,也就是说代码逻辑也得是stateless的,那么SQLite 就不行啦,这个就是个文件和服务实例在一起的。要找单独的数据层解决,因此要做数据迁移。由于技术栈和团队方向,大概率是要走向DynamoDB了,没有关系型数据库的日子,我会想它的。哈哈。

这个任务我遇到的最大困难,是对vibe-coding项目代码本身的不了解,它经过了上层vibe-coding搭产品,又经过了工程师的接管与迭代,已经不可避免的是屎山一坨。虽然整个部署过程我都是和CC一起分析决策的,但很多时候我需要先了解项目本身有什么,或者说我无法、也不能完全信任CC给我的决定,我需要学习,理解,然后指导CC做判断。

在这个过程中深切意识到,人类的时间和判断是真正的稀缺资源,当AI 全能,决定最终结果的还是人和人的差距。

相关推荐
向量引擎1 小时前
向量引擎技术文档给我的创作启发:AI搜索生态下的内容适配实践
人工智能·gpt·ai编程·ai写作·key
DS随心转APP2 小时前
AI 一键导出 Word 与 Excel 实战应用指南
人工智能·ai·word·excel·deepseek·ai导出鸭
云水一下2 小时前
JavaScript 从零基础到精通系列:流程控制、函数与作用域
前端·javascript
丷丩2 小时前
MapLibre GL JS第28课:PMTiles源和协议
javascript·gis·map·mapbox·maplibre gl js
之歆2 小时前
Day24_JavaScript正则表达式与性能优化实战:从入门到精通
javascript·性能优化·正则表达式
大雷神2 小时前
HarmonyOS APP<玩转React>开源教程三十一:示例项目下载功能
react.js·开源·harmonyos
柚子科技2 小时前
Vue3 响应式原理:我被 ref 和 reactive 坑了3次后终于搞懂了
前端·javascript·vue.js
小哈里2 小时前
【K8S】云原生时代的GitOps最佳实践 —— ArgoCD
云原生·kubernetes·云计算·argocd·基础设施
大鱼前端2 小时前
Veaury:让Vue和React组件在同一应用中共存的神器
前端·vue.js·react.js