部署运维与容错韧性

AWS Lambda 原生函数部署

本指南将向你展示如何使用 GraalVM Native Image 将 Vert.x 应用编译为原生二进制执行文件,并作为 Serverless 函数极速部署运行在 AWS Lambda 之上。

你将构建什么

  • 编写一个返回每日名言(Quote Of The Day)的无服务器计算函数;

  • 使用纯 Java 语言实现业务逻辑;

  • 利用 GraalVM 提前编译(AOT)为操作系统原生二进制文件;

  • 使用 AWS CLI 命令行工具配置与管理云端函数;

  • 在 AWS Lambda 极速冷启动运行。

前置要求

  • 文本编辑器或 IDE

  • GraalVM(版本 >= 1.0.0-rc13)

  • Maven 构建工具

  • 已完成认证配置的 AWS CLI 命令行套件

  • 具有 Lambda 与 IAM 操作权限的 AWS 云账号

为什么 Vert.x 与 GraalVM Native Image 是 Serverless 的绝配?

AWS Lambda 是一种无服务器事件驱动型计算服务。然而,传统 Java 应用在 JVM 虚拟机上的冷启动(Cold Start)延迟动辄数秒甚至十余秒,这对要求按需瞬时响应的 Serverless 场景是致命瓶颈。

Vert.x 结合 GraalVM Native Image 彻底颠覆了这一现状:

  1. 毫秒级极速冷启动:Vert.x 设计上避免了重量级反射和复杂的运行期动态代理,Native Image 启动仅需几毫秒;

  2. 极致低内存占用:剥离庞大的 JVM 运行时,内存开销降至数十兆;

  3. 天然高吞吐与高并发:基于非阻塞事件驱动模型,即使单实例并发依然保持极低资源消耗;

  4. 庞大成熟的反应式生态:提供涵盖各类数据库、消息队列与微服务的完整反应式驱动。

工程结构与依赖配置

项目构建配置 pom.xml 如下:

1 引入 svm-driver 处理 SubstrateVM 原生镜像兼容性;
2 引入 vertx-webclient 与 AWS Lambda 运行时 REST 接口高效交互;
3 配置 GraalVM native-image-maven-plugin 生成可执行文件。

编写名言函数

/*
 * Copyright 2019 Paulo Lopes.
 *
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License v1.0
 *  and Apache License v2.0 which accompanies this distribution.
 *
 *  The Eclipse Public License is available at
 *  http://www.eclipse.org/legal/epl-v10.html
 *
 *  The Apache License v2.0 is available at
 *  http://www.opensource.org/licenses/apache2.0.php
 *
 *  You may elect to redistribute this code under either of these licenses.
 */
package lambda;

import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import vertx.lambda.Lambda;

import java.util.Random;

/**
 * A simple QOTD Lambda
 */
public class QOTDLambda implements Lambda {

  private static final Random RANDOM = new Random();

  private static final String[] QUOTES = {    (1)
    "If you find a random command on the internet, try running it as root. What's the worse that could happen?",
    "Can root create a process that even root can't kill?",
    "There's no place like ~",
    "Thou shalt not kill -9.",
    "The 'n' in unmount is missing, leaving it as umount! If you find it, please write to Bell Labs, 600 Mountain Avenue Murray Hill, NJ.",
    "echo \"Just another useless use of cat.\" | cat",
    "`echo \"Just another useless use of backticks.\"`",
    "We've just created a special character device for handling complaints, conveniently located at /dev/null.",
    "false - do nothing, unsuccessfully"
  };

  @Override
  public
  Future<Buffer>  (2)
  call(Vertx vertx, MultiMap headers, Buffer body) {    (3)
    // return a random qotd
    return Future.succeededFuture(Buffer.buffer(QUOTES[RANDOM.nextInt(QUOTES.length)]));  (4)
  }
}
1 定义名言文本数据集;
2 实现 Lambda 接口并返回反应式 Future,支持异步非阻塞处理;
3 接收当前的 Vertx 实例以及请求头与请求体数据;
4 随机选取一条名言并封装为成功 Future 返回。

本地单元测试

/*
 * Copyright 2019 Paulo Lopes.
 *
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License v1.0
 *  and Apache License v2.0 which accompanies this distribution.
 *
 *  The Eclipse Public License is available at
 *  http://www.eclipse.org/legal/epl-v10.html
 *
 *  The Apache License v2.0 is available at
 *  http://www.opensource.org/licenses/apache2.0.php
 *
 *  You may elect to redistribute this code under either of these licenses.
 */
package lambda;

import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.buffer.Buffer;
import io.vertx.ext.unit.Async;
import io.vertx.ext.unit.TestContext;
import io.vertx.ext.unit.junit.RunTestOnContext;
import io.vertx.ext.unit.junit.VertxUnitRunner;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(VertxUnitRunner.class)
public class QOTDLambdaTest {

  private final QOTDLambda fn = new QOTDLambda();

  @Rule
  public RunTestOnContext rule = new RunTestOnContext();

  @Test
  public void shouldGetAQOTD(TestContext should) {
    final Async test = should.async();

    Future<Buffer> fut = fn.call(rule.vertx(), MultiMap.caseInsensitiveMultiMap(), null);   (1)

    fut.setHandler(call -> {
      if (call.failed()) {
        should.fail(call.cause());  (2)
      } else {
        should.assertNotNull(call.result());  (3)
        should.assertTrue(call.result().length() > 0);
        test.complete();  (4)
      }
    });
  }

}
1 模拟调用函数;
2 断言执行成功;
3 校验名言内容非空;
4 结束测试用例。

执行本地测试:mvn test。

构建原生二进制与部署至 AWS

1. 编译打包原生制品

$ mvn clean package
$ zip -r function.zip bootstrap target/lambda

生成的 function.zip 包含了原生可执行文件以及 AWS Lambda 自定义运行时所需的 bootstrap 入口脚本。

2. 发布 Lambda 自定义运行时层 (Layer)

$ aws lambda publish-layer-version \
    --layer-name vertx-native-example \
    --zip-file fileb://function.zip

3. 创建与测试云端函数

$ aws lambda create-function --function-name vertxNativeTester \
    --zip-file fileb://function.zip --handler lambda.EchoLambda --runtime provided \
    --role arn:aws:iam::<YOUR-ARN>:role/service-role/lambda-role

使用 AWS CLI 触发测试调用:

$ aws lambda invoke --function-name vertxNativeTester \
    --payload '{"message":"Hello World"}' \
    --log-type Tail response.txt | grep "LogResult" | awk -F'"' '{print $4}' | base64 --decode

本文总结

  • 编写了基于 Vert.x 的原生 Serverless 名言函数;

  • 利用 GraalVM 实现了毫秒级冷启动二进制编译;

  • 通过 AWS CLI 完成了 Layer 发布与函数调用测试。