簡單介紹
dubbo是阿里開源出來的一個rpc框架,主要是用於微服務分佈式項目的遠程調用,它提供了三大核心能力:面向接口的遠程方法調用,智能容錯和負載均衡,以及服務自動註冊和發現,下面是調用的原理圖:
dubbo框架的整體設計:
圖例說明:
1,圖中左邊淡藍背景的爲服務消費方使用的接口,右邊淡綠色背景的爲服務提供方使用的接口,位於中軸線上的爲雙方都用到的接口。
2,圖中從下至上分爲十層,各層均爲單向依賴,右邊的黑色箭頭代表層之間的依賴關係,每一層都可以剝離上層被複用,其中,Service 和 Config 層爲 API,其它各層均爲 SPI。
3,圖中綠色小塊的爲擴展接口,藍色小塊爲實現類,圖中只顯示用於關聯各層的實現類。
4,圖中藍色虛線爲初始化過程,即啓動時組裝鏈,紅色實線爲方法調用過程,即運行時調時鏈,紫色三角箭頭爲繼承,可以把子類看作父類的同一個節點,線上的文字爲調用的方法
遠程調用細節
服務提供者暴露一個服務的詳細過程
上圖是服務提供者暴露服務的主過程:
首先 ServiceConfig 類拿到對外提供服務的實際類 ref(如:HelloWorldImpl),然後通過 ProxyFactory 類的 getInvoker 方法使用 ref 生成一個 AbstractProxyInvoker 實例,到這一步就完成具體服務到 Invoker 的轉化。接下來就是 Invoker 轉換到 Exporter 的過程。
Dubbo 處理服務暴露的關鍵就在 Invoker 轉換到 Exporter 的過程,上圖中的紅色部分。下面我們以 Dubbo 和 RMI 這兩種典型協議的實現來進行說明:
Dubbo 的實現
Dubbo 協議的 Invoker 轉爲 Exporter 發生在 DubboProtocol 類的 export 方法,它主要是打開 socket 偵聽服務,並接收客戶端發來的各種請求,通訊細節由 Dubbo 自己實現。
RMI 的實現
RMI 協議的 Invoker 轉爲 Exporter 發生在 RmiProtocol類的 export 方法,它通過 Spring 或 Dubbo 或 JDK 來實現 RMI 服務,通訊細節這一塊由 JDK 底層來實現,這就省了不少工作量。
服務消費者消費一個服務的詳細過程
上圖是服務消費的主過程:
首先 ReferenceConfig 類的 init 方法調用 Protocol 的 refer 方法生成 Invoker 實例(如上圖中的紅色部分),這是服務消費的關鍵。接下來把 Invoker 轉換爲客戶端需要的接口(如:HelloWorld)。
dubbo直接引用
1,xml形式
服務提供者:
public interface DemoService {
String sayHello(String name);
}
具體實現:
public class DemoServiceImpl implements DemoService {
public String sayHello(String name) {
return "Hello " + name;
}
}
spring聲明暴露服務:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- 提供方應用信息,用於計算依賴關係 -->
<dubbo:application name="hello-world-app" />
<!-- 使用multicast廣播註冊中心暴露服務地址 -->
<dubbo:registry address="multicast://224.5.6.7:1234" />
<!-- 用dubbo協議在20880端口暴露服務 -->
<dubbo:protocol name="dubbo" port="20880" />
<!-- 聲明需要暴露的服務接口 -->
<dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService" />
<!-- 和本地bean一樣實現服務 -->
<bean id="demoService" class="org.apache.dubbo.demo.provider.DemoServiceImpl" />
</beans>
服務消費者:
通過 Spring 配置引用遠程服務:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- 消費方應用名,用於計算依賴關係,不是匹配條件,不要與提供方一樣 -->
<dubbo:application name="consumer-of-helloworld-app" />
<!-- 使用multicast廣播註冊中心暴露發現服務地址 -->
<dubbo:registry address="multicast://224.5.6.7:1234" />
<!-- 生成遠程服務代理,可以和本地bean一樣使用demoService -->
<dubbo:reference id="demoService" interface="org.apache.dubbo.demo.DemoService" />
</beans>
遠程調用:
public class Consumer {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"http://10.20.160.198/wiki/display/dubbo/consumer.xml"});
context.start();
DemoService demoService = (DemoService)context.getBean("demoService"); // 獲取遠程服務代理
String hello = demoService.sayHello("world"); // 執行遠程方法
System.out.println( hello ); // 顯示調用結果
}
}
2,api形式
提供者暴露服務:
/**
* @author shihaowei
* @date 2020-06-11 11:43
*/
@Configuration
//@EnableDubbo
@DubboComponentScan("person.shw.dubbo.provider")
public class DubboConfig {
@Value("${nacos.data.id:f21d3b6b-20ed-4091-ab17-ac073abc3eba}")
private String namespace;
@Bean
public ApplicationConfig applicationConfig(){
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("provider-nacos");
return applicationConfig;
}
@Bean
public RegistryConfig registryConfig(){
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("nacos://120.79.76.230:8848");
registryConfig.setGroup("ONE_GROUP");
Map<String,String> paramMap = new HashMap<String, String>();
paramMap.put("namespace",namespace);
registryConfig.setParameters(paramMap);
return registryConfig;
}
@Bean
public ProtocolConfig protocolConfig(){
ProtocolConfig protocolConfig = new ProtocolConfig();
protocolConfig.setName("dubbo");
protocolConfig.setPort(12347);
return protocolConfig;
}
}
暴露服務的接口實現:
import org.apache.dubbo.config.annotation.Service;
import person.shw.dubbo.api.DubboApi;
/**
* @author shihaowei
* @date 2020-06-09 17:21
*/
@Service
public class ProviderServiceImpl implements DubboApi {
public String getConsumerData() {
return "333333333333333333";
}
}
org.apache.dubbo.config.annotation.Service是引用的dubbo的包,而不是Spring的@Service
消費者消費服務:
@Configuration
public class DubboNacosConfig {
@Bean
public ApplicationConfig applicationConfig(RegistryConfig registryConfig){
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("consumer-nacos");
applicationConfig.setRegistry(registryConfig);
return applicationConfig;
}
@Bean
public RegistryConfig registryConfig(){
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setCheck(false);
registryConfig.setAddress("nacos://120.79.76.230:8848");
registryConfig.setUsername("nacos");
registryConfig.setPassword("nacos");
Map<String,String> paramMap = new HashMap<String, String>();
paramMap.put("namespace","f21d3b6b-20ed-4091-ab17-ac073abc3eba");
registryConfig.setParameters(paramMap);
return registryConfig;
}
}
調用服務
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import person.shw.dubbo.api.DubboApi;
/**
* @author shihaowei
* @date 2020-06-09 17:22
*/
@RestController
public class ConsumerController {
@Reference
private DubboApi dubboApi;
@GetMapping(value = "/consumer/getMessage")
public String getMessage(){
return "【收到發來的消息】---->"+dubboApi.getConsumerData();
}
}
dubbo的泛化調用
爲什麼要使用泛化調用?
一般使用dubbo,provider端需要暴露出接口和方法,consumer端要十分明確服務使用的接口定義和方法定義(還有入參返參類型等等信息,常常還需要基於provider端提供的API),兩端才能正常通信調用。
然而存在一種使用場景,調用方並不關心要調用的接口的詳細定義,它只關注我要調用哪個方法,需要傳什麼參數,我能接收到什麼返回結果即可,這樣可以大大降低consumer端和provider端的耦合性。
所以爲了應對以上的需求,dubbo提供了泛化調用,也就是在consumer只知道一個接口全限定名以及入參和返參的情況下,就可以調用provider端的調用,而不需要傳統的接口定義這些繁雜的結構。
比如微服務的網關就可以採用泛化調用,又不需要強引用衆多需要調用的服務
例子
繼續使用上面使用api的配置,當我調用的時候,不時用@reference註解,而是用genericservice泛化調用,參數及返回值中的所有 POJO 均用 Map 表示,通常用於框架集成
@RestController
public class ConsumerController {
@Autowired
private ApplicationConfig applicationConfig;
@GetMapping(value = "/consumer/getMessage")
public String getMessage(){
/** dubbo的泛化調用 */
ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();
reference.setApplication(applicationConfig);
reference.setInterface("person.shw.dubbo.api.DubboApi");
reference.setTimeout(200000);
reference.setGeneric(true);
/** 添加緩存策略 */
/*ReferenceConfigCache cache = ReferenceConfigCache.getCache();
GenericService genericService = cache.get(reference);*/
GenericService genericService = reference.get();
/** 設置接口所需要的參數類型 */
String[] parametertypes = new String[]{};
String[] args = new String[]{};
Object data = genericService.$invoke("getConsumerData", parametertypes, args);
System.out.println(JSON.toJSONString(data));
return "【收到consumer發來的消息】---->"+JSON.toJSONString(data);
}
}
調用服務,打印日誌
2020-06-20 17:44:25.482 INFO 3033 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 924 ms
2020-06-20 17:44:25.664 WARN 3033 --- [ main] org.apache.dubbo.config.AbstractConfig : [DUBBO] There's no valid metadata config found, if you are using the simplified mode of registry url, please make sure you have a metadata address configured properly., dubbo version: 2.7.3, current host: 192.168.31.132
2020-06-20 17:44:25.669 INFO 3033 --- [ main] org.apache.dubbo.config.AbstractConfig : [DUBBO] There's no valid monitor config found, if you want to open monitor statistics for Dubbo, please make sure your monitor is configured properly., dubbo version: 2.7.3, current host: 192.168.31.132
2020-06-20 17:44:25.689 INFO 3033 --- [ main] o.a.d.qos.protocol.QosProtocolWrapper : [DUBBO] qos won't be started because it is disabled. Please check dubbo.application.qos.enable is configured either in system property, dubbo.properties or XML/spring-boot configuration., dubbo version: 2.7.3, current host: 192.168.31.132
2020-06-20 17:44:25.786 INFO 3033 --- [ main] o.a.dubbo.registry.nacos.NacosRegistry : [DUBBO] Load registry cache file /Users/edz/.dubbo/dubbo-registry-consumer-nacos-120.79.76.230:8848.cache, data: {person.shw.dubbo.api.DubboApi=dubbo://192.168.31.132:12347/person.shw.dubbo.api.DubboApi?anyhost=true&application=provider-nacos&bean.name=ServiceBean:person.shw.dubbo.api.DubboApi&category=providers&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=person.shw.dubbo.api.DubboApi&methods=getConsumerData&path=person.shw.dubbo.api.DubboApi&pid=2846&protocol=dubbo®ister=true&release=2.7.3&side=provider×tamp=1592646130152}, dubbo version: 2.7.3, current host: 192.168.31.132
2020-06-20 17:44:25.804 INFO 3033 --- [ main] o.a.dubbo.registry.nacos.NacosRegistry : [DUBBO] Register: consumer://192.168.31.132/org.apache.dubbo.rpc.service.GenericService?application=consumer-nacos&category=consumers&check=false&dubbo=2.0.2&generic=true&interface=person.shw.dubbo.api.DubboApi&lazy=false&pid=3033&qos.enable=false&release=2.7.3&side=consumer&sticky=false&timeout=200000×tamp=1592646265664, dubbo version: 2.7.3, current host: 192.168.31.132
2020-06-20 17:44:25.967 INFO 3033 --- [ main] o.a.dubbo.registry.nacos.NacosRegistry : [DUBBO] Subscribe: consumer://192.168.31.132/org.apache.dubbo.rpc.service.GenericService?application=consumer-nacos&category=providers,configurators,routers&dubbo=2.0.2&generic=true&interface=person.shw.dubbo.api.DubboApi&lazy=false&pid=3033&qos.enable=false&release=2.7.3&side=consumer&sticky=false&timeout=200000×tamp=1592646265664, dubbo version: 2.7.3, current host: 192.168.31.132
2020-06-20 17:44:26.009 INFO 3033 --- [ main] o.a.dubbo.registry.nacos.NacosRegistry : [DUBBO] Notify urls for subscribe url consumer://192.168.31.132/org.apache.dubbo.rpc.service.GenericService?application=consumer-nacos&category=providers,configurators,routers&dubbo=2.0.2&generic=true&interface=person.shw.dubbo.api.DubboApi&lazy=false&pid=3033&qos.enable=false&release=2.7.3&side=consumer&sticky=false&timeout=200000×tamp=1592646265664, urls: [dubbo://192.168.31.132:12347/person.shw.dubbo.api.DubboApi?anyhost=true&application=provider-nacos&bean.name=ServiceBean:person.shw.dubbo.api.DubboApi&category=providers&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=person.shw.dubbo.api.DubboApi&methods=getConsumerData&path=person.shw.dubbo.api.DubboApi&pid=2846&protocol=dubbo®ister=true&release=2.7.3&side=provider×tamp=1592646130152], dubbo version: 2.7.3, current host: 192.168.31.132
2020-06-20 17:44:26.181 INFO 3033 --- [ main] o.a.d.remoting.transport.AbstractClient : [DUBBO] Succeed connect to server /192.168.31.132:12347 from NettyClient 192.168.31.132 using dubbo version 2.7.3, channel is NettyChannel [channel=[id: 0x5cf0891f, L:/192.168.31.132:55666 - R:/192.168.31.132:12347]], dubbo version: 2.7.3, current host: 192.168.31.132
2020-06-20 17:44:26.181 INFO 3033 --- [ main] o.a.d.remoting.transport.AbstractClient : [DUBBO] Start NettyClient /192.168.31.132 connect to the server /192.168.31.132:12347, dubbo version: 2.7.3, current host: 192.168.31.132
2020-06-20 17:44:26.216 INFO 3033 --- [client.listener] o.a.dubbo.registry.nacos.NacosRegistry : [DUBBO] Notify urls for subscribe url consumer://192.168.31.132/org.apache.dubbo.rpc.service.GenericService?application=consumer-nacos&category=providers,configurators,routers&dubbo=2.0.2&generic=true&interface=person.shw.dubbo.api.DubboApi&lazy=false&pid=3033&qos.enable=false&release=2.7.3&side=consumer&sticky=false&timeout=200000×tamp=1592646265664, urls: [dubbo://192.168.31.132:12347/person.shw.dubbo.api.DubboApi?anyhost=true&application=provider-nacos&bean.name=ServiceBean:person.shw.dubbo.api.DubboApi&category=providers&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=person.shw.dubbo.api.DubboApi&methods=getConsumerData&path=person.shw.dubbo.api.DubboApi&pid=2846&protocol=dubbo®ister=true&release=2.7.3&side=provider×tamp=1592646130152], dubbo version: 2.7.3, current host: 192.168.31.132
2020-06-20 17:44:26.218 INFO 3033 --- [ main] org.apache.dubbo.config.AbstractConfig : [DUBBO] Refer dubbo service org.apache.dubbo.rpc.service.GenericService from url nacos://120.79.76.230:8848/org.apache.dubbo.registry.RegistryService?anyhost=true&application=consumer-nacos&bean.name=ServiceBean:person.shw.dubbo.api.DubboApi&category=providers&check=false&deprecated=false&dubbo=2.0.2&dynamic=true&generic=true&interface=person.shw.dubbo.api.DubboApi&lazy=false&methods=getConsumerData&path=person.shw.dubbo.api.DubboApi&pid=3033&protocol=dubbo&qos.enable=false®ister=true®ister.ip=192.168.31.132&release=2.7.3&remote.application=provider-nacos&side=consumer&sticky=false&timeout=200000×tamp=1592646265664, dubbo version: 2.7.3, current host: 192.168.31.132
[泛化調用成功]----->"333333333333333333"
2020-06-20 17:44:26.409 INFO 3033 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2020-06-20 17:44:26.627 INFO 3033 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 10009 (http) with context path ''
2020-06-20 17:44:26.630 INFO 3033 --- [ main] p.s.dubbo.consumer.DubboConsumerLanuch : Started DubboConsumerLanuch in 2.383 seconds (JVM running for 2.875)
倒數第四行打印日誌,泛化調用成功。
// TODO 後面補上一個網關gateway使用dubbo泛化調用的例子