1. 程式人生 > >Dubbo(一):Dubbo執行原理

Dubbo(一):Dubbo執行原理

前言:

  在開始入門Javaweb時,學的基本都是MVC開發模式,一個專案基本上就是model,view,controller三層。但是隨著系統的服務逐漸加多,SOA模式更加適合目前專案開發。而SOA模式在Java開發過程中基本上是Dubbo和SpringCloud的天下。所以今天來看看Dubbo中的執行原理。

一、SOA模式

  首先簡單介紹一下SOA模式,這對我們後面理解Dubbo很有幫助。

  SOA模式是什麼?

  SQA(Service-Oriented Architecture)即面向服務架構,它將應用程式的不同功能單元(這裡就理解為服務)進行了拆分。在這種架構下專案不會直接和資料庫進行互動,而是通過呼叫不同服務的介面來訪問資料庫。

  模式優點在哪?

  這樣最直接的好處就是解決程式碼冗餘,如果多個專案同時都要訪問資料庫同一張表。比如使用者表的訪問。我們可以直接呼叫使用者服務裡面的介面進行開發,而不需要每個專案都去寫一遍使用者表的增刪改查。除了這個,SOA能帶給我們的好處就是能夠讓開發者以更迅速、更可靠、更具重用性架構整個業務系統。較之以往MVC開發模式,以SOA架構的系統能夠更加從容地面對業務的急劇變化。

  SOA示意圖:

二、Dubbo基本組成

  聊完了SOA,現在來看看Dubbo內部架構,相信大家多多少少看過下面這幅圖:

  很多人會漏看上面線的示意圖,下面解釋一下:

  紫色虛線:在Dubbo啟動時完成的功能  藍青色的線:都是程式執行過程中執行的功能,虛線是非同步操作,實線是同步操作

  Provider :提供者,服務釋出方。如果是採用SOA開發的模式,這個就是和資料庫互動的介面,也就是service主要放在生產者這邊

  Consumer:消費者,呼叫服務方。面向前端的Controller主要是在這邊,可以遠端呼叫生產者中的方法,生產者發生變化時也會實時更新消費者的呼叫列表。具體的看下面介紹

  Container:Dubbo容器,依賴於Spring容器。這裡比較注意的就是Dubbo是依賴與Spring容器的。所以必須要和Spring配合著使用

  Registry:註冊中心.當Container啟動時把所有可以提供的服務列表上Registry中進行註冊。作用:告訴Consumer提供了什麼服務和服務方在哪裡.

  Monitor:監聽器

三、Dubbo執行原理

  就著上面的架構圖來看看Dubbo的執行原理:

  0.Start: 啟動容器,相當於在啟動Dubbo的Provider,並且會建立對應的目錄結構,例如程式碼中的共用介面名為com.learnDubbo.demo.DemoService,就會建立 /dubbo/com.learnDubbo.demo.DemoService目錄,然後在建立providers目錄,再在providers目錄下寫入自己的 URL 地址。

  1.Register:啟動後會去註冊中心進行註冊,註冊所有可以提供的服務列表。即訂閱/dubbo/com.learnDubbo.demo.DemoService 目錄下的所有提供者和消費者 URL 地址。

  2.Subscribe:Consumer在啟動時,不僅僅會註冊自身到 …/consumers/目錄下,同時還會訂閱…/providers目錄,實時獲取其上Provider的URL字串資訊。當服務消費者啟動時:會在/dubbo/com.learnDubbo.demo.DemoService目錄建立/consumers目錄,並在/consumers目錄寫入自己的 URL 地址。

  3.notify:當Provider有修改後,註冊中心會把訊息推送給Consummer。也就是註冊中心會對Provider進行觀察,這裡就是使用設計模式中的觀察者模式。以Zookeeper註冊中心為例,Dubbo中有ZookeeperRegistry中的doSubscribe方法也就是進行生產者訂閱和監聽。下面分析一下原始碼,看看訂閱過程

@Override
    protected void doSubscribe(final URL url, final NotifyListener listener) {
        try {
            if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {//根據URL得到服務介面為*,也就是所有
                String root = toRootPath();
                ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);//拿取URL下的監聽器
                if (listeners == null) {//不存在則進行建立
                    zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
                    listeners = zkListeners.get(url);
                }
                ChildListener zkListener = listeners.get(listener);//得到子目錄的監聽器
                if (zkListener == null) {//無法得到子目錄監聽器,則會新建一個。
                    listeners.putIfAbsent(listener, new ChildListener() {
                        @Override
                        public void childChanged(String parentPath, List<String> currentChilds) {
                            for (String child : currentChilds) {
                                child = URL.decode(child);
                                if (!anyServices.contains(child)) {
                                    anyServices.add(child);
                                    //如果consumer的interface為*,會訂閱每一個url,會觸發另一個分支的邏輯
                                    //這裡是用來對/dubbo下面提供者新增時的回撥,相當於增量
                                    subscribe(url.setPath(child).addParameters(Constants.INTERFACE_KEY, child,
                                            Constants.CHECK_KEY, String.valueOf(false)), listener);
                                }
                            }
                        }
                    });
                    zkListener = listeners.get(listener);
                }
                zkClient.create(root, false);
                //新增監聽器會返回子節點集合
                List<String> services = zkClient.addChildListener(root, zkListener);//訂閱root目錄下的子元素,比如:/dubbo/com.learnDubbo.demo.DemoService/providers
                if (services != null && !services.isEmpty()) {
                    for (String service : services) {
                        service = URL.decode(service);
                        anyServices.add(service);
                        subscribe(url.setPath(service).addParameters(Constants.INTERFACE_KEY, service,
                                Constants.CHECK_KEY, String.valueOf(false)), listener);
                    }
                }
            } else {
                //這邊是針對明確interface的訂閱邏輯
                List<URL> urls = new ArrayList<URL>();
                //針對每種category路徑進行監聽
                for (String path : toCategoriesPath(url)) {
                    ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
                    if (listeners == null) {
                        zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
                        listeners = zkListeners.get(url);
                    }
                    ChildListener zkListener = listeners.get(listener);
                    if (zkListener == null) {
                        //封裝回調邏輯
                        listeners.putIfAbsent(listener, new ChildListener() {
                            @Override
                            public void childChanged(String parentPath, List<String> currentChilds) {
                                ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
                            }
                        });
                        zkListener = listeners.get(listener);
                    }
                    //建立節點
                    zkClient.create(path, false);
                    //增加回調
                    List<String> children = zkClient.addChildListener(path, zkListener);
                    if (children != null) {
                        urls.addAll(toUrlsWithEmpty(url, path, children));
                    }
                }
                //並且會對訂閱的URL下的服務進行監聽,並會實時的更新Consumer中的invoke列表,使得能夠進行呼叫。這個方法不展開講
                notify(url, listener, urls);
            }
        } catch (Throwable e) {
            throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
        }
    }

  4、invoke:根據獲取到的Provider地址,真實呼叫Provider中功能。這裡就是唯一一個同步的方法,因為消費者要得到生產者傳來的資料才能進行下一步操作,但是Dubbo是一個RPC框架,RPC的核心就在於只能知道介面不能知道內部具體實現。所以在Consumer方使用了代理設計模式,建立一個Provider方類的一個代理物件,通過代理物件獲取Provider中真實功能,起到保護Provider真實功能的作用。

  invoke部分原始碼分析

  有興趣的可以看看invoke呼叫過程

  5、Monitor:Consumer和Provider每隔1分鐘向Monitor傳送統計資訊,統計資訊包含,訪問次數,頻率等

四、總結

  這裡只是稍微對Dubbo的原理做了一下分析,想要弄懂Dubbo還需要結合原始碼來看。儘管很多時候我們只是一個使用者,但是能夠理解內部執行原理不但能夠讓我們更好的使用,同時裡面的程式設計思路,設計模式也是我們需要學習的。後面還會做更多關於Dubbo的原理解析。