玩转阿里云函数计算(一)----Java Http 触发器极速迁移传统 Spring 应用

前言

阿里云函数计算 Function Compute(FC),旨在帮助用户采用弹性伸缩、动态分配资源的方式,来执行业务函数。让用户无需购买部署服务器,无需考虑业务负载,就能快速搭建可处理高并发的后台服务。
函数计算平台针对 Java 语言推出的 Java HTTP 触发器功能,能够无缝迁移传统的 Java Web 应用。支持基于 Servlet 协议的 Web 框架所开发的应用,比如常用的 Spring、SpringBoot、Struts2等。
本文介绍如何使用 Java HTTP 触发器来快速迁移 Spring 提供的开源 Web 项目 GreenHouse

相关链接

开始迁移

Java HTTP 触发器使用示例代码

一、打包需要迁移的 Web 工程为 war 包

可以使用已编译好的 greenhouse.war,本步骤可直接跳过

  1. GreenHouse github 上下载 Web 工程源码。
  2. 在源码根目录执行 maven 打包命令 maven clean package -DskipTests

注意 GreenHouse 使用的 JDK 版本是1.6,比较老。这里打包前需要修改下 pom.xml 为你使用的 JDK 版本。
玩转阿里云函数计算(一)----Java Http 触发器极速迁移传统 Spring 应用

打包成功后,在 target 目录下能看到 greenhouse-1.0.0.BUILD-SNAPSHOT.war 这个文件。最后为了方便将该 war 包重命名为 greenhouse.war

二、在函数计算平台创建 Java 函数

将要运行的应用 war 包可以和函数代码一起打包上传,也可以放在网络存储中比如 阿里云对象存储(OSS),或者任何其它的网络存储。以下示例将应用 war 包放在函数代码工程中和存储到 OSS 中两种方式。

方式一:应用 war 包放在函数代码工程

  1. 在本地创建 maven 工程,并创建一个 package 比如 com.aliyun.fc.example,在 package 中添加 Java 类 HelloWebLocal.java:

    public class HelloWebLocal implements FunctionInitializer, HttpRequestHandler {
        private FcAppLoader fcAppLoader = new FcAppLoader();
    
        private String key = "greenhouse.war";
        private String userContextPath = "/2016-08-15/proxy/${YourServiceName}/${YourFunctionName}";
      
        @Override
        public void initialize(Context context) throws IOException {
            FunctionComputeLogger fcLogger = context.getLogger();
          
            fcAppLoader.setFCContext(context);
          
            // Set war file path
            fcAppLoader.loadCodeFromLocalProject(key);
          
            // Init webapp from code
            long timeBegin = System.currentTimeMillis();
            fcLogger.info("Loading webapp: " + key);
            boolean initSuccess = fcAppLoader.initApp(userContextPath, HelloWebOSS.class.getClassLoader());
            if(! initSuccess) {
                throw new IOException("Init web app failed");
            }
            fcLogger.info("Loaded webapp, elapsed: " + (System.currentTimeMillis() - timeBegin) + "ms");
        }
      
        @Override
        public void handleRequest(HttpServletRequest request, HttpServletResponse response, Context context)
                throws IOException, ServletException {
            try {
                fcAppLoader.forward(request, response);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    其中引用的 maven 库:

    <dependency>
       <groupId>com.aliyun.fc.runtime</groupId>
       <artifactId>fc-java-core</artifactId>
       <version>1.3.0</version>
      </dependency>
    
    <dependency>
       <groupId>com.aliyun.fc.runtime</groupId>
       <artifactId>fc-java-common</artifactId>
       <version>1.0.0</version>
      </dependency>
  2. 将 greenhouse.war 拷贝到 Java 工程中,上述代码中 loadCodeFromLocalProject 方法参数为 greenhouse.war 文件在工程中的相对路径。比如放在 src/main/resources 目录,那就和上述代码保持一样,否则改成对应的相对路径。

方式二:应用 war 包存储到 OSS 中

如果将 war 包放在创建函数的 Java 工程中,会增加创建函数时上传的代码包大小。对于代码包大小函数计算有限制最大为 50M ,请参考函数计算使用限制。往往 Java 的 web 应用 war 包会比较大,因而更好的方式是将 war 包放在 OSS 中,然后通过初始化函数 initializer 来下载 war 包到执行环境中。同样可以调用 fc-java-common 库中的 loadCodeFromOSS 方法即可,该方法会将对应的 war 包下载到执行环境的临时磁盘目录 /tmp 中。

  1. 将 greenhouse.war 上传到 OSS 中
  2. 方式一创建 maven 工程以及 Java package,并创建 HelloWebOSS.java:

    public class HelloWebOSS implements FunctionInitializer, HttpRequestHandler {
        private FcAppLoader fcAppLoader = new FcAppLoader();
    
        private String ossEndPoint = "${YourOSSEndpoint}";
        private String bucket = "${YourOSSBucket}";
        private String key = "greenhouse.war";
        private String userContextPath = "/2016-08-15/proxy/${YourServiceName}/${YourFunctionName}";
      
        @Override
        public void initialize(Context context) throws IOException {
            FunctionComputeLogger fcLogger = context.getLogger();
          
            fcAppLoader.setFCContext(context);
          
            // Load code from OSS
            fcLogger.info("Begin load code: " + key);
            boolean codeSuccess = fcAppLoader.loadCodeFromOSS(ossEndPoint, bucket, key);
            if (! codeSuccess) {
                throw new IOException("Download code failed");
            }
            fcLogger.info("End load code");
          
            // Init webapp from code
            long timeBegin = System.currentTimeMillis();
            fcLogger.info("Begin load webapp");
            boolean initSuccess = fcAppLoader.initApp(userContextPath, HelloWebOSS.class.getClassLoader());
            if(! initSuccess) {
                throw new IOException("Init web app failed");
            }
            fcLogger.info("End load webapp, elapsed: " + (System.currentTimeMillis() - timeBegin) + "ms");
        }
      
        @Override
        public void handleRequest(HttpServletRequest request, HttpServletResponse response, Context context)
                throws IOException, ServletException {
            try {
                fcAppLoader.forward(request, response);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

函数计算平台创建函数

关于怎么在函数计算平台创建函数请参考: Hello World 示例
关于怎么使用 Java 语言请参考:Java 运行环境

将上述的 maven 工程打包,并在函数计算平台创建服务和函数,这里需要注意的点:

  1. 需要将您创建的服务名和函数名,填充到上述 HelloWebLocal.java 中:

    private String userContextPath = "/2016-08-15/proxy/${YourServiceName}/${YourFunctionName}";
  2. 如果您使用的是 OSS 存储方式,需要填充 OSS 相关信息到 HelloWebOSS.java 中:

      private String ossEndPoint = "${YourOSSEndpoint}";
      private String bucket = "${YourOSSBucket}";
      private String userContextPath = "/2016-08-15/proxy/${YourServiceName}/${YourFunctionName}";
  3. 创建函数时除了需要设置函数入口外,还需要设置初始化入口指向上述代码的 initialize 函数。
  4. Web 应用使用的内存较多,请注意函数内存大小设置,比如我这里设置的是 512M

示例配置如下:
玩转阿里云函数计算(一)----Java Http 触发器极速迁移传统 Spring 应用

为函数创建 HTTP 触发器

关于怎么创建 HTTP 触发器请参考:HTTP 触发器

测试函数运行

在函数计算控制台执行 HTTP 触发器,看到如下返回:
玩转阿里云函数计算(一)----Java Http 触发器极速迁移传统 Spring 应用

可以看到成功返回了 greenhouse 的 web 页面。
但是如果在浏览器中执行 HTTP 触发器的请求地址,web 页面将会已文件的形式下载。这是因为函数计算为了安全强制设置了请求返回的 header 为 Content-Disposition: attachment,所以返回结果会以附件形式下载。

为 HTTP 触发器绑定自定义域名

为了解决这个问题,需要用户申请自定义域名,并绑定域名解析到函数计算中,请参考函数计算绑定自定义域名
使用自定义域名访问,需要将最初的 Java 代码中 userContextPath 改为自定义域名的 contextPath 比如/ /greenhouse 等,这样才能保证页面中的超链接是相对于当前请求的域名:

// Not use custom domain
//private String userContextPath = "/2016-08-15/proxy/${YourServiceName}/${YourFunctionName}";

// Use custom domain
private String userContextPath = "/greenhouse";

自定义域名绑定到 HTTP 触发的示例配置:
玩转阿里云函数计算(一)----Java Http 触发器极速迁移传统 Spring 应用

自定义域名路径前缀必须和代码中 userContextPath 保持一致,比如这里都是 /greenhouse。当然可以选择其它的路径名,也可以使用根路径,即路径配置为 /* 对应的 userContextPath 为 /
通过修改配置路径和对应的 userContextPath,可映射到不同的 HTTP 触发器函数,从而映射到不同的 web 应用中。这样就能同一个域名同时复用到不同的 web 应用。

浏览器中访问自定义域名:
玩转阿里云函数计算(一)----Java Http 触发器极速迁移传统 Spring 应用

大功告成!

后语

函数计算是 serverless 服务,对于传统的 Web 应用支持目前还是存在一些瑕疵。

  1. 无状态性,每次函数执行都是无状态的。对于 Web 应用往往有很多状态需要保持,比如 session,用户需要在自己的 Web 应用中去处理。当然很多 web 框架已经提供了很方便的分布式 session 方案,只需简单的配置即可,可以参考Spring Session Redis
  2. 冷启动,新的执行环境中第一次请求 web 应用会比较耗时,这是由于执行环境需要启动 JVM 以及加载 web 应用,针对这个可通过定时预热的方式来解决。

但这些相对于 serverless 提供的弹性伸缩和按需付费的优点不值一提,个人认为 serverless 必定会取代传统物理机或虚拟机的服务器方式,从而让有限的资源得到更高效的利用。

上一篇:MySQL:级联删除操作


下一篇:mysql 的几种安装方式