本地伪装
本地伪装通常用于容错处理和服务降级。对于容错处理,我们可以自定义在消费者服务远程调用失败后的返回值,或者向外抛出异常信息。对于服务降级,我们可以屏蔽某些提供者服务,所有消费者调用这些被屏蔽的服务都不会真正发起远程调用,而是直接返回自定义的值或者异常信息。
容错处理
无论是容错处理还是服务降级都是通过在消费者服务的<dubbo:reference />
标签配置mock
属性来实现。
对于容错处理mock
值可以分为三类:指定mock实现类、返回指定值、抛出异常
mock实现类
package com.foo;
public class BarServiceMock implements BarService {
public String sayHello(String name) {
// 你可以伪造容错数据,此方法只在出现RpcException时被执行
return "容错数据";
}
}
<dubbo:reference interface="com.foo.BarService" mock="true" />
或
<dubbo:reference interface="com.foo.BarService" mock="com.foo.BarServiceMock" />
返回指定值
<dubbo:reference interface="com.foo.BarService" mock="return null" />
使用return
返回一个字符串对象表示的对象,来作为Mock
的返回值。合法的字符串可以是:
- empty: 代表空,基本类型的默认值,或者集合类的空值
- null: null
- true: true
- false: false
- JSON 格式: 反序列化 JSON 所得到的对象
抛出异常
<dubbo:reference id="userService" interface="com.luke.dubbo.api.service.UserService"
mock="throw java.lang.IllegalAccessException"/>
使用throw
指定抛出的异常,如果只有throw
,将抛出一个默认的RPCException
。
服务降级
在2.6.6以上的版本,可以使用fail
和force
来指定是容错处理还是服务降级,force
和 fail
都支持与throw
或者return
组合使用,所以对于容错处理,在返回指定值和抛出异常前面加上fail
效果一样,都是当消费者服务远程调用发生失败后做对应的Mock
处理。而与之相反的服务降级使用force
则完全屏蔽对应的提供者服务,不发起远程调用,直接执行Mock
处理。
返回指定值
<dubbo:reference interface="com.foo.BarService" mock="force:return fake" />
抛出异常
<dubbo:reference interface="com.foo.BarService" mock="force:throw com.foo.MockException" />
方法级配置Mock
容错处理和服务降级也可以在方法级别进行配置,例如:
<dubbo:reference id="demoService" check="false" interface="com.foo.BarService">
<dubbo:parameter key="sayHello.mock" value="force:return fake"/>
</dubbo:reference>
参数回调
参数回调类似于本地的Callback
或listener
机制,但Callback
并不是Dubbo
内部提供的类或接口,而是由提供者服务端自己定义的,Dubbo
将基于长连接生成反向代理,从而实现从提供者服务调用消费者服务的逻辑。在我们常见的应用场景中经常会出现一些需要回调的场景,比如在调用提供者服务端的某个rpc接口之后需要重新调用到消费者服务,也就是需要调用到消费者服务的某个接口用来进行回调通知。下图是参数回调的原理图:
参数回调的最大特点就是服务接口包含了一个监听器接口类型参数,消费者服务发起远程调用时,需要传递该监听器接口类型参数的实例,由于消费者服务和提供者服务保持长连接,所以提供者服务可以随时发起多次的callback
(回调),消费者服务的监听器实现类则负责处理接收到参数回调后的业务逻辑。
1、定义监听器接口。
public interface CallbackListener {
void changed(String msg);
}
2、定义服务接口,服务接口包含监听器类型入参。
public interface CallbackService {
void addListener(String key, CallbackListener listener);
}
3、提供者服务实现服务接口,并负责发起参数回调。
public class CallbackServiceImpl implements CallbackService {
private final Map<String, List<CallbackListener>> listeners = new ConcurrentHashMap<>();
public CallbackServiceImpl() {
Thread t = new Thread(() ->{
while(true) {
try {
System.out.println("listeners:"+listeners);
for(Map.Entry<String, List<CallbackListener>> entry : listeners.entrySet()){
try {
List<CallbackListener> callbackListeners = entry.getValue();
callbackListeners.forEach((callbackListener) -> {
callbackListener.changed(getChanged(entry.getKey()));
});
} catch (Throwable t1) {
listeners.remove(entry.getKey());
}
}
Thread.sleep(5000); // 定时触发变更通知
} catch (Throwable t2) { // 防御容错
t2.printStackTrace();
}
}
});
t.setDaemon(true);
t.start();
}
@Override
public void addListener(String key, CallbackListener listener) {
System.out.println("---addListener,key:"+key+",listener:"+listener);
if(listeners.containsKey(key)){
listeners.get(key).add(listener);
}else{
List<CallbackListener> list = new ArrayList<CallbackListener>();
list.add(listener);
listeners.put(key, list);
//listener.changed(getChanged(key)); // 发送变更通知
}
}
private String getChanged(String key) {
return "Changed: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
}
}
<dubbo:service interface="com.luke.dubbo.api.service.CallbackService"
ref="callbackService" connections="2" callbacks="0">
<dubbo:method name="addListener">
<dubbo:argument index="1" callback="true" />
<!--也可以通过指定类型的方式-->
<!--<dubbo:argument type="com.demo.CallbackListener" callback="true" />-->
</dubbo:method>
</dubbo:service>
4、消费者服务引用服务接口,并处理参数回调。
<dubbo:reference id="callbackService" interface="com.luke.dubbo.api.service.CallbackService" />
CallbackService callbackService = (CallbackService) context.getBean("callbackService");
callbackService.addListener("foo.bar", new CallbackListener(){
public void changed(String msg) {
//处理参数回调业务逻辑
System.out.println("callback1:" + msg);
}
});
执行结果:
callback1:Changed: 2020-03-31 13:43:08
callback1:Changed: 2020-03-31 13:43:13
callback1:Changed: 2020-03-31 13:43:18
callback1:Changed: 2020-03-31 13:43:23
callback1:Changed: 2020-03-31 13:43:28
callback1:Changed: 2020-03-31 13:43:33
事件通知
Dubbo提供了事件通知的功能,能够让用户处理消费者服务远程调用过程中出现的三个事件:oninvoke
(调用前)、onreturn
(调用后)、onthrow
(出现异常)。
事件通知的处理逻辑全部在消费者服务,对于提供者服务来说就是一个普通的服务暴露。
提供者服务
public interface IDemoService {
public Person get(int id);
}
public class MyDemoService implements IDemoService {
@Override
public Person get(int id) {
return new Person("luke"+id);
}
}
<bean id="myDemoService" class="com.luke.dubbo.user.service.impl.MyDemoService"/>
<dubbo:service interface="com.luke.dubbo.api.service.IDemoService" ref="myDemoService"/>
消费者服务
消费者服务需要根据服务接口IDemoService
的入参和出参自定义事件通知处理类。
public interface Notify {
/**方法参数:出参和入参*/
public void onreturn(Person msg,Integer id);
/**方法参数:异常和入参*/
public void onthrow(Throwable ex, Integer id);
}
public class NotifyImpl implements Notify{
public Map<Integer, Person> ret = new HashMap<Integer, Person>();
public Map<Integer, Throwable> errors = new HashMap<Integer, Throwable>();
@Override
public void onreturn(Person msg,Integer id) {
System.out.println("onreturn:" + msg);
ret.put(id, msg);
}
@Override
public void onthrow(Throwable ex, Integer id) {
errors.put(id, ex);
}
}
- oninvoke方法:
必须具有与服务接口相同的入参列表,例如:
oninvoke(Integer id)
。
- onreturn方法:
至少要有一个入参且第一个入参必须与服务接口的返回类型相同,用于接收返回结果,例如:
onreturnWithoutParam(Person msg)
。可以有多个参数,多个参数的情况下,第一个参数对应服务接口返回值类型,后面参数对应服务接口入参,例如:onreturn(Person msg,Integer id)
。
- onthrow方法:
至少要有一个入参且第一个入参类型为
Throwable
或其子类,用于接收异常结果;例如,onthrow(Throwable ex)
。可以有多个参数,多个参数的情况下,第一个参数为Throwable或其子类,后面参数对应服务接口入参:例如,onthrow(Throwable ex, Integer id)
。
<dubbo:reference/>
引用远程接口服务时需要使用oninvoke、onreturn、onthrow
标签属性指定事件通知对应的回调方法。
<!--event callback 事件通知-->
<bean id="notify" class="com.luke.dubbo.order.service.impl.event.NotifyImpl" />
<dubbo:reference id="demoService" interface="com.luke.dubbo.api.service.IDemoService">
<dubbo:method name="get" async="true"
onreturn="notify.onreturn" onthrow="notify.onthrow"/>
</dubbo:reference>
同时我们还可以配合async
属性声明调用过程是同步还是异步,默认是同步方式,即async="false"
。
测试代码
//事件回调
IDemoService demoService = (IDemoService)context.getBean("demoService");
NotifyImpl notify = (NotifyImpl)context.getBean("notify");
int requestId = 2;
Person person = demoService.get(requestId);
System.out.println("person:"+person);
while (true){
if (!notify.ret.containsKey(requestId)) {
Thread.sleep(200);
} else {
break;
}
}
System.out.println("result:"+notify.ret.get(requestId));
执行测试
对于异步调用方式即async="true"
,测试结果:
person:null
onreturn:Person{name='luke2'}
result:Person{name='luke2'}
对于同步调用方式即async="false"
,测试结果:
onreturn:Person{name='luke2'}
person:Person{name='luke2'}
result:Person{name='luke2'}