OpenFaaS實戰之三:Java函數

歡迎訪問我的GitHub

https://github.com/zq2599/blog_demos

內容:所有原創文章分類彙總及配套源碼,涉及Java、Docker、Kubernetes、DevOPS等;

OpenFaaS實戰系列文章鏈接

  1. 部署
  2. 函數入門
  3. Java函數
  4. 模板操作(template)
  5. 大話watchdog
  6. of-watchdog(爲性能而生)
  7. java11模板解析
  8. OpenFaaS實戰之八:自制模板(maven+jdk8)
  9. OpenFaaS實戰之九:終篇,自制模板(springboot+maven+jdk8)

本篇概覽

  1. 本文是《OpenFaaS實戰》系列的第三篇,經過前文實戰,咱們掌握了函數開發和部署的要領,作爲一名Java程序員,當然迫切的希望用Java編寫OpenFaaS函數,於是就有了本文;
  2. 本文開發一個Java函數,功能是解析請求body中的JSON字符串,再加上JVM進程ID、IP地址、當前時間一起拼成字符串,包裝在JSON中返回;
  3. 平時寫java代碼會用到各種二方庫,這裏引入jackson的庫,作爲OpenFaaS添加依賴的參考;

源碼下載

名稱 鏈接 備註
項目主頁 https://github.com/zq2599/blog_demos 該項目在GitHub上的主頁
git倉庫地址(https) https://github.com/zq2599/blog_demos.git 該項目源碼的倉庫地址,https協議
git倉庫地址(ssh) [email protected]:zq2599/blog_demos.git 該項目源碼的倉庫地址,ssh協議
  • 這個git項目中有多個文件夾,本章的應用在<font color="blue">openfaas</font>文件夾下,如下圖紅框所示:

在這裏插入圖片描述

  • <font color="blue">openfaas</font>裏面有多個子文件夾,本篇的源碼在<font color="blue">currenttime</font>中,如下圖紅框:

在這裏插入圖片描述

創建函數

  1. 執行以下命令,即可創建名爲<font color="blue">faas-currenttime</font>的函數,此函數的鏡像前綴是<font color="blue">bolingcavalry</font>,語言類型爲<font color="red">java11</font>:
faas-cli new faas-currenttime --lang java11 -p bolingcavalry
  1. 控制檯響應如下:
[root@node1 20]# faas-cli new faas-currenttime --lang java11 -p bolingcavalry
2020/11/20 15:47:50 No templates found in current directory.
2020/11/20 15:47:50 Attempting to expand templates from https://github.com/openfaas/templates.git

2020/11/20 15:47:56 Fetched 12 template(s) : [csharp dockerfile go java11 java11-vert-x node node12 php7 python python3 python3-debian ruby] from https://github.com/openfaas/templates.git
Folder: faas-currenttime created.
  ___                   _____           ____
 / _ \ _ __   ___ _ __ |  ___|_ _  __ _/ ___|
| | | | '_ \ / _ \ '_ \| |_ / _` |/ _` \___ \
| |_| | |_) |  __/ | | |  _| (_| | (_| |___) |
 \___/| .__/ \___|_| |_|_|  \__,_|\__,_|____/
      |_|


Function created in folder: faas-currenttime
Stack file written: faas-currenttime.yml

Notes:
You have created a function using the java11 template which uses an LTS
version of the OpenJDK.
  1. 當前目錄已經新增了文件<font color="blue">faas-currenttime.yml</font>和文件夾<font color="blue">faas-currenttime</font>
  2. 文件夾<font color="blue">faas-currenttime</font>的內容如下,可見是個gradle工程:
faas-currenttime
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
    ├── main
    │   └── java
    │       └── com
    │           └── openfaas
    │               └── function
    │                   └── Handler.java
    └── test
        └── java
            └── HandlerTest.java
  1. 打開<font color="red">build.gradle</font>文件,添加下圖紅框中的內容,即<font color="blue">jackson</font>和<font color="blue">common</font>庫的依賴:

在這裏插入圖片描述

  1. 進入文件夾<font color="blue">faas-currenttime/src/main/java/com/openfaas/function/</font>,可見已創建了默認的業務功能類<font color="red">Handler.java</font>,打開看看OpenFaaS給的默認代碼啥樣的,如下所示:
package com.openfaas.function;

import com.openfaas.model.IHandler;
import com.openfaas.model.IResponse;
import com.openfaas.model.IRequest;
import com.openfaas.model.Response;

public class Handler extends com.openfaas.model.AbstractHandler {

    public IResponse Handle(IRequest req) {
        Response res = new Response();
            res.setBody("Hello, world!");

            return res;
    }
}
  1. 把Handler.java的內容用以下代碼替換掉,替換後的函數,其功能是取得請求參數,再把當前JVM的進程ID、IP地址、當前時間都拼接到一個字符串中返回,需要重點關注的有兩點:將請求參數反序列化成Map實例,以及將Map序列化成JSON字符串返回:
package com.openfaas.function;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.openfaas.model.IRequest;
import com.openfaas.model.IResponse;
import com.openfaas.model.Response;
import org.apache.commons.lang3.StringUtils;

import java.lang.management.ManagementFactory;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

public class Handler extends com.openfaas.model.AbstractHandler {

    private static final String PARAM_USER_NAME = "name";


    private static final String RESPONSE_TEMPLETE = "Hello %s, response from [%s], PID [%s], %s";

    private ObjectMapper mapper = new ObjectMapper();


    /**
     * 獲取本機IP地址
     * @return
     */
    public static String getIpAddress() {
        try {
            Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();
            InetAddress ip = null;
            while (allNetInterfaces.hasMoreElements()) {
                NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement();
                if (netInterface.isLoopback() || netInterface.isVirtual() || !netInterface.isUp()) {
                    continue;
                } else {
                    Enumeration<InetAddress> addresses = netInterface.getInetAddresses();
                    while (addresses.hasMoreElements()) {
                        ip = addresses.nextElement();
                        if (ip != null && ip instanceof Inet4Address) {
                            return ip.getHostAddress();
                        }
                    }
                }
            }
        } catch (Exception e) {
            System.err.println("IP地址獲取失敗" + e.toString());
        }
        return "";
    }

    /**
     * 返回當前進程ID
     * @return
     */
    private static String getPID() {
        return ManagementFactory
                .getRuntimeMXBean()
                .getName()
                .split("@")[0];
    }


    private String getUserName(IRequest req) {
        // 如果從請求body中取不到userName,就用
        String userName = null;

        try {
            Map<String, Object> mapFromStr = mapper.readValue(req.getBody(),
                    new TypeReference<Map<String, Object>>() {});

            if(null!=mapFromStr && mapFromStr.containsKey(PARAM_USER_NAME)) {
                userName = String.valueOf(mapFromStr.get(PARAM_USER_NAME));
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        // 如果從請求body中取不到userName,就給個默認值
        if(StringUtils.isBlank(userName)) {
            userName = "anonymous";
        }

        return userName;
    }

    public IResponse Handle(IRequest req) {

        String userName = getUserName(req);

        System.out.println("1. ---" + userName);

        // 返回信息帶上當前JVM所在機器的IP、進程號、時間
        String message = String.format(RESPONSE_TEMPLETE,
                userName,
                getIpAddress(),
                getPID(),
                new SimpleDateFormat( "yyyy-MM-dd hh:mm:ss" ).format(new Date()));

        System.out.println("2. ---" + message);

        // 響應內容也是JSON格式,所以先存入map,然後再序列化
        Map<String, Object> rlt = new HashMap<>();
        rlt.put("success", true);
        rlt.put("message", message);

        String rltStr = null;

        try {
            rltStr = mapper.writeValueAsString(rlt);
        } catch (Exception e) {
            e.printStackTrace();
        }

        Response res = new Response();
        res.setContentType("application/json;charset=utf-8");
        res.setBody(rltStr);

	    return res;
    }
}
  • 至此編碼完成,接下來是製作鏡像和部署;

部署

  1. 在<font color="blue">faas-currenttime.yml</font>所在目錄執行以下命令,即可開始製作鏡像,製作過程中會有gradle的編譯過程,如果編譯失敗會中斷鏡像製作:
faas-cli build -f ./faas-currenttime.yml
  1. 鏡像製作成功時,控制檯輸出類似如下信息:
Step 27/30 : ENV fprocess="java -XX:+UseContainerSupport com.openfaas.entrypoint.App"
---> Running in 0f50636cc747
Removing intermediate container 0f50636cc747
---> 54a5c9a193c8
Step 28/30 : EXPOSE 8080
---> Running in 3252f165af15
Removing intermediate container 3252f165af15
---> c05afc826ec5
Step 29/30 : HEALTHCHECK --interval=5s CMD [ -e /tmp/.lock ] || exit 1
---> Running in 4106410be0a2
Removing intermediate container 4106410be0a2
---> 6d95b73b5f33
Step 30/30 : CMD ["fwatchdog"]
---> Running in 1606dbcd7003
Removing intermediate container 1606dbcd7003
---> 99a519ab82fd
Successfully built 99a519ab82fd
Successfully tagged bolingcavalry/faas-currenttime:latest
Image: bolingcavalry/faas-currenttime:latest built.
[0] < Building faas-currenttime done in 34.94s.
[0] Worker done.


Total build time: 34.94s
  1. 將鏡像推送到鏡像倉庫,以便Kubernetes可以下載到此鏡像,我這裏用的是hub.docker.com,因爲我的ID是<font color="blue">bolingcavalry</font>,所執行以下命令即可推送成功:
docker push bolingcavalry/faas-currenttime:latest
  1. 執行以下命令部署函數到OpenFaaS:
faas-cli deploy -f faas-currenttime.yml
  1. 控制檯響應如下,可見部署已經開始,並且給出了endpoint:
[root@node1 20]# faas-cli deploy -f faas-currenttime.yml
Deploying: faas-currenttime.
WARNING! Communication is not secure, please consider using HTTPS. Letsencrypt.org offers free SSL/TLS certificates.

Deployed. 202 Accepted.
URL: http://192.168.133.187:31112/function/faas-currenttime.openfaas-fn
  1. 打開web端,在頁面上可見新增的函數,驗證操作如下圖所示,可見入參的JSON內容可以被正常解析:

在這裏插入圖片描述

  1. 也可以在控制檯用curl命令測試:
[root@node1 20]# curl \
> -H "Content-Type: application/json" \
> -X POST \
> --data '{"name":"Jerry}' \
> http://192.168.133.187:31112/function/faas-currenttime
{"success":true,"message":"Hello anonymous, response from [10.233.90.79], PID [11], 2020-11-20 02:14:46"}
  1. 執行命令<font color="blue">faas-cli deploy -f faas-currenttime.yml</font>開始部署,控制檯已經接受了部署請求,並給出了函數的endpoint:
[root@node1 20]# faas-cli deploy -f faas-currenttime.yml
Deploying: faas-currenttime.
WARNING! Communication is not secure, please consider using HTTPS. Letsencrypt.org offers free SSL/TLS certificates.

Deployed. 202 Accepted.
URL: http://192.168.133.187:31112/function/faas-currenttime.openfaas-fn

清理

  • 刪除函數的命令如下,依舊是<font color="blue">faas-currenttime.yml</font>所在目錄:
faas-cli remove -f faas-currenttime.yml
  • 至此,最基本的Java函數的開發、部署、驗證都已經完成,如果您也打算用Java開發OpenFaaS函數,希望本文能給您一些參考;

你不孤單,欣宸原創一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 數據庫+中間件系列
  6. DevOps系列

歡迎關注公衆號:程序員欣宸

微信搜索「程序員欣宸」,我是欣宸,期待與您一同暢遊Java世界... https://github.com/zq2599/blog_demos

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章