Dubbo 版本 :
Dubbo 社區目前主力維護的有 2.6.x 和 2.7.x 兩大版本,其中,
- 2.6.x 主要以 bugfix 和少量 enhancements 爲主,因此能完全保證穩定性
- 2.7.x 作爲社區的主要開發版本,得到持續更新並增加了大量新 feature 和優化,同時也帶來了一些穩定性挑戰
版本更多信息請參考官網。相信小夥伴們對於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 常用功能簡介:
多協議支持:
官網協議參考手冊 :http://dubbo.apache.org/zh-cn/docs/user/references/protocol/introduction.html
- 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:
最短響應時間負載均衡算法,篩選成功調用響應時間最短的調用程序的數量,並計算這些調用程序的權重和數量。然後根據響應時間的長短來分配目標服務的路由權重。
集羣容錯:
官方文檔 :http://dubbo.apache.org/zh-cn/docs/user/demos/fault-tolerent-strategy.html
在集羣調用失敗時,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/user/references/xml/dubbo-provider.html
- consumer:http://dubbo.apache.org/zh-cn/docs/user/references/xml/dubbo-consumer.html
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
然後我們打開它:
我們會發現服務信息已經被緩存下來了。這個時候,把註冊中心關了,再去訪問接口 ,發現也是沒問題的。
更多特性請參考官網。