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.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,第三个null是提供者getUserInfoByName2中获取隐形参数)
----
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(这一次可以获取到RPCContext中的隐性参数)
----
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不是spring的 ,@com.alibaba.dubbo.config.annotation.Service)消费者:a.配置文件跟提供者一样b.消费者用Reference注解,标准在引用的接口上(也就是提供者实现的接口,消费者要有的接口)1.属性作用域(timeout等)a.方法的覆盖接口的,接口的覆盖<Service>或<Consumer>b.Consumer的覆盖Service的也就是Consumer的方法> Service的方法>Consumer标签的>Service标签的