关于微服务:Serverless与微服务探索二-SpringBoot项目部署实践

41次阅读

共计 10397 个字符,预计需要花费 26 分钟才能阅读完成。

前言

上次的文章分享后,有粉丝反馈内容太实践太形象,看不到理论的样子。

因而,我这里就写一篇教程,手把手教你如何把一个 SpringBoot 我的项目部署到 Serverless 并测试胜利。

上面的链接是我发表到官网的文章,但官网的文章会综合思考,所以不会有那么细的步骤。本文是最具体的步骤。

SpringBoot + SCF 最佳实际:实现待办利用

本文章以腾讯云 Serverless 云函数为例,将分为事件函数和 Web 函数两种教程。

事件函数就是指函数是由事件触发的。

Web 函数就是指函数能够间接发送 HTTP 申请触发函数。具体区别能够看这里。

两者在 Spring 我的项目迁徙革新上的区别在于:

  • 事件函数须要减少一个入口类。
  • Web 函数须要批改端口为固定的 9000。
  • 事件函数须要操作更多的控制台配置。
  • Web 函数须要减少一个 scf_bootstrap 启动文件,和不一样的打包形式。

事件函数

Spring 我的项目筹备

事件函数示例代码下载地址:https://github.com/woodyyan/scf-springboot-java8/tree/eventfunction

示例代码介绍

@SpringBootApplication 类保持原状不变。

package com.tencent.scfspringbootjava8;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

Controller 类也会依照原来的写法,放弃不变。这里以 todo 利用为例子。

记住此处的/todos 门路,前面会用到。

代码如下:

package com.tencent.scfspringbootjava8.controller;

import com.tencent.scfspringbootjava8.model.TodoItem;
import com.tencent.scfspringbootjava8.repository.TodoRepository;
import org.springframework.web.bind.annotation.*;

import java.util.Collection;

@RestController
@RequestMapping("/todos")
public class TodoController {
    private final TodoRepository todoRepository;

    public TodoController() {todoRepository = new TodoRepository();
    }

    @GetMapping
    public Collection<TodoItem> getAllTodos() {return todoRepository.getAll();
    }

    @GetMapping("/{key}")
    public TodoItem getByKey(@PathVariable("key") String key) {return todoRepository.find(key);
    }

    @PostMapping
    public TodoItem create(@RequestBody TodoItem item) {todoRepository.add(item);
        return item;
    }

    @PutMapping("/{key}")
    public TodoItem update(@PathVariable("key") String key, @RequestBody TodoItem item) {if (item == null || !item.getKey().equals(key)) {return null;}

        todoRepository.update(key, item);
        return item;
    }

    @DeleteMapping("/{key}")
    public void delete(@PathVariable("key") String key) {todoRepository.remove(key);
    }
}

减少一个 ScfHandler 类,我的项目构造如下:

Scfhandle类次要用于接管事件触发,并转发音讯给 Spring application,而后接管到 Spring application 的返回后把后果返回给调用方。

默认端口号为8080.

其代码内容如下:

package com.tencent.scfspringbootjava8;

import com.alibaba.fastjson.JSONObject;
import com.qcloud.services.scf.runtime.events.APIGatewayProxyRequestEvent;
import com.qcloud.services.scf.runtime.events.APIGatewayProxyResponseEvent;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

public class ScfHandler {
    private static volatile boolean cold_launch;

    // initialize phase, initialize cold_launch
    static {cold_launch = true;}

    // function entry, use ApiGatewayEvent to get request
    // send to localhost:8080/hello as defined in helloSpringBoot.java
    public String mainHandler(APIGatewayProxyRequestEvent req) {System.out.println("start main handler");
        if (cold_launch) {System.out.println("start spring");
            ScfSpringbootJava8Application.main(new String[]{""});
            System.out.println("stop spring");
            cold_launch = false;
        }
        // 从 api geteway event -> spring request -> spring boot port

        // System.out.println("request:" + req);
        // path to request
        String path = req.getPath();
        System.out.println("request path:" + path);

        String method = req.getHttpMethod();
        System.out.println("request method:" + method);

        String body = req.getBody();
        System.out.println("Body:" + body);

        Map<String, String> reqHeaders = req.getHeaders();
        // construct request
        HttpMethod httpMethod = HttpMethod.resolve(method);
        HttpHeaders headers = new HttpHeaders();
        headers.setAll(reqHeaders);
        RestTemplate client = new RestTemplate();
        HttpEntity<String> entity = new HttpEntity<>(body, headers);

        String url = "http://127.0.0.1:8080" + path;

        System.out.println("send request");
        ResponseEntity<String> response = client.exchange(url, httpMethod != null ? httpMethod : HttpMethod.GET, entity, String.class);
        // 期待 spring 业务返回解决构造 -> api geteway response。APIGatewayProxyResponseEvent resp = new APIGatewayProxyResponseEvent();
        resp.setStatusCode(response.getStatusCodeValue());
        HttpHeaders responseHeaders = response.getHeaders();
        resp.setHeaders(new JSONObject(new HashMap<>(responseHeaders.toSingleValueMap())));
        resp.setBody(response.getBody());
        System.out.println("response body:" + response.getBody());
        return resp.toString();}
}

Gradle

这里以 gradle 为例,与传统开发不一样的中央次要在于,build.gradle中须要退出全量打包的 plugin,来保障所有用到的依赖都打入 jar 包中。

  1. 增加id 'com.github.johnrengelman.shadow' version '7.0.0' 这个 plugin。
  2. 增加id 'application'
  3. 增加id 'io.spring.dependency-management' version '1.0.11.RELEASE'
  4. 指定mainClass

build.gradle具体内容如下:

plugins {
    id 'org.springframework.boot' version '2.5.5'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java-library'
    id 'application'
    id 'com.github.johnrengelman.shadow' version '7.0.0'
}

group = 'com.tencent'
version = '0.0.2-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {mavenCentral()
}

dependencies {
    api 'org.springframework.boot:spring-boot-starter-web'
    api group: 'com.tencentcloudapi', name: 'tencentcloud-sdk-java', version: '3.1.356'
    api group: 'com.tencentcloudapi', name: 'scf-java-events', version: '0.0.4'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {useJUnitPlatform()
}

application {
    // Define the main class for the application.
    mainClass = 'com.tencent.scfspringbootjava8.ScfSpringbootJava8Application'
}

Maven

这里以 maven 为例,与传统开发不一样的点次要在于,pom.xml 须要退出 maven-shade-plugin,来保障所有用到的依赖都打入 jar 包中。同时须要指定mainClass,上面代码中的mainClass 须要改为你本人的 mainClass 门路。

pom.xml具体内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>1.0</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
      <!-- Build an executable JAR -->
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-jar-plugin</artifactId>
      <version>3.1.0</version>
      <configuration>
        <archive>
          <manifest>
            <addClasspath>true</addClasspath>
            <classpathPrefix>lib/</classpathPrefix>
            <mainClass>com.mypackage.MyClass</mainClass>
          </manifest>
        </archive>
      </configuration>
    </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <dependencies>
                    <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-maven-plugin</artifactId>
                        <version>2.1.1.RELEASE</version>
                    </dependency>
                </dependencies>
                <configuration>
                    <keepDependenciesWithProvidedScope>true</keepDependenciesWithProvidedScope>
                    <createDependencyReducedPom>true</createDependencyReducedPom>
                    <filters>
                        <filter>
                            <artifact>*:*</artifact>
                            <excludes>
                                <exclude>META-INF/*.SF</exclude>
                                <exclude>META-INF/*.DSA</exclude>
                                <exclude>META-INF/*.RSA</exclude>
                            </excludes>
                        </filter>
                    </filters>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                    <resource>META-INF/spring.handlers</resource>
                                </transformer>
                                <transformer
                                        implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer">
                                    <resource>META-INF/spring.factories</resource>
                                </transformer>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                    <resource>META-INF/spring.schemas</resource>
                                </transformer>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

编译 JAR 包

下载代码之后,到该项目标根目录,运行编译命令:

  • Gradle 我的项目运行:gradle build
  • Maven 我的项目运行:mvn package

编译实现后就能在以后我的项目的输入目录找到打包好的 jar 包。

  • Gradle 我的项目:在 build/libs 目录下看到打包好的 jar 包,这里须要抉择后缀是 -all 的 JAR 包。如下图。
  • Maven 我的项目:在 target 目录下能看到打包好的 jar 包,这里须要抉择前缀 不带 orginal- 的 jar 包。

一会部署函数的时候就用这个 JAR 包。

云函数筹备

云函数创立

在函数服务中,点击新建,开始创立函数。

如下图

  1. 抉择自定义创立
  2. 抉择事件函数
  3. 输出一个函数名称
  4. 运行环境抉择 Java8
  5. 提交办法抉择本地上传 zip 包
  6. 执行办法指定为 包名. 类名:: 入口函数名

    1. 比方此处是:com.tencent.scfspringbootjava8.ScfHandler::mainHandler
  7. 上传那里抉择后面编译好的带 -all 后缀的 jar 包。

而后点击实现创立函数。

云函数配置

创立实现之后,选择函数治理 - 函数配置 - 编辑。如下图。

点开编辑之后,在环境配置中:

  1. 把内存批改为 1024MB
  2. 把执行超时工夫批改为 15 秒

触发器配置

在触发治理中,创立触发器。

创立触发器时,在下图中:

  1. 触发形式抉择 API 网关触发。
  2. 集成响应勾选。
  3. 而后提交

创立实现之后须要批改一些 API 网关参数。点击 API 服务名进入批改。

点击右侧的编辑按钮批改。

第一个前端配置中,将门路批改为 Spring 我的项目中的默认门路。如下图。

而后点击立刻实现。

而后点击公布服务。

公布实现之后回到云函数控制台。

开始测试

此处咱们就以 Controller 外面写的第一个 GET 办法为例,如下图,咱们将取得所有的 todo items。

在函数治理中,选择函数代码,就能够很不便的进行测试。如下图。

  1. 测试事件抉择“API Gateway 事件模版”。
  2. 申请形式抉择GET
  3. Path 填/todos
  4. 最初就能够点击测试按钮。

测试后果和日志将间接显示在界面的右下方。如下图。

如果想要获取残缺的拜访 URL,能够在触发治理中,找到方才创立的 API 网关触发器,上面有能够拜访的 URL。URL 前面有复制按钮。如下图。


Web 函数

Spring 我的项目筹备

示例代码介绍

Web 函数示例代码下载地址:https://github.com/woodyyan/scf-springboot-java8/tree/webfunction

Web 函数的我的项目代码相比事件函数更简略。代码革新老本简直没有。对原代码的批改只有一个端口号。

Web 函数则不须要 ScfHandler 入口类,我的项目构造如下:

因为 web 函数必须保障我的项目监听端口为9000,所以须要将 Spring 监听的端口改为 9000。如下图:

代码部署包筹备

代码包编译形式参考下面的“编译 JAR 包”。

而后新建一个 scf_bootstrap 启动文件,文件名字必须是scf_bootstrap,没有后缀名。

  1. 第一行需有 #!/bin/bash
  2. java 启动命令必须是绝对路径,java 的绝对路径是:/var/lang/java8/bin/java
  3. 请确保你的 scf_bootstrap 文件具备 777 或 755 权限,否则会因为权限有余而无奈执行。

因而启动文件内容如下:

#!/bin/bash
/var/lang/java8/bin/java -Dserver.port=9000 -jar scf-springboot-java8-0.0.2-SNAPSHOT-all.jar

接着,在 scf_bootstrap 文件所在目录执行下列命令来保障 scf_bootstrap 文件可执行。

chmod 755 scf_bootstrap

而后将 scf_bootstrap 文件和方才编译解决的 scf-springboot-java8-0.0.2-SNAPSHOT-all.jar 文件,一起打包成 zip 文件。如下图。

打包好的 zip 文件就是咱们的部署包。

云函数创立

在函数服务中,点击新建,开始创立函数。

如下图

  1. 抉择自定义创立
  2. 抉择 Web 函数
  3. 输出一个函数名称
  4. 运行环境抉择 Java8
  5. 提交办法抉择本地上传 zip 包
  6. 上传那里抉择后面压缩好的 scf_spring_boot.zip 包。

而后在上面的高级配置中,写上启动命令,命令中的 jar 文件应该是你编译进去的 jar 文件的名字。

因为 web 函数必须保障我的项目监听端口为9000,所以命令中要指定一下端口。

更多对于启动命令的写法能够参考启动文件阐明。

如下图:

而后环境配置那里,把内存改为 512MB。执行超时工夫设置为 15 秒。

其余设置都应用默认的就能够了。而后点击实现。

点击实现之后如果没有反馈,是因为要先期待 ZIP 文件上传,才会开始创立函数。

因为 Web 函数默认会创立 API 网关触发器,因而咱们不须要独自配置触发器。

开始测试

此处咱们就以 Controller 外面写的第一个 GET 办法为例,如下图,咱们将取得所有的 todo items。

在函数控制台的函数代码外面,咱们能够间接测试咱们的云函数。

根据下面的代码,咱们申请形式抉择GET,path 填写/todos,而后点击测试按钮,而后就能够在右下角看到咱们的后果了。

如果想在其余中央测试,能够复制下图中的拜访门路进行测试。

最初

本教程没有波及镜像函数,因为镜像部署和原来的部署形式没有差别。我的项目代码也不须要革新。实践上这是最适宜微服务项目的形式。

下一篇文章中,我就会详细分析 Serverless 中上面几个话题了。

  • Serverless 中的服务间调用
  • Serverless 中的数据库拜访
  • Serverless 中的服务的注册与发现
  • Serverless 中的服务熔断与降级
  • Serverless 中的服务拆分

正文完
 0