Dubbo 版本 :
Dubbo 社區目前主力維護的有 2.6.x 和 2.7.x 兩大版本,其中,
- 2.6.x 主要以 bugfix 和少量 enhancements 為主,因此能完全保證穩定性
- 2.7.x 作為社區的主要開發版本,得到持續更新並增加了大量新 feature 和優化,同時也帶來了一些穩定性挑戰
Apache dubbo 官網已經出現3.0版本。目前官網展示版本為 2.7 與3.0.如若下列官網地址打不開,可能是項目更新了,進入 http://dubbo.apache.org/ 自行查閱
版本更多信息請參考官網。相信小伙伴們對於Dubbo 都有一定的了解。相關基礎知識點可以參考 https://www.cnblogs.com/wuzhenzhao/p/10008824.html .
Dubbo 再聚首之自動化配置:
dubbo-spring-boot-starter(org.apache.dubbo:2.7.7):
基於目前的 Spring Boot 自動化配置的盛行,我們在使用 Dubbo的時候不再像以前集成 spring 的時候那樣的繁瑣,需要進行很多的配置。接下來來體驗一下 dubbo-spring-boot-starter 帶來的便捷。
本文注冊中心采用 Spring Cloud Alibaba Nacos ,不熟悉的小伙伴可以參考 https://www.cnblogs.com/wuzhenzhao/category/1530796.html
項目目錄:
springboot-dubbo-api 模塊:
1.構建服務接口,api模塊,導入 Rest 協議支持依賴:
<dependencies> <!--添加REST支持--> <!--Rest協議--> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jaxrs</artifactId> <version>3.8.1.Final</version> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-client</artifactId> <version>4.0.0.Final</version> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-server</artifactId> <version>9.4.12.RC2</version> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-servlet</artifactId> <version>9.4.12.RC2</version> </dependency> </dependencies>
2. 創建接口 :
@Path("/") public interface HelloService { @GET @Path("/sayRest") String sayHello() throws Exception; }
springboot-dubbo-provider 模塊:
1.導入依賴:
<dependencies> <!--基於spring-boot-dependencies 2.3.0RELEASE 版本 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- dubbo 依賴--> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>2.7.7</version> </dependency> <!--nacos注冊中心依賴--> <dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> <version>1.2.1</version> </dependency> <!--zk注冊中心依賴--> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>4.0.1</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>4.0.1</version> </dependency> <dependency> <artifactId>springboot-dubbo-api</artifactId> <groupId>com.wuzz.demo</groupId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
3. 創建服務實現類 :
@DubboService(loadbalance = "random", // 負載均衡 timeout = 50000, //超時 cluster = "failsafe", // 服務容錯 protocol = {"dubbo", "rest"}, //多協議支持 registry = {"hangzhou", "wenzhou"} //多注冊中心 ) public class HelloServiceImpl implements HelloService { @Override public String sayHello() throws Exception { return "Hello Dubbo"; } }
4. 配置文件配置
spring.application.name=springboot-dubbo # Netty -> dubbo.protocols.dubbo.name=dubbo dubbo.protocols.dubbo.port=-1 # jetty (配置了rest協議) dubbo.protocols.rest.name=rest dubbo.protocols.rest.port=-1 dubbo.protocols.rest.server=jetty # zk注冊中心 dubbo.registries.hangzhou.address=zookeeper://192.168.1.101:2181 dubbo.registries.hangzhou.timeout=10000 dubbo.registries.hangzhou.default=true ## 服務啟動的時候,如果注冊中心有問題,那么服務就啟動失敗 dubbo.registries.hangzhou.check=false # nacos 注冊中心 dubbo.registries.wenzhou.address=nacos://localhost:8848
5. 服務啟動類,配置掃描路徑
@DubboComponentScan(basePackages = "com.wuzz.demo") //dubbo服務掃描 @SpringBootApplication public class SpringBootDubboProviderApp { private final static Logger log = LoggerFactory.getLogger(SpringBootDubboProviderApp.class); public static void main(String[] args) { SpringApplication.run(SpringBootDubboProviderApp.class, args); log.info("服務啟動成功"); } }
springboot-dubbo-client 模塊:
1.導入相關依賴:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>2.7.7</version> </dependency> <dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> <version>1.2.1</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>4.0.1</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>4.0.1</version> </dependency> <dependency> <artifactId>springboot-dubbo-api</artifactId> <groupId>com.wuzz.demo</groupId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
2. 創建測試類 :
@RestController public class DubboController { //Dubbo提供的注解 @DubboReference(loadbalance = "roundrobin", timeout = 9000, cluster = "failfast", mock = "com.wuzz.demo.mock.HelloServiceMock", check = false) HelloService helloService; @GetMapping("/sayhello") public String sayHello() throws Exception { return helloService.sayHello(); //我調用這個服務可能失敗,如果失敗了,我要怎么處理 } // dubbo 泛化調用 @DubboReference(interfaceName = "com.wuzz.demo.api.HelloService",generic = true,check = false) GenericService genericService; @GetMapping("/demo") public String demo(){ return genericService.$invoke("sayHello",new String[0],null).toString(); } }
mock 實現類:
public class HelloServiceMock implements HelloService { @Override public String sayHello() { return "服務端發生異常, 被降解了。返回兜底數據。。。"; } }
3.配置文件,啟動類無需任何配置
spring.application.name=springboot-dubbo-client dubbo.registry.address=nacos://localhost:8848 server.port=8889
然后先后啟動 服務提供者、服務消費者模塊。可以看到 注冊中心應當有兩個服務的相關注冊信息:
然后就可以訪問對應的接口進行測試。
spring-cloud-starter-dubbo(org.apache.dubbo:2.7.6):
與springboot 集成不同,spring-cloud-alibaba 自成生態,在多注冊中心的用法上有兼容問題。
項目目錄
spring-cloud-alibaba-dubbo-api 模塊:
1.添加相關接口
public interface HelloService { String sayHello() throws Exception; }
spring-cloud-alibaba-dubbo-provider 模塊:
1.添加相關依賴:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!--dubbo 依賴--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-dubbo</artifactId> <version>2.2.1.RELEASE</version> </dependency> <!--nacos 注冊中心依賴--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2.2.1.RELEASE</version> </dependency> <dependency> <groupId>com.wuzz.demo</groupId> <artifactId>spring-cloud-alibaba-dubbo-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
2.實現類:
@Service(loadbalance = "random",timeout = 50000,cluster = "failsafe") public class HelloServiceImpl implements HelloService { @Override public String sayHello() throws Exception { return "Hello Dubbo"; } }
3. 配置文件配置:
spring.application.name=springboot-dubbo dubbo.scan.base-packages=com.wuzz.demo dubbo.protocol.port=20882 dubbo.protocol.name=dubbo spring.cloud.nacos.discovery.server-addr=localhost:8848
4.啟動類:
@EnableDiscoveryClient @SpringBootApplication public class SpringCloudAlibabaDubboProviderApp { private final static Logger log = LoggerFactory.getLogger(SpringCloudAlibabaDubboProviderApp.class); public static void main(String[] args) { SpringApplication.run(SpringCloudAlibabaDubboProviderApp.class, args); log.info("服務啟動成功"); } }
spring-cloud-alibaba-dubbo-client 模塊:
1.導入依賴:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-dubbo</artifactId> <version>2.2.1.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2.2.1.RELEASE</version> </dependency> <dependency> <groupId>com.wuzz.demo</groupId> <artifactId>spring-cloud-alibaba-dubbo-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
3.測試類編寫:
@RestController public class DubboController { //Dubbo提供的注解 @Reference(loadbalance = "roundrobin", timeout = 1, cluster = "failfast", mock = "com.wuzz.demo.mock.HelloServiceMock", check = false) HelloService helloService; @GetMapping("/sayhello") public String sayHello() throws Exception { return helloService.sayHello(); //我調用這個服務可能失敗,如果失敗了,我要怎么處理 } }
mock 實現類:
public class HelloServiceMock implements HelloService { @Override public String sayHello() { return "服務端發生異常, 被降解了。返回兜底數據。。。"; } }
4.配置文件:
spring.application.name=springboot-dubbo-client server.port=8889 spring.cloud.nacos.discovery.server-addr=localhost:8848
5.啟動類:
@EnableDiscoveryClient @SpringBootApplication public class SpringCloudAlibabaDubboClientApp { private final static Logger log = LoggerFactory.getLogger(SpringCloudAlibabaDubboClientApp.class); public static void main(String[] args) { SpringApplication.run(SpringCloudAlibabaDubboClientApp.class, args); log.info("服務啟動成功"); } }
先后啟動服務提供者、服務消費者進行測試即可。
Dubbo 常用功能簡介:
多協議支持:
- dubbo:// :Dubbo 缺省協議采用單一長連接和 NIO 異步通訊,適合於小數據量大並發的服務調用,以及服務消費者機器數遠大於服務提供者機器數的情況。反之,Dubbo 缺省協議不適合傳送大數據量的服務,比如傳文件,傳視頻等,除非請求量很低。
- rmi:// : RMI 協議采用 JDK 標准的
java.rmi.*
實現,采用阻塞式短連接和 JDK 標准序列化方式。 - hessian:// : Hessian 協議用於集成 Hessian 的服務,Hessian 底層采用 Http 通訊,采用 Servlet 暴露服務,Dubbo 缺省內嵌 Jetty 作為服務器實現。
- http:// :基於 HTTP 表單的遠程調用協議,采用 Spring 的 HttpInvoker 實現
- webservice:// :基於 WebService 的遠程調用協議,基於 Apache CXF 的
frontend-simple
和transports-http
實現 。 - thrift:// :當前 dubbo 支持 的 thrift 協議是對 thrift 原生協議 的擴展,在原生協議的基礎上添加了一些額外的頭信息,比如 service name,magic number 等。
- memcached:// :基於 memcached 實現的 RPC 協議 。
- redis:// :基於 Redis 實現的 RPC 協議 。
- rest:// :基於標准的Java REST API——JAX-RS 2.0(Java API for RESTful Web Services的簡寫)實現的REST調用支持
- grpc:// :Dubbo 自 2.7.5 版本開始支持 gRPC 協議,對於計划使用 HTTP/2 通信,或者想利用 gRPC 帶來的 Stream、反壓、Reactive 編程等能力的開發者來說, 都可以考慮啟用 gRPC 協議。
Dubbo 的負載均衡:
在集群負載均衡時,Dubbo 提供了多種均衡策略,缺省為 random
隨機調用。
1.Random LoadBalance:
- 隨機,按權重設置隨機概率。
- 在一個截面上碰撞的概率高,但調用量越大分布越均勻,而且按概率使用權重后也比較均勻,有利於動態調整提供者權重。
2.RoundRobin LoadBalance:
- 輪詢,按公約后的權重設置輪詢比率。
- 存在慢的提供者累積請求的問題,比如:第二台機器很慢,但沒掛,當請求調到第二台時就卡在那,久而久之,所有請求都卡在調到第二台上。
3.LeastActive LoadBalance:
- 最少活躍調用數,相同活躍數的隨機,活躍數指調用前后計數差。
- 使慢的提供者收到更少請求,因為越慢的提供者的調用前后計數差會越大。
4.ConsistentHash LoadBalance:
- 一致性 Hash,相同參數的請求總是發到同一提供者。
- 當某一台提供者掛時,原本發往該提供者的請求,基於虛擬節點,平攤到其它提供者,不會引起劇烈變動。
- 算法參見:http://en.wikipedia.org/wiki/Consistent_hashing
- 缺省只對第一個參數 Hash,如果要修改,請配置
<dubbo:parameter key="hash.arguments" value="0,1" />
- 缺省用 160 份虛擬節點,如果要修改,請配置
<dubbo:parameter key="hash.nodes" value="320" />
5.ShortestResponse LoadBalance:
最短響應時間負載均衡算法,篩選成功調用響應時間最短的調用程序的數量,並計算這些調用程序的權重和數量。然后根據響應時間的長短來分配目標服務的路由權重。
集群容錯:
在集群調用失敗時,Dubbo 提供了多種容錯方案,缺省為 failover 重試。
-
Failover Cluster :失敗自動切換,當出現失敗,重試其它服務器 。通常用於讀操作,但重試會帶來更長延遲。可通過
retries="2"
來設置重試次數(不含第一次)。 -
Failfast Cluster :快速失敗,只發起一次調用,失敗立即報錯。通常用於非冪等性的寫操作,比如新增記錄。
-
Failsafe Cluster :失敗安全,出現異常時,直接忽略。通常用於寫入審計日志等操作。
-
Failback Cluster :失敗自動恢復,后台記錄失敗請求,定時重發。通常用於消息通知操作。
-
Forking Cluster :並行調用多個服務器,只要一個成功即返回。通常用於實時性要求較高的讀操作,但需要浪費更多服務資源。可通過
forks="2"
來設置最大並行數。 -
Broadcast Cluster :廣播調用所有提供者,逐個調用,任意一台報錯則報錯 。通常用於通知所有提供者更新緩存或日志等本地資源信息。
服務降級:
dubbo的降級方式: Mock。上文代碼中已給出示例實現步驟:
- 在client端創建一個 HelloServiceMock 類,實現對應的接口(需要對哪個接口進行mock,就實現哪個),名稱必須以Mock結尾
- 在client端的服務調用的注解配置中,添加 mock 配置,增加一個mock屬性指向創建的HelloServiceMock
- 模擬錯誤(設置timeout),模擬超時異常,運行測試代碼即可訪問到HelloServiceMock 這個類。當服務端故障解除以后,調用過程將恢復正常,
Dubbo泛化:
泛化接口調用方式主要用於客戶端沒有 API 接口及模型類元的情況,參數及返回值中的所有 POJO 均用 Map
表示,通常用於框架集成,比如:實現一個通用的服務測試框架,可通過 GenericService
調用所有服務實現。 上文已給出示例。
更多的泛化配置可以參考官網 :http://dubbo.apache.org/zh-cn/docs/user/demos/generic-reference.html。
主機綁定:
關於主機綁定的源碼實現位於 org.apache.dubbo.config.ServiceConfig#doExportUrlsFor1Protocol
String host = findConfigedHosts(protocolConfig, registryURLs, map); Integer port = findConfigedPorts(protocolConfig, name, map);
主機綁定的步驟主要有以下幾個步驟:
- 查找環境變量中是否存在啟動參數 [DUBBO_IP_TO_BIND] =服務注冊的ip
- 讀取配置文件, dubbo.protocols.dubbo.host= 服務注冊的ip
- InetAddress.getLocalHost().getHostAddress() 獲得本機ip地址
- 通過Socket去連接注冊中心,從而獲取本機IP
- 會輪詢本機的網卡,直到找到合適的IP地址
- 上面獲取到的ip地址是bindip,如果需要作為服務注冊中心的ip, DUBBO_IP_TO_REGISTRY -dDUBBO_IP_TO_REGISTRY=ip
配置優先級:
- 方法層面的配置要優先於接口層面的配置, 接口層面的配置要優先於全局配置.
- 如果級別一樣,以客戶端的配置優先,服務端次之.
性能調優的參數:
dubbo 提供了針對服務端/客戶端的相關參數調優,以下列舉了一些比較重要的參數。
@Configuration public class DubboConfig { //服務端相關調優參數 @Bean public ProviderConfig providerConfig() { ProviderConfig config = new ProviderConfig(); //默認200 服務線程池大小(固定大小) config.setThreads(200); //默認CPU + 1 //IO線程池,接收網絡讀寫中斷,以及序列化和反序列化, // 不處理業務,業務線程池參見threads配置,此線程池和CPU相關,不建議配置。 config.setIothreads(Runtime.getRuntime().availableProcessors() + 1); //線程池類型,可選:fixed/cached/limit(2.5.3以上)/eager(2.6.x以上) config.setThreadpool("fixed"); //對每個提供者的最大連接數,rmi、http、hessian //等短連接協議表示限制連接數,dubbo等長連接協表示建立的長連接個數 config.setConnections(0); //線程池隊列大小,當線程池滿時,排隊等待執行的隊列大小, //建議不要設置,當線程池滿時應立即失敗,重試其它服務提供機器, //而不是排隊,除非有特殊需求。 config.setQueues(0); //每服務消費者每服務每方法最大並發調用數 config.setAccepts(0); //服務提供者每服務每方法最大可並行執行請求數 config.setExecutes(0); return config; } //客戶端相關調優參數 @Bean public ConsumerConfig consumerConfig() { ConsumerConfig config = new ConsumerConfig(); //每個服務對每個提供者的最大連接數, //rmi、http、hessian等短連接協議支持此配置,dubbo協議長連接不支持此配置 config.setConnections(100); //每服務消費者每服務每方法最大並發調用數 config.setActives(0); return config; } }
參數調優可以參考以下dubbo的處理流程
更多參數請參考官網:
- provider:http://dubbo.apache.org/zh-cn/docs/2.7/user/references/xml/dubbo-provider/
- consumer:http://dubbo.apache.org/zh-cn/docs/2.7/user/references/xml/dubbo-consumer/
Dubbo緩存文件:
配置服務地址的緩存,避免注冊中心掛了之后對於服務通信的影響,客戶端做以下配置 :
spring.application.name=springboot-dubbo-client server.port=8889 # nacos 注冊中心 dubbo.registries.wenzhou.address=nacos://localhost:8848 # 配置服務地址的緩存,避免注冊中心掛了之后對於服務通信的影響 dubbo.registries.wenzhou.file=${user.home}/dubbo.cache
然后啟動服務提供者/服務消費者,可以到用戶目錄下看到一個文件,dubbo.cache
然后我們打開它:
我們會發現服務信息已經被緩存下來了。這個時候,把注冊中心關了,再去訪問接口 ,發現也是沒問題的。
更多特性請參考官網。