文章目錄
Tips: 關於Dubbo的學習,Dubbo官網寫的簡單易懂Dubbo官網,如果有時間沒必要網上找了,基本也是官網一大抄。如果趕時間瞭解下可以參考下面的內容。
Zookeeper和Dubbo-Admin的安裝
linux下安裝,linux下安裝zookeeper;
windows下安裝Dubbo:
下載zookeeper-3.4.11.tar.gz
,解壓後進入bin
目錄,命令行運行zkServer.cmd
,會報錯,提示找不到zoo.cfg配置文件,如下:
ERROR [main:QuorumPeerMain@88] - Invalid config, exiting abnormally
org.apache.zookeeper.server.quorum.QuorumPeerConfig$ConfigException: Error processing C:\Users\lenovo\Desktop\dubbo\zookeeper-3.4.11\bin\..\conf\zoo.cfg
at org.apache.zookeeper.server.quorum.QuorumPeerConfig.parse(QuorumPeerConfig.java:156)
at org.apache.zookeeper.server.quorum.QuorumPeerMain.initializeAndRun(QuorumPeerMain.java:104)
at org.apache.zookeeper.server.quorum.QuorumPeerMain.main(QuorumPeerMain.java:81)
Caused by: java.lang.IllegalArgumentException: C:\Users\lenovo\Desktop\dubbo\zookeeper-3.4.11\bin\..\conf\zoo.cfg file is missing
解決: 進入zookeeper的conf目錄下,找到zoo_xample.cfg
文件,複製一份,重命名爲zookeeper
即可。可以進入到這個配置文件瞄一眼,看到快照存儲位置/tmp/zookeeper
,監聽的端口2181(可用於客戶端連接)
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=/tmp/zookeeper
# the port at which the clients will connect
clientPort=2181
既然讓不要用/tmp/zookeeper
來存儲,那麼我們就修改以下,比如修改到conf同級data文件
dataDir=../data
現在再來啓動,看到下面的日誌說明成功了
也可以使用客戶端來訪問一下,同樣還是進入到bin目錄,命令行執行./zkCli.cmd
,並且查看根節點有哪些目錄
更多關於zookeeper的命令行操作,查看zookeeper數據結構和監聽功能
tips:至於Dubbo則是不需要安裝的,因爲dubbo本身並不是一個服務軟件。它其實就是一個jar包能夠幫你的java程序連接到zookeeper,並利用zookeeper消費、提供服務。但是爲了讓用戶更好的管理監控衆多的dubbo服務,官方提供了一個可視化的監控程序,不過這個監控即使不裝也不影響使用(下面裝的是管理控制檯)。
下載dubbo-admin,進入目錄,打jar包mvn clean pagkage
,接下來在target目錄下就能看到dubbo-admin-0.0.1-SNAPSHOT.jar
,該目錄下命令行運行java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
程序。啓動起來後瀏覽器訪問localhost:7001
,要求輸入用戶名和密碼,均爲root
。
這個時候服務數,應用數都爲0。接下來編寫服務提供者和服務消費者程序,之後再來看這個管理控制檯界面。
需要注意的是在application.properties文件裏面是配置了zookeeper的地址和監聽的端口號。
使用Dubbo進行原遠程過程調用
服務提供方:提供商品查詢的服務;
服務消費方:傳入參數,調用服務提供方的服務。
通用服務:存放服務接口,服務模型,服務異常等均放在 API。
所以需要創建三個工程,使用idea的話,那麼就先創建一個空的project,再創建三個moduel。
通用服務:service-interface
實體類:
public class Goods implements Serializable {
private Integer id;
private String name;
private Double price;
private String desc;
toString,全參和無參構造器,getter和setter方法
}
服務接口:
public interface GoodsService {
Goods getGoods(int id);
}
最後將該模塊安裝到本地倉庫,mvn install
服務提供方: goods-service-provider
pom.xml引入依賴 分別爲dubbo,zk, 通用服務
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.scu</groupId>
<artifactId>goods-service-provider</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.scu</groupId>
<artifactId>service-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 引入dubbo -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.2</version>
</dependency>
<!-- 由於我們使用zookeeper作爲註冊中心,所以需要操作zookeeper
dubbo 2.6以前的版本引入zkclient操作zookeeper
dubbo 2.6及以後的版本引入curator操作zookeeper
下面兩個zk客戶端根據dubbo版本2選1即可
-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
</dependencies>
</project>
服務配置文件(Spring配置文件)applicatonContext
.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!--當前服務名稱-->
<dubbo:application name="goods-service-provider"/>
<!--使用zookeeper廣播註冊中心暴露服務地址-->
<dubbo:registry address="zookeeper://localhost:2181"/>
<!--使用dubbo協議在20880端口暴露服務-->
<dubbo:protocol name="dubbo" port="20880"/>
<!--聲明需要暴露的服務的接口-->
<dubbo:service interface="com.scu.service.GoodsService" ref="goodsService"/>
<bean id="goodsService" class="com.scu.dubbo.service.GoodsServiceImpl"/>
</beans>
服務實現類:
public class GoodsServiceImpl implements GoodsService {
@Override
public Goods getGoods(int id) {
System.out.println(id);
return new Goods(id,"mi 9 pro",3600.0,"小米首款5G手機");
}
}
啓動類:讀取配置文件,註冊服務
public class DubboMain {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
System.in.read();
}
}
目錄結構如下
運行main方法啓動服務(先得把zookeeper服務啓動起來),查看dubbo admin
服務消費者:goods-service-consumer
其中pom.xml同服務提供者
applicationContext.xml內容:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!--當前服務名稱-->
<dubbo:application name="goods-service-provider"/>
<!--使用zookeeper廣播註冊中心暴露服務地址-->
<dubbo:registry address="zookeeper://localhost:2181"/>
<!--生成遠程代理服務,可以和本地bean一樣使用GoodsService-->
<dubbo:reference id="goodsService" interface="com.scu.service.GoodsService" />
</beans>
主程序,啓動Spring容器調用服務
public class ConsumerMain {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
GoodsService goodsService = applicationContext.getBean("goodsService",GoodsService.class);
Goods goods = goodsService.getGoods(1);
System.out.println("id爲1的商品信息爲:" + goods);
System.in.read();
}
}
目錄結構如下:
啓動服務,查看兩個控制檯的輸出分別爲:
1
id爲1的商品信息爲:Goods{id=1, name='mi 9 pro', price=3600.0, desc='小米首款5G手機'}
到這一個簡單的使用dubbo的RPC調用demo就完成了。
tips:maven如果使用3.6.2會報綁定錯誤。換回3.6.1
Dubbo整合spring-boot
三個點:
1.導包不同
2.配置文件使用application.yml(或者application.properties),照着applicationContext.xml填即可
3.添加註解的時候注意導入的是哪個類(@Service,@Reference)。
服務提供者依賴如下
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>com.scu</groupId>
<artifactId>service-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
</dependencies>
編寫配置文件application.yml:
dubbo:
application:
name: goods-service-provider
registry:
protocol: zookeeper
address: localhost:2181
protocol:
name: dubbo
port: 20880
application.name就是服務名,不能跟別的dubbo提供端重複
registry.protocol 是指定註冊中心協議
registry.address 是註冊中心的地址加端口號
protocol.name 是分佈式固定是dubbo,不要改。
base-package 註解方式要掃描的包,可以在啓動類上使用註解代替
服務實現:
package com.scu.dubbo.service;
import com.alibaba.dubbo.config.annotation.Service;
import com.scu.bean.Goods;
import com.scu.service.GoodsService;
@Service // 注意導入的是alibaba的Service而不是Spring的
public class GoodsServiceImpl implements GoodsService {
@Override
public Goods getGoods(int id) {
System.out.println(id);
return new Goods(id,"mi 9 pro",3600.0,"小米首款5G手機");
}
}
啓動類:
package com.scu.dubbo;
import com.alibaba.dubbo.config.spring.context.annotation.DubboComponentScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@DubboComponentScan(basePackages = "com.scu.dubbo.service") // 掃描的包
public class ServiceStarter {
public static void main(String[] args) {
SpringApplication.run(ServiceStarter.class,args);
}
}
再寫消費者
導入依賴:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>com.scu</groupId>
<artifactId>service-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
</dependencies>
編寫配置文件application.yml:
server:
port: 8081 #使用了81端口 前面用了80端口
dubbo:
application:
name: goods-service-consumer
registry:
protocol: zookeeper
address: localhost:2181
controller類:
package com.scu.dubbo.controller;
import com.alibaba.dubbo.config.annotation.Reference;
import com.scu.bean.Goods;
import com.scu.service.GoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class GoodsController {
@Reference
private GoodsService goodsService;
@GetMapping("/goods")
public Goods getGoodsById(int id){
System.out.println(123);
return goodsService.getGoods(id);
}
}
啓動類:
@SpringBootApplication
public class ConsumerStarter {
public static void main(String[] args) {
SpringApplication.run(ConsumerStarter.class,args);
}
}
整個的目錄結構如下
先啓動生產者,再啓動消費者
頁面訪問 http://localhost:8081/goods?id=1
,返回如下結果說明調用成功。
{"id":1,"name":"mi 9 pro","price":3600.0,"desc":"小米首款5G手機"}
配置解釋
啓動時檢查
之前的demo要求先啓動服務提供方,後啓動服務消費方。如果先啓動服務消費方,那麼會報錯,找不到服務提供方。這是因爲啓動的時候會檢查容器,找不到服務提供方,拋出異常服務就沒有啓動起來了。必要的時候我們需要關閉這個啓動時檢查。使用配置check=false
即可。
public class ConsumerMain {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// GoodsService goodsService = applicationContext.getBean("goodsService", GoodsService.class);
// Goods goods = goodsService.getGoods(10086);
// System.out.println(goods);
System.in.read();
}
}
<dubbo:reference id="goodsService" interface="com.scu.service.GoodsService" check="false"/>
或者使用全局配置
<dubbo:consumer check="false" timeout="4000"/>
超時設置
默認情況下,服務消費方調用服務提供方提供的服務,如果1000ms內沒有收到響應,拋出超時異常。比如我在服務消費方,設置服務超時時間爲3秒,服務提供方提供的服務在4秒後纔會返回結果。
服務消費方設置超時時間:timeout=3000
<dubbo:reference id="goodsService" interface="com.scu.service.GoodsService" check="false"
timeout="3000"/>
服務提供方: 4秒後返回結果
public class GoodsServiceImpl implements GoodsService {
@Override
public Goods getGoods(int id) {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("服務被調用:" + id);
return new Goods(id,"xiaomi 9 pro",3699.0,"小米的第一款5G手機");
}
}
所以最後會拋出超時異常
Caused by: com.alibaba.dubbo.remoting.TimeoutException: Waiting server-side response timeout.
此外,超時設置還可以加到方法上,全局配置上。優先級是方法>接口>全局配置
<dubbo:reference id="goodsService" interface="com.scu.service.GoodsService" check="false"
timeout="3000">
<dubbo:method name="getGoods" timeout="1000"/>
</dubbo:reference>
<!--消費者全局配置:所有的服務都不檢查-->
<dubbo:consumer check="false" timeout="4000"/>
甚至可以配置到服務提供的那一方,而且如果級別是一樣的話那麼就是消費者優先。但是,如果服務提供者的級別更高,那麼仍然是服務提供者郵箱。具體的順序,參考官網。
重試次數
由於網絡問題可能導致超時,所以有時需要設置超時重試,使用retries=2
即可,2表示重試的次數,不包括最初的調用,所以最終會調用三次,但是隻要有一次返回則不會繼續重新調用。
服務消費方,設置重試次數 2次。
<dubbo:reference id="goodsService" interface="com.scu.service.GoodsService" check="false"
timeout="3000" retries="2">
<dubbo:method name="getGoods" timeout="1000"/>
</dubbo:reference>
服務提供方代碼如下: 每次調用會答應id,同時休眠2秒,調用方超時時間設置爲1秒,所以肯定會超時。
public class GoodsServiceImpl implements GoodsService {
@Override
public Goods getGoods(int id) {
System.out.println("服務被調用:" + id);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new Goods(id,"xiaomi 9 pro",3699.0,"小米的第一款5G手機");
}
}
想起的服務提供方再啓動服務消費方,服務方控制檯輸出三次:服務被調用:10086
服務被調用:10086
服務被調用:10086
服務被調用:10086
此外,如果該服務在多臺機器上都有,比如三臺機器都有該服務,那麼默認每次重試會調用不同的服務。爲了做個試驗,啓動三次服務提供方(三個進程當作三臺機器),每次都需要修改服務暴露的端口(20880,20881,20882),同時修改getGoods
方法,比如啓動三次,3次只修改如下
System.out.println("服務1被調用:" + id);
System.out.println("服務2被調用:" + id);
System.out.println("服務3被調用:" + id);
public class ProviderMain {
@Test // 端口20880 System.out.println("服務1被調用:" + id);
public void test1() throws IOException {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
System.in.read();
}
@Test // 端口20881 System.out.println("服務2被調用:" + id);
public void test2() throws IOException {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
System.in.read();
}
@Test // 端口20882 System.out.println("服務3被調用:" + id);
public void test3() throws IOException {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
System.in.read();
}
}
啓動服務消費方後,對應的test1-test3控制檯分別打印
服務1被調用:10086
服務2被調用:10086
服務3被調用:10086
tips: 如果是修改/刪除/查詢等操作需要設計成冪等操作(多次相同的操作不會帶來額外的影響,比如多次相同的查詢得到的會是同一個結果,多次相同的刪除操作,第一次成功,後面雖然不能刪除帶上不會帶來額外的影響),同時設置重試次數;新增操作則設計爲非冪等操作,並且不要加重試次數。
多版本
有時候會遇到接口的升級,那麼就有新版本和舊版本,我們先讓服務消費方調用部分的新版本,等新版本穩定會再全部換成新版本。比如GoodsService接口,現在又了新版本實現如下
舊版本:
public class GoodsServiceImpl implements GoodsService {
@Override
public Goods getGoods(int id) {
System.out.println("服務1被調用:" + id);// 新舊版本區別在這
return new Goods(id,"xiaomi 9 pro",3699.0,"小米的第一款5G手機");
}
}
新版本:
public class GoodsServiceImpl2 implements GoodsService {
@Override
public Goods getGoods(int id) {
System.out.println("新的服務被調用:" + id);
return new Goods(id,"xiaomi 9 pro",3699.0,"小米的第一款5G手機");
}
}
提供方修改的配置文件
<dubbo:service interface="com.scu.service.GoodsService" ref="goodsService" version="0.0.1"/>
<bean id="goodsService" class="com.scu.dubbo.service.GoodsServiceImpl"/>
<dubbo:service interface="com.scu.service.GoodsService" ref="goodsService2" version="0.0.2"/>
<bean id="goodsService2" class="com.scu.dubbo.service.GoodsServiceImpl2"/>
消費方的配置文件: 使用version="0.0.1"
使用舊版本
<dubbo:reference id="goodsService" interface="com.scu.service.GoodsService" check="false"
timeout="3000" retries="2" version="0.0.1">
<dubbo:method name="getGoods" timeout="1000"/>
</dubbo:reference>
輸出爲: 服務1被調用:10086
假設新服務穩定了,此時修改調用方配置文件,設置version="0.0.2"
,
那麼這次輸出的就是:新的服務被調用:10086
Zookeeper宕機與Dubbo直連
現象:註冊中心Zookeeper宕機後,還可以調用Dubbo暴露的服務,這是因爲註冊中心雖然全部宕機,但是服務提供者和服務消費者可以通過本地緩存進行通訊,即服務消費者,如果調用過服務,那麼會本地記錄服務地址,但Zookeeper宕機後,可以繞過Zookeeper直接和Dubbo提供的服務連接。此外,還有種方案,即Dubbo直連可以使用url=127.0.0.1:20880
標籤(如果是使用@Reference
註解就是``@Reference(url=xxx)
)與Dubbo直接進行直連,即告訴消費者服務提供者地址。
負載均衡
假設將GoodsService
服務部署到了3臺機器,那麼則需要使用負載均衡策略來決定每次服務調用者調用的是哪臺機器。
下面的介紹來自Dubbo官網,因爲確實沒啥好說的了。
Random LoadBalance
- 隨機,按權重設置隨機概率。
- 在一個截面上碰撞的概率高,但調用量越大分佈越均勻,而且按概率使用權重後也比較均勻,有利於動態調整提供者權重。
RoundRobin LoadBalance
- 輪詢,按公約後的權重設置輪詢比率。
- 存在慢的提供者累積請求的問題,比如:第二臺機器很慢,但沒掛,當請求調到第二臺時就卡在那,久而久之,所有請求都卡在調到第二臺上。
LeastActive LoadBalance
- 最少活躍調用數,相同活躍數的隨機,活躍數指調用前後計數差。
- 使慢的提供者收到更少請求,因爲越慢的提供者的調用前後計數差會越大。
ConsistentHash LoadBalance
- 一致性 Hash,相同參數的請求總是發到同一提供者。
- 當某一臺提供者掛時,原本發往該提供者的請求,基於虛擬節點,平攤到其它提供者,不會引起劇烈變動。
服務端服務級別
<dubbo:service interface="..." loadbalance="roundrobin" />
客戶端服務級別
<dubbo:reference interface="..." loadbalance="roundrobin" />
服務端方法級別
<dubbo:service interface="...">
<dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:service>
客戶端方法級別
<dubbo:reference interface="...">
<dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:reference>