如何将 Web 框架迁移到 Serverless

Serverless HTTP

Serverless 通常翻译为 “无服务架构”,是一种软件系统设计架构思想和方法,并不是一个开发框架或者工具。他的出现是为了让开发者更加关注业务的开发,而将繁杂的运维和部署交给云厂商。Serverless 由 Faas 和 Baas 组成,Faas 为开发者提供业务运算环境,然后与 Baas 提供的数据和存储服务,进行交互,从而提供与传统服务一致的体验。但是由于 Faas 是无状态的,并且其运行环境是有读写限制的,最重要的是它是基于事件触发的。因此如果传统 Web 服务想迁移到 Serverless 上,是需要进行相关改造和特殊处理的,为此迁移成本是必不可少的。本文将具体帮助大家剖析下,如何 Serverless 化传统的 Web 服务。

读完本文将了解到:

  1. 传统 Web 服务特点
  2. Serverless 适用场景
  3. Web 框架如何迁移到 Serverless
  4. 使用 Serverless Components 快速部署 Web 框架

传统 Web 服务特点

Web 服务定义:

Web 服务是一种 面向服务的架构 (SOA) 的技术,通过标准的 Web 协议提供服务,目的是保证不同平台的应用服务可以互操作。

日常生活中,接触最多的就是基于 HTTP 协议的服务,客户端发起请求,服务端接受请求,进行计算处理,然后返回响应,简单示意图如下:

Web Service Flow

传统 Web 服务部署流程:通常需要将项目代码部署到服务器上,启动服务进程,监听服务器的相关端口,然后等待客户端请求,从而响应返回处理结果。而这个服务进程是常驻的,就算没有客户端请求,也会占用相应服务器资源。

一般我们的服务是由高流量和低流量场景交替组成的,但是为了考虑高流量场景,我们需要提供较高的服务器配置和多台服务进行负载均衡。这就导致服务处在低流量场景时,会多出很多额外的闲置资源,但是购买的资源却需要按照高流量场景进行付费,这是非常不划算的。

如果我们的服务能在高流量场景自动扩容,低流量场景自动缩容,并且只在进行计算处理响应时,才进行收费,而空闲时间不占用任何资源,就不需要收费呢?

答案就是 Serverless

Serverless 适用场景

上面已经提到了 Serverless 的两个核心特点:按需使用和收费自动扩缩容。而且近几年 Serverless 的应用也越来越广泛,但是它并不是银弹,任何技术都是有它的适合场景和不适合场景。我们不能因为一项技术的火热,而盲目的追捧。Serverless 是有它的局限性的,一般 Serverless 适合如下几种场景:

  1. 异步的并发,组件可独立部署和扩展
  2. 应对突发或服务使用量不可预测
  3. 无状态,计算耗时较短服务
  4. 请求延时不敏感服务
  5. 需要快速开发迭代的业务

如果你的服务不满足以上条件,笔者是不推荐迁移到 Serverless。

Web 框架如何迁移到 Serverless

如果你的服务是以上提到的任何话一个场景,那么就可以尝试迁移到 Serverless 上。

常见的 Serverless HTTP 服务结构图如下:

Serverless HTTP Framework

那么我们如何将 Web 服务进行迁移呢?

我们知道 Faas (云函数)是基于事件触发的,也就是云函数被触发运行时,接收到的是一个 JSON 结构体,它跟传统 Web 请求时有区别的,这就是为什么需要额外的改造工作。而改造的工作就是围绕如何将事件 JSON 结构体转化成标准的 Web 请求

所以 Serverless 化 Web 服务的核心就是需要开发一个 适配层,来帮我们将触发事件转化为标准的 Web 请求。

整个处理流程图如下:

Serverless HTTP Flow

接下来将介绍如何为 Express 框架开发一个适配层。

Serverless Express 适配层开发

实现原理

首先我们先来看看一个标准的云函数结构:

module.exports.handler = (event, context) => {
  // do some culculation
  return res;
};

在介绍如何开发一个 Express 的适配层前,我们先来熟悉下 Express 框架。

一个简单的 Node.js Web 服务如下:

const http = require("http");
const server = http.createServer(function (req, res) {
  res.end("helloword");
});
server.listen(3000);

Express 就是基于 Node.js 的 Web 框架,而 Express 核心就是 通过中间件的方式,生成一个回调函数,然后提供给 http.createServer() 方法使用。

Express 核心架构图如下:

Express Framework

由此可知,我们可以将 Express 框架生成的回调函数,作为 http.createServer() 的参数,来创建可控的 HTTP Server,然后将云函数的 event 对象转化成一个 request 对象,通过 http.request() 方法发起 HTTP 请求,获取请求响应,返回给用户,就可以实现我们想要的结果。

Node.js Server 的监听方式选择

对于 Node.js 的 HTTP Server,可以通过调用 server.listen() 方法来启动服务,listen() 方法支持多种参数类型,主要有两种监听方式 从一个TCP端口启动监听从一个UNIX Socket套接字启动监听

  • server.listen(port[, hostname][, backlog][, callback]):从一个TCP端口启动监听
  • server.listen(path, [callback]):从一个UNIX Domain Socket启动监听

服务器创建后,我们可以像下面这样启动服务器:

// 从'127.0.0.1'和3000端口开始接收连接
server.listen(3000, '127.0.0.1', () => {});
// 从 UNIX 套接字所在路径 path 上监听连接
server.listen('path/to/socket', () => {})

无论是 TCP Socket 还是 Unix Domain Socket,每个 Socket 都是唯一的。TCP Socket 通过 IP和端口 描述,而 Unix Domain Socket 通过 文件路径 描述。

TCP属于传输层的协议,使用 TCP Socket 进行通讯时,需要经过传输层 TCP/IP 协议的解析。

Unix Domain Socket 可用于不同进程间的通讯和传递,使用 Unix Domain Socket 进行通讯时不需要经过传输层,也不需要使用 TCP/IP 协议。所以,理论上讲 Unix Domain Socket 具有更好的传输效率。

因此这里在设计启动服务时,采用了 Unix Domain Socket 方式,以便减少函数执行时间,节约成本。

关于 Node.js 如何实现 IPC 通信,这里就不详细介绍的,感兴趣的小伙伴可以深入研究下,这里有个简单的示例,nodejs-ipc

代码实现

原理大概介绍清楚了,我们的核心实现代码需要以下三步:

  1. 通过 Node.js HTTP Server 监听 Unix Domain Socket,启动服务
function createServer(requestListener, serverListenCallback) {
  const server = http.createServer(requestListener);

  server._socketPathSuffix = getRandomString();
  server.on("listening", () => {
    server._isListening = true;
    if (serverListenCallback) serverListenCallback();
  });
  server
    .on("close", () => {
      server._isListening = false;
    })
    .on("error", (error) => {
      // ...
    });
  server.listen(`/tmp/server-${server._socketPathSuffix}.sock`)
  return server;
}
  1. 将 Serverless Event 对象转化为 Http 请求
function forwardRequestToNodeServer(server, event, context, resolver) {
  try {
    const requestOptions = mapApiGatewayEventToHttpRequest(
      event,
      context,
      getSocketPath(server._socketPathSuffix),
    );
    // make http request to node server
    const req = http.request(requestOptions, (response) =>
      forwardResponseToApiGateway(server, response, resolver),
    );
    if (event.body) {
      const body = getEventBody(event);
      req.write(body);
    }

    req
      .on('error', (error) =>
        // ...
      )
      .end();
  } catch (error) {
    // ...
    return server;
  }
}
  1. 将 HTTP 响应转化为 API 网关标准数据结构
function forwardResponseToApiGateway(server, response, resolver) {
  response
    .on("data", (chunk) => buf.push(chunk))
    .on("end", () => {
      // ...
      resolver.succeed({
        statusCode,
        body,
        headers,
        isBase64Encoded,
      });
    });
}

最后函数的 handler 将异步请求返回就可以了。

借助 tencent-serverless-http 库实现

如果不想手写这些适配层代码,可以直接使用 tencent-serverless-http 模块。

它使用起来很简单,创建我们的 Express 应用入口文件 sls.js

const express = require("express");
const app = express();

// Routes
app.get(`/`, (req, res) => {
  res.send({
    msg: `Hello Express`,
  });
});

module.exports = app;

然后创建云函数 sl_handler.js 文件:

const { createServer, proxy } = require("tencent-serverless-http");
const app = require("./sls");

exports.handler = async (event, context) => {
  const server = createServer(app);
  const result = await proxy(server, event, context, "PROMISE").promise;
  return result;
};

接下来,将业务代码和依赖模块一起打包部署到云函数就可以了(记得指定 执行方法sl_handler.handler )。

其他 Node.js 框架

除了 Express 框架,其他的 Node.js 框架也基本类似,只需要按照要求,exports 一个 HTTP Server 的回调函数就可以。

比如 Koa,我们拿到初始化的 Koa 应用后,只需要将 app.callback() 作为 createServer() 方法的参数就可以了,如下:

const { createServer, proxy } = require("tencent-serverless-http");
const app = require("./sls");

exports.handler = async (event, context) => {
  // 这里和 Express 略有区别
  const server = createServer(app.callback());
  const result = await proxy(server, event, context, "PROMISE").promise;
  return result;
};

其他语言框架

对于非 Node.js 框架,比如 PythonFlask 框架,原理都是一样的,核心只需要做到 将 Serverless Event 对象转化为 Http 请求,就可以了。由于笔者对其他语言不太熟悉,这里就不做深入介绍了,感兴趣的小伙伴,可以到 Github 社区搜索下,已经有很多对应的解决方案了,或者自己尝试手撸也是可以的。

使用 Serverless Components 快速部署 Web 框架

读到这里,相信你已经清楚,如何将自己的 Node.js 框架迁移到 Serverless 了。但是在这之前,我们都是手动处理的,而且每次都需要自己创建 handler.js 文件,还是不够方便。

为此开源社区提供了一套优秀的解决方案 Serverless Component,通过组件,我们进行简单的 yaml 文件配置后,就可以方便的将我们的框架代码部署到云端。

比如上面提到的 Express 框架,就有对应的组件,我们只需要在项目根目录下创建 serverless.yml 配置文件:

component: express
name: expressDemo

inputs:
  src: ./
  region: ap-guangzhou
  runtime: Nodejs10.15
  apigatewayConf:
    protocols:
      - https
    environment: release

然后全局安装 serverless 命令 npm install serverless -g 之后,执行部署命令即可:

$ serverless deploy

耐心等待几秒,我们的 Express 应用就成功部署到云端了。更多详细信息,请参考 Express 官方文档

注意:本文 Serverless 服务均基于 腾讯云 部署。

Serverless Express 组件不仅能帮我们快速部署 Express 应用,而且它还提供了 实时日志云端调试 的能力。

只需要在项目目录下执行 serverless dev 命令,serverless 命令行工具就会自动监听项目业务代码的更改,并且实时部署,同时我们可以通过打开 Chrome Devtools 来调试 Express 应用。

关于云端调试,腾讯云 Serverless Framework 正式发布公告 中有详细的介绍,并且有视频演示。

而且除了 Express 组件,还支持: Koa.js,Egg.js,Next.js,Nuxt.js.....

发现更多组件

最后

当然 Serverless 化 Web 服务并没有本文介绍的那么简单,比如文件读写,服务日志存储,Cookie/Session 存储等......实际开发中,我们还会面临各种未知的坑,但是比起困难,Serverless 带给我们的收益是值得去尝试的。当然传统 Web 服务真的适合迁移到 Serverless 架构上,也是值得我们去思考的问题,毕竟现有的 Web 框架都是面向传统 Web 服务开发实现的 (推荐阅读 利与弊-传统框架要不要部署在 Serverless 架构上)。但是笔者相信,很快就会出现一个专门为 Serverless 而生的 Web 框架,可以帮助我们更好地基于 Serverless 开发应用 ~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342