# 2024 探索云开发及其应用

> 探索2024Serverless云开发的现状及其应用

- 发布于：2024-01-11
- 更新于：2024-01-11
- 分类：软件开发
- 标签：全栈
- 原文：https://chanx.tech/blog/2024-serverless

> 原来这就是云开发! Serverless?!

<!-- more -->

# 探索云开发及其应用

> 开始想分享一篇 AI 相关的文章，但是比较尴尬的是很多了解都是在应用层面，理论知识又不够丰富；
> 
> 后面在写 AI Demo 的时候想到部署这块，恰好在云开发上有些体验，结合个人理解编写本文



# 云开发

## 概述

> 引用腾讯云云开发的介绍


「云开发」是云原生一体化开发环境和工具平台，为开发者提供高可用、自动弹性扩缩的后端云服务，包含计算、存储、托管等 Serverless 化能力，可用于云端一体化开发多种端应用（小程序、公众号、Web 应用等），帮助开发者统一构建和管理后端服务和云资源，避免了应用开发过程中繁琐的服务器搭建及运维，开发者可以专注于业务逻辑的实现，开发门槛更低，效率更高。

## 优劣势

### 优势

- **弹性伸缩**： 根据应用负载的变化，自动扩展或缩减计算资源，提高了稳定性，满足并发场景。

- **无服务器架构（Serverless）**： 云开发采用无服务器架构，开发者只需关注代码的编写，无需关心服务器的维护和管理，降低了运维成本。Serverless 并非表示没有服务器，而是指开发者无需关心底层的服务器管理。

- **多环境快速部署**： 云开发可以将应用部署在全球范围内的服务器上，提供低延迟和高可用性的服务

- **性价比高**： 按使用量计费，无需支付闲置资源费用

### 劣势

- **平台强相关**： 使用云开发平台意味着对云服务提供商的依赖，依赖于平台提供的环境和功能进行开发

- **定制化能力不足**： 一些云开发平台为了提供简便性和快速开发，可能限制了一些高度定制的选项，对于一些特殊需求的应用可能不够灵活

- **数据隐私和合规**： 对于一些行业或地区的应用，特别关注数据隐私和合规性。使用云开发平台时，需要确保选择的平台符合相关法规和标准

- **网络延迟**： 尽管云服务提供商通常有全球性的数据中心，但仍然可能存在一些网络延迟，对于某些对延迟非常敏感的应用可能会成为一个问题

### 应用场景

- 轻量应用后端： 通过无服务器框架构建灵活的后端服务

- 事件驱动处理： 处理异步事件，如消息队列、文件上传

- 定时任务： 周期性执行

## 选择

云开发平台那么多，选择一个平台需要考虑哪些方面？几个常见的考虑点：

- 功能多少：是否提供满足业务场景的能力或者说可以定制化开发的能力

- 迭代速度：平台开发功能是否稳定，直接影响开发者体验

- 环境稳定：决定线上生产环境是否稳定，直接影响应用稳定性和用户体验

- 费用核算：付费方式及各种计费资源的价格

通常找到一个大公司背书的云开发平台就能解决以上问题，但受制于多方面的因素影响，还是需要找到更适合当前场景的平台并做好备份迁移等措施。

# 产品分析

> 这里列举的主要是云开发平台，部分平台只是函数计算


## 海外

## Google Firebase

官网地址：https://firebase.google.com/

Firebase 是一家实时后端数据库创业公司，它能帮助开发者很快的写出Web端和移动端的应用。自2014年10月Google 收购 Firebase以来，用户可以在更方便地使用Firebase的同时，结合Google的云服务。

虽然Firebase本身是一个应用开发的平台，但一个亮点功能是 Analytics（分析），所以面向的不只是研发人员，还有运营人员，包括一些市场投放人员。

![JWQcbntmfooUNsxpT23cb1gun7e](https://cdn.chanx.tech/image/JWQcbntmfooUNsxpT23cb1gun7e.jpg)

## Google Cloud

官网地址：https://cloud.google.com/compute

谷歌提供的云函数计算服务，结合平台提供的其他云服务能力实现应用开发

## AWS Lambda

官网地址：https://aws.amazon.com/lambda

亚马逊提供的云函数计算服务，结合平台提供的其他云服务能力实现应用开发

## Azure Function

官网地址：https://azure.microsoft.com/en-us/products/functions/

微软提供的云函数计算服务，结合平台提供的其他云服务能力实现应用开发

## 国内

### 阿里云 - 云开发（已下线）

> 云开发平台即将停止对外服务，如果需要云开发相关的功能，您可以根据自己的需求使用阿里云其他云产品提供的同类工具：函数计算应用管理、云效AppStack、SAE应用管理


官网地址：https://workbench.aliyun.com/

云开发平台下线，但是其他云服务可以满足同样的能力，服务对个人和企业可用

![QD2ObgxqkoxcW5xmLUBciTqOnAc](https://cdn.chanx.tech/image/QD2ObgxqkoxcW5xmLUBciTqOnAc.jpg)

### 字节跳动 - 轻服务（已下线）

> 轻服务将从2022年5月14日起停止服务创建，2022年6月14日23点59分停止服务并删除所有资源


官网地址：https://qingfuwu.cn/

字节跳动旗下云服务平台「[火山引擎](https://www.volcengine.com/)」提供函数服务，但服务对象是企业，**不面向个人开发者**

![WHEBb3JkDoohshxOdd9cc6JTnD4](https://cdn.chanx.tech/image/WHEBb3JkDoohshxOdd9cc6JTnD4.jpg)

![LcO9biKA2onnuSxkvwUcgMYhned](https://cdn.chanx.tech/image/LcO9biKA2onnuSxkvwUcgMYhned.jpg)

![EryobiPP9oDzxBxahapcMniFnVf](https://cdn.chanx.tech/image/EryobiPP9oDzxBxahapcMniFnVf.jpg)

### 腾讯云 - 云开发

官网地址：https://cloud.tencent.com/product/tcb

目前应该是国内大厂还在提供云开发平台服务的，从免费到商业化，经历过多轮价格调整。

![DLiybELu7oOO47xDThsc2xpNnig](https://cdn.chanx.tech/image/DLiybELu7oOO47xDThsc2xpNnig.jpg)

![X7I6b3V6YovhBWx8TVecdzFhnwb](https://cdn.chanx.tech/image/X7I6b3V6YovhBWx8TVecdzFhnwb.jpg)

### Aircode（已下线）

官网地址：https://aircode.io/

开发文档地址：https://docs.aircode.io/

开源组织：https://github.com/AirCodeLabs

不知道什么名字的团队在孵化的项目，提供一些的基础能力，还在功能快速迭代期，刚进入商业化不久

提供三个免费项目额度，超出需要购买收费套餐

![NIGSbN5aYoZ3EOxoduacSjmcnCc](https://cdn.chanx.tech/image/NIGSbN5aYoZ3EOxoduacSjmcnCc.jpg)

![PcigbynyToMAORxjFxLc8t5Xngo](https://cdn.chanx.tech/image/PcigbynyToMAORxjFxLc8t5Xngo.jpg)

### Laf

官网地址： [<u>laf.run</u>](https://laf.run/) （国内版） [<u>laf.dev</u>](https://laf.dev/) （海外版）

开发文档地址：https://doc.laf.run/zh/

开源组织：https://github.com/labring/laf

国内企业环界云计算孵化的项目，目前进入商业化阶段，新用户赠送一定余额体验

应该是国内除了大厂外做的最好的（或者说云开发不是腾讯云就是Laf？哈哈）

亮点：

- **代码开源，拥抱社区，函数市场**

- **支持私有化部署**

- 在线协作

- 支持 Websocket 链接

![AwXfbU1eXo3ZxKxVBvYcktuInxf](https://cdn.chanx.tech/image/AwXfbU1eXo3ZxKxVBvYcktuInxf.jpg)

![H5YEbStSsoQSVxxdDuVc0PqIncc](https://cdn.chanx.tech/image/H5YEbStSsoQSVxxdDuVc0PqIncc.jpg)

## 小结

可以看到上述云服务厂商或平台提供了主要的云能力：**函数计算、数据库、存储、CDN**

- 海外厂商基本都只提供单独的云服务能力，没有将服务聚合成云开发平台进行销售

- 国内大部分厂商都对云开发平台进行下线处理，将用户引流到自家的各种云服务能力

- 腾讯云考虑继续将服务聚合成云开发平台，笔者猜测原因是：

- 新的云开发平台收获好评，拥抱开源社区，发展势头强劲

以前比较火的还有「新浪 SAE」 ，不知道什么原因在国内市场渐渐消失。

![IRBXbrit7o5pUFxhJ6EcZvqVnVd](https://cdn.chanx.tech/image/IRBXbrit7o5pUFxhJ6EcZvqVnVd.jpg)

# 使用体验

实际上云开发平台功能的使用上基本一致，这里主要拿 **laf 平台 **举例。

30s 完成 helloword 接口的创建、开发、发布，如下图示例。

基于上述路径，我们可以编写「**云函数**」，实现自己的逻辑，快速完成上线

![OMoSb7qPDoCk3UxpCq1c5WVFnXd](https://cdn.chanx.tech/image/OMoSb7qPDoCk3UxpCq1c5WVFnXd.jpg)

需要注意的是，每次函数的执行都是在新的环境里，也就是两次函数的状态本身是不共享的，即无状态

但是一些数据需要进行持久存储，这时候需要使用到 「**数据库**」** **能力

平台本身封装数据库的能力并注入到函数的上下文当中，即下面代码中提供的`@lafjs/cloud`

```javascript
// 官方文档示例
import cloud from '@lafjs/cloud'
const db = cloud.mongo.db

export default async function () {
  const ret = await db.collection('users').insertOne({ 
    name: '王小波',
    age: 44,
    books: ['黄金时代', '白银时代', '青铜时代']
  })

  console.log(ret)
}
````

继续进行应用开发，可能遇到一些非结构化的数据或者说大文件的存储，数据库不能满足诉求了

这时候需要「**文件存储**」来解决这个问题，平台也把存储能力集成到 SDK 里

```javascript
// 官方文档示例
import cloud from '@lafjs/cloud'

export default async function (ctx: FunctionContext) {
  // 获取存储桶
  const bucket = cloud.storage.bucket('data')

  // 写文件，假设是一个大文件
  const content = 'hello, laf'
  await bucket.writeFile('laf.html', content)
}
````

云开发三件套组合技一使用，貌似应用后端的雏形就出来了，前端同学可以大声说：**~~后端不是有手就行？**~~

确实，云开发平台大大降低了开发的成本，只关注逻辑即可完成开发，一个人也可以是一支军队

除了上面说的三件套，平台也会提供其他的一些功能：

- 大多数平台都会提供的触发器，满足部分定时任务的场景

- 腾讯云额外提供兼容 Redis 协议的缓存数据库，满足高速缓存的场景

- Laf 平台提供处理 WebSocket 连接的云函数能力

尽管平台提供再全面的功能，但是云开发不是万金油，也会有不能满足的场景，这时候就要考虑其他方案

# 技术分析

> 感兴趣可以了解关于 laf 的一些题外话
> [laf 的发展背景和方向讨论 · labring/laf · Discussion #178](https://github.com/labring/laf/discussions/178)
> [laf next 重构计划说明 · labring/laf · Discussion #352](https://github.com/labring/laf/discussions/352)
> [laf product key & roadmap · labring/laf · Discussion #354](https://github.com/labring/laf/discussions/354)


从个人使用和理解的角度出发，对云开发平台使用到的核心能力进行简单梳理。

怎么从零开始跑通一个最简单云开发平台流程呢？

首先，核心能力分为云函数、数据库、文件存储三个方面

## 云函数

云函数，首先是一个函数，写在函数内的逻辑会被执行，实际上就是一段 ESM 代码

编写的函数需要默认导出，平台后续会导入并调用

```javascript
import cloud from '@lafjs/cloud'

export default async function (ctx: FunctionContext) {
  console.log('Hello World')
  return { data: 'hi, laf' }
}
````

### 平台能力注入

将各种实例封装成 SDK 引入到每一个云函数文件里，实现开箱即用，即`@lafjs/cloud`

```javascript
// https://github.com/labring/laf/blob/main/runtimes/nodejs/src/support/cloud-sdk.ts
/**
 * Create a new Cloud SDK instance
 *
 * @returns
 */
export function createCloudSdk() {
  const cloud: CloudSdkInterface = {
    database: () => getDb(DatabaseAgent.accessor),
    invoke: invokeInFunction,
    shared: _shared_preference,
    getToken: getToken,
    parseToken: parseToken,
    mongo: {
      client: DatabaseAgent.client as any,
      db: DatabaseAgent.db as any,
    },
    sockets: WebSocketAgent.clients,
    appid: Config.APPID,
    get env() {
      return process.env
    },
    storage: null,
  }

  /**
   * Ensure the database is connected, update its Mongo object, otherwise it is null
   */
  DatabaseAgent.ready.then(() => {
    cloud.mongo.client = DatabaseAgent.client as any
    cloud.mongo.db = DatabaseAgent.accessor.db as any
  })
  return cloud
}
````

### 云函数相互调用

- 正常编写 import 语句，使用相对路径导入

- 使用SDK的`invoke`方法。运行时会由`FunctionModule`加载并执行

```javascript
// https://github.com/labring/laf/blob/main/runtimes/nodejs/src/support/cloud-sdk.ts
async function invokeInFunction(name: string, ctx?: FunctionContext) {
  const mod = FunctionModule.get(name)
  const func = mod?.default || mod?.main

  if (!func) {
    throw new Error(`invoke() failed to get function: ${name}`)
  }

  ctx = ctx ?? ({} as any)
  ctx.__function_name = name

  ctx.requestId = ctx.requestId ?? 'invoke'

  ctx.method = ctx.method ?? 'call'

  return await func(ctx)
}
````

### 三方依赖

环境初始化后，执行命令安装 NPM 包；运行时通过`FunctionModule`加载依赖并执行

```javascript
// https://github.com/labring/laf/blob/main/runtimes/nodejs/src/init.ts
/**
 * Install packages
 * @param packages
 * @returns
 */
export function installPackages() {
  const deps = process.env.DEPENDENCIES
  if (!deps) {
    return
  }

  const flags = Config.NPM_INSTALL_FLAGS
  logger.info('run command: ', `npm install ${deps} ${flags}`)
  const r = execSync(`npm install ${deps} ${flags}`)
  console.log(r.toString())
}
````

### 沙箱执行

代码需要在沙箱环境执行，这里可以使用 **Node.js vm 模块**

Laf 函数调用的分析：[开源 Serverless 框架 Laf 性能优化实践](https://forum.laf.run/d/1146)

```javascript
// https://github.com/labring/laf/blob/main/runtimes/nodejs/src/support/engine/module.ts
import * as vm from 'vm'

export class FunctionModule {
  protected static cache: Map<string, any> = new Map()

  private static customRequire = createRequire(
    CUSTOM_DEPENDENCY_NODE_MODULES_PATH,
  )

  static get(functionName: string): any {
    const moduleName = `@/${functionName}`
    return this.require(moduleName, [])
  }

  static require(moduleName: string, fromModule: string[], filename = ''): any {
      // 加载依赖的逻辑，处理缓存、循环引用等
      // #1 平台封装的sdk
      // #2 云函数
      // #3 其他模块
  }
  /**
   * Compile function module
   */
  static compile(){
    ...
    // #1 包装代码
    const wrapped = this.wrap(code)
    // #2 创建全局对象上下文
    const sandbox = this.buildSandbox(
      functionName,
      fromModules,
      consoleInstance,
    )
    // #3 通过vm创建script脚本
    const script = this.createScript(wrapped, {})
    // #4 在沙箱内执行脚本，返回对应模块
    return script.runInNewContext(sandbox, options)
  }

  static deleteCache(): void {
    FunctionModule.cache.clear()
  }

  protected static wrap(code: string): string {
    return `
    const require = (name) => {
      __from_modules.push(__filename)
      return __require(name, __from_modules, __filename)
    }

    ${code}
    module.exports;
    `
  }

  /**
   * Create vm.Script
   */
  protected static createScript(){
    const script = new vm.Script(code, _options)
    return script
  }

  /**
   * Build function module global sandbox
   */
  protected static buildSandbox(){
    const sandbox: FunctionModuleGlobalContext = {
      __filename: functionName,
      module: _module,
      exports: _module.exports,
      console: fConsole,
      __require: this.require.bind(this),
      Buffer: Buffer,
      setImmediate: setImmediate,
      clearImmediate: clearImmediate,
      Float32Array: Float32Array,
      setInterval: setInterval,
      clearInterval: clearInterval,
      setTimeout: setTimeout,
      clearTimeout: clearTimeout,
      process: process,
      URL: URL,
      fetch: globalThis.fetch,
      global: null,
      __from_modules: [...__from_modules],
      ObjectId: ObjectId,
    }
    sandbox.global = sandbox
    return sandbox
}
````

### 函数调用

每一个函数对应着发布上线后的一个接口，有唯一的调用地址，`/:name`即是函数名称

请求调用的上下文，比如请求的 body 等，即`ctx: FunctionContext`

```javascript
// https://github.com/labring/laf/blob/main/runtimes/nodejs/src/handler/router.ts
import { Router } from 'express'
export const router = Router();
/**
 * Invoke cloud function through HTTP request.
 * @method *
 */
// router.all('/:name', uploader.any(), handleInvokeFunction)
router.all('*', uploader.any(), (req, res) => {
  let func_name = req.path

  // remove the leading slash
  if (func_name.startsWith('/')) {
    func_name = func_name.slice(1)
  }

  // check length
  if (func_name.length > 256) {
    return res.status(500).send('function name is too long')
  }

  req.params['name'] = func_name
  handleInvokeFunction(req, res)
})
````

```javascript
// https://github.com/labring/laf/blob/main/runtimes/nodejs/src/handler/invoke.ts
export async function handleInvokeFunction(req: IRequest, res: Response) {
  const name = req.params?.name

  const ctx: FunctionContext = {
    __function_name: name,
    requestId: req.requestId,
    query: req.query,
    files: req.files as any,
    body: req.body,
    headers: req.headers,
    method: req.method,
    auth: req['auth'],
    user: req.user,
    request: req,
    response: res,
  }

   ...
  return await invokeFunction(ctx, useInterceptor)
}

async function invokeFunction() {
  const name = ctx.__function_name

  // 获取函数
  let func = FunctionCache.get(name)
  if (!func) {
    func = FunctionCache.get(DEFAULT_FUNCTION_NAME)
    if (!func) {
      return ctx.response.status(404).send('Function Not Found')
    }
  }

  try {
    const executor = new FunctionExecutor(func)
    const result = await executor.invoke(ctx, useInterceptor)

    if (result.error) {
      logger.error(result.error)
      return ctx.response.status(500).send({
        error: 'Internal Server Error',
        requestId,
      })
    }
}


````

```javascript
// https://github.com/labring/laf/blob/main/runtimes/nodejs/src/support/engine/executor.ts

export class FunctionExecutor {
  async invoke(){
    try {
      const mod = this.getModule()
      const main = mod.default || mod.main
      // 执行函数
      data = await main(context)
      return {
        data,
        time_usage: timeUsage,
      }
    } catch (error) {

      return {
        error,
        time_usage: timeUsage,
      }
    }
  }
````

## 数据库

一般使用 **NoSQL** 数据库，比如 **MongoDB**

- 比较自由，不用设置固定的数据结构

- 在水平扩展上也有比较好的优势，便于做弹性伸缩

## 文件存储

一般是**对象存储**方案，比如开源的 **MinIO**（https://github.com/minio/minio）

对象存储是以对象的形式存储和组织数据，每个对象通常包括数据、元数据和唯一的标识符，该标识符用于在存储系统中检索对象。相比传统的文件系统或块存储，对象存储的优势：

- 更灵活： 采用扁平的命名空间，不受目录结构限制；文件用元数据来描述

- 可伸缩：采用水平扩展的架构，允许通过添加更多的存储节点来增加存储容量和性能

- 高度可用：分布式存储，数据通常会被冗余存储在多个节点或多个数据中心

## 小结

从上面的简单介绍来看，核心能力的选用都离不开「弹性伸缩」，根据资源使用情况进行实时调度扩容，提高整个系统的吞吐量，这也算是云计算的一个特点之一。

# 应用案例

> 云开发 x 开放接口 = 任意组合 = 无限可能


## 对接飞书开放平台

![MoLNb1uohoxblox2iNicfNebnxh](https://cdn.chanx.tech/image/MoLNb1uohoxblox2iNicfNebnxh.jpg)

### 定时发送技术文章

通过接口、页面解析等方式对信息源做过滤和聚合，调用飞书消息相关的接口，定时将数据发送到飞书上

![RLuhbStRso3fOdxSjurcEXkfnbd](https://cdn.chanx.tech/image/RLuhbStRso3fOdxSjurcEXkfnbd.jpg)

![MPjcbPAWzoR8pnxYsjrc06GinNI](https://cdn.chanx.tech/image/MPjcbPAWzoR8pnxYsjrc06GinNI.jpg)

### 云文档周报管理

- 基于周报模版文档每周定时创建新的周报文档，并给团队成员授权文档编辑权限；

- 自动将新周报文档归档到指定知识库指定节点；

- 通过群机器人发送群消息，通知所有人更新周报内容

![KKL8bN0elo0r1JxSQnpcjeOJnvc](https://cdn.chanx.tech/image/KKL8bN0elo0r1JxSQnpcjeOJnvc.jpg)

![MnBLbqPaNoR81uxIrcNcczNln1d](https://cdn.chanx.tech/image/MnBLbqPaNoR81uxIrcNcczNln1d.jpg)

### 可视化数据后台

> [解放社畜~基于飞书API实现next.js网站内容自动生成实践 - 开发者广场](https://open.feishu.cn/community/articles/7271149634339438594)


人事维护飞书表格，基于飞书表格 API 自动化生成招聘网页内容（飞书表格  -> Markdown -> Next.js）

![ATPlbV1RBoLDRQxUbwzc3b54nce](https://cdn.chanx.tech/image/ATPlbV1RBoLDRQxUbwzc3b54nce.jpg)

## 对接 OpenAI 实现 AI 应用

> Openai 提供的Node SDK：https://github.com/openai/openai-node


### Chat GPT 应用

Laf函数示例：https://laf.dev/market/templates/64c8b75c644db038c51d174e

实践案例：[用 Laf 云开发搭建一个 ChatGPT Web 演示网页](https://icloudnative.io/posts/build-chatgpt-web-using-laf/)

![Tzc8b2ioHozaEgxpdyec1nZSnAb](https://cdn.chanx.tech/image/Tzc8b2ioHozaEgxpdyec1nZSnAb.jpg)

### ChatMind

官网地址：https://chatmind.tech/

通过与 AI 对话，快速创建和完善思维导图，该产品后续被 Xmind 收购。（据说是大学生基于 Laf 平台编写的项目

![IWINb672moBvyqxG702cdCdtnPc](https://cdn.chanx.tech/image/IWINb672moBvyqxG702cdCdtnPc.jpg)

## 对接 Gitlab 实现自动化工作流

结合 Gitlab 的 Open API 或者 Webhook 实现自动化工作流

- Gitlab API：https://docs.gitlab.com/ee/api/api_resources.html

- Webhook：https://docs.gitlab.com/ee/user/project/integrations/webhooks.html

发挥想象力：

- 主分支合入/上线群通知提醒

- 自动切出版本分支/打tag

- 自动生成changelog文档

- GPT + Merge Diff = 自动代码审查
