进击的 Serverless:Java 应用如何从容地面对突增流量

作者:袭周、十眠

让我们从一个十分常见的业务场景说起。

移动互联网时代,许许多多的业务都有着流量的周期性变化,无论是直播、游戏、点单系统、电商平台等等,都会存在着流量的高峰与波谷。如果采用固定的计算资源部署服务,使用的多了,大量资源在流量波谷闲置浪费,使用的少了,服务难以抗住高峰期的大规模流量,易带来业务损耗。

尤其在大促或节日期间,难以预估具体需要的计算资源数量,同时不论水平还是垂直扩容,人工操作成本都十分高昂。容器技术在某种程度上解决了这类问题,K8s 原生的 HPA 可以根据应用的 CPU、Mem 等指标进行灵活弹性,无需手动扩容,但是使用 K8s 容器化需要付出大量的学习和维护成本,这会极大地侵占运维团队的精力。

许多企业都面临着这样的问题。Serverless 产品因此而走进大众的视野。提到简单,低负担,大家都会想到 Serverless,它的本质就是希望开发者不再需要去处理 Server 运维的复杂性。 它能够让用户无需使用和运维 K8s 等容器技术,同时又能享受到容器技术带来的技术红利,解放人力。

非弹性场景

下图是我使用镜像部署的 demo web 应用,想动手实践体验下的同学可以参考本文后边附加的实践教程。这里我使用阿里云的压测工具 PTS 模拟流量波峰的场景,在不使用弹性能力的场景下,可以看到,随着流量的递增,服务器难以顶住流量的压力,调用的成功率大幅降低,RT 在 5000ms(timeout 阈值)左右徘徊,线上业务产生了很大的损失。

加一点弹性?

在 SAE 控制台可以一键开启弹性能力,无需任何修改和 K8s 运维,同时,除了 K8s 原生支持的 CPU、Mem 指标外,SAE 支持还多种指标弹性,包括 QPS、RT、TCP 连接数等,还支持定时弹性策略和混合弹性策略,下图是开启指标弹性策略后的压测结果,可以看到,在同样的,短暂的延迟之后,请求的成功率接近百分百,延迟也降至几十毫秒,业务几乎没有收到影响,相比于未开启弹性能力时,有极大的改善。

对于非 Java 应用而言,这里已经足够应对绝大多数的场景了,然而针对 Java 应用,又有特殊的地方。众所周知,Java 应用启动相对较慢,从开始创建容器,到服务完全 ready,这需要消耗大量的时间,在流量短时间内大幅度上升的情况下,这段真空时间可能也会导致线上业务的损失,那么如何解决这个问题呢?

流量防护解决突发流量风险

每个系统和服务都有其能够容纳的上限,但是流量的变化是不确定的。有时候前一秒还很平稳,但后一秒可能会出现大量的流量,就像双十一零点的场景一样。如果突然来的流量超过了系统的承受能力,即使我们配置了弹性能力,由于 Java 应用冷启动过程时间过长的问题,即使我们立即扩容,请求仍然处理不过来。因为扩容来不及,新的 Pod 无法启动并分配新的流量,而堆积的请求会持续增加且处理速度缓慢,老的 Pod 由于请求过多导致 CPU/Load 飙升,最终导致整个系统崩溃。因此,在这个过程中,我们需要针对这种突发的流量来进行限制,在尽可能处理请求的同时来保障服务不被打垮,这就是流量防护。

为了应对多余的请求,我们需要使用流量防护能力来限制这些请求,以避免系统被压垮。经过我们的调研,我们发现 MSE 流量防护可以满足我们的需求。MSE 流量防护基于毫秒级滑动窗口精确统计以及令牌桶、漏桶等流量控制算法,可以提供多种维度的流量控制场景,包括秒级精准流控和匀速排队等。这样,我们就可以通过 MSE 流量防护来限制多余的请求,确保系统的正常运行。

我们集成了 MSE 的流量防护能力,对于 SAE 的用户来说,通常在 Web 入口或服务提供方(Service Provider)的场景下,我们需要根据服务提供方的服务能力进行流量控制。我们可以结合前期压测评估核心接口的承受能力,配置流控规则,当每秒的请求量超过设定的阈值时,会自动拒绝多余的请求。从而保护服务提供方自身不被突然出现的过多的流量给打垮。

无损上线确保应用完全预热

在压力测试过程中,我们还发现了 Java 应用普遍存在的一个问题,即冷启动过程中容量过小的问题。 当微服务应用刚启动后,由于 JIT 预热和框架资源的懒加载等情况,应用的容量会比正常情况下小得多。特别是对于 Java 应用来说,这个问题会更加明显。如果我们在这个时候不进行任何预处理,应用直接接收线上均分下来的大量流量,很容易导致大量请求响应缓慢、资源阻塞甚至应用实例宕机的问题。

在大流量下,服务竟然扩容不出来,新启动的 Pod 很快就被大流量打垮,导致 Liveness 检查失败引起 Pod 重启。在这个阶段我们需要一种手段保证新启动的 Pod 可以平稳地度过冷启动阶段。

此时,预热一词蹦入我们脑海;在这阶段我们需要通过小流量预热的方式让系统充分进行预热,合理分配少量的流量启到充分预热我们系统的作用,确保新的 Pod 完全启动后再接受生产中均分下来的大流量。

SAE 默认集成了 MSE 无损上下线, 我们需要在无损上线页面开启无损上线开关,并配置预热时长;然后我们重启应用后,我们就可以看到上线 Pod 的流量曲线如上图所示,缓慢增长,符合小流量预热曲线。

Java 应用难道启动就要很长时间?

我们通过实验发现,Java 应用在扩容的过程中需要预热、启动过慢,因此很大程度上限制了 Serverless 的能力,难道 Java 应用启动就应该很慢吗?最近在社区中出现了另外的一些声音。

图片来自: https://shipilev.net/talks/j1-Oct2011-21682-benchmarking.pdf

一个 Java 程序的执行生命周期如下图所示,分为 JVM 初始化、应用程序初始化、应用预热、应用稳定执行和关闭 5 个部分:在这个过程中我们可以看到 Java 应用的局限性,Java 在运行基于 JVM 之上,因此Java程序启动过程中需要进行 JVM 的初始化与启动。

Java 静态编译在这个过程中相比于传统基于 JVM 的 Java 应用程序运行方式,采用静态编译技术能给 Java 应用程序带来如下优势:

  • 消除冷启动: 没有了程序解释执行、虚拟机初始化等步骤,冷启动问题得到大幅度改善。
  • 内存占用率大幅降低: 静态编译做了大量优化,并且没有了虚拟机运行时内存占用,内存占用得到大幅降低。

右图来自: https://learn.lianglianglee.com/

GraalVM 为 Java 应用提供 AOT 编译和二进制打包能力,基于 GraalVM 打出的二进制包可以实现快速启动、具有超高性能、无需预热时间、同时需要非常少的资源消耗。可以很好地解决 Java 应用启动慢的问题。

Spring Cloud Alibaba 社区基于 GraalVM 技术的最新版本 Spring Cloud Alibaba 应用使用 Nacos 进行服务注册与消费/配置订阅的启动速度提升了近 10 倍、内存占用率降低为原来 1/3。 同时,我们也在 SAE 上验证基于 GraalVM 技术对 Java 应用启动的效果,Java 应用启动时长从 3s 优化至 300ms,极大满足了 Serverless 极致弹性的诉求。可以说 GraalVM 技术为 Java 在 Serverless 场景下带来了新的变革与机遇。

无损下线确保流量低峰缩容过程平滑

当流量高峰过去后,我们弹性扩容出来的服务也会随着流量的下降而缩容,从而确保最高的资源利用率,这也是Serverless 弹性所带来的红利。但是我们在实践的过程中发现,开源微服务在缩容的过程中流量并不能做到平滑,在Pod销毁的过程中依旧有流量在持续访问下线中的服务,从而引起业务问题。

如上图左侧所示,只有到客户端感知到服务端下线并且使用最新的地址列表进行路由与负载均衡时,请求才不会被负载均衡至下线的节点上。那么在节点开始下线的开始到请求不再被打到下线的节点上的这段时间内,业务请求都有可能出现问题,这段时间我们可以称之为服务调用报错期。

想象一下,在大流量的场景下,如果服务调用报错期持续数秒至数十秒,我们的业务会有多少请求出现错误?也正因为这个原因,我们碰到许多客户,扩容出来后就不敢缩容。MSE 无损下线可以有效解决微服务下线过程中的流量损失问题, MSE 通过提前注销、主动通知等策略,保证每一次微服务下线过程中,尽可能缩短服务调用报错期,同时确保待下线节点处理完任何发往该节点的请求之后再下线。对于 SAE 的应用来说,我们只需要开启微服务治理的无损下线能力,无需额外操作即可实现平滑缩容的效果。

进击的 Serverless

提到简单,低负担,相信大家都会想到 Serverless,它的本质就是希望开发者不再需要去处理 Server 运维的复杂性,当然 Server 并没有消失,而是交给了云平台,这也就是 Less 的部分。

经过这么多年的发展,Serverless 也在不断地演进,"Less"的范围在不断扩大,不仅仅包含 Server 部分,还有 0 改造、可观测、弹性、应用托管、微服务、CI/CD 等等各个方面。结合我们前面的微服务架构下复杂性讨论,Serverless 其实是微服务架构下降低复杂度,减少维护成本的最好的一个解决方案之一。

完整的 Serverless 微服务解决方案:SAE

在阿里云最新的 Serverless 体系中,已经不局限仅仅关注"Server"部分,而是围绕应用整体需要,开发或者融合各种 PaaS 能力,真正对开发者做到"将复杂留给平台,简单留给客户"。以下为阿里云 SAE 产品作为例:

首先是第一部分,SAE 给用户提供了一个白屏化的界面,极大的降低用户的使用门槛,甚至可以说 0 门槛,它的交互符合大多数开发者心中 PaaS 的心智,同时,它提供了大量的开发者工具和 SaaS 服务的集成,如 Cloudtoolkit 插件、云效流水线、镜像仓库、jar/war 代码包部署、OpenAPI 等等。同时也有很多企业级特性,比如命名空间隔离,细粒度的权限控制、事件告警中心等等,这些能帮助企业解决许多需要面对的实实在在的问题,实现微服务无缝迁移,0 代码改造,开箱即用。

第二部分, 在微服务能力上,SAE 通过集成 MSE,在平台侧注入 MSE 探针,整体上实现了一个无侵入,业务无感的解决方案,提供了丰富的微服务治理能力,包括无损上下线,灰度发布、流量防护等等。

第三部分, 在微服务架构下,应用数量较多,定位问题困难,可观测就需要具备非常高的要求,SAE 结合阿里云的 ARMS、云监控、SLS、Prometheus 等产品,在 Metrices、Tracing、Logging 等方面都提供了相对完整的解决方案,切实解决开发者在可观测方面的痛点,包括基础监控、调用链、实时日志、事件等等

第四部分, SAE 让开发者不仅不需要运维 IaaS,也不需要运维 K8s,免去了 K8s 高昂的学习、试错、运维成本,同时又可以利用 K8s 的各种能力,包括健康检查、弹性。在此基础上,SAE 又实现了比原生 K8s 更强劲的能力。以弹性为例,除了 K8s 原生支持的 CPU、Mem 指标外,SAE 支持还多种指标弹性,如 QPS、RT、TCP 连接数等,还支持定时弹性策略和混合弹性策略,满足应用在不同环境下按需启停,实现 15s 的端到端快速扩容,应对突发流量。

提到云原生,大家都会想到容器、微服务、不可变基础设施等等技术,但是我们回到为什么会出现云原生,本质也是希望能借助云原生相关的技术构建出"容错性好、易于管理和便于观察的松耦合系统",所以它也是为了降低复杂性和门槛。相信微服务和 Serverless PaaS 结合的解决方案是实现这个目标的最佳手段之一。

Serverless 的概念已经出现很久,并且持续走热,在 Serverless 场景下,我们的 Java 应用如何更优雅地处理突增的流量呢?通过本文的实践,我们可以看到 Java 应用在流量洪峰到来之前,如何安全平滑地进行防护、扩容和预热,以及在流量峰值过去后如何无损地缩容。这样我们可以充分享受 Serverless 架构带来的成本、稳定等方面的红利,Serverless 让业务更关注于业务本身。

如何一键使用无损上下线/流量防护能力

前文已经介绍了无损上下线与流量治理,相信许多技术从业者已经迫不及待想体验对应的能力了。那么是否有方法可以为应用一键开启这两种微服务治理能力?实际上,之前 SAE 通过集成 MSE,已经提供了公测版本的无损上下线功能。最近,随着 SAE 2.0 商业化版本的发布,SAE 更为深入地集成了 MSE,提供了更完整的治理能力,更简洁的配置方式,和更清晰的交互逻辑,下面来为大家介绍一下,如何在 SAE 控制台为部署好的应用开启无损上下线和微服务治理。

前提条件:已创建微服务应用,可参考链接文档 [ 1]

创建完成后,在应用详情页可以统一在微服务治理菜单下管理相关功能,开启无损上下线需要用户开通 MSE 专业版或企业版,开启限流降级需要开通 MSE 企业版。点击开通链接完成开通操作后,可以点击开启按钮,期间会自动触发一次应用重启,完成后,可跳转至对应功能页,微服务治理能力即接入应用中。

针对无损上线功能,有两个重要参数需要进行配置,一个是预热时长,用来设置应用实例下一次启动的预热时间。避免前文提到的,在较大流量下,刚启动的冷系统因应用内部资源初始化不彻底,在直接处理大量请求时出现请求阻塞、报错等问题。取值范围为[0,86400],即最大取值为 24 小时,默认为 120 秒。

另一个是延迟注册时间,用来设置服务注册的延迟时间,从而避免应用还未完全初始化就已经被注册到注册中心供外部消费者调用,导致请求报错。取值范围为[0,3600],即最大取值为 1 小时,默认为 0 秒。

在高级设置中,可以设置在就绪检查前完成服务注册或服务预热,系统会默认为应用业务就绪检查(Readiness 配置)路径为 /health,端口为 54199,原有就绪检查可能会被覆盖。详细功能介绍可以参考无损上下线文档 [ 2] 。针对无损下线,点击开启主动通知功能后,应用在收到下线请求时,会主动通知消费方,避免下线过程中消费方继续访问即将下线的应用实例。

相关链接:

[1] 链接文档

https://help.aliyun.com/zh/sae/user-guide/application-management?spm=a2c4g.11186623.0.i6#82bbc67015v82

[2] 无损上下线文档

https://help.aliyun.com/zh/sae/user-guide/configure-graceful-start-and-shutdown?spm=a2c4g.11186623.0.i2

相关推荐
duration~29 分钟前
Maven随笔
java·maven
zmgst32 分钟前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql
跃ZHD41 分钟前
前后端分离,Jackson,Long精度丢失
java
blammmp1 小时前
Java:数据结构-枚举
java·开发语言·数据结构
暗黑起源喵1 小时前
设计模式-工厂设计模式
java·开发语言·设计模式
WaaTong1 小时前
Java反射
java·开发语言·反射
九圣残炎2 小时前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode
wclass-zhengge2 小时前
Netty篇(入门编程)
java·linux·服务器
Re.不晚2 小时前
Java入门15——抽象类
java·开发语言·学习·算法·intellij-idea
雷神乐乐3 小时前
Maven学习——创建Maven的Java和Web工程,并运行在Tomcat上
java·maven