一、概要
上篇介绍什么是 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>
<!-- <!– Dapr's SDK integration with SpringBoot (optional). –>-->
<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)
我们再回顾一下之前的路径图:
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,重复部分不再赘述。本文实战了服务之间调用、状态管理,后续还会针对官网其它的主要功能进行实战介绍。
推荐阅读
招贤纳士
政采云技术团队(Zero),一个富有激情、创造力和执行力的团队,Base 在风景如画的杭州。团队现有300多名研发小伙伴,既有来自阿里、华为、网易的“老”兵,也有来自浙大、中科大、杭电等校的新人。团队在日常业务开发之外,还分别在云原生、区块链、人工智能、低代码平台、中间件、大数据、物料体系、工程平台、性能体验、可视化等领域进行技术探索和实践,推动并落地了一系列的内部技术产品,持续探索技术的新边界。此外,团队还纷纷投身社区建设,目前已经是 google flutter、scikit-learn、Apache Dubbo、Apache Rocketmq、Apache Pulsar、CNCF Dapr、Apache DolphinScheduler、alibaba Seata 等众多优秀开源社区的贡献者。如果你想改变一直被事折腾,希望开始折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊……如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的技术团队的成长过程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 zcy-tc@cai-inc.com
微信公众号
文章同步发布,政采云技术团队公众号,欢迎关注