serverless深入浅出

serverless 基础

什么是serverless ?

在这里插入图片描述

伯克利大学的一篇论文Cloud Programming Simplified: A Berkeley View on Serverless Computing给出了如下定义:

Serverless cloud computing handles virtually all the system administration operations needed to make it easier for programmers to use the cloud. It provides an interface that greatly simplifies cloud programming, and represents an evolution that parallels the transition from assembly language to high-level programming languages.

无服务器云计算(Serverless Computing)几乎
封装了所有的底层资源管理和系统运维工作,使开发人员更容易使用云基础设施。Serverless
它提供了一个接口,极大地简化了基于云服务的编程,犹如汇编语言到高级编程语言般的转换。

这么看还是很抽象,从字面意思粗暴的理解就是Server 这里指服务端,它是 Serverless 解决问题的边界;而less我们可以理解为较少关心,它是 Serverless 解决问题的目的。组合在一起就是“较少关心服务端”

给出最简洁的定义就是:

Serverless = FaaS + BaaS

什么是FaaS

  • FaaS(Function as a Service) 就是函数即服务,它可以让我们随时随地创建、使用、销毁一个函数。
    • 通常函数的使用过程:它需要先从代码加载到内存,也就是实例化,然后被其它函数调用时执行
    • 在 FaaS 中也是一样的,函数需要实例化,然后被触发器 Trigger 或者被其他的函数调用。二者最大的区别就是在 Runtime,也就是函数的上下文,函数执行时的语境。
    • FaaS 的 Runtime 是预先设置好的,Runtime 里面加载的函数和资源都是云服务商提供的,我们可以使用却无法控制。你可以理解为 FaaS 的 Runtime 是临时的,函数调用完后,这个临时 Runtime 和函数一起销毁。
    • FaaS 的函数调用完后,云服务商会销毁实例,回收资源,所以 FaaS 推荐无状态的函数。

什么是BaaS

  • BaaS(Backend as a Service) 就是后端即服务

    • BaaS 其实是一个集合,是指具备高可用性和弹性,而且免运维的后端服务。
    • 以一个列表查询的服务为例,在FaaS中完成的就是查询逻辑的封装,而具体对数据库的增删改查操作,需要由BaaS封装成 HTTP 的 OpenAPI,提供给 FaaS 调用,自己控制这个 API 的请求频率以及限流降级
    • 这个后端服务本身则可以通过连接池、MySQL 集群等方式去优化
    • 从下面这个图中其实可以看出来相对于FaaS服务,BaaS服务是一个有状态、高可用、可扩展的服务,
      image
  • 可能你初次看到上面的架构图,你会觉得,它有点像一个PaaS服务平台,网上对PaaS的介绍太多了,这里不再赘述

  • 首先PaaS解决的痛点在于,它极大的提高了人效和资源的利用率,对于原始的服务,一个项目组内可能需要由专门的人来去维护服务保证服务的可用性、稳定性、和高性能,同时还要投入大量的硬件服务器资源,但是所有的服务部署到PaaS平台上以后,原来n个项目投入的n份人力现在只需要1/n 就可以统一运维所有的服务,不仅如此,PaaS平台上的服务对硬件资源的使用是透明的,资源在不适用的时候可以释放出来,供其他项目组重复利用,所以从两个方面看,PaaS平台极大的提高了项目的人力和硬件资源使用率

  • 而serverless 相比PaaS解决的痛点在于,在服务不用的时候,服务的实例数量为0,也就是没有资源消耗,当服务第一个请求来临时,可以在ms级别启动一个服务,并在一段时间内保持服务,以方便后续的调用;而PaaS启动一个服务至少几十秒,所以 要至少保证一个服务的实例

开箱即用的serverless服务

下面讲解一下如何在阿里云上快速部署一个所见即所得的serverless服务

  • 首先需要登录阿里云 并选择函数计算,没有开通的需要开通一下,放心大胆的点,整个部署过程不花钱,只有产生实际调用才会计费,而且阿里云还提供了免费的调用额度
    在这里插入图片描述

  • 之后选择服务部署的可用区,我选的是北京,然后选择node.js的模板来创建一个服务
    在这里插入图片描述

  • 然后在页面定义应用名,点击部署,就可以看到阿里云自动为你部署了服务
    在这里插入图片描述

在这里插入图片描述

  • 等服务都创建成功,你就可以点击上面HttpTriggerEndpoint对应的地址,这是就返回了helloworld的字符串,你也可以用curl 命令去请求这个地址,观察一下返回的结果
    在这里插入图片描述

  • 接下来可以实际看一下我们编写的FaaS函数,点击应服务/函数,在函数列表中找到我们刚才创建的函数GreetingFunction点击
    在这里插入图片描述

  • 选择代码执行就可以看到node.js的源代码
    在这里插入图片描述

FaaS

从第一部分能看出来,相对于目前前后端分离的架构来说,你可以直观的认为FaaS+BaaS 是对整个后端服务做了更精细的划分,也就是更极致的抽象

传统服务 VS severless服务 的style

做过后端开发的朋友都知道要部署运行一个传统的服务,总体来说都会经历下面的过程:

  • 服务端构建代码的运行环境
    • 我们要购买虚拟机服务
    • 初始化虚拟机运行环境
    • 安装我们需要的应用运行环境
    • 尽量和本地开发环境保持一致
    • 如果不是裸机部署,可以使用docker镜像保证不同环境的服务的一致性
  • 配置负载均衡和反向代理,为了让用户能够访问我们刚刚启动的应用,我们需要购买域名,用虚拟机 IP 注册域名;配置 Nginx
    • 配置反向代理、负载均衡
    • 启动 Nginx;
  • 最后我们还需要上传应用代码,启动应用。
  • 这里面从你第一步购买虚拟机,其实你就已经在云厂商环境掏钱了,不管你后续是否会把服务上线,钱你已经掏了

而部署一个serverless服务,经过刚才简单的实操你只需要:

  • 配置函数服务(构建运行环境)
  • 配置HTTP 函数触发器(构建负载均衡和代理)
  • 配置函数代码(上传代码、启动服务)

你可以把三步对应比传统服务的部署过程,具体在阿里云上只需要操作3步就可以配置好一个severless服务了,而且到现在为止的所有操作,你还没有产生任何费用,目前的云厂商都会提供一定的免费额度

FaaS的请求链路

上面我们了解了一个serverless服务的从部署-运行-成功相应的过程,接下来看一下他是如何走完整个调用链路的

  • 当用户第一次访问 HTTP 函数触发器(GreetingServiceGreetingFunctionhttpTrigger)时,函数触发器就会 Hold 住用户的 HTTP 请求,并产生一个 HTTP Request 事件通知函数服务(GreetingService)
    • 函数触发器是所有请求的统一入口,当请求发生时,它会触发事件通知函数服务,并且等待函数服务执行返回后,将结果返回给等待的请求。
  • 紧接着函数服务(GreetingService)就会检查有没有闲置的函数实例;如果没有函数实例,就去函数代码仓库中拉取你的函数代码(GreetingServiceGreetingFunction);初始化并启动一个函数实例,执行这个函数,传入这个 HTTP Request 对象作为函数的参数,执行函数。
    • 当函数触发器通知的“事件”到来,它会查看当前有没有闲置的函数实例,如果有则调用函数实例处理;如果没有,则会创建函数实例,等实例创建完毕后,再调用函数实例处理事件。
    • “函数服务”在第一次实例化函数时,就会从这个代码仓库中拉取代码,并构建函数实例
  • 函数执行的结果 HTTP Response 返回函数触发器,函数触发器再将结果返回给等待的用户客户端。

FaaS的极致冷启动

FaaS之所以可以做到ms级别启动服务,不得不说一下它的另一个特性-冷启动,先看一下这张图了解FaaS冷启动的大概过程:
image

从图上可知冷启动的过程包括从调用函数开始到函数实例准备完成的整个过程,图中蓝色部分的阶段都是云服务商来提供的,红色部分才是我们自己来提供的,每当你再控制台提交代码,云服务商就会偷偷开始调度资源,下载你的代码构建函数实例的镜像。请求第一次访问时,云服务商就可以利用构建好的缓存镜像,直接跳过冷启动的下载函数代码步骤,从镜像启动容器,这个过程云服务商打了一个提前量,把最耗时的下载代码阶段提前了,当服务来临的时候,代码已经下载好了,可以拿来即用。

  • FaaS之所以敢这么做冷启动就是因为它简化了编程和代码模型,通过分层的思想,在大量不变的层级中极致的缩小可变性的范围,这样做的代价就是牺牲了一定的用户的可控性和应用场景,下图就是FaaS分层的一张图
    image
  • FaaS至少被分成了3层:容器、运行时 Runtime、具体函数代码
    • 容器你可以理解为操作系统 OS。代码要运行,总需要和硬件打交道,容器就是模拟出内核和硬件信息,让你的代码和 Runtime 可以在里面运行。容器的信息包括内存大小、OS 版本、CPU 信息、环境变量等等。目前的 FaaS 实现方案中,容器方案可能是 Docker 容器、VM 虚拟机,甚至 Sandbox 沙盒环境。
    • 运行时 Runtime,就是你的函数执行时的上下文 context。Runtime 的信息包括代码运行的语言和版本,例如 Node.js v10,Python3.6、java8;可调用对象,例如 aliyun SDK;系统信息,例如环境变量等等。
    • 函数代码 就是你用特定编程语言所写的项目代码,可以是java、node.js、python等等
  • 通过分层可以发现从上到下,每层的适用性逐渐降低,容器层适用性最广,云服务商可以预热大量的容器实例,将物理服务器的计算资源池化,提供给不同语言、不同场景实现的服务使用,Runtime 的实例适用性较低,相对于容器可以少预热一些,只针对特定编程语言去提供,而代码层适用性最低,他可能只针对某个服务、某个业务、去预热,这样就可以做到资源统筹优化,最大化物理资源的使用率,低成本的去运行服务
  • 而PaaS因为服务层级的划分粒度相比FaaS更大,运维过或者熟悉PaaS的朋友都知道,如果你使用过AI工程院的AI-PaaS平台,PaaS无论是基础服务组件还是上层业务服务组件,在初始化环境时,有大量服务依赖和多语言版本需要兼容,而且兼容多种用户的应用代码往往也会增加应用构建过程的时间。所以通常 PaaS 无法抽象出轻量的可复用的层级,只能选择服务器或容器方案,从操作系统层开始构建应用实例。同时PaaS的资源利用率相比FaaS,也比较低,很多服务空占用了大量资源,但是调用量很稀松,运行成本相对比较大

Java FaaS函数实践

通过以上原理的讲解,要想搞明白FaaS函数是什么,还是要实操一下,Java基本占据了后端服务开发的半壁江山,接下来就以Java为例,通过实操来体会一下FaaS函数在Java中的运用

阿里云的函数计算目前支持 Java OpenJDK 1.8.0 (runtime = java8) 运行环境,使用 Java 编程,需要定义一个 Java 函数作为入口,Java 语言由于需要编译后才可以在 JVM 虚拟机中运行。和 Python、Node.js 这类脚本型语言不同,有以下限制:

  • 不支持上传代码:使用 Java 语言,仅支持上传已经开发完成,编译打包后的 zip/jar 包。函数计算不提供 Java 的编译能力。
  • 不支持在线编辑:不能上传代码,所以不支持在线编辑代码。Java 运行时的函数,在代码页面仅能看到再次通过页面上传或 OSS 上传提交代码的方法。
事件函数接口

使用 Java 编程时,必须要实现函数计算提供的接口类,对于事件入口函数目前有 2 个预定义接口可以选择。这 2 个预定义接口分别是:

  • StreamRequestHandler
    • 以流的方式接受调用输入 event 和返回执行结果,您需要从输入流中读取调用函数时的输入,处理完成后把函数执行结果写入到输出流中来返回。
  • PojoRequestHandler
    • 通过泛型的方式,您可以自定义输入和输出的类型,但是输入和输出的类型必须是 POJO 类型。

接下来的代码我们使用StreamRequestHandler,更详细的讲解可以查看阿里云的官方文档,这里不再赘述
同时确保已安装java 8以上版本和maven

  1. 创建一个 Java 项目,目录结构如下。
test/src/main/java/example/App.java
  1. 在 App.java 文件内输入如下内容。
package example;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import com.aliyun.fc.runtime.Context;
import com.aliyun.fc.runtime.StreamRequestHandler;
import com.aliyun.fc.runtime.FunctionInitializer;
/**
 * Hello world!
 *
 */
public class App implements StreamRequestHandler, FunctionInitializer {
    public void initialize(Context context) throws IOException {
    }
    public void handleRequest(
            InputStream inputStream, OutputStream outputStream, Context context) throws IOException {
        outputStream.write(new String("hello world\n").getBytes());
    }
}
  1. 在项目文件夹根目录下创建 pom.xml 文件,在 pom.xml 中添加 maven-assembly-plugin 插件,项目需要引用 Maven Central 的外部包,还要引入接口库依赖,内容如下。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>example</groupId>
  <artifactId>Java-example</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>Java-example</name>
  <dependencies>
    <dependency>
      <groupId>com.aliyun.fc.runtime</groupId>
      <artifactId>fc-java-core</artifactId>
      <version>1.3.0</version>
    </dependency>
  </dependencies>
  <properties>
    <maven.compiler.target>1.8</maven.compiler.target>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.test.skip>true</maven.test.skip>
  </properties>
<build>
  <plugins>
      <plugin>
          <artifactId>maven-assembly-plugin</artifactId>
          <version>3.1.0</version>
          <configuration>
              <descriptorRefs>
                  <descriptorRef>jar-with-dependencies</descriptorRef>
              </descriptorRefs>
              <appendAssemblyId>false</appendAssemblyId> <!-- this is used for not append id to the jar name -->
          </configuration>
          <executions>
              <execution>
                  <id>make-assembly</id> <!-- this is used for inheritance merges -->
                  <phase>package</phase> <!-- bind to the packaging phase -->
                  <goals>
                      <goal>single</goal>
                  </goals>
              </execution>
          </executions>
      </plugin>
      <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <configuration>
              <source>1.8</source>
              <target>1.8</target>
          </configuration>
      </plugin>
  </plugins>
  </build>
</project>
  1. 在项目的根目录下执行mvn package命令打包
mvn package
  1. 编译后的 jar 包位于项目文件夹内的 target 目录内,并根据 pom.xml 内的 artifactId、version 字段命名为 Java-example-1.0-SNAPSHOT.jar。
  2. 通过函数计算控制台代码执行页面,选择 OSS 上传或者代码包上传方式上传上述步骤打包的 jar 包,这里选择直接控制台上传
  3. 打开函数计算的控制台,选择-服务/函数-新建服务,因为所有的云函数都是依托于服务这个概念的,所以要先创建服务,指定服务名称,点击创建
    在这里插入图片描述

在这里插入图片描述

  1. 接着创建云函数,点击新建函数,选择Http函数,点击下一步
    在这里插入图片描述

在这里插入图片描述

  1. 配置函数中有几项信息需要说明
  • 函数名称可以自定义
  • 运行环境选择java8
  • 代码选择代码包上传
  • 函数入口指定我们编写的handleRequest函数,格式为 [文件名].[函数名]。我们创建的包名是 “example”,类名是 “App”,那么创建函数时指定的 Handler 为 example.App::handleRequest。
  • 函数执行内存 指定256mb
  • 这里的实例如果是热点调用的函数可以启动多个,这里指定1个
    在这里插入图片描述
  1. 配置Http触发器,指定触发器名称,认证方式选择匿名就是不认证,请求接口的方式选择http的get 方式,然后点击完成,这样一个云函数就创建好了
    在这里插入图片描述

  2. 创建好之后,选择刚才创建的函数,点开代码执行的页签,可以看到:

  • 函数jar报可以重复上传更新
  • 复制接口地址 进行调用,就可以看到我们在函数中返回的字符串
  • 点击下面的执行,就可以在线测试部署好的函数
    在这里插入图片描述

在这里插入图片描述

其实所谓的FaaS云函数就可以按照我们字面上的理解一样,以前我们部署一个接口的最小单位是一个服务器,服务器上承载我们的多个接口,但是现在我们部署的最小单位是一个函数,只不过云服务商替我们隐藏了部署的细节,但是其实质,承载这个函数的仍然是一个进程服务,只不过上面只有一个接口而已

BaaS

常见的后端服务中避免不了对持久化存储的读写,通俗来讲就是对数据库的读写,例如MySQL,如果在FaaS中连接并访问传统数据库就会增加额外的开销,也就是冷启动时连接数据库产生的延时,我们可以采用数据编排的思想,将数据库操作转为 RESTful API。顺着这个思路,引出了后端应用的 BaaS 化。

image

图中冷启动包括在函数调用链路中包含了代码下载、启动函数实例容器、运行时初始化、用户代码初始化等环节。当冷启动完成后,函数实例就绪,后续请求就能直接被函执行。冷启动的优化是一个共享的责任(shared responsibility),需要开发者和云平台共同努力。函数计算已经对云厂商系统侧的冷启动做了大量优化。

如何BaaS化?

就是将对数据库的访问包装成一个有状态的服务,这个服务为了降低因为冷启动带来的延时发生的概率可以通过以下方式进行优化:

  • 精简紧凑的代码包: 开发者要尽可能瘦身代码包,去掉不必要的依赖。降低 Download/Extract Code 的时间。例如对 Nodejs 函数使用 npm prune, 对 Python 函数使用autoflake,autoremove 去除没有使用的依赖。另外一些第三方库中可能会包含测试用例源代码,无用 binary 和数据文件。有选择地删除无用文件可以降低函数代码下载解压时间。
  • 选择合适的函数语言: 由于语言理念的差异,Java 运行时冷启动时间通常要高于其他语言。对于冷启动延迟敏感的应用。在热启动延迟差别不大的情况下,使用 Python 这样的轻量语言可以大幅降低长尾延迟。
  • 选择合适的内存: 在并发量一定的情况下,函数内存越大,冷启动表现越优。
  • 降低冷启动概率:
    • 使用定时触发器预热函数
    • 使用 Initializer 函数入口,函数计算会异步调用初始化接口,消除掉 “User Code Init” 的时间,在函数计算系统升级或者函数更新过程中,用户对冷启动无感知。

下面重点讲解如何通过预留实例来讲后端应用BaaS 化,云厂商一般也都提供了类似的功能

实例预留

目前在阿里云中当用户混合使用预留和按量实例时,函数计算保证优先使用预留资源实例。假设您为函数预留了 10 个实例,意味着每秒钟能使用的预留资源是 10 个实例的算力。如果一秒内需要的实例数超过 10 个,系统会创建新的按量实例处理请求。判断一个实例是否满载和该实例上的并发请求数配置有关。系统追踪每个函数实例上正在处理的请求数,当并发的请求数达到用户设定的上限后,系统会选择其他的函数实例。当所有实例的请求数都达到上限后,则创建新的实例。预留实例由用户管理,即使没有请求,也需要为闲置的预留实例付费

  • 首先在版本管理页面中给之前创建的service/function 创建发布版本

在这里插入图片描述

  • 之后基于创建的版本创建对应的别名
    在这里插入图片描述

在这里插入图片描述

  • 创建好别名之后点击打开预留资源页面,点击新建预留来创建预留实例
    在这里插入图片描述

在这里插入图片描述

  • 这里可以选择预留实例的数量
    在这里插入图片描述

这样就可以把一个有状态的后端应用拆分为FaaS(无状态)+BaaS (有状态)服务了。

总结

回想一下在文章开头给出的serverless的定义是较少关心服务端,对比针对一个web后端应用拆分后的FaaS+BaaS 服务,可以发现有以下几点差异:

  • 分工上,后端关注的领域变小了,而前端关注的领域扩大了,也就是为前端提供了广大的可操作空间,现在主流的前后端分离架构中,前端只需要关注静态页面的展示,数据的获取以及交互,但是引入serverless之后,一些服务端相关的逻辑处理的工作也可以由前端承担了,因为FaaS函数目前也支持js,php等语言,灵活性提高了很多,而后端则更加专注于底层数据的维护
  • 资源上,将后端的工作内容逻辑+数据进行了拆分,逻辑处理相关的工作需要的资源在不需要是进行了释放,只保留了最小范围的有状态服务。同时让开放人员只需要专注于业务开发,由云厂商极致地屏蔽了底层服务器、网络等相关资源问题
  • 针对事务的处理增加了复杂度,更多的服务意味着引入了分布式事务的复杂性,也为后期问题排查带来了相应的难度,必须要引入配套的运维监控体系才能使serverless落地有保障

参考:https://time.geekbang.org/column/article/224559?utm_campaign=guanwang&utm_source=baidu-ad&utm_medium=ppzq-pc&utm_content=title&utm_term=baidu-ad-ppzq-title

Packt.Kubernetes.for.Serverless.Applications.1788620372.PDF Chapter 1, The Serverless Landscape, explains what is meant by serverless. Also, we will get some practical experience of running serverless functions on public clouds using AWS Lambda and Azure Functions. Chapter 2, An Introduction to Kubernetes, discusses what Kubernetes is, what problems it solves, and also takes a look at its backstory, from internal engineering tool at Google to an open source powerhouse. Chapter 3, Installing Kubernetes Locally, explains how to get hands-on experience with Kubernetes. We will install a local single node Kubernetes cluster using Minikube and interact with it using the command-line client. Chapter 4, Introducing Kubeless Functioning, explains how to launch your first serverless function using Kubeless once the Kubernetes is up and running locally. Chapter 5, Using Funktion for Serverless Applications, explains the use of Funktion for a slightly different take on calling serverless functions. Chapter 6, Installing Kubernetes in the Cloud, covers launching a cluster in DigitalOcean, AWS, Google Cloud, and Microsoft Azure after getting some hands-on experience using Kubernetes locally. Chapter 7, Apache OpenWhisk and Kubernetes, explains how to launch, configure, and use Apache OpenWhisk, the serverless platform originally developed by IBM, using our newly launched cloud Kubernetes cluster. Chapter 8, Launching Applications Using Fission, covers the deploying of Fission, the popular serverless framework for Kubernetes, along with a few example functions. Chapter 9, Looking at OpenFaaS, covers OpenFaaS. While it's, first and foremost, a Functions as a Service framework for Docker, it is also possible to deploy it on top of Kubernetes. Chapter 10, Serverless Considerations, discusses security best practices along with how you can monitor your Kubernetes cluster. Chapter 11, Running Serverless Workloads, explains how quickly the Kubernetes ecosystem is evolving and how you can keep up. We also discuss which tools you should use, and why you would want your serverless functions on Kubernetes.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值