Dapr Java Http 呼叫

Zhang_Xiang發表於2020-11-08

版本介紹

  • Java 版本:8
  • Dapr Java SKD 版本:0.9.2

Dapr Java-SDK HTTP 呼叫文件 有個先決條件,內容如下:

  • Dapr and Dapr CLI.
  • Java JDK 11 (or greater): Oracle JDK or OpenJDK.
  • Apache Maven version 3.x.

大家看到 Java JDK 版本最低要求是 11,但是本文顯示使用的 JDK 8,這麼做的原因是什麼呢,可以參考 Java-SDK Issues,Issues 中回答如下:

We want to validate that the SDK is built with Java 8 and apps can use it with Java 11.

意思是他們想通過 Java 11 寫的應用程式驗證 Java 8 寫的 SDK 是否能正常使用。本文不需要驗證 Java 11 能否使用 Java-SDK ,因此本文將使用 Java 8 構建應用程式。

工程結構

3 個子工程,一個 client,兩個 service。新建兩個 service 的意義在於展示 http 鏈路呼叫使用 dapr 如何實現。3 個工程專案都整合了 Spring Boot。Spring Boot 啟動後會自動註冊 Controller、Config 之類的 bean。

graph LR; java-client-a--1-->java-service-b; java-service-b--2-->java-service-c; java-service-c--3-->java-service-b; java-service-b--4-->java-client-a;
  1. java-client-a 做為客戶端呼叫 java-service-b;
  2. java-service-b 接收請求,並呼叫 java-service-c;
  3. java-service-c 接收請求,並響應;
  4. java-service-b 收到 java-service-c 應答,並響應 java-client-a 請求。

java-service-c

java-service-c 做為 http 呼叫鏈路末端,只需監聽 http 請求即可。

package com.dapr.service;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Options;

/**
 * @author Zhang_Xiang
 * @since 2020/11/7 10:51:22
 */
public class ServiceC {

    /**
     * Starts the service.
     *
     * @param args Expects the port: -p PORT
     * @throws Exception If cannot start service.
     */
    public static void main(String[] args) throws Exception {
        Options options = new Options();
        options.addRequiredOption("p", "port", true, "Port to listen to.");

        CommandLineParser parser = new DefaultParser();
        CommandLine cmd = parser.parse(options, args);

        // If port string is not valid, it will throw an exception.
        int port = Integer.parseInt(cmd.getOptionValue("port"));

        DaprApplication.start(port);
    }
}

DaprApplication.start(port); 整合 SpringBoot 啟動。

package com.dapr.service;

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

/**
 * Dapr's HTTP callback implementation via SpringBoot.
 * Scanning package io.dapr.springboot is required.
 *
 * @author zhangxiang
 */
@SpringBootApplication(scanBasePackages = {"com.dapr.service"})
public class DaprApplication {

    /**
     * Starts Dapr's callback in a given port.
     *
     * @param port Port to listen to.
     */
    public static void start(int port) {
        SpringApplication app = new SpringApplication(DaprApplication.class);
        app.run(String.format("--server.port=%d", port));
    }

}

啟動命令:

dapr run --app-id java-service-c --app-port 9100 --dapr-http-port 3510 -- java -jar target/dapr-java-service-exec.jar com.dapr.service.ServiceC -p 9100

java-service-b

java-service-b 需要配置一個 DaprClient Bean,以在需要使用 Http 客戶端的地方注入。

package com.dapr.service.config;

import io.dapr.client.DaprClient;
import io.dapr.client.DaprClientBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author Zhang_Xiang
 * @since 2020/11/8 08:46:49
 */
@Configuration
public class Client {

    @Bean
    public DaprClient getClient(){
        return (new DaprClientBuilder()).build();
    }
}

接下來在需要呼叫的 Controller 中新增構造器注入。


/**
 * SpringBoot Controller to handle input binding.
 *
 * @author zhangxiang
 */
@RestController
public class HelloController {

    private final DaprClient client;
    ...

    public HelloController(DaprClient client) {
        this.client = client;
    }
    ...
}

發起 http 請求。

...

byte[] response = client.invokeService(SERVICE_APP_ID, "say", message, HttpExtension.POST, null,
                        byte[].class).block();
                if (response != null) {
                    ...
                }
...

啟動命令:

dapr run --app-id java-service-b --app-port 9101 --dapr-http-port 3511 -- java -jar target/dapr-java-service-exec.jar com.dapr.service.ServiceB -p 9101

java-client-a

對於 java-client-a 來說,整合 Springboot 是可選項,此處構造一個每隔 5 秒發起一次請求的客戶端。

package com.dapr.client;

import com.alibaba.fastjson.JSON;
import com.common.ResponseResult;
import io.dapr.client.DaprClient;
import io.dapr.client.DaprClientBuilder;
import io.dapr.client.domain.HttpExtension;

import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.TimeZone;

/**
 * @author Zhang_Xiang
 * @since 2020/11/7 17:30:26
 */
public class ClientA {
    /**
     * Identifier in Dapr for the service this client will invoke.
     */
    private static final String SERVICE_APP_ID = "java-service-b";

    /**
     * Format to output date and time.
     */
    private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

    /**
     * Starts the invoke client.
     *
     * @param args Messages to be sent as request for the invoke API.
     */
    public static void main(String[] args) throws IOException {
        try (DaprClient client = (new DaprClientBuilder()).build()) {
            while (true) {
                Calendar utcNow = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
                String utcNowAsString = DATE_FORMAT.format(utcNow.getTime());
                String msg = String.format("%s:this this java client A", utcNowAsString);
                byte[] response = client.invokeService(SERVICE_APP_ID, "say", msg.getBytes(), HttpExtension.POST, null,
                        byte[].class).block();
                if (response != null) {
                    String responseResultStr = new String(response);
                    ResponseResult responseResult = JSON.parseObject(responseResultStr, ResponseResult.class);
                    System.out.println(responseResult.getMessage());
                }
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

啟動命令:

dapr run --app-id java-client-a  --dapr-http-port 3006 -- java -jar target/dapr-java-client-exec.jar com.dapr.client.ClientA

總結

各個模組的啟動順序應為:

graph LR; java-service-c-->java-service-b; java-service-b-->java-client-a;

這裡限定順序的原因是,如果先啟動 java-client-a ,java-client-a 會立刻通過 dapr 開始發起請求到 java-service-b ,而這時 java-service-b 並未啟動。這將觸發 dapr 的重試機制。

重試

服務呼叫在事件呼叫失敗和瞬態錯誤時,將執行帶避退時間間隔(backoff time periods)的自動重試。
引起重試的錯誤:

  • 網路錯誤,包括終端不可用和拒絕連線。
  • 身份認證錯誤,由於在呼叫方/被呼叫方的 dapr 邊車證照更新導致。

每次重試都以 1 秒的時間避退時間為間隔,最大重試次數為 3 次。和目的地邊車通過 gRPC 建立連線 5 秒超時。

java-client-a 列印:

== APP == This is java-service-b,receive the message:2020-11-08 14:21:14.336:this this java client A,and request java-service-c get the response:{"message":"This is java-service-c,receive the message:\"2020-11-08 14:21:14.336:this this java client A\""}

java-service-b 列印:

== APP == This is java-service-b,receive the message:2020-11-08 14:21:44.454:this this java client A

java-service-c 列印:

== APP == This is java-service-c,receive the message:"2020-11-08 14:22:19.571:this this java client A"

開啟新的命令列視窗,輸入 dapr list

啟動示例

原始碼地址:https://github.com/ZhangX-Byte/dapr-java

克隆倉庫

git clone https://github.com/ZhangX-Byte/dapr-java.git
cd dapr-java

構建 dapr-java 專案

mvn install

然後各個專案各自 install 就能正常啟動了。

相關文章