Dapr 实战(二)

image.png

水手.png

一、概要

​ 上篇介绍什么是 Dapr、Dapr 能解决什么问题、Dapr 的发展史,Dapr 的功能简介、Dapr 的安装,本文针对 Dapr 的功能展开,深入功能进行实战。本文先介绍三个功能:服务之间调用 (Service-to-service invocation)、状态管理 (State management) ,所有 demo 均以 Java 的形式展示。

二、Demo 项目配置

2.1 resources 配置

<!-- application.properties -->
server.port=8880
  
<!-- logback.xml -->
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d {HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

2.2 启动类

<!-- HelloWorldApplication -->
package com.test.dapr.sample;

import com.test.dapr.sample.interfaces.http.DemoActorImpl;
import io.dapr.actors.runtime.ActorRuntime;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class HelloWorldApplication {

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

2.3 基本类

<!-- Response -->
package com.test.dapr.sample.interfaces.http;

import lombok.Data;
import lombok.ToString;
import java.io.Serializable;

@Data
@ToString
public class Response<T> implements Serializable {

    private boolean success;

    private T result;

    private String code;

    private String message;

    public static <T> Response<T> ok(T data) {
        Response resp = new Response();
        resp.setResult(data);
        resp.setSuccess(true);
        return resp;
    }
}

<!-- DaprConfig -->
package com.test.dapr.sample.config;

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

@Configuration
public class DaprConfig {

    private static final DaprClientBuilder BUILDER = new DaprClientBuilder();

    @Bean
    public DaprClient daprClient() {
        return BUILDER.build();
    }
}

2.4 pom 基本配置

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>dapr-helloworld</artifactId>
    <version>0.0.35</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <zcy-dependency-manage.version>3.0.0-RELEASE</zcy-dependency-manage.version>
        <zcy-release-check-plugin.version>3.0.0-RELEASE</zcy-release-check-plugin.version>
        <hutool.version>4.5.2</hutool.version>
        <guava.version>18.0</guava.version>
        <commons-lang3.version>3.3.1</commons-lang3.version>
        <fastjson.version>1.2.60</fastjson.version>
        <zcy-common-api.version>1.0.0-RELEASE</zcy-common-api.version>
        <protobuf.output.directory>${project.build.directory}/generated-sources</protobuf.output.directory>
        <protobuf.input.directory>${project.basedir}/proto</protobuf.input.directory>
        <maven.deploy.skip>true</maven.deploy.skip>
        <spotbugs.fail>false</spotbugs.fail>
        <opentelemetry.version>0.14.0</opentelemetry.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.12.2</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>commons-cli</groupId>
            <artifactId>commons-cli</artifactId>
            <version>1.4</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>1.39.0</version>
            <exclusions>
                <exclusion>
                    <artifactId>protobuf-java</artifactId>
                    <groupId>com.google.protobuf</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>error_prone_annotations</artifactId>
                    <groupId>com.google.errorprone</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <artifactId>protobuf-java</artifactId>
            <groupId>com.google.protobuf</groupId>
            <version>3.13.0</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>1.39.0</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-api</artifactId>
            <version>1.39.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
        <dependency>
            <groupId>com.jayway.jsonpath</groupId>
            <artifactId>json-path</artifactId>
            <version>2.4.0</version>
        </dependency>

        <!-- Dapr's core SDK with all features, except Actors. -->
        <dependency>
            <groupId>io.dapr</groupId>
            <artifactId>dapr-sdk</artifactId>
            <version>1.3.1</version>
        </dependency>
        <!-- Dapr's SDK for Actors (optional). -->
        <dependency>
            <groupId>io.dapr</groupId>
            <artifactId>dapr-sdk-actors</artifactId>
            <version>1.3.1</version>
        </dependency>
        <!--        &lt;!&ndash; Dapr's SDK integration with SpringBoot (optional). &ndash;&gt;-->
        <dependency>
            <groupId>io.dapr</groupId>
            <artifactId>dapr-sdk-springboot</artifactId>
            <version>1.3.1</version>
        </dependency>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-stdlib</artifactId>
            <version>1.3.50</version>
        </dependency>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-stdlib-common</artifactId>
            <version>1.3.50</version>
        </dependency>
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>4.9.0</version>
            <exclusions>
                <exclusion>
                    <artifactId>kotlin-stdlib</artifactId>
                    <groupId>org.jetbrains.kotlin</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>kotlin-stdlib-common</artifactId>
                    <groupId>org.jetbrains.kotlin</groupId>
                </exclusion>
            </exclusions>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.0.3.RELEASE</version>
                <executions>
                    <execution>
                        <id>sprint-boot-application</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-deploy-plugin</artifactId>
                <version>2.8.2</version>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <configuration>
                    <attach>true</attach>
                </configuration>
                <executions>
                    <execution>
                        <phase>compile</phase>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

2.5 打包Docker镜像

使用 Dockfile 文件方式

FROM java:8-jre
WORKDIR /root
ADD ./dapr-helloworld-0.0.35.jar ./
ENTRYPOINT ["java", "-Xmx200m", "-jar", "dapr-helloworld-0.0.35.jar"]

Maven 插件配置如下:

<!-- docker-maven-plugin -->
<plugin>
    <groupId>com.spotify</groupId>
    <artifactId>docker-maven-plugin</artifactId>
    <version>0.4.13</version>
    <configuration>
        <imageName>xxx.xxx.com/xxx/${project.artifactId}</imageName>
        <baseImage>java:8-jre</baseImage>
        <workdir>/root</workdir>
        <entryPoint>["java", "-Xmx200m", "-jar", "${project.build.finalName}.jar"]</entryPoint>
        <resources>
            <resource>
                <targetPath>.</targetPath>
                <directory>${project.build.directory}</directory>
                <include>${project.build.finalName}.jar</include>
            </resource>
        </resources>
        <imageTags>
            <imageTag>${project.version}</imageTag>
        </imageTags>
    </configuration>
</plugin>

Docker 相关的不详细介绍,如不会使用的同学,可自行去学习一波。打包完后,推送镜像到 Docker 仓库

三、服务之间调用 (Service-to-service invocation)

我们再回顾一下之前的路径图:

image-20211229154848648

3.1 Demo 代码

首先建立2个服务及 Controller,设置两个服务的 serverport,demo1是8888,demo2是8880。

demo1 的 Controller

<!-- HelloWorldController -->
@RestController
public class HelloWorldController {
  
  @PostMapping("/helloWorld")
    public Response<String> helloWorld() {
        System.out.println("post-helloWorld");
        return Response.ok("post-helloWorld");
    }

    @GetMapping("/helloWorld")
    public Response<String> getHelloWorld() {
        System.out.println("get-helloWorld");
        return Response.ok("get-helloWorld");
    }
}

Demo2 的 Controller

@RestController
public class HelloWorldController {

  	@Autowired
    private DaprClient daprClient;

		@PostMapping("/dapr-helloWorld")
    public Response<String> helloWorld() {
        System.out.println("helloWorld");
        return Response.ok("helloWorld");
    }
    
    @GetMapping("/dapr-helloWorld")
    public Response<String> getHelloWorld(@RequestHeader Map<String, String> headers) {
        System.out.println("getHelloWorld");
        HttpExtension httpExtension = new HttpExtension(DaprHttp.HttpMethods.GET, null, headers);

        InvokeMethodRequest request = new InvokeMethodRequest("nodeapp", "helloWorld")
                .setBody(null)
                .setHttpExtension(httpExtension);
        Response response = daprClient.invokeMethod(request, TypeRef.get(Response.class)).block();
//                .subscriberContext(getReactorContext()).block();
        System.out.println("finish getHelloWorld");
        if (response != null) {
            return Response.ok(response.getResult().toString());
        }
        return null;
    }
}

3.2 k8s 的 yaml 配置

demo1.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nodeapp
  labels:
    app: http-demo
spec:
  selector:
    matchLabels:
      app: http-demo
  replicas: 1
  template:
    metadata:
      annotations:
        dapr.io/enabled: "true"
        dapr.io/app-id: "nodeapp"
        dapr.io/app-port: "8888"
        dapr.io/app-protocol: "http"
#        dapr.io/log-as-json: "true"
        dapr.io/config: "zipkin-config"
#        dapr.io/app-config: "appconfig"
        dapr.io/sidecar-listen-addresses: 0.0.0.0
      labels:
        app: http-demo
    spec:
      containers:
        - name: http-demo
          image: xxxx.xxxx.com/xxxx/helloworld:0.0.3
          ports:
            - containerPort: 8888
---
apiVersion: v1
kind: Service
metadata:
  name: http-demo
spec:
  type: NodePort
  selector:
    app: http-demo
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8888
      nodePort: 31111

demo2.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: daprnodeapp
  labels:
    app: dapr-http-demo
spec:
  selector:
    matchLabels:
      app: dapr-http-demo
  replicas: 1
  template:
    metadata:
      annotations:
        dapr.io/enabled: "true"
        dapr.io/app-id: "daprnodeapp"
        dapr.io/app-port: "8880"
#        dapr.io/log-as-json: "true"
        dapr.io/config: "zipkin-config"
#        dapr.io/app-config: "appconfig"
        dapr.io/sidecar-listen-addresses: 0.0.0.0
      labels:
        app: dapr-http-demo
    spec:
      containers:
        - name: dapr-http-demo
          image: xxxx.xxxx.com/xxxx/dapr-helloworld:0.0.35
          ports:
            - containerPort: 8880
---
apiVersion: v1
kind: Service
metadata:
  name: dapr-http-demo
spec:
  type: NodePort
  selector:
    app: dapr-http-demo
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8880
      nodePort: 32222

3.3 k8s 启动项目

执行 kubectl apply -f http-demo.yaml -n xxx 启动 demo1 项目

执行 kubectl apply -f http-demo-dapr.yaml -n xxx 启动 demo2 项目

3.4 服务验证

1. 外网demo1服务

   curl http://外网ip:31111/helloWorld

2. 内网通过dapr访问服务

   查询到内网ip:kubectl get pods -o wide -n xxx

   curl http://demo1的内网ip:3500/v1.0/invoke/nodeapp/method/helloWorld

3. 作为sidecard的dapr验证正常后,验证通过demo2去访问demo1的服务。demo2外网访问(同2)

   curl http://demo2的内网ip:3500/v1.0/invoke/daprnodeapp/method/dapr-helloWorld

至此,dapr服务之间的调用实战完成。我们可以使用官方轻量级的sdk进行调用,当然也可以自己通过http进行调用都可以。

四、状态管理 (State management)

状态管理官方提供通用的GA版本只有Redis 和 MongoDB,下面我们通过Redis的实战加深对此功能熟悉。

4.1 k8s 安装 redis

创建文件 redis.yaml,创建 namespace:kubectl create namespace redis-sys

启动 kubectl create -f redis.yaml -n redis-sys

kind: Deployment
apiVersion: apps/v1
metadata:
  name: redis
  namespace: redis-sys
spec:
  replicas: 1
  selector:
    matchLabels:
      name: redis
  template:
    metadata:
      labels:
        name: redis
    spec:
      containers:
      - name: redis
        image: redis:5
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 6379
          protocol: TCP
---
kind: Service
apiVersion: v1
metadata:
  name: redis
  namespace: redis-sys
  labels:
    name: redis
spec:
 type: NodePort
 ports:
 - port: 6379
   targetPort: 6379
   nodePort: 30041
   name: redis-port
 selector:
   name: redis

4.2 创建状态储存

新建 statestore.yaml 文件,启动:kubectl create -f statestore.yaml,启动 http-demo 项目

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: statestore
  namespace: default
spec:
  type: state.redis
  version: v1
  metadata:
  - name: redisHost
    value: redis内网ip:6379
  - name: redisPassword
    value: ""

4.3 服务验证

访问的 ip 地址还是 http-demo 对应的内网 ip

单个创建

curl -X POST -H "Content-Type: application/json" -d '[{ "key": "key1", "value": "value1"}]' xx.xx.xx.xx:3500/v1.0/state/…

批量创建

curl -X POST -H "Content-Type: application/json" -d '[{ "key": "key1", "value": "value1"}, { "key": "key2", "value": "value2"}]' xx.xx.xx.xx:3500/v1.0/state/…

单个获取

curl xx.xx.xx.xx:3500/v1.0/state/…

批量获取

curl -X POST -H "Content-Type: application/json" -d '{"keys":["key1", "key2"]}' xx.xx.xx.xx:3500/v1.0/state/…

删除

curl -X DELETE 'xx.xx.xx.xx:3500/v1.0/state/…'

状态事务性操作

操作:curl -X POST -H "Content-Type: application/json" -d '{"operations": [{"operation":"upsert", "request": {"key": "key1", "value": "newValue1"}}, {"operation":"delete", "request": {"key": "key2"}}]}' xx.xx.xx.xx:3500/v1.0/state/…

查询:curl -X POST -H "Content-Type: application/json" -d '{"keys":["key1", "key2"]}' xx.xx.xx.xx:3500/v1.0/state/…

默认模式:Last-write-wins,也支持First-Write-Wins(乐观锁)

官方支持的中间件:www.bookstack.cn/read/dapr-1…

五、小结

​ 以上提供了 Dapr 相关的 Java Demo,后续其它功能也基于上面的demo,重复部分不再赘述。本文实战了服务之间调用、状态管理,后续还会针对官网其它的主要功能进行实战介绍。

推荐阅读

Dapr 实战(一)

DS 版本控制核心原理揭秘

DS 2.0 时代 API 操作姿势

一次搜索性能的排查过程和优化效果

招贤纳士

政采云技术团队(Zero),一个富有激情、创造力和执行力的团队,Base 在风景如画的杭州。团队现有300多名研发小伙伴,既有来自阿里、华为、网易的“老”兵,也有来自浙大、中科大、杭电等校的新人。团队在日常业务开发之外,还分别在云原生、区块链、人工智能、低代码平台、中间件、大数据、物料体系、工程平台、性能体验、可视化等领域进行技术探索和实践,推动并落地了一系列的内部技术产品,持续探索技术的新边界。此外,团队还纷纷投身社区建设,目前已经是 google flutter、scikit-learn、Apache Dubbo、Apache Rocketmq、Apache Pulsar、CNCF Dapr、Apache DolphinScheduler、alibaba Seata 等众多优秀开源社区的贡献者。如果你想改变一直被事折腾,希望开始折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊……如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的技术团队的成长过程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 zcy-tc@cai-inc.com

微信公众号

文章同步发布,政采云技术团队公众号,欢迎关注

image.png