一、需求背景: 二、Dubbo和Spring Cloud 的比較 首先Dubbo是一個分散式服務框架,以及SOA治理方案。它的功能主要包括:高性能NIO通訊及多協議集成,服務動態定址與路由,軟負載均衡與容錯,依賴分析與降級等,它是著名的阿裡服務治理的核心框架。Spring Cloud更加關心為開發人 ...
一、需求背景:
- 公司內部老項目微服務技術棧使用Dubbo, 新項目技術棧使用主流的Spring Cloud相關組件開發,新舊項目涉及交互調用,無法直接通信數據傳遞。
- 老項目基於Dubbo,重構代碼升級使用Spring Cloud,改造升級要求成本最低,不影響現有系統運行。
二、Dubbo和Spring Cloud 的比較
首先Dubbo是一個分散式服務框架,以及SOA治理方案。它的功能主要包括:高性能NIO通訊及多協議集成,服務動態定址與路由,軟負載均衡與容錯,依賴分析與降級等,它是著名的阿裡服務治理的核心框架。Spring Cloud更加關心為開發人員提供開箱即用的一系列常見的分散式工具(例如配置管理,服務發現,斷路器,智能路由,微代理,控制匯流排),它是基於輕量級框架Spring家族,維護版本速度相對較快。
想深入瞭解的朋友,可以參考這篇文章專門分析了兩者的區別:聽聽八年阿裡架構師怎樣講述Dubbo和Spring Cloud微服務架構
改造思路:Spring Cloud和Dubbo用於協調服務組件需要進行統一,使得Dubbo服務和Spring Cloud 服務能夠相互感知。其次統一微服務之間的通信協議,Spring Cloud使用Http協議,Dubbo支持Dubbo,Hessian,rmi,http,webservice,thrift,redis,rest,memcached協議;數據傳輸載體或者說格式在網路中也是統一的,和調用的服務架構無關。改造前需要明確的是兩種架構Spring Cloud和Dubbo在項目中的主次,不然容易造成開發人員使用API困惑,代碼介面混亂,其次不要急於期望短期將架構統一,改造完整,特別是Spring Cloud帶來了項目開發更多的環節,更多的組件(意味著有更多的坑)。
改造方案:
- 傳統方案:保留完整的Dubbo老系統,Dubbo服務不需要向SpringCloud組件註冊服務,通過Ribbon/Fegin調用Dubbo服務暴露的Restful Api.缺點也明顯,需要人工維護Dubbo服務和Spring Cloud服務的關係,當Dubbo服務較多時,不同的環境配置太多。
- 傳統方案:藉助SideCar支持多語言的特性,連接Dubbo和Spring Cloud底層使用Sidecar交互,同時Dubbo也可以將信息傳播到Eureka上面。缺點明顯,需要每個Dubbo服務節點額外配置Sidecar服務節點,同時增加了鏈路的長度。
我的方案:Spring Cloud和Dubbo的服務中心選擇阿裡的Nacos,它是一個動態服務發現、配置管理和服務管理平臺,為什麼不選擇使用Zookeeper,因為zookeeper是個CP系統,強一致性。如果其中master掛掉,此時zookeeper集群會進行重新選舉,不能提供服務列表信息的服務,其次zookeeper通過tcp不能準確判斷服務此時是否可用,資料庫掛了,資料庫連接池滿了等也能提供TCP信息。通信協議我選擇Http協議,Dubbo2.6之後支持http協議,數據格式使用Json,前端無需根據服務區分數據格式解析。
Nacos支持部署的模式有單機,集群,多集群模式,Nacos 0.8之後支持資料庫持久化,可以方便看見服務信息的前後變化。單機模式很簡單啟動,下載最新版Nacos:https://github.com/alibaba/nacos/releases ,解壓直接運行bin/startup.sh或者startup.cmd即可。
Nacos不僅提供服務發現和服務健康監測,它也提供控制台可視化頁面進行動態配置服務,動態 DNS 服務,服務及其元數據管理等功能,Nacos0.8版本支持簡單登錄功能,預設用戶名/密碼為 nacos/nacos。:
相比Eureka提供的單一的看板頁面,提供的管理功能可以說沒得比,具體使用手冊參考官方:https://nacos.io/zh-cn/docs/console-guide.html,這裡不再贅述。
首先開發基於Spring Cloud+Nacos架構的微服務,nacos-discovery-provider為服務提供者,nacos-discovery-consumer為服務消費方,
nacos-discovery-provider的pom.xml加入相關依賴:
1 <dependencies> 2 <dependency> 3 <groupId>org.springframework.cloud</groupId> 4 <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> 5 <version>0.2.1.RELEASE</version> 6 </dependency> 7 <dependency> 8 <groupId>org.springframework.boot</groupId> 9 <artifactId>spring-boot-starter-web</artifactId> 10 <version>2.0.6.RELEASE</version> 11 </dependency> 12 <dependency> 13 <groupId>org.springframework.boot</groupId> 14 <artifactId>spring-boot-starter-actuator</artifactId> 15 <version>2.0.6.RELEASE</version> 16 </dependency> 17 </dependencies>
application.properties的配置為:
1 server.port=18082 2 spring.application.name=service-provider 3 spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 4 management.endpoints.web.exposure.include=*
其中spring.cloud.nacos.discovery.server-addr為配置的Nacos地址,management.endpoints.web.exposure.include=*表示對外暴露所有項目信息。
啟動類的代碼:
1 package org.springframework.cloud.alibaba.cloud.examples; 2 3 import org.springframework.boot.SpringApplication; 4 import org.springframework.boot.autoconfigure.SpringBootApplication; 5 import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 import org.springframework.web.bind.annotation.PathVariable; 7 import org.springframework.web.bind.annotation.RequestMapping; 8 import org.springframework.web.bind.annotation.RequestMethod; 9 import org.springframework.web.bind.annotation.RequestParam; 10 import org.springframework.web.bind.annotation.RestController; 11 12 @SpringBootApplication 13 @EnableDiscoveryClient 14 public class ProviderApplication { 15 16 public static void main(String[] args) { 17 SpringApplication.run(ProviderApplication.class, args); 18 } 19 20 @RestController 21 class EchoController { 22 @RequestMapping(value = "/echo/{string}", method = RequestMethod.GET) 23 public String echo(@PathVariable String string) { 24 return "hello Nacos Discovery " + string; 25 } 26 27 @RequestMapping(value = "/divide", method = RequestMethod.GET) 28 public String divide(@RequestParam Integer a, @RequestParam Integer b) { 29 return String.valueOf(a / b); 30 } 31 } 32 }
使用SpringCloud的原生註解@EnableDiscoveryClient 開啟服務註冊發現功能。
接下來創建服務消費方nacos-discovery-consumer進行服務消費:
pom.xml:
1 <dependencies> 2 <dependency> 3 <groupId>org.springframework.boot</groupId> 4 <artifactId>spring-boot-starter-web</artifactId> 5 <version>2.0.6.RELEASE</version> 6 </dependency> 7 <dependency> 8 <groupId>org.springframework.cloud</groupId> 9 <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> 10 <version>0.2.1.RELEASE</version> 11 </dependency> 12 <dependency> 13 <groupId>org.springframework.boot</groupId> 14 <artifactId>spring-boot-starter-actuator</artifactId> 15 <version>2.0.6.RELEASE</version> 16 </dependency> 17 <dependency> 18 <groupId>org.springframework.cloud</groupId> 19 <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> 20 <version>2.0.2.RELEASE</version> 21 </dependency> 22 <dependency> 23 <groupId>org.springframework.cloud</groupId> 24 <artifactId>spring-cloud-starter-openfeign</artifactId> 25 <version>2.0.2.RELEASE</version> 26 </dependency> 27 <dependency> 28 <groupId>org.springframework.cloud</groupId> 29 <artifactId>spring-cloud-alibaba-sentinel</artifactId> 30 <version>0.2.1.RELEASE</version> 31 </dependency> 32 </dependencies>
其中sentinel和傳統的Spring Cloud組件Hystrix類似,提供熔斷降級,系統負載保護等功能。application.properties的配置為:
1 spring.application.name=service-consumer 2 server.port=18083 3 management.endpoints.web.exposure.include=* 4 spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 5 6 feign.sentinel.enabled=true 7 8 spring.cloud.sentinel.transport.dashboard=localhost:8080 9 spring.cloud.sentinel.eager=true 10 11 spring.cloud.sentinel.datasource.ds1.file.file=classpath: flowrule.json 12 spring.cloud.sentinel.datasource.ds1.file.data-type=json 13 spring.cloud.sentinel.datasource.ds1.file.rule-type=flow
其中flowrule.json配置了限流降級的規則:
1 [ 2 { 3 "resource": "GET:http://service-provider/echo/{str}", 4 "controlBehavior": 0, 5 "count": 1, 6 "grade": 1, 7 "limitApp": "default", 8 "strategy": 0 9 } 10 ]
消費啟動類ConsumerApplication.java:
1 package org.springframework.cloud.alibaba.cloud.examples; 2 3 import org.springframework.boot.SpringApplication; 4 import org.springframework.boot.autoconfigure.SpringBootApplication; 5 import org.springframework.cloud.alibaba.cloud.examples.ConsumerApplication.EchoService; 6 import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 7 import org.springframework.cloud.client.loadbalancer.LoadBalanced; 8 import org.springframework.cloud.openfeign.EnableFeignClients; 9 import org.springframework.cloud.openfeign.FeignClient; 10 import org.springframework.context.annotation.Bean; 11 import org.springframework.web.bind.annotation.PathVariable; 12 import org.springframework.web.bind.annotation.RequestMapping; 13 import org.springframework.web.bind.annotation.RequestMethod; 14 import org.springframework.web.bind.annotation.RequestParam; 15 import org.springframework.web.client.RestTemplate; 16 17 /** 18 * @author liujie037 19 */ 20 @SpringBootApplication 21 @EnableDiscoveryClient 22 @EnableFeignClients 23 public class ConsumerApplication { 24 25 @LoadBalanced 26 @Bean 27 public RestTemplate restTemplate() { 28 return new RestTemplate(); 29 } 30 31 public static void main(String[] args) { 32 SpringApplication.run(ConsumerApplication.class, args); 33 } 34 35 @FeignClient(name = "service-provider", fallback = EchoServiceFallback.class, configuration = FeignConfiguration.class) 36 public interface EchoService { 37 @RequestMapping(value = "/echo/{str}", method = RequestMethod.GET) 38 String echo(@PathVariable("str") String str); 39 40 @RequestMapping(value = "/divide", method = RequestMethod.GET) 41 String divide(@RequestParam("a") Integer a, @RequestParam("b") Integer b); 42 43 @RequestMapping(value = "/notFound", method = RequestMethod.GET) 44 String notFound(); 45 } 46 47 } 48 49 class FeignConfiguration { 50 @Bean 51 public EchoServiceFallback echoServiceFallback() { 52 return new EchoServiceFallback(); 53 } 54 } 55 56 class EchoServiceFallback implements EchoService { 57 @Override 58 public String echo(@PathVariable("str") String str) { 59 return "echo fallback"; 60 } 61 62 @Override 63 public String divide(@RequestParam Integer a, @RequestParam Integer b) { 64 return "divide fallback"; 65 } 66 67 @Override 68 public String notFound() { 69 return "notFound fallback"; 70 } 71 }
通過 Spring Cloud 原生註解 @EnableDiscoveryClient 開啟服務註冊發現功能。給 RestTemplate 實例添加 @LoadBalanced 註解,開啟 @LoadBalanced 與 Ribbon 的集成:
消費的介面TestController.java:
1 package org.springframework.cloud.alibaba.cloud.examples; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.cloud.alibaba.cloud.examples.ConsumerApplication.EchoService; 5 import org.springframework.cloud.client.discovery.DiscoveryClient; 6 import org.springframework.web.bind.annotation.PathVariable; 7 import org.springframework.web.bind.annotation.RequestMapping; 8 import org.springframework.web.bind.annotation.RequestMethod; 9 import org.springframework.web.bind.annotation.RequestParam; 10 import org.springframework.web.bind.annotation.RestController; 11 import org.springframework.web.client.RestTemplate; 12 13 /** 14 * @author liujie037 15 */ 16 @RestController 17 public class TestController { 18 19 @Autowired 20 private RestTemplate restTemplate; 21 @Autowired 22 private EchoService echoService; 23 24 @Autowired 25 private DiscoveryClient discoveryClient; 26 27 @RequestMapping(value = "/echo-rest/{str}", method = RequestMethod.GET) 28 public String rest(@PathVariable String str) { 29 return restTemplate.getForObject("http://service-provider/echo/" + str, 30 String.class); 31 } 32 33 @RequestMapping(value = "/notFound-feign", method = RequestMethod.GET) 34 public String notFound() { 35 return echoService.notFound(); 36 } 37 38 @RequestMapping(value = "/divide-feign", method = RequestMethod.GET) 39 public String divide(@RequestParam Integer a, @RequestParam Integer b) { 40 return echoService.divide(a, b); 41 } 42 43 @RequestMapping(value = "/echo-feign/{str}", method = RequestMethod.GET) 44 public String feign(@PathVariable String str) { 45 return echoService.echo(str); 46 } 47 48 @RequestMapping(value = "/services/{service}", method = RequestMethod.GET) 49 public Object client(@PathVariable String service) { 50 return discoveryClient.getInstances(service); 51 } 52 53 @RequestMapping(value = "/services", method = RequestMethod.GET) 54 public Object services() { 55 return discoveryClient.getServices(); 56 } 57 }
訪問Nacos控制台:
測試服務消費正常使用postman:
下一篇中我將繼續展示Dubbo服務創建和Spring Cloud 互相調用。