前言
随着技术的发展,我们有越来越多的选择来实现我们的业务逻辑。Serverless作为时下前沿的技术,是不是也可以探索一下微服务架构的新可能性?
这篇文章就是总结近段时间以来,我探索的用serverless落地SpringBoot微服务项目的一些成果。
什么是Serverless
什么是微服务和什么是springBoot已经不需要我讲解了。
那什么是Serverless呢?
根据CNCF的定义,Serverless(无服务器)是指构建和运行不需要服务器管理的应用程序的概念。
Serverless并不是没有服务器就能进行计算,而是指对于开发者或者公司来说,无需了解和管理底层服务器,就能进行计算。
通俗一点讲,Serverless就是封装了底层计算资源,你只需要提供函数,就可以运行了。
这里还要提到一个概念,就是FaaS(Function as a Service),函数即服务。我们通常运行在Serverless上的逻辑是函数级别的粒度。
因此对于拆分粒度控制很合理的微服务,是非常适合使用serverless的。
Serverless对于微服务的价值
- 每个微服务API被调用的频率不一样,可以利用Serverless精准管理成本和弹性。
- 不用担心一个API调用量大而需要扩容整个服务。Serverless可以自动扩缩容。
- 不需要去运维每个服务背后部署多少个容器,多少个服务器,不用做负载均衡。
- 屏蔽了K8S等容器编排的复杂学习成本。
- Serverless这种无状态的特性也非常符合微服务使用Restful API的特性。
初步实践
首先,需要准备一个SpringBoot项目,可以通过start.spring.io快速创建一个。
在业务开发上,Serverless和传统的微服务开发并没有任何不同。所以我就快速写了一个todo后端服务,完成了增删改查功能。
示例代码在这里。
那么使用Serverless真正有差异的地方在哪里呢?
如果只是简单的想要部署单个服务,那么主要差异在于两个方面:
- 部署方式
- 启动方式
部署方式
由于我们摸不到服务器了,所以部署方式的变化是很大的。
传统的微服务部署,通常是直接部署到虚拟机上运行,或者用K8S做容器化的调度。
如果使用serverless通常要求我们的微服务拆分粒度更细,才能做到FaaS。
所以使用Serverless部署微服务的关系大致如下图。
Serverless只需要提供代码就可以了,因为serverless自带运行环境,因此serverless部署微服务通常有两种方式:
- 代码包上传部署
- 镜像部署
第一种方式和传统部署相比是差异最大的。它需要我们将写好的代码打包上传。并且需要指定一个入口函数或者指定监听端口。
第二个种方式和传统的方式相比几乎不变,都是把做好的镜像上传到我们的镜像仓库。然后在serverless平台部署的时候选择对应的镜像。
启动方式
因为serverless是使用的时候才会创建对应的实例,不使用的时候就会销毁实例,体现了serverless按量计费的特点。
所以serverless在第一次调用的时候存在一个冷启动的过程。所谓冷启动就是指需要平台分配计算资源、加载并启动代码。因此依据不同的运行环境和代码可能有不同的冷启动时间。
而Java作为一种静态语言,它的启动速度也一直被人诟病。然而还有更慢的,就是spring的启动时间,是大家有目共睹的慢。所以,java+spring这种强强联合造就了树懒般的启动速度。就有可能造成首次调用服务出现超长的等待时间。
不过,不用担心,spring已经提供了两种解决方案来缩短启动时间。
- 一种是SpringFu
- 另一种是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有几大好处:
- 在构建时会移除未使用的代码
- classpath 在构建时就已经确定
- 没有类延迟加载:可执行文件中所有的内容都会在启动时加载到内存中
- 在构建时就运行了一些代码
基于这些特性,因此它能让程序的启动时间大大加快。
关于如何使用它我将在下一篇文章中讲解,详细教程可以查看这个官方教程。我也是参考这个教程做的。
我就说说我的测试对比结果吧。
我把编译好的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作为目前先进的技术,它给我们带来诸多好处。
- 自动扩缩容的弹性和并发性
- 细粒度的资源分配
- 松耦合
- 免运维
但serverless也不是完美的,当我们尝试在微服务领域使用它的时候,我们依然能看到它存在很多问题等待解决。
难以监视和调试
- 这是目前公认的一个痛
可能会有更多的冷启动
- 当我们拆分微服务为了适应函数粒度时,同时也分散了每个函数的调用时间,导致每个函数调用频率变低,带来更多的冷启动。
函数间的交互会更复杂
- 由于函数粒度变细,在大型微服务项目中,导致原本就错综复杂的微服务会变得更加错综复杂。
总结起来就是,想要完全替代传统的虚拟机,在微服务这条路上Serverless还有很长的路要走。
下一步
我会继续探索serverless与微服务的实践。
后面的文章我会探讨下面几个话题
- Serverless中的服务间调用
- Serverless中的数据库访问
- Serverless中的服务的注册与发现
- Serverless中的服务熔断与降级
- Serverless中的服务拆分