很多人都是使用SpringBoot 和 Spring Cloud来开发微服务。Dapr 也是开发微服务的框架,它和Spring Cloud有什么区别呢,其实这不是一个区别的问题,它是不同的时代需要不同的框架。
Spring Cloud 是一种产品,提供了分布式应用程序所需的所有要素,包括服务发现、消息传递/流处理、分布式跟踪、 以易于处理的形式从springboot提供功能, 到目前为止,可能没有其他产品比 Spring Cloud 更易于使用。Spring Cloud并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
Spring Cloud 是分布式应用程序开发中的重要产品,足以影响语言选择。 假如你想使用Java 以外的语言开发微服务,比如golang,你想用Spring Cloud Springboot , 最终还是选择了使用Java。
Dapr 的出现是分布式应用程序开发中拥有了语言无关的微服务开发,Dapr足以替代Spring Cloud成为云原生分布式应用开发的选择。熟悉Azure的人可能会觉得它其实更像是Service Fabric的加强版。
我们将Spring Cloud提供的组件与 Dapr 的构建块作一些横向对比:
总的来说无论是Dapr还是Spring Cloud上述这些项目,都是想帮助开发人员简单快速地构建分布式应用。但是由于时代背景的原因,它们的出发点、实现形式又存在一些差异。
我们从分布式应用程序的三大支柱性功能来比较一下Dapr 和 Spring Cloud:
- 服务调用
- 传递异步消息
- 分布式追踪
- 服务调用
首先,比较从应用程序调用另一个应用程序的功能。
Dapr 的调用使用InvokeAPI
源代码如下所示:
@Value("${baseUrl}")
private String baseUrl;
@GetMapping("/invokeHello")
public Map<String, ?> invokeHello() {
Map<?, ?> result = restTemplate.getForObject(baseUrl "/hello", Map.class);
return Map.of("baseUrl", baseUrl, "remoteMessage", result);
}
baseUrl 可以通过为 Dapr 指定调用 API的值来通过 Dapr 调用目标应用程序。http://localhost:${DAPR_HTTP_PORT}/v1.0/invoke/hello-app/method
Dapr 在本地环境中使用mDNS(多播DNS)从应用程序名称中查找目标服务运行的主机,而k8s使用k8s本身的名称解析功能。 在两者都不可用的环境中,您当前必须使用 Consul。
除此之外,Dapr 的优势在于它基本上可以做到开箱即用。
Spring Cloud 服务发现
spring cloud使用Netflix Eureka 进行名称解析,它具有 Eureka 服务器(等效于上述内容)作为名称解析的服务器,每个应用程序都使用Netflix 客户端向Eureka服务器注册自己,并用它来构建客户端来解决自己的主机。 服务注册表非常有用,因为它允许客户端负载平衡,并将服务提供商与使用者隔离开来,而无需 DNS。
服务器端Eureka服务器代码如下所示:
@EnableEurekaServer
@SpringBootApplication
public class ServiceRegistrationAndDiscoveryServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceRegistrationAndDiscoveryServiceApplication.class, args);
}
}
只需启动具有注释@EnableEurekaServer 的应用程序。 当然,您可以添加配置文件以进行精细设置或组合群集。
客户端源代码如下所示:
@Value("${baseUrl}")
private String baseUrl;
@GetMapping("/invokeHello")
public Map<String, ?> invokeHello() {
Map<?, ?> result = restTemplate.getForObject(baseUrl "/hello", Map.class);
return Map.of("baseUrl", baseUrl, "remoteMessage", result);
}
与 Dapr 端的源代码相同。
baseUrl 只要指定了应用程序名称 ,RestTemplate 就会使用Eureka 发现客户端自动访问该应用程序。 简单地说,它比使用Dapr更容易理解。
异构服务通信
传统分布式中间件往往锁定某个语言,比如 Java 体系通常会使用Feign或者Dubbo实现,但它们并没有提供其他语言的库。
因此如果是多语言的环境,那么就需要基于某种通用协议如REST或者GRPC进行通信,可能还会需要额外的注册中心和负载均衡器
当然,现实中的情况往往要比这复杂的多,并且考虑到会引入额外的中间件,带来的运维方面的成本也需要慎重考虑。
Dapr 提供了多语言的SDK,如 .NET、Java、Go、Python、PHP 等,可以使用 HTTP 或者 GRPC 的方式进行异构服务间的调用,能很好地解决这个问题。
Dapr 和Spring Cloud 的服务调用哪个更好?
虽然Dapr 不需要单独的DNS,但它更易于使用,但Spring Cloud需要在本地环境中建立Eureka 服务器。 当然它不是那么难建立,所以不能说这是一个缺点。
此外,调用时的 URL 在Spring Cloud中更易于理解,而 Dapr 的调用 API会很长。 当然这不是一个很大的缺点。
Dapr 和 Spring Cloud各有千秋,但是在kubernetes 环境下,Dapr 直接就利用了Kubernetes的Service ,更加贴合云原生环境,异构服务通信的支持更好。
- 传递异步消息
Dapr 的 Pus/sub API
Dapr 使用消息传递的 Pub/sub API发送消息,只需创建订阅配置文件和 Web API即可接收消息,采用标准的CloudEvents 格式。
发送消息的源代码如下所示:
@Value("${pubsubUrl}")
private String pubsubUrl;
@PostMapping("/publish")
public void publish(@RequestBody MyMessage message) {
restTemplate.postForObject(pubsubUrl, message, Void.class);
}
您可以通过指定名为 pubsubUrl的大写 Pub/sub API向消息代理发送消息。http://localhost:${DAPR_HTTP_PORT}/v1.0/publish/rabbitmq-pubsub/my-message
然后是接收消息的源代码。 它看起来像这样:
@PostMapping("/subscribe")
public void subscribe(@RequestBody CloudEvent<MyMessage> cloudEventMessage) {
System.out.println("subscriber is called");
System.out.println(message);
}
只需创建一个 Web API来接收消息。若要利用此 Web API,请创建类似于以下内容的配置文件:
apiVersion: dapr.io/v1alpha1
kind: Subscription
metadata:
name: subscription
spec:
pubsubname: rabbitmq-pubsub
topic: my-message
route: /subscribe
scopes:
- subscribe-app
metadata.name 值不是特别可用,因此您可以为其指定任何名称。
pubsubname 是 pubsub 的名称。 使用默认情况下设置的pubsub
topic 是消息主题。 此处指定了发布端应用程序中指定的 。my-message
route 是要调用的 Web 应用程序的路径。 是,因为它正在等待的路径。SubscribeController/subscribe
scopes 是正在等待的应用程序的应用 id。 这一次,我将启动一个应用程序应用程序的应用程序,称为子脚本端的应用程序,所以我指定它。subscribe-app
如果在此处列出多个应用程序的 app-id,则多个应用程序可以接收相同的消息。
GitHub示例代码将此文件放在 中。 如果要使用它,请将其复制到用户指令。subscribe/.dapr/components/subscription.yaml
Spring Cloud Stream
使用spring cloud stream 向 cloud 发布信息
在spring cloud stream 2.x 到 3.x 之间,API发生了重大变化,现在更特定于流处理。 在这里,我将介绍3.x 版本。
发送消息一方的源代码:
@PostMapping("/publish")
public void publish(@RequestBody MyMessage message) {
streamBridge.send("my-message-0", message);
}
使用类StreamBridge发送消息。
此外,在配置文件中写入要发送到源代码中指定消息的键的消息代理。
spring.cloud.stream.bindings.my-message-0.destination=my-message
此处指定的值用作 RabbitMQ 交换的名称。
然后是接收消息的源代码。
@Bean
public Consumer<MyMessage> subscribe() {
return (map) -> {
System.out.println("subscriber is called");
System.out.println(map);
};
}
使用包java.util.funciton的 Consumer和Function 来实现 ,而不是像 Dapr 这样的标准的 Web API。
然后,创建一个配置文件,以便在收到消息时调用此方法 (Bean)。
spring.cloud.stream.bindings.subscribe-in-0.destination=my-message
spring.cloud.stream.bindings.subscribe-in-0.group=my-message-subscribe
上面的值用作 Exchange 的名称,下面的值用作队列的名称。 设置中有一些问题就是很难理解。
然而,有一种令人信服的感觉是,将子脚本端视为"函数",而不是"API"并实现它。 您可能从未阅读过此版本的 Spring Cloud Stream 的源代码,因此您可能已经将多个调用合并到 WebFlux 的非阻塞中,而不是逐个从消息代理接收和处理消息。 这有性能优势。
Dapr 提供了一些基础服务的抽象接口,以消息中间件为例,Dapr支持以下中间件的Pub/Sub:
用 Dapr 抽象接口来使用基础服务能力的好处是————当你需要更换中间件的时候,可以少动点代码,换句话也可以说是增加了服务的可移植性,在熬小剑的文章里也有相关描述。
Dapr 使用 HTTP 进行消息传递,内部的通信通过GRPC进行传递,但 Spring Cloud Stream 使用自己的类进行消息传递。因此,虽然 Dapr 在测试时更容易替换为另一个进程,并使用curl 命令进行测试。
Dapr 在可操作性方面会更好。
- 分布式追踪
Dapr 的分布式追踪支持
在 Dapr 中,只需编写配置文件即可启用分布式追踪。
配置文件有以下内容。
apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: daprConfig
spec:
tracing:
samplingRate: "1"
zipkin:
endpointAddress: http://localhost:9411/api/v2/spans
只需指定分布式跟踪的采样率和 zipkin 服务器的地址即可启用分布式追踪。
但是,您必须自己传播跟踪 ID,因此您需要编写如下代码:
@GetMapping("/invokeHello")
public Map<String, ?> invokeHello(@RequestHeader("traceparent") String traceparent) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("traceparent", traceparent);
HttpEntity<?> request = new HttpEntity<>(httpHeaders);
Map<?, ?> result = restTemplate.exchange(helloUrl "/hello", HttpMethod.GET, request, Map.class).getBody();
return Map.of("baseUrl", helloUrl, "remoteMessage", result);
}
在 HTTP 标头中接收到的标头值traceparent 将传递给下一个请求的 HTTP 标头。
Spring Cloud Sleuth
使用 Spring Cloud Sleuth 在 Spring Cloud 中进行分布式追踪。
将 Spring Cloud Sleuth 添加到依赖项并创建配置文件,如下所示:
spring.sleuth.sampler.rate=100
spring.zipkin.sender.type=web
spring.zipkin.baseUrl=http://localhost:9411
使用此设置,将启用分布式追踪并将追踪信息发送到 Zipkin。
分布式追踪涵盖了从与 RestTemplate 和 WebClient 的 HTTP 通信、与 Spring Cloud Stream 的消息传递等所有内容,并且还自动传播 Dapr 存在问题的跟踪 ID。
分布式追踪上Dapr 不需要修改应用,通过配置就可以轻松的调整。
综合比较
到目前为止,我们已经比较了 Dapr 和 Spring Cloud 的三个功能,但总的来说,哪个更好?
Dapr 在清晰性和通过 HTTP的松耦合方面具有优势,另外,不仅考虑到这三个功能,还考虑到其他功能,或者世界信息量的差异,可以说Dapr 更胜一筹。
与版本升级相关的痛苦
那么我为什么不选择 Spring Cloud 而选择 Dapr 呢? 有个重要因素是“版本兼容性和版本升级问题”。
例如,如果您的系统运行旧版本的Java和 Spring Boot,并且您尝试在新系统上使用更新版本的Java和 Spring Boot 进行开发,如果您尝试在每个系统上使用 Spring Cloud,每个 Spring Boot 由于对应的 Spring Cloud 版本不同,有时会失去兼容性。比如随着 Spring Cloud 的版本升级,内部使用的 Eureka 版本升级时协议发生了变化,如前所述,Spring Cloud Stream 是 2.x 中 了API 终端。
如果是这样,最好继续更新Java 、 Spring Boot 和 Spring Cloud 到最新版本。但是,Spring Cloud 往往是有与版本升级相关的大型工作。
这是因为Cloud Native这个领域对应的产品和趋势都发生了变化,Spring Cloud试图跟风的时候,不得不失去兼容性。
另外,作为一个稍微小一点的问题,如果由于Spring Boot的提供速度和Spring Cloud的提供速度不同,以及依赖的复杂度等原因,尝试升级Spring Boot的版本,Spring Cloud还不支持有时候引用库的版本不一样,会报错。
通过体验这方面的痛苦,而不是 Spring Cloud 不好,“与提供 Web API的应用程序和支持它的基础设施接壤的层更加松散耦合,并且每个版本都是独立的。最好能够上传吧。”
这就是它被用于 Dapr 而不是 Spring Cloud 的原因。
总结
- 在服务调用方面,Dapr 和 Spring Cloud 变化不大。
- 在消息传递方面,Dapr 更简单,并具有更好的性能。
- 对于分布式跟踪,Spring Cloud 比 Dapr 更复杂、更易于使用。
Spring Cloud 版本升级让你吃不少苦头,有点难以理解Spring Cloud文档在哪里以及是什么,随着具有各种功能的历史产品变得更加复杂,文档可能会变得更加复杂。当然,Spring 的好处是有许多指南、博客、Demo材料等作为该领域的补充。
Spring Cloud 这样的微服务框架,把微服务架构上的很多东西也带到了代码开发上来。虽然 Spring Cloud 做了封装和简化,但开发的时候你还是会分心去处理它,不能完全只关注业务。 Dapr 是微服务的发展方向,它简化微服务的开发,把微服务架构方面的东西都剥离出来成为基础设施,开发只需关注具体的业务实现。
所以mecha架构的 Dapr完全可以取代Spring Cloud。 而且具备更多优势:
- 更加云原生,和kubernetes结合更好。
- 业务代码无需集成sdk,这样决定了sdk升级会更加方便,降低了耦合。
Dapr通过把一些构建微服务应用所需的最佳实践内置到开放、独立的Building Block中,Dapr还在Actor运行时中提供了许多功能,包括并发控制,状态管理,生命周期管理如Actor的激活/停用以及用于唤醒Actor的Timer(计时器)和Reminder(提醒)。Dapr让开发人员更加专注于业务逻辑代码的编写,即可开发出功能强大的微服务应用。
更为重要的是,Dapr还抽象了运行环境,避免微服务应用和运行环境强绑定(这也是很多团队“假上云”——仅使用VM的原因之一)。并且支撑Dapr的运行环境不仅仅限于Cloud,还有广阔的Edge。
参考文章:
- https://xie.infoq.cn/article/aa422c431873fc7b1de3591e1
- https://xie.infoq.cn/article/047e1ece428ef11350f7a129c
- https://www.infoq.cn/article/ex3rr5jCByXOCX7mwJbu
- https://xie.infoq.cn/article/7308df260ebbf5995a1401115
- https://github.com/dapr/dapr/blob/master/docs/decision_records/sdk/SDK-002-java-jdk-versions.md
- 通过K8S自带技能卸下SpringCloud依赖