前言

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

因而,我这里就写一篇教程,手把手教你如何把一个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;@SpringBootApplicationpublic 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中的服务拆分