3年前 (2021-06-16)  Serverless |   抢沙发  664 
文章评分 0 次,平均分 0.0

使用GraalVM将Spring Boot项目转成本地镜像
作为一个Java开发人员,进程有时会遭受更长的启动时间和相对较高的内存使用率。 我们将寻找一个令人兴奋的解决方案。

在本文中,我们将介绍一个简单的Spring Boot应用程序,然后使用GraalVM将其转换为本机映像。

如果你不知道GraalVM,别担心。我们一会儿再讨论这个问题。现在,请记住,它是一个提供提前编译(AOT)的JVM,与运行时的即时编译相结合。

我们先来讨论一下今天要编写的Spring Boot应用程序(我们将在这里使用Maven,但您也可以使用Gradle)。

应用程序将公开两个REST端点,这两个端点都将接受httpget请求。

  • 第一个端点应该提供与Github用户(/users/{githubUserName})对应的信息。
  • 第二个端点应该提供与Github存储库的贡献者相对应的信息(/contributors/{githubOrgName}/{githubRepoName})。

GithubController可以定义如下-

@RestController
public class GithubController {

  private final GithubClient githubClient;

  @Autowired
  public GithubController(GithubClient githubClient) {
    this.githubClient = githubClient;
  }

  @GetMapping("/users/{userName}")
  public ResponseEntity<User> getUser(@PathVariable String userName) {
    ResponseEntity<User> user = this.githubClient.fetchUser(userName);
    return ResponseEntity.ok(user.getBody());
  }

  @GetMapping("/contributors/{orgName}/{projectName}")
  public ResponseEntity<User[]> getProjectContributors(@PathVariable String orgName,
                                                       @PathVariable String projectName) {
    ResponseEntity<User[]> contributors = this.githubClient.fetchContributors(orgName, projectName);
    return ResponseEntity.ok(contributors.getBody());
  }
}

我们在控制器中自动连接了一个类GithubClient,并使用它获取用户或参与者的详细信息。

@Component
public class GithubClient {

  private final RestTemplate restTemplate;

  private static final String USERS_URL = "https://api.github.com/users/{userName}";
  private static final String CONTRIBUTORS_URL = "https://api.github.com/repos/{owner}/{repo}/contributors";

  @Autowired
  public GithubClient(RestTemplateBuilder restTemplateBuilder) {
    this.restTemplate = restTemplateBuilder.build();
  }

  public ResponseEntity<User> fetchUser(String userName) {
    return this.restTemplate.getForEntity(USERS_URL, User.class, userName);
  }

  public ResponseEntity<User[]> fetchContributors(String orgName, String repoName) {
    return this.restTemplate.getForEntity(CONTRIBUTORS_URL, User[].class, orgName, repoName);
  }
}

这里,我们使用RestTemplate来调用Github APIs。

现在,让我们看一下User DTO类,它将被用作REST响应。

public class User {

  private final String login;
  private final String avatarUrl;
  private final String name;
  private final String company;
  private final String blogUrl;
  private final Integer numPublicRepos;
  private final Integer numContributions;
  private final String htmlUrl;

  @JsonCreator
  public User(@JsonProperty("login") String login,
              @JsonProperty("avatar_url") String avatarUrl,
              @JsonProperty("name") String name,
              @JsonProperty("company") String company,
              @JsonProperty("blog") String blogUrl,
              @JsonProperty("public_repos") Integer numPublicRepos,
              @JsonProperty("contributions") Integer numContributions,
              @JsonProperty("html_url") String htmlUrl) {
    this.login = login;
    this.avatarUrl = avatarUrl;
    this.name = name;
    this.company = company;
    this.blogUrl = blogUrl;
    this.numPublicRepos = numPublicRepos;
    this.numContributions = numContributions;
    this.htmlUrl = htmlUrl;
  }
  
  public String getLogin() {
    return login;
  }

  public String getAvatarUrl() {
    return avatarUrl;
  }
  // other getters
  // ....
}

这对于完成任务来说已经足够了,但是让我们为我们的应用程序添加更多的香料。

Github API规定了一个速率限制,因此一个给定的IP在一小时内不能发出超过60个请求。我们可以使用Github身份验证令牌来提高这个速率限制。

让我们将GithubProperties定义为Spring Boot ConfigurationProperties

@ConfigurationProperties("github")
@Validated
public class GithubProperties {

  /**
   * Github API token ("user:token")
   */
  @Pattern(regexp = "\\w+:\\w+")
  private String token;

  public String getToken() {
    return token;
  }
  
  public void setToken(String token) {
    this.token = token;
  }
}

因此,现在我们可以在application.properties/application.yaml中添加一个名为github.token的属性。自@Validated@Pattern在应用程序启动时使用模式注释-将确保属性遵循所提到的正则表达式。

现在,在尝试调用Github API时如何使用这个令牌?好吧,让我们实现一个Spring RestTemplate拦截器。

public class GithubAppTokenInterceptor implements ClientHttpRequestInterceptor {

  private final String token;

  public GithubAppTokenInterceptor(String token) {
    this.token = token;
  }

  @Override
  public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes,
                                      ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
    if (StringUtils.hasText(this.token)) {
      byte[] basicAuthValue = this.token.getBytes(StandardCharsets.UTF_8);
      httpRequest.getHeaders().set(HttpHeaders.AUTHORIZATION,
                                   "Basic " + Base64Utils.encodeToString(basicAuthValue));
    }
    return clientHttpRequestExecution.execute(httpRequest, bytes);
  }
}

在这里,GithubAppTokenInterceptorintercept函数使用提供的令牌的编码值添加授权请求头。

public class RateLimitHeaderInterceptor implements ClientHttpRequestInterceptor {

  Logger log = LoggerFactory.getLogger(RateLimitHeaderInterceptor.class);

  @Override
  public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes,
                                      ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
    ClientHttpResponse response = clientHttpRequestExecution.execute(httpRequest, bytes);
    final String remainingRateLimit = response.getHeaders().getFirst("X-RateLimit-Remaining");
    assert remainingRateLimit != null;
    if (Integer.parseInt(remainingRateLimit) > 0) {
      log.info("Remaining Rate Limit: - {}", remainingRateLimit);
    }
    else {
      log.warn("API Rate limit has exceeded");
    }
    return response;
  }
}

RateLimitHeaderInterceptor的截取函数提取X-RateLimit-Remaining响应头,并提供日志语句以提供有关剩余速率限制的信息。

为了使用这两个拦截器,我们只需要在初始化restemplate对象时提供这些拦截器

@Autowired
  public GithubClient(RestTemplateBuilder restTemplateBuilder, GithubProperties githubProperties) {
    this.restTemplate =
        restTemplateBuilder.additionalInterceptors(new GithubAppTokenInterceptor(githubProperties.getToken()))
                           .additionalInterceptors(new RateLimitHeaderInterceptor())
                           .build();
  }

最后一段代码是应用程序的入口点。

@SpringBootApplication
@EnableConfigurationProperties(GithubProperties.class)
public class RestApplication {

  public static void main(String[] args) {
    SpringApplication.run(RestApplication.class, args);
  }
}

我们目前讨论的代码可以在这里找到:https://github.com/shivamgarg7276/graal-spring-native/tree/main/spring-boot-without-aot

现在,使用mvn clean install构建。然后运行mvn spring-boot:run command 应用程序将从端口8080启动。

使用GraalVM将Spring Boot项目转成本地镜像
使用GraalVM将Spring Boot项目转成本地镜像

请求:http://localhost:8080/users/shivamgarg7276 返回:

{
    "login": "shivamgarg7276",
    "name": "Shivam Garg",
    "company": "Nutanix",
    "avatarUrl": "https://avatars.githubusercontent.com/u/49524850?v=4",
    "blogUrl": "https://www.linkedin.com/in/shivam-garg-067b46141/",
    "numPublicRepos": 1,
    "htmlUrl": "https://github.com/shivamgarg7276"
}

因此,在我的机器上启动大约需要1.5秒,内存消耗大约是200MB。这就完成了任务,但我们将看到如何使用GraalVM对其进行大规模改进。

什么是GraalVM,为什么它是JAVA应用程序的未来?

GraalVM-字节码到位码

GraalVM是一个高性能的多语言编译器,有助于实现:

1. 提高应用程序吞吐量并减少延迟

2. 将应用程序编译成小型独立的本机二进制文件

3. 无缝地使用多种语言和库

它既可以用于java JIT,也可以用于AOT编译。

对于AOT编译,它使用本机映像生成器或本机映像技术将Java代码提前编译为独立的可执行文件。它处理应用程序的所有类及其依赖关系,包括来自JDK的类。它静态地分析这些数据,以确定在应用程序执行期间哪些类和方法是可访问的。

您可以使用我在这里强调的步骤在系统上安装GraalVM:

https://github.com/shivamgarg7276/graal-spring-native/blob/main/README.md

Spring Native Beta

一段时间前,Spring团队宣布了Spring Native Beta项目,以支持使用GraalVM将Spring应用程序编译为原生映像。

内存消耗有望大大降低,而且启动几乎是即时的!

现在,让我们切换设备,对原来的Spring Boot应用程序进行一些更改,将其转换为本机可执行的Spring应用程序。

在计算机上安装GraalVM后,需要在原始应用程序中执行这些更改-

添加org.springframework.experimental:spring-native-image maven依赖。

<dependency>
  <groupId>org.springframework.experimental</groupId>
  <artifactId>spring-native</artifactId>
  <version>0.9.2</version>
</dependency>

添加Spring AOT插件:

<plugin>
  <groupId>org.springframework.experimental</groupId>
  <artifactId>spring-aot-maven-plugin</artifactId>
  <version>0.9.2</version>
  <executions>
    <execution>
      <id>test-generate</id>
      <goals>
        <goal>test-generate</goal>
      </goals>
    </execution>
    <execution>
      <id>generate</id>
      <goals>
        <goal>generate</goal>
      </goals>
    </execution>
  </executions>
</plugin>

添加在package阶段触发插件的GraalVM本机映像配置文件

<profiles>
  <profile>
    <id>native-image</id>
    <build>
      <plugins>
        <plugin>
          <groupId>org.graalvm.nativeimage</groupId>
          <artifactId>native-image-maven-plugin</artifactId>
          <version>21.0.0.2</version>
          <configuration>
            <!-- The native image build needs to know the entry point to your application -->
            <mainClass>com.example.graal.restserver.main.RestApplicationWithAOT</mainClass>
            <buildArgs>
              <buildArg>--enable-https</buildArg>
            </buildArgs>
          </configuration>
          <executions>
            <execution>
              <goals>
                <goal>native-image</goal>
              </goals>
              <phase>package</phase>
            </execution>
          </executions>
        </plugin>
      </plugins>
    </build>
  </profile>
</profiles>

这里提供了mainClass参数,它指向应用程序的入口点,即用SpringBootApplication注释的类。

另外,buildArgs在使用RestTemplate调用基于https的Github url时启用了https。

到目前为止我们讨论的代码更改可以在这里找到:https://github.com/shivamgarg7276/graal-spring-native/tree/main/spring-boot-with-aot

只需使用以下命令即可构建模块:

mvn -Pnative-image clean package

这将在模块的目标文件夹下创建一个包含Spring Boot应用程序的本机可执行文件。

简单地调用:

target/com.example.graal.restserver.main.restapplicationwithaot

这将在端口8081启动Spring Boo服务器。

使用GraalVM将Spring Boot项目转成本地镜像
使用GraalVM将Spring Boot项目转成本地镜像

如果你现在看到,我的机器的启动时间是0.093秒,太快了。

而且,内存消耗显示在40MB以下。

您还可以使用GraalVM+Spring Native生成一个优化的容器映像,它可以很容易地部署,并且包含一个最小的OS层。

结论

显然,您应该在您的机器上测试以比较这些数字。但是,这清楚地证明了GraalVM的真正威力,以及为什么这将是Java应用程序的未来。

正如一些业内专家所说:

使用GraalVM将Spring Boot项目转成本地镜像

我还没有讨论本地映像的许多方面,将在本文的后续部分中讨论。我们将研究一些需要高级配置设置的角落案例,以及一些其他优化。

GraalVM官网:https://www.graalvm.org/

原文链接:https://blog.devgenius.io/spring-boot-application-with-graalvm-native-image-8074034ba35f

 

除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/2016.html

关于

发表评论

表情 格式

暂无评论

登录

忘记密码 ?

切换登录

注册