Dubbo本地調用、參數回調、事件通知(八)

1、參考

本地調用:http://dubbo.apache.org/en-us/docs/user/demos/local-call.html

參數回調:http://dubbo.apache.org/en-us/docs/user/demos/callback-parameter.html

事件通知:http://dubbo.apache.org/zh-cn/docs/user/demos/events-notify.html

2、本地調用

如果Consumer與Provider部署在一臺主機上,共用一個JVM,那麼當Consumer調用Provider時就沒有必要經過網絡棧,直接調用即可,不需要通過網絡,便是Dubbo的其它Filtter正常生效。

對於Provider端而言,從2.2.0開始默認自動支持本地調用,無需任務特殊配置。值得注意的是,如果只打算讓Provider提供的服務支持本地調用,可以把服務的protocol設置成injvm一個協議。這樣,被配置的服務將不用支持遠程調用,服務在運行時也不會開端口。

對於Consumer端而言,當調用某個服務時,如果它在本地已經暴露,則默認直接調用,這個是從2.2.0開始後的默認行爲。如果想關掉此行爲,則如下配置:

<dubbo:reference ... scope="remote" />

有點雞肋的功能,爲服務本身就是要將功能打散然後分佈到系統中的多個節點上,爲什麼會分佈在一個節點上呢?另個就算不走injvm,走其它協議,開銷能有多大呢?不能指着把服務部署在相同節點上節省網絡流量吧。

3、參數回調

拋開Dubbo,在普通的開發中,當調用一個方法時,我們可能在參數中給方法傳遞一個引用,然後方法在執行的過程中調用引中的一個方法,這就是普通的回調。

Dubbo也有類似功能,Dubbo 將基於長連接生成反向代理,Consumer端臨時允當Provider端,Provider端調用的接口,真正的邏輯將在Consumer端執行。

服務端接口示例,CallbackService.java:

package com.callback;
 
public interface CallbackService {
    void addListener(String key, CallbackListener listener);
}

後邊一個參數是接口,下邊是參數接口定義CallbackListener.java文件:

package com.callback;
 
public interface CallbackListener {
    void changed(String msg);
}

這個接口要在Consumer端實現。

服務端接口實現:

package com.callback.impl;
 
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
 
import com.callback.CallbackListener;
import com.callback.CallbackService;
 
public class CallbackServiceImpl implements CallbackService {
     
    private final Map<String, CallbackListener> listeners = new ConcurrentHashMap<String, CallbackListener>();
  
    public CallbackServiceImpl() {
        Thread t = new Thread(new Runnable() {
            public void run() {
                while(true) {
                    try {
                        for(Map.Entry<String, CallbackListener> entry : listeners.entrySet()){
                           try {
                               entry.getValue().changed(getChanged(entry.getKey()));
                           } catch (Throwable t) {
                               listeners.remove(entry.getKey());
                           }
                        }
                        Thread.sleep(5000); // 定時觸發變更通知
                    } catch (Throwable t) { // 防禦容錯
                        t.printStackTrace();
                    }
                }
            }
        });
        t.setDaemon(true);
        t.start();
    }
  
    public void addListener(String key, CallbackListener listener) {
        listeners.put(key, listener);
        listener.changed(getChanged(key)); // 發送變更通知
    }
     
    private String getChanged(String key) {
        return "Changed: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
    }
}

在上邊的實現中,在構造方法中啓動了一個線程,裏邊是一個無限循環,按固定間隔調用從Consumer端註冊進來的回調。

服務端配置:

<bean id="callbackService" class="com.callback.impl.CallbackServiceImpl" />
<dubbo:service interface="com.callback.CallbackService" ref="callbackService" connections="1" callbacks="1000">
    <dubbo:method name="addListener">
        <dubbo:argument index="1" callback="true" />
        <!--也可以通過指定類型的方式-->
        <!--<dubbo:argument type="com.demo.CallbackListener" callback="true" />-->
    </dubbo:method>
</dubbo:service>

需要明確指定那個參數是用來回調的。

Consumer端配置,一切如常:

<dubbo:reference id="callbackService" interface="com.callback.CallbackService" />

Consumer端代碼:

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:consumer.xml");
context.start();
 
CallbackService callbackService = (CallbackService) context.getBean("callbackService");
 
callbackService.addListener("foo.bar", new CallbackListener(){
    public void changed(String msg) {
        System.out.println("callback1:" + msg);
    }
});

需要爲回調的參數指定一個實例。

4、事件通知

對於一次遠程方法調用,有oninvokeonreturnonthrow三個事件,分別爲調用之前、返回之後,拋出異常三個事件。在Consumer端,可以爲三個事件指定事件處理方法。

服務端接口:

interface IDemoService {
    public Person get(int id);
}

服務端實現:

class NormalDemoService implements IDemoService {
    public Person get(int id) {
        return new Person(id, "charles`son", 4);
    }
}

服務端配置:

<dubbo:application name="rpc-callback-demo" />
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<bean id="demoService" class="org.apache.dubbo.callback.implicit.NormalDemoService" />
<dubbo:service interface="org.apache.dubbo.callback.implicit.IDemoService" ref="demoService" version="1.0.0" group="cn"/>

沒有什麼特別的地方。

Consumer端的處理事件通知的接口:

interface Notify {
    public void onreturn(Person msg, Integer id);
    public void onthrow(Throwable ex, Integer id);
}

方法中,第一個參數應該是方法的返回值,其它是方法的參數。

然後Consumer端實現這個接口:

class NotifyImpl implements Notify {
    public Map<Integer, Person>    ret    = new HashMap<Integer, Person>();
    public Map<Integer, Throwable> errors = new HashMap<Integer, Throwable>();
    
    public void onreturn(Person msg, Integer id) {
        System.out.println("onreturn:" + msg);
        ret.put(id, msg);
    }
    
    public void onthrow(Throwable ex, Integer id) {
        errors.put(id, ex);
    }
}

服務端配置:

<bean id ="demoCallback" class = "org.apache.dubbo.callback.implicit.NofifyImpl" />
<dubbo:reference id="demoService" interface="org.apache.dubbo.callback.implicit.IDemoService" version="1.0.0" group="cn" >
      <dubbo:method name="get" async="true" onreturn = "demoCallback.onreturn" onthrow="demoCallback.onthrow" />
</dubbo:reference>

配置中async與onreturn、onthrow是一對,組合不同,功能也不同:

  • 異步回調模式:async=true onreturn="xxx"
  • 同步回調模式:async=false onreturn="xxx"
  • 異步無回調 :async=true
  • 同步無回調 :async=false

Consumer端測試代碼:

IDemoService demoService = (IDemoService) context.getBean("demoService");
NofifyImpl notify = (NofifyImpl) context.getBean("demoCallback");
int requestId = 2;
Person ret = demoService.get(requestId);
Assert.assertEquals(null, ret);
//for Test:只是用來說明callback正常被調用,業務具體實現自行決定.
for (int i = 0; i < 10; i++) {
    if (!notify.ret.containsKey(requestId)) {
        Thread.sleep(200);
    } else {
        break;
    }
}
Assert.assertEquals(requestId, notify.ret.get(requestId).getId());

當前線程正在執行for循環,事件處理器中的代碼應該是在Consumer端的IOThread中執行,可能涉及到線程同步的問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章