Dapr 能否取代 Spring Cloud?
很多人都是使用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 調用目標應用程式。//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向消息代理髮送消息。//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: //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=//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。
參考文章: