Serverless与微服务探索(一)- 如何用serverless实践Spring boot项目

前言

随着技术的发展,我们有越来越多的选择来实现我们的业务逻辑。Serverless作为时下前沿的技术,是不是也可以探索一下微服务架构的新可能性?

这篇文章就是总结近段时间以来,我探索的用serverless落地SpringBoot微服务项目的一些成果。

什么是Serverless

什么是微服务和什么是springBoot已经不需要我讲解了。

那什么是Serverless呢?

根据CNCF的定义,Serverless(无服务器)是指构建和运行不需要服务器管理的应用程序的概念。

Serverless并不是没有服务器就能进行计算,而是指对于开发者或者公司来说,无需了解和管理底层服务器,就能进行计算。

通俗一点讲,Serverless就是封装了底层计算资源,你只需要提供函数,就可以运行了。

这里还要提到一个概念,就是FaaS(Function as a Service),函数即服务。我们通常运行在Serverless上的逻辑是函数级别的粒度。

因此对于拆分粒度控制很合理的微服务,是非常适合使用serverless的。

Serverless对于微服务的价值

  1. 每个微服务API被调用的频率不一样,可以利用Serverless精准管理成本和弹性。
  2. 不用担心一个API调用量大而需要扩容整个服务。Serverless可以自动扩缩容。
  3. 不需要去运维每个服务背后部署多少个容器,多少个服务器,不用做负载均衡。
  4. 屏蔽了K8S等容器编排的复杂学习成本。
  5. Serverless这种无状态的特性也非常符合微服务使用Restful API的特性。

初步实践

首先,需要准备一个SpringBoot项目,可以通过start.springboot.io快速创建一个。

在业务开发上,Serverless和传统的微服务开发并没有任何不同。所以我就快速写了一个todo后端服务,完成了增删改查功能。

示例代码在这里

那么使用Serverless真正有差异的地方在哪里呢?

如果只是简单的想要部署单个服务,那么主要差异在于两个方面:

  1. 部署方式
  2. 启动方式

部署方式

由于我们摸不到服务器了,所以部署方式的变化是很大的。

传统的微服务部署,通常是直接部署到虚拟机上运行,或者用K8S做容器化的调度。

传统的部署关系大致如下图。

如果使用serverless通常要求我们的微服务拆分粒度更细,才能做到FaaS。
所以使用Serverless部署微服务的关系大致如下图。

Serverless只需要提供代码就可以了,因为serverless自带运行环境,因此serverless部署微服务通常有两种方式:

  1. 代码包上传部署
  2. 镜像部署

第一种方式和传统部署相比是差异最大的。它需要我们将写好的代码打包上传。并且需要指定一个入口函数或者指定监听端口。

第二个种方式和传统的方式相比几乎不变,都是把做好的镜像上传到我们的镜像仓库。然后在serverless平台部署的时候选择对应的镜像。

启动方式

因为serverless是使用的时候才会创建对应的实例,不使用的时候就会销毁实例,体现了serverless按量计费的特点。

所以serverless在第一次调用的时候存在一个冷启动的过程。所谓冷启动就是指需要平台分配计算资源、加载并启动代码。因此依据不同的运行环境和代码可能有不同的冷启动时间。

而Java作为一种静态语言,它的启动速度也一直被人诟病。然而还有更慢的,就是spring的启动时间,是大家有目共睹的慢。所以,java+spring这种强强联合造就了树懒般的启动速度。就有可能造成首次调用服务出现超长的等待时间。

不过,不用担心,spring已经提供了两种解决方案来缩短启动时间。

  1. 一种是SpringFu
  2. 另一种是Spring Native

SpringFu

Spring Fu 是 JaFu (Java DSL) 和 KoFu (Kotlin DSL) 的孵化器,以声明式方式使用代码显式配置 Spring Boot,由于自动完成,具有很高的可发现性。 它提供快速启动(比最小 Spring MVC 应用程序上的常规自动配置快 40%)、低内存消耗,并且由于其(几乎)无反射方法非常适合 GraalVM 本机。如果搭配上GraalVM编译器,应用启动速度就能直线下降到原先的大约1%。

不过,目前SpringFu还处于特别早期的阶段,使用过程中问题也比较多。另外,使用SpringFu会有较大的代码改造成本,因为它干掉了所有的annotation,所以这次我没有使用SpringFu的方式。

Spring Native

Spring Native 为使用 GraalVM native-image编译器将 Spring 应用程序编译为native可执行文件,以提供打包在轻量级容器中的native部署选项。 Spring Native的目标是在这个新平台上支持几乎没有代码改造成本的 Spring Boot 应用程序。

因此我选择了Spring native,因为它不需要改造代码,只需要添加一些插件与依赖就能实现native image。

Native image有几大好处:

  1. 在构建时会移除未使用的代码
  2. classpath 在构建时就已经确定
  3. 没有类延迟加载:可执行文件中所有的内容都会在启动时加载到内存中
  4. 在构建时就运行了一些代码

基于这些特性,因此它能让程序的启动时间大大加快。

关于如何使用它我将在下一篇文章中讲解,详细教程可以查看这个官方教程。我也是参考这个教程做的。

我就说说我的测试对比结果吧。

我把编译好的image分别在本地,腾讯云serverless的云函数和AWS serverless lambda进行了部署和测试。

规格 SpringBoot冷启动时长 SpringNative冷启动时长
本地16G内存Mac 1秒 79毫秒
腾讯云Serverless 256M内存 13秒 300毫秒
AWS Serverless 256M内存 21秒 1秒

从测试结果看,SpringNative大大提升了启动速度。提高serverless的规格还能进一步提升速度。

如果Serverless的冷启动速度控制到了1秒内,那么大部分业务都是能接受的。并且也只有首次请求的时候会存在冷启动的情况,其他请求都和普通的微服务响应时间一样。

此外,目前各大平台的Serverless都支持预置实例,也就是在访问到来之前提前创建实例来减少冷启动时间。带来业务上更高的相应时间。

总结

Serverless作为目前先进的技术,它给我们带来诸多好处。

  1. 自动扩缩容的弹性和并发性
  2. 细粒度的资源分配
  3. 松耦合
  4. 免运维

但serverless也不是完美的,当我们尝试在微服务领域使用它的时候,我们依然能看到它存在很多问题等待解决。

  1. 难以监视和调试
    1. 这是目前公认的一个痛
  2. 可能会有更多的冷启动
    1. 当我们拆分微服务为了适应函数粒度时,同时也分散了每个函数的调用时间,导致每个函数调用频率变低,带来更多的冷启动。
  3. 函数间的交互会更复杂
    1. 由于函数粒度变细,在大型微服务项目中,导致原本就错综复杂的微服务会变得更加错综复杂。

总结起来就是,想要完全替代传统的虚拟机,在微服务这条路上Serverless还有很长的路要走。


原文:java - Serverless与微服务探索(一)- 如何用serverless实践Spring boot项目 - Woody的专栏 - SegmentFault 思否
作者: Woody