什麼是註冊中心:html
註冊中心就是用來存儲服務信息的地方,就像房屋中介同樣;java
爲何須要註冊中心:算法
在前面的例子中咱們使用了客戶端與服務器直連的方式完成了服務的調用,在實際開發中這回帶來一些問題,例如服務器地址變動了,或服務搭建了集羣,客戶端不知道服務的地址,此時註冊中心就派上用場了,服務提供方發佈服務後將服務信息放在zookeeper中,而後消費方從zookeeper獲取服務器信息,進行調用,這樣就使提供方和消費方解開了耦合,也讓服務提供方能夠更方便的搭建集羣;spring
使用:apache
1.啓動zookeeper,單機和集羣均可以api
2.在雙方配置文件中指定註冊中心的信息(內容相同)緩存
<!--註冊中心 N/A 表示不使用註冊中心 直連客戶端 地址能夠是一個或多個 多個表示集羣--> <dubbo:registry protocol="zookeeper" address="10.211.55.6:2181,10.211.55.7:2181"/>
須要說明的是,註冊中心不是必須使用zookeeper,dubbo還支持其餘三種:Simple,Redis,Multicast,因其優秀的可用性,官方推薦使用zookeeper;服務器
簡單的說就是不使用配置文件而是使用使用代碼來完成配置,該方式主要用於測試環境或集成其餘框架,不推薦用於生產環境;網絡
服務提供者:併發
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,主要服務端固定端口便可;
相比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註解;
默認狀況下,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
在後續的使用中咱們可能會對某一個服務部署多個示例造成集羣,隨着項目的運行時間愈來愈常,一些服務節點可能會宕機或是因爲網絡緣由暫時不可用,集羣容錯可指定在調用服務失敗時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>
爲了提升系統的可用性,可以承受更大的併發量,咱們會將壓力的服務部署爲集羣,可是若是每次請求都交給集羣中的同一個節點,那這個幾點極可能直接就宕了,因此合理的分配任務給集羣中的每一臺機器也是咱們必須考慮的事情,好在dubbo已經提供相應的功能,咱們只需簡單的配置便可完成負載均衡;
dubbo支持的任務分配方式:
顧名思義,從Provider列表中選擇隨機選擇一個,可是咱們能夠爲Provider指定權重,權重越大的被選中的概率越高,所以對於性能更好的機器應設置更大的權重,反之則反,若是不指定負載均衡,默認使用隨機負載均衡;
即依次調用全部Provider,每一個Provider輪流處理請求,固然咱們也能夠指定權重,Provider收到的請求數量比約等於權重比; 性能差的機器可能會累積一堆請求,最終拖慢整個系統;
每一個Provider收到一個請求則將活躍數+1,每處理完成一個請求則活躍數-1,新的請求將會交給活躍數最少的Provider; 簡單的說性能越好的機器將收到更多的請求,反之則反;
將根據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"/>