一、demo構建
1. demo項目結構如圖所示:
包含api,consumer,provider三個module
api如下所示,提供provider需要暴露的接口
package com.alibaba.dubbo.demo;
import java.util.List;
/**
* Created by Xkane on 2018/9/13.
*/
public interface DemoService {
List<String> getPermissions(Long id);
}
2. provider 發布服務配置文件
3. consumer訂閱服務配置文件
二、dubbo框架結構
dubbo框架設計總共分了10層:
1. 服務接口層(Service):
該層是與實際業務邏輯相關,就如上面demo配置的
<!--使用 dubbo 協議實現定義好的 api.PermissionService 接口-->
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" protocol="dubbo" />
這個service就是業務方自己定義的接口與其實現。
2.配置層(Config):
該層是將業務方的service信息,配置文件的信息進行收集配置,主要是以ServiceConfig和ReferenceConfig為中心,
ServiceConfig是配置提供方provider提供的配置,當Spring啟動的時候會響應的啟動provider服務發布和注冊的過程,
主要是加入一個ServiceBean繼承ServiceConfig在spring注冊,同理ReferenceConfig是consumer方的配置,
當消費方consumer啟動時,會啟動consumer的發布服務訂閱服務的過程,當然也是使用一個ReferenceBean繼承
ReferenceConfig注冊在spring上
3. 服務代理層(Proxy):
對服務接口進行透明代理,生成服務的客戶端和服務端,使服務的遠程調用就像在本地調用一樣,默認使用JavassistProxyFactory,
返回一個Invoker,Invoker則是個可執行核心實體,Invoker的invoke方法通過反射執行service方法。
4. 服務注冊層(Registry):
封裝服務地址的注冊和發現,以服務URL為中心,基于zk。
5. 集群層(Cluster):
封裝多個提供者的路由及負載均衡,并橋接注冊中心,以Invoker為中心,擴展接口為Cluster、Directory、Router和LoadBalance。
將多個服務提供方組合為一個服務提供方,實現對服務消費方來透明,只需要與一個服務提供方進行交互。
6. 監控層(Monitor):
RPC調用次數和調用時間監控,以Statistics為中心,擴展接口為MonitorFactory、Monitor和MonitorService。
7. 遠程調用層(Protocol):
封裝RPC調用,provider通過export方法進行暴露服務/consumer通過refer方法調用服務。而Protocol依賴的是Invoker。通過上面說的Proxy獲得的Invoker,包裝成Exporter。
8.信息交換層(Exchange):
該層封裝了請求響應模型,將同步轉為異步,信息交換層依賴Exporter,最終將通過網絡傳輸層接收調用請求RequestFuture和ResponseFuture。
9.網絡傳輸層(Transport):
抽象mina和netty為統一接口,以Message為中心,擴展接口為Channel、Transporter、Client、Server和Codec。
10.數據序列化層:
該層無需多言,將數據序列化反序列化。
三、dubbo發布過程
發布過程的一些動作
暴露本地服務
暴露遠程服務
啟動netty
連接zookeeper
到zookeeper注冊
監聽zookeeper
流程圖:
dubbo發布服務涉及到的相關類。
上圖展示了部分服務發布過程中需要使用到的類和接口,其中:
spring適配涉及到的類:DubboNamespaceHandler、DubboBeanDefinitionParser、ServiceBean;
配置信息存儲:ServicdConfig、RegistryConfig、MonitorConfig、ProtocolConfig、ProviderConfig等;
應用協議:Protocol、DubboProtocol、HessianProtocol、ThriftProtocol、RmiProtocol、AbstractProxyProtocol、AbstractProtocol等;
Server相關:Exchanger、HeaderExchanger、ExchangeServer、HeaderExchangeServer、Transporters、Transporter、NettyTransporter、NettyServer等;
- 通過demo配置文件查看
<!--定義了提供方應用信息,用于計算依賴關系;在 dubbo-admin 或 dubbo-monitor 會顯示這個名字,方便辨識-->
<dubbo:application name="demotest-provider" owner="programmer" organization="dubbox"/>
<!--使用 zookeeper 注冊中心暴露服務,注意要先開啟 zookeeper-->
<dubbo:registry address="zookeeper://localhost:2181"/>
<!-- 用dubbo協議在20880端口暴露服務 -->
<dubbo:protocol name="dubbo" port="20880" />
<!--使用 dubbo 協議實現定義好的 api.PermissionService 接口-->
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" protocol="dubbo" />
<!--具體實現該接口的 bean-->
<bean id="demoService" class="com.alibaba.dubbo.demo.impl.DemoServiceImpl"/>
是通過dubbo的schema service進行注入的,找到DubboNameSpaceHandler
,Dubbo命名空間處理器,找到<dubbo:service>
標簽解析行:
此時,進入DubboNameSpaceHandler
類,初始化配置文件,spring容器通過DubboBeanDefinitionParser
類的parse方法來解xml文件中的標簽,生成ServiceConfig等配置對象;
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}
public void init() {
//<dubbo:application>標簽解析行
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
//<dubbo:registry>標簽解析行,本例中使用 zookeeper 注冊中心暴露服務
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
//<dubbo:protocol>標簽解析行,例如,demo中用dubbo協議在20880端口暴露服務
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
//<dubbo:service>標簽解析行
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));
}
}
從上述代碼中可以看到 registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
會把<dubbo:service>
標簽會被解析成 ServiceBean
。ServiceBean
實現了 InitializingBean
,在類加載完成之后會調用afterPropertiesSet()
方法。在afterPropertiesSet()
方法中,依次解析以下標簽信息:
<dubbo:provider>
<dubbo:application>
<dubbo:module>
<dubbo:registry>
<dubbo:monitor>
<dubbo:protocol>
在 Spring 容器初始化的時候會調用 onApplicationEvent
方法,即spring的事件機制(event),監聽Spring 容器初始化
。ServiceBean
重寫了 onApplicationEvent
方法,實現了服務暴露的功能。
dubbo暴露服務有兩種情況,一種是設置了延遲暴露(比如delay=”5000”),另外一種是沒有設置延遲暴露或者延遲設置為-1(delay=”-1”):
1、設置了延遲暴露: dubbo在Spring實例化bean(initializeBean
)的時候會對實現了InitializingBean
的類進行回調,回調方法是afterPropertySet()
,如果設置了延遲暴露,dubbo在這個方法中進行服務的發布。
2、沒有設置延遲或者延遲為-1: dubbo會在Spring實例化完bean之后,在刷新容器最后一步發布ContextRefreshEvent
事件的時候,通知實現了ApplicationListener
的類進行回調onApplicationEvent
,dubbo會在這個方法中發布服務。
ServiceConfig.export()
的流程圖如下:
<1> export()方法
export()
進行服務發布,是發布核心。由上面代碼可知,如果設置了 delay 參數,Dubbo 的處理方式是啟動一個守護線程在 sleep 指定時間后再
doExport()
。從export代碼中可以看到,export先判斷是否需要延遲暴露,如果設置了延遲delay!=null
,則通過一個后臺線程Thread.sleep(delay)
延遲調用doExport()
方法;反之如果不存愛delay,直接調用doExport()
方法。
<2> doExport()
protected synchronized void doExport() {
if (unexported) {
throw new IllegalStateException("Already unexported!");
}
if (exported) {
return;
}
exported = true;
if (interfaceName == null || interfaceName.length() == 0) {
throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
}
checkDefault();
if (provider != null) {
if (application == null) {
application = provider.getApplication();
}
if (module == null) {
module = provider.getModule();
}
if (registries == null) {
registries = provider.getRegistries();
}
if (monitor == null) {
monitor = provider.getMonitor();
}
if (protocols == null) {
protocols = provider.getProtocols();
}
}
if (module != null) {
if (registries == null) {
registries = module.getRegistries();
}
if (monitor == null) {
monitor = module.getMonitor();
}
}
if (application != null) {
if (registries == null) {
registries = application.getRegistries();
}
if (monitor == null) {
monitor = application.getMonitor();
}
}
if (ref instanceof GenericService) {
interfaceClass = GenericService.class;
generic = true;
} else {
try {
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
.getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
checkInterfaceAndMethods(interfaceClass, methods);
checkRef();
generic = false;
}
if(local !=null){
if(local=="true"){
local=interfaceName+"Local";
}
Class<?> localClass;
try {
localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
if(!interfaceClass.isAssignableFrom(localClass)){
throw new IllegalStateException("The local implemention class " + localClass.getName() + " not implement interface " + interfaceName);
}
}
if(stub !=null){
if(stub=="true"){
stub=interfaceName+"Stub";
}
Class<?> stubClass;
try {
stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
if(!interfaceClass.isAssignableFrom(stubClass)){
throw new IllegalStateException("The stub implemention class " + stubClass.getName() + " not implement interface " + interfaceName);
}
}
checkApplication();
checkRegistry();
checkProtocol();
appendProperties(this);
checkStubAndMock(interfaceClass);
if (path == null || path.length() == 0) {
path = interfaceName;
}
doExportUrls();
}
?? doExport
方法先執行一系列的檢查方法,然后調用doExportUrls
方法。檢查方法會檢測dubbo的配置是否在Spring配置文件中聲明,沒有的話讀取properties文件初始化。
??
doExportUrls
方法先調用loadRegistries
加載注冊信息,組裝注冊中心url信息,如源碼中config.properties中讀取鏈接信息,組裝Provider注冊鏈接串; 然后遍歷調用doExportUrlsFor1Protocol
方法。 里面有個for循環,代表一個服務可以有多個通信協議,例如tcp、http、dubbo等協議,dubbo支持多種協議,默認使用的是dubbo協議。
??debug圖中可以看到,
<dubbo:protocol>
的name="dubbo"
為dubbo協議。可以在配置文件中進行配置協議:
??
<dubbo:protocol name="dubbo" port="20880" />
其他幾種協議對比:
1 dubbo協議
連接個數:單連接
連接方式:長連接
傳輸協議:TCP
傳輸方式:NIO異步傳輸
序列化:Hessian二進制序列化
適用范圍:傳入傳出參數數據包較小(建議小于100K),消費者比提供者個數多,單一消費者無法壓滿提供者,盡量不要用dubbo協議傳輸大文件或超大字符串。
適用場景:常規遠程服務方法調用
注:
1)、dubbo默認采用dubbo協議,dubbo協議采用單一長連接和NIO異步通訊,適合于小數據量大 并發的服務調用,以及服務消費者機器數遠大于服務提供者機器數的情況
2)、他不適合傳送大數據量的服務,比如傳文件,傳視頻等,除非請求量很低。
3)、為防止被大量連接撐掛,可在服務提供方限制大接收連接數,以實現服務提供方自我保護
<dubbo:protocol name="dubbo" accepts="1000" />
2 rmi協議
Java標準的遠程調用協議。
連接個數:多連接
連接方式:短連接
傳輸協議:TCP
傳輸方式:同步傳輸
序列化:Java標準二進制序列化
適用范圍:傳入傳出參數數據包大小混合,消費者與提供者個數差不多,可傳文件。
適用場景:常規遠程服務方法調用,與原生RMI服務互操作
1)、RMI協議采用JDK標準的java.rmi.*實現,采用阻塞式短連接和JDK標準序列化方式
3 hessian協議
基于Hessian的遠程調用協議。
連接個數:多連接
連接方式:短連接
傳輸協議:HTTP
傳輸方式:同步傳輸
序列化: 表單序列化
適用范圍:傳入傳出參數數據包大小混合,提供者比消費者個數多,可用瀏覽器查看,可用表單或URL傳入參數,暫不支持傳文件。
適用場景:需同時給應用程序和瀏覽器JS使用的服務。
約束
1)、參數及返回值需實現Serializable接口
2)、參數及返回值不能自定義實現List, Map, Number, Date, Calendar等接口,只能用JDK自帶的實現,因為hessian會做特殊處理,自定義實現類中的屬性值都會丟失。
4 http協議
基于http表單的遠程調用協議。參見:[HTTP協議使用說明]
連接個數:多連接
連接方式:短連接
傳輸協議:HTTP
傳輸方式:同步傳輸
序列化:表單序列化
適用范圍:傳入傳出參數數據包大小混合,提供者比消費者個數多,可用瀏覽器查看,可用表單或URL傳入參數,暫不支持傳文件。
適用場景:需同時給應用程序和瀏覽器JS使用的服務。
5 webservice協議
基于WebService的遠程調用協議。
連接個數:多連接
連接方式:短連接
傳輸協議:HTTP
傳輸方式:同步傳輸
序列化:SOAP文本序列化
適用場景:系統集成,跨語言調用
6 thrift協議
當前 dubbo 支持的 thrift 協議是對 thrift 原生協議的擴展,在原生協議的基礎上添加了一些額外的頭信息,比如service name,magic number等。使用dubbo thrift協議同樣需要使用thrift的idl compiler編譯生成相應的java代碼,后續版本中會在這方面做一些增強 。
注意:
Thrift不支持null值,不能在協議中傳null
7 memcached協議
可以通過腳本或監控中心手工填寫表單注冊memcached服務的地址。
8 redis協議
可以通過腳本或監控中心手工填寫表單注冊redis服務的地址。
??繼續debug代碼,進入方法doExportUrlsFor1Protocol
:
doExportUrlsFor1Protocol()
先將Bean屬性轉換成URL對象,然后根據不同協議將服務已URL形式發布。如果scope
配置為none
則不暴露,如果服務未配置成remote
,則本地暴露exportLocal
,如果未配置成local
,則遠程暴露。
??方法第一部分:
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
//獲取是哪種協議,如果未配置,默認為dubbo
String name = protocolConfig.getName();
if (name == null || name.length() == 0) {
name = "dubbo";
}
//獲取host
String host = protocolConfig.getHost(); //從配置中獲取
if (provider != null && (host == null || host.length() == 0)) {
host = provider.getHost();
}
boolean anyhost = false;
if (NetUtils.isInvalidLocalHost(host)) { //為了確保獲得的主機有效,還有相應的驗證:
anyhost = true;
try {
host = InetAddress.getLocalHost().getHostAddress();//返回本機的ip地址
} catch (UnknownHostException e) {
logger.warn(e.getMessage(), e);
}
if (NetUtils.isInvalidLocalHost(host)) {
if (registryURLs != null && registryURLs.size() > 0) {
for (URL registryURL : registryURLs) {
try {
Socket socket = new Socket();
try {
//通過獲取注冊中心地址和端口號,創建socket連接地址
SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
socket.connect(addr, 1000);
host = socket.getLocalAddress().getHostAddress();
break;
} finally {
try {
socket.close();
} catch (Throwable e) {}
}
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
}
}
if (NetUtils.isInvalidLocalHost(host)) {
host = NetUtils.getLocalHost();//遍歷本地網卡,返回第一個合理的IP
}
}
}
//獲取各個協義需要暴露的端口, 按照這樣的優先級去獲取 :
//Protocol的實現類的默認端口 ——> Protocol的配置端口 ——> 隨機端口
Integer port = protocolConfig.getPort();
if (provider != null && (port == null || port == 0)) {
port = provider.getPort();
}
final int defaultPort = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(name).getDefaultPort();
if (port == null || port == 0) {
port = defaultPort;
}
if (port == null || port <= 0) {
port = getRandomPort(name);
if (port == null || port < 0) {
port = NetUtils.getAvailablePort(defaultPort);
putRandomPort(name, port);
}
logger.warn("Use random available port(" + port + ") for protocol " + name);
}
??第二部分:收集各類參數,放入map中,在為服務暴露做參數收集準備工作
// paramsMap,存放所有配置參數,下面生成url用。
Map<String, String> map = new HashMap<String, String>();
if (anyhost) {
map.put(Constants.ANYHOST_KEY, "true");
}
map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, provider, Constants.DEFAULT_KEY);
appendParameters(map, protocolConfig);
appendParameters(map, this);
// method子標簽配置規則解析,retry次數,參數等。沒有使用過,不做解釋
if (methods != null && methods.size() > 0) {//服務接口的方法
for (MethodConfig method : methods) {
appendParameters(map, method, method.getName());
String retryKey = method.getName() + ".retry"; //重試次數
if (map.containsKey(retryKey)) {
String retryValue = map.remove(retryKey);
if ("false".equals(retryValue)) {
map.put(method.getName() + ".retries", "0");
}
}
List<ArgumentConfig> arguments = method.getArguments();
if (arguments != null && arguments.size() > 0) {
for (ArgumentConfig argument : arguments) {
//類型自動轉換.
if(argument.getType() != null && argument.getType().length() >0){
Method[] methods = interfaceClass.getMethods();
//遍歷所有方法
if(methods != null && methods.length > 0){
for (int i = 0; i < methods.length; i++) {
String methodName = methods[i].getName();
//匹配方法名稱,獲取方法簽名.
if(methodName.equals(method.getName())){
Class<?>[] argtypes = methods[i].getParameterTypes();
//一個方法中單個callback
if (argument.getIndex() != -1 ){
if (argtypes[argument.getIndex()].getName().equals(argument.getType())){
appendParameters(map, argument, method.getName() + "." + argument.getIndex());
}else {
throw new IllegalArgumentException("argument config error : the index attribute and type attirbute not match :index :"+argument.getIndex() + ", type:" + argument.getType());
}
} else {
//一個方法中多個callback
for (int j = 0 ;j<argtypes.length ;j++) {
Class<?> argclazz = argtypes[j];
if (argclazz.getName().equals(argument.getType())){
appendParameters(map, argument, method.getName() + "." + j);
if (argument.getIndex() != -1 && argument.getIndex() != j){
throw new IllegalArgumentException("argument config error : the index attribute and type attirbute not match :index :"+argument.getIndex() + ", type:" + argument.getType());
}
}
}
}
}
}
}
}else if(argument.getIndex() != -1){
appendParameters(map, argument, method.getName() + "." + argument.getIndex());
}else {
throw new IllegalArgumentException("argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
}
}
}
} // end of methods for
}
if (generic) {
map.put("generic", String.valueOf(true));
map.put("methods", Constants.ANY_VALUE);
} else {
String revision = Version.getVersion(interfaceClass, version);
if (revision != null && revision.length() > 0) {
map.put("revision", revision);
}
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
if(methods.length == 0) {
logger.warn("NO method found in service interface " + interfaceClass.getName());
map.put("methods", Constants.ANY_VALUE);
}
else {
map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
}
}
if (! ConfigUtils.isEmpty(token)) {
if (ConfigUtils.isDefault(token)) {
map.put("token", UUID.randomUUID().toString());
} else {
map.put("token", token);
}
}
if ("injvm".equals(protocolConfig.getName())) {
protocolConfig.setRegister(false);
map.put("notify", "false");
}
??第三部分:
??這段主要是拼接URL,Dubbo框架是以URL為總線的模式,即運行過程中所有的狀態數據信息都可以通過URL來獲取,比如當前系統采用什么序列化,采用什么通信,采用什么負載均衡等信息,都是通過URL的參數來呈現的,所以在框架運行過程中,運行到某個階段需要相應的數據,都可以通過對應的Key
從URL的參數列表中獲取。
本例url: dubbo://xxx.xxx.xxx.xxx:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demotest-provider&dubbo=2.5.3&interface=com.alibaba.dubbo.demo.DemoService&methods=getPermissions&organization=dubbox&owner=programmer&pid=35270&side=provider×tamp=1539154732792
// 導出服務
String contextPath = protocolConfig.getContextpath();//獲取協議的上下文路徑
if ((contextPath == null || contextPath.length() == 0) && provider != null) {
contextPath = provider.getContextpath();
}
// 根據參數創建url對象
URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
// 如果url使用的協議存在擴展,調用對應的擴展來修改原url。目前擴展有override,absent
if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.hasExtension(url.getProtocol())) {
url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
}
//根據scope的配置決定是作本地暴露還是遠程暴露,
//做服務暴露從結果上看就是產生了一個特定服務的 Exporter 類,
//并將其存儲在對應的ServiceBean實例的 exporters屬性中。
String scope = url.getParameter(Constants.SCOPE_KEY);
//配置為none不暴露
if (! Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
//配置不是remote的情況下做本地暴露 (配置為remote,則表示只暴露遠程服務)
if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
exportLocal(url);
}
//如果配置不是local則暴露為遠程服務.(配置為local,則表示只暴露遠程服務)
if (! Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope) ){
if (logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
//只有遠程暴露才需要用到注冊中心url
if (registryURLs != null && registryURLs.size() > 0
&& url.getParameter("register", true)) {
for (URL registryURL : registryURLs) {
url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));
//獲取監控中心的URL
URL monitorUrl = loadMonitor(registryURL);
if (monitorUrl != null) {
url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
}
if (logger.isInfoEnabled()) {
logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
}
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
Exporter<?> exporter = protocol.export(invoker);
exporters.add(exporter);
}
} else {
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
Exporter<?> exporter = protocol.export(invoker);
exporters.add(exporter);
}
}
}
this.urls.add(url);
本地暴露
此處demo,為本地暴露,此時,調用exportLocal(url);
生成exporter
:
private void exportLocal(URL url) {
if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
URL local = URL.valueOf(url.toFullString())
.setProtocol(Constants.LOCAL_PROTOCOL)
.setHost(NetUtils.LOCALHOST)
.setPort(0);
//這里的protocol是Protocol$Adpative的實例(spi機制)
//proxyFactory是ProxyFactory$Adpative實例(spi機制)
Exporter<?> exporter = protocol.export(
proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
exporters.add(exporter);
logger.info("Export dubbo service " + interfaceClass.getName() +" to local registry");
}
}
上述exportLocal
方法參數url,為:
dubbo://xxx.xxx.xxx.xxx:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demotest-provider&dubbo=2.5.3&interface=com.alibaba.dubbo.demo.DemoService&methods=getPermissions&organization=dubbox&owner=programmer&pid=78965&side=provider×tamp=1539163125437
上述方法中URL local
重新構造url,本地暴露的url是以injvm
開頭的,host修改為本地,端口更改為0
,這與遠程發布時不同的,url更換為一下內容:
injvm://127.0.0.1/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demotest-provider&dubbo=2.5.3&interface=com.alibaba.dubbo.demo.DemoService&methods=getPermissions&organization=dubbox&owner=programmer&pid=78965&side=provider×tamp=1539163125437
這里的proxyFactory.getInvoker
使用的是JavassistProxyFactory.getInvoker
方法,
對服務接口進行透明代理,生成服務的客戶端和服務端,使服務的遠程調用就像在本地調用一樣,默認使用JavassistProxyFactory
,返回一個Invoker
,Invoker
則是個可執行核心實體,Invoker
的invoke
方法通過反射執行service方法。
public class JavassistProxyFactory extends AbstractProxyFactory {
@SuppressWarnings("unchecked")
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
//proxy 是服務實現類,type是服務接口
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
////利用Wrapper類通過服務接口生成對應的代理類。
//根據傳入的 proxy對象的類信息創建對它的包裝對象Wrapper, Wrapper類不能正確處理帶$的類名
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
return new AbstractProxyInvoker<T>(proxy, type, url) {
//實現抽象類AbstractProxyInvoker抽象方法doInvoke,
//并調用(proxy, type, url)構造函數實例化匿名類
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}
}
最后返回invoker后,使用protocol.export
進行發布:
Protocol
的實現是RegistryProtocol
類,實現了export
方法
類及方法的概念作用:
1、proxyFactory:
就是為了獲取一個接口的代理類,例如獲取一個遠程接口的代理。
它有2個方法,代表2個作用
a、getInvoker:
針對server端,將服務對象,例如DemoServiceImpl
包裝成一個Wrapper對象。
b、getProxy:
針對client端,創建接口的代理對象,例如DemoService
的接口。
2、makeWrapper
:它類似spring的BeanWrapper
,它就是包裝了一個接口或一個類,可以通過Wrapper對實例對象進行賦值取值以及指定方法的調用。
3、Invoker:
它是一個可執行的對象,能夠根據方法名稱、參數得到相應的執行結果。它里面有一個很重要的方法Result invoke(Invocation invocation)
,Invocation
是包含了需要執行的方法和參數的重要信息,目前它只有2個實現類RpcInvocation、 MockInvocation
它有3種類型的Invoker
1、本地執行類的Invoker
2、遠程通信類的Invoker
3、多個遠程通信執行類的Invoker聚合成集群版的Invoker
4、Protocol:
1)export
暴露遠程服務(用于服務端),就是將proxyFactory.getInvoker
創建的代理類invoker
對象,通過協議暴露給外部。
2)refer:
引用遠程服務(用于客戶端)
5、Exporter:
維護invoker的生命周期
6、exchanger:
信息交換層,封裝請求響應模式同步轉異步
7、transporter:
網絡傳輸層,用來抽象Netty(dubbo默認)或者Mina的統一接口
暴露本地服務與暴露遠程服務的區別
a、暴露本地服務:
指暴露在同一個JVM里面,不用通過zk來進行遠程通信,例如在同一個服務里面(tomcat),自己調用自己的接口,就無需進行網絡IP連接通信。
b、暴露遠程服務:
指暴露給遠程客戶端的IP和端口號,通過網絡來實現通信。
遠程暴露
在doExportUrlsFor1Protocol
方法中if (! Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope) )
條件,如果配置不是local則暴露為遠程服務.(配置為local,則表示只暴露遠程服務)
public static final String SCOPE_LOCAL = "local";
未完待續
歡迎關注公眾號