1负载均衡
提供了多种负载均衡策略,缺省为random随机调用
负载均衡策略:
1)Random LoadBalance(随机)
2)RoundRobin (轮询)
3)LeastActive(最少活跃调用数)
4)ConsistentHash(一致性) 参数hash值一样,调用的是同一台机器
2集群容错
重试是在消费端发起的
超时报错了,服务端时间是3s,消费端配置的是1s超时,重试设置的是2次。服务端会执行3次,每次都会返回,但是消费端已经超时,连接已经没有了所以返回了也没用
提供了多种集群容错策略
集群容错策略:
1)Failover Cluster(失败自动切换)
失败自动切换,当出现失败,重试其他服务器。重试会带来更长延迟,可通过retries="2"来设置重试次数(不含第一次)
2)Failfast Cluster(快速失败)
3)Failsafe Cluster(失败安全)
出现异常时,直接忽略。通常用于写入审计日志等操作。
4)Failback Cluster(失败自动恢复)
后台记录失败请求,定时重发。通常用于消息通知操作。
5)Forking Cluster(并行调用多个服务,只要一个成功即返回)
通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过forks="2"来设置最大并行数
6)Broadcast Cluster(广播调用所有提供者)
逐个调用,任意一台报错即报错。通常用于通知所有提供者更新缓存或日志等本地资源信息
3服务降级
mock是stub的一个子集
在服务消费者注册的地方配置mock参数,mock=true走默认的规则(HelloWorldService对应HelloWorldServiceMock),也可以用mock=类的全路径制定实现类来处理。
可以通过服务降级功能 [1] 临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。
RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&application=foo&mock=force:return+null"));
1)mock=force:return+null 表示消费方对该服务的方法调用都直接返回 null值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。
2)还可以改为 mock=fail:return+null 表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。
4本地存根
本地存根是在消费端执行的
远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,比如:做 ThreadLocal 缓存,提前验证参数,调用失败后伪造容错数据等等,此时就需要在 API 中带上 Stub,客户端生成 Proxy 实例,会把 Proxy 通过构造函数传给 Stub [1],然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy。
在服务消费者注册的地方配置stub参数,stub=true走默认的规则(HelloWorldService对应HelloWorldServiceStub),也可以用stub=类的全路径制定实现类来提供 Stub 的实现:
package com.foo;
public class BarServiceStub implements BarService {
private final BarService barService;
// 构造函数传入真正的远程代理对象
public BarServiceStub(BarService barService){
this.barService = barService;
}
public String sayHello(String name) {
// 此代码在客户端执行, 你可以在客户端做ThreadLocal本地缓存,或预先验证参数是否合法,等等
try {
return barService.sayHello(name);
} catch (Exception e) {
// 你可以容错,可以做任何AOP拦截事项
return "容错数据";
}
}
}
1)Stub 必须有可传入 Proxy 的构造函数
2)在 interface 旁边放一个 Stub 实现,它实现 BarService 接口,并有一个传入远程 BarService 实例的构造函数
5本地伪装
本地伪装 [1] 通常用于服务降级,比如某验权服务,当服务提供方全部挂掉后,客户端不抛出异常,而是通过 Mock 数据返回授权失败。
在 spring 配置文件中按以下方式配置:
<dubbo:reference interface="com.foo.BarService" mock="true" />
或
<dubbo:reference interface="com.foo.BarService" mock="com.foo.BarServiceMock" />
在工程中提供 Mock 实现 [2]:
package com.foo;
public class BarServiceMock implements BarService {
public String sayHello(String name) {
// 你可以伪造容错数据,此方法只在出现RpcException时被执行
return "容错数据";
}
}
如果服务的消费方经常需要 try-catch 捕获异常,如:
Offer offer = null;
try {
offer = offerService.findOffer(offerId);
} catch (RpcException e) {
logger.error(e);
}
请考虑改为 Mock 实现,并在 Mock 实现中 return null。如果只是想简单的忽略异常,在 2.0.11
以上版本可用:
<dubbo:reference interface="com.foo.BarService" mock="return null" />
进阶用法
return
使用 return
来返回一个字符串表示的对象,作为 Mock 的返回值。合法的字符串可以是:
- empty: 代表空,基本类型的默认值,或者集合类的空值
- null:
null
- true:
true
- false:
false
- JSON 格式: 反序列化 JSON 所得到的对象
throw
使用 throw
来返回一个 Exception 对象,作为 Mock 的返回值。
当调用出错时,抛出一个默认的 RPCException:
<dubbo:reference interface="com.foo.BarService" mock="throw" />
当调用出错时,抛出指定的 Exception:
<dubbo:reference interface="com.foo.BarService" mock="throw com.foo.MockException" />
force 和 fail
在 2.6.6
以上的版本,可以开始在 Spring XML 配置文件中使用 fail:
和 force:
。force:
代表强制使用 Mock 行为,在这种情况下不会走远程调用。fail:
与默认行为一致,只有当远程调用发生错误时才使用 Mock 行为。force:
和 fail:
都支持与 throw
或者 return
组合使用。
强制返回指定值:
<dubbo:reference interface="com.foo.BarService" mock="force:return fake" />
强制抛出指定异常:
<dubbo:reference interface="com.foo.BarService" mock="force:throw com.foo.MockException" />
在方法级别配置 Mock
Mock 可以在方法级别上指定,假定 com.foo.BarService
上有好几个方法,我们可以单独为 sayHello()
方法指定 Mock 行为。具体配置如下所示,在本例中,只要 sayHello()
被调用到时,强制返回 "fake":
<dubbo:reference id="demoService" check="false" interface="com.foo.BarService">
<dubbo:parameter key="sayHello.mock" value="force:return fake"/>
</dubbo:reference>
-
Mock 是 Stub 的一个子集,便于服务提供方在客户端执行容错逻辑,因经常需要在出现 RpcException (比如网络失败,超时等)时进行容错,而在出现业务异常(比如登录用户名密码错误)时不需要容错,如果用 Stub,可能就需要捕获并依赖 RpcException 类,而用 Mock 就可以不依赖 RpcException,因为它的约定就是只有出现 RpcException 时才执行。 ↩︎
-
在 interface 旁放一个 Mock 实现,它实现 BarService 接口,并有一个无参构造函数
6CallBack
服务回调方法是消费者实现的
参数回调方式与调用本地 callback 或 listener 相同,只需要在 Spring 的配置文件中声明哪个参数是 callback 类型即可。Dubbo 将基于长连接生成反向代理,这样就可以从服务器端调用客户端逻辑 [1]。可以参考 dubbo 项目中的示例代码。
服务接口示例
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);
}
服务提供者接口实现示例
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());
}
}
服务提供者配置示例
<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>
服务消费者配置示例
<dubbo:reference id="callbackService" interface="com.callback.CallbackService" />
服务消费者调用示例
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);
}
});
-
2.0.6
及其以上版本支持
7异步调用
从v2.7.0开始,Dubbo的所有异步编程接口开始以CompletableFuture为基础
基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小。
使用CompletableFuture签名的接口
需要服务提供者事先定义CompletableFuture签名的服务,具体参见服务端异步执行接口定义:
public interface AsyncService {
CompletableFuture<String> sayHello(String name);
}
注意接口的返回类型是CompletableFuture<String>
。
XML引用服务:
<dubbo:reference id="asyncService" timeout="10000" interface="com.alibaba.dubbo.samples.async.api.AsyncService"/>
调用远程服务:
// 调用直接返回CompletableFuture
CompletableFuture<String> future = asyncService.sayHello("async call request");
// 增加回调
future.whenComplete((v, t) -> {
if (t != null) {
t.printStackTrace();
} else {
System.out.println("Response: " + v);
}
});
// 早于结果输出
System.out.println("Executed before response return.");
使用RpcContext
在 consumer.xml 中配置:
<dubbo:reference id="asyncService" interface="org.apache.dubbo.samples.governance.api.AsyncService">
<dubbo:method name="sayHello" async="true" />
</dubbo:reference>
调用代码:
// 此调用会立即返回null
asyncService.sayHello("world");
// 拿到调用的Future引用,当结果返回后,会被通知和设置到此Future
CompletableFuture<String> helloFuture = RpcContext.getContext().getCompletableFuture();
// 为Future添加回调
helloFuture.whenComplete((retValue, exception) -> {
if (exception == null) {
System.out.println(retValue);
} else {
exception.printStackTrace();
}
});
或者,你也可以这样做异步调用:
CompletableFuture<String> future = RpcContext.getContext().asyncCall(
() -> {
asyncService.sayHello("oneway call request1");
}
);
future.get();
重载服务接口
如果你只有这样的同步服务定义,而又不喜欢RpcContext的异步使用方式。
public interface GreetingsService {
String sayHi(String name);
}
那还有一种方式,就是利用Java 8提供的default接口实现,重载一个带有带有CompletableFuture签名的方法。
有两种方式来实现:
- 提供方或消费方自己修改接口签名
public interface GreetingsService {
String sayHi(String name);
// AsyncSignal is totally optional, you can use any parameter type as long as java allows your to do that.
default CompletableFuture<String> sayHi(String name, AsyncSignal signal) {
return CompletableFuture.completedFuture(sayHi(name));
}
}
- Dubbo官方提供compiler hacker,编译期自动重写同步方法,请在此讨论和跟进具体进展。
你也可以设置是否等待消息发出: [1]
sent="true"
等待消息发出,消息发送失败将抛出异常。sent="false"
不等待消息发出,将消息放入 IO 队列,即刻返回。
<dubbo:method name="findFoo" async="true" sent="true" />
如果你只是想异步,完全忽略返回值,可以配置 return="false"
,以减少 Future 对象的创建和管理成本:
<dubbo:method name="findFoo" async="true" return="false" />
-
异步总是不等待返回
8泛化调用
泛化接口调用方式主要用于客户端没有 API 接口及模型类元的情况,参数及返回值中的所有 POJO 均用 Map
表示,通常用于框架集成,比如:实现一个通用的服务测试框架,可通过 GenericService
调用所有服务实现。
通过 Spring 使用泛化调用
在 Spring 配置申明 generic="true"
:
<dubbo:reference id="barService" interface="com.foo.BarService" generic="true" />
在 Java 代码获取 barService 并开始泛化调用:
GenericService barService = (GenericService) applicationContext.getBean("barService");
Object result = barService.$invoke("sayHello", new String[] { "java.lang.String" }, new Object[] { "World" });
通过 API 方式使用泛化调用
import org.apache.dubbo.rpc.service.GenericService;
...
// 引用远程服务
// 该实例很重量,里面封装了所有与注册中心及服务提供方连接,请缓存
ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();
// 弱类型接口名
reference.setInterface("com.xxx.XxxService");
reference.setVersion("1.0.0");
// 声明为泛化接口
reference.setGeneric(true);
// 用org.apache.dubbo.rpc.service.GenericService可以替代所有接口引用
GenericService genericService = reference.get();
// 基本类型以及Date,List,Map等不需要转换,直接调用
Object result = genericService.$invoke("sayHello", new String[] {"java.lang.String"}, new Object[] {"world"});
// 用Map表示POJO参数,如果返回值为POJO也将自动转成Map
Map<String, Object> person = new HashMap<String, Object>();
person.put("name", "xxx");
person.put("password", "yyy");
// 如果返回POJO将自动转成Map
Object result = genericService.$invoke("findPerson", new String[]
{"com.xxx.Person"}, new Object[]{person});
...
有关泛化类型的进一步解释
假设存在 POJO 如:
package com.xxx;
public class PersonImpl implements Person {
private String name;
private String password;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
则 POJO 数据:
Person person = new PersonImpl();
person.setName("xxx");
person.setPassword("yyy");
可用下面 Map 表示:
Map<String, Object> map = new HashMap<String, Object>();
// 注意:如果参数类型是接口,或者List等丢失泛型,可通过class属性指定类型。
map.put("class", "com.xxx.PersonImpl");
map.put("name", "xxx");
map.put("password", "yyy");
泛接口实现方式主要用于服务器端没有API接口及模型类元的情况,参数及返回值中的所有POJO均用Map表示,通常用于框架集成,比如:实现一个通用的远程服务Mock框架,可通过实现GenericService接口处理所有服务请求。
在 Java 代码中实现 GenericService
接口:
package com.foo;
public class MyGenericService implements GenericService {
public Object $invoke(String methodName, String[] parameterTypes, Object[] args) throws GenericException {
if ("sayHello".equals(methodName)) {
return "Welcome " + args[0];
}
}
}
通过 Spring 暴露泛化实现
在 Spring 配置申明服务的实现:
<bean id="genericService" class="com.foo.MyGenericService" />
<dubbo:service interface="com.foo.BarService" ref="genericService" />
通过 API 方式暴露泛化实现
...
// 用org.apache.dubbo.rpc.service.GenericService可以替代所有接口实现
GenericService xxxService = new XxxGenericService();
// 该实例很重量,里面封装了所有与注册中心及服务提供方连接,请缓存
ServiceConfig<GenericService> service = new ServiceConfig<GenericService>();
// 弱类型接口名
service.setInterface("com.xxx.XxxService");
service.setVersion("1.0.0");
// 指向一个通用服务实现
service.setRef(xxxService);
// 暴露及注册服务
service.export();
9管理台
目前的管理控制台已经发布0.1版本,结构上采取了前后端分离的方式,前端使用Vue和Vuetify分别作为Javascript框架和UI框架,后端采用Spring Boot框架。既可以按照标准的Maven方式进行打包,部署,也可以采用前后端分离的部署方式,方便开发,功能上,目前具备了服务查询,服务治理(包括Dubbo2.7中新增的治理规则)以及服务测试三部分内容。
Maven方式部署
- 安装
git clone https://github.com/apache/dubbo-admin.git
cd dubbo-admin
mvn clean package
cd dubbo-admin-distribution/target
java -jar dubbo-admin-0.1.jar
前后端分离部署
- 前端
cd dubbo-admin-ui
npm install
npm run dev
- 后端
cd dubbo-admin-server
mvn clean package
cd target
java -jar dubbo-admin-server-0.1.jar
- 访问
http://localhost:8081 - 前后端分离模式下,前端的修改可以实时生效
配置: [1]
配置文件为:
dubbo-admin-server/src/main/resources/application.properties
主要的配置有:
admin.config-center=zookeeper://127.0.0.1:2181
admin.registry.address=zookeeper://127.0.0.1:2181
admin.metadata-report.address=zookeeper://127.0.0.1:2181
三个配置项分别指定了配置中心,注册中心和元数据中心的地址,关于这三个中心的详细说明,可以参考这里。 也可以和Dubbo2.7一样,在配置中心指定元数据和注册中心的地址,以zookeeper为例,配置的路径和内容如下:
# /dubbo/config/dubbo/dubbo.properties
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.metadata-report.address=zookeeper://127.0.0.1:2181
配置中心里的地址会覆盖掉本地application.properties
的配置
其他配置请访问github中的文档:
https://github.com/apache/dubbo-admin
-
当前版本中未实现登录功能,会在后续版本加上