Dubbo進階
- 2020 年 3 月 10 日
- 筆記
註冊中心zookeeper
什麼是註冊中心:
註冊中心就是用來存儲服務資訊的地方,就像房屋中介一樣;
為什麼需要註冊中心:
在前面的例子中我們使用了客戶端與伺服器直連的方式完成了服務的調用,在實際開發中這回帶來一些問題,例如伺服器地址變更了,或服務搭建了集群,客戶端不知道服務的地址,此時註冊中心就派上用場了,服務提供方發布服務後將服務資訊放在zookeeper中,然後消費方從zookeeper獲取伺服器資訊,進行調用,這樣就使提供方和消費方解開了耦合,也讓服務提供方可以更方便的搭建集群;
使用:
1.啟動zookeeper,單機和集群都可以
2.在雙方配置文件中指定註冊中心的資訊(內容相同)
<!--註冊中心 N/A 表示不使用註冊中心 直連客戶端 地址可以是一個或多個 多個表示集群--> <dubbo:registry protocol="zookeeper" address="10.211.55.6:2181,10.211.55.7:2181"/>
需要說明的是,註冊中心不是必須使用zookeeper,dubbo還支援其他三種:Simple,Redis,Multicast,因其優秀的可用性,官方推薦使用zookeeper;
Dubbo的其他配置方式
API配置
簡單的說就是不使用配置文件而是使用使用程式碼來完成配置,該方式主要用於測試環境或集成其他框架,不推薦用於生產環境;
服務提供者:
public class ProviderApplication { public static void main(String[] args) throws IOException { // xmlConfig(); apiConfig(); System.in.read();//阻塞主執行緒保持運行 } private static void apiConfig() { //應用配置 ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName("my-service"); applicationConfig.setQosEnable(true); //註冊中心 RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setProtocol("zookeeper"); registryConfig.setAddress("10.211.55.6:2181,10.211.55.7:2181"); //rpc協議 ProtocolConfig protocolConfig = new ProtocolConfig(); protocolConfig.setName("dubbo"); protocolConfig.setPort(20880); //發布服務 ServiceConfig<HelloService> serviceConfig = new ServiceConfig<HelloService>(); serviceConfig.setApplication(applicationConfig); serviceConfig.setProtocol(protocolConfig); serviceConfig.setRegistry(registryConfig); serviceConfig.setInterface(HelloService.class); serviceConfig.setRef(new HelloServiceImpl()); serviceConfig.export(); }
消費者:
public class ConsumerApplication { public static void main(String[] args) { // xmlConfig(); apiConfig(); } private static void apiConfig() { //應用配置 ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName("my-consumer"); applicationConfig.setQosEnable(false); //註冊中心 RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setProtocol("zookeeper"); registryConfig.setAddress("10.211.55.6:2181,10.211.55.7:2181"); //調用服務 ReferenceConfig<HelloService> serviceReferenceConfig = new ReferenceConfig<HelloService>(); serviceReferenceConfig.setApplication(applicationConfig); serviceReferenceConfig.setRegistry(registryConfig); serviceReferenceConfig.setInterface(HelloService.class); HelloService service = serviceReferenceConfig.get(); String tom = service.sayHello("tom"); System.out.println(tom); }
註解配置
註解配置是使用較多的一種方式,可加快開發速度,讓我們從繁瑣的配置文件中解脫出來;
Dubbo使用了Spring容器來管理bean,所以配置方式也大同小異,可使用Configuration將一個類作為配置類;在該類中提供必要的幾個bean
服務提供者
配置類:
@Configuration @EnableDubbo(scanBasePackages = "com.yyh.service") public class ProviderConfiguration { //無論如何配置我們最終需要的還是那幾個bean @Bean public ApplicationConfig applicationConfig(){ //應用配置 ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName("my-service"); applicationConfig.setQosEnable(true); return applicationConfig; } @Bean public RegistryConfig registryConfig(){ RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setProtocol("zookeeper"); registryConfig.setAddress("10.211.55.6:2181,10.211.55.7:2181"); return registryConfig; } @Bean public ProtocolConfig protocolConfig(){ //rpc協議 ProtocolConfig protocolConfig = new ProtocolConfig(); protocolConfig.setName("dubbo"); protocolConfig.setPort(20880); return protocolConfig; } }
服務實現類:
//注意該註解是Dubbo提供的 不要用錯 @Service public class HelloServiceImpl implements HelloService { public String sayHello(String name) { return "hello: "+name; } }
發布服務:
public static void main(String[] args) throws IOException { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class); context.start(); System.in.read();//阻塞主執行緒保持運行 }
消費者
配置類:
@Configuration @EnableDubbo @ComponentScan("com.yyh.consumer") public class ConsumerConfiguration { @Bean public ApplicationConfig applicationConfig(){ //應用配置 ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName("my-consumer"); applicationConfig.setQosEnable(true); return applicationConfig; } @Bean public RegistryConfig registryConfig(){ RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setProtocol("zookeeper"); registryConfig.setAddress("10.211.55.6:2181,10.211.55.7:2181"); return registryConfig; } }
消費者類:
@Component public class SayHelloConsumer { @Reference private HelloService helloService; public void sayHello(){ String jack = helloService.sayHello("jack"); System.out.println(jack); } }
執行測試:
public class ConsumerApplication { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfiguration.class); SayHelloConsumer consumer = context.getBean(SayHelloConsumer.class); consumer.sayHello(); }
可以發現消費者不需要指定ProtocolConfig,主要服務端固定埠即可;
使用properties配置
相比xml和api的方式,properties是體量是最輕的,在面對一些簡單配置時可以採用properties
服務提供者
在resource下提供名為dubbo.properties
的文件,內容如下:
dubbo.application.name=my-service dubbo.application.owner=jerry dubbo.protocol.dubbo.port=1099 dubbo.registry.address=zookeeper://10.211.55.6:2181
配置類:
@Configuration @EnableDubbo(scanBasePackages = "com.yyh.service") @PropertySource("classpath:/dubbo.properties") public class AnnotationAndPropperties { }
測試程式碼:
public class ProviderApplication { public static void main(String[] args) throws IOException { annotationAndPropConfig(); System.out.println("服務已啟動 按任意鍵退出"); System.in.read();//阻塞 主執行緒 保持運行 } private static void annotationAndPropConfig() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationAndPropperties.class); context.start(); } }
消費者
同樣提供properties文件,但不需要指定protocol
dubbo.application.name=my-service dubbo.application.qos.enable=false dubbo.application.owner=jerry dubbo.registry.address=zookeeper://10.211.55.6:2181
配置類:
@EnableDubbo @Configuration @ComponentScan("com.yyh.consumer") @PropertySource("classpath:/dubbo.properties") public class AnnotationAndPropConfiguration { }
消費者類:
@Component public class SayHelloConsumer { @Reference private HelloService helloService; public void sayHello(){ String jack = helloService.sayHello("jack"); System.out.println(jack); } }
測試類:
public class ConsumerApplication { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfiguration.class); SayHelloConsumer consumer = context.getBean(SayHelloConsumer.class); consumer.sayHello(); }
強調:註解使用時,掃描服務的實現類使用dubbo提供的EnableDubbo註解,而掃描其他bean用的是spring的ComponentScan註解;
常用配置項
1.啟動時檢查
默認情況下,dubbo在啟動時會自動檢查依賴(作為消費者)的服務是否可用,若服務不可用則直接拋出異常並阻止容器正常初始化,但在一些情況下我們會希望先啟動程式,因為服務可能會在之後的時間裡變為可用的;
啟動檢查註冊中心
<!--啟動程式時是否檢查註冊中心的可用性--> <dubbo:registry protocol="zookeeper" check="true" address="10.211.55.8:2181,10.211.55.7:2181,10.211.55.6:2181"/>
啟動檢查服務提供方(對所有提供者)
<!--這裡的啟動指的是從容器中獲取一個服務方的代理對象時 即getBean()時是否檢查--> <dubbo:consumer check="false"/>
啟動檢查服務提供方(對某個提供者)
<!--這裡的啟動指的是從容器中獲取一個服務方的代理對象時 即getBean()時是否檢查--> <dubbo:reference interface="com.yyh.service.HelloService" id="helloService" />
properties文件寫法:
java -Ddubbo.reference.com.foo.BarService.check=false #強制修改所有reference的check java -Ddubbo.reference.check=false #當reference的check為空時有效 java -Ddubbo.consumer.check=false java -Ddubbo.registry.check=false
2.集群容錯
在後續的使用中我們可能會對某一個服務部署多個示例形成集群,隨著項目的運行時間越來越常,一些服務節點可能會宕機或是由於網路原因暫時不可用,集群容錯可指定在調用服務失敗時dubbo要採取的行為;
dubbo提供以下6種容錯機制:
策略名稱 | 優點 | 缺點 | 主要應用場景 |
---|---|---|---|
failover(默認) | 對調用者屏蔽調用失敗的資訊 | 額外資源開銷,資源浪費 | 通訊環境良好,並發不高的場景 |
failfast | 業務快速感知失敗狀態進行自主決策 | 產生較多報錯的資訊 | 非冪等性操作,需要快速感知失敗的場景 |
failsafe | 即使失敗了也不會影響核心流程 | 對於失敗的資訊不敏感,需要額外的監控 | 旁路系統,失敗不影響核心流程正確性的場景 |
failback | 失敗自動非同步重試 | 重試任務可能堆積 | 對於實時性要求不高,且不需要返回值的一些非同步操作 |
forking | 並行發起多個調用,降低失敗概率 | 消耗額外的機器資源,需要確保操作冪等性 | 資源充足,且對於失敗的容忍度較低,實時性要求高的場景 |
broadcast | 支援對所有的服務提供者進行操作 | 資源消耗很大 | 通知所有提供者更新快取或日誌等本地資源資訊 |
冪等性:指的是每次調用都會產生相同的結果,即不會對數據進行寫操作(增刪改)
配置方式:
容錯配置分為兩個粒度:介面級別,方法級別
服務方配置:
服務方配置即將容錯配置放在服務提供方,這樣一來所有消費方就可以使用統一的容錯機制,而不用每個消費方都配一遍;
<!--介面級別:--> <dubbo:service interface="com.yyh.service.HelloService" ref="helloService" cluster="failover" retries="2"/> <!--方法級別:--> <dubbo:service interface="com.yyh.service.HelloService" cluster="failover" ref="helloService"> <dubbo:method name="sayHello" retries="2"/> </dubbo:service>
消費方配置:
<!--介面級別:--> <dubbo:service interface="com.yyh.service.HelloService" ref="helloService" cluster="failsafe"/> <!--方法級別--> <dubbo:service interface="com.yyh.service.HelloService" cluster="failover" ref="helloService"> <dubbo:method name="sayHello" retries="2"/> </dubbo:service>
3.負載均衡
為了提高系統的可用性,能夠承受更大的並發量,我們會將壓力的服務部署為集群,但是如果每次請求都交給集群中的同一個節點,那這個幾點很可能直接就宕了,所以合理的分配任務給集群中的每一台機器也是我們必須考慮的事情,好在dubbo已經提供相應的功能,我們只需簡單的配置即可完成負載均衡;
dubbo支援的任務分配方式:
隨機random
顧名思義,從Provider列表中選擇隨機選擇一個,但是我們可以為Provider指定權重,權重越大的被選中的幾率越高,因此對於性能更好的機器應設置更大的權重,反之則反,如果不指定負載均衡,默認使用隨機負載均衡;
輪詢roundrobin
即依次調用所有Provider,每個Provider輪流處理請求,當然我們也可以指定權重,Provider收到的請求數量比約等於權重比; 性能差的機器可能會累積一堆請求,最終拖慢整個系統;
基於活躍數leastactive
每個Provider收到一個請求則將活躍數+1,每處理完成一個請求則活躍數-1,新的請求將會交給活躍數最少的Provider; 簡單的說性能越好的機器將收到更多的請求,反之則反;
基於hash一致consistenthash
將根據Provider的 ip 或者其他的資訊為Provider生成一個 hash,並將這個 hash 投射到 [0, 232 – 1] 的圓環上。當有請求時,則使用請求參數計算得出一個hash值。然後查找第一個大於或等於該 hash 值的快取Provider,並將請求交予該Provider處理。如果當前Provider掛了,則在下一次請求時,為快取項查找另一個大於其 hash 值的Provider即可。 具體演算法參考:去官網看看
配置方法:
與容錯配置一樣,我們可以選擇在服務方或是消費方進行設置;
服務方:
<!--介面級別--> <dubbo:service interface="com.yyh.service.HelloService" loadbalance="roundrobin" /> <!--方法級別--> <dubbo:service interface="com.yyh.service.HelloService" cluster="failover" ref="helloService"> <dubbo:method name="sayHello" retries="2" loadbalance="roundrobin"/> </dubbo:service>
消費方:
<!--介面級別--> <dubbo:reference interface="com.yyh.service.HelloService" id="helloService" loadbalance="roundrobin"/> <!--方法級別--> <dubbo:reference interface="com.yyh.service.HelloService" id="helloService"> <dubbo:method name="sayHello" loadbalance="roundrobin"/> </dubbo:reference>
直連
即跳過註冊中新直接找服務提供方,必須提前明確服務提供方的地址,所以該方式一般僅用於開發調試;
<dubbo:registry address="N/A"/>
僅訂閱
僅訂閱指的是,不發布服務到註冊中心,只從註冊中心訂閱依賴的服務;
使用場景:當我們要開發一個新的Provider,而這個Provider需要依賴其他Provider時,使用,其目的是避免正在開發的服務發布後被消費方調用,因為開發還未完成,可能造成意想不到的結果; 這就用到了僅訂閱,再搭配直連即可完成開發調試;
<dubbo:registry protocol="zookeeper" address="10.211.55.8:2181" register="false"/>
僅註冊
僅註冊指的是,發布自身服務到註冊中心,但不從註冊中心訂閱依賴的服務;
使用場景: 自身需要對外提供服務,但是依賴的某個服務還在開發調試總,不能正常提供訪問;
<dubbo:registry protocol="zookeeper" address="10.211.55.8:2181" subscribe="false"/>
多註冊中心
對於一些大型系統,為了加快響應速度,可能會在不同地區進行部署,例如阿里雲分布在7個不同城市,有的時候可能因為當地系統還未部署完成,但是仍然需要提供訪問,這是就需要我們將相同的服務註冊到多個不同的註冊中心;
反過來,一些時候當前系統依賴的服務可能部署在不同的註冊中心中,這就需要同時向多個不同的註冊中心訂閱服務;
配置方式也非常簡單,添加額外registry即可;
案例:
我們在Common模組中創建新的介面com.yyh.service.UserService
,同時在Provider中實現該介面,最後發布到註冊中心;
發布到多個註冊中心
<!--兩個註冊中心--> <dubbo:registry id="reg1" protocol="zookeeper" address="10.211.55.8:2181,10.211.55.7:2181,10.211.55.6:2181"/> <dubbo:registry id="reg2" protocol="zookeeper" address="10.211.55.7:2188"/> <!--註冊到多個註冊中心 id用逗號隔開--> <dubbo:service registry="reg1,reg2" interface="com.yyh.service.HelloService" ref="helloService" cluster="failsafe" loadbalance="random"/> <!--userService僅註冊到id為reg2的註冊中心--> <dubbo:service registry="reg2" interface="com.kkb.service.UserService" ref="userService" cluster="failsafe" loadbalance="random"/> <!--實現Bean--> <bean id="helloService" class="com.kkb.service.impl.HelloServiceImpl"/> <bean id="userService" class="com.kkb.service.impl.UserServiceImpl"/>
從不同註冊中心訂閱
<!--兩個註冊中心--> <dubbo:registry id="reg1" protocol="zookeeper" address="10.211.55.8:2181,10.211.55.7:2181,10.211.55.6:2181"/> <dubbo:registry id="reg2" protocol="zookeeper" address="10.211.55.7:2188"/> <!--從兩個註冊中心分別訂閱 --> <dubbo:reference interface="com.yyh.service.HelloService" id="helloService" cluster="failover" retries="3" registry="reg1"/> <dubbo:reference interface="com.yyh.service.UserService" id="userService" cluster="failover" retries="3" registry="reg2"/> <dubbo:consumer check="false"/>