RPC-dubbo基本使用
22.本地存根
消費者通過建立實現一個服務介面的例項,可以在執行遠端呼叫前拿到遠端呼叫的代理例項,進而可以在遠端呼叫前、後新增一些操作,在出現異常後進行一些容錯處理。
這個使用場景,可以呼叫前作資料引數校驗、做ThreadLocal快取(這個執行緒操作多次呼叫這個服務,而且結果是一樣的,就可以快取起來,第二次就不用再遠端呼叫)、出錯後如何處理等等;其實就是相當於一個AOP一樣的功能
如
服務介面:
public interface User {
String getUserInfoByName(String name);
}
提供者和平常一樣正常實現介面並註冊能力到註冊中心:
消費者建立一個服務介面實現類:
public class UserSub implements User {
private final User user;
/**必須提供一個傳服務介面的構造器,最終傳入的是代理物件,用於呼叫遠端服務*/
public UserSub(User user){
this.user = user;
}
public String getUserInfoByName(String name) {
try {
//遠端呼叫之前
System.out.println("遠端呼叫之前");
//遠端呼叫
String userInfoByName = this.user.getUserInfoByName(name);
//遠端呼叫之後
System.out.println("遠端呼叫之後"+userInfoByName);
return userInfoByName;
}catch (Exception e) {
return null;
}
}
}
配置消費者:(stub指向剛才的類)
<dubbo:reference id="user" interface="api.User" stub="com.ned.consumer.sub.UserSub"/>
使用:和平常一樣
User user = (User) context.getBean("user");
System.out.println(user.getUserInfoByName("啊哈"));
結果:(提供者是直接返回name)
------------
遠端呼叫之前
遠端呼叫之後啊哈
啊哈 (遠端呼叫結果)
------------
21.事件通知
用於消費者reference下的method 屬性<dubbo:method name="addListener" oninvoke="" onthrow="" onreturn="">,
oninvoke:呼叫服務之前;
onreturn:呼叫方法之後;
onthrow:出異常後;
它們的值是spring容器中某個bean的名稱.方法名
如:
<bean id="notifys" class="com.ned.consumer.notify.TestNotify"></bean>
<dubbo:reference interface="api.CallBackService" id="sbs">
<dubbo:method name="addListener" oninvoke="notifys.oninvoke" onthrow="notifys.onthrow" onreturn="notifys..onreturn">
</dubbo:method>
</dubbo:reference>
要注意的是TestNotify的三個方法引數問題
先看 服務介面 :
/**兩個引數*/
public interface CallBackService extends Serializable{
String addListener( String name, CallBackListener callBackListener );
}
public class TestNotify {
/**oninvoke方法的引數必須與服務介面一致,包括順序*/
public void oninvoke(String name, CallBackListener listener){
System.out.println("-------oninvoke-----------");
}
/**onreturn方法的第一個引數是服務介面的返回型別,服務介面的引數要麼全無*/
public void onreturn(String result){
System.out.println("----------onreturn--------");
}
/**onreturn方法的第一個引數是服務介面的返回型別,服務介面的引數要麼全有,但順序也要一致*/
public void onreturn2(String result,String name, CallBackListener listener){
System.out.println("----------onreturn--------");
}
/**onthrow方法的第一個引數是異常型別,服務介面的引數要麼全無*/
public void onthrow(Throwable ex) {
System.out.println("--------onthrow----------");
}
/**onthrow方法的第一個引數是異常型別,服務介面的引數要麼全有,但順序也要一致*/
public void onthrow2(Throwable ex,String name, CallBackListener listener) {
System.out.println("--------onthrow----------");
}
}
值得注意的是:假如配置onthrow 方法,同事也配置了oninvoke或onreturn,如果 oninvoke或onreturn的引數不對(或這兩個方法裡面報異常) ,將會把 oninvoke或onreturn轉為呼叫onthrow,這樣就有可能出現明明是服務呼叫正常卻跑進了onthrow
比如:
/**oninvoke方法的引數必須與服務介面一致,包括順序*/
public void oninvoke(String name, CallBackListener listener){
System.out.println("-------oninvoke-----------"+ 1/0 );
}
結果是:
-------------
回撥:222(服務呼叫前邏輯)
--------onthrow--------- - (服務呼叫前通知,明明是服務正常呼叫,去跑進了onthrow,卻又沒發現程式碼有拋異常)
回撥:CallBackService:CallBackServiceImpl(服務呼叫)
--------onthrow----------(服務呼叫後通知)
-------------
20.引數回撥
定義一些服務介面,引數傳一個回撥監聽器,這樣消費者呼叫服務是,可以在服務端的回撥客服端的程式碼,比如在提供者根據引數,符合條件才執行回撥;
補充:就是提供者某個入參為一個抽象的一個介面,而具體的實現由消費者實現,這樣把消費者寫的程式碼邏可以在服務端執行,也就是實現一種RPC的廣義多型
原理:服務端檢查到某個服務介面的某個方法的某個引數是回撥的,會通過註冊通行,在消費者暴露一個介面給服務端呼叫( 需要在provider中配置回撥引數,這些配置資訊會通過registry傳遞到consumer中 )
例子:
/**正常的一個服務介面,有個回撥引數callBackListener*/
public interface CallBackService extends Serializable{
void addListener(String name, CallBackListener callBackListener);
}
/**回撥引數介面*/
public interface CallBackListener extends Serializable {
void listner(String name);
}
提供者:
public class CallBackServiceImpl implements CallBackService {
public void addListener(String name, CallBackListener callBackListener) {
name += "CallBackServiceImpl";
callBackListener.listner(name);//呼叫回撥
}
}
提供者配置回撥引數(指定哪個方法第幾個引數為callBack=true):
<bean id="callBackServiceImpl" class="com.ned.rpc.CallBackServiceImpl"></bean>
<dubbo:service interface="api.CallBackService" ref="callBackServiceImpl">
<dubbo:method name="addListener">
<dubbo:argument index="1" callback="true"></dubbo:argument>
</dubbo:method>
</dubbo:service>
消費者正常配置:
<dubbo:reference interface="api.CallBackService" id="sbs"></dubbo:reference>
消費服務
CallBackService sbs = (CallBackService) context.getBean("sbs");
CallBackListener listener = new CallBackListener() {
public void listner(String name) {
System.out.println("回撥:"+name); //2
}
};
listener.listner("222") ; //3
sbs.addListener("CallBackService:", listener); //1
結果
---------------
回撥:222
回撥:CallBackService:CallBackServiceImpl
---------------
注意: 只有提供者執行了這個程式碼 callBackListener.listner(name);//呼叫回撥,不管後面有沒有報錯、超時,消費這個的回撥函式一樣被呼叫
如果把提供者改為:
提供者:
public void addListener(String name, CallBackListener callBackListener) {
name += "CallBackServiceImpl";
callBackListener.listner(name);
int a =1/0;
}
消費者不變,結果為:
--------------
回撥:222
回撥:CallBackService:CallBackServiceImpl
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.ned.rpc.CallBackServiceImpl.addListener(CallBackServiceImpl.java:18)
at com.alibaba.dubbo.common.bytecode.Wrapper5.invokeMethod(Wrapper5.java )
at com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory$1.doInvoke(JavassistProxyFactory.java:47)
at com.alibaba.dubbo.rpc.proxy.AbstractProxyInvoker.invoke(AbstractProxyInvoker.java:76)
……
--------------
19.非同步呼叫
在reference獲取其method上使用async=true,當呼叫方法發出去後,立即響應null,通過Future來獲取非同步的響應;
提供者:
public String getUserInfoByName(String name) {
String test = RpcContext.getContext().getAttachment("test");
return userService.getUserInfoByName(test+"getUserInfoByName"+name);
}
public String getUserInfoByName2(String name) {
String test = RpcContext.getContext().getAttachment("test");
return userService.getUserInfoByName(test+"getUserInfoByName2"+name);
}
消費者:
<dubbo:reference id="user2" interface="api.User2">
<dubbo:method name="getUserInfoByName" async="true"></dubbo:method>
<dubbo:method name="getUserInfoByName2" async="true"></dubbo:method>
</dubbo:reference>
呼叫:
User2 user2 = (User2) context.getBean("user2");
RpcContext.getContext().setAttachment("test", "測試Rpcontext");
System.out.println(user2.getUserInfoByName("555")); //1
System.out.println(user2.getUserInfoByName2("555")); //2
Future<User2> future = RpcContext.getContext().getFuture();
System.out.println(future.get());
結果:
----
null
null
nullgetUserInfoByName2555 (這個其實是2註釋的結果,同時RpcContext在第二次調後前被清空,證明如下 )
----
========================================================================
再呼叫:
User2 user2 = (User2) context.getBean("user2");
RpcContext.getContext().setAttachment("test", "測試Rpcontext");
System.out.println(user2.getUserInfoByName("555"));
Future<User2> future = RpcContext.getContext().getFuture();//兩行順序換了
System.out.println(user2.getUserInfoByName2("555"));
System.out.println(future.get());
結果:
----
null
null
測試RpcontextgetUserInfoByName555
----
18.上下文資訊
隱式引數每一次遠端呼叫後都會被清空
17.回聲測試
消費者從容器中獲取服務bean例項強轉為 EchoService這個型別,呼叫其唯一方法Object $echo(Object var1);
使用(通過$Echo方法發出去什麼內容,響應回來什麼內容,這個就可以測試服務提供是否正常):
User3 user = (User3) context.getBean("user");//這個是一個服務例項
EchoService echoService = (EchoService) user;//轉為EchoService型別
Object ok = echoService.$echo("響應我回來就成功了");
System.out.println(ok);
輸出:
----
響應我回來就成功了
----
package com.alibaba.dubbo.rpc.service;
public interface EchoService {
Object $echo(Object var1);
}
16.泛化呼叫
提供者跟其他一樣,不同在於消費者。消費者reference標籤上使用generic="true" 的屬性,而interface的介面在消費者是不存在的。
A.引數為基本簡單型別
消費者配置:
<dubbo:reference id="genericService" interface="com.ned.generic.GenericApi" generic="true"></dubbo:reference>
a.給reference配置id;
b.給reference配置interface,注意這個com.ned.generic.GenericApi在是介面的全稱,但這介面在消費者是不存在的;
c.給reference配置generic為true,標識泛化呼叫
使用:
a.從容器中獲取一個bean,名稱為上面配置的Id屬性,強轉為GenericService
GenericService genericService = (GenericService) context.getBean("genericService");
b.呼叫genericService唯一的一個方法$invoke();
Object testGeneric = genericService.$invoke("testGeneric", new String[]{"java.lang.String"}, new Object[]{"啊哈"});
結果與正常的呼叫一樣
public interface GenericService {
/**
* 泛化呼叫
*
* @param method 方法名,如:findPerson,如果有過載方法,需帶上引數列表,如:findPerson(java.lang.String)
* @param parameterTypes 引數型別
* @param args 引數列表
* @return 返回值
* @throws Throwable 方法丟擲的異常
*/
Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException;
}
B.引數為物件型別
提供者有個方法如下:
person物件
消費者使用:
a.從容器中獲取一個bean,名稱為上面配置的Id屬性,強轉為GenericService
GenericService genericService = (GenericService) context.getBean("genericService");
b.呼叫genericService唯一的一個方法$invoke();
物件型別的引數用Map封裝,如果返回結果也是物件型別,也會被轉成map
Map paramMap = new HashMap();
paramMap.put("class", "com.ned.entity.Person");
paramMap.put("name","啊哈");
paramMap.put("password","123456");
Map resultMap = (Map) genericService.$invoke("findPerson", new String[]{"com.ned.entity.Person"}, new Object[]{paramMap});
System.out.println(resultMap);
服務端泛化的實現
服務介面不在提供者模組(也就是提供者是沒有介面的情況,與泛化呼叫相反)
有個介面在消費者模組中(注意:在提供者模組是沒有這個介面的)
public interface ConsumerGenericApi {
String serviceGenericTest(String name);
}
a.服務端實現GenericService介面:
public class ServiceGenericImpl implements GenericService {
public Object $invoke(String s, String[] strings, Object[] objects) throws GenericException {
return "OK";
}
}
b.配置服務端提供者:
(com.ned.generic.ServiceGenericImpl,就是上面的實現類;interface="com.ned.consumer.generic.ConsumerGenericApi" 這是介面的全稱,但不在提供這個的模組中)
<bean id="serviceGenericImpl" class="com.ned.generic.ServiceGenericImpl" ></bean>
<dubbo:service interface="com.ned.consumer.generic.ConsumerGenericApi" ref="serviceGenericImpl"></dubbo:service>
c.配置消費者:
(與正常的消費者一樣)
<dubbo:reference id="consumerGenericApi" interface="com.ned.consumer.generic.ConsumerGenericApi"></dubbo:reference>
d.消費者從容器中獲取介面例項consumerGenericApi(c步驟中的id),直接呼叫方法:
(也就是介面在消費者,實現在提供者,因此serviceGenericTest方法呼叫,其實走了消費者的$invoke方法)
ConsumerGenericApi consumerGenericApi = (ConsumerGenericApi) context.getBean("consumerGenericApi");
String t = consumerGenericApi.serviceGenericTest("t");
System.out.println(t);
結果:(a步驟中return的:OK)
-----
OK
-----
15.分組聚合( merger="true" 布林型,用在reference 或其的method上,true :把指定的組結果聚合,fasle:處哪個不聚合)
*注意的是,服務的返回型別是有要求的,在com.alibaba.dubbo.rpc.cluster.merger這個包下的所有型別
也就是陣列,list,map,set
14.多版本
消費者與提供者使用相同的version
13. 服務分組(使用場景:消費者的group,可以統一,從而實現開發/測試環境的快速切換呼叫不同的能力如:<dubbo:consumer check="false" timeout="5000" group ="${dubbo.achievement.group}"/>)
service端,同一個介面的多種實現
<dubbo:service group="group1" protocol="dubbo,hessian" interface="api.User" ref="userImpl3" version="1.0.0"></dubbo:service>
<dubbo:service group="group3" protocol="dubbo,hessian" interface="api.User" ref="userImpl" version="1.0.0"></dubbo:service>
消費者
(如果所有提供者都配了group,消費者一定要新增group屬性,可以是具體,可以是*,如果提供者部分配置group,消費者有不配那麼消費者只會使用不配group的提供的能力)
<dubbo:reference group="*" protocol="dubbo" id="user" interface="api.User" timeout="1000" version="1.0.0" loadbalance="leastactive"/>
12.多協議
在reference、service上通過protocol欄位設定對於的protocol的名稱即可(service上可以設定多個<dubbo:service protocol="dubbo,hessian",但reference只能配一個<dubbo:reference protocol="dubbo")
如消費者指定某種協議
提供者提供多種協議(不同的服務提供不同的協議,或一個服務多個協議逗號隔開)
11.hessianx協議(使用短連結,適用於大檔案傳輸,dubbo協議不適應於大檔案傳輸的原因是使用長連線,)
a.新增依賴
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.7</version>
</dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty</artifactId>
<version>6.1.26</version>
</dependency>
b.設定提供者協議
<dubbo:protocol name="hessian" port="8088" server="jetty"/>
c.設定消費者協議(不用指定sever)
<dubbo:protocol name="hessian" port="8088"/>
10. 靜態服務
應用部署後,服務是禁用狀態,需要人工啟動,斷線後,也不會清除,要人工刪除
標籤<dubbo:registetry dynamic="false">
9.只註冊,不訂閱別人服務
a.只是提供服務,不用註冊中心訂閱別的服務(不依賴別的服務);
b.有兩個註冊中心R1與R2,服務S1在R1上註冊了,不能在R2上註冊,但是有應用A要使用服務S1並且應用的註冊A的中心是由R1與R2叢集的,這樣會導致一些請求到R2上會沒找到服務S1;因此應用A在配置註冊中心叢集時就必須對R2進行只註冊(也就是所有依賴的服務都從R1中獲取),但又不能把R2直接去掉,因為應用A要註冊一下服務到R2上給別的應用使用;
8.只訂閱,不註冊自己服務
開發者要依賴使用通過註冊中心上的服務,但是又不想把自己正在開發的服務註冊到註冊中心(原因正在開發的功能未完善,註冊到註冊中心可能會影響別調用未完善的服務)
使用標籤<dubbo:registry register="false"></dubbo:registry> ,在註冊標識reigistry設定為reigster="false" 就不會本服務註冊註冊中心了
7.直連提供者
三種方式:JVM引數、通過JVM引數指定配置檔案、標籤<dubbo:reference >(前面的會覆蓋後面的)
jvm引數:
如下圖所示,消費者對於api.User這個服務是不通註冊中心,而是直接連線消費者
配置檔案的方式(用於多個介面要處理時,可以配置到單獨的檔案上):
a.建立配置檔案放到${user_home}/ dubbo-resolve.properties(注意不要改名)
${user.home}指的是當前作業系統使用者目錄,如 Win7系統 Administrator的使用者目錄就是 C:\Users\Administrator
b.編輯配置檔案對映
安裝上圖還是會執行20889的原因是因為jvm的配置會覆蓋配置檔案的配置,把-Dapi.User= dubbo://localhost:20889 去除,就會直連20888
標籤reference上
標籤筆記簡單,直接在url中新增就可以,但是要注意的是看看會不會陪前面兩種覆蓋
6.執行緒模型(超連結文章詳細說明)
使用在protocal標籤上 <dubbo:protocol name="dubbo" dispatcher="all" threadpool="fixed" threads="200" />
Dispatcher
all 所有訊息都派發到執行緒池,包括請求,響應,連線事件,斷開事件,心跳等。(預設)
對應相關的類:com.alibaba.dubbo.remoting.transport.dispatcher.all.AllChannelHandler
direct 所有訊息都不派發到執行緒池,全部在 IO 執行緒上直接執行。
對應的類:com.alibaba.dubbo.remoting.transport.dispatcher.direct.DirectDispatcher
message 只有請求響應訊息派發到執行緒池,其它連線斷開事件,心跳等訊息,直接在 IO 執行緒上執行。
對應的類:com.alibaba.dubbo.remoting.transport.dispatcher.message.MessageOnlyDispatcher
execution 只請求訊息派發到執行緒池,不含響應,響應和其它連線斷開事件,心跳等訊息,直接在 IO 執行緒上執行。
對應的類:com.alibaba.dubbo.remoting.transport.dispatcher.execution.ExecutionDispatcher
connection 在 IO 執行緒上,將連線斷開事件放入佇列,有序逐個執行,其它訊息派發到執行緒池。
對應的類:com.alibaba.dubbo.remoting.transport.dispatcher.connection.ConnectionOrderedDispatcher
ThreadPool
-
fixed 固定大小執行緒池,啟動時建立執行緒,不關閉,一直持有。(預設,請不要使用佇列,而是應該尤其呼叫失敗直接訪問其他容錯提供者,而不是等待
)
-
cached 快取執行緒池,空閒一分鐘自動刪除,需要時重建。
-
limited 可伸縮執行緒池,但池中的執行緒數只會增長不會收縮。只增長不收縮的目的是為了避免收縮時突然來了大流量引起的效能問題。
5.負載均衡
用於標籤消費者method、提供者service、consumer、service(前面覆蓋後面)
如:
<dubbo:reference id="user" interface="api.User" timeout="1000" loadbalance="leastactive"/>
random(預設):按權重隨機分配;
roundrobin:輪循負載均衡;會出現錯誤累積問題
leastactive:活躍數指呼叫前後計數差(呼叫前的時刻減去響應後的時刻的值),可以說是最快響應的概率大
一致hash:
4.叢集方式
用於標識reference、service、consumer、provdier (前面覆蓋後面)
方式有:
failover(預設):失敗自動切換其他機器,配合retries=“1”(重試次數,預設3次) 可以用於非冪等的介面如新增型別的介面(重複呼叫可能會有問題,如新增型別介面)
failfast:失敗立即返回錯誤
failsafe:失敗安全,出現異常時,直接忽略。(如超時:在消費者列印錯誤error級別日誌,但不是報錯,介面返回null,然後繼續執行後面的程式碼
)
failback: 失敗自動恢復,後臺記錄失敗請求,定時重發。通常用於訊息通知操作。(也是列印eror日誌,返回null,但會一直重試,直到成功,預設5s週期)
forking:並行呼叫多個伺服器,只要一個成功即返回。通常用於實時性要求較高的讀操作,但需要浪費更多服務資源
3.啟動檢查(啟動消費者檢查提供者、啟動檢查註冊中心) 標籤為check 預設是true(檢查)
注意覆蓋性jvm的覆蓋方法的,方法的覆蓋reference的,reference的覆蓋consumer的
啟動消費者檢查提供者:
可以用在consumer、reference、method、jvm啟動引數( )
啟動檢查註冊中心
用在<duboo:registry check="">,JVM啟動引數中
2.註解實現提供者和消費者
提供者:
a.在配置檔案配置,更spring自動掃描一樣道理
b.在介面實現型別面標註@service(注意是dubbo不是[email protected])
消費者:
a.配置檔案跟提供者一樣
b.消費者用Reference註解,標準在引用的介面上(也就是提供者實現的介面,消費者要有的介面)
1.屬性作用域(timeout等)
a.方法的覆蓋介面的,介面的覆蓋<Service>或<Consumer>
b.Consumer的覆蓋Service的
也就是Consumer的方法> Service的方法>Consumer標籤的>Service標籤的